about summary refs log tree commit diff stats
path: root/type.lua
blob: 0d3b77620544f86710e42727665d2f1ca6e53e0f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
--- lam.type
-- lisp types

local t = {}
local util = require "util"
local unpack = table.unpack or unpack

--- Determining types

t.luatype = type

function t.lamtype (x)
	if t.luatype(x) == "number" then
		return "Number"
	elseif t.luatype(x) == "string" then
		return "Symbol"
	elseif getmetatable(x) and getmetatable(x).__type then
		return getmetatable(x).__type
	else
		return t.luatype(x)
	end
end

-- isa is really only useful on basic types (i.e., not Lists)
function t.isa (x, type)
	return t.lamtype(x) == type
end

--- Creating types

-- Symbols and Numbers are strings and numbers, respectively.  At some point
-- I'll want to implement a full numeric tower and symbol tables or namespaces
-- or whatever, but today is not that day

t.Symbol = tostring
t.Number = tonumber

-- Strings are (lightly) wrapped
function t.String (str)
	local v = {
		value = str,
		escape =
			function (self)
				return self.gsub("[\\\"]", "\\%1")
			end,
	}
	local mt = {
		__type = "String",
		__tostring =
			function (self)
				return string.format("\"%s\"", self:escape())
			end,
	}
	return setmetatable(v, mt)
end

function t.totable (cons)
	local out = {}
	local car, cdr = cons.car, cons.cdr
	while cdr do
		table.insert(out, tostring(car))
		if t.luatype(cdr) == "table" then
			car = cdr.car
			cdr = cdr.cdr
		else
			table.insert(out, cdr)
			break
		end
	end
	return out
end

-- Conses are Lisp's fundamental collection type
function t.Cons (a, b)
	local v = { a, b, }
	local mt = {
		__type = "Cons",
		__index =
			function (self, key)
				if key == "car" then
					return self[1]
				elseif key == "cdr" then
					return self[2]
				end
			end,
		__tostring =
			function (self)
				local out = {}
				local car, cdr = self.car, self.cdr
				while cdr do
					table.insert(out, tostring(car))
					if t.luatype(cdr) == "table" then
						car = cdr.car
						cdr = cdr.cdr
					else
						table.insert(out, ".")
						table.insert(out, cdr)
						break
					end
				end
				return "("..table.concat(out, " ")..")"
			end,
	}
	return setmetatable(v, mt)
end

-- Null is the one value that is both an atom and a list
t.Null = setmetatable({}, {
		__type = "Null",
		__tostring = function (self) return "()" end,
})

function t.isNull (x)
	return x == t.Null
end

-- Lists are chained Conses ending in Null
function t.List (items)
	local function tolist (base, items)
		if #items == 0 then return base end
		return tolist(t.Cons(table.remove(items), base), items)
	end
	return tolist(t.Null, items)
end

function t.isList (x)
	-- TODO: this does not detect circular lists yet
	if t.isNull(x) then
		return true
	elseif t.isa(x, "Cons") then
		return t.isList(x.cdr)
	else
		return false
	end
end

---
return t