mt-lang 0.3.11 → 0.3.13

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.
@@ -0,0 +1,420 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module SexprParser
5
+ module_function
6
+
7
+ # ── public API ────────────────────────────────────────────────────
8
+
9
+ def parse_ir(sexpr)
10
+ tokens = tokenize(sexpr)
11
+ _parse_value(tokens)
12
+ end
13
+
14
+ def parse_ast(sexpr)
15
+ tokens = tokenize(sexpr)
16
+ _parse_value(tokens)
17
+ end
18
+
19
+ def parse_tokens(sexpr)
20
+ tokens = tokenize(sexpr)
21
+ _parse_value(tokens)
22
+ end
23
+
24
+ # ── tokenizer ─────────────────────────────────────────────────────
25
+
26
+ Token = Data.define(:kind, :text, :line, :column)
27
+
28
+ def tokenize(source)
29
+ tokens = []
30
+ i = 0
31
+ len = source.length
32
+ line = 1
33
+ col = 1
34
+ while i < len
35
+ ch = source[i]
36
+ case ch
37
+ when "(", ")"
38
+ tokens << Token.new(kind: ch, text: ch, line:, column: col)
39
+ i += 1
40
+ col += 1
41
+ when '"'
42
+ j = i + 1
43
+ chars = +""
44
+ while j < len
45
+ if source[j] == "\\" && j + 1 < len
46
+ case source[j + 1]
47
+ when "n" then chars << "\n"
48
+ when "t" then chars << "\t"
49
+ when "r" then chars << "\r"
50
+ when '"' then chars << '"'
51
+ when "\\" then chars << "\\"
52
+ else chars << source[j + 1]
53
+ end
54
+ j += 2
55
+ elsif source[j] == '"'
56
+ j += 1
57
+ break
58
+ else
59
+ chars << source[j]
60
+ j += 1
61
+ end
62
+ end
63
+ tokens << Token.new(kind: "string", text: chars, line:, column: col)
64
+ col += j - i
65
+ i = j
66
+ when ":", "-", "0".."9"
67
+ # keyword, negative number, or number
68
+ j = i
69
+ j += 1 while j < len && source[j] != " " && source[j] != "\n" && source[j] != "(" && source[j] != ")" && source[j] != '"'
70
+ text = source[i...j]
71
+ if text.start_with?(":")
72
+ tokens << Token.new(kind: "keyword", text: text[1..], line:, column: col)
73
+ elsif text.match?(/\A-?[0-9]+\.[0-9]+([eE][+-]?[0-9]+)?\z/) || text.match?(/\A-?[0-9]+\.[0-9]*([eE][+-]?[0-9]+)?\z/)
74
+ tokens << Token.new(kind: "float", text:, line:, column: col)
75
+ elsif text.match?(/\A-?[0-9]+\z/)
76
+ tokens << Token.new(kind: "integer", text:, line:, column: col)
77
+ else
78
+ tokens << Token.new(kind: "atom", text:, line:, column: col)
79
+ end
80
+ col += j - i
81
+ i = j
82
+ when /\s/
83
+ if ch == "\n"
84
+ line += 1
85
+ col = 0
86
+ end
87
+ i += 1
88
+ col += 1
89
+ else
90
+ # atom (identifier, true, false, nil)
91
+ j = i
92
+ j += 1 while j < len && source[j] != " " && source[j] != "\n" && source[j] != "(" && source[j] != ")" && source[j] != '"'
93
+ text = source[i...j]
94
+ tokens << Token.new(kind: "atom", text:, line:, column: col)
95
+ col += j - i
96
+ i = j
97
+ end
98
+ end
99
+ tokens
100
+ end
101
+
102
+ # ── parser ────────────────────────────────────────────────────────
103
+
104
+ def _parse_value(tokens, index = [0])
105
+ token = tokens[index[0]]
106
+ return nil unless token
107
+
108
+ case token.kind
109
+ when "("
110
+ _parse_list(tokens, index)
111
+ when "string"
112
+ index[0] += 1
113
+ token.text
114
+ when "integer"
115
+ index[0] += 1
116
+ Integer(token.text)
117
+ when "float"
118
+ index[0] += 1
119
+ Float(token.text)
120
+ when "keyword"
121
+ index[0] += 1
122
+ token.text.gsub("-", "_").to_sym
123
+ when "atom"
124
+ index[0] += 1
125
+ case token.text
126
+ when "nil" then nil
127
+ when "true" then true
128
+ when "false" then false
129
+ else token.text
130
+ end
131
+ else
132
+ raise "unexpected token: #{token.inspect}"
133
+ end
134
+ end
135
+
136
+ def _parse_list(tokens, index)
137
+ index[0] += 1 # skip "("
138
+ if tokens[index[0]]&.kind == ")"
139
+ index[0] += 1 # skip ")"
140
+ return []
141
+ end
142
+
143
+ first = _parse_value(tokens, index)
144
+
145
+ if first.is_a?(String) && first.match?(/\A[A-Z]/)
146
+ _parse_typed_node(tokens, index, first, first)
147
+ else
148
+ # plain array — first element was the first value
149
+ result = [first]
150
+ while tokens[index[0]]&.kind != ")"
151
+ result << _parse_value(tokens, index)
152
+ end
153
+ index[0] += 1 # skip ")"
154
+ result
155
+ end
156
+ end
157
+
158
+ def _parse_typed_node(tokens, index, first_token, first_str)
159
+ type_name = _resolve_type_name(first_str)
160
+
161
+ # Collect keyword-value pairs
162
+ fields = {}
163
+ while tokens[index[0]]&.kind == "keyword"
164
+ kw = tokens[index[0]].text.gsub("-", "_").to_sym
165
+ index[0] += 1 # skip keyword
166
+ val = _parse_value(tokens, index)
167
+ fields[kw] = val
168
+ end
169
+
170
+ index[0] += 1 # skip ")"
171
+
172
+ if type_name.start_with?("Types::")
173
+ _construct_types_object(type_name, fields)
174
+ elsif type_name.start_with?("IR::")
175
+ _construct_ir_object(type_name, fields)
176
+ elsif type_name.start_with?("AST::")
177
+ _construct_ast_object(type_name, fields)
178
+ else
179
+ _construct_generic(type_name, fields)
180
+ end
181
+ end
182
+
183
+ # ── type name resolution ──────────────────────────────────────────
184
+
185
+ def _resolve_type_name(str)
186
+ # Already fully qualified ("Types::Primitive", "IR::Program")
187
+ return str if str.include?("::")
188
+
189
+ # Try common prefixes
190
+ [
191
+ "MilkTea::IR::#{str}",
192
+ "MilkTea::AST::#{str}",
193
+ "MilkTea::Types::#{str}",
194
+ ].each do |candidate|
195
+ begin
196
+ return candidate if Object.const_get(candidate)
197
+ rescue NameError
198
+ nil
199
+ end
200
+ end
201
+ str
202
+ end
203
+
204
+ # ── object constructors ───────────────────────────────────────────
205
+
206
+ def _construct_ir_object(type_name, fields)
207
+ name = type_name.split("::").last
208
+ klass = MilkTea::IR.const_get(name)
209
+ sym_fields = fields.transform_keys(&:to_sym)
210
+ klass.new(**sym_fields)
211
+ rescue NameError
212
+ _construct_generic(type_name, fields)
213
+ end
214
+
215
+ def _construct_ast_object(type_name, fields)
216
+ name = type_name.split("::").last
217
+ klass = MilkTea::AST.const_get(name)
218
+ sym_fields = fields.transform_keys(&:to_sym)
219
+ klass.new(**sym_fields)
220
+ rescue NameError
221
+ _construct_generic(type_name, fields)
222
+ end
223
+
224
+ def _construct_types_object(type_name, fields)
225
+ short = type_name.sub(/\ATypes::/, "")
226
+ case short
227
+ when "Primitive"
228
+ Types::Registry.primitive(fields[:name])
229
+ when "Null"
230
+ Types::Null.new(fields[:target_type])
231
+ when "Nullable"
232
+ Types::Registry.nullable(fields[:base])
233
+ when "Generic"
234
+ Types::Registry.generic_instance(fields[:name], fields[:arguments])
235
+ when "Span"
236
+ Types::Registry.span(fields[:element_type])
237
+ when "Task"
238
+ rt = fields[:result_type] || Types::Registry.primitive("void")
239
+ Types::Registry.task(rt)
240
+ when "Function"
241
+ Types::Registry.function(
242
+ fields[:name],
243
+ params: fields[:params] || [],
244
+ return_type: fields[:return_type],
245
+ receiver_type: fields[:receiver_type],
246
+ receiver_editable: fields[:receiver_editable] || false,
247
+ variadic: fields[:variadic] || false,
248
+ external: fields[:external] || false,
249
+ )
250
+ when "Proc"
251
+ Types::Registry.proc(params: fields[:params] || [], return_type: fields[:return_type])
252
+ when "Tuple"
253
+ Types::Registry.tuple(
254
+ fields.fetch(:element_types, []),
255
+ field_names: fields[:field_names],
256
+ )
257
+ when "Vector"
258
+ Types::Registry.generic_instance(fields[:name], [Types::LiteralTypeArg.new(fields[:name].delete_prefix("vec").to_i)])
259
+ when "Matrix"
260
+ Types::Registry.generic_instance(fields[:name], [Types::LiteralTypeArg.new(fields[:name].delete_prefix("mat").to_i)])
261
+ when "Quaternion"
262
+ Types::Registry.generic_instance("quat", [])
263
+ when "SoA"
264
+ Types::Registry.soa(fields[:element_type], count: fields[:count])
265
+ when "Simd"
266
+ Types::Registry.simd(fields[:element_type], lane_count: fields[:lane_count])
267
+ when "StringView"
268
+ Types::Registry.string_view
269
+ when "Parameter"
270
+ Types::Registry.parameter(
271
+ fields[:name],
272
+ fields[:type],
273
+ mutable: fields[:mutable] || false,
274
+ passing_mode: fields[:passing_mode]&.to_sym || :plain,
275
+ boundary_type: fields[:boundary_type],
276
+ )
277
+ when "LiteralTypeArg"
278
+ Types::LiteralTypeArg.new(fields[:value])
279
+ when "TypeVar"
280
+ Types::TypeVar.new(fields[:name])
281
+ when "LifetimeRef"
282
+ Types::LifetimeRef.new(fields[:name])
283
+ when "Dyn"
284
+ interface_binding = MilkTea::SemanticAnalyzer::InterfaceBinding.new(
285
+ name: fields[:interface_name],
286
+ methods: [],
287
+ ast: nil,
288
+ module_name: nil,
289
+ )
290
+ Types::Registry.dyn(interface_binding, fields.fetch(:type_arguments, []))
291
+ when "Struct", "StructInstance", "Union", "Variant", "VariantInstance",
292
+ "VariantArmPayload", "Enum", "Flags", "Opaque", "Event", "Subscription", "Handle",
293
+ "TypeType", "Error", "DynVtable", "ReflectionHandleType", "StructHandle",
294
+ "FieldHandle", "CallableHandle", "AttributeHandle", "MemberHandle",
295
+ "GenericStructDefinition", "GenericVariantDefinition"
296
+ _construct_standalone_type(short, fields)
297
+ else
298
+ _construct_standalone_type(short, fields)
299
+ end
300
+ end
301
+
302
+ def _construct_standalone_type(short, fields)
303
+ case short
304
+ when "TypeType"
305
+ Types::TypeType.new
306
+ when "Error"
307
+ Types::Error.new
308
+ when "Subscription"
309
+ Types::Subscription.new
310
+ when "Handle"
311
+ Types::Handle.new
312
+ when "Struct"
313
+ Types::Struct.new(
314
+ fields[:name],
315
+ module_name: fields[:module_name],
316
+ external: false,
317
+ packed: false,
318
+ alignment: nil,
319
+ linkage_name: nil,
320
+ lifetime_params: [],
321
+ )
322
+ when "Variant"
323
+ Types::Variant.new(fields[:name], module_name: fields[:module_name])
324
+ when "Union"
325
+ Types::Union.new(
326
+ fields[:name],
327
+ module_name: fields[:module_name],
328
+ external: false,
329
+ packed: false,
330
+ alignment: nil,
331
+ linkage_name: nil,
332
+ lifetime_params: [],
333
+ )
334
+ when "Opaque"
335
+ Types::Opaque.new(fields[:name], module_name: fields[:module_name], external: false, linkage_name: nil)
336
+ when "Enum"
337
+ Types::Enum.new(fields[:name], module_name: fields[:module_name], external: false)
338
+ when "Flags"
339
+ Types::Flags.new(fields[:name], module_name: fields[:module_name], external: false)
340
+ when "Event"
341
+ Types::Event.new(
342
+ fields[:name],
343
+ capacity: fields[:capacity],
344
+ payload_type: fields[:payload_type],
345
+ module_name: nil,
346
+ visibility: :private,
347
+ owner_type_name: nil,
348
+ )
349
+ when "StringView"
350
+ Types::Registry.string_view
351
+ else
352
+ _construct_types_fallback(short, fields)
353
+ end
354
+ end
355
+
356
+ def _construct_types_fallback(short, fields)
357
+ klass = begin
358
+ Types.const_get(short)
359
+ rescue NameError
360
+ return fields
361
+ end
362
+
363
+ begin
364
+ sym_fields = fields.transform_keys(&:to_sym)
365
+ klass.new(**sym_fields)
366
+ rescue StandardError
367
+ begin
368
+ args = fields.values
369
+ klass.new(*args)
370
+ rescue StandardError
371
+ begin
372
+ obj = klass.allocate
373
+ fields.each do |k, v|
374
+ ivar = "@#{k}"
375
+ obj.instance_variable_set(ivar, v) if obj.instance_variable_defined?(ivar) || obj.respond_to?(:"#{k}=")
376
+ rescue StandardError
377
+ nil
378
+ end
379
+ begin
380
+ obj.instance_variable_set(:@hash, obj.object_id.hash)
381
+ rescue StandardError
382
+ nil
383
+ end
384
+ obj
385
+ rescue StandardError
386
+ fields
387
+ end
388
+ end
389
+ end
390
+ end
391
+
392
+ def _construct_generic(type_name, fields)
393
+ klass = begin
394
+ name_parts = type_name.split("::")
395
+ if name_parts.length > 1
396
+ top = Object.const_get(name_parts.first)
397
+ rest = name_parts[1..].join("::")
398
+ top.const_get(rest)
399
+ else
400
+ Object.const_get(type_name)
401
+ end
402
+ rescue NameError
403
+ nil
404
+ end
405
+
406
+ if klass && klass.respond_to?(:members)
407
+ sym_fields = fields.transform_keys(&:to_sym)
408
+ klass.new(**sym_fields)
409
+ else
410
+ fields
411
+ end
412
+ end
413
+
414
+ # ── helpers ───────────────────────────────────────────────────────
415
+
416
+ def _unescape(str)
417
+ str.gsub("\\n", "\n").gsub("\\t", "\t").gsub("\\r", "\r").gsub('\\"', '"').gsub("\\\\", "\\")
418
+ end
419
+ end
420
+ end
@@ -7,6 +7,8 @@ require_relative "packages"
7
7
  require_relative "tooling/debug_map"
