kumi-parser 0.0.33 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 781cca128266d2ec1272a8c4c373ca9df96ddc48ca88c9a59a3e48a70bf75b76
4
- data.tar.gz: dd328fe55e5ec9357ea7ce76bb512429c4dbed7543b8bf70db17467bfda2f4c6
3
+ metadata.gz: b880347a083ba29083167538910e1d1453a1b7c9f334bd4a8ec86437878de8e3
4
+ data.tar.gz: 529fcc0f2bb7102ff8a79e3c8e69ef4cc3a613f13378f81d3d86feaf359d6cb8
5
5
  SHA512:
6
- metadata.gz: f02f7ba094cbf3432b2f8bc4b27b0e6772fa2b10aa9cb23e6f4061e0d038877c1d8b9581f219c9edd32d8a85e568f563bea0f7cad2b67f622019435d318caefb
7
- data.tar.gz: 6c35951a61d9160ab880e84769bbb612bcd0acb27663ae9d94f2be47b36b4ad53c22999fe0d8d2fe760971ff8cddaf895008dc06769a4ff7ed2260ca4a42ef1d
6
+ metadata.gz: 59a7da2e91de9ff04d804ad3eefe846dea9dac256083220d286ea4d29f509fdd456019f84683e9b147667da767ed090e0f5a0769777edcdeed6b9585e20b36a4
7
+ data.tar.gz: 8faaaf5d58767f4f5aa776be90033d0e65b9b6484c402434c24db305f0759ff874091445341f47b50a46e915552dcd9f4f2f63ec13caf94fdc9a12c58147e4d2
data/.rubocop.yml ADDED
@@ -0,0 +1,41 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+ NewCops: disable
4
+ SuggestExtensions: false
5
+
6
+ # A recursive-descent parser and a single-pass lexer naturally have a handful
7
+ # of longer dispatch methods; the default 10-line limit fights that structure
8
+ # rather than improving it. Keep generous ceilings and let the grammar read
9
+ # linearly.
10
+ Metrics/MethodLength:
11
+ Max: 35
12
+ Metrics/AbcSize:
13
+ Max: 30
14
+ Metrics/CyclomaticComplexity:
15
+ Max: 12
16
+ Metrics/PerceivedComplexity:
17
+ Max: 12
18
+ Metrics/ClassLength:
19
+ Max: 600
20
+ Metrics/ModuleLength:
21
+ Max: 200
22
+
23
+ # The gem's entry point must be named after the gem (`kumi-parser`), which is
24
+ # hyphenated by convention.
25
+ Naming/FileName:
26
+ Exclude:
27
+ - "lib/kumi-parser.rb"
28
+
29
+ # Positional format tokens read fine for short, local format strings.
30
+ Style/FormatStringToken:
31
+ Enabled: false
32
+
33
+ # Specs read better with descriptive backtick-quoted example names and longer
34
+ # example/group bodies than the metric defaults allow.
35
+ Metrics/BlockLength:
36
+ Exclude:
37
+ - "spec/**/*"
38
+ - "*.gemspec"
39
+
40
+ Style/Documentation:
41
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,64 @@
1
+ # Changelog
2
+
3
+ All notable changes to kumi-parser are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.0] – 2026-06-17
8
+ ### Changed
9
+ - **Full rewrite of the lexer and parser.** The hand-rolled `SmartTokenizer`
10
+ (char-by-char loop with string accumulation and a context stack) and the
11
+ metadata-bag `TOKEN_METADATA` table are replaced by a single-pass
12
+ `StringScanner` lexer producing typed tokens, plus a compact `Grammar` of
13
+ lookup tables (keywords, type keywords, function sugar, operator
14
+ precedence/associativity). The recursive-descent + Pratt parser builds the
15
+ same `Kumi::Syntax::*` AST as before — verified byte-for-byte against
16
+ kumi-core's golden AST snapshots (50 schemas) and the full compile/runtime
17
+ pipeline.
18
+ - **First-class parse errors.** Every syntax error now reports an exact
19
+ `file:line:col`, a plain-English "expected X, but found Y", and a
20
+ caret-annotated source frame. Errors are strictly scoped to the parse phase
21
+ (lexing + AST construction); name resolution, types, and axes remain the
22
+ analyzer's concern. The raised `Kumi::Errors::SyntaxError` carries a
23
+ structured `Location`, so callers render the frame from data rather than
24
+ scraping the message.
25
+ - The public surface is a single `Kumi::Parser::TextParser` facade
26
+ (`parse` / `valid?` / `validate`). The duplicate `Base`, `Api`,
27
+ `SyntaxValidator`, and `ErrorExtractor` entry points, the `Kumi::TextParser`
28
+ and `Kumi::TextSchema` shims, and the unused `parslet` / `zeitwerk`
29
+ dependencies are removed. `kumi` is now a declared runtime dependency
30
+ (the parser builds its AST nodes).
31
+
32
+ ### Fixed
33
+ - `element :type, :name` array-element declarations and chained array access
34
+ through deeply nested inputs now parse (the old parser failed its own specs
35
+ for these).
36
+
37
+ ### Removed
38
+ - The `element` input-declaration keyword in the text grammar (it was unused by
39
+ any schema and duplicated the standard `array :x do <type> :name end` form).
40
+
41
+ ## [0.0.33] – 2026-06-14
42
+ ### Added
43
+ - `outer(...)` recognized as function sugar, mirroring `cross(...)`. `outer` is
44
+ the cross-array all-pairs operator (A × B) — the sibling of `cross` (A × A').
45
+ Both bare `outer(expr)` and `fn(:outer, expr)` parse to the same
46
+ `CallExpression(:outer, …)`, so text schemas can now express two-array
47
+ all-pairs (e.g. a pixels × lights field).
48
+
49
+ ## [0.0.32] – 2026-06
50
+ ### Added
51
+ - `cross(...)` recognized as function sugar (self-join all-pairs / N-body axis op).
52
+
53
+ ## [0.0.31] – 2026-06
54
+ ### Added
55
+ - Multi-level namespace constants in the tokenizer.
56
+
57
+ ## [0.0.29] – 2026-06
58
+ ### Added
59
+ - `import` syntax for composing schemas.
60
+
61
+ ## [0.0.25] – 2026-06
62
+ ### Changed
63
+ - Renamed `token_metadata` to `token_constants`; told Zeitwerk to ignore it
64
+ (it is a plain constants module, not an autoloadable class).
data/CLAUDE.md CHANGED
@@ -1,120 +1,59 @@
1
- # Kumi Parser - Technical Context
2
-
3
- ## Current Architecture (January 2025)
4
-
5
- ## Key Files
6
-
7
- - `lib/kumi/parser/smart_tokenizer.rb` - Tokenizer with context tracking
8
- - `lib/kumi/parser/direct_parser.rb` - Parser implementation (renamed from direct_ast_parser.rb)
9
- - `lib/kumi/parser/token_metadata.rb` - Token types and metadata
10
- - `lib/kumi/parser/text_parser.rb` - Public API maintaining compatibility
11
- - `lib/kumi/parser/base.rb` - Core parsing interface
12
- - `lib/kumi/parser/syntax_validator.rb` - Validation with proper diagnostics
13
- - `lib/kumi/parser/errors.rb` - Custom error types
14
-
15
- ## Important Syntax Rules
16
-
17
- - **Functions**: `fn(:symbol, args...)` only (no dot notation like `fn.max()`)
18
- - **Operators**: Standard precedence (*/% > +- > comparisons > & > |)
19
- - **Array access**: Uses `array[index]` syntax (converted to `:at` function internally)
20
- - **Equality**: `==` and `!=` operators (converted from `:eq`/`:ne` tokens)
21
- - **Multi-line expressions**: Parser skips newlines within expressions
22
- - **Cascade**: `value :name do ... on condition, result ... base result ... end`
23
- - **Constants**: Text parser cannot resolve Ruby constants - use inline values
24
-
25
- ## AST Structure & Compatibility
26
-
27
- All nodes from `Kumi::Syntax::*` (defined in main kumi gem):
28
- - `Root(inputs, values, traits)`
29
- - `InputDeclaration(name, domain, type, children)`
30
- - `ValueDeclaration(name, expression)`
31
- - `TraitDeclaration(name, expression)`
32
- - `CallExpression(fn_name, args)`
33
- - `InputReference(name)` / `InputElementReference(path)`
34
- - `DeclarationReference(name)`
35
- - `Literal(value)`
36
- - `CascadeExpression(cases)` / `CaseExpression(condition, result)`
37
- - `ArrayExpression(elements)`
38
-
39
- **Ruby DSL Compatibility**:
40
- - Cascade conditions: Simple trait references wrapped in `all?([trait])` function calls
41
- - Array access: `[index]` becomes `CallExpression(:at, [array, index])`
42
- - Operators: `:eq` → `:==`, `:ne` → `:!=` for consistency
43
- - Constants: Ruby constants resolved to values in DSL, remain as `DeclarationReference` in text parser
44
-
45
- ## Debugging & Testing
46
-
47
- **View AST structure**:
48
- ```ruby
49
- ast = Kumi::Parser::TextParser.parse(schema)
50
- puts Kumi::Support::SExpressionPrinter.print(ast)
51
- # => (Root
52
- # inputs: [(InputDeclaration :income :float)]
53
- # values: [(ValueDeclaration :tax (CallExpression :+ ...))]
54
- # traits: [(TraitDeclaration :adult (CallExpression :>= ...))])
55
- ```
56
-
57
- **Quick validation test**:
58
- ```ruby
59
- ruby -r./lib/kumi/parser/text_parser -e "p Kumi::Parser::TextParser.valid?('schema do input do float :x end end')"
60
- ```
61
-
62
- **Compare with Ruby DSL**:
63
- ```ruby
64
- # Define schema in Ruby
65
- module TestSchema
66
- extend Kumi::Schema
67
- schema do
68
- input do
69
- float :income
70
- end
71
- value :tax, fn(:calc, input.income)
72
- end
73
- end
74
-
75
- # Parse equivalent text
76
- text_ast = Kumi::Parser::TextParser.parse(<<~KUMI)
77
- schema do
78
- input do
79
- float :income
80
- end
81
- value :tax, fn(:calc, input.income)
82
- end
83
- KUMI
84
-
85
- # Compare ASTs
86
- ruby_ast = TestSchema.__kumi_syntax_tree__
87
- text_ast == ruby_ast # Should be true
88
- ```
89
-
90
- - Tax schema in `spec/kumi/parser/text_parser_example tax_schema_spec.rb` is canonical test
91
- - Run all tests: `rspec spec/kumi/parser/`
92
- - Integration tests: `rspec spec/kumi/parser/text_parser_integration_spec.rb`
93
-
94
- ## Error Handling & Validation
95
-
96
- - **Parse errors**: `Kumi::Parser::Errors::ParseError` (internal) → `Kumi::Errors::SyntaxError` (public API)
97
- - **Tokenizer errors**: `Kumi::Parser::Errors::TokenizerError` with location info
98
- - **Diagnostics**: Use `SyntaxValidator` for detailed error reporting with line/column info
99
- - **Location tracking**: All tokens and AST nodes include `Kumi::Syntax::Location(file, line, column)`
100
-
101
- ## Test Status (January 2025)
102
-
103
- ✅ **All specs passing**: 32 examples, 0 failures, 1 pending
104
- - ✅ Syntax validation with proper diagnostics
105
- - ✅ AST compatibility with Ruby DSL (when constants aren't used)
106
- - ✅ Integration with analyzer and compiler
107
- - ✅ End-to-end execution testing
108
- - ✅ Error type compatibility
109
-
110
- ## Known Limitations
111
-
112
- - **Ruby constants**: Text parser cannot resolve Ruby constants like `CONST_NAME` - use inline values instead
113
- - **Domain specification**: Parsing not fully implemented
114
- - **Diagnostic APIs**: Monaco/CodeMirror/JSON format methods not implemented
115
-
116
- ## Performance
117
-
118
- - Tokenization: <1ms for typical schemas
119
- - Parsing: ~4ms for complete tax schema (21 values, 4 traits)
120
- - Direct AST construction eliminates transformation overhead
1
+ # Kumi Parser Technical Context
2
+
3
+ ## Architecture
4
+
5
+ `source Lexer → tokens → Parser → AST`. The AST is kumi-core's
6
+ `Kumi::Syntax::*` (so this gem depends on `kumi`).
7
+
8
+ - `lib/kumi/parser/lexer.rb` — single-pass `StringScanner` lexer. Emits a flat
9
+ array of `Token`s; newlines and comments are emitted but skipped by the
10
+ parser. Context-free (no input/schema context stack).
11
+ - `lib/kumi/parser/parser.rb` recursive descent for declarations, Pratt for
12
+ expressions. Builds `Kumi::Syntax::*` nodes directly.
13
+ - `lib/kumi/parser/grammar.rb` lookup tables: `KEYWORDS`, `TYPE_KEYWORDS`,
14
+ `FUNCTION_SUGAR`, `BINARY_OPERATORS` (precedence + associativity + fn name),
15
+ `BOOLEANS`. Replaces the old `TOKEN_METADATA` bag.
16
+ - `lib/kumi/parser/token.rb` — `Struct(:kind, :value, :offset)`. No metadata.
17
+ - `lib/kumi/parser/source.rb` offset `Location` and caret code frames.
18
+ - `lib/kumi/parser/parse_error.rb` `ParseError` (located, framed).
19
+ - `lib/kumi/parser/text_parser.rb` public API: `parse` / `valid?` /
20
+ `validate`. Raises `Kumi::Errors::SyntaxError` carrying a `Location`.
21
+
22
+ ## Grammar notes
23
+
24
+ - Functions: `fn(:name, args...)` or bare sugar `name(...)` for the entries in
25
+ `FUNCTION_SUGAR` (`select`, `shift`, `roll`, `cross`, `outer`, `index`, the
26
+ `to_*` casts). `select` lowers to `:select`.
27
+ - Operators lower to fn names: `==`→`:==`, `!=`→`:!=`, `**`→`:power`,
28
+ `&`→`:and`, `|`→`:or`, the rest to `:add`/`:multiply`/etc.
29
+ - `array[i]` → `CallExpression(:at, [array, i])`.
30
+ - Unary minus on a non-literal → `subtract(0, x)`; a `-` directly before a digit
31
+ in operand position is a negative literal (`Literal(-1)`), while a spaced `-`
32
+ after a value is the binary operator.
33
+ - `let :n, …` → `ValueDeclaration` with `hints: { inline: true }`.
34
+ - Cascade `on`: a single trait ref is wrapped in `cascade_and([ref])`; multiple
35
+ conditions become `cascade_and([...])`.
36
+ - Function-option kwargs (`policy: :clamp`, `axis_offset: 1`) are stored as raw
37
+ scalars on `CallExpression#opts`. Imported-call kwargs are full expressions
38
+ on `ImportCall#input_mapping`.
39
+ - A bare capitalized word is an identifier (e.g. `let :W, …` referenced as `W`);
40
+ only `Foo::Bar` paths are constants. `Float::INFINITY` is the one resolved
41
+ constant value.
42
+
43
+ ## Error boundary
44
+
45
+ Parse errors are about shape only (unexpected char, missing `end`, malformed
46
+ hash pair). Anything needing meaning — undefined references, types, axes — is
47
+ the analyzer's job in kumi-core and must not be flagged here.
48
+
49
+ ## Testing
50
+
51
+ - `bundle exec rspec` — the parser's own specs (lexer, AST, errors). The
52
+ `Gemfile` points `kumi` at `../kumi-core` for local dev.
53
+ - **The real equivalence gate is in kumi-core**: from that repo,
54
+ `bundle exec bin/kumi golden_v2 verify --repr ast` byte-compares the parser's
55
+ AST against 50 frozen `golden/*/expected/ast.txt` snapshots; the full
56
+ `golden_v2 verify` runs the whole pipeline (incl. Ruby/JS runtime parity).
57
+ Keep both green. kumi-core's `Gemfile` points `kumi-parser` at this path for
58
+ local dev.
59
+ - `bundle exec rubocop` clean.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Kumi::Parser
2
2
 
3
- Text parser for [Kumi](https://github.com/amuta/kumi) schemas. Direct tokenizer AST construction with ~4ms parse time.
3
+ Text parser for [Kumi](https://github.com/amuta/kumi) schemas: a single-pass lexer feeding a recursive-descent + Pratt parser that builds kumi-core's AST directly, with located, framed parse errors.
4
4
 
5
5
  ## Installation
6
6
 
@@ -59,8 +59,7 @@ end
59
59
  **Function calls**: `fn(:name, arg1, arg2, ...)`
60
60
  **Operators**: `+` `-` `*` `**` `` `/` `%` `>` `<` `>=` `<=` `==` `!=` `&` `|`
61
61
  **References**: `input.field`, `value_name`, `array[index]`
62
- **Strings**: Both `"double"` and `'single'` quotes supported
63
- **Element syntax**: `element :type, :name` for array element specifications
62
+ **Strings**: Both `"double"` and `'single'` quotes supported
64
63
 
65
64
  ## Ruby DSL Differences
66
65
 
@@ -70,9 +69,32 @@ end
70
69
 
71
70
  ## Architecture
72
71
 
73
- - `smart_tokenizer.rb` - Context-aware tokenization with embedded metadata
74
- - `direct_ast_parser.rb` - Recursive descent parser, direct AST construction
75
- - `token_metadata.rb` - Token types, precedence, and semantic hints
72
+ The pipeline is `source Lexer tokens Parser → AST`, where the AST is
73
+ kumi-core's `Kumi::Syntax::*` nodes.
74
+
75
+ - `lexer.rb` — single-pass `StringScanner` lexer producing a flat array of
76
+ typed `Token`s, each carrying only its kind, value, and start offset.
77
+ - `parser.rb` — recursive descent for declarations, Pratt for expressions.
78
+ - `grammar.rb` — the lookup tables (keywords, type keywords, function sugar,
79
+ operator precedence/associativity) shared by the lexer and parser.
80
+ - `source.rb` / `parse_error.rb` — turn a byte offset into a `file:line:col`
81
+ location and a caret-annotated code frame for error messages.
82
+ - `text_parser.rb` — the public `parse` / `valid?` / `validate` facade.
83
+
84
+ ### Error reporting
85
+
86
+ Parse errors report an exact location, a plain-English description of what was
87
+ expected versus what was found, and a source frame:
88
+
89
+ ```
90
+ demo.kumi:2:3: expected an `input do` block, but found `value`
91
+ ➤ 2 | value :y, input.x
92
+ | ^
93
+ ```
94
+
95
+ Errors are confined to the parse phase. Resolving names, checking types, and
96
+ reasoning about axes are semantic concerns handled later by kumi-core's
97
+ analyzer, not by this gem.
76
98
 
77
99
  ## License
78
100
 
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Parse a Kumi text schema and print its AST, then show what a parse error
4
+ # looks like. Run with: ruby -Ilib examples/parse_and_inspect.rb
5
+ require 'kumi-parser'
6
+
7
+ schema = <<~KUMI
8
+ schema do
9
+ input do
10
+ integer :age, domain: 18..120
11
+ array :scores do
12
+ float :value
13
+ end
14
+ end
15
+
16
+ trait :adult, input.age >= 18
17
+ let :total, fn(:sum, input.scores.value)
18
+
19
+ value :tier do
20
+ on adult, "adult"
21
+ base "minor"
22
+ end
23
+ end
24
+ KUMI
25
+
26
+ ast = Kumi::Parser::TextParser.parse(schema)
27
+ puts Kumi::Support::SExpressionPrinter.print(ast)
28
+
29
+ puts "\n--- a parse error ---"
30
+ begin
31
+ Kumi::Parser::TextParser.parse("schema do\n value :y input.x\nend\n", source_file: 'demo.kumi')
32
+ rescue Kumi::Errors::SyntaxError => e
33
+ puts "#{e.message} (#{e.location})"
34
+ end
data/kumi-parser.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = 'Allows Kumi schemas to be written as plain text with syntax validation and editor integration.'
13
13
  spec.homepage = 'https://github.com/amuta/kumi-parser'
14
14
  spec.license = 'MIT'
15
- spec.required_ruby_version = '>= 3.0.0'
15
+ spec.required_ruby_version = '>= 3.1.0'
16
16
 
17
17
  spec.metadata['allowed_push_host'] = 'https://rubygems.org'
18
18
  spec.metadata['homepage_uri'] = spec.homepage
@@ -31,9 +31,8 @@ Gem::Specification.new do |spec|
31
31
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
32
  spec.require_paths = ['lib']
33
33
 
34
- # Dependencies
35
- spec.add_dependency 'parslet', '~> 2.0'
36
- spec.add_dependency 'zeitwerk', '~> 2.6'
34
+ # The parser builds Kumi::Syntax::* nodes, so it needs the core gem's AST.
35
+ spec.add_dependency 'kumi'
37
36
 
38
37
  # Development dependencies
39
38
  spec.add_development_dependency 'bundler', '~> 2.0'
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Parser
5
+ # Static grammar tables shared by the lexer and parser. These replace the
6
+ # old per-token TOKEN_METADATA bag: lookups are keyed by a word or token
7
+ # kind, so the data lives once here instead of being copied onto every
8
+ # token instance.
9
+ module Grammar
10
+ # Bare words that are keywords rather than identifiers. `true`/`false` are
11
+ # not here: they are boolean literals, handled directly by the lexer.
12
+ KEYWORDS = {
13
+ 'schema' => :schema,
14
+ 'input' => :input,
15
+ 'value' => :value,
16
+ 'let' => :let,
17
+ 'trait' => :trait,
18
+ 'import' => :import,
19
+ 'codegen' => :codegen,
20
+ 'do' => :do,
21
+ 'end' => :end,
22
+ 'on' => :on,
23
+ 'base' => :base,
24
+ 'fn' => :fn
25
+ }.freeze
26
+
27
+ # The two boolean literal words, mapped to their Ruby values.
28
+ BOOLEANS = { 'true' => true, 'false' => false }.freeze
29
+
30
+ # Type keywords introduce input declarations. The value is the canonical
31
+ # type symbol stored on InputDeclaration.
32
+ TYPE_KEYWORDS = {
33
+ 'integer' => :integer,
34
+ 'float' => :float,
35
+ 'decimal' => :decimal,
36
+ 'string' => :string,
37
+ 'boolean' => :boolean,
38
+ 'any' => :any,
39
+ 'array' => :array,
40
+ 'hash' => :hash
41
+ }.freeze
42
+
43
+ # Container types whose declarations may open a nested `do … end` block.
44
+ CONTAINER_TYPES = %i[array hash].freeze
45
+
46
+ # Bare-call sugar: `name(args)` parses as `fn(:resolved_name, args)`.
47
+ # The value is the function symbol the call lowers to.
48
+ FUNCTION_SUGAR = {
49
+ 'select' => :select,
50
+ 'shift' => :shift,
51
+ 'roll' => :roll,
52
+ 'cross' => :cross,
53
+ 'outer' => :outer,
54
+ 'index' => :index,
55
+ 'to_decimal' => :to_decimal,
56
+ 'to_integer' => :to_integer,
57
+ 'to_float' => :to_float,
58
+ 'to_string' => :to_string
59
+ }.freeze
60
+
61
+ # Binary operators: kind => [precedence, :left/:right, fn_name].
62
+ # Higher precedence binds tighter. fn_name is the symbol the operator
63
+ # lowers to in the AST (CallExpression fn_name).
64
+ BINARY_OPERATORS = {
65
+ power: [7, :right, :power],
66
+ multiply: [6, :left, :multiply],
67
+ divide: [6, :left, :divide],
68
+ modulo: [6, :left, :modulo],
69
+ add: [5, :left, :add],
70
+ subtract: [5, :left, :subtract],
71
+ gte: [4, :left, :>=],
72
+ lte: [4, :left, :<=],
73
+ gt: [4, :left, :>],
74
+ lt: [4, :left, :<],
75
+ eq: [4, :left, :==],
76
+ ne: [4, :left, :!=],
77
+ and: [3, :left, :and],
78
+ or: [2, :left, :or]
79
+ }.freeze
80
+
81
+ module_function
82
+
83
+ def keyword(word)
84
+ KEYWORDS[word]
85
+ end
86
+
87
+ def boolean?(word)
88
+ BOOLEANS.key?(word)
89
+ end
90
+
91
+ def boolean(word)
92
+ BOOLEANS[word]
93
+ end
94
+
95
+ def type_keyword(word)
96
+ TYPE_KEYWORDS[word]
97
+ end
98
+
99
+ def function_sugar(word)
100
+ FUNCTION_SUGAR[word]
101
+ end
102
+
103
+ def binary_operator?(kind)
104
+ BINARY_OPERATORS.key?(kind)
105
+ end
106
+
107
+ def precedence(kind)
108
+ BINARY_OPERATORS.fetch(kind)[0]
109
+ end
110
+
111
+ def right_associative?(kind)
112
+ BINARY_OPERATORS.fetch(kind)[1] == :right
113
+ end
114
+
115
+ def operator_fn(kind)
116
+ BINARY_OPERATORS.fetch(kind)[2]
117
+ end
118
+ end
119
+ end
120
+ end