lat 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/lat +2 -2
- data/compiler/codegen.rb +39 -181
- data/compiler/compile.rb +109 -3
- data/compiler/errors.rb +8 -0
- data/compiler/parser.rb +126 -40
- data/compiler/tokenizer.rb +31 -7
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c2053d16f88f07ebebeb98b8132ae6bf6adfaecb06d1573be7c0639b73755837
|
|
4
|
+
data.tar.gz: 02063ec4e1819c2743e426bdbc4b642f4e0862b1ce4cdfd4b02e2aba69abc380
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d080afc37148a4c9cab7daf30cdada85ab226f11a0bc13a7786ba3aa840156fc5b622ab5474dec0e7879926fa0952a09024a463afb93800b11facd0b15336a6c
|
|
7
|
+
data.tar.gz: 6674298934d89ce7f1f9c1e108e114a64d96330bb7d7e06ce27becd8e8e0f2b89cf2a71a5a0d963427c202ee2586a1cf5b13d824ef4f021168da04b6681f039e
|
data/bin/lat
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
$LOAD_PATH.unshift File.join(__dir__, "../compiler")
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
$LOAD_PATH.unshift File.join(__dir__, "../compiler")
|
|
3
3
|
require_relative "../compiler/compile"
|
data/compiler/codegen.rb
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
class Generator
|
|
2
|
+
|
|
3
|
+
def generate_statement(node)
|
|
4
|
+
code = generate(node)
|
|
5
|
+
line = node.instance_variable_get(:@lat_line) if node.respond_to?(:instance_variable_get)
|
|
6
|
+
line ? "--[[@#{line}]]#{code}" : code
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def generate_arguments(args)
|
|
10
|
+
args.map { |expr| generate(expr) }.join(",")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def generate_param_names(names)
|
|
14
|
+
names.join(",")
|
|
15
|
+
end
|
|
16
|
+
|
|
2
17
|
def generate(node)
|
|
3
18
|
if node.is_a?(Array)
|
|
4
|
-
return node.map { |n|
|
|
19
|
+
return node.map { |n| generate_statement(n) }.join("\n")
|
|
5
20
|
end
|
|
6
21
|
|
|
7
22
|
case node
|
|
@@ -39,20 +54,15 @@ class Generator
|
|
|
39
54
|
|
|
40
55
|
out.join("\n")
|
|
41
56
|
when ClassDefNode
|
|
42
|
-
body_code = node.body
|
|
43
|
-
"function %s(%s)\n %s\nend" % [node.name, node.args
|
|
57
|
+
body_code = generate(node.body)
|
|
58
|
+
"function %s(%s)\n %s\nend" % [node.name, generate_param_names(node.args), body_code]
|
|
44
59
|
|
|
45
60
|
when DefNode
|
|
46
|
-
body_code =
|
|
47
|
-
if node.body.is_a?(Array)
|
|
48
|
-
node.body.map { |n| generate (n) }.join("\n ")
|
|
49
|
-
else
|
|
50
|
-
generate(node.body)
|
|
51
|
-
end
|
|
61
|
+
body_code = generate(node.body)
|
|
52
62
|
|
|
53
63
|
"function #{node.type == "love"? "love." : ""}%s(%s)\n %s \nend" % [
|
|
54
64
|
node.name,
|
|
55
|
-
node.args
|
|
65
|
+
generate_param_names(node.args),
|
|
56
66
|
body_code
|
|
57
67
|
]
|
|
58
68
|
|
|
@@ -61,28 +71,23 @@ class Generator
|
|
|
61
71
|
|
|
62
72
|
first = node.condition
|
|
63
73
|
compiled << "if #{generate(first)} then\n "
|
|
64
|
-
compiled << node.body
|
|
74
|
+
compiled << generate(node.body)
|
|
65
75
|
|
|
66
76
|
node.elif_blocks.each do |c|
|
|
67
77
|
compiled << "\nelseif #{generate(c.condition)} then\n "
|
|
68
|
-
compiled << c.body
|
|
78
|
+
compiled << generate(c.body)
|
|
69
79
|
end
|
|
70
80
|
|
|
71
81
|
if node.else_body
|
|
72
82
|
compiled << "\nelse\n "
|
|
73
|
-
compiled << node.else_body
|
|
83
|
+
compiled << generate(node.else_body)
|
|
74
84
|
end
|
|
75
85
|
|
|
76
86
|
compiled << "\nend\n"
|
|
77
87
|
compiled
|
|
78
88
|
|
|
79
89
|
when WhileNode
|
|
80
|
-
body_code =
|
|
81
|
-
if node.body.is_a?(Array)
|
|
82
|
-
node.body.map{ |n| generate (n) }.join("\n")
|
|
83
|
-
else
|
|
84
|
-
generate(node.body)
|
|
85
|
-
end
|
|
90
|
+
body_code = generate(node.body)
|
|
86
91
|
|
|
87
92
|
"while %s do \n %s \nend" % [
|
|
88
93
|
generate(node.statement),
|
|
@@ -90,12 +95,8 @@ class Generator
|
|
|
90
95
|
]
|
|
91
96
|
|
|
92
97
|
when ForNode
|
|
93
|
-
body_code =
|
|
94
|
-
|
|
95
|
-
node.body.map{ |n| generate (n) }.join("\n")
|
|
96
|
-
else
|
|
97
|
-
generate(node.body)
|
|
98
|
-
end
|
|
98
|
+
body_code = generate(node.body)
|
|
99
|
+
|
|
99
100
|
increment = ", #{node.step.nil? ? "" : generate(node.step)}"
|
|
100
101
|
"for %s = %s, %s%s do\n %s \nend" % [
|
|
101
102
|
generate(node.var),
|
|
@@ -106,12 +107,7 @@ class Generator
|
|
|
106
107
|
]
|
|
107
108
|
|
|
108
109
|
when ForPairNode
|
|
109
|
-
body_code =
|
|
110
|
-
if node.body.is_a?(Array)
|
|
111
|
-
node.body.map{ |n| generate (n) }.join("\n")
|
|
112
|
-
else
|
|
113
|
-
generate(node.body)
|
|
114
|
-
end
|
|
110
|
+
body_code = generate(node.body)
|
|
115
111
|
"for %s, %s in pairs(%s) do\n %s \nend" % [
|
|
116
112
|
node.key,
|
|
117
113
|
node.val,
|
|
@@ -119,16 +115,12 @@ class Generator
|
|
|
119
115
|
body_code
|
|
120
116
|
]
|
|
121
117
|
|
|
122
|
-
when
|
|
123
|
-
body_code =
|
|
124
|
-
|
|
125
|
-
node.body.map{ |n| generate (n) }.join("\n")
|
|
126
|
-
else
|
|
127
|
-
generate(node.body)
|
|
128
|
-
end
|
|
118
|
+
when ForIPairNode
|
|
119
|
+
body_code = generate(node.body)
|
|
120
|
+
|
|
129
121
|
"for %s, %s in ipairs(%s) do\n %s \nend" % [
|
|
130
|
-
|
|
131
|
-
|
|
122
|
+
node.index,
|
|
123
|
+
node.val,
|
|
132
124
|
generate(node.t),
|
|
133
125
|
body_code
|
|
134
126
|
]
|
|
@@ -142,7 +134,7 @@ class Generator
|
|
|
142
134
|
compiled << "elseif #{generate(c.match)} == #{generate(node.value)} then\n"
|
|
143
135
|
end
|
|
144
136
|
|
|
145
|
-
body_code = c.body
|
|
137
|
+
body_code = generate(c.body)
|
|
146
138
|
compiled << " #{body_code}\n"
|
|
147
139
|
|
|
148
140
|
end
|
|
@@ -153,12 +145,12 @@ class Generator
|
|
|
153
145
|
when CallNode
|
|
154
146
|
"%s(%s)" % [
|
|
155
147
|
node.name,
|
|
156
|
-
|
|
148
|
+
generate_arguments(node.arg_expr)
|
|
157
149
|
]
|
|
158
150
|
|
|
159
151
|
when PrintNode
|
|
160
152
|
"print(%s)" % [
|
|
161
|
-
node.args
|
|
153
|
+
generate_arguments(node.args)
|
|
162
154
|
]
|
|
163
155
|
|
|
164
156
|
when VarAssignNode
|
|
@@ -169,7 +161,7 @@ class Generator
|
|
|
169
161
|
|
|
170
162
|
when VarSetNode
|
|
171
163
|
"%s = %s" % [
|
|
172
|
-
node.
|
|
164
|
+
node.children.join("."),
|
|
173
165
|
generate(node.value)
|
|
174
166
|
]
|
|
175
167
|
|
|
@@ -185,7 +177,7 @@ class Generator
|
|
|
185
177
|
selfstring = "self#{node.type}#{node.name}"
|
|
186
178
|
|
|
187
179
|
if !node.args.empty?
|
|
188
|
-
args_string = node.args
|
|
180
|
+
args_string = generate_arguments(node.args)
|
|
189
181
|
selfstring += "(#{args_string})"
|
|
190
182
|
else
|
|
191
183
|
selfstring += " = %s" % [
|
|
@@ -198,7 +190,7 @@ class Generator
|
|
|
198
190
|
"love.%s.%s(%s)" % [
|
|
199
191
|
node.namespace,
|
|
200
192
|
node.name,
|
|
201
|
-
node.args
|
|
193
|
+
generate_arguments(node.args)
|
|
202
194
|
]
|
|
203
195
|
|
|
204
196
|
when ReturnNode
|
|
@@ -221,146 +213,12 @@ class Generator
|
|
|
221
213
|
]
|
|
222
214
|
|
|
223
215
|
when ArrayNode
|
|
224
|
-
elements = node.elements
|
|
216
|
+
elements = generate_arguments(node.elements)
|
|
225
217
|
"{#{elements}}"
|
|
226
218
|
|
|
227
219
|
when ArrayAccessNode
|
|
228
220
|
"#{node.name}[#{generate(node.index)}]"
|
|
229
221
|
|
|
230
|
-
# when ErrorCallNode
|
|
231
|
-
# <<~LUA
|
|
232
|
-
# local utf8 = require("utf8")
|
|
233
|
-
|
|
234
|
-
# local function error_printer(msg, layer)
|
|
235
|
-
# print((debug.traceback("Error: " .. tostring(msg), 1+(layer or 1)):gsub("\n[^\n]+$", "")))
|
|
236
|
-
# end
|
|
237
|
-
|
|
238
|
-
# function love.errorhandler(msg)
|
|
239
|
-
# msg = tostring(msg)
|
|
240
|
-
|
|
241
|
-
# error_printer(msg, 2)
|
|
242
|
-
|
|
243
|
-
# if not love.window or not love.graphics or not love.event then
|
|
244
|
-
# return
|
|
245
|
-
# end
|
|
246
|
-
|
|
247
|
-
# if not love.graphics.isCreated() or not love.window.isOpen() then
|
|
248
|
-
# local success, status = pcall(love.window.setMode, 800, 600)
|
|
249
|
-
# if not success or not status then
|
|
250
|
-
# return
|
|
251
|
-
# end
|
|
252
|
-
# end
|
|
253
|
-
|
|
254
|
-
# -- Reset state.
|
|
255
|
-
# if love.mouse then
|
|
256
|
-
# love.mouse.setVisible(true)
|
|
257
|
-
# love.mouse.setGrabbed(false)
|
|
258
|
-
# love.mouse.setRelativeMode(false)
|
|
259
|
-
# if love.mouse.isCursorSupported() then
|
|
260
|
-
# love.mouse.setCursor()
|
|
261
|
-
# end
|
|
262
|
-
# end
|
|
263
|
-
# if love.joystick then
|
|
264
|
-
# -- Stop all joystick vibrations.
|
|
265
|
-
# for i,v in ipairs(love.joystick.getJoysticks()) do
|
|
266
|
-
# v:setVibration()
|
|
267
|
-
# end
|
|
268
|
-
# end
|
|
269
|
-
# if love.audio then love.audio.stop() end
|
|
270
|
-
|
|
271
|
-
# love.graphics.reset()
|
|
272
|
-
# local font = love.graphics.setNewFont(14)
|
|
273
|
-
|
|
274
|
-
# love.graphics.setColor(1, 1, 1)
|
|
275
|
-
|
|
276
|
-
# local trace = debug.traceback()
|
|
277
|
-
|
|
278
|
-
# love.graphics.origin()
|
|
279
|
-
|
|
280
|
-
# local sanitizedmsg = {}
|
|
281
|
-
# for char in msg:gmatch(utf8.charpattern) do
|
|
282
|
-
# table.insert(sanitizedmsg, char)
|
|
283
|
-
# end
|
|
284
|
-
# sanitizedmsg = table.concat(sanitizedmsg)
|
|
285
|
-
|
|
286
|
-
# local err = {}
|
|
287
|
-
|
|
288
|
-
# table.insert(err, "Error\n")
|
|
289
|
-
# table.insert(err, sanitizedmsg)
|
|
290
|
-
|
|
291
|
-
# if #sanitizedmsg ~= #msg then
|
|
292
|
-
# table.insert(err, "Invalid UTF-8 string in error message.")
|
|
293
|
-
# end
|
|
294
|
-
|
|
295
|
-
# table.insert(err, "\n")
|
|
296
|
-
|
|
297
|
-
# for l in trace:gmatch("(.-)\n") do
|
|
298
|
-
# if not l:match("boot.lua") then
|
|
299
|
-
# l = l:gsub("stack traceback:", "Traceback\n")
|
|
300
|
-
# table.insert(err, l)
|
|
301
|
-
# end
|
|
302
|
-
# end
|
|
303
|
-
|
|
304
|
-
# local p = table.concat(err, "\n")
|
|
305
|
-
|
|
306
|
-
# p = p:gsub("\t", "")
|
|
307
|
-
# p = p:gsub("%[string \"(.-)\"%]", "%1")
|
|
308
|
-
|
|
309
|
-
# local function draw()
|
|
310
|
-
# if not love.graphics.isActive() then return end
|
|
311
|
-
# local pos = 70
|
|
312
|
-
# love.graphics.clear(89/255, 157/255, 220/255)
|
|
313
|
-
# love.graphics.printf(p, pos, pos, love.graphics.getWidth() - pos)
|
|
314
|
-
# love.graphics.present()
|
|
315
|
-
# end
|
|
316
|
-
|
|
317
|
-
# local fullErrorText = p
|
|
318
|
-
# local function copyToClipboard()
|
|
319
|
-
# if not love.system then return end
|
|
320
|
-
# love.system.setClipboardText(fullErrorText)
|
|
321
|
-
# p = p .. "\nCopied to clipboard!"
|
|
322
|
-
# end
|
|
323
|
-
|
|
324
|
-
# if love.system then
|
|
325
|
-
# p = p .. "\n\nPress Ctrl+C or tap to copy this error"
|
|
326
|
-
# end
|
|
327
|
-
|
|
328
|
-
# return function()
|
|
329
|
-
# love.event.pump()
|
|
330
|
-
|
|
331
|
-
# for e, a, b, c in love.event.poll() do
|
|
332
|
-
# if e == "quit" then
|
|
333
|
-
# return 1
|
|
334
|
-
# elseif e == "keypressed" and a == "escape" then
|
|
335
|
-
# return 1
|
|
336
|
-
# elseif e == "keypressed" and a == "c" and love.keyboard.isDown("lctrl", "rctrl") then
|
|
337
|
-
# copyToClipboard()
|
|
338
|
-
# elseif e == "touchpressed" then
|
|
339
|
-
# local name = love.window.getTitle()
|
|
340
|
-
# if #name == 0 or name == "Untitled" then name = "Game" end
|
|
341
|
-
# local buttons = {"OK", "Cancel"}
|
|
342
|
-
# if love.system then
|
|
343
|
-
# buttons[3] = "Copy to clipboard"
|
|
344
|
-
# end
|
|
345
|
-
# local pressed = love.window.showMessageBox("Quit "..name.."?", "", buttons)
|
|
346
|
-
# if pressed == 1 then
|
|
347
|
-
# return 1
|
|
348
|
-
# elseif pressed == 3 then
|
|
349
|
-
# copyToClipboard()
|
|
350
|
-
# end
|
|
351
|
-
# end
|
|
352
|
-
# end
|
|
353
|
-
|
|
354
|
-
# draw()
|
|
355
|
-
|
|
356
|
-
# if love.timer then
|
|
357
|
-
# love.timer.sleep(0.1)
|
|
358
|
-
# end
|
|
359
|
-
# end
|
|
360
|
-
|
|
361
|
-
# end
|
|
362
|
-
# LUA
|
|
363
|
-
|
|
364
222
|
else
|
|
365
223
|
raise "Unknown node type: #{node.class}"
|
|
366
224
|
end
|
data/compiler/compile.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative "tokenizer"
|
|
4
4
|
require_relative "parser"
|
|
5
5
|
require_relative "codegen"
|
|
6
|
+
require_relative "errors"
|
|
6
7
|
|
|
7
8
|
if ARGV.empty?
|
|
8
9
|
puts "Usage: lat <input.lat> [output.lua]"
|
|
@@ -21,6 +22,22 @@ end
|
|
|
21
22
|
|
|
22
23
|
OS = detect_os
|
|
23
24
|
|
|
25
|
+
def report_syntax_error(err, filename, source)
|
|
26
|
+
location = err.line ? "#{filename}:#{err.line}" : filename
|
|
27
|
+
puts "lat: syntax error in #{location}"
|
|
28
|
+
|
|
29
|
+
if err.line && source
|
|
30
|
+
src_line = source.split("\n", -1)[err.line - 1]
|
|
31
|
+
if src_line
|
|
32
|
+
puts " #{src_line}"
|
|
33
|
+
puts " #{" " * (err.column - 1)}^" if err.column
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
puts " #{err.message}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
|
|
24
41
|
def love_installed?
|
|
25
42
|
if OS == :windows
|
|
26
43
|
common_paths = [
|
|
@@ -120,25 +137,114 @@ if RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/
|
|
|
120
137
|
system("attrib +h #{latcDir}") # -h to unhide
|
|
121
138
|
end
|
|
122
139
|
|
|
140
|
+
inputDir = File.dirname(inputFile)
|
|
123
141
|
basename = File.basename(inputFile, ".*")
|
|
124
142
|
outputFile = File.join(latcDir, basename == "main" ? "main.lua" : "#{basename}.lua")
|
|
143
|
+
confOutputFile = File.join(latcDir, "conf.lua")
|
|
144
|
+
|
|
145
|
+
confPath = File.join(inputDir, "conf.lat")
|
|
146
|
+
confInput = File.read(confPath) if File.exist?(confPath)
|
|
125
147
|
|
|
126
148
|
input = File.read(inputFile)
|
|
149
|
+
confInput = File.read(confPath) if File.exist?(confPath)
|
|
127
150
|
|
|
128
|
-
|
|
151
|
+
confTokens = Tokenizer.new(confInput).tokenize if confInput
|
|
129
152
|
# puts "--- TOKENS ---"
|
|
130
153
|
# puts tokens.map(&:inspect).join("\n")
|
|
131
154
|
|
|
132
|
-
|
|
155
|
+
confTree = Parser.new(confTokens).parse if confTokens
|
|
133
156
|
# puts "--- AST ---"
|
|
134
157
|
# p tree
|
|
135
158
|
|
|
159
|
+
|
|
160
|
+
begin
|
|
161
|
+
tokens = Tokenizer.new(input).tokenize
|
|
162
|
+
tree = Parser.new(tokens).parse
|
|
163
|
+
rescue LatSyntaxError => e
|
|
164
|
+
report_syntax_error(e, inputFile, input)
|
|
165
|
+
exit 1
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
confTree = nil
|
|
169
|
+
if confInput
|
|
170
|
+
begin
|
|
171
|
+
confTokens = Tokenizer.new(confInput).tokenize
|
|
172
|
+
confTree = Parser.new(confTokens).parse
|
|
173
|
+
rescue LatSyntaxError => e
|
|
174
|
+
report_syntax_error(e, confPath, confInput)
|
|
175
|
+
exit 1
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
136
182
|
generated = Generator.new.generate(tree)
|
|
183
|
+
confGenerated = Generator.new.generate(confTree) if confTree
|
|
137
184
|
# puts "--- LUA OUTPUT ---"
|
|
138
185
|
# puts generated
|
|
139
|
-
File.open(outputFile, 'w') { |file| file.write(generated)}
|
|
140
186
|
|
|
187
|
+
def build_source_map(lua_source)
|
|
188
|
+
map = {}
|
|
189
|
+
clean_lines = lua_source.split("\n", -1).each_with_index.map do |line, idx|
|
|
190
|
+
if line =~ /\A(\s*)--\[\[@(\d+)\]\](.*)\z/m
|
|
191
|
+
map[idx + 1] = $2.to_i
|
|
192
|
+
"#{$1}#{$3}"
|
|
193
|
+
else
|
|
194
|
+
line
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
[clean_lines.join("\n"), map]
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def build_lat_shim(basename, source_map)
|
|
201
|
+
table_entries = source_map.map { |k, v| "[#{k}]=#{v}" }.join(",")
|
|
202
|
+
<<~LUA
|
|
203
|
+
local _LAT_SOURCE_MAP_T = { #{table_entries} }
|
|
204
|
+
local _LAT_FILE = "#{basename}.lat"
|
|
205
|
+
|
|
206
|
+
local function _lat_remap(text)
|
|
207
|
+
return (text:gsub("#{basename}%.lua:(%d+)", function(n)
|
|
208
|
+
local mapped = _LAT_SOURCE_MAP_T[tonumber(n)]
|
|
209
|
+
return mapped and (_LAT_FILE .. ":" .. mapped) or ("#{basename}.lua:" .. n)
|
|
210
|
+
end))
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
local _LAT_ERROR_BG = {0.1, 0.1, 0.15}
|
|
214
|
+
|
|
215
|
+
if _lat_default_handler then
|
|
216
|
+
local function _lat_wrapped_handler(msg)
|
|
217
|
+
local loop = _lat_default_handler(_lat_remap(tostring(msg)))
|
|
218
|
+
if type(loop) == "function" then
|
|
219
|
+
local real_clear = love.graphics.clear
|
|
220
|
+
return function(...)
|
|
221
|
+
love.graphics.clear = function() real_clear(_LAT_ERROR_BG[1], _LAT_ERROR_BG[2], _LAT_ERROR_BG[3]) end
|
|
222
|
+
local result = loop(...)
|
|
223
|
+
love.graphics.clear = real_clear
|
|
224
|
+
return result
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
return loop
|
|
228
|
+
end
|
|
229
|
+
love.errorhandler = _lat_wrapped_handler
|
|
230
|
+
love.errhand = _lat_wrapped_handler
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
LUA
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
generated, source_map = build_source_map(generated)
|
|
238
|
+
|
|
239
|
+
shim_line_count = build_lat_shim(basename, {}).each_line.count
|
|
240
|
+
offset_map = source_map.each_with_object({}) { |(k, v), h| h[k + shim_line_count] = v }
|
|
241
|
+
|
|
242
|
+
generated = build_lat_shim(basename, offset_map) + generated
|
|
243
|
+
|
|
244
|
+
confGenerated, _conf_source_map = build_source_map(confGenerated) if confGenerated
|
|
141
245
|
|
|
246
|
+
File.open(outputFile, 'w') { |file| file.write(generated) }
|
|
247
|
+
File.open(confOutputFile, 'w') { |file| file.write(confGenerated) } if confGenerated
|
|
142
248
|
exec(find_love(), latcDir) unless skip_run
|
|
143
249
|
|
|
144
250
|
# ln -s "$(pwd)/compiler/compile.rb" /usr/local/bin/lat
|
data/compiler/errors.rb
ADDED
data/compiler/parser.rb
CHANGED
|
@@ -19,7 +19,7 @@ StringNode = Struct.new(:value)
|
|
|
19
19
|
CallNode = Struct.new(:name, :arg_expr)
|
|
20
20
|
VarRefNode = Struct.new(:value)
|
|
21
21
|
VarAssignNode = Struct.new(:name, :value)
|
|
22
|
-
VarSetNode = Struct.new(:
|
|
22
|
+
VarSetNode = Struct.new(:children, :value)
|
|
23
23
|
BinOpNode = Struct.new(:left, :op, :right)
|
|
24
24
|
PrintNode = Struct.new(:args)
|
|
25
25
|
ReturnNode = Struct.new(:statement)
|
|
@@ -35,7 +35,6 @@ SelfNode = Struct.new(:name, :type, :args, :value)
|
|
|
35
35
|
|
|
36
36
|
ErrorCallNode = Struct.new()
|
|
37
37
|
|
|
38
|
-
|
|
39
38
|
LOVE_NAMESPACES = {
|
|
40
39
|
lgraphics: "graphics",
|
|
41
40
|
laudio: "audio",
|
|
@@ -59,9 +58,48 @@ OP_NAMESPACES = {
|
|
|
59
58
|
|
|
60
59
|
}
|
|
61
60
|
|
|
61
|
+
FRIENDLY_TOKEN_NAMES = {
|
|
62
|
+
end: "'done'",
|
|
63
|
+
def: "'call'",
|
|
64
|
+
local: "'nat'",
|
|
65
|
+
while: "'when'",
|
|
66
|
+
forpairs: "'each'",
|
|
67
|
+
foripairs: "'seq'",
|
|
68
|
+
in: "'as'",
|
|
69
|
+
import: "'bring'",
|
|
70
|
+
if: "'if'",
|
|
71
|
+
elif: "'elif'",
|
|
72
|
+
else: "'else'",
|
|
73
|
+
switch: "'switch'",
|
|
74
|
+
to: "'to'",
|
|
75
|
+
class: "'class'",
|
|
76
|
+
self: "'self'",
|
|
77
|
+
identifier: "an identifier",
|
|
78
|
+
string: "a string",
|
|
79
|
+
integer: "a number",
|
|
80
|
+
float: "a number",
|
|
81
|
+
oparen: "'('",
|
|
82
|
+
cparen: "')'",
|
|
83
|
+
lbracket: "'['",
|
|
84
|
+
rbracket: "']'",
|
|
85
|
+
lbrace: "'{'",
|
|
86
|
+
rbrace: "'}'",
|
|
87
|
+
comma: "','",
|
|
88
|
+
dot: "'.'",
|
|
89
|
+
colon: "':'",
|
|
90
|
+
equal: "'='",
|
|
91
|
+
dequal: "'=='",
|
|
92
|
+
newline: "a newline",
|
|
93
|
+
}.freeze
|
|
94
|
+
|
|
62
95
|
class Parser
|
|
63
96
|
def initialize(tokens)
|
|
64
97
|
@tokens = tokens
|
|
98
|
+
@last_line = tokens.first&.line
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def describe_token_type(type)
|
|
102
|
+
FRIENDLY_TOKEN_NAMES[type] || "'#{type}'"
|
|
65
103
|
end
|
|
66
104
|
|
|
67
105
|
def parse
|
|
@@ -86,36 +124,41 @@ class Parser
|
|
|
86
124
|
skip_newlines
|
|
87
125
|
return nil if peek(:end)
|
|
88
126
|
|
|
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
|
-
|
|
127
|
+
line = @tokens[0]&.line
|
|
128
|
+
|
|
129
|
+
node =
|
|
130
|
+
if peek(:import) then
|
|
131
|
+
parse_import
|
|
132
|
+
elsif peek(:class) then
|
|
133
|
+
parse_class
|
|
134
|
+
elsif peek(:def)
|
|
135
|
+
parse_def
|
|
136
|
+
elsif peek(:if)
|
|
137
|
+
parse_if
|
|
138
|
+
elsif peek(:while)
|
|
139
|
+
parse_while
|
|
140
|
+
elsif peek(:for)
|
|
141
|
+
parse_for
|
|
142
|
+
elsif peek(:forpairs)
|
|
143
|
+
parse_forpairs
|
|
144
|
+
elsif peek(:foripairs)
|
|
145
|
+
parse_foripairs
|
|
146
|
+
elsif peek(:switch)
|
|
147
|
+
parse_switch
|
|
148
|
+
elsif peek(:print)
|
|
149
|
+
parse_print
|
|
150
|
+
elsif peek(:local)
|
|
151
|
+
parse_var_assign
|
|
152
|
+
elsif seems_var_set?
|
|
153
|
+
parse_var_set
|
|
154
|
+
elsif peek(:return)
|
|
155
|
+
parse_return
|
|
156
|
+
else
|
|
157
|
+
parse_expr
|
|
158
|
+
end
|
|
118
159
|
|
|
160
|
+
node.instance_variable_set(:@lat_line, line) if node.respond_to?(:instance_variable_set) && line
|
|
161
|
+
node
|
|
119
162
|
|
|
120
163
|
end
|
|
121
164
|
|
|
@@ -123,6 +166,16 @@ class Parser
|
|
|
123
166
|
consume(:newline) while peek(:newline)
|
|
124
167
|
end
|
|
125
168
|
|
|
169
|
+
def seems_var_set?
|
|
170
|
+
return false unless peek(:identifier)
|
|
171
|
+
offset = 1
|
|
172
|
+
|
|
173
|
+
while peek(:dot, offset) && peek(:identifier, offset + 1)
|
|
174
|
+
offset += 2
|
|
175
|
+
end
|
|
176
|
+
peek(:equal, offset)
|
|
177
|
+
end
|
|
178
|
+
|
|
126
179
|
def parse_import
|
|
127
180
|
consume(:import)
|
|
128
181
|
location = consume(:string).value
|
|
@@ -177,7 +230,6 @@ class Parser
|
|
|
177
230
|
DefNode.new(type, name, args, body)
|
|
178
231
|
end
|
|
179
232
|
|
|
180
|
-
|
|
181
233
|
def parse_if
|
|
182
234
|
consume(:if)
|
|
183
235
|
condition = parse_expr
|
|
@@ -267,7 +319,6 @@ class Parser
|
|
|
267
319
|
ForPairNode.new(key, val, t, body)
|
|
268
320
|
end
|
|
269
321
|
|
|
270
|
-
|
|
271
322
|
def parse_foripairs
|
|
272
323
|
consume(:foripairs)
|
|
273
324
|
index = consume(:identifier).value
|
|
@@ -322,10 +373,14 @@ class Parser
|
|
|
322
373
|
end
|
|
323
374
|
|
|
324
375
|
def parse_var_set
|
|
325
|
-
|
|
376
|
+
children = [consume(:identifier).value] # First get the identifier
|
|
377
|
+
while peek(:dot) # Check if there is a dot or a child for it
|
|
378
|
+
consume(:dot)
|
|
379
|
+
children << consume(:identifier).value
|
|
380
|
+
end
|
|
326
381
|
consume(:equal)
|
|
327
382
|
value = parse_expr
|
|
328
|
-
VarSetNode.new(
|
|
383
|
+
VarSetNode.new(children, value)
|
|
329
384
|
end
|
|
330
385
|
|
|
331
386
|
def parse_return
|
|
@@ -371,8 +426,6 @@ class Parser
|
|
|
371
426
|
body
|
|
372
427
|
end
|
|
373
428
|
|
|
374
|
-
|
|
375
|
-
|
|
376
429
|
def parse_additive
|
|
377
430
|
left = parse_multiplicative
|
|
378
431
|
|
|
@@ -395,7 +448,6 @@ class Parser
|
|
|
395
448
|
left
|
|
396
449
|
end
|
|
397
450
|
|
|
398
|
-
|
|
399
451
|
def parse_term
|
|
400
452
|
skip_newlines
|
|
401
453
|
if peek(:integer)
|
|
@@ -458,7 +510,21 @@ class Parser
|
|
|
458
510
|
elsif LOVE_NAMESPACES.keys.include?(peek_type)
|
|
459
511
|
parse_love_call
|
|
460
512
|
else
|
|
461
|
-
|
|
513
|
+
token = @tokens[0]
|
|
514
|
+
if token.nil?
|
|
515
|
+
raise LatSyntaxError.new(
|
|
516
|
+
"Expected an expression but reached the end of the file",
|
|
517
|
+
line: @last_line
|
|
518
|
+
)
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
raise LatSyntaxError.new(
|
|
522
|
+
"Expected an expression but got #{describe_token_type(token.type)} (\"#{token.value}\")",
|
|
523
|
+
line: token.line,
|
|
524
|
+
column: token.column
|
|
525
|
+
|
|
526
|
+
)
|
|
527
|
+
# raise "Unexpected token #{@tokens[0].inspect} in term"
|
|
462
528
|
|
|
463
529
|
end
|
|
464
530
|
end
|
|
@@ -554,8 +620,28 @@ class Parser
|
|
|
554
620
|
|
|
555
621
|
def consume(type)
|
|
556
622
|
token = @tokens.shift
|
|
557
|
-
raise "Expected #{type}, got #{token.type}" unless token.type == type
|
|
623
|
+
# raise "Expected #{type}, got #{token.type}" unless token.type == type
|
|
624
|
+
# token
|
|
625
|
+
|
|
626
|
+
if token.nil?
|
|
627
|
+
raise LatSyntaxError.new(
|
|
628
|
+
"Expected #{describe_token_type(type)} but reached the end of the file",
|
|
629
|
+
line: @last_line
|
|
630
|
+
)
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
unless token.type == type
|
|
634
|
+
raise LatSyntaxError.new(
|
|
635
|
+
"Expected #{describe_token_type(type)} but got #{describe_token_type(token.type)} (\"#{token.value}\")",
|
|
636
|
+
line: token.line,
|
|
637
|
+
column: token.column
|
|
638
|
+
|
|
639
|
+
)
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
@last_line = token.line
|
|
558
643
|
token
|
|
644
|
+
|
|
559
645
|
end
|
|
560
646
|
|
|
561
647
|
def peek(type, offset = 0)
|
data/compiler/tokenizer.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Token = Struct.new(:type, :value)
|
|
1
|
+
Token = Struct.new(:type, :value, :line, :column)
|
|
2
2
|
|
|
3
3
|
class Tokenizer
|
|
4
4
|
|
|
@@ -73,8 +73,12 @@ class Tokenizer
|
|
|
73
73
|
|
|
74
74
|
]
|
|
75
75
|
|
|
76
|
-
def initialize(code)
|
|
77
|
-
@code = code
|
|
76
|
+
def initialize(code, filename = nil)
|
|
77
|
+
@code = code.gsub(/\r\n?/, "\n")
|
|
78
|
+
@filename = filename
|
|
79
|
+
@line = 1
|
|
80
|
+
@column = 1
|
|
81
|
+
# puts "Tokenizer initialized with: #{code.inspect}"
|
|
78
82
|
end
|
|
79
83
|
|
|
80
84
|
def tokenize
|
|
@@ -89,19 +93,39 @@ class Tokenizer
|
|
|
89
93
|
def tokenize_token
|
|
90
94
|
TOKEN_TYPES.each do |type, regex|
|
|
91
95
|
anchored = /\A(#{regex})/
|
|
92
|
-
|
|
93
|
-
|
|
96
|
+
|
|
94
97
|
if @code =~ anchored
|
|
95
98
|
value = $1
|
|
96
99
|
if type == :space
|
|
97
100
|
@code = @code[value.length..-1]
|
|
101
|
+
@column += value.length
|
|
98
102
|
return tokenize_token
|
|
99
103
|
end
|
|
104
|
+
|
|
105
|
+
token = Token.new(type, value, @line)
|
|
100
106
|
@code = @code[value.length..-1]
|
|
101
|
-
|
|
107
|
+
|
|
108
|
+
if type == :newline
|
|
109
|
+
@line += value.count("\n")
|
|
110
|
+
@column = 1
|
|
111
|
+
else
|
|
112
|
+
@column += value.length
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# return Token.new(type, value)
|
|
116
|
+
return token
|
|
102
117
|
end
|
|
103
118
|
end
|
|
104
119
|
|
|
105
|
-
|
|
120
|
+
snippet = @code.lines.first.to_s.chomp
|
|
121
|
+
snippet = snippet[0, 30] + "..." if snippet.length > 30
|
|
122
|
+
|
|
123
|
+
raise LatSyntaxError.new(
|
|
124
|
+
"Unrecognized syntax near #{snippet.inspect}",
|
|
125
|
+
line: @line,
|
|
126
|
+
column: @column
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# raise "Couldn't match token on #{@code.inspect}"
|
|
106
130
|
end
|
|
107
131
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lat
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- JakeOJeff
|
|
@@ -17,6 +17,7 @@ files:
|
|
|
17
17
|
- bin/lat
|
|
18
18
|
- compiler/codegen.rb
|
|
19
19
|
- compiler/compile.rb
|
|
20
|
+
- compiler/errors.rb
|
|
20
21
|
- compiler/parser.rb
|
|
21
22
|
- compiler/tokenizer.rb
|
|
22
23
|
homepage: https://github.com/JakeOJeff/lat
|