diff options
author | Case Duckworth | 2024-03-30 22:20:36 -0500 |
---|---|---|
committer | Case Duckworth | 2024-03-30 22:20:36 -0500 |
commit | ab8a02fd30451207578927c7e69aa397ad596459 (patch) | |
tree | 24803910776ed692f1610f44e35d0f23b9712ca3 /type.lua | |
parent | Special-case '.', '...', '+', '-' (diff) | |
download | lam-ab8a02fd30451207578927c7e69aa397ad596459.tar.gz lam-ab8a02fd30451207578927c7e69aa397ad596459.zip |
Read from ports now
Diffstat (limited to 'type.lua')
-rw-r--r-- | type.lua | 202 |
1 files changed, 113 insertions, 89 deletions
diff --git a/type.lua b/type.lua index 0a0c62d..3c26188 100644 --- a/type.lua +++ b/type.lua | |||
@@ -1,138 +1,162 @@ | |||
1 | --- lam.type | 1 | --- lam.type |
2 | -- lisp types | ||
3 | 2 | ||
4 | local t = {} | 3 | local m = {} |
5 | local util = require "util" | 4 | local utf8 = require "utf8" |
6 | local unpack = table.unpack or unpack | 5 | utf_char, utf_codepoint = utf8.char, utf8.codepoint |
7 | 6 | ||
8 | --- Determining types | 7 | --- atomic types |
9 | 8 | ||
10 | t.luatype = type | 9 | -- true, false and nil are just ... true, false, and nil |
11 | 10 | ||
12 | function t.lamtype (x) | 11 | -- Characters contain both their string reputations and their codepoints |
13 | if t.luatype(x) == "number" then | 12 | function m.character (x) |
14 | return "Number" | 13 | -- is storing a character with its string and numerical representation |
15 | elseif t.luatype(x) == "string" then | 14 | -- overkill? ... maybe. |
16 | return "Symbol" | 15 | local s = tostring(x) |
17 | elseif getmetatable(x) and getmetatable(x).__type then | 16 | local uc = utf_codepoint(s) |
18 | return getmetatable(x).__type | 17 | local t = { -- String representation of the character |
19 | else | 18 | v = utf_char(uc), |
20 | return t.luatype(x) | 19 | u = uc, |
21 | end | 20 | } |
22 | end | 21 | local mt = { |
23 | 22 | __type = "character", | |
24 | -- isa is really only useful on basic types (i.e., not Lists) | 23 | __eq = function (self) return self.v end, |
25 | function t.isa (x, type) | 24 | __lt = function (a, b) return a.u < b.u end, |
26 | return t.lamtype(x) == type | 25 | __tostring = |
26 | function (self) | ||
27 | local v = self.v | ||
28 | if v == "\n" then | ||
29 | return "#\\newline" | ||
30 | elseif v == " " then | ||
31 | return "#\\space" | ||
32 | else | ||
33 | return "#\\" .. v | ||
34 | end | ||
35 | end, | ||
36 | } | ||
37 | return setmetatable(t, mt) | ||
27 | end | 38 | end |
28 | 39 | ||
29 | --- Creating types | 40 | -- a symbol is just a string, unadorned. I was going to have a character be |
30 | 41 | -- represented by a one-character string, but then it would be indistinguishable | |
31 | -- Symbols and Numbers are strings and numbers, respectively. At some point | 42 | -- from a one-character symbol internally. |
32 | -- I'll want to implement a full numeric tower and symbol tables or namespaces | 43 | m.symbol = tostring |
33 | -- or whatever, but today is not that day | ||
34 | 44 | ||
35 | t.Symbol = tostring | 45 | -- for now, number will just be lua's number. At *some* point, it will be the |
36 | t.Number = tonumber | 46 | -- whole numeric tower, yaaayyy |
47 | m.number = tonumber | ||
37 | 48 | ||
38 | -- Strings are (lightly) wrapped | 49 | -- strings are wrapped strings |
39 | function t.String (str) | 50 | function m.string (x) |
40 | local v = { | 51 | local x = tostring(x) |
41 | value = str, | 52 | local t = { |
53 | v = x, | ||
42 | escape = | 54 | escape = |
43 | function (self) | 55 | function (self) |
44 | return self.gsub("[\\\"]", "\\%1") | 56 | return self.v:gsub("[\\\"]", "\\%1") |
45 | end, | 57 | end, |
46 | } | 58 | } |
47 | local mt = { | 59 | local mt = { |
48 | __type = "String", | 60 | __type = "string", |
49 | __tostring = | 61 | __tostring = |
50 | function (self) | 62 | function (self) |
51 | return string.format("\"%s\"", self:escape()) | 63 | return "\"" .. self:escape() .. "\"" |
52 | end, | 64 | end, |
53 | } | 65 | } |
54 | return setmetatable(v, mt) | 66 | return setmetatable(t, mt) |
55 | end | 67 | end |
56 | 68 | ||
57 | function t.totable (cons) | 69 | -- null () is both an atom and a list (yay) |
58 | local out = {} | 70 | -- this one is NOT a function |
59 | local car, cdr = cons.car, cons.cdr | 71 | m.null = setmetatable({}, { |
60 | while cdr do | 72 | __type = "null", |
61 | table.insert(out, tostring(car)) | 73 | __tostring = function (self) return "()" end, |
62 | if t.luatype(cdr) == "table" then | 74 | }) |
63 | car = cdr.car | 75 | |
64 | cdr = cdr.cdr | 76 | --- collection types |
65 | else | ||
66 | table.insert(out, cdr) | ||
67 | break | ||
68 | end | ||
69 | end | ||
70 | return out | ||
71 | end | ||
72 | 77 | ||
73 | -- Conses are Lisp's fundamental collection type | 78 | -- cons are lisp's fundamental collection type |
74 | function t.Cons (a, b) | 79 | function m.cons (a, b) |
75 | local v = { a, b, } | 80 | local t = { a, b, } |
76 | local mt = { | 81 | local mt = { |
77 | __type = "Cons", | 82 | __type = "cons", |
78 | __index = | ||
79 | function (self, key) | ||
80 | if key == "car" then | ||
81 | return self[1] | ||
82 | elseif key == "cdr" then | ||
83 | return self[2] | ||
84 | end | ||
85 | end, | ||
86 | __tostring = | 83 | __tostring = |
87 | function (self) | 84 | function (self) |
88 | local out = {} | 85 | local out = {} |
89 | local car, cdr = self.car, self.cdr | 86 | local car, cdr = self[1], self[2] |
90 | while cdr do | 87 | while cdr do |
91 | table.insert(out, tostring(car)) | 88 | table.insert(out, tostring(car)) |
92 | if t.luatype(cdr) == "table" then | 89 | if m.luatype(cdr) == "table" then |
93 | car = cdr.car | 90 | car = cdr[1] |
94 | cdr = cdr.cdr | 91 | cdr = cdr[2] |
95 | else | 92 | else |
96 | table.insert(out, ".") | 93 | table.insert(out, ".") |
97 | table.insert(out, cdr) | 94 | table.insert(out, cdr) |
98 | break | 95 | break |
99 | end | 96 | end |
100 | end | 97 | end |
101 | return "("..table.concat(out, " ")..")" | 98 | return "(" .. table.concat(out, " ") .. ")" |
102 | end, | 99 | end, |
103 | } | 100 | } |
104 | return setmetatable(v, mt) | 101 | return setmetatable(t, mt) |
105 | end | 102 | end |
106 | 103 | ||
107 | -- Null is the one value that is both an atom and a list | 104 | -- lists are singly-linked cons cells |
108 | t.Null = setmetatable({}, { | 105 | function m.list (items, last) |
109 | __type = "Null", | 106 | -- ITEMS is a table and LAST is an optional final cdr. If it's nil, the |
110 | __tostring = function (self) return "()" end, | 107 | -- list is a "proper" list; that is, it ends in (). |
111 | }) | 108 | local function tolist (base, items) |
109 | if #items == 0 then return base end | ||
110 | return tolist(m.cons(table.remove(items), base), items) | ||
111 | end | ||
112 | return tolist(last or m.null, items) | ||
113 | end | ||
112 | 114 | ||
113 | function t.isNull (x) | 115 | -- convert a list to a lua table |
114 | return x == t.Null | 116 | function m.totable (cons) |
117 | local t = {} | ||
118 | local car, cdr = cons[1], cons[2] | ||
119 | while cdr do | ||
120 | table.insert(t, car) | ||
121 | if m.luatype(cdr) == "table" then | ||
122 | car = cdr[1] | ||
123 | cdr = cdr[2] | ||
124 | else | ||
125 | table.insert(t, cdr) | ||
126 | end | ||
127 | end | ||
128 | return t | ||
115 | end | 129 | end |
116 | 130 | ||
117 | -- Lists are chained Conses ending in Null | 131 | -- testing types |
118 | function t.List (items, last) | 132 | |
119 | local function tolist (base, items) | 133 | -- we love name collisions |
120 | if #items == 0 then return base end | 134 | m.luatype = type |
121 | return tolist(t.Cons(table.remove(items), base), items) | 135 | |
136 | function m.lamtype (x) | ||
137 | if m.luatype(x) == "string" then | ||
138 | return "symbol" | ||
139 | elseif getmetatable(x) and getmetatable(x).__type then | ||
140 | return getmetatable(x).__type | ||
141 | else | ||
142 | return m.luatype(x) | ||
122 | end | 143 | end |
123 | return tolist(last or t.Null, items) | ||
124 | end | 144 | end |
125 | 145 | ||
126 | function t.isList (x) | 146 | function m.isa (x, t) |
127 | -- TODO: this does not detect circular lists yet | 147 | return m.lamtype(x) == t |
128 | if t.isNull(x) then | 148 | end |
149 | |||
150 | function m.islist (x) | ||
151 | -- TODO: detect circular lists | ||
152 | if x == m.null then | ||
129 | return true | 153 | return true |
130 | elseif t.isa(x, "Cons") then | 154 | elseif m.isa(x, "cons") then |
131 | return t.isList(x.cdr) | 155 | return m.islist(x[2]) |
132 | else | 156 | else |
133 | return false | 157 | return false |
134 | end | 158 | end |
135 | end | 159 | end |
136 | 160 | ||
137 | --- | 161 | -------- |
138 | return t | 162 | return m |