kumi-parser 0.0.28 → 0.0.30

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: bc1abe5a6e23f15904189f2d1f8550a5c88b5441318ba95a2df9b6437daca570
4
- data.tar.gz: 851f8dbdd42fcc3210214e48223466d7e3dbb19a8802227c184a5b7b442df48b
3
+ metadata.gz: 4ec60e114746a0b2790e33eee5cc5fee382102057cca454adcf90c585a1b71e3
4
+ data.tar.gz: a37374381ed4f9ecd8cac6bc7ba92a23ddaf9ed40a02a3ba7f61cd2b8c8f70bd
5
5
  SHA512:
6
- metadata.gz: 433f85f8185b8d4b88982358ef3291c5b445e811b107cd9c0300414fd47066dd70f2b5970357d79406d02c1dc0ec72611f3282d4f39c23dbc820c127dbbbd6ba
7
- data.tar.gz: df753a5c5b8bcf4fdcb50e03d6f4ebef3162af9f0ff4d445941aa39da3e2b5265cdc460942f099cbfc8c191451088098a55d19d47102cb9b629f4bcedafc6426
6
+ metadata.gz: 3f64d28aa1d89ea9abb1ed1aa0cd3db327a7ac5a1e10e77c0cb8fa0bf66473d31914632ef977f89200bfd76a11b1abbaf6a505da959cd1e8cd9ec4994cfedaaf
7
+ data.tar.gz: b77960ec662f5549a8b2047115fb8f6fbdfb0a9617ead6f1de8958a216b62107c961e950a3054042d7b2e4ef6512c04ee6300cdd3c82c8d8690e09566443dfec
@@ -9,11 +9,24 @@ module Kumi
9
9
  def initialize(tokens)
10
10
  @tokens = tokens
11
11
  @pos = 0
12
+ @imported_names = Set.new
12
13
  end
13
14
 
14
15
  def parse
15
16
  skip_comments_and_newlines
17
+
18
+ # Parse root-level imports (before schema)
19
+ root_imports = parse_imports
20
+ @imported_names.merge(root_imports.flat_map(&:names))
21
+
16
22
  schema_node = parse_schema
23
+
24
+ # If we have root imports, add them to the schema
25
+ if root_imports.any?
26
+ # Merge root imports with schema imports
27
+ schema_node.imports.concat(root_imports)
28
+ end
29
+
17
30
  skip_comments_and_newlines
18
31
  expect_token(:eof)
19
32
  schema_node
@@ -49,6 +62,9 @@ module Kumi
49
62
  expect_token(:do)
50
63
 
51
64
  skip_comments_and_newlines
65
+ import_declarations = parse_imports
66
+ @imported_names.merge(import_declarations.flat_map(&:names))
67
+
52
68
  input_declarations = parse_input_block
53
69
 
54
70
  value_declarations = []
@@ -73,10 +89,76 @@ module Kumi
73
89
  input_declarations,
74
90
  value_declarations, # values
75
91
  trait_declarations,
92
+ import_declarations,
76
93
  loc: schema_token.location
77
94
  )
78
95
  end
79
96
 
97
+ # Parse import declarations: 'import' :symbol, from: Module
98
+ def parse_imports
99
+ imports = []
100
+ skip_comments_and_newlines
101
+
102
+ while current_token.type == :import
103
+ import_token = expect_token(:import)
104
+
105
+ names = []
106
+ names << expect_token(:symbol).value.to_sym
107
+
108
+ while current_token.type == :comma
109
+ expect_token(:comma)
110
+ skip_comments_and_newlines
111
+
112
+ # Check if this is the 'from:' keyword argument or another symbol to import
113
+ if current_token.type == :label && current_token.value == 'from'
114
+ # This is 'from:' - end of imports list
115
+ break
116
+ else
117
+ # Another symbol to import
118
+ names << expect_token(:symbol).value.to_sym
119
+ end
120
+ end
121
+
122
+ skip_comments_and_newlines
123
+
124
+ # Handle 'from:' keyword argument
125
+ if current_token.type == :label && current_token.value == 'from'
126
+ expect_token(:label) # consume 'from:'
127
+ else
128
+ raise_parse_error("Expected 'from:' keyword argument in import statement")
129
+ end
130
+
131
+ skip_comments_and_newlines
132
+
133
+ module_ref = parse_constant
134
+
135
+ imports << Kumi::Syntax::ImportDeclaration.new(
136
+ names,
137
+ module_ref,
138
+ loc: import_token.location
139
+ )
140
+
141
+ skip_comments_and_newlines
142
+ end
143
+
144
+ imports
145
+ end
146
+
147
+ # Parse a constant reference like Schemas::Tax
148
+ def parse_constant
149
+ const_parts = []
150
+ const_parts << expect_token(:constant).value
151
+
152
+ while current_token.type == :colon && peek_token.type == :colon
153
+ expect_token(:colon)
154
+ expect_token(:colon)
155
+ const_parts << expect_token(:constant).value
156
+ end
157
+
158
+ # Return the full constant path as a string that will be evaluated at runtime
159
+ const_parts.join('::')
160
+ end
161
+
80
162
  # Input block: 'input' 'do' ... 'end'
81
163
  def parse_input_block
82
164
  expect_token(:input)
@@ -341,6 +423,9 @@ module Kumi
341
423
  parse_input_reference
342
424
  elsif token.value == 'index' && peek_token.type == :lparen
343
425
  parse_index_intrinsic
426
+ elsif peek_token.type == :lparen
427
+ # This is a function call like tax(amount: input.amount)
428
+ parse_imported_function_call
344
429
  else
345
430
  advance
346
431
  Kumi::Syntax::DeclarationReference.new(token.value.to_sym, loc: token.location)
