lat 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8e6c5e2d1956cf589b2b5806eb6e51c66641c25229891b78c185cb5176c56efe
4
+ data.tar.gz: feab0e27cd58b07e5c98c172131c4266bbbeb5a261fdd796e8f7dd6624d448ce
5
+ SHA512:
6
+ metadata.gz: d4d9151124f40dc9e43ea4784b72c771a3d0df57072dc195cb14d88827adc311d57d73134dbeec57d351f0abcce88310756c073801d6004e85217a4a4a0da725
7
+ data.tar.gz: 7edc1cfecf74c3d99dc4b3379a9e511a64ddc325eb0f5c43b1cc7268ab37effbfbcdf760c65e9967edf2cded23a99dc925ca16e33c5c1daba04b711a02336463
data/bin/lat ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.join(__dir__, "../compiler")
3
+ require_relative "../compiler/compile"
@@ -0,0 +1,322 @@
1
+ class Generator
2
+ def generate(node)
3
+ if node.is_a?(Array)
4
+ return node.map { |n| generate(n) }.join("\n")
5
+ end
6
+
7
+ case node
8
+
9
+ when ImportNode
10
+ "require %s" % [
11
+ node.location
12
+ ]
13
+ when ClassNode
14
+ out = []
15
+ out << "local #{node.name} = {}"
16
+ out << "#{node.name}.__index = #{node.name}"
17
+
18
+ out << ""
19
+
20
+ # entry point constructor
21
+ out << "function #{node.name}:new()"
22
+ out << " local instance = setmetatable({}, self)"
23
+ node.body.each do |stmt|
24
+ if stmt.is_a?(VarAssignNode)
25
+ out << " instance.#{stmt.name} = #{generate(stmt.value)}"
26
+ else
27
+ out << "#{generate(stmt)}"
28
+ end
29
+ end
30
+
31
+ out << " return instance"
32
+ out << "end"
33
+ out << ""
34
+
35
+ #methods
36
+ node.defs.each do |d|
37
+ out << generate(d)
38
+ out << ""
39
+ end
40
+
41
+ out.join("\n")
42
+ when ClassDefNode
43
+ body_code = node.body.map { |n| generate(n) }.join("\n ")
44
+ "function %s(%s)\n %s\nend" % [node.name, node.args.join(","), body_code]
45
+
46
+ 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
53
+
54
+ "function #{node.type == "love"? "love." : ""}%s(%s)\n %s \nend" % [
55
+ node.name,
56
+ node.args.join(","),
57
+ body_code
58
+ ]
59
+
60
+ when IfNode
61
+ compiled = ""
62
+
63
+ first = node.condition
64
+ compiled << "if #{generate(first)} then\n "
65
+ compiled << node.body.map { |n| generate (n) }.join("\n")
66
+
67
+ node.elif_blocks.each do |c|
68
+ compiled << "\nelseif #{generate(c.condition)} then\n "
69
+ compiled << c.body.map { |n| generate (n) }.join("\n")
70
+ end
71
+
72
+ if node.else_body
73
+ compiled << "\nelse\n "
74
+ compiled << node.else_body.map { |n| generate (n) }.join("\n")
75
+ end
76
+
77
+ compiled << "\nend\n"
78
+ compiled
79
+
80
+ 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
87
+
88
+ "while %s do \n %s \nend" % [
89
+ generate(node.statement),
90
+ body_code
91
+ ]
92
+
93
+ when SwitchNode
94
+ compiled = ""
95
+ node.cases.each_with_index do |c, i|
96
+ if i == 0
97
+ compiled << "if #{generate(c.match)} == #{generate(node.value)} then\n"
98
+ else
99
+ compiled << "elseif #{generate(c.match)} == #{generate(node.value)} then\n"
100
+ end
101
+
102
+ body_code = c.body.map { |b| generate(b) }.join("\n")
103
+ compiled << " #{body_code}\n"
104
+
105
+ end
106
+ compiled << "end\n"
107
+ compiled
108
+
109
+
110
+ when CallNode
111
+ "%s(%s)" % [
112
+ node.name,
113
+ node.arg_expr.map { |expr| generate(expr) }.join(",")
114
+ ]
115
+
116
+ when PrintNode
117
+ "print(%s)" % [
118
+ node.args.map { |expr| generate(expr) }.join(",")
119
+ ]
120
+
121
+ when VarAssignNode
122
+ "local %s = %s" % [
123
+ node.name,
124
+ generate(node.value)
125
+ ]
126
+
127
+ when VarSetNode
128
+ "%s = %s" % [
129
+ node.name,
130
+ generate(node.value)
131
+ ]
132
+
133
+ when BinOpNode
134
+ if node.right.is_a?(AndOrListNode) && node.op == :dequal
135
+ left = generate(node.left)
136
+ parts = node.right.items.map{ |item| "#{left} == #{generate(item)}"}
137
+ return "(#{parts.join(' or ')})"
138
+ end
139
+ "(#{generate(node.left)} #{OP_NAMESPACES[node.op]} #{generate(node.right)})"
140
+
141
+ when SelfNode
142
+ selfstring = "self#{node.type}#{node.name}"
143
+
144
+ if !node.args.empty?
145
+ args_string = node.args.map {|expr| generate(expr) }.join(",")
146
+ selfstring += "(#{args_string})"
147
+ else
148
+ selfstring += " = %s" % [
149
+ generate(node.value)
150
+ ]
151
+ end
152
+ selfstring
153
+
154
+ when LoveCallNode
155
+ "love.%s.%s(%s)" % [
156
+ node.namespace,
157
+ node.name,
158
+ node.args.map { |expr| generate(expr) }.join(",")
159
+ ]
160
+
161
+ when ReturnNode
162
+ "return %s" % [
163
+ generate(node.statement)
164
+ ]
165
+
166
+ when VarRefNode
167
+ node.value
168
+
169
+ when IntegerNode
170
+ node.value.to_s
171
+
172
+ when StringNode
173
+ "\"%s\"" % [
174
+ node.value
175
+ ]
176
+
177
+ when ArrayNode
178
+ elements = node.elements.map { |e| generate(e) }.join(", ")
179
+ "{#{elements}}"
180
+
181
+ when ArrayAccessNode
182
+ "#{node.name}[#{generate(node.index)}]"
183
+
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
+ else
319
+ raise "Unknown node type: #{node.class}"
320
+ end
321
+ end
322
+ end
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "tokenizer"
4
+ require_relative "parser"
5
+ require_relative "codegen"
6
+
7
+ if ARGV.empty?
8
+ puts "Usage: lat <input.lat> [output.lua]"
9
+ exit 1
10
+ end
11
+
12
+ def find_love
13
+ candidates = [
14
+ "love",
15
+ "C:/Program Files/LOVE/love.exe",
16
+ "C:/Program Files (x86)/LOVE/love.exe",
17
+ ENV["LOVE_PATH"]
18
+ ].compact
19
+
20
+ candidates.each do |path|
21
+ return path if system("where #{path} >nul 2>&1") || File.exist?(path.to_s)
22
+ end
23
+
24
+ puts "Error: LOVE2D not found. Install it from https://love2d.org or set LOVE_PATH env variable."
25
+ exit 1
26
+ end
27
+ skip_run = false
28
+ love = find_love()
29
+
30
+ if ARGV[0] == "run"
31
+ latcDir = File.join(Dir.pwd, ".latc")
32
+ unless Dir.exist?(latcDir)
33
+ puts "Error: no .latc folder foound. Compile something first \n 'lat <input.lat> [main.lua]"
34
+ exit 1
35
+ end
36
+
37
+ unless File.exist?(File.join(latcDir, "main.lua"))
38
+ puts "Error: .latc exists but no main.lua found"
39
+ exit 1
40
+ end
41
+ exec(love, latcDir)
42
+ elsif ARGV[0] == "build"
43
+ ARGV.shift
44
+ skip_run = true
45
+ end
46
+ inputFile = ARGV[0] || "main.lat"
47
+
48
+ unless File.exist?(inputFile)
49
+ puts "Error: fil `#{inputFile}` not found"
50
+ exit 1
51
+ end
52
+
53
+ latcDir = File.join(Dir.pwd, ".latc")
54
+ Dir.mkdir(latcDir) unless Dir.exist?(latcDir)
55
+
56
+ if RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/
57
+ system("attrib +h #{latcDir}") # -h to unhide
58
+ end
59
+
60
+ basename = File.basename(inputFile, ".*")
61
+ outputFile = File.join(latcDir, basename == "main" ? "main.lua" : "#{basename}.lua")
62
+
63
+ input = File.read(inputFile)
64
+
65
+ tokens = Tokenizer.new(input).tokenize
66
+ # puts "--- TOKENS ---"
67
+ # puts tokens.map(&:inspect).join("\n")
68
+
69
+ tree = Parser.new(tokens).parse
70
+ # puts "--- AST ---"
71
+ # p tree
72
+
73
+ generated = Generator.new.generate(tree)
74
+ # puts "--- LUA OUTPUT ---"
75
+ # puts generated
76
+ File.open(outputFile, 'w') { |file| file.write(generated)}
77
+
78
+ exec(love, latcDir) unless skip_run
79
+
80
+ # ln -s "$(pwd)/compiler/compile.rb" /usr/local/bin/lat
@@ -0,0 +1,537 @@
1
+ DefNode = Struct.new(:type, :name, :args, :body)
2
+ ClassNode = Struct.new(:name, :defs, :body )
3
+ ClassDefNode = Struct.new(:name, :args, :body)
4
+
5
+ IfNode = Struct.new(:condition, :body, :elif_blocks, :else_body)
6
+ ElifBlock = Struct.new(:condition, :body)
7
+
8
+ ImportNode = Struct.new(:location)
9
+ WhileNode = Struct.new(:statement, :body)
10
+ IntegerNode = Struct.new(:value)
11
+ StringNode = Struct.new(:value)
12
+ CallNode = Struct.new(:name, :arg_expr)
13
+ VarRefNode = Struct.new(:value)
14
+ VarAssignNode = Struct.new(:name, :value)
15
+ VarSetNode = Struct.new(:name, :value)
16
+ BinOpNode = Struct.new(:left, :op, :right)
17
+ PrintNode = Struct.new(:args)
18
+ ReturnNode = Struct.new(:statement)
19
+ AndOrListNode = Struct.new(:items)
20
+ SwitchNode = Struct.new(:value, :cases)
21
+ CaseNode = Struct.new(:match, :body)
22
+
23
+ ArrayNode = Struct.new(:elements)
24
+ ArrayAccessNode = Struct.new(:name, :index)
25
+
26
+ LoveCallNode = Struct.new(:namespace, :name, :args)
27
+ SelfNode = Struct.new(:name, :type, :args, :value)
28
+
29
+ ErrorCallNode = Struct.new()
30
+
31
+
32
+ LOVE_NAMESPACES = {
33
+ lgraphics: "graphics",
34
+ laudio: "audio",
35
+ ldata: "data",
36
+ levent: "event",
37
+ lfilesystem: "filesystem",
38
+ lfont: "font",
39
+ limage: "image",
40
+ ljoystick: "joystick",
41
+ lmouse: "mouse",
42
+ lkeyboard: "keyboard"
43
+ }
44
+
45
+ OP_NAMESPACES = {
46
+ dequal: "==",
47
+ equal: "=",
48
+ divide: "/",
49
+ multiply: "*",
50
+ plus: "+",
51
+ minus: "-",
52
+
53
+ }
54
+
55
+ class Parser
56
+ def initialize(tokens)
57
+ @tokens = tokens
58
+ end
59
+
60
+ def parse
61
+ # if peek(:def)
62
+ # parse_def
63
+ # else
64
+ # parse_statement
65
+ # end
66
+ statements = []
67
+ while @tokens.any?
68
+ skip_newlines
69
+ break if @tokens.empty?
70
+ statements << parse_statement
71
+ skip_newlines
72
+ end
73
+ # statements << ErrorCallNode.new()
74
+ statements
75
+ end
76
+
77
+ def parse_statement
78
+
79
+ skip_newlines
80
+ return nil if peek(:end)
81
+
82
+ if peek(:import) then
83
+ parse_import
84
+ elsif peek(:class) then
85
+ parse_class
86
+ elsif peek(:def)
87
+ parse_def
88
+ elsif peek(:if)
89
+ parse_if
90
+ elsif peek(:while)
91
+ parse_while
92
+ elsif peek(:switch)
93
+ parse_switch
94
+ elsif peek(:print)
95
+ parse_print
96
+ elsif peek(:local)
97
+ parse_var_assign
98
+ elsif peek(:identifier) && peek(:equal, 1)
99
+ parse_var_set
100
+ elsif peek(:return)
101
+ parse_return
102
+ else
103
+ parse_expr
104
+ end
105
+
106
+
107
+ end
108
+
109
+ def skip_newlines
110
+ consume(:newline) while peek(:newline)
111
+ end
112
+
113
+ def parse_import
114
+ consume(:import)
115
+ location = consume(:string).value
116
+
117
+ ImportNode.new(location)
118
+ end
119
+
120
+ def parse_class
121
+ consume(:class)
122
+ name = consume(:identifier).value
123
+
124
+ skip_newlines
125
+
126
+ defs = []
127
+ body = []
128
+ until peek(:end)
129
+ if peek(:def)
130
+ defs << parse_class_def(name)
131
+ else
132
+ body << parse_statement
133
+ end
134
+ skip_newlines
135
+ end
136
+ consume (:end)
137
+ ClassNode.new(name, defs, body)
138
+
139
+ end
140
+
141
+ def parse_class_def(name)
142
+ consume(:def)
143
+ name = "#{name}:#{consume(:identifier).value}"
144
+ args = parse_args
145
+ skip_newlines
146
+
147
+ body = []
148
+
149
+ until peek(:end)
150
+ body << parse_statement
151
+ skip_newlines
152
+ end
153
+ consume(:end)
154
+ ClassDefNode.new(name, args, body)
155
+ end
156
+
157
+ def parse_def
158
+ consume(:def)
159
+ type = "normal"
160
+ if peek(:love)
161
+ consume(:love)
162
+ type = "love"
163
+ consume(:dot)
164
+ end
165
+ name = consume(:identifier).value
166
+ args = parse_args
167
+
168
+ skip_newlines
169
+ body = []
170
+
171
+ until peek(:end)
172
+ body << parse_statement
173
+ skip_newlines
174
+ end
175
+ consume(:end)
176
+ DefNode.new(type, name, args, body)
177
+ end
178
+
179
+
180
+ def parse_if
181
+ consume(:if)
182
+ condition = parse_expr
183
+ skip_newlines
184
+ if_body = []
185
+ while !peek(:elif) && !peek(:else) && !peek(:end)
186
+ if_body << parse_statement
187
+ skip_newlines
188
+ end
189
+
190
+ elif_blocks = []
191
+ while peek(:elif)
192
+ consume(:elif)
193
+ elif_condition = parse_expr
194
+ skip_newlines
195
+
196
+ elif_body = []
197
+ while !peek(:elif) && !peek(:else) && !peek(:end)
198
+ elif_body << parse_statement
199
+ skip_newlines
200
+ end
201
+
202
+ elif_blocks << ElifBlock.new(elif_condition, elif_body)
203
+ end
204
+
205
+ else_body = nil
206
+ if peek(:else)
207
+ consume(:else)
208
+ skip_newlines
209
+
210
+ else_body = []
211
+ while !peek(:end)
212
+ else_body << parse_statement
213
+ skip_newlines
214
+ end
215
+ end
216
+
217
+ consume(:end)
218
+
219
+ IfNode.new(condition, if_body, elif_blocks, else_body)
220
+ end
221
+
222
+ def parse_while
223
+ consume(:while)
224
+
225
+ if peek(:oparen)
226
+ consume(:oparen)
227
+ statement = parse_expr
228
+ consume(:cparen)
229
+ else
230
+ statement = parse_expr
231
+ end
232
+
233
+ skip_newlines
234
+ body = []
235
+
236
+ until peek(:end)
237
+ body << parse_statement
238
+ skip_newlines
239
+ end
240
+
241
+ consume(:end)
242
+ WhileNode.new(statement, body)
243
+ end
244
+
245
+ def parse_switch
246
+ consume(:switch)
247
+ value = parse_expr
248
+ skip_newlines
249
+
250
+ cases = []
251
+
252
+ while peek(:to)
253
+ consume(:to)
254
+ match = parse_expr
255
+ skip_newlines
256
+
257
+ body = []
258
+
259
+ until peek(:to) || peek(:end)
260
+ body << parse_statement
261
+ skip_newlines
262
+ end
263
+
264
+ cases << CaseNode.new(match, body)
265
+ end
266
+
267
+ consume(:end)
268
+ SwitchNode.new(value, cases)
269
+ end
270
+
271
+ def parse_print
272
+ consume(:print)
273
+ args = parse_arg_expr
274
+ PrintNode.new(args)
275
+ end
276
+
277
+ def parse_var_assign
278
+ consume(:local)
279
+ name = consume(:identifier).value
280
+ consume(:equal)
281
+ value = parse_expr
282
+ VarAssignNode.new(name, value)
283
+ end
284
+
285
+ def parse_var_set
286
+ name = consume(:identifier).value
287
+ consume(:equal)
288
+ value = parse_expr
289
+ VarSetNode.new(name, value)
290
+ end
291
+
292
+ def parse_return
293
+ consume(:return)
294
+ statement = parse_expr
295
+ ReturnNode.new(statement)
296
+ end
297
+
298
+ def parse_expr
299
+ left = parse_additive
300
+
301
+ while peek(:dequal)
302
+ op = consume(:dequal)
303
+ right = parse_additive
304
+ left = BinOpNode.new(left, op.type, right)
305
+ end
306
+
307
+ left
308
+ 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
+
328
+ def parse_args
329
+ consume(:oparen)
330
+ args = []
331
+ if peek(:identifier)
332
+ args << consume(:identifier).value
333
+ while peek(:comma)
334
+ consume(:comma)
335
+ args << consume(:identifier).value
336
+ end
337
+ end
338
+ consume(:cparen)
339
+ args
340
+ end
341
+
342
+
343
+
344
+ def parse_additive
345
+ left = parse_multiplicative
346
+
347
+ while peek(:plus) || peek(:minus)
348
+ op = @tokens.shift.type
349
+ right = parse_multiplicative
350
+ left = BinOpNode.new(left, op, right)
351
+ end
352
+ left
353
+ end
354
+
355
+ def parse_multiplicative
356
+ left = parse_term
357
+
358
+ while peek(:multiply) || peek(:divide)
359
+ op = @tokens.shift.type
360
+ right = parse_term
361
+ left = BinOpNode.new(left, op, right)
362
+ end
363
+ left
364
+ 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
+
387
+ def parse_term
388
+ if peek(:integer)
389
+ IntegerNode.new(consume(:integer).value.to_i)
390
+
391
+ elsif peek(:identifier) && peek(:oparen, 1)
392
+ parse_call
393
+
394
+ elsif peek(:identifier)
395
+ VarRefNode.new(consume(:identifier).value)
396
+
397
+ elsif peek(:string)
398
+ strVal = consume(:string).value
399
+ StringNode.new(strVal[1..-2])
400
+
401
+ elsif peek(:oparen)
402
+ # consume(:oparen)
403
+ # expr = parse_expr
404
+ # consume(:cparen)
405
+ # expr
406
+ consume(:oparen)
407
+
408
+ first = parse_expr
409
+ items = [first]
410
+ until peek(:identifier) && peek(:or) && peek(:dequal) && peek(:cparen) && peek(:plus)
411
+ break
412
+ end
413
+
414
+ if !peek(:identifier) && peek(:or)
415
+ while peek(:or)
416
+ consume(:or)
417
+ items << parse_expr
418
+ end
419
+ consume(:cparen)
420
+ return AndOrListNode.new(items)
421
+ end
422
+
423
+ expr = first
424
+ consume(:cparen)
425
+ expr
426
+ elsif peek(:self)
427
+ parse_self_node
428
+
429
+ elsif peek(:coparen)
430
+ parse_array
431
+
432
+
433
+ elsif LOVE_NAMESPACES.keys.include?(peek_type)
434
+ parse_love_call
435
+ else
436
+ raise "Unexpected token #{@tokens[0].inspect} in term"
437
+
438
+ end
439
+ end
440
+
441
+
442
+
443
+ def parse_love_call
444
+ prefix = @tokens.shift.type
445
+ namespace = LOVE_NAMESPACES[prefix]
446
+
447
+ name = consume(:identifier).value
448
+ args = parse_arg_expr
449
+
450
+ LoveCallNode.new(namespace, name, args)
451
+ end
452
+
453
+ def parse_self_node
454
+ consume(:self)
455
+
456
+ type = ""
457
+ if peek(:dot)
458
+ consume(:dot)
459
+ type = "."
460
+ elsif peek(:colon)
461
+ consume(:colon)
462
+ type = ":"
463
+ end
464
+
465
+ name = consume(:identifier).value
466
+
467
+ if peek(:oparen)
468
+ args = parse_arg_expr # parse args are for func defs and parse args expr is for call
469
+ else
470
+ args = []
471
+ end
472
+
473
+ if (type == ".") && peek(:equal)
474
+ consume(:equal)
475
+ value = parse_expr
476
+ end
477
+
478
+ SelfNode.new(name, type, args, value)
479
+ end
480
+
481
+ def parse_call
482
+ name = consume(:identifier).value
483
+ arg_expr = parse_arg_expr
484
+ CallNode.new(name, arg_expr)
485
+ end
486
+
487
+ def parse_arg_expr
488
+ consume(:oparen)
489
+ args = []
490
+ unless peek(:cparen)
491
+ args << parse_expr
492
+ while peek(:comma)
493
+ consume(:comma)
494
+ args << parse_expr
495
+ end
496
+ end
497
+ consume(:cparen)
498
+ args
499
+ end
500
+
501
+ def parse_array
502
+ consume(:coparen)
503
+ elements = []
504
+ unless peek(:ccparen)
505
+ elements << parse_expr
506
+ while peek(:comma)
507
+ consume(:comma)
508
+ elements << parse_expr
509
+ end
510
+ end
511
+ consume(:ccparen)
512
+ ArrayNode.new(elements)
513
+ end
514
+
515
+
516
+ def parse_array_access
517
+ name = consume(:identifier).value
518
+ consume(:soparen)
519
+ index = parse_expr
520
+ consume(:scparen)
521
+ ArrayAccessNode.new(name, index)
522
+ end
523
+
524
+ def consume(type)
525
+ token = @tokens.shift
526
+ raise "Expected #{type}, got #{token.type}" unless token.type == type
527
+ token
528
+ end
529
+
530
+ def peek(type, offset = 0)
531
+ @tokens[offset]&.type == type
532
+ end
533
+
534
+ def peek_type(offset = 0)
535
+ @tokens[offset]&.type
536
+ end
537
+ end
@@ -0,0 +1,97 @@
1
+ Token = Struct.new(:type, :value)
2
+
3
+ class Tokenizer
4
+
5
+ TOKEN_TYPES = [
6
+ [:local, /\bnat\b/],
7
+ [:def, /\bcall\b/],
8
+ [:end, /\bdone\b/],
9
+ [:if, /\bif\b/],
10
+ [:elif, /\belif\b/],
11
+ [:else, /\belse\b/],
12
+ [:while, /\bwhen\b/],
13
+ [:print, /\bprint\b/],
14
+ [:return, /\breturn\b/],
15
+ [:or, /\bor\b/],
16
+ [:and, /\band\b/],
17
+ [:switch, /\bswitch\b/],
18
+ [:to, /\bto\b/],
19
+ [:class, /\bclass\b/],
20
+ [:self, /\bself\b/],
21
+ [:import, /\bbring\b/],
22
+
23
+ #love
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/],
35
+
36
+ # thingies
37
+ [:identifier, /\b[a-zA-Z]+\b/],
38
+ [:string, /"([^"]*)"/],
39
+ [:integer, /\b[0-9]+\b/],
40
+ [:oparen, /\(/],
41
+ [:cparen, /\)/],
42
+ [:soparen, /\[/],
43
+ [:scparen, /\]/],
44
+ [:coparen, /\{/],
45
+ [:ccparen, /\}/],
46
+ [:comma, /,/],
47
+ [:dot, /\./],
48
+ [:colon, /:/],
49
+
50
+ # operators
51
+ [:dequal, /==/],
52
+ [:greater, />/],
53
+ [:lesser, /</],
54
+ [:grequal, />=/],
55
+ [:lequal, /<=/],
56
+ [:equal, /=/],
57
+ [:divide, /\//],
58
+ [:multiply, /\*/],
59
+ [:plus, /\+/],
60
+ [:minus, /\-/],
61
+ [:newline, /\n+/],
62
+ [:space, /[ \t]+/],
63
+
64
+ ]
65
+
66
+ def initialize(code)
67
+ @code = code
68
+ end
69
+
70
+ def tokenize
71
+ tokens = []
72
+ until @code.empty?
73
+ tokens << tokenize_token
74
+ # @code = @code.strip
75
+ end
76
+ tokens
77
+ end
78
+
79
+ def tokenize_token
80
+ TOKEN_TYPES.each do |type, regex|
81
+ anchored = /\A(#{regex})/
82
+
83
+
84
+ if @code =~ anchored
85
+ value = $1
86
+ if type == :space
87
+ @code = @code[value.length..-1]
88
+ return tokenize_token
89
+ end
90
+ @code = @code[value.length..-1]
91
+ return Token.new(type, value)
92
+ end
93
+ end
94
+
95
+ raise "Couldn't match token on #{@code.inspect}"
96
+ end
97
+ end
metadata ADDED
@@ -0,0 +1,43 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - JakeOJeff
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ executables:
13
+ - lat
14
+ extensions: []
15
+ extra_rdoc_files: []
16
+ files:
17
+ - bin/lat
18
+ - compiler/codegen.rb
19
+ - compiler/compile.rb
20
+ - compiler/parser.rb
21
+ - compiler/tokenizer.rb
22
+ homepage: https://github.com/JakeOJeff/lat
23
+ licenses:
24
+ - MIT
25
+ metadata: {}
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ requirements: []
40
+ rubygems_version: 3.6.9
41
+ specification_version: 4
42
+ summary: A language that compiles to Lua/LÖVE2D
43
+ test_files: []