parsanol 1.3.13-arm-linux
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 +7 -0
- data/HISTORY.txt +98 -0
- data/LICENSE +23 -0
- data/README.adoc +905 -0
- data/Rakefile +141 -0
- data/lib/parsanol/3.2/parsanol_native.so +0 -0
- data/lib/parsanol/3.3/parsanol_native.so +0 -0
- data/lib/parsanol/3.4/parsanol_native.so +0 -0
- data/lib/parsanol/4.0/parsanol_native.so +0 -0
- data/lib/parsanol/ast_visitor.rb +122 -0
- data/lib/parsanol/atoms/alternative.rb +123 -0
- data/lib/parsanol/atoms/base.rb +208 -0
- data/lib/parsanol/atoms/can_flatten.rb +194 -0
- data/lib/parsanol/atoms/capture.rb +38 -0
- data/lib/parsanol/atoms/context.rb +357 -0
- data/lib/parsanol/atoms/context_optimized.rb +38 -0
- data/lib/parsanol/atoms/custom.rb +110 -0
- data/lib/parsanol/atoms/cut.rb +66 -0
- data/lib/parsanol/atoms/dsl.rb +96 -0
- data/lib/parsanol/atoms/dynamic.rb +39 -0
- data/lib/parsanol/atoms/entity.rb +75 -0
- data/lib/parsanol/atoms/ignored.rb +37 -0
- data/lib/parsanol/atoms/infix.rb +167 -0
- data/lib/parsanol/atoms/lookahead.rb +85 -0
- data/lib/parsanol/atoms/named.rb +74 -0
- data/lib/parsanol/atoms/re.rb +83 -0
- data/lib/parsanol/atoms/repetition.rb +277 -0
- data/lib/parsanol/atoms/scope.rb +35 -0
- data/lib/parsanol/atoms/sequence.rb +195 -0
- data/lib/parsanol/atoms/str.rb +109 -0
- data/lib/parsanol/atoms/visitor.rb +91 -0
- data/lib/parsanol/atoms.rb +46 -0
- data/lib/parsanol/buffer.rb +133 -0
- data/lib/parsanol/builder_callbacks.rb +353 -0
- data/lib/parsanol/cause.rb +122 -0
- data/lib/parsanol/context.rb +39 -0
- data/lib/parsanol/convenience.rb +36 -0
- data/lib/parsanol/edit_tracker.rb +111 -0
- data/lib/parsanol/error_reporter/contextual.rb +99 -0
- data/lib/parsanol/error_reporter/deepest.rb +120 -0
- data/lib/parsanol/error_reporter/tree.rb +63 -0
- data/lib/parsanol/error_reporter.rb +100 -0
- data/lib/parsanol/expression/treetop.rb +154 -0
- data/lib/parsanol/expression.rb +106 -0
- data/lib/parsanol/fast_mode.rb +187 -0
- data/lib/parsanol/first_set.rb +79 -0
- data/lib/parsanol/grammar_builder.rb +179 -0
- data/lib/parsanol/incremental_parser.rb +182 -0
- data/lib/parsanol/interval_tree.rb +226 -0
- data/lib/parsanol/lazy_result.rb +179 -0
- data/lib/parsanol/mermaid.rb +142 -0
- data/lib/parsanol/native/batch_decoder.rb +255 -0
- data/lib/parsanol/native/dynamic.rb +238 -0
- data/lib/parsanol/native/parser.rb +102 -0
- data/lib/parsanol/native/serializer.rb +252 -0
- data/lib/parsanol/native/transformer.rb +604 -0
- data/lib/parsanol/native/types.rb +29 -0
- data/lib/parsanol/native.rb +223 -0
- data/lib/parsanol/optimizer.rb +85 -0
- data/lib/parsanol/optimizers/choice_optimizer.rb +78 -0
- data/lib/parsanol/optimizers/cut_inserter.rb +182 -0
- data/lib/parsanol/optimizers/lookahead_optimizer.rb +56 -0
- data/lib/parsanol/optimizers/quantifier_optimizer.rb +60 -0
- data/lib/parsanol/optimizers/sequence_optimizer.rb +97 -0
- data/lib/parsanol/options/zero_copy.rb +127 -0
- data/lib/parsanol/options.rb +21 -0
- data/lib/parsanol/parallel.rb +128 -0
- data/lib/parsanol/parser.rb +242 -0
- data/lib/parsanol/parslet.rb +151 -0
- data/lib/parsanol/pattern/binding.rb +91 -0
- data/lib/parsanol/pattern.rb +162 -0
- data/lib/parsanol/pool.rb +219 -0
- data/lib/parsanol/pools/array_pool.rb +75 -0
- data/lib/parsanol/pools/buffer_pool.rb +182 -0
- data/lib/parsanol/pools/position_pool.rb +92 -0
- data/lib/parsanol/pools/slice_pool.rb +64 -0
- data/lib/parsanol/position.rb +94 -0
- data/lib/parsanol/resettable.rb +29 -0
- data/lib/parsanol/result.rb +46 -0
- data/lib/parsanol/result_builder.rb +208 -0
- data/lib/parsanol/result_stream.rb +266 -0
- data/lib/parsanol/rig/rspec.rb +71 -0
- data/lib/parsanol/rope.rb +81 -0
- data/lib/parsanol/scope.rb +104 -0
- data/lib/parsanol/slice.rb +160 -0
- data/lib/parsanol/source/line_cache.rb +102 -0
- data/lib/parsanol/source.rb +185 -0
- data/lib/parsanol/source_location.rb +167 -0
- data/lib/parsanol/streaming_parser.rb +124 -0
- data/lib/parsanol/string_view.rb +198 -0
- data/lib/parsanol/transform.rb +226 -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 +240 -0
- data/lib/parsanol.rb +278 -0
- data/parsanol.gemspec +67 -0
- metadata +279 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Parsanol
|
|
4
|
+
# Zero-copy string view that references original input.
|
|
5
|
+
#
|
|
6
|
+
# StringView avoids string copies by maintaining a reference to the
|
|
7
|
+
# original string with offset and length. Strings are only materialized
|
|
8
|
+
# when explicitly requested via #to_s.
|
|
9
|
+
#
|
|
10
|
+
# == Usage
|
|
11
|
+
#
|
|
12
|
+
# view = StringView.new(input_string, offset: 10, length: 5)
|
|
13
|
+
# view.to_s # Materializes string only when needed
|
|
14
|
+
# view[0] # Direct character access without copying
|
|
15
|
+
#
|
|
16
|
+
# == Performance
|
|
17
|
+
#
|
|
18
|
+
# - No string allocation until to_s called
|
|
19
|
+
# - Direct character access without copying
|
|
20
|
+
# - Reduced GC pressure from intermediate strings
|
|
21
|
+
# - Caches materialized strings for reuse
|
|
22
|
+
#
|
|
23
|
+
# == Design Principles
|
|
24
|
+
#
|
|
25
|
+
# 1. Zero-Copy: Reference original string, don't copy
|
|
26
|
+
# 2. Lazy Materialization: Create strings only when to_s called
|
|
27
|
+
# 3. Caching: Cache materialized strings for reuse
|
|
28
|
+
# 4. Compatibility: Acts like String where needed
|
|
29
|
+
# 5. Extensibility: Foundation for Rope (Phase 3.2)
|
|
30
|
+
#
|
|
31
|
+
class StringView
|
|
32
|
+
include Resettable
|
|
33
|
+
|
|
34
|
+
# @return [String] Original input string
|
|
35
|
+
attr_reader :string
|
|
36
|
+
|
|
37
|
+
# @return [Integer] Byte offset into string
|
|
38
|
+
attr_reader :offset
|
|
39
|
+
|
|
40
|
+
# @return [Integer] Length in bytes
|
|
41
|
+
attr_reader :length
|
|
42
|
+
|
|
43
|
+
# Initialize a new StringView.
|
|
44
|
+
#
|
|
45
|
+
# @param string [String] Original input string
|
|
46
|
+
# @param offset [Integer] Byte offset (default: 0)
|
|
47
|
+
# @param length [Integer] Length in bytes (default: string.bytesize)
|
|
48
|
+
#
|
|
49
|
+
def initialize(string, offset: 0, length: nil)
|
|
50
|
+
@string = string
|
|
51
|
+
@offset = offset
|
|
52
|
+
@length = length || (string.bytesize - offset)
|
|
53
|
+
@materialized = nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Materialize to string (with caching).
|
|
57
|
+
#
|
|
58
|
+
# First call creates string slice, subsequent calls return cached.
|
|
59
|
+
# This implements lazy evaluation - strings are only created when
|
|
60
|
+
# explicitly needed, not during parsing.
|
|
61
|
+
#
|
|
62
|
+
# @return [String] Materialized string
|
|
63
|
+
#
|
|
64
|
+
def to_s
|
|
65
|
+
@materialized ||= @string.byteslice(@offset, @length)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Get character at index (zero-copy).
|
|
69
|
+
#
|
|
70
|
+
# Direct access to character in original string without creating
|
|
71
|
+
# intermediate string objects.
|
|
72
|
+
#
|
|
73
|
+
# @param index [Integer] Zero-based index
|
|
74
|
+
# @return [String, nil] Character at index or nil
|
|
75
|
+
#
|
|
76
|
+
def [](index)
|
|
77
|
+
return nil if index.negative? || index >= @length
|
|
78
|
+
|
|
79
|
+
@string.byteslice(@offset + index, 1)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Get byte size.
|
|
83
|
+
#
|
|
84
|
+
# @return [Integer] Length in bytes
|
|
85
|
+
#
|
|
86
|
+
def bytesize
|
|
87
|
+
@length
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
alias size bytesize
|
|
91
|
+
alias length bytesize
|
|
92
|
+
|
|
93
|
+
# Check if empty.
|
|
94
|
+
#
|
|
95
|
+
# @return [Boolean] true if length is 0
|
|
96
|
+
#
|
|
97
|
+
def empty?
|
|
98
|
+
@length.zero?
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Compare with another object.
|
|
102
|
+
#
|
|
103
|
+
# StringViews are only equal if they reference the exact same string object
|
|
104
|
+
# (by object_id) and have the same offset/length. This is consistent with
|
|
105
|
+
# the view pattern - they're views of a specific string instance.
|
|
106
|
+
#
|
|
107
|
+
# When comparing with a String, content is compared.
|
|
108
|
+
#
|
|
109
|
+
# @param other [Object] Object to compare with
|
|
110
|
+
# @return [Boolean] true if equal
|
|
111
|
+
#
|
|
112
|
+
def ==(other)
|
|
113
|
+
case other
|
|
114
|
+
when String
|
|
115
|
+
to_s == other
|
|
116
|
+
when StringView
|
|
117
|
+
# Only equal if viewing the exact same string object with same range
|
|
118
|
+
@string.equal?(other.string) &&
|
|
119
|
+
@offset == other.offset &&
|
|
120
|
+
@length == other.length
|
|
121
|
+
else
|
|
122
|
+
super
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
alias eql? ==
|
|
127
|
+
|
|
128
|
+
# Hash code for hashing.
|
|
129
|
+
#
|
|
130
|
+
# Uses object_id of string to avoid materializing the view.
|
|
131
|
+
#
|
|
132
|
+
# @return [Integer] Hash code
|
|
133
|
+
#
|
|
134
|
+
def hash
|
|
135
|
+
[@string.object_id, @offset, @length].hash
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Create substring view (zero-copy).
|
|
139
|
+
#
|
|
140
|
+
# Returns a new StringView referencing a substring of this view.
|
|
141
|
+
# No string allocation occurs - just a new view with adjusted offset.
|
|
142
|
+
#
|
|
143
|
+
# @param start [Integer] Start offset (relative to view)
|
|
144
|
+
# @param len [Integer] Length
|
|
145
|
+
# @return [StringView] New view of substring
|
|
146
|
+
#
|
|
147
|
+
def slice(start, len)
|
|
148
|
+
# Handle edge cases
|
|
149
|
+
if len <= 0 || start >= @length
|
|
150
|
+
return self.class.new(@string, offset: @offset,
|
|
151
|
+
length: 0)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Clamp start to valid range [0, @length)
|
|
155
|
+
clamped_start = [[start, 0].max, @length].min
|
|
156
|
+
|
|
157
|
+
# Calculate actual offset in original string
|
|
158
|
+
actual_offset = @offset + clamped_start
|
|
159
|
+
|
|
160
|
+
# Calculate actual length (min of requested and available)
|
|
161
|
+
available = @length - clamped_start
|
|
162
|
+
actual_length = [len, available].min
|
|
163
|
+
|
|
164
|
+
self.class.new(@string, offset: actual_offset, length: actual_length)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Inspect for debugging.
|
|
168
|
+
#
|
|
169
|
+
# Shows whether string has been materialized.
|
|
170
|
+
#
|
|
171
|
+
# @return [String] Inspection string
|
|
172
|
+
#
|
|
173
|
+
def inspect
|
|
174
|
+
if @materialized
|
|
175
|
+
"#<StringView:#{object_id} @offset=#{@offset} @length=#{@length} cached=#{@materialized.inspect}>"
|
|
176
|
+
else
|
|
177
|
+
"#<StringView:#{object_id} @offset=#{@offset} @length=#{@length}>"
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Reset for pooling (if needed in future phases).
|
|
182
|
+
#
|
|
183
|
+
# Allows StringView objects to be reused from a pool.
|
|
184
|
+
#
|
|
185
|
+
# @param string [String] New string
|
|
186
|
+
# @param offset [Integer] New offset
|
|
187
|
+
# @param length [Integer] New length
|
|
188
|
+
# @return [self]
|
|
189
|
+
#
|
|
190
|
+
def reset!(string, offset, length)
|
|
191
|
+
@string = string
|
|
192
|
+
@offset = offset
|
|
193
|
+
@length = length
|
|
194
|
+
@materialized = nil
|
|
195
|
+
self
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "parsanol/pattern"
|
|
4
|
+
|
|
5
|
+
# Tree transformation engine for converting parse trees into abstract syntax trees.
|
|
6
|
+
#
|
|
7
|
+
# Transforms expression trees through depth-first post-order traversal.
|
|
8
|
+
# When a rule pattern matches a node, that node is replaced by the result
|
|
9
|
+
# of the rule's transformation block. Unmatched nodes pass through unchanged.
|
|
10
|
+
#
|
|
11
|
+
# @example Basic transformation class
|
|
12
|
+
# class NumberTransform < Parsanol::Transform
|
|
13
|
+
# rule(int: simple(:value)) { Integer(value) }
|
|
14
|
+
# rule(float: simple(:value)) { Float(value) }
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# transform = NumberTransform.new
|
|
18
|
+
# transform.apply({ int: '42' }) # => 42
|
|
19
|
+
#
|
|
20
|
+
# @example Inline transformation definition
|
|
21
|
+
# transform = Parsanol::Transform.new do
|
|
22
|
+
# rule(a: simple(:x)) { x.upcase }
|
|
23
|
+
# end
|
|
24
|
+
# transform.apply({ a: 'hello' }) # => 'HELLO'
|
|
25
|
+
#
|
|
26
|
+
# @example Using context for external dependencies
|
|
27
|
+
# builder = AstBuilder.new
|
|
28
|
+
# transform = Parsanol::Transform.new do
|
|
29
|
+
# rule(expr: simple(:e)) { builder.build_node(e) }
|
|
30
|
+
# rule(expr: simple(:e)) { |ctx| ctx[:builder].build_node(e) }
|
|
31
|
+
# end
|
|
32
|
+
# transform.apply(tree, builder: builder)
|
|
33
|
+
#
|
|
34
|
+
# Rule blocks can have two forms:
|
|
35
|
+
# - Zero-arity: executed in a context where pattern bindings are local variables
|
|
36
|
+
# - Arity-1: receives a hash of bindings as the argument
|
|
37
|
+
#
|
|
38
|
+
# Inspired by tree transformation patterns in parser combinators.
|
|
39
|
+
#
|
|
40
|
+
module Parsanol
|
|
41
|
+
class Transform
|
|
42
|
+
include Parsanol
|
|
43
|
+
|
|
44
|
+
# Class-level rule definition for subclass inheritance.
|
|
45
|
+
class << self
|
|
46
|
+
include Parsanol
|
|
47
|
+
|
|
48
|
+
# Defines a transformation rule at the class level.
|
|
49
|
+
# Rules are inherited by subclasses and evaluated in reverse order
|
|
50
|
+
# (most recently defined rules have highest precedence).
|
|
51
|
+
#
|
|
52
|
+
# @param expression [Object] pattern to match against tree nodes
|
|
53
|
+
# @yield block to execute when pattern matches, receives bindings
|
|
54
|
+
# @return [Array] the updated rules list
|
|
55
|
+
#
|
|
56
|
+
def rule(expression, &transformer)
|
|
57
|
+
class_rules.unshift([Parsanol::Pattern.new(expression), transformer])
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns all class-level rules defined for this transform.
|
|
61
|
+
#
|
|
62
|
+
# @return [Array<Array>] array of [pattern, block] pairs
|
|
63
|
+
#
|
|
64
|
+
def class_rules
|
|
65
|
+
@class_rules ||= []
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Ensures subclasses inherit parent rules.
|
|
69
|
+
def inherited(subclass)
|
|
70
|
+
super
|
|
71
|
+
subclass.instance_variable_set(:@class_rules, class_rules.dup)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Creates a new transform instance.
|
|
76
|
+
#
|
|
77
|
+
# @param strict [Boolean] if true, raises on unmatched hash nodes
|
|
78
|
+
# @yield optional block for inline rule definition
|
|
79
|
+
#
|
|
80
|
+
def initialize(strict = false, &definition)
|
|
81
|
+
@strict_mode = strict
|
|
82
|
+
@instance_rules = []
|
|
83
|
+
|
|
84
|
+
instance_eval(&definition) if definition
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Defines an instance-level transformation rule.
|
|
88
|
+
# Instance rules are checked before class rules.
|
|
89
|
+
#
|
|
90
|
+
# @param expression [Object] pattern to match
|
|
91
|
+
# @yield transformation block
|
|
92
|
+
#
|
|
93
|
+
def rule(expression, &transformer)
|
|
94
|
+
@instance_rules.unshift([Parsanol::Pattern.new(expression), transformer])
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Applies transformation to a parse tree.
|
|
98
|
+
#
|
|
99
|
+
# Performs depth-first post-order traversal, transforming nodes
|
|
100
|
+
# from leaves to root. Context values are available in rule blocks.
|
|
101
|
+
#
|
|
102
|
+
# @param tree [Object] parse tree to transform
|
|
103
|
+
# @param context [Hash, nil] optional context bindings
|
|
104
|
+
# @return [Object] transformed tree
|
|
105
|
+
#
|
|
106
|
+
def apply(tree, context = nil)
|
|
107
|
+
# First, recursively transform children (depth-first)
|
|
108
|
+
transformed = transform_children(tree, context)
|
|
109
|
+
|
|
110
|
+
# Then, try to match and transform this node
|
|
111
|
+
attempt_transformation(transformed, context)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Returns combined class and instance rules.
|
|
115
|
+
# Instance rules take precedence over class rules.
|
|
116
|
+
#
|
|
117
|
+
# @return [Array<Array>] all applicable rules
|
|
118
|
+
#
|
|
119
|
+
def all_rules
|
|
120
|
+
@instance_rules + self.class.class_rules
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Executes a transformation block with the given bindings.
|
|
124
|
+
# Public API for testing and advanced usage.
|
|
125
|
+
#
|
|
126
|
+
# @param bindings [Hash] pattern bindings
|
|
127
|
+
# @param block [Proc] transformation block
|
|
128
|
+
# @return [Object] block result
|
|
129
|
+
#
|
|
130
|
+
def call_on_match(bindings, block)
|
|
131
|
+
return nil unless block
|
|
132
|
+
|
|
133
|
+
if block.arity == 1
|
|
134
|
+
block.call(bindings)
|
|
135
|
+
else
|
|
136
|
+
Context.new(bindings).instance_eval(&block)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
# Recursively transforms child nodes based on tree type.
|
|
143
|
+
#
|
|
144
|
+
# @param node [Object] current tree node
|
|
145
|
+
# @param ctx [Hash, nil] context bindings
|
|
146
|
+
# @return [Object] node with transformed children
|
|
147
|
+
#
|
|
148
|
+
def transform_children(node, ctx)
|
|
149
|
+
case node
|
|
150
|
+
when Hash
|
|
151
|
+
transform_hash_children(node, ctx)
|
|
152
|
+
when Array
|
|
153
|
+
transform_array_children(node, ctx)
|
|
154
|
+
else
|
|
155
|
+
node
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Transforms all values in a hash.
|
|
160
|
+
#
|
|
161
|
+
def transform_hash_children(hash, ctx)
|
|
162
|
+
result = {}
|
|
163
|
+
hash.each { |key, val| result[key] = apply(val, ctx) }
|
|
164
|
+
result
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Transforms all elements in an array.
|
|
168
|
+
#
|
|
169
|
+
def transform_array_children(array, ctx)
|
|
170
|
+
array.map { |element| apply(element, ctx) }
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Attempts to match a node against all rules and transform if matched.
|
|
174
|
+
#
|
|
175
|
+
# @param node [Object] node to potentially transform
|
|
176
|
+
# @param ctx [Hash, nil] context bindings
|
|
177
|
+
# @return [Object] transformed node or original if no match
|
|
178
|
+
#
|
|
179
|
+
def attempt_transformation(node, ctx)
|
|
180
|
+
all_rules.each do |pattern, block|
|
|
181
|
+
bindings = pattern.match(node, ctx)
|
|
182
|
+
next unless bindings
|
|
183
|
+
|
|
184
|
+
return execute_block(block, bindings)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# No rule matched
|
|
188
|
+
handle_unmatched(node)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Executes a transformation block with proper binding context.
|
|
192
|
+
#
|
|
193
|
+
# @param block [Proc] transformation block
|
|
194
|
+
# @param bindings [Hash] matched pattern bindings
|
|
195
|
+
# @return [Object] block result
|
|
196
|
+
#
|
|
197
|
+
def execute_block(block, bindings)
|
|
198
|
+
return nil unless block
|
|
199
|
+
|
|
200
|
+
if block.arity == 1
|
|
201
|
+
# Block expects bindings as argument
|
|
202
|
+
block.call(bindings)
|
|
203
|
+
else
|
|
204
|
+
# Block executes in context with bindings as local variables
|
|
205
|
+
Context.new(bindings).instance_eval(&block)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Handles nodes that didn't match any rule.
|
|
210
|
+
#
|
|
211
|
+
# @param node [Object] unmatched node
|
|
212
|
+
# @return [Object] the node (or raises in strict mode)
|
|
213
|
+
# @raise [NotImplementedError] if strict mode and node is a Hash
|
|
214
|
+
#
|
|
215
|
+
def handle_unmatched(node)
|
|
216
|
+
return node unless @strict_mode
|
|
217
|
+
return node unless node.is_a?(Hash)
|
|
218
|
+
|
|
219
|
+
# In strict mode, provide helpful error about what wasn't matched
|
|
220
|
+
signature = node.transform_values(&:class)
|
|
221
|
+
raise NotImplementedError, "Failed to match #{signature.inspect}"
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
require "parsanol/context"
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# @parsanol/wasm
|
|
2
|
+
|
|
3
|
+
High-performance PEG parser using WebAssembly. Designed for use with Opal (Ruby in JavaScript) and general JavaScript applications.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **18-44x faster** than pure Ruby parser
|
|
8
|
+
- **99.5% fewer allocations**
|
|
9
|
+
- Works in browsers and Node.js
|
|
10
|
+
- Full TypeScript support
|
|
11
|
+
- Compatible with Opal
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @parsanol/wasm
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Browser/ESM
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<script type="module">
|
|
25
|
+
import { initParsanol, ParsanolParser } from '@parsanol/wasm';
|
|
26
|
+
|
|
27
|
+
// Initialize WASM (call once)
|
|
28
|
+
await initParsanol();
|
|
29
|
+
|
|
30
|
+
// Create parser from grammar JSON
|
|
31
|
+
const grammar = {
|
|
32
|
+
atoms: [
|
|
33
|
+
{ Str: { pattern: "hello" } }
|
|
34
|
+
],
|
|
35
|
+
root: 0
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const parser = new ParsanolParser(grammar);
|
|
39
|
+
|
|
40
|
+
// Parse input
|
|
41
|
+
const result = parser.parse('hello');
|
|
42
|
+
console.log(result); // "hello"
|
|
43
|
+
</script>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Node.js
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
const { initParsanol, ParsanolParser } = require('@parsanol/wasm');
|
|
50
|
+
|
|
51
|
+
async function main() {
|
|
52
|
+
await initParsanol();
|
|
53
|
+
|
|
54
|
+
const parser = new ParsanolParser(grammarJson);
|
|
55
|
+
const result = parser.parse('input text');
|
|
56
|
+
console.log(result);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
main();
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Opal (Ruby in Browser)
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
# First initialize WASM in JavaScript:
|
|
66
|
+
# Parsanol::WasmParser.init.then { puts "ready" }
|
|
67
|
+
|
|
68
|
+
require 'parsanol/wasm_parser'
|
|
69
|
+
|
|
70
|
+
grammar_json = {
|
|
71
|
+
atoms: [
|
|
72
|
+
{ Str: { pattern: "hello" } }
|
|
73
|
+
],
|
|
74
|
+
root: 0
|
|
75
|
+
}.to_json
|
|
76
|
+
|
|
77
|
+
parser = Parsanol::WasmParser.new(grammar_json)
|
|
78
|
+
result = parser.parse('hello')
|
|
79
|
+
puts result # => "hello"
|
|
80
|
+
```
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@parsanol/wasm",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "High-performance PEG parser using WebAssembly for Opal and JavaScript",
|
|
5
|
+
"main": "parsanol.js",
|
|
6
|
+
"module": "parsanol.js",
|
|
7
|
+
"types": "parsanol.d.ts",
|
|
8
|
+
"browser": "parsanol.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"parsanol.js",
|
|
11
|
+
"parsanol.d.ts",
|
|
12
|
+
"parsanol_native.js",
|
|
13
|
+
"parsanol_native_bg.js",
|
|
14
|
+
"parsanol_native_bg.wasm",
|
|
15
|
+
"parsanol_native.d.ts"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "cd ../../ext/parsanol_native && wasm-pack build --features wasm --target web --out-dir ../../lib/parsanol/wasm",
|
|
19
|
+
"build:node": "cd ../../ext/parsanol_native && wasm-pack build --features wasm --target nodejs --out-dir ../../lib/parsanol/wasm-node",
|
|
20
|
+
"test": "node test/parsanol.test.js",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"parser",
|
|
25
|
+
"peg",
|
|
26
|
+
"parsing-expression-grammar",
|
|
27
|
+
"wasm",
|
|
28
|
+
"webassembly",
|
|
29
|
+
"opal",
|
|
30
|
+
"ruby",
|
|
31
|
+
"performance"
|
|
32
|
+
],
|
|
33
|
+
"author": "Ribose Inc. <open.source@ribose.com>",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/parsanol/parsanol-rs.git",
|
|
38
|
+
"directory": "lib/parsanol/wasm"
|
|
39
|
+
},
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/parsanol/parsanol-ruby/issues"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/parsanol/parsanol-ruby#readme",
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=14.0.0"
|
|
46
|
+
},
|
|
47
|
+
"sideEffects": [
|
|
48
|
+
"parsanol_native.js",
|
|
49
|
+
"parsanol_native_bg.js"
|
|
50
|
+
]
|
|
51
|
+
}
|