@@ -448,7 +533,51 @@ module Kumi
448
533
  args, opts = parse_args_and_opts_inside_parens
449
534
  end
450
535
  # expect_token(:rparen)
451
- Kumi::Syntax::CallExpression.new(fn_name_token.value, args, opts, loc: fn_name_token.location)
536
+
537
+ # Check if this is an imported function call
538
+ if @imported_names.include?(fn_name_token.value) && args.empty? && opts.any?
539
+ # Convert to ImportCall - opts become the input mapping
540
+ Kumi::Syntax::ImportCall.new(fn_name_token.value, opts, loc: fn_name_token.location)
541
+ else
542
+ # Regular call expression
543
+ Kumi::Syntax::CallExpression.new(fn_name_token.value, args, opts, loc: fn_name_token.location)
544
+ end
545
+ end
546
+
547
+ def parse_imported_function_call
548
+ fn_name_token = current_token
549
+ fn_name = fn_name_token.value.to_sym
550
+ advance # consume identifier
551
+ expect_token(:lparen)
552
+
553
+ # Parse keyword arguments for imported function calls
554
+ # Imported functions only accept keyword arguments
555
+ opts = {}
556
+
557
+ unless current_token.type == :rparen
558
+ # Parse keyword arguments with full expression values
559
+ while current_token.type == :label
560
+ key = current_token.value.to_sym
561
+ advance
562
+
563
+ opts[key] = parse_expression
564
+
565
+ break unless current_token.type == :comma
566
+ advance
567
+ skip_comments_and_newlines
568
+ end
569
+ end
570
+
571
+ expect_token(:rparen)
572
+
573
+ # Check if this is an imported function call
574
+ if @imported_names.include?(fn_name) && opts.any?
575
+ # Convert to ImportCall - opts become the input mapping
576
+ Kumi::Syntax::ImportCall.new(fn_name, opts, loc: fn_name_token.location)
577
+ else
578
+ # Regular call expression (shouldn't happen for imported functions)
579
+ Kumi::Syntax::CallExpression.new(fn_name, [], opts, loc: fn_name_token.location)
580
+ end
452
581
  end
453
582
 
454
583
  def parse_array_literal
@@ -163,7 +163,21 @@ module Kumi
163
163
  identifier_or_label_name = consume_while { |c| c.match?(/[a-zA-Z0-9_]/) }
164
164
  location = Kumi::Syntax::Location.new(file: @source_file, line: @line, column: start_column)
165
165
 
166
- # Check if the next character is a colon
166
+ # Check if it's a constant FIRST (e.g., Float::INFINITY or Kumi::TestSharedSchemas::Tax)
167
+ # This needs to be checked before label detection because labels also start with `:``
168
+ if current_char == ':' && peek_char == ':'
169
+ full_constant = identifier_or_label_name
170
+ while current_char == ':' && peek_char == ':'
171
+ advance # consume first :
172
+ advance # consume second :
173
+ constant_name = consume_while { |c| c.match?(/[a-zA-Z0-9_]/) }
174
+ full_constant = "#{full_constant}::#{constant_name}"
175
+ end
176
+ add_token(:constant, full_constant, Kumi::Parser::TOKEN_METADATA[:constant])
177
+ return
178
+ end
179
+
180
+ # Check if the next character is a single colon (label)
167
181
  if current_char == ':'
168
182
  # It's a hash key or a label (e.g., `name:`)
169
183
  advance # consume the colon
@@ -174,16 +188,6 @@ module Kumi
174
188
  # If it's not a label, proceed to check for keywords and identifiers
175
189
  # The logic below is adapted from your original `consume_identifier_or_keyword` method
176
190
 
177
- # Check if it's a constant (e.g., Float::INFINITY)
178
- if identifier_or_label_name == 'Float' && current_char == ':' && peek_char == ':'
179
- advance # consume first :
180
- advance # consume second :
181
- constant_name = consume_while { |c| c.match?(/[a-zA-Z0-9_]/) }
182
- full_constant = "#{identifier_or_label_name}::#{constant_name}"
183
- add_token(:constant, full_constant, Kumi::Parser::TOKEN_METADATA[:constant])
184
- return
185
- end
186
-
187
191
  # Check if it's a keyword
188
192
  if keyword_type = Kumi::Parser::KEYWORDS[identifier_or_label_name]
189
193
  metadata = Kumi::Parser::TOKEN_METADATA[keyword_type].dup
@@ -20,6 +20,8 @@ module Kumi
20
20
  INPUT = :input
21
21
  VALUE = :value
22
22
  TRAIT = :trait
23
+ IMPORT = :import
24
+ FROM = :from
23
25
  DO = :do
24
26
  END_KW = :end
25
27
  ON = :on
@@ -100,6 +102,14 @@ module Kumi
100
102
  expects_expression: true,
101
103
  declaration_type: :trait
102
104
  },
105
+ import: {
106
+ category: :keyword,
107
+ import_declaration: true
108
+ },
109
+ from: {
110
+ category: :keyword,
111
+ import_source: true
112
+ },
103
113
  do: {
104
114
  category: :keyword,
105
115
  block_opener: true
@@ -421,6 +431,8 @@ module Kumi
421
431
  'value' => :value,
422
432
  'let' => :let,
423
433
  'trait' => :trait,
434
+ 'import' => :import,
435
+ 'from' => :from,
424
436
  'do' => :do,
425
437
  'end' => :end,
426
438
  'on' => :on,
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Kumi
4
4
  module Parser
5
- VERSION = '0.0.28'
5
+ VERSION = '0.0.30'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kumi-parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.28
4
+ version: 0.0.30
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kumi Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-10-20 00:00:00.000000000 Z
11
+ date: 2025-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: parslet