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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 79c0178cf5e3b3e1168af97aec3a318b082b25c43a2835464682f6bdcdf545f7
4
- data.tar.gz: ef5b003a8c15b86ffd3575844232bfe909b084844587a114312b35d5d76e113b
3
+ metadata.gz: c2053d16f88f07ebebeb98b8132ae6bf6adfaecb06d1573be7c0639b73755837
4
+ data.tar.gz: 02063ec4e1819c2743e426bdbc4b642f4e0862b1ce4cdfd4b02e2aba69abc380
5
5
  SHA512:
6
- metadata.gz: a8377175fe6c7cb97521c4a51c6a7a5b2ebe0aa1a9bb9c3a0f19299fdd2e7d9f08fc9c5efe86098b716063ea656293d4612fc5575cfb39481d81fadd2282210b
7
- data.tar.gz: 50e003a2f76a40427759f032ce0da3355422c481d6089f99d2deb1f1d384ca137478ffc0b14dd64c74ab3c856edc09c19ac99dc8b3665214d608e6b847ab39b7
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| generate(n) }.join("\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.map { |n| generate(n) }.join("\n ")
43
- "function %s(%s)\n %s\nend" % [node.name, node.args.join(","), body_code]
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.join(","),
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.map { |n| generate (n) }.join("\n")
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.map { |n| generate (n) }.join("\n")
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.map { |n| generate (n) }.join("\n")
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
- if node.body.is_a?(Array)
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 ForPairNode
123
- body_code =
124
- if node.body.is_a?(Array)
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
- generate(node.index),
131
- generate(node.val),
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.map { |b| generate(b) }.join("\n")
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
- node.arg_expr.map { |expr| generate(expr) }.join(",")
148
+ generate_arguments(node.arg_expr)
157
149
  ]
158
150
 
159
151
  when PrintNode
160
152
  "print(%s)" % [
161
- node.args.map { |expr| generate(expr) }.join(",")
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.name,
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.map {|expr| generate(expr) }.join(",")
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.map { |expr| generate(expr) }.join(",")
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.map { |e| generate(e) }.join(", ")
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
- tokens = Tokenizer.new(input).tokenize
151
+ confTokens = Tokenizer.new(confInput).tokenize if confInput
129
152
  # puts "--- TOKENS ---"
130
153
  # puts tokens.map(&:inspect).join("\n")
131
154
 
132
- tree = Parser.new(tokens).parse
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
@@ -0,0 +1,8 @@
1
+ class LatSyntaxError < StandardError
2
+ attr_reader :line, :column
3
+ def initialize(message, line: nil, column: nil)
4
+ @line = line
5
+ @column = column
6
+ super(message)
7
+ end
8
+ end
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(:name, :value)
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
- if peek(:import) then
90
- parse_import
91
- elsif peek(:class) then
92
- parse_class
93
- elsif peek(:def)
94
- parse_def
95
- elsif peek(:if)
96
- parse_if
97
- elsif peek(:while)
98
- parse_while
99
- elsif peek(:for)
100
- parse_for
101
- elsif peek(:forpairs)
102
- parse_forpairs
103
- elsif peek(:foripairs)
104
- parse_foripairs
105
- elsif peek(:switch)
106
- parse_switch
107
- elsif peek(:print)
108
- parse_print
109
- elsif peek(:local)
110
- parse_var_assign
111
- elsif peek(:identifier) && peek(:equal, 1)
112
- parse_var_set
113
- elsif peek(:return)
114
- parse_return
115
- else
116
- parse_expr
117
- end
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
- name = consume(:identifier).value
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(name, value)
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
- raise "Unexpected token #{@tokens[0].inspect} in term"
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)
@@ -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
- return Token.new(type, value)
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
- raise "Couldn't match token on #{@code.inspect}"
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.2
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