8
8
  require_relative "tooling/debug_info_formatter"
9
9
  require_relative "tooling/asset_pack"
10
+ require_relative "tooling/sexpr_dumper"
11
+ require_relative "tooling/sexpr_parser"
10
12
  require_relative "tooling/build"
11
13
  require_relative "tooling/run"
12
14
  require_relative "tooling/cst_formatter"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mt-lang
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.11
4
+ version: 0.3.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Long (Teefan) Tran
@@ -288,10 +288,10 @@ files:
288
288
  - lib/milk_tea/core/semantic_analyzer/type_declaration.rb
289
289
  - lib/milk_tea/core/token.rb
290
290
  - lib/milk_tea/core/token_stream.rb
291
+ - lib/milk_tea/core/types.rb
291
292
  - lib/milk_tea/core/types/layout.rb
292
293
  - lib/milk_tea/core/types/predicates.rb
293
294
  - lib/milk_tea/core/types/registry.rb
294
- - lib/milk_tea/core/types/types.rb
295
295
  - lib/milk_tea/core/types/visitor.rb
296
296
  - lib/milk_tea/dap.rb
297
297
  - lib/milk_tea/dap/backends/lldb_dap.rb
@@ -385,6 +385,8 @@ files:
385
385
  - lib/milk_tea/tooling/public/css/docs.css
386
386
  - lib/milk_tea/tooling/public/js/docs.js
387
387
  - lib/milk_tea/tooling/run.rb
388
+ - lib/milk_tea/tooling/sexpr_dumper.rb
389
+ - lib/milk_tea/tooling/sexpr_parser.rb
388
390
  - lib/milk_tea/tooling/templates/wasm_shell.html
389
391
  - lib/milk_tea/tooling/toolchain_cli.rb
390
392
  - lib/milk_tea/tooling/views/404.erb
@@ -603,7 +605,7 @@ metadata:
603
605
  homepage_uri: https://teefan.github.io/mt-lang/
604
606
  source_code_uri: https://github.com/teefan/mt-lang
605
607
  post_install_message: |
606
- Milk Tea 0.3.11 installed!
608
+ Milk Tea 0.3.13 installed!
607
609
 
608
610
  System requirements:
609
611
  - A C compiler (gcc or clang) must be available on PATH