/
types.moon
197 lines (165 loc) · 4 KB
/
types.moon
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
util = require "moonscript.util"
import Set from require "moonscript.data"
import insert from table
import unpack from util
-- implicit return does not work on these statements
manual_return = Set {
"foreach", "for", "while", "return"
}
-- Assigns and returns are bubbled into their bodies.
-- All cascading statement transform functions accept a second arugment that
-- is the transformation to apply to the last statement in their body
cascading = Set {
"if", "unless", "with", "switch", "class", "do"
}
terminating = Set {
"return", "break"
}
-- type of node as string
ntype = (node) ->
switch type node
when "nil"
"nil"
when "table"
node[1]
else
"value"
-- gets the class of a type if possible
mtype = do
moon_type = util.moon.type
-- lets us check a smart node without throwing an error
(val) ->
mt = getmetatable val
return "table" if mt and mt.smart_node
moon_type val
-- can this value be compiled in a line by itself
value_can_be_statement = (node) ->
return false unless ntype(node) == "chain"
-- it's a function call
ntype(node[#node]) == "call"
is_value = (stm) ->
compile = require "moonscript.compile"
transform = require "moonscript.transform"
compile.Block\is_value(stm) or transform.Value\can_transform stm
value_is_singular = (node) ->
type(node) != "table" or node[1] != "exp" or #node == 2
is_slice = (node) ->
ntype(node) == "chain" and ntype(node[#node]) == "slice"
t = {}
node_types = {
class: {
{"name", "Tmp"}
{"body", t}
}
fndef: {
{"args", t}
{"whitelist", t}
{"arrow", "slim"}
{"body", t}
}
foreach: {
{"names", t}
{"iter"}
{"body", t}
}
for: {
{"name"}
{"bounds", t}
{"body", t}
}
while: {
{"cond", t}
{"body", t}
}
assign: {
{"names", t}
{"values", t}
}
declare: {
{"names", t}
}
if: {
{"cond", t}
{"then", t}
}
}
build_table = ->
key_table = {}
for node_name, args in pairs node_types
index = {}
for i, tuple in ipairs args
prop_name = tuple[1]
index[prop_name] = i + 1
key_table[node_name] = index
key_table
key_table = build_table!
make_builder = (name) ->
spec = node_types[name]
error "don't know how to build node: "..name if not spec
(props={}) ->
node = { name }
for i, arg in ipairs spec
key, default_value = unpack arg
val = if props[key] then props[key] else default_value
val = {} if val == t
node[i + 1] = val
node
build = nil
build = setmetatable {
group: (body={}) ->
{"group", body}
do: (body) ->
{"do", body}
assign_one: (name, value) ->
build.assign {
names: {name}
values: {value}
}
table: (tbl={}) ->
-- convert strings to key literals
for tuple in *tbl
if type(tuple[1]) == "string"
tuple[1] = {"key_literal", tuple[1]}
{"table", tbl}
block_exp: (body) ->
{"block_exp", body}
chain: (parts) ->
base = parts.base or error"expecting base property for chain"
if type(base) == "string"
base = {"ref", base}
node = {"chain", base}
for part in *parts
insert node, part
node
}, {
__index: (name) =>
self[name] = make_builder name
rawget self, name
}
smart_node_mt = setmetatable {}, {
__index: (node_type) =>
index = key_table[node_type]
mt = {
smart_node: true
__index: (node, key) ->
if index[key]
rawget node, index[key]
elseif type(key) == "string"
error "unknown key: `"..key.."` on node type: `"..ntype(node).. "`"
__newindex: (node, key, value) ->
key = index[key] if index[key]
rawset node, key, value
}
self[node_type] = mt
mt
}
-- makes it so node properties can be accessed by name instead of index
smart_node = (node) ->
setmetatable node, smart_node_mt[ntype node]
NOOP = {"noop"}
{
:ntype, :smart_node, :build, :is_value, :is_slice, :manual_return,
:cascading, :value_is_singular,
:value_can_be_statement, :mtype, :terminating
:NOOP
}