parsanol 3.0.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.
Potentially problematic release.
This version of parsanol might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/HISTORY.txt +25 -0
- data/LICENSE +23 -0
- data/README.adoc +643 -0
- data/Rakefile +189 -0
- data/example/balanced-parens/basic.rb +42 -0
- data/example/balanced-parens/basic.rb.md +86 -0
- data/example/balanced-parens/parens.rb +42 -0
- data/example/balanced-parens/ruby_transform.rb +162 -0
- data/example/big.erb +73 -0
- data/example/boolean-algebra/basic.rb +70 -0
- data/example/boolean-algebra/basic.rb.md +108 -0
- data/example/boolean-algebra/ruby_transform.rb +263 -0
- data/example/calculator/basic.rb +153 -0
- data/example/calculator/basic.rb.md +120 -0
- data/example/calculator/pattern.rb +153 -0
- data/example/calculator/ruby_transform.rb +156 -0
- data/example/calculator/ruby_transform.rb.md +32 -0
- data/example/calculator/serialized.rb +257 -0
- data/example/calculator/serialized.rb.md +32 -0
- data/example/calculator/transform.rb +153 -0
- data/example/calculator/zero_copy.rb +269 -0
- data/example/calculator/zero_copy.rb.md +36 -0
- data/example/capture/basic.rb +49 -0
- data/example/capture/basic.rb.md +106 -0
- data/example/capture/example.json +39 -0
- data/example/comments/basic.rb +35 -0
- data/example/comments/basic.rb.md +110 -0
- data/example/csv/ruby_transform.rb +148 -0
- data/example/csv/ruby_transform.rb.md +131 -0
- data/example/csv/serialized.rb +201 -0
- data/example/csv/serialized.rb.md +31 -0
- data/example/csv/zero_copy.rb +276 -0
- data/example/csv/zero_copy.rb.md +36 -0
- data/example/custom_atoms/indent_atom.rb +79 -0
- data/example/deepest-errors/basic.rb +131 -0
- data/example/deepest-errors/basic.rb.md +152 -0
- data/example/documentation/basic.rb +18 -0
- data/example/documentation/basic.rb.md +97 -0
- data/example/email/basic.rb +55 -0
- data/example/email/basic.rb.md +102 -0
- data/example/email/ruby_transform.rb +106 -0
- data/example/empty/basic.rb +13 -0
- data/example/empty/basic.rb.md +73 -0
- data/example/empty/example.json +38 -0
- data/example/erb/basic.rb +47 -0
- data/example/erb/basic.rb.md +103 -0
- data/example/erb/optimized.rb +42 -0
- data/example/error-reporting/basic.rb +132 -0
- data/example/error-reporting/basic.rb.md +122 -0
- data/example/expression-evaluator/basic.rb +284 -0
- data/example/expression-evaluator/basic.rb.md +138 -0
- data/example/ini/basic.rb +154 -0
- data/example/ini/basic.rb.md +129 -0
- data/example/ini/ruby_transform.rb +154 -0
- data/example/ip-address/basic.rb +125 -0
- data/example/ip-address/basic.rb.md +139 -0
- data/example/iso-6709/basic.rb +231 -0
- data/example/iso-6709/basic.rb.md +143 -0
- data/example/iso-8601/basic.rb +275 -0
- data/example/iso-8601/basic.rb.md +149 -0
- data/example/json/basic.rb +128 -0
- data/example/json/basic.rb.md +121 -0
- data/example/json/pattern.rb +128 -0
- data/example/json/ruby_transform.rb +200 -0
- data/example/json/ruby_transform.rb.md +32 -0
- data/example/json/serialized.rb +233 -0
- data/example/json/serialized.rb.md +31 -0
- data/example/json/transform.rb +128 -0
- data/example/json/zero_copy.rb +316 -0
- data/example/json/zero_copy.rb.md +36 -0
- data/example/local/basic.rb +34 -0
- data/example/local/basic.rb.md +91 -0
- data/example/local/example.json +38 -0
- data/example/markdown/basic.rb +287 -0
- data/example/markdown/basic.rb.md +160 -0
- data/example/markup/basic.rb +173 -0
- data/example/markup/basic.rb.md +118 -0
- data/example/mathn/basic.rb +47 -0
- data/example/mathn/basic.rb.md +96 -0
- data/example/mathn/example.json +39 -0
- data/example/minilisp/basic.rb +94 -0
- data/example/minilisp/basic.rb.md +133 -0
- data/example/modularity/basic.rb +47 -0
- data/example/modularity/basic.rb.md +152 -0
- data/example/nested-errors/basic.rb +132 -0
- data/example/nested-errors/basic.rb.md +157 -0
- data/example/output/boolean_algebra.out +4 -0
- data/example/output/calc.out +1 -0
- data/example/output/capture.out +3 -0
- data/example/output/comments.out +8 -0
- data/example/output/deepest_errors.out +54 -0
- data/example/output/documentation.err +4 -0
- data/example/output/documentation.out +1 -0
- data/example/output/email_parser.out +2 -0
- data/example/output/empty.err +1 -0
- data/example/output/erb.out +7 -0
- data/example/output/ignore.out +1 -0
- data/example/output/ignore_whitespace.out +1 -0
- data/example/output/ip_address.out +9 -0
- data/example/output/json.out +5 -0
- data/example/output/local.out +3 -0
- data/example/output/mathn.out +4 -0
- data/example/output/minilisp.out +5 -0
- data/example/output/modularity.out +0 -0
- data/example/output/nested_errors.out +54 -0
- data/example/output/optimized_erb.out +1 -0
- data/example/output/parens.out +8 -0
- data/example/output/prec_calc.out +5 -0
- data/example/output/readme.out +1 -0
- data/example/output/scopes.out +1 -0
- data/example/output/seasons.out +28 -0
- data/example/output/sentence.out +1 -0
- data/example/output/simple_xml.out +2 -0
- data/example/output/string_parser.out +3 -0
- data/example/prec-calc/basic.rb +71 -0
- data/example/prec-calc/basic.rb.md +114 -0
- data/example/readme/basic.rb +30 -0
- data/example/readme/basic.rb.md +80 -0
- data/example/scopes/basic.rb +15 -0
- data/example/scopes/basic.rb.md +73 -0
- data/example/scopes/example.json +38 -0
- data/example/seasons/basic.rb +46 -0
- data/example/seasons/basic.rb.md +117 -0
- data/example/seasons/example.json +40 -0
- data/example/sentence/basic.rb +36 -0
- data/example/sentence/basic.rb.md +81 -0
- data/example/sexp/ruby_transform.rb +180 -0
- data/example/sexp/ruby_transform.rb.md +143 -0
- data/example/simple-xml/basic.rb +54 -0
- data/example/simple-xml/basic.rb.md +125 -0
- data/example/simple.lit +3 -0
- data/example/string-literal/basic.rb +77 -0
- data/example/string-literal/basic.rb.md +128 -0
- data/example/test.lit +4 -0
- data/example/toml/basic.rb +226 -0
- data/example/toml/basic.rb.md +173 -0
- data/example/url/basic.rb +219 -0
- data/example/url/basic.rb.md +142 -0
- data/example/url/ruby_transform.rb +219 -0
- data/example/yaml/basic.rb +216 -0
- data/example/yaml/basic.rb.md +148 -0
- data/ext/parsanol_native/extconf.rb +4 -0
- data/lib/parsanol/accelerator/application.rb +62 -0
- data/lib/parsanol/accelerator/engine.rb +112 -0
- data/lib/parsanol/accelerator.rb +162 -0
- data/lib/parsanol/ast_visitor.rb +122 -0
- data/lib/parsanol/atoms/alternative.rb +97 -0
- data/lib/parsanol/atoms/base.rb +214 -0
- data/lib/parsanol/atoms/can_flatten.rb +192 -0
- data/lib/parsanol/atoms/capture.rb +41 -0
- data/lib/parsanol/atoms/context.rb +351 -0
- data/lib/parsanol/atoms/context_optimized.rb +42 -0
- data/lib/parsanol/atoms/custom.rb +110 -0
- data/lib/parsanol/atoms/cut.rb +62 -0
- data/lib/parsanol/atoms/dsl.rb +130 -0
- data/lib/parsanol/atoms/dynamic.rb +33 -0
- data/lib/parsanol/atoms/entity.rb +55 -0
- data/lib/parsanol/atoms/ignored.rb +28 -0
- data/lib/parsanol/atoms/infix.rb +121 -0
- data/lib/parsanol/atoms/lookahead.rb +64 -0
- data/lib/parsanol/atoms/named.rb +50 -0
- data/lib/parsanol/atoms/re.rb +61 -0
- data/lib/parsanol/atoms/repetition.rb +241 -0
- data/lib/parsanol/atoms/scope.rb +28 -0
- data/lib/parsanol/atoms/sequence.rb +157 -0
- data/lib/parsanol/atoms/str.rb +90 -0
- data/lib/parsanol/atoms/visitor.rb +91 -0
- data/lib/parsanol/atoms.rb +36 -0
- data/lib/parsanol/buffer.rb +130 -0
- data/lib/parsanol/builder_callbacks.rb +353 -0
- data/lib/parsanol/cause.rb +101 -0
- data/lib/parsanol/context.rb +23 -0
- data/lib/parsanol/convenience.rb +35 -0
- data/lib/parsanol/edit_tracker.rb +107 -0
- data/lib/parsanol/error_reporter/contextual.rb +122 -0
- data/lib/parsanol/error_reporter/deepest.rb +106 -0
- data/lib/parsanol/error_reporter/tree.rb +68 -0
- data/lib/parsanol/error_reporter.rb +98 -0
- data/lib/parsanol/export.rb +163 -0
- data/lib/parsanol/expression/treetop.rb +94 -0
- data/lib/parsanol/expression.rb +51 -0
- data/lib/parsanol/fast_mode.rb +145 -0
- data/lib/parsanol/first_set.rb +75 -0
- data/lib/parsanol/grammar_builder.rb +177 -0
- data/lib/parsanol/graphviz.rb +97 -0
- data/lib/parsanol/incremental_parser.rb +179 -0
- data/lib/parsanol/interval_tree.rb +215 -0
- data/lib/parsanol/lazy_result.rb +178 -0
- data/lib/parsanol/lexer.rb +146 -0
- data/lib/parsanol/native/parser.rb +630 -0
- data/lib/parsanol/native/serializer.rb +245 -0
- data/lib/parsanol/native/transformer.rb +438 -0
- data/lib/parsanol/native/types.rb +41 -0
- data/lib/parsanol/native.rb +217 -0
- data/lib/parsanol/optimizer.rb +86 -0
- data/lib/parsanol/optimizers/choice_optimizer.rb +78 -0
- data/lib/parsanol/optimizers/cut_inserter.rb +175 -0
- data/lib/parsanol/optimizers/lookahead_optimizer.rb +58 -0
- data/lib/parsanol/optimizers/quantifier_optimizer.rb +62 -0
- data/lib/parsanol/optimizers/sequence_optimizer.rb +97 -0
- data/lib/parsanol/options/ruby_transform.rb +109 -0
- data/lib/parsanol/options/serialized.rb +94 -0
- data/lib/parsanol/options/zero_copy.rb +130 -0
- data/lib/parsanol/options.rb +20 -0
- data/lib/parsanol/parallel.rb +133 -0
- data/lib/parsanol/parsanol_native.bundle +0 -0
- data/lib/parsanol/parser.rb +151 -0
- data/lib/parsanol/parslet.rb +148 -0
- data/lib/parsanol/parslet_native.bundle +0 -0
- data/lib/parsanol/pattern/binding.rb +49 -0
- data/lib/parsanol/pattern.rb +115 -0
- data/lib/parsanol/pool.rb +220 -0
- data/lib/parsanol/pools/array_pool.rb +75 -0
- data/lib/parsanol/pools/buffer_pool.rb +173 -0
- data/lib/parsanol/pools/position_pool.rb +92 -0
- data/lib/parsanol/pools/slice_pool.rb +64 -0
- data/lib/parsanol/position.rb +89 -0
- data/lib/parsanol/result.rb +44 -0
- data/lib/parsanol/result_builder.rb +208 -0
- data/lib/parsanol/result_stream.rb +262 -0
- data/lib/parsanol/rig/rspec.rb +52 -0
- data/lib/parsanol/rope.rb +78 -0
- data/lib/parsanol/scope.rb +42 -0
- data/lib/parsanol/slice.rb +172 -0
- data/lib/parsanol/source/line_cache.rb +99 -0
- data/lib/parsanol/source.rb +171 -0
- data/lib/parsanol/source_location.rb +164 -0
- data/lib/parsanol/streaming_parser.rb +124 -0
- data/lib/parsanol/string_view.rb +192 -0
- data/lib/parsanol/transform.rb +267 -0
- data/lib/parsanol/version.rb +5 -0
- data/lib/parsanol/wasm/README.md +80 -0
- data/lib/parsanol/wasm/package.json +51 -0
- data/lib/parsanol/wasm/parsanol.js +252 -0
- data/lib/parsanol/wasm/parslet.d.ts +129 -0
- data/lib/parsanol/wasm_parser.rb +239 -0
- data/lib/parsanol.rb +408 -0
- data/parsanol-ruby.gemspec +56 -0
- data/spec/acceptance/examples_spec.rb +96 -0
- data/spec/acceptance/infix_parser_spec.rb +145 -0
- data/spec/acceptance/mixing_parsers_spec.rb +74 -0
- data/spec/acceptance/regression_spec.rb +329 -0
- data/spec/acceptance/repetition_and_maybe_spec.rb +44 -0
- data/spec/acceptance/unconsumed_input_spec.rb +21 -0
- data/spec/benchmark/comparative/runner_spec.rb +105 -0
- data/spec/integration/array_pooling_spec.rb +193 -0
- data/spec/integration/buffer_allocation_spec.rb +324 -0
- data/spec/integration/position_pooling_spec.rb +184 -0
- data/spec/integration/result_builder_spec.rb +282 -0
- data/spec/integration/rope_stringview_integration_spec.rb +188 -0
- data/spec/integration/slice_pooling_spec.rb +63 -0
- data/spec/integration/string_view_integration_spec.rb +125 -0
- data/spec/lexer_spec.rb +231 -0
- data/spec/parsanol/atom_results_spec.rb +39 -0
- data/spec/parsanol/atoms/alternative_spec.rb +26 -0
- data/spec/parsanol/atoms/base_spec.rb +127 -0
- data/spec/parsanol/atoms/capture_spec.rb +21 -0
- data/spec/parsanol/atoms/combinations_spec.rb +5 -0
- data/spec/parsanol/atoms/custom_spec.rb +79 -0
- data/spec/parsanol/atoms/dsl_spec.rb +7 -0
- data/spec/parsanol/atoms/entity_spec.rb +77 -0
- data/spec/parsanol/atoms/ignored_spec.rb +15 -0
- data/spec/parsanol/atoms/infix_spec.rb +5 -0
- data/spec/parsanol/atoms/lookahead_spec.rb +22 -0
- data/spec/parsanol/atoms/named_spec.rb +4 -0
- data/spec/parsanol/atoms/re_spec.rb +14 -0
- data/spec/parsanol/atoms/repetition_spec.rb +24 -0
- data/spec/parsanol/atoms/scope_spec.rb +26 -0
- data/spec/parsanol/atoms/sequence_spec.rb +28 -0
- data/spec/parsanol/atoms/str_spec.rb +15 -0
- data/spec/parsanol/atoms/visitor_spec.rb +101 -0
- data/spec/parsanol/atoms_spec.rb +488 -0
- data/spec/parsanol/auto_optimize_spec.rb +334 -0
- data/spec/parsanol/buffer_spec.rb +219 -0
- data/spec/parsanol/builder_callbacks_spec.rb +377 -0
- data/spec/parsanol/choice_optimizer_spec.rb +231 -0
- data/spec/parsanol/convenience_spec.rb +54 -0
- data/spec/parsanol/cut_inserter_spec.rb +248 -0
- data/spec/parsanol/cut_spec.rb +66 -0
- data/spec/parsanol/edit_tracker_spec.rb +218 -0
- data/spec/parsanol/error_reporter/contextual_spec.rb +122 -0
- data/spec/parsanol/error_reporter/deepest_spec.rb +82 -0
- data/spec/parsanol/error_reporter/tree_spec.rb +7 -0
- data/spec/parsanol/export_spec.rb +67 -0
- data/spec/parsanol/expression/treetop_spec.rb +75 -0
- data/spec/parsanol/first_set_spec.rb +298 -0
- data/spec/parsanol/interval_tree_spec.rb +205 -0
- data/spec/parsanol/lazy_result_spec.rb +288 -0
- data/spec/parsanol/lookahead_optimizer_spec.rb +252 -0
- data/spec/parsanol/minilisp.citrus +29 -0
- data/spec/parsanol/minilisp.tt +29 -0
- data/spec/parsanol/optimizer_spec.rb +459 -0
- data/spec/parsanol/options/parslet_compat_spec.rb +166 -0
- data/spec/parsanol/options/ruby_transform_spec.rb +70 -0
- data/spec/parsanol/options/serialized_spec.rb +69 -0
- data/spec/parsanol/options/zero_copy_spec.rb +230 -0
- data/spec/parsanol/parser_spec.rb +36 -0
- data/spec/parsanol/parslet_spec.rb +38 -0
- data/spec/parsanol/pattern_spec.rb +272 -0
- data/spec/parsanol/pool_spec.rb +392 -0
- data/spec/parsanol/pools/array_pool_spec.rb +356 -0
- data/spec/parsanol/pools/buffer_pool_spec.rb +365 -0
- data/spec/parsanol/pools/position_pool_spec.rb +118 -0
- data/spec/parsanol/pools/slice_pool_spec.rb +262 -0
- data/spec/parsanol/position_spec.rb +14 -0
- data/spec/parsanol/result_builder_spec.rb +391 -0
- data/spec/parsanol/rig/rspec_spec.rb +54 -0
- data/spec/parsanol/rope_spec.rb +207 -0
- data/spec/parsanol/scope_spec.rb +45 -0
- data/spec/parsanol/slice_spec.rb +249 -0
- data/spec/parsanol/source/line_cache_spec.rb +74 -0
- data/spec/parsanol/source_spec.rb +207 -0
- data/spec/parsanol/string_view_spec.rb +345 -0
- data/spec/parsanol/transform/context_spec.rb +56 -0
- data/spec/parsanol/transform_spec.rb +183 -0
- data/spec/parsanol/tree_memoization_spec.rb +149 -0
- data/spec/parslet_compatibility/expressir_edge_cases_spec.rb +153 -0
- data/spec/parslet_compatibility/minimal_reproduction.rb +199 -0
- data/spec/parslet_compatibility_spec.rb +399 -0
- data/spec/parslet_imported/atom_spec.rb +93 -0
- data/spec/parslet_imported/combinator_spec.rb +161 -0
- data/spec/parslet_imported/spec_helper.rb +73 -0
- data/spec/performance/batch_parsing_benchmark.rb +129 -0
- data/spec/performance/complete_optimization_summary.rb +143 -0
- data/spec/performance/grammar_caching_analysis.rb +121 -0
- data/spec/performance/grammar_caching_benchmark.rb +80 -0
- data/spec/performance/native_benchmark_spec.rb +230 -0
- data/spec/performance/phase5_benchmark.rb +144 -0
- data/spec/performance/profiling_benchmark.rb +131 -0
- data/spec/performance/ruby_improvements_benchmark.rb +171 -0
- data/spec/performance_spec.rb +374 -0
- data/spec/spec_helper.rb +79 -0
- data/spec/support/opal.rb +8 -0
- data/spec/support/opal.rb.erb +14 -0
- metadata +485 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# JSON Parser Example - ZeroCopy: Mirrored Objects (Direct FFI)
|
|
2
|
+
#
|
|
3
|
+
# This example demonstrates ZeroCopy for parsing JSON:
|
|
4
|
+
# 1. Rust parser (parsanol-rs) does the parsing
|
|
5
|
+
# 2. Rust constructs typed JSON value objects
|
|
6
|
+
# 3. Direct Ruby object construction via FFI (no serialization!)
|
|
7
|
+
# 4. Maximum performance with zero-copy
|
|
8
|
+
#
|
|
9
|
+
# This option provides the best performance for JSON parsing.
|
|
10
|
+
|
|
11
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
|
12
|
+
|
|
13
|
+
require 'parsanol'
|
|
14
|
+
|
|
15
|
+
# NOTE: This example requires:
|
|
16
|
+
# 1. ZeroCopy extension support for parse_to_objects
|
|
17
|
+
# 2. #[derive(RubyObject)] proc macro in Rust
|
|
18
|
+
# 3. Matching Ruby class definitions
|
|
19
|
+
#
|
|
20
|
+
# This serves as an API preview.
|
|
21
|
+
|
|
22
|
+
# Step 1: Define Ruby classes that mirror Rust struct definitions
|
|
23
|
+
module Json
|
|
24
|
+
class Value
|
|
25
|
+
def to_ruby
|
|
26
|
+
raise NotImplementedError
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class Null < Value
|
|
31
|
+
def to_ruby = nil
|
|
32
|
+
def to_s = "null"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class Bool < Value
|
|
36
|
+
attr_reader :value
|
|
37
|
+
|
|
38
|
+
def initialize(value)
|
|
39
|
+
@value = value
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_ruby = @value
|
|
43
|
+
def to_s = @value ? "true" : "false"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class Number < Value
|
|
47
|
+
attr_reader :value
|
|
48
|
+
|
|
49
|
+
def initialize(value)
|
|
50
|
+
@value = value
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def to_ruby = @value
|
|
54
|
+
def to_s = @value.to_s
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class String < Value
|
|
58
|
+
attr_reader :value
|
|
59
|
+
|
|
60
|
+
def initialize(value)
|
|
61
|
+
@value = value
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_ruby = @value
|
|
65
|
+
def to_s = "\"#{@value}\""
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class Array < Value
|
|
69
|
+
attr_reader :elements
|
|
70
|
+
|
|
71
|
+
def initialize(elements)
|
|
72
|
+
@elements = elements
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def to_ruby = @elements.map(&:to_ruby)
|
|
76
|
+
def to_s = "[#{@elements.map(&:to_s).join(', ')}]"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class Object < Value
|
|
80
|
+
attr_reader :members
|
|
81
|
+
|
|
82
|
+
def initialize(members)
|
|
83
|
+
@members = members
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def to_ruby = @members.transform_values(&:to_ruby)
|
|
87
|
+
|
|
88
|
+
def to_s
|
|
89
|
+
pairs = @members.map { |k, v| "\"#{k}\": #{v}" }
|
|
90
|
+
"{#{pairs.join(', ')}}"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def [](key)
|
|
94
|
+
@members[key]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def keys
|
|
98
|
+
@members.keys
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Step 2: Define the parser with output type mapping
|
|
104
|
+
class JsonParser < Parsanol::Parser
|
|
105
|
+
# Include ZeroCopy module (planned)
|
|
106
|
+
# include Parsanol::ZeroCopy
|
|
107
|
+
|
|
108
|
+
root :json
|
|
109
|
+
|
|
110
|
+
rule(:json) { space? >> value >> space? }
|
|
111
|
+
|
|
112
|
+
rule(:value) {
|
|
113
|
+
object |
|
|
114
|
+
array |
|
|
115
|
+
string |
|
|
116
|
+
number |
|
|
117
|
+
true_value |
|
|
118
|
+
false_value |
|
|
119
|
+
null_value
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
rule(:object) {
|
|
123
|
+
str('{') >> space? >>
|
|
124
|
+
(entry >> (comma >> entry).repeat).maybe.as(:object) >>
|
|
125
|
+
space? >> str('}')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
rule(:entry) {
|
|
129
|
+
(string.as(:key) >> space? >> colon >> space? >> value.as(:val)).as(:entry)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
rule(:array) {
|
|
133
|
+
str('[') >> space? >>
|
|
134
|
+
(value >> (comma >> value).repeat).maybe.as(:array) >>
|
|
135
|
+
space? >> str(']')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
rule(:string) {
|
|
139
|
+
str('"') >> (
|
|
140
|
+
str('\\') >> any | str('"').absent? >> any
|
|
141
|
+
).repeat.as(:string) >> str('"')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
rule(:number) {
|
|
145
|
+
(
|
|
146
|
+
str('-').maybe >>
|
|
147
|
+
(str('0') | (match('[1-9]') >> digit.repeat)) >>
|
|
148
|
+
(str('.') >> digit.repeat(1)).maybe >>
|
|
149
|
+
(match('[eE]') >> (str('+') | str('-')).maybe >> digit.repeat(1)).maybe
|
|
150
|
+
).as(:number)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
rule(:true_value) { str('true').as(:true) }
|
|
154
|
+
rule(:false_value) { str('false').as(:false) }
|
|
155
|
+
rule(:null_value) { str('null').as(:null) }
|
|
156
|
+
|
|
157
|
+
rule(:digit) { match('[0-9]') }
|
|
158
|
+
rule(:space) { match('\s').repeat(1) }
|
|
159
|
+
rule(:space?) { space.maybe }
|
|
160
|
+
rule(:comma) { space? >> str(',') >> space? }
|
|
161
|
+
rule(:colon) { str(':') }
|
|
162
|
+
|
|
163
|
+
# Output type mapping (planned feature)
|
|
164
|
+
# output_types(
|
|
165
|
+
# null: Json::Null,
|
|
166
|
+
# bool: Json::Bool,
|
|
167
|
+
# number: Json::Number,
|
|
168
|
+
# string: Json::String,
|
|
169
|
+
# array: Json::Array,
|
|
170
|
+
# object: Json::Object
|
|
171
|
+
# )
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Step 3: Parse with direct object construction
|
|
175
|
+
def parse_json(input)
|
|
176
|
+
parser = JsonParser.new
|
|
177
|
+
|
|
178
|
+
# ZeroCopy: Parse and get direct Ruby objects
|
|
179
|
+
# NOTE: This requires native extension support
|
|
180
|
+
# value = parser.parse(input)
|
|
181
|
+
# # value is already a Json::String, Json::Number, etc.!
|
|
182
|
+
# # No transform needed, no JSON serialization!
|
|
183
|
+
|
|
184
|
+
# For demonstration, simulate what ZeroCopy would return
|
|
185
|
+
value = simulate_parse(input)
|
|
186
|
+
puts "Parsed: #{value.class}"
|
|
187
|
+
puts "Value: #{value.to_s}"
|
|
188
|
+
|
|
189
|
+
result = value.to_ruby
|
|
190
|
+
puts "Ruby: #{result.inspect[0..100]}..."
|
|
191
|
+
|
|
192
|
+
result
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Simulated parsing for demonstration
|
|
196
|
+
def simulate_parse(input)
|
|
197
|
+
input = input.strip
|
|
198
|
+
|
|
199
|
+
case input
|
|
200
|
+
when 'null'
|
|
201
|
+
Json::Null.new
|
|
202
|
+
when 'true'
|
|
203
|
+
Json::Bool.new(true)
|
|
204
|
+
when 'false'
|
|
205
|
+
Json::Bool.new(false)
|
|
206
|
+
when /^"(.*)"$/
|
|
207
|
+
Json::String.new($1)
|
|
208
|
+
when /^-?\d+$/
|
|
209
|
+
Json::Number.new(input.to_i)
|
|
210
|
+
when /^-?\d+\.\d+$/
|
|
211
|
+
Json::Number.new(input.to_f)
|
|
212
|
+
when /^\[(.*)\]$/
|
|
213
|
+
inner = $1.strip
|
|
214
|
+
return Json::Array.new([]) if inner.empty?
|
|
215
|
+
|
|
216
|
+
# Simple split for demonstration
|
|
217
|
+
elements = inner.split(',').map { |e| simulate_parse(e.strip) }
|
|
218
|
+
Json::Array.new(elements)
|
|
219
|
+
when /^\{(.*)\}$/
|
|
220
|
+
inner = $1.strip
|
|
221
|
+
return Json::Object.new({}) if inner.empty?
|
|
222
|
+
|
|
223
|
+
# Simple parse for demonstration
|
|
224
|
+
members = {}
|
|
225
|
+
inner.scan(/"([^"]+)":\s*([^,}]+)/) do |key, val|
|
|
226
|
+
members[key] = simulate_parse(val.strip)
|
|
227
|
+
end
|
|
228
|
+
Json::Object.new(members)
|
|
229
|
+
else
|
|
230
|
+
raise "Cannot parse: #{input}"
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Example usage
|
|
235
|
+
if __FILE__ == $0
|
|
236
|
+
puts "=" * 60
|
|
237
|
+
puts "JSON Parser Example - ZeroCopy: Mirrored Objects"
|
|
238
|
+
puts "=" * 60
|
|
239
|
+
puts
|
|
240
|
+
puts "NOTE: This example shows the planned API for ZeroCopy."
|
|
241
|
+
puts "The native extension support for parse_to_objects is coming soon."
|
|
242
|
+
puts
|
|
243
|
+
|
|
244
|
+
test_cases = [
|
|
245
|
+
['"hello"', "hello"],
|
|
246
|
+
['42', 42],
|
|
247
|
+
['true', true],
|
|
248
|
+
['null', nil],
|
|
249
|
+
['[1, 2, 3]', [1, 2, 3]],
|
|
250
|
+
['{"a": 1}', { "a" => 1 }],
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
test_cases.each do |input, expected|
|
|
254
|
+
puts
|
|
255
|
+
puts "-" * 40
|
|
256
|
+
puts "Input: #{input}"
|
|
257
|
+
begin
|
|
258
|
+
result = parse_json(input)
|
|
259
|
+
status = result == expected ? "✓ PASS" : "✗ FAIL"
|
|
260
|
+
puts "Expected: #{expected.inspect}, Got: #{result.inspect} - #{status}"
|
|
261
|
+
rescue => e
|
|
262
|
+
puts "Error: #{e.message}"
|
|
263
|
+
puts "✗ FAIL"
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Show type safety benefit
|
|
268
|
+
puts
|
|
269
|
+
puts "-" * 40
|
|
270
|
+
puts "Type Safety Example:"
|
|
271
|
+
json_obj = simulate_parse('{"name": "Alice", "age": 30}')
|
|
272
|
+
puts "Parsed object type: #{json_obj.class}"
|
|
273
|
+
puts "Name field type: #{json_obj['name'].class}"
|
|
274
|
+
puts "Age field type: #{json_obj['age'].class}"
|
|
275
|
+
|
|
276
|
+
puts
|
|
277
|
+
puts "=" * 60
|
|
278
|
+
puts "ZeroCopy Benefits for JSON:"
|
|
279
|
+
puts "- FASTEST: No serialization overhead"
|
|
280
|
+
puts "- Type-safe: Each JSON value type is a different class"
|
|
281
|
+
puts "- Methods: Can add custom methods to Json::Object, etc."
|
|
282
|
+
puts "- Zero-copy: Direct construction from Rust"
|
|
283
|
+
puts
|
|
284
|
+
puts "When to use ZeroCopy for JSON:"
|
|
285
|
+
puts "- High-throughput JSON parsing"
|
|
286
|
+
puts "- When you need typed access to values"
|
|
287
|
+
puts "- When you want custom methods on JSON objects"
|
|
288
|
+
puts "=" * 60
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Rust code that would be needed (for reference):
|
|
292
|
+
#
|
|
293
|
+
# // In parsanol-rs
|
|
294
|
+
# use parsanol_ruby_derive::RubyObject;
|
|
295
|
+
#
|
|
296
|
+
# #[derive(Debug, Clone, RubyObject)]
|
|
297
|
+
# #[ruby_class("Json::Value")]
|
|
298
|
+
# pub enum JsonValue {
|
|
299
|
+
# #[ruby_variant("null")]
|
|
300
|
+
# Null,
|
|
301
|
+
#
|
|
302
|
+
# #[ruby_variant("bool")]
|
|
303
|
+
# Bool(bool),
|
|
304
|
+
#
|
|
305
|
+
# #[ruby_variant("number")]
|
|
306
|
+
# Number(f64),
|
|
307
|
+
#
|
|
308
|
+
# #[ruby_variant("string")]
|
|
309
|
+
# String(String),
|
|
310
|
+
#
|
|
311
|
+
# #[ruby_variant("array")]
|
|
312
|
+
# Array(Vec<JsonValue>),
|
|
313
|
+
#
|
|
314
|
+
# #[ruby_variant("object")]
|
|
315
|
+
# Object(HashMap<String, JsonValue>),
|
|
316
|
+
# }
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# JSON (Zero-Copy - Option C)
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
This implementation demonstrates direct FFI object construction for JSON
|
|
6
|
+
parsing without serialization overhead.
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
- Maximum performance required
|
|
11
|
+
- Production systems
|
|
12
|
+
- When zero-copy is critical
|
|
13
|
+
|
|
14
|
+
## Key Concepts
|
|
15
|
+
|
|
16
|
+
1. **Direct FFI**: No serialization overhead
|
|
17
|
+
2. **Ruby Object Construction**: Direct via rb_funcall
|
|
18
|
+
3. **Type Safety**: Mirrored types on both sides
|
|
19
|
+
|
|
20
|
+
## Running
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
ruby example/json/zero_copy.rb
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Output
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
Input: {"key": "value"}
|
|
30
|
+
Result: Hash
|
|
31
|
+
Value: {"key" => "value"}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Note
|
|
35
|
+
|
|
36
|
+
This is the fastest option but requires more complex FFI setup.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
# An exploration of two ideas:
|
|
3
|
+
# a) Constructing a whole parser inline, without the artificial class around
|
|
4
|
+
# it.
|
|
5
|
+
# and:
|
|
6
|
+
# b) Constructing non-greedy or non-blind parsers by transforming the
|
|
7
|
+
# grammar.
|
|
8
|
+
|
|
9
|
+
$:.unshift File.dirname(__FILE__) + "/../lib"
|
|
10
|
+
|
|
11
|
+
require 'parsanol/parslet'
|
|
12
|
+
include Parsanol::Parslet
|
|
13
|
+
|
|
14
|
+
a = str('a').repeat >> str('aa')
|
|
15
|
+
|
|
16
|
+
# E1% E2
|
|
17
|
+
#
|
|
18
|
+
# S = E2 | E1 S
|
|
19
|
+
|
|
20
|
+
def this(name, &block); return Parsanol::Atoms::Entity.new(name, &block) end
|
|
21
|
+
def epsilon; any.absent? end
|
|
22
|
+
|
|
23
|
+
# Traditional repetition will try as long as the pattern can be matched and
|
|
24
|
+
# then give up. This is greedy and blind.
|
|
25
|
+
a = str('a').as(:e) >> this('a') { a }.as(:rec) | epsilon
|
|
26
|
+
|
|
27
|
+
# Here's a pattern match that is greedy and non-blind. The first pattern
|
|
28
|
+
# 'a'* will be tried as many times as possible, while still matching the
|
|
29
|
+
# end pattern 'aa'.
|
|
30
|
+
b = str('aa').as(:e2) >> epsilon | str('a').as(:e1) >> this('b') { b }.as(:rec)
|
|
31
|
+
|
|
32
|
+
p a.parse('aaaa')
|
|
33
|
+
p b
|
|
34
|
+
p b.parse('aaaa')
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Non-Greedy Parsing Patterns - Ruby Implementation
|
|
2
|
+
|
|
3
|
+
## How to Run
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
cd parsanol-ruby/example/local
|
|
7
|
+
ruby basic.rb
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Code Walkthrough
|
|
11
|
+
|
|
12
|
+
### The Problem with Greedy Parsing
|
|
13
|
+
|
|
14
|
+
Traditional repetition is greedy and blind:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
a = str('a').repeat >> str('aa')
|
|
18
|
+
# Input: 'aaaa'
|
|
19
|
+
# Fails! repeat consumes all 'a's, leaving nothing for 'aa'
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Greedy repetition takes as much as possible without considering what follows.
|
|
23
|
+
|
|
24
|
+
### Non-Blind Pattern
|
|
25
|
+
|
|
26
|
+
Transform the grammar to look ahead:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
# E1% E2 transformation:
|
|
30
|
+
# S = E2 | E1 S
|
|
31
|
+
|
|
32
|
+
def this(name, &block)
|
|
33
|
+
Parsanol::Atoms::Entity.new(name, &block)
|
|
34
|
+
end
|
|
35
|
+
def epsilon
|
|
36
|
+
any.absent?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
a = str('a').as(:e) >> this('a') { a }.as(:rec) | epsilon
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This recursively matches while checking alternatives.
|
|
43
|
+
|
|
44
|
+
### Greedy Non-Blind Alternative
|
|
45
|
+
|
|
46
|
+
Put the terminal first:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
b = str('aa').as(:e2) >> epsilon |
|
|
50
|
+
str('a').as(:e1) >> this('b') { b }.as(:rec)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Try to match the end (`aa`) first, then recurse if that fails.
|
|
54
|
+
|
|
55
|
+
### Entity for Recursion
|
|
56
|
+
|
|
57
|
+
The `this()` helper enables recursive rule definition:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
def this(name, &block)
|
|
61
|
+
Parsanol::Atoms::Entity.new(name, &block)
|
|
62
|
+
end
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Entity delays evaluation until parse time, allowing forward references.
|
|
66
|
+
|
|
67
|
+
## Output Types
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
# Greedy blind (fails):
|
|
71
|
+
a.parse('aaaa') # => ParseFailed
|
|
72
|
+
|
|
73
|
+
# Greedy non-blind (succeeds):
|
|
74
|
+
b.parse('aaaa')
|
|
75
|
+
# => {:e2=>"aa"@0}
|
|
76
|
+
# or with proper structure showing the match
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Design Decisions
|
|
80
|
+
|
|
81
|
+
### Why Transform Instead of Modify Parser?
|
|
82
|
+
|
|
83
|
+
Grammar transformation is more flexible than modifying the parser engine. Different transformations achieve different behaviors.
|
|
84
|
+
|
|
85
|
+
### Why Entity for Recursion?
|
|
86
|
+
|
|
87
|
+
Ruby blocks capture variables at definition time. Entity provides a way to reference rules that aren't defined yet.
|
|
88
|
+
|
|
89
|
+
### Ruby-Only Feature
|
|
90
|
+
|
|
91
|
+
These advanced PEG patterns use Parslet's Ruby-specific constructs. They demonstrate theoretical parsing concepts more than practical usage.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "local",
|
|
3
|
+
"title": "Local Variable Parsing",
|
|
4
|
+
"description": "Demonstrate non-greedy parsing patterns using local variable bindings.",
|
|
5
|
+
"category": "conceptual",
|
|
6
|
+
"tags": ["local", "non-greedy", "parslet", "context"],
|
|
7
|
+
"difficulty": "intermediate",
|
|
8
|
+
"concepts": ["local binding", "non-greedy matching", "context", "delimiters"],
|
|
9
|
+
|
|
10
|
+
"motivation": {
|
|
11
|
+
"why": "Local provides non-greedy parsing within a specific context. This enables matching up to delimiters without consuming them.",
|
|
12
|
+
"useCases": [
|
|
13
|
+
"Matching up to delimiters",
|
|
14
|
+
"Context-sensitive parsing",
|
|
15
|
+
"Non-greedy content extraction"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
"inputFormat": {
|
|
20
|
+
"description": "Content with delimiters to parse non-greedily.",
|
|
21
|
+
"examples": [
|
|
22
|
+
{ "input": "content", "description": "Content to parse", "valid": true }
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
"outputFormat": {
|
|
27
|
+
"description": "Non-greedily matched content.",
|
|
28
|
+
"structure": {
|
|
29
|
+
"content": { "description": "The locally matched content" }
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
"rubyOnly": true,
|
|
34
|
+
"parsletCompatible": true,
|
|
35
|
+
"implementations": {
|
|
36
|
+
"ruby": { "basic": "basic.rb" }
|
|
37
|
+
}
|
|
38
|
+
}
|