lat 0.1.1 → 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/compiler/codegen.rb +67 -163
- data/compiler/compile.rb +127 -21
- data/compiler/errors.rb +8 -0
- data/compiler/parser.rb +223 -106
- data/compiler/tokenizer.rb +61 -27
- 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/compiler/codegen.rb
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
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
|
|
8
23
|
|
|
9
24
|
when ImportNode
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
]
|
|
25
|
+
"require(\"#{node.location}\")"
|
|
26
|
+
|
|
13
27
|
when ClassNode
|
|
14
28
|
out = []
|
|
15
29
|
out << "local #{node.name} = {}"
|
|
@@ -40,20 +54,15 @@ class Generator
|
|
|
40
54
|
|
|
41
55
|
out.join("\n")
|
|
42
56
|
when ClassDefNode
|
|
43
|
-
body_code = node.body
|
|
44
|
-
"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]
|
|
45
59
|
|
|
46
60
|
when DefNode
|
|
47
|
-
body_code =
|
|
48
|
-
if node.body.is_a?(Array)
|
|
49
|
-
node.body.map { |n| generate (n) }.join("\n ")
|
|
50
|
-
else
|
|
51
|
-
generate(node.body)
|
|
52
|
-
end
|
|
61
|
+
body_code = generate(node.body)
|
|
53
62
|
|
|
54
63
|
"function #{node.type == "love"? "love." : ""}%s(%s)\n %s \nend" % [
|
|
55
64
|
node.name,
|
|
56
|
-
node.args
|
|
65
|
+
generate_param_names(node.args),
|
|
57
66
|
body_code
|
|
58
67
|
]
|
|
59
68
|
|
|
@@ -62,34 +71,60 @@ class Generator
|
|
|
62
71
|
|
|
63
72
|
first = node.condition
|
|
64
73
|
compiled << "if #{generate(first)} then\n "
|
|
65
|
-
compiled << node.body
|
|
74
|
+
compiled << generate(node.body)
|
|
66
75
|
|
|
67
76
|
node.elif_blocks.each do |c|
|
|
68
77
|
compiled << "\nelseif #{generate(c.condition)} then\n "
|
|
69
|
-
compiled << c.body
|
|
78
|
+
compiled << generate(c.body)
|
|
70
79
|
end
|
|
71
80
|
|
|
72
81
|
if node.else_body
|
|
73
82
|
compiled << "\nelse\n "
|
|
74
|
-
compiled << node.else_body
|
|
83
|
+
compiled << generate(node.else_body)
|
|
75
84
|
end
|
|
76
85
|
|
|
77
86
|
compiled << "\nend\n"
|
|
78
87
|
compiled
|
|
79
88
|
|
|
80
89
|
when WhileNode
|
|
81
|
-
body_code =
|
|
82
|
-
if node.body.is_a?(Array)
|
|
83
|
-
node.body.map{ |n| generate (n) }.join("\n")
|
|
84
|
-
else
|
|
85
|
-
generate(node.body)
|
|
86
|
-
end
|
|
90
|
+
body_code = generate(node.body)
|
|
87
91
|
|
|
88
92
|
"while %s do \n %s \nend" % [
|
|
89
93
|
generate(node.statement),
|
|
90
94
|
body_code
|
|
91
95
|
]
|
|
92
96
|
|
|
97
|
+
when ForNode
|
|
98
|
+
body_code = generate(node.body)
|
|
99
|
+
|
|
100
|
+
increment = ", #{node.step.nil? ? "" : generate(node.step)}"
|
|
101
|
+
"for %s = %s, %s%s do\n %s \nend" % [
|
|
102
|
+
generate(node.var),
|
|
103
|
+
generate(node.start),
|
|
104
|
+
generate(node.stop),
|
|
105
|
+
increment,
|
|
106
|
+
body_code
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
when ForPairNode
|
|
110
|
+
body_code = generate(node.body)
|
|
111
|
+
"for %s, %s in pairs(%s) do\n %s \nend" % [
|
|
112
|
+
node.key,
|
|
113
|
+
node.val,
|
|
114
|
+
generate(node.t),
|
|
115
|
+
body_code
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
when ForIPairNode
|
|
119
|
+
body_code = generate(node.body)
|
|
120
|
+
|
|
121
|
+
"for %s, %s in ipairs(%s) do\n %s \nend" % [
|
|
122
|
+
node.index,
|
|
123
|
+
node.val,
|
|
124
|
+
generate(node.t),
|
|
125
|
+
body_code
|
|
126
|
+
]
|
|
127
|
+
|
|
93
128
|
when SwitchNode
|
|
94
129
|
compiled = ""
|
|
95
130
|
node.cases.each_with_index do |c, i|
|
|
@@ -99,7 +134,7 @@ class Generator
|
|
|
99
134
|
compiled << "elseif #{generate(c.match)} == #{generate(node.value)} then\n"
|
|
100
135
|
end
|
|
101
136
|
|
|
102
|
-
body_code = c.body
|
|
137
|
+
body_code = generate(c.body)
|
|
103
138
|
compiled << " #{body_code}\n"
|
|
104
139
|
|
|
105
140
|
end
|
|
@@ -110,12 +145,12 @@ class Generator
|
|
|
110
145
|
when CallNode
|
|
111
146
|
"%s(%s)" % [
|
|
112
147
|
node.name,
|
|
113
|
-
|
|
148
|
+
generate_arguments(node.arg_expr)
|
|
114
149
|
]
|
|
115
150
|
|
|
116
151
|
when PrintNode
|
|
117
152
|
"print(%s)" % [
|
|
118
|
-
node.args
|
|
153
|
+
generate_arguments(node.args)
|
|
119
154
|
]
|
|
120
155
|
|
|
121
156
|
when VarAssignNode
|
|
@@ -126,7 +161,7 @@ class Generator
|
|
|
126
161
|
|
|
127
162
|
when VarSetNode
|
|
128
163
|
"%s = %s" % [
|
|
129
|
-
node.
|
|
164
|
+
node.children.join("."),
|
|
130
165
|
generate(node.value)
|
|
131
166
|
]
|
|
132
167
|
|
|
@@ -142,7 +177,7 @@ class Generator
|
|
|
142
177
|
selfstring = "self#{node.type}#{node.name}"
|
|
143
178
|
|
|
144
179
|
if !node.args.empty?
|
|
145
|
-
args_string = node.args
|
|
180
|
+
args_string = generate_arguments(node.args)
|
|
146
181
|
selfstring += "(#{args_string})"
|
|
147
182
|
else
|
|
148
183
|
selfstring += " = %s" % [
|
|
@@ -155,7 +190,7 @@ class Generator
|
|
|
155
190
|
"love.%s.%s(%s)" % [
|
|
156
191
|
node.namespace,
|
|
157
192
|
node.name,
|
|
158
|
-
node.args
|
|
193
|
+
generate_arguments(node.args)
|
|
159
194
|
]
|
|
160
195
|
|
|
161
196
|
when ReturnNode
|
|
@@ -169,152 +204,21 @@ class Generator
|
|
|
169
204
|
when IntegerNode
|
|
170
205
|
node.value.to_s
|
|
171
206
|
|
|
207
|
+
when FloatNode
|
|
208
|
+
node.value.to_s
|
|
209
|
+
|
|
172
210
|
when StringNode
|
|
173
211
|
"\"%s\"" % [
|
|
174
212
|
node.value
|
|
175
213
|
]
|
|
176
214
|
|
|
177
215
|
when ArrayNode
|
|
178
|
-
elements = node.elements
|
|
216
|
+
elements = generate_arguments(node.elements)
|
|
179
217
|
"{#{elements}}"
|
|
180
218
|
|
|
181
219
|
when ArrayAccessNode
|
|
182
220
|
"#{node.name}[#{generate(node.index)}]"
|
|
183
221
|
|
|
184
|
-
# when ErrorCallNode
|
|
185
|
-
# <<~LUA
|
|
186
|
-
# local utf8 = require("utf8")
|
|
187
|
-
|
|
188
|
-
# local function error_printer(msg, layer)
|
|
189
|
-
# print((debug.traceback("Error: " .. tostring(msg), 1+(layer or 1)):gsub("\n[^\n]+$", "")))
|
|
190
|
-
# end
|
|
191
|
-
|
|
192
|
-
# function love.errorhandler(msg)
|
|
193
|
-
# msg = tostring(msg)
|
|
194
|
-
|
|
195
|
-
# error_printer(msg, 2)
|
|
196
|
-
|
|
197
|
-
# if not love.window or not love.graphics or not love.event then
|
|
198
|
-
# return
|
|
199
|
-
# end
|
|
200
|
-
|
|
201
|
-
# if not love.graphics.isCreated() or not love.window.isOpen() then
|
|
202
|
-
# local success, status = pcall(love.window.setMode, 800, 600)
|
|
203
|
-
# if not success or not status then
|
|
204
|
-
# return
|
|
205
|
-
# end
|
|
206
|
-
# end
|
|
207
|
-
|
|
208
|
-
# -- Reset state.
|
|
209
|
-
# if love.mouse then
|
|
210
|
-
# love.mouse.setVisible(true)
|
|
211
|
-
# love.mouse.setGrabbed(false)
|
|
212
|
-
# love.mouse.setRelativeMode(false)
|
|
213
|
-
# if love.mouse.isCursorSupported() then
|
|
214
|
-
# love.mouse.setCursor()
|
|
215
|
-
# end
|
|
216
|
-
# end
|
|
217
|
-
# if love.joystick then
|
|
218
|
-
# -- Stop all joystick vibrations.
|
|
219
|
-
# for i,v in ipairs(love.joystick.getJoysticks()) do
|
|
220
|
-
# v:setVibration()
|
|
221
|
-
# end
|
|
222
|
-
# end
|
|
223
|
-
# if love.audio then love.audio.stop() end
|
|
224
|
-
|
|
225
|
-
# love.graphics.reset()
|
|
226
|
-
# local font = love.graphics.setNewFont(14)
|
|
227
|
-
|
|
228
|
-
# love.graphics.setColor(1, 1, 1)
|
|
229
|
-
|
|
230
|
-
# local trace = debug.traceback()
|
|
231
|
-
|
|
232
|
-
# love.graphics.origin()
|
|
233
|
-
|
|
234
|
-
# local sanitizedmsg = {}
|
|
235
|
-
# for char in msg:gmatch(utf8.charpattern) do
|
|
236
|
-
# table.insert(sanitizedmsg, char)
|
|
237
|
-
# end
|
|
238
|
-
# sanitizedmsg = table.concat(sanitizedmsg)
|
|
239
|
-
|
|
240
|
-
# local err = {}
|
|
241
|
-
|
|
242
|
-
# table.insert(err, "Error\n")
|
|
243
|
-
# table.insert(err, sanitizedmsg)
|
|
244
|
-
|
|
245
|
-
# if #sanitizedmsg ~= #msg then
|
|
246
|
-
# table.insert(err, "Invalid UTF-8 string in error message.")
|
|
247
|
-
# end
|
|
248
|
-
|
|
249
|
-
# table.insert(err, "\n")
|
|
250
|
-
|
|
251
|
-
# for l in trace:gmatch("(.-)\n") do
|
|
252
|
-
# if not l:match("boot.lua") then
|
|
253
|
-
# l = l:gsub("stack traceback:", "Traceback\n")
|
|
254
|
-
# table.insert(err, l)
|
|
255
|
-
# end
|
|
256
|
-
# end
|
|
257
|
-
|
|
258
|
-
# local p = table.concat(err, "\n")
|
|
259
|
-
|
|
260
|
-
# p = p:gsub("\t", "")
|
|
261
|
-
# p = p:gsub("%[string \"(.-)\"%]", "%1")
|
|
262
|
-
|
|
263
|
-
# local function draw()
|
|
264
|
-
# if not love.graphics.isActive() then return end
|
|
265
|
-
# local pos = 70
|
|
266
|
-
# love.graphics.clear(89/255, 157/255, 220/255)
|
|
267
|
-
# love.graphics.printf(p, pos, pos, love.graphics.getWidth() - pos)
|
|
268
|
-
# love.graphics.present()
|
|
269
|
-
# end
|
|
270
|
-
|
|
271
|
-
# local fullErrorText = p
|
|
272
|
-
# local function copyToClipboard()
|
|
273
|
-
# if not love.system then return end
|
|
274
|
-
# love.system.setClipboardText(fullErrorText)
|
|
275
|
-
# p = p .. "\nCopied to clipboard!"
|
|
276
|
-
# end
|
|
277
|
-
|
|
278
|
-
# if love.system then
|
|
279
|
-
# p = p .. "\n\nPress Ctrl+C or tap to copy this error"
|
|
280
|
-
# end
|
|
281
|
-
|
|
282
|
-
# return function()
|
|
283
|
-
# love.event.pump()
|
|
284
|
-
|
|
285
|
-
# for e, a, b, c in love.event.poll() do
|
|
286
|
-
# if e == "quit" then
|
|
287
|
-
# return 1
|
|
288
|
-
# elseif e == "keypressed" and a == "escape" then
|
|
289
|
-
# return 1
|
|
290
|
-
# elseif e == "keypressed" and a == "c" and love.keyboard.isDown("lctrl", "rctrl") then
|
|
291
|
-
# copyToClipboard()
|
|
292
|
-
# elseif e == "touchpressed" then
|
|
293
|
-
# local name = love.window.getTitle()
|
|
294
|
-
# if #name == 0 or name == "Untitled" then name = "Game" end
|
|
295
|
-
# local buttons = {"OK", "Cancel"}
|
|
296
|
-
# if love.system then
|
|
297
|
-
# buttons[3] = "Copy to clipboard"
|
|
298
|
-
# end
|
|
299
|
-
# local pressed = love.window.showMessageBox("Quit "..name.."?", "", buttons)
|
|
300
|
-
# if pressed == 1 then
|
|
301
|
-
# return 1
|
|
302
|
-
# elseif pressed == 3 then
|
|
303
|
-
# copyToClipboard()
|
|
304
|
-
# end
|
|
305
|
-
# end
|
|
306
|
-
# end
|
|
307
|
-
|
|
308
|
-
# draw()
|
|
309
|
-
|
|
310
|
-
# if love.timer then
|
|
311
|
-
# love.timer.sleep(0.1)
|
|
312
|
-
# end
|
|
313
|
-
# end
|
|
314
|
-
|
|
315
|
-
# end
|
|
316
|
-
# LUA
|
|
317
|
-
|
|
318
222
|
else
|
|
319
223
|
raise "Unknown node type: #{node.class}"
|
|
320
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]"
|
|
@@ -19,8 +20,26 @@ def detect_os
|
|
|
19
20
|
:linux
|
|
20
21
|
end
|
|
21
22
|
|
|
23
|
+
OS = detect_os
|
|
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
|
+
|
|
22
41
|
def love_installed?
|
|
23
|
-
if
|
|
42
|
+
if OS == :windows
|
|
24
43
|
common_paths = [
|
|
25
44
|
"C:/Program Files/LOVE/love.exe",
|
|
26
45
|
"C:/Program Files (x86)/LOVE/love.exe",
|
|
@@ -33,15 +52,14 @@ def love_installed?
|
|
|
33
52
|
end
|
|
34
53
|
|
|
35
54
|
def install_love
|
|
36
|
-
os = detect_os
|
|
37
55
|
puts "[lat] Love2D not found, attempting to install"
|
|
38
|
-
print "Do you want to install
|
|
56
|
+
print "Do you want to install LOVE2D? (y/n)"
|
|
39
57
|
answer = $stdin.gets.chomp.downcase
|
|
40
58
|
|
|
41
59
|
if %w[y yes].include?(answer.downcase)
|
|
42
60
|
puts "installing love..."
|
|
43
61
|
|
|
44
|
-
case
|
|
62
|
+
case OS
|
|
45
63
|
when :arch
|
|
46
64
|
system("sudo pacman -S --noconfirm love")
|
|
47
65
|
when :debian
|
|
@@ -52,11 +70,11 @@ def install_love
|
|
|
52
70
|
if system("which brew > /dev/null 2>&1")
|
|
53
71
|
system("brew install love")
|
|
54
72
|
else
|
|
55
|
-
puts "[lat] Homebrew not found. Install
|
|
73
|
+
puts "[lat] Homebrew not found. Install LOVE2D from https://love2d.org"
|
|
56
74
|
exit 1
|
|
57
75
|
end
|
|
58
76
|
when :windows
|
|
59
|
-
puts "[lat] Please install
|
|
77
|
+
puts "[lat] Please install LOVE2D from https://love2d.org"
|
|
60
78
|
puts "[lat] Set LOVE_PATH to the love.exe location:"
|
|
61
79
|
puts '[lat] setx LOVE_PATH "C:\\Program Files\\LOVE\\love.exe"'
|
|
62
80
|
else
|
|
@@ -74,22 +92,19 @@ end
|
|
|
74
92
|
def find_love
|
|
75
93
|
install_love unless love_installed?
|
|
76
94
|
|
|
77
|
-
|
|
78
|
-
"love",
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
candidates.each do |path|
|
|
85
|
-
return path if system("where #{path} >nul 2>&1") || File.exist?(path.to_s)
|
|
95
|
+
if OS == :windows
|
|
96
|
+
candidates = [ENV["LOVE_PATH"], "C:/Program Files/LOVE/love.exe", "C:/Program Files (x86)/LOVE/love.exe"].compact
|
|
97
|
+
candidates.each { |p| return p if File.exist?(p) }
|
|
98
|
+
else
|
|
99
|
+
["love", "love2d"].each do |cmd|
|
|
100
|
+
return cmd if system("which #{cmd} > /dev/null 2>&1")
|
|
101
|
+
end
|
|
86
102
|
end
|
|
87
103
|
|
|
88
104
|
puts "Error: LOVE2D not found. Install it from https://love2d.org or set LOVE_PATH env variable."
|
|
89
105
|
exit 1
|
|
90
106
|
end
|
|
91
107
|
skip_run = false
|
|
92
|
-
love = find_love()
|
|
93
108
|
|
|
94
109
|
if ARGV[0] == "run"
|
|
95
110
|
latcDir = File.join(Dir.pwd, ".latc")
|
|
@@ -102,6 +117,7 @@ if ARGV[0] == "run"
|
|
|
102
117
|
puts "Error: .latc exists but no main.lua found"
|
|
103
118
|
exit 1
|
|
104
119
|
end
|
|
120
|
+
love = find_love()
|
|
105
121
|
exec(love, latcDir)
|
|
106
122
|
elsif ARGV[0] == "build"
|
|
107
123
|
ARGV.shift
|
|
@@ -110,7 +126,7 @@ end
|
|
|
110
126
|
inputFile = ARGV[0] || "main.lat"
|
|
111
127
|
|
|
112
128
|
unless File.exist?(inputFile)
|
|
113
|
-
puts "Error:
|
|
129
|
+
puts "Error: file `#{inputFile}` not found"
|
|
114
130
|
exit 1
|
|
115
131
|
end
|
|
116
132
|
|
|
@@ -121,24 +137,114 @@ if RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/
|
|
|
121
137
|
system("attrib +h #{latcDir}") # -h to unhide
|
|
122
138
|
end
|
|
123
139
|
|
|
140
|
+
inputDir = File.dirname(inputFile)
|
|
124
141
|
basename = File.basename(inputFile, ".*")
|
|
125
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)
|
|
126
147
|
|
|
127
148
|
input = File.read(inputFile)
|
|
149
|
+
confInput = File.read(confPath) if File.exist?(confPath)
|
|
128
150
|
|
|
129
|
-
|
|
151
|
+
confTokens = Tokenizer.new(confInput).tokenize if confInput
|
|
130
152
|
# puts "--- TOKENS ---"
|
|
131
153
|
# puts tokens.map(&:inspect).join("\n")
|
|
132
154
|
|
|
133
|
-
|
|
155
|
+
confTree = Parser.new(confTokens).parse if confTokens
|
|
134
156
|
# puts "--- AST ---"
|
|
135
157
|
# p tree
|
|
136
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
|
+
|
|
137
182
|
generated = Generator.new.generate(tree)
|
|
183
|
+
confGenerated = Generator.new.generate(confTree) if confTree
|
|
138
184
|
# puts "--- LUA OUTPUT ---"
|
|
139
185
|
# puts generated
|
|
140
|
-
File.open(outputFile, 'w') { |file| file.write(generated)}
|
|
141
186
|
|
|
142
|
-
|
|
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
|
|
245
|
+
|
|
246
|
+
File.open(outputFile, 'w') { |file| file.write(generated) }
|
|
247
|
+
File.open(confOutputFile, 'w') { |file| file.write(confGenerated) } if confGenerated
|
|
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
|
@@ -6,13 +6,20 @@ IfNode = Struct.new(:condition, :body, :elif_blocks, :else_body)
|
|
|
6
6
|
ElifBlock = Struct.new(:condition, :body)
|
|
7
7
|
|
|
8
8
|
ImportNode = Struct.new(:location)
|
|
9
|
+
|
|
9
10
|
WhileNode = Struct.new(:statement, :body)
|
|
11
|
+
ForNode = Struct.new(:var, :start, :stop, :step, :body)
|
|
12
|
+
ForPairNode = Struct.new(:key, :val, :t, :body)
|
|
13
|
+
ForIPairNode = Struct.new(:index, :val, :t, :body)
|
|
14
|
+
|
|
10
15
|
IntegerNode = Struct.new(:value)
|
|
16
|
+
FloatNode = Struct.new(:value)
|
|
11
17
|
StringNode = Struct.new(:value)
|
|
18
|
+
|
|
12
19
|
CallNode = Struct.new(:name, :arg_expr)
|
|
13
20
|
VarRefNode = Struct.new(:value)
|
|
14
21
|
VarAssignNode = Struct.new(:name, :value)
|
|
15
|
-
VarSetNode = Struct.new(:
|
|
22
|
+
VarSetNode = Struct.new(:children, :value)
|
|
16
23
|
BinOpNode = Struct.new(:left, :op, :right)
|
|
17
24
|
PrintNode = Struct.new(:args)
|
|
18
25
|
ReturnNode = Struct.new(:statement)
|
|
@@ -28,7 +35,6 @@ SelfNode = Struct.new(:name, :type, :args, :value)
|
|
|
28
35
|
|
|
29
36
|
ErrorCallNode = Struct.new()
|
|
30
37
|
|
|
31
|
-
|
|
32
38
|
LOVE_NAMESPACES = {
|
|
33
39
|
lgraphics: "graphics",
|
|
34
40
|
laudio: "audio",
|
|
@@ -52,9 +58,48 @@ OP_NAMESPACES = {
|
|
|
52
58
|
|
|
53
59
|
}
|
|
54
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
|
+
|
|
55
95
|
class Parser
|
|
56
96
|
def initialize(tokens)
|
|
57
97
|
@tokens = tokens
|
|
98
|
+
@last_line = tokens.first&.line
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def describe_token_type(type)
|
|
102
|
+
FRIENDLY_TOKEN_NAMES[type] || "'#{type}'"
|
|
58
103
|
end
|
|
59
104
|
|
|
60
105
|
def parse
|
|
@@ -79,30 +124,41 @@ class Parser
|
|
|
79
124
|
skip_newlines
|
|
80
125
|
return nil if peek(:end)
|
|
81
126
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
105
159
|
|
|
160
|
+
node.instance_variable_set(:@lat_line, line) if node.respond_to?(:instance_variable_set) && line
|
|
161
|
+
node
|
|
106
162
|
|
|
107
163
|
end
|
|
108
164
|
|
|
@@ -110,6 +166,16 @@ class Parser
|
|
|
110
166
|
consume(:newline) while peek(:newline)
|
|
111
167
|
end
|
|
112
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
|
+
|
|
113
179
|
def parse_import
|
|
114
180
|
consume(:import)
|
|
115
181
|
location = consume(:string).value
|
|
@@ -142,14 +208,8 @@ class Parser
|
|
|
142
208
|
consume(:def)
|
|
143
209
|
name = "#{name}:#{consume(:identifier).value}"
|
|
144
210
|
args = parse_args
|
|
145
|
-
|
|
211
|
+
body = parse_block
|
|
146
212
|
|
|
147
|
-
body = []
|
|
148
|
-
|
|
149
|
-
until peek(:end)
|
|
150
|
-
body << parse_statement
|
|
151
|
-
skip_newlines
|
|
152
|
-
end
|
|
153
213
|
consume(:end)
|
|
154
214
|
ClassDefNode.new(name, args, body)
|
|
155
215
|
end
|
|
@@ -165,18 +225,11 @@ class Parser
|
|
|
165
225
|
name = consume(:identifier).value
|
|
166
226
|
args = parse_args
|
|
167
227
|
|
|
168
|
-
|
|
169
|
-
body = []
|
|
170
|
-
|
|
171
|
-
until peek(:end)
|
|
172
|
-
body << parse_statement
|
|
173
|
-
skip_newlines
|
|
174
|
-
end
|
|
228
|
+
body = parse_block
|
|
175
229
|
consume(:end)
|
|
176
230
|
DefNode.new(type, name, args, body)
|
|
177
231
|
end
|
|
178
232
|
|
|
179
|
-
|
|
180
233
|
def parse_if
|
|
181
234
|
consume(:if)
|
|
182
235
|
condition = parse_expr
|
|
@@ -207,11 +260,8 @@ class Parser
|
|
|
207
260
|
consume(:else)
|
|
208
261
|
skip_newlines
|
|
209
262
|
|
|
210
|
-
else_body =
|
|
211
|
-
|
|
212
|
-
else_body << parse_statement
|
|
213
|
-
skip_newlines
|
|
214
|
-
end
|
|
263
|
+
else_body = parse_block
|
|
264
|
+
|
|
215
265
|
end
|
|
216
266
|
|
|
217
267
|
consume(:end)
|
|
@@ -230,16 +280,56 @@ class Parser
|
|
|
230
280
|
statement = parse_expr
|
|
231
281
|
end
|
|
232
282
|
|
|
233
|
-
|
|
234
|
-
body = []
|
|
283
|
+
body = parse_block
|
|
235
284
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
285
|
+
consume(:end)
|
|
286
|
+
WhileNode.new(statement, body)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def parse_for
|
|
290
|
+
consume(:for)
|
|
291
|
+
var = parse_expr
|
|
292
|
+
consume(:equal)
|
|
293
|
+
start = parse_expr
|
|
294
|
+
consume(:comma)
|
|
295
|
+
stop = parse_expr
|
|
296
|
+
|
|
297
|
+
step = nil
|
|
298
|
+
if peek(:comma)
|
|
299
|
+
consume(:comma)
|
|
300
|
+
step = parse_expr
|
|
239
301
|
end
|
|
302
|
+
body = parse_block
|
|
240
303
|
|
|
241
304
|
consume(:end)
|
|
242
|
-
|
|
305
|
+
ForNode.new(var, start, stop, step, body)
|
|
306
|
+
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def parse_forpairs
|
|
310
|
+
consume(:forpairs)
|
|
311
|
+
key = consume(:identifier).value
|
|
312
|
+
consume(:comma)
|
|
313
|
+
val = consume(:identifier).value
|
|
314
|
+
consume(:in)
|
|
315
|
+
t = parse_expr
|
|
316
|
+
body = parse_block
|
|
317
|
+
consume(:end)
|
|
318
|
+
|
|
319
|
+
ForPairNode.new(key, val, t, body)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def parse_foripairs
|
|
323
|
+
consume(:foripairs)
|
|
324
|
+
index = consume(:identifier).value
|
|
325
|
+
consume(:comma)
|
|
326
|
+
val = consume(:identifier).value
|
|
327
|
+
consume(:in)
|
|
328
|
+
t = parse_expr
|
|
329
|
+
body = parse_block
|
|
330
|
+
consume(:end)
|
|
331
|
+
|
|
332
|
+
ForIPairNode.new(index, val, t, body)
|
|
243
333
|
end
|
|
244
334
|
|
|
245
335
|
def parse_switch
|
|
@@ -283,10 +373,14 @@ class Parser
|
|
|
283
373
|
end
|
|
284
374
|
|
|
285
375
|
def parse_var_set
|
|
286
|
-
|
|
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
|
|
287
381
|
consume(:equal)
|
|
288
382
|
value = parse_expr
|
|
289
|
-
VarSetNode.new(
|
|
383
|
+
VarSetNode.new(children, value)
|
|
290
384
|
end
|
|
291
385
|
|
|
292
386
|
def parse_return
|
|
@@ -306,24 +400,6 @@ class Parser
|
|
|
306
400
|
|
|
307
401
|
left
|
|
308
402
|
end
|
|
309
|
-
# consume(:if)
|
|
310
|
-
|
|
311
|
-
# if peek(:oparen)
|
|
312
|
-
# consume(:oparen)
|
|
313
|
-
# statement = parse_expr
|
|
314
|
-
# consume(:cparen)
|
|
315
|
-
# else
|
|
316
|
-
# statement = parse_expr
|
|
317
|
-
# end
|
|
318
|
-
# skip_newlines
|
|
319
|
-
# body = []
|
|
320
|
-
|
|
321
|
-
# until peek(:elif) peek(:end)
|
|
322
|
-
# body << parse_statement
|
|
323
|
-
# skip_newlines
|
|
324
|
-
# end
|
|
325
|
-
# consume(:end)
|
|
326
|
-
# IfNode.new(statement, body)
|
|
327
403
|
|
|
328
404
|
def parse_args
|
|
329
405
|
consume(:oparen)
|
|
@@ -339,7 +415,16 @@ class Parser
|
|
|
339
415
|
args
|
|
340
416
|
end
|
|
341
417
|
|
|
418
|
+
def parse_block
|
|
419
|
+
skip_newlines
|
|
420
|
+
body = []
|
|
342
421
|
|
|
422
|
+
until peek(:end)
|
|
423
|
+
body << parse_statement
|
|
424
|
+
skip_newlines
|
|
425
|
+
end
|
|
426
|
+
body
|
|
427
|
+
end
|
|
343
428
|
|
|
344
429
|
def parse_additive
|
|
345
430
|
left = parse_multiplicative
|
|
@@ -362,41 +447,30 @@ class Parser
|
|
|
362
447
|
end
|
|
363
448
|
left
|
|
364
449
|
end
|
|
365
|
-
# def parse_operators
|
|
366
|
-
# left = parse_term
|
|
367
|
-
|
|
368
|
-
# left = parse_op(left, :divide)
|
|
369
|
-
# left = parse_op(left, :multiply)
|
|
370
|
-
# left = parse_op(left, :plus)
|
|
371
|
-
# left = parse_op(left, :minus)
|
|
372
|
-
|
|
373
|
-
# left
|
|
374
|
-
# end
|
|
375
|
-
|
|
376
|
-
# def parse_op(left, operator)
|
|
377
|
-
|
|
378
|
-
# while peek(operator)
|
|
379
|
-
# consume(operator)
|
|
380
|
-
# right = parse_term
|
|
381
|
-
# left = BinOpNode.new(left, operator, right)
|
|
382
|
-
# end
|
|
383
|
-
# left
|
|
384
|
-
|
|
385
|
-
# end
|
|
386
450
|
|
|
387
451
|
def parse_term
|
|
452
|
+
skip_newlines
|
|
388
453
|
if peek(:integer)
|
|
389
454
|
IntegerNode.new(consume(:integer).value.to_i)
|
|
390
455
|
|
|
456
|
+
elsif peek(:float)
|
|
457
|
+
FloatNode.new(consume(:float).value.to_f)
|
|
458
|
+
|
|
391
459
|
elsif peek(:identifier) && peek(:oparen, 1)
|
|
392
460
|
parse_call
|
|
393
461
|
|
|
462
|
+
elsif peek(:identifier) && peek(:lbracket, 1)
|
|
463
|
+
parse_array_access
|
|
464
|
+
|
|
465
|
+
elsif peek(:lbrace)
|
|
466
|
+
parse_array
|
|
467
|
+
|
|
394
468
|
elsif peek(:identifier)
|
|
395
469
|
VarRefNode.new(consume(:identifier).value)
|
|
396
470
|
|
|
397
471
|
elsif peek(:string)
|
|
398
472
|
strVal = consume(:string).value
|
|
399
|
-
StringNode.new(strVal[1..-2])
|
|
473
|
+
StringNode.new(strVal[1..-2])
|
|
400
474
|
|
|
401
475
|
elsif peek(:oparen)
|
|
402
476
|
# consume(:oparen)
|
|
@@ -407,15 +481,19 @@ class Parser
|
|
|
407
481
|
|
|
408
482
|
first = parse_expr
|
|
409
483
|
items = [first]
|
|
410
|
-
until peek(:identifier)
|
|
484
|
+
until peek(:identifier) || peek(:dequal)
|
|
411
485
|
break
|
|
412
486
|
end
|
|
413
487
|
|
|
414
|
-
if !peek(:identifier) && peek(:or)
|
|
488
|
+
if !peek(:identifier) && peek(:or) || peek(:and)
|
|
415
489
|
while peek(:or)
|
|
416
490
|
consume(:or)
|
|
417
491
|
items << parse_expr
|
|
418
492
|
end
|
|
493
|
+
while peek(:and)
|
|
494
|
+
consume(:and)
|
|
495
|
+
items << parse_expr
|
|
496
|
+
end
|
|
419
497
|
consume(:cparen)
|
|
420
498
|
return AndOrListNode.new(items)
|
|
421
499
|
end
|
|
@@ -426,14 +504,27 @@ class Parser
|
|
|
426
504
|
elsif peek(:self)
|
|
427
505
|
parse_self_node
|
|
428
506
|
|
|
429
|
-
|
|
430
|
-
parse_array
|
|
507
|
+
|
|
431
508
|
|
|
432
509
|
|
|
433
510
|
elsif LOVE_NAMESPACES.keys.include?(peek_type)
|
|
434
511
|
parse_love_call
|
|
435
512
|
else
|
|
436
|
-
|
|
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"
|
|
437
528
|
|
|
438
529
|
end
|
|
439
530
|
end
|
|
@@ -444,7 +535,8 @@ class Parser
|
|
|
444
535
|
prefix = @tokens.shift.type
|
|
445
536
|
namespace = LOVE_NAMESPACES[prefix]
|
|
446
537
|
|
|
447
|
-
name =
|
|
538
|
+
name = @tokens.shift.value
|
|
539
|
+
|
|
448
540
|
args = parse_arg_expr
|
|
449
541
|
|
|
450
542
|
LoveCallNode.new(namespace, name, args)
|
|
@@ -499,32 +591,57 @@ class Parser
|
|
|
499
591
|
end
|
|
500
592
|
|
|
501
593
|
def parse_array
|
|
502
|
-
consume(:
|
|
594
|
+
consume(:lbrace)
|
|
595
|
+
skip_newlines
|
|
503
596
|
elements = []
|
|
504
|
-
unless peek(:
|
|
597
|
+
unless peek(:rbrace)
|
|
505
598
|
elements << parse_expr
|
|
599
|
+
skip_newlines
|
|
506
600
|
while peek(:comma)
|
|
507
601
|
consume(:comma)
|
|
602
|
+
skip_newlines
|
|
508
603
|
elements << parse_expr
|
|
604
|
+
skip_newlines
|
|
509
605
|
end
|
|
510
606
|
end
|
|
511
|
-
|
|
607
|
+
skip_newlines
|
|
608
|
+
consume(:rbrace)
|
|
512
609
|
ArrayNode.new(elements)
|
|
513
610
|
end
|
|
514
611
|
|
|
515
612
|
|
|
516
613
|
def parse_array_access
|
|
517
614
|
name = consume(:identifier).value
|
|
518
|
-
consume(:
|
|
615
|
+
consume(:lbracket)
|
|
519
616
|
index = parse_expr
|
|
520
|
-
consume(:
|
|
617
|
+
consume(:rbracket)
|
|
521
618
|
ArrayAccessNode.new(name, index)
|
|
522
619
|
end
|
|
523
620
|
|
|
524
621
|
def consume(type)
|
|
525
622
|
token = @tokens.shift
|
|
526
|
-
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
|
|
527
643
|
token
|
|
644
|
+
|
|
528
645
|
end
|
|
529
646
|
|
|
530
647
|
def peek(type, offset = 0)
|
|
@@ -534,4 +651,4 @@ class Parser
|
|
|
534
651
|
def peek_type(offset = 0)
|
|
535
652
|
@tokens[offset]&.type
|
|
536
653
|
end
|
|
537
|
-
end
|
|
654
|
+
end
|
data/compiler/tokenizer.rb
CHANGED
|
@@ -1,58 +1,68 @@
|
|
|
1
|
-
Token = Struct.new(:type, :value)
|
|
1
|
+
Token = Struct.new(:type, :value, :line, :column)
|
|
2
2
|
|
|
3
3
|
class Tokenizer
|
|
4
4
|
|
|
5
5
|
TOKEN_TYPES = [
|
|
6
|
+
|
|
7
|
+
#love
|
|
8
|
+
[:lgraphics, /-G:/],
|
|
9
|
+
[:laudio, /-A:/],
|
|
10
|
+
[:ldata, /-D:/],
|
|
11
|
+
[:levent, /-E:/],
|
|
12
|
+
[:lfilesystem, /-FS:/],
|
|
13
|
+
[:lfont, /-F:/],
|
|
14
|
+
[:limage, /-I:/],
|
|
15
|
+
[:ljoystick, /-J:/],
|
|
16
|
+
[:lmouse, /-M:/],
|
|
17
|
+
[:lkeyboard, /-K:/],
|
|
18
|
+
[:love, /\blove\b/],
|
|
19
|
+
|
|
6
20
|
[:local, /\bnat\b/],
|
|
7
21
|
[:def, /\bcall\b/],
|
|
8
22
|
[:end, /\bdone\b/],
|
|
9
23
|
[:if, /\bif\b/],
|
|
10
24
|
[:elif, /\belif\b/],
|
|
11
25
|
[:else, /\belse\b/],
|
|
26
|
+
|
|
12
27
|
[:while, /\bwhen\b/],
|
|
28
|
+
[:for, /\bfor\b/],
|
|
29
|
+
[:forpairs, /\beach\b/],
|
|
30
|
+
[:foripairs, /\bseq\b/],
|
|
31
|
+
|
|
13
32
|
[:print, /\bprint\b/],
|
|
14
33
|
[:return, /\breturn\b/],
|
|
15
34
|
[:or, /\bor\b/],
|
|
16
35
|
[:and, /\band\b/],
|
|
36
|
+
[:in, /\bas\b/],
|
|
17
37
|
[:switch, /\bswitch\b/],
|
|
18
38
|
[:to, /\bto\b/],
|
|
19
39
|
[:class, /\bclass\b/],
|
|
20
40
|
[:self, /\bself\b/],
|
|
21
41
|
[:import, /\bbring\b/],
|
|
22
42
|
|
|
23
|
-
|
|
24
|
-
[:lgraphics, /-G:/],
|
|
25
|
-
[:laudio, /-A:/],
|
|
26
|
-
[:ldata, /-D:/],
|
|
27
|
-
[:levent, /-E:/],
|
|
28
|
-
[:lfilesystem, /-FS:/],
|
|
29
|
-
[:lfont, /-F:/],
|
|
30
|
-
[:limage, /-I:/],
|
|
31
|
-
[:ljoystick, /-J:/],
|
|
32
|
-
[:lmouse, /-M:/],
|
|
33
|
-
[:lkeyboard, /-K:/],
|
|
34
|
-
[:love, /\blove\b/],
|
|
43
|
+
|
|
35
44
|
|
|
36
45
|
# thingies
|
|
37
|
-
[:identifier, /\b[a-zA-
|
|
46
|
+
[:identifier, /\b[a-zA-Z_][a-zA-Z0-9_]*\b/],
|
|
38
47
|
[:string, /"([^"]*)"/],
|
|
39
|
-
[:
|
|
48
|
+
[:float, /(?:\d+\.\d*|\.\d+)/],
|
|
49
|
+
[:integer, /\b\d+\b/],
|
|
40
50
|
[:oparen, /\(/],
|
|
41
51
|
[:cparen, /\)/],
|
|
42
|
-
[:
|
|
43
|
-
[:
|
|
44
|
-
[:
|
|
45
|
-
[:
|
|
52
|
+
[:lbracket, /\[/],
|
|
53
|
+
[:rbracket, /\]/],
|
|
54
|
+
[:lbrace, /\{/],
|
|
55
|
+
[:rbrace, /\}/],
|
|
46
56
|
[:comma, /,/],
|
|
47
57
|
[:dot, /\./],
|
|
48
58
|
[:colon, /:/],
|
|
49
59
|
|
|
50
60
|
# operators
|
|
51
61
|
[:dequal, /==/],
|
|
52
|
-
[:greater, />/],
|
|
53
|
-
[:lesser, /</],
|
|
54
62
|
[:grequal, />=/],
|
|
55
63
|
[:lequal, /<=/],
|
|
64
|
+
[:greater, />/],
|
|
65
|
+
[:lesser, /</],
|
|
56
66
|
[:equal, /=/],
|
|
57
67
|
[:divide, /\//],
|
|
58
68
|
[:multiply, /\*/],
|
|
@@ -63,8 +73,12 @@ class Tokenizer
|
|
|
63
73
|
|
|
64
74
|
]
|
|
65
75
|
|
|
66
|
-
def initialize(code)
|
|
67
|
-
@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}"
|
|
68
82
|
end
|
|
69
83
|
|
|
70
84
|
def tokenize
|
|
@@ -79,19 +93,39 @@ class Tokenizer
|
|
|
79
93
|
def tokenize_token
|
|
80
94
|
TOKEN_TYPES.each do |type, regex|
|
|
81
95
|
anchored = /\A(#{regex})/
|
|
82
|
-
|
|
83
|
-
|
|
96
|
+
|
|
84
97
|
if @code =~ anchored
|
|
85
98
|
value = $1
|
|
86
99
|
if type == :space
|
|
87
100
|
@code = @code[value.length..-1]
|
|
101
|
+
@column += value.length
|
|
88
102
|
return tokenize_token
|
|
89
103
|
end
|
|
104
|
+
|
|
105
|
+
token = Token.new(type, value, @line)
|
|
90
106
|
@code = @code[value.length..-1]
|
|
91
|
-
|
|
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
|
|
92
117
|
end
|
|
93
118
|
end
|
|
94
119
|
|
|
95
|
-
|
|
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}"
|
|
96
130
|
end
|
|
97
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
|