kumi 0.0.8 → 0.0.9

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: 652ef5f59f7c4469c86ecda4add1f650121d75d333231ae50306a7bf2ce7725c
4
- data.tar.gz: 367a335e09a9d5cefaecbfa8e870c99ac49153a771f2451e3cfa8f73531d0701
3
+ metadata.gz: c5f9f087001a482c906f041da8cb229511966a11c56e3ced99930b59d4141543
4
+ data.tar.gz: fe5fafe03a5ca1e414b5bc3af9eccd830553aa8f44762e9d9b38e589fa342fd1
5
5
  SHA512:
6
- metadata.gz: 2dece8cc4284177c7f45a97768ea0c228cf41210d85cff985850a5e98f64c4ce02cc2c97cc2a20897d1f192422390ad39357b0d883e4e6aebbae1d0abc41cfe8
7
- data.tar.gz: cb817018fab35b9594053d5bbd7fa4adb31c79a92d7e968a2739f9ffd16d194a3a45aeb75b6fcc28743fadcd93da83c9227cfe81d14bb2f9d917c18e1fb5d16b
6
+ metadata.gz: b26279c08a9387616104252d24147c920f27b0f66574cde1987024325c5fd1ba87aff876b39fdc3d4f472d9aba2c713b19a5105890b66ac3ef10a3018a0b1bca
7
+ data.tar.gz: d4c8db755880cd7088fd4b6087d2f38e86a4fdee11f867d58a480e143494ca2b85440abd9249b8c02a469a9ad21642c287d5ef8f30948bdc6d67695a895c1c13
data/README.md CHANGED
@@ -325,6 +325,19 @@ Kumi::Explain.call(FederalTax2024, :fed_tax, inputs: {income: 100_000, filing_st
325
325
  # = 15,099.50
326
326
  ```
327
327
 
328
+ **Debug AST Structure**: Visualize the parsed schema as S-expressions:
329
+
330
+ ```ruby
331
+ puts Kumi::Support::SExpressionPrinter.print(FederalTax2024.__syntax_tree__)
332
+ # => (Root
333
+ # inputs: [
334
+ # (InputDeclaration :income :float)
335
+ # (InputDeclaration :filing_status :string domain: ["single", "married_joint"])
336
+ # ]
337
+ # traits: [...]
338
+ # attributes: [...])
339
+ ```
340
+
328
341
  </details>
329
342
 
330
343
  <details>
data/docs/AST.md CHANGED
@@ -39,6 +39,13 @@ InputReference = Struct.new(:name)
39
39
  # Has operator methods: >=, <=, >, <, ==, != that create CallExpression nodes
40
40
  ```
41
41
 
42
+ **InputElementReference**: Access of nested input fields (`input.field_name.element.subelement.subsubelement`)
43
+ ```ruby
44
+ InputElementReference = Struct.new(:path)
45
+ # Represents nested input access
46
+ # DSL: input.address.street → InputElementReference([:address, :street])
47
+ ```
48
+
42
49
  **DeclarationReference**: References to other declarations
43
50
  ```ruby
44
51
  DeclarationReference = Struct.new(:name)
@@ -37,6 +37,13 @@ Processes large schemas with optimized algorithms.
37
37
  - Result caching
38
38
  - Selective evaluation
39
39
 
40
+ ### [S-Expression Printer](s-expression-printer.md)
41
+ Debug and inspect AST structures with readable S-expression notation output.
42
+
43
+ - Visitor pattern implementation for all node types
44
+ - Proper indentation and hierarchical structure
45
+ - Useful for debugging schema parsing and AST analysis
46
+
40
47
  ## Integration
41
48
 
42
49
  - Type inference uses input declarations
@@ -0,0 +1,77 @@
1
+ # S-Expression Printer
2
+
3
+ Debug and inspect Kumi AST structures with readable S-expression notation output.
4
+
5
+ ## Overview
6
+
7
+ The S-Expression Printer provides a clean, structured way to visualize Kumi's Abstract Syntax Tree (AST) nodes in traditional Lisp-style S-expression format. This is particularly useful for debugging schema parsing, understanding AST structure, and analyzing complex expressions.
8
+
9
+ ## Usage
10
+
11
+ ```ruby
12
+ require 'kumi/support/s_expression_printer'
13
+
14
+ # Print any AST node
15
+ Kumi::Support::SExpressionPrinter.print(node)
16
+
17
+ # Print a complete schema AST
18
+ module MySchema
19
+ extend Kumi::Schema
20
+
21
+ schema do
22
+ input do
23
+ integer :age
24
+ string :name
25
+ end
26
+
27
+ trait :adult, (input.age >= 18)
28
+ value :greeting, fn(:concat, "Hello ", input.name)
29
+ end
30
+ end
31
+
32
+ puts Kumi::Support::SExpressionPrinter.print(MySchema.__syntax_tree__)
33
+ ```
34
+
35
+ ## Output Format
36
+
37
+ The printer produces indented S-expressions that clearly show the hierarchical structure:
38
+
39
+ ```lisp
40
+ (Root
41
+ inputs: [
42
+ (InputDeclaration :age :integer)
43
+ (InputDeclaration :name :string)
44
+ ]
45
+ attributes: [
46
+ (ValueDeclaration :greeting
47
+ (CallExpression :concat
48
+ (Literal "Hello ")
49
+ (InputReference :name)
50
+ )
51
+ )
52
+ ]
53
+ traits: [
54
+ (TraitDeclaration :adult
55
+ (CallExpression :>=
56
+ (InputReference :age)
57
+ (Literal 18)
58
+ )
59
+ )
60
+ ]
61
+ )
62
+ ```
63
+
64
+ ## Supported Node Types
65
+
66
+ The printer handles all Kumi AST node types:
67
+
68
+ - **Root** - Schema container with inputs, attributes, and traits
69
+ - **Declarations** - InputDeclaration, ValueDeclaration, TraitDeclaration
70
+ - **Expressions** - CallExpression, ArrayExpression, CascadeExpression, CaseExpression
71
+ - **References** - InputReference, InputElementReference, DeclarationReference
72
+ - **Literals** - Literal values (strings, numbers, booleans)
73
+ - **Collections** - Arrays and HashExpression nodes
74
+
75
+ ## Implementation
76
+
77
+ Built as a visitor pattern class that traverses AST nodes recursively, with each node type having its own specialized formatting method. The printer preserves proper indentation and handles nested structures gracefully.
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Support
5
+ class SExpressionPrinter
6
+ def initialize(indent: 0)
7
+ @indent = indent
8
+ end
9
+
10
+ def visit(node)
11
+ return node.inspect unless node.respond_to?(:class)
12
+
13
+ case node
14
+ when nil then "nil"
15
+ when Array then visit_array(node)
16
+ when Kumi::Syntax::Root then visit_root(node)
17
+ when Kumi::Syntax::ValueDeclaration then visit_value_declaration(node)
18
+ when Kumi::Syntax::TraitDeclaration then visit_trait_declaration(node)
19
+ when Kumi::Syntax::InputDeclaration then visit_input_declaration(node)
20
+ when Kumi::Syntax::CallExpression then visit_call_expression(node)
21
+ when Kumi::Syntax::ArrayExpression then visit_array_expression(node)
22
+ when Kumi::Syntax::CascadeExpression then visit_cascade_expression(node)
23
+ when Kumi::Syntax::CaseExpression then visit_case_expression(node)
24
+ when Kumi::Syntax::InputReference then visit_input_reference(node)
25
+ when Kumi::Syntax::InputElementReference then visit_input_element_reference(node)
26
+ when Kumi::Syntax::DeclarationReference then visit_declaration_reference(node)
27
+ when Kumi::Syntax::Literal then visit_literal(node)
28
+ when Kumi::Syntax::HashExpression then visit_hash_expression(node)
29
+ else visit_generic(node)
30
+ end
31
+ end
32
+
33
+ def self.print(node, indent: 0)
34
+ new(indent: indent).visit(node)
35
+ end
36
+
37
+ private
38
+
39
+ def visit_array(node)
40
+ return "[]" if node.empty?
41
+
42
+ elements = node.map { |child| child_printer.visit(child) }
43
+ "[\n#{indent_str(2)}#{elements.join("\n#{indent_str(2)}")}\n#{indent_str}]"
44
+ end
45
+
46
+ def visit_root(node)
47
+ fields = %i[inputs attributes traits].map do |field|
48
+ value = node.public_send(field)
49
+ "#{field}: #{child_printer.visit(value)}"
50
+ end.join("\n#{indent_str(2)}")
51
+
52
+ "(Root\n#{indent_str(2)}#{fields}\n#{indent_str})"
53
+ end
54
+
55
+ def visit_value_declaration(node)
56
+ "(ValueDeclaration :#{node.name}\n#{child_indent}#{child_printer.visit(node.expression)}\n#{indent_str})"
57
+ end
58
+
59
+ def visit_trait_declaration(node)
60
+ "(TraitDeclaration :#{node.name}\n#{child_indent}#{child_printer.visit(node.expression)}\n#{indent_str})"
61
+ end
62
+
63
+ def visit_input_declaration(node)
64
+ fields = [":#{node.name}"]
65
+ fields << ":#{node.type}" if node.respond_to?(:type) && node.type
66
+ fields << "domain: #{node.domain.inspect}" if node.respond_to?(:domain) && node.domain
67
+
68
+ if node.respond_to?(:children) && !node.children.empty?
69
+ children_str = child_printer.visit(node.children)
70
+ "(InputDeclaration #{fields.join(' ')}\n#{child_indent}#{children_str}\n#{indent_str})"
71
+ else
72
+ "(InputDeclaration #{fields.join(' ')})"
73
+ end
74
+ end
75
+
76
+ def visit_call_expression(node)
77
+ return "(CallExpression :#{node.fn_name})" if node.args.empty?
78
+
79
+ args = node.args.map { |arg| child_printer.visit(arg) }
80
+ "(CallExpression :#{node.fn_name}\n#{indent_str(2)}#{args.join("\n#{indent_str(2)}")}\n#{indent_str})"
81
+ end
82
+
83
+ def visit_array_expression(node)
84
+ return "(ArrayExpression)" if node.elements.empty?
85
+
86
+ elements = node.elements.map { |elem| child_printer.visit(elem) }
87
+ "(ArrayExpression\n#{indent_str(2)}#{elements.join("\n#{indent_str(2)}")}\n#{indent_str})"
88
+ end
89
+
90
+ def visit_cascade_expression(node)
91
+ cases = node.cases.map do |case_expr|
92
+ "(#{visit(case_expr.condition)} #{visit(case_expr.result)})"
93
+ end.join("\n#{indent_str(2)}")
94
+
95
+ "(CascadeExpression\n#{indent_str(2)}#{cases}\n#{indent_str})"
96
+ end
97
+
98
+ def visit_case_expression(node)
99
+ "(CaseExpression #{visit(node.condition)} #{visit(node.result)})"
100
+ end
101
+
102
+ def visit_input_reference(node)
103
+ "(InputReference :#{node.name})"
104
+ end
105
+
106
+ def visit_input_element_reference(node)
107
+ "(InputElementReference #{node.path.map(&:to_s).join('.')})"
108
+ end
109
+
110
+ def visit_declaration_reference(node)
111
+ "(DeclarationReference :#{node.name})"
112
+ end
113
+
114
+ def visit_literal(node)
115
+ "(Literal #{node.value.inspect})"
116
+ end
117
+
118
+ def visit_hash_expression(node)
119
+ return "(HashExpression)" if node.pairs.empty?
120
+
121
+ pairs = node.pairs.map do |pair|
122
+ "(#{visit(pair.key)} #{visit(pair.value)})"
123
+ end.join("\n#{indent_str(2)}")
124
+
125
+ "(HashExpression\n#{indent_str(2)}#{pairs}\n#{indent_str})"
126
+ end
127
+
128
+ def visit_generic(node)
129
+ class_name = node.class.name&.split('::')&.last || node.class.to_s
130
+
131
+ if node.respond_to?(:children) && !node.children.empty?
132
+ children = node.children.map { |child| child_printer.visit(child) }
133
+ "(#{class_name}\n#{indent_str(2)}#{children.join("\n#{indent_str(2)}")}\n#{indent_str})"
134
+ elsif node.respond_to?(:members)
135
+ fields = node.members.reject { |m| m == :loc }.map do |member|
136
+ value = node[member]
137
+ "#{member}: #{child_printer.visit(value)}"
138
+ end
139
+
140
+ return "(#{class_name})" if fields.empty?
141
+
142
+ "(#{class_name}\n#{indent_str(2)}#{fields.join("\n#{indent_str(2)}")}\n#{indent_str})"
143
+ else
144
+ "(#{class_name} #{node.inspect})"
145
+ end
146
+ end
147
+
148
+ def child_printer
149
+ @child_printer ||= self.class.new(indent: @indent + 2)
150
+ end
151
+
152
+ def indent_str(extra = 0)
153
+ ' ' * (@indent + extra)
154
+ end
155
+
156
+ def child_indent
157
+ indent_str(2)
158
+ end
159
+ end
160
+ end
161
+ end
data/lib/kumi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kumi
4
- VERSION = "0.0.8"
4
+ VERSION = "0.0.9"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kumi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - André Muta
@@ -48,6 +48,7 @@ files:
48
48
  - docs/features/array-broadcasting.md
49
49
  - docs/features/input-declaration-system.md
50
50
  - docs/features/performance.md
51
+ - docs/features/s-expression-printer.md
51
52
  - docs/schema_metadata.md
52
53
  - docs/schema_metadata/broadcasts.md
53
54
  - docs/schema_metadata/cascades.md
@@ -143,6 +144,7 @@ files:
143
144
  - lib/kumi/registry.rb
144
145
  - lib/kumi/schema.rb
145
146
  - lib/kumi/schema_metadata.rb
147
+ - lib/kumi/support/s_expression_printer.rb
146
148
  - lib/kumi/syntax/array_expression.rb
147
149
  - lib/kumi/syntax/call_expression.rb
148
150
  - lib/kumi/syntax/cascade_expression.rb