parsanol 1.3.5 → 1.3.7
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 +4 -4
- data/Rakefile +48 -48
- data/ext/parsanol_native/extconf.rb +4 -4
- data/lib/parsanol/ast_visitor.rb +1 -1
- data/lib/parsanol/atoms/alternative.rb +3 -2
- data/lib/parsanol/atoms/base.rb +12 -6
- data/lib/parsanol/atoms/can_flatten.rb +8 -8
- data/lib/parsanol/atoms/context.rb +23 -16
- data/lib/parsanol/atoms/custom.rb +2 -2
- data/lib/parsanol/atoms/dynamic.rb +1 -1
- data/lib/parsanol/atoms/infix.rb +10 -5
- data/lib/parsanol/atoms/lookahead.rb +7 -4
- data/lib/parsanol/atoms/re.rb +1 -1
- data/lib/parsanol/atoms/repetition.rb +29 -11
- data/lib/parsanol/atoms/sequence.rb +3 -2
- data/lib/parsanol/atoms/str.rb +9 -3
- data/lib/parsanol/atoms.rb +20 -20
- data/lib/parsanol/builder_callbacks.rb +2 -2
- data/lib/parsanol/cause.rb +2 -2
- data/lib/parsanol/context.rb +2 -2
- data/lib/parsanol/error_reporter.rb +5 -5
- data/lib/parsanol/expression/treetop.rb +17 -17
- data/lib/parsanol/expression.rb +1 -1
- data/lib/parsanol/fast_mode.rb +50 -12
- data/lib/parsanol/first_set.rb +1 -1
- data/lib/parsanol/grammar_builder.rb +10 -8
- data/lib/parsanol/incremental_parser.rb +13 -8
- data/lib/parsanol/interval_tree.rb +12 -3
- data/lib/parsanol/lazy_result.rb +2 -2
- data/lib/parsanol/mermaid.rb +12 -9
- data/lib/parsanol/native/batch_decoder.rb +13 -9
- data/lib/parsanol/native/dynamic.rb +7 -6
- data/lib/parsanol/native/parser.rb +7 -5
- data/lib/parsanol/native/serializer.rb +42 -42
- data/lib/parsanol/native/transformer.rb +55 -28
- data/lib/parsanol/native/types.rb +3 -3
- data/lib/parsanol/native.rb +26 -20
- data/lib/parsanol/optimizer.rb +6 -6
- data/lib/parsanol/optimizers/choice_optimizer.rb +1 -1
- data/lib/parsanol/optimizers/cut_inserter.rb +5 -2
- data/lib/parsanol/optimizers/lookahead_optimizer.rb +9 -3
- data/lib/parsanol/optimizers/quantifier_optimizer.rb +5 -5
- data/lib/parsanol/optimizers/sequence_optimizer.rb +1 -1
- data/lib/parsanol/options/zero_copy.rb +1 -1
- data/lib/parsanol/options.rb +1 -1
- data/lib/parsanol/parallel.rb +4 -3
- data/lib/parsanol/parser.rb +18 -16
- data/lib/parsanol/parslet.rb +7 -7
- data/lib/parsanol/pattern/binding.rb +1 -1
- data/lib/parsanol/pattern.rb +4 -1
- data/lib/parsanol/pool.rb +3 -3
- data/lib/parsanol/pools/buffer_pool.rb +2 -2
- data/lib/parsanol/pools/position_pool.rb +2 -2
- data/lib/parsanol/position.rb +1 -1
- data/lib/parsanol/result_builder.rb +4 -4
- data/lib/parsanol/result_stream.rb +10 -5
- data/lib/parsanol/slice.rb +11 -8
- data/lib/parsanol/source.rb +14 -9
- data/lib/parsanol/source_location.rb +1 -1
- data/lib/parsanol/streaming_parser.rb +3 -3
- data/lib/parsanol/string_view.rb +4 -1
- data/lib/parsanol/transform.rb +2 -2
- data/lib/parsanol/version.rb +1 -1
- data/lib/parsanol/wasm_parser.rb +1 -1
- data/lib/parsanol.rb +37 -39
- data/parsanol.gemspec +30 -30
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 39ce7a8554166a92125c5336c0f7e7f3c9011791cd0c366c037f74d6d9b73a6e
|
|
4
|
+
data.tar.gz: b33ecab1a711628c5fc4135705a5d21e7af53e48b5472fb60925664e15ce9688
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: da8b989aba1451e3bcbe5a922eef7a481614e098a5e1095a5f9a8d8d6f6d8ac46fbcf7e6fe727fc06c40753d80df1fb2731d54bdf66f96d5441aaf284eb6d2bd
|
|
7
|
+
data.tar.gz: bbab802f416f69dfb61bd856549a3755b87923fb0944d9fcc06b96466ebe8cad8ac51515086b5bd40b7eb04bc3c8f813a7f6c109a9931952fc9d70c21f0304d8
|
data/Rakefile
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
4
|
|
|
5
5
|
begin
|
|
6
|
-
require
|
|
6
|
+
require "rspec/core/rake_task"
|
|
7
7
|
rescue LoadError
|
|
8
8
|
# RSpec not available in this environment
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
require
|
|
12
|
-
require
|
|
11
|
+
require "rdoc/task"
|
|
12
|
+
require "rubygems/package_task"
|
|
13
13
|
|
|
14
14
|
begin
|
|
15
|
-
require
|
|
15
|
+
require "opal/rspec/rake_task"
|
|
16
16
|
rescue LoadError, NoMethodError
|
|
17
17
|
# Opal not available or incompatible with current Ruby version
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
GEMSPEC = Gem::Specification.load(
|
|
20
|
+
GEMSPEC = Gem::Specification.load("parsanol.gemspec")
|
|
21
21
|
|
|
22
22
|
# Load rake tasks from rakelib/
|
|
23
|
-
Dir.glob(
|
|
23
|
+
Dir.glob("rakelib/*.rake").each { |r| load r }
|
|
24
24
|
|
|
25
|
-
desc
|
|
25
|
+
desc "Run all tests"
|
|
26
26
|
RSpec::Core::RakeTask.new(:spec)
|
|
27
27
|
|
|
28
28
|
namespace :spec do
|
|
29
|
-
desc
|
|
29
|
+
desc "Run unit tests only"
|
|
30
30
|
RSpec::Core::RakeTask.new(:unit) do |task|
|
|
31
|
-
task.pattern =
|
|
31
|
+
task.pattern = "spec/parsanol/**/*_spec.rb"
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
if defined?(Opal::RSpec::RakeTask)
|
|
35
|
-
desc
|
|
35
|
+
desc "Run Opal (JavaScript) tests"
|
|
36
36
|
Opal::RSpec::RakeTask.new(:opal) do |task|
|
|
37
|
-
task.append_path
|
|
37
|
+
task.append_path "lib"
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
RDoc::Task.new do |rdoc|
|
|
43
|
-
rdoc.rdoc_dir =
|
|
44
|
-
rdoc.title =
|
|
45
|
-
rdoc.options <<
|
|
46
|
-
rdoc.rdoc_files.include(
|
|
47
|
-
rdoc.rdoc_files.include(
|
|
43
|
+
rdoc.rdoc_dir = "rdoc"
|
|
44
|
+
rdoc.title = "Parsanol"
|
|
45
|
+
rdoc.options << "--line-numbers"
|
|
46
|
+
rdoc.rdoc_files.include("README.adoc")
|
|
47
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
desc
|
|
50
|
+
desc "Print LOC statistics"
|
|
51
51
|
task :stat do
|
|
52
52
|
%w[lib spec example].each do |dir|
|
|
53
53
|
next unless Dir.exist?(dir)
|
|
@@ -59,82 +59,82 @@ end
|
|
|
59
59
|
|
|
60
60
|
# ===== Native Gem Building =====
|
|
61
61
|
namespace :gem do
|
|
62
|
-
desc
|
|
63
|
-
task
|
|
64
|
-
sh
|
|
62
|
+
desc "Build source gem (compile on install)"
|
|
63
|
+
task "native:any" do
|
|
64
|
+
sh "rake gem:platform:any gem"
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
desc
|
|
68
|
-
task
|
|
69
|
-
spec = Gem::Specification.load(
|
|
67
|
+
desc "Define the gem task to build on any platform (compile on install)"
|
|
68
|
+
task "platform:any" do
|
|
69
|
+
spec = Gem::Specification.load("parsanol.gemspec").dup
|
|
70
70
|
task = Gem::PackageTask.new(spec)
|
|
71
71
|
task.define
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
namespace :benchmark do
|
|
76
|
-
desc
|
|
76
|
+
desc "Run comprehensive benchmark suite"
|
|
77
77
|
task :all do
|
|
78
|
-
ruby
|
|
78
|
+
ruby "benchmark/benchmark_suite.rb"
|
|
79
79
|
end
|
|
80
80
|
|
|
81
|
-
desc
|
|
81
|
+
desc "Run example-focused benchmarks"
|
|
82
82
|
task :examples do
|
|
83
|
-
ruby
|
|
83
|
+
ruby "benchmark/example_benchmarks.rb"
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
-
desc
|
|
86
|
+
desc "Run benchmarks and export results to JSON/YAML"
|
|
87
87
|
task :export do
|
|
88
|
-
ruby
|
|
88
|
+
ruby "benchmark/benchmark_runner.rb"
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
desc
|
|
91
|
+
desc "Run quick benchmark (examples only)"
|
|
92
92
|
task quick: :examples
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
# Load comparative benchmark tasks
|
|
96
|
-
Dir.glob(
|
|
96
|
+
Dir.glob("benchmark/tasks/*.rake").each { |r| load r }
|
|
97
97
|
|
|
98
|
-
desc
|
|
99
|
-
task benchmark:
|
|
98
|
+
desc "Run quick benchmarks"
|
|
99
|
+
task benchmark: "benchmark:quick"
|
|
100
100
|
|
|
101
101
|
# ===== Parslet Compatibility Tests =====
|
|
102
102
|
namespace :compat do
|
|
103
|
-
desc
|
|
103
|
+
desc "Run imported Parslet tests with original Parslet (baseline)"
|
|
104
104
|
task :parslet do
|
|
105
|
-
ENV[
|
|
106
|
-
sh
|
|
105
|
+
ENV["PARSANOL_BACKEND"] = "parslet"
|
|
106
|
+
sh "bundle exec rspec spec/parslet_imported/ --format documentation"
|
|
107
107
|
end
|
|
108
108
|
|
|
109
|
-
desc
|
|
109
|
+
desc "Run imported Parslet tests with Parsanol compatibility layer"
|
|
110
110
|
task :parsanol do
|
|
111
|
-
ENV[
|
|
112
|
-
sh
|
|
111
|
+
ENV["PARSANOL_BACKEND"] = "parsanol"
|
|
112
|
+
sh "bundle exec rspec spec/parslet_imported/ --format documentation"
|
|
113
113
|
end
|
|
114
114
|
|
|
115
|
-
desc
|
|
115
|
+
desc "Run both and save results for comparison"
|
|
116
116
|
task :compare do
|
|
117
|
-
require
|
|
117
|
+
require "fileutils"
|
|
118
118
|
|
|
119
|
-
results_dir =
|
|
119
|
+
results_dir = "tmp/compat_results"
|
|
120
120
|
FileUtils.mkdir_p(results_dir)
|
|
121
121
|
|
|
122
|
-
puts
|
|
123
|
-
ENV[
|
|
122
|
+
puts "=== Running with original Parslet ==="
|
|
123
|
+
ENV["PARSANOL_BACKEND"] = "parslet"
|
|
124
124
|
sh "bundle exec rspec spec/parslet_imported/ --format documentation > #{results_dir}/parslet.txt 2>&1"
|
|
125
125
|
|
|
126
126
|
puts "\n=== Running with Parsanol::Parslet ==="
|
|
127
|
-
ENV[
|
|
127
|
+
ENV["PARSANOL_BACKEND"] = "parsanol"
|
|
128
128
|
sh "bundle exec rspec spec/parslet_imported/ --format documentation > #{results_dir}/parsanol.txt 2>&1"
|
|
129
129
|
|
|
130
130
|
puts "\n=== Comparing results ==="
|
|
131
|
-
puts
|
|
131
|
+
puts "Results saved to:"
|
|
132
132
|
puts " - #{results_dir}/parslet.txt"
|
|
133
133
|
puts " - #{results_dir}/parsanol.txt"
|
|
134
134
|
puts "\nTo compare: diff #{results_dir}/parslet.txt #{results_dir}/parsanol.txt"
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
-
desc
|
|
137
|
+
desc "Run imported Parslet tests (default: with Parsanol)"
|
|
138
138
|
task run: :parsanol
|
|
139
139
|
end
|
|
140
140
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "mkmf"
|
|
4
|
+
require "rb_sys/mkmf"
|
|
5
5
|
|
|
6
|
-
create_rust_makefile(
|
|
6
|
+
create_rust_makefile("parsanol/parsanol_native") do |r|
|
|
7
7
|
# Create debug builds in dev, release in production
|
|
8
|
-
r.profile = ENV.fetch(
|
|
8
|
+
r.profile = ENV.fetch("RB_SYS_CARGO_PROFILE", :dev).to_sym
|
|
9
9
|
|
|
10
10
|
# Enable stable API compiled fallback for ruby-head and older Ruby versions
|
|
11
11
|
r.use_stable_api_compiled_fallback = true
|
data/lib/parsanol/ast_visitor.rb
CHANGED
|
@@ -51,7 +51,8 @@ module Parsanol
|
|
|
51
51
|
when 2
|
|
52
52
|
try_two(options[0], options[1], source, context, consume_all)
|
|
53
53
|
when 3
|
|
54
|
-
try_three(options[0], options[1], options[2], source, context,
|
|
54
|
+
try_three(options[0], options[1], options[2], source, context,
|
|
55
|
+
consume_all)
|
|
55
56
|
else
|
|
56
57
|
try_many(options, source, context, consume_all)
|
|
57
58
|
end
|
|
@@ -64,7 +65,7 @@ module Parsanol
|
|
|
64
65
|
# @param prec [Integer] precedence
|
|
65
66
|
# @return [String]
|
|
66
67
|
def to_s_inner(prec)
|
|
67
|
-
@alternatives.map { |a| a.to_s(prec) }.join(
|
|
68
|
+
@alternatives.map { |a| a.to_s(prec) }.join(" / ")
|
|
68
69
|
end
|
|
69
70
|
|
|
70
71
|
# FIRST set is union of all alternatives' FIRST sets.
|
data/lib/parsanol/atoms/base.rb
CHANGED
|
@@ -38,7 +38,8 @@ module Parsanol
|
|
|
38
38
|
return finalize_result(value) if success
|
|
39
39
|
|
|
40
40
|
# Reparse with error reporting for diagnostics
|
|
41
|
-
report_detailed_error(input, must_consume_all, options[:reporter],
|
|
41
|
+
report_detailed_error(input, must_consume_all, options[:reporter],
|
|
42
|
+
value)
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
# Creates a new parsing context and executes the atom.
|
|
@@ -49,7 +50,8 @@ module Parsanol
|
|
|
49
50
|
# @return [Array(Boolean, Object)] outcome tuple
|
|
50
51
|
def run_with_context(input, reporter, consume_all)
|
|
51
52
|
parser_class = detect_parser_class
|
|
52
|
-
context = Parsanol::Atoms::Context.new(reporter,
|
|
53
|
+
context = Parsanol::Atoms::Context.new(reporter,
|
|
54
|
+
parser_class: parser_class)
|
|
53
55
|
apply(input, context, consume_all)
|
|
54
56
|
end
|
|
55
57
|
|
|
@@ -69,7 +71,10 @@ module Parsanol
|
|
|
69
71
|
context.succ(input)
|
|
70
72
|
|
|
71
73
|
# Verify full consumption when required
|
|
72
|
-
|
|
74
|
+
if consume_all && input.chars_left.positive?
|
|
75
|
+
return unconsumed_error(input, context,
|
|
76
|
+
position_before)
|
|
77
|
+
end
|
|
73
78
|
|
|
74
79
|
outcome
|
|
75
80
|
end
|
|
@@ -83,7 +88,7 @@ module Parsanol
|
|
|
83
88
|
# @raise [NotImplementedError] if not overridden
|
|
84
89
|
def try(input, context, consume_all)
|
|
85
90
|
raise NotImplementedError,
|
|
86
|
-
|
|
91
|
+
"Atom must implement #try(source, context, consume_all)"
|
|
87
92
|
end
|
|
88
93
|
|
|
89
94
|
# Whether packrat caching benefits this atom.
|
|
@@ -178,7 +183,8 @@ module Parsanol
|
|
|
178
183
|
excess_pos = input.bytepos
|
|
179
184
|
preview = input.consume(10)
|
|
180
185
|
input.bytepos = saved_pos
|
|
181
|
-
context.err_at(self, input,
|
|
186
|
+
context.err_at(self, input,
|
|
187
|
+
UNCONSUMED_INPUT_MSG + preview.to_s.inspect, excess_pos)
|
|
182
188
|
end
|
|
183
189
|
|
|
184
190
|
# Reports detailed error by reparsing with reporter.
|
|
@@ -188,7 +194,7 @@ module Parsanol
|
|
|
188
194
|
success, cause = run_with_context(input, error_reporter, consume_all)
|
|
189
195
|
|
|
190
196
|
# Second parse should also fail
|
|
191
|
-
raise
|
|
197
|
+
raise "Invariant violation: parse succeeded during error reporting" if success
|
|
192
198
|
|
|
193
199
|
cause.raise
|
|
194
200
|
end
|
|
@@ -39,7 +39,7 @@ module Parsanol
|
|
|
39
39
|
when :sequence
|
|
40
40
|
return flattened
|
|
41
41
|
when :maybe
|
|
42
|
-
return named ? flattened : (flattened ||
|
|
42
|
+
return named ? flattened : (flattened || "")
|
|
43
43
|
when :repetition
|
|
44
44
|
return flatten_repetition([flattened], named)
|
|
45
45
|
end
|
|
@@ -57,7 +57,7 @@ module Parsanol
|
|
|
57
57
|
when :sequence
|
|
58
58
|
return flatten_sequence(result)
|
|
59
59
|
when :maybe
|
|
60
|
-
return named ? result.first : result.first ||
|
|
60
|
+
return named ? result.first : result.first || ""
|
|
61
61
|
when :repetition
|
|
62
62
|
return flatten_repetition(result, named)
|
|
63
63
|
end
|
|
@@ -68,15 +68,15 @@ module Parsanol
|
|
|
68
68
|
# Lisp style fold left where the first element builds the basis for
|
|
69
69
|
# an inject. Optimized with early return and reduced method calls.
|
|
70
70
|
#
|
|
71
|
-
def foldl(list
|
|
71
|
+
def foldl(list)
|
|
72
72
|
len = list.size
|
|
73
|
-
return
|
|
73
|
+
return "" if len.zero?
|
|
74
74
|
return list[0] if len == 1 # Fast path for single element
|
|
75
75
|
|
|
76
76
|
result = list[0]
|
|
77
77
|
i = 1
|
|
78
78
|
while i < len
|
|
79
|
-
result =
|
|
79
|
+
result = yield(result, list[i])
|
|
80
80
|
i += 1
|
|
81
81
|
end
|
|
82
82
|
result
|
|
@@ -120,7 +120,7 @@ module Parsanol
|
|
|
120
120
|
return r if r_is_slice
|
|
121
121
|
return l if l_is_slice
|
|
122
122
|
|
|
123
|
-
raise
|
|
123
|
+
raise "NOTREACHED: What other stringlike classes are there?"
|
|
124
124
|
end
|
|
125
125
|
|
|
126
126
|
# special case: If one of them is a string/slice, the other is more important
|
|
@@ -168,8 +168,8 @@ module Parsanol
|
|
|
168
168
|
# If any arrays are nested in this array, flatten all arrays to this
|
|
169
169
|
# level.
|
|
170
170
|
return list
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
.select { |e| e.instance_of?(Array) }
|
|
172
|
+
.flatten(1)
|
|
173
173
|
end
|
|
174
174
|
|
|
175
175
|
# Consistent handling of empty lists, when we act on a named result
|
|
@@ -21,11 +21,11 @@ module Parsanol
|
|
|
21
21
|
# Per-parser cache size thresholds based on profiling different grammar types
|
|
22
22
|
# Different grammars benefit from caching at different input sizes
|
|
23
23
|
PARSER_CACHE_LIMITS = {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
:default => 1000
|
|
24
|
+
"JsonParser" => 10_000, # JSON needs large inputs to benefit
|
|
25
|
+
"ErbParser" => 800, # ERB benefits earlier
|
|
26
|
+
"CalcParser" => 2000, # Calculator has low repetition
|
|
27
|
+
"SentenceParser" => 5000, # Linear grammar, minimal benefit
|
|
28
|
+
:default => 1000,
|
|
29
29
|
}.freeze
|
|
30
30
|
|
|
31
31
|
# Creates a new parsing context.
|
|
@@ -66,8 +66,8 @@ module Parsanol
|
|
|
66
66
|
# Optional GPeg-style interval caching
|
|
67
67
|
@use_intervals = interval_cache
|
|
68
68
|
if @use_intervals
|
|
69
|
-
require
|
|
70
|
-
require
|
|
69
|
+
require "parsanol/interval_tree"
|
|
70
|
+
require "parsanol/edit_tracker"
|
|
71
71
|
@interval_trees = Hash.new { |h, k| h[k] = Parsanol::IntervalTree.new }
|
|
72
72
|
@edits = Parsanol::EditTracker.new
|
|
73
73
|
end
|
|
@@ -78,7 +78,7 @@ module Parsanol
|
|
|
78
78
|
# Determine adaptive cache threshold
|
|
79
79
|
threshold = adaptive_cache_threshold
|
|
80
80
|
if threshold.nil? && parser_class
|
|
81
|
-
name = parser_class.name&.split(
|
|
81
|
+
name = parser_class.name&.split("::")&.last
|
|
82
82
|
threshold = PARSER_CACHE_LIMITS[name] || PARSER_CACHE_LIMITS[:default]
|
|
83
83
|
end
|
|
84
84
|
threshold ||= PARSER_CACHE_LIMITS[:default]
|
|
@@ -143,7 +143,11 @@ module Parsanol
|
|
|
143
143
|
|
|
144
144
|
# Only cache if beneficial (heuristic)
|
|
145
145
|
attempts = @hit_stats[key] + @miss_stats[key]
|
|
146
|
-
|
|
146
|
+
if attempts <= @min_hits_for_cache || @hit_stats[key].positive?
|
|
147
|
+
@memo[pos][key] =
|
|
148
|
+
[outcome,
|
|
149
|
+
delta]
|
|
150
|
+
end
|
|
147
151
|
|
|
148
152
|
outcome
|
|
149
153
|
end
|
|
@@ -175,7 +179,10 @@ module Parsanol
|
|
|
175
179
|
end_pos = pos + delta
|
|
176
180
|
|
|
177
181
|
attempts = @hit_stats[key] + @miss_stats[key]
|
|
178
|
-
|
|
182
|
+
if attempts <= @min_hits_for_cache || @hit_stats[key].positive?
|
|
183
|
+
tree.insert(pos, end_pos,
|
|
184
|
+
[outcome, delta])
|
|
185
|
+
end
|
|
179
186
|
|
|
180
187
|
outcome
|
|
181
188
|
end
|
|
@@ -188,8 +195,8 @@ module Parsanol
|
|
|
188
195
|
#
|
|
189
196
|
# @return [Array(Boolean, Object)] error result tuple
|
|
190
197
|
#
|
|
191
|
-
def err_at(*
|
|
192
|
-
return [false, @reporter.err_at(*
|
|
198
|
+
def err_at(*)
|
|
199
|
+
return [false, @reporter.err_at(*)] if @reporter
|
|
193
200
|
|
|
194
201
|
ERROR_RESULT
|
|
195
202
|
end
|
|
@@ -198,8 +205,8 @@ module Parsanol
|
|
|
198
205
|
#
|
|
199
206
|
# @return [Array(Boolean, Object)] error result tuple
|
|
200
207
|
#
|
|
201
|
-
def err(*
|
|
202
|
-
return [false, @reporter.err(*
|
|
208
|
+
def err(*)
|
|
209
|
+
return [false, @reporter.err(*)] if @reporter
|
|
203
210
|
|
|
204
211
|
ERROR_RESULT
|
|
205
212
|
end
|
|
@@ -208,10 +215,10 @@ module Parsanol
|
|
|
208
215
|
#
|
|
209
216
|
# @return [Array(Boolean, Object)] success result tuple
|
|
210
217
|
#
|
|
211
|
-
def succ(*
|
|
218
|
+
def succ(*)
|
|
212
219
|
return SUCCESS_RESULT unless @reporter
|
|
213
220
|
|
|
214
|
-
val = @reporter.succ(*
|
|
221
|
+
val = @reporter.succ(*)
|
|
215
222
|
return SUCCESS_RESULT if val.nil?
|
|
216
223
|
|
|
217
224
|
[true, val]
|
|
@@ -54,7 +54,7 @@ module Parsanol
|
|
|
54
54
|
#
|
|
55
55
|
def try_match(source, context, consume_all)
|
|
56
56
|
raise NotImplementedError,
|
|
57
|
-
|
|
57
|
+
"Custom atoms must implement #try_match(source, context, consume_all)"
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
# Override of Base#try that delegates to try_match
|
|
@@ -71,7 +71,7 @@ module Parsanol
|
|
|
71
71
|
context.err(
|
|
72
72
|
self,
|
|
73
73
|
source,
|
|
74
|
-
"Failed to match custom atom: #{self.class.name}"
|
|
74
|
+
"Failed to match custom atom: #{self.class.name}",
|
|
75
75
|
)
|
|
76
76
|
end
|
|
77
77
|
end
|
data/lib/parsanol/atoms/infix.rb
CHANGED
|
@@ -74,7 +74,8 @@ module Parsanol
|
|
|
74
74
|
right_operand = build_result_tree(right_operand)
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
accumulator = combiner.call(accumulator, operator_token,
|
|
77
|
+
accumulator = combiner.call(accumulator, operator_token,
|
|
78
|
+
right_operand)
|
|
78
79
|
end
|
|
79
80
|
|
|
80
81
|
accumulator
|
|
@@ -96,7 +97,8 @@ module Parsanol
|
|
|
96
97
|
ok, first_value = element_parser.apply(source, context, false)
|
|
97
98
|
unless ok
|
|
98
99
|
throw :parse_error,
|
|
99
|
-
context.err(self, source, "Expected #{element_parser.inspect}",
|
|
100
|
+
context.err(self, source, "Expected #{element_parser.inspect}",
|
|
101
|
+
[first_value])
|
|
100
102
|
end
|
|
101
103
|
|
|
102
104
|
expression_parts << flatten(first_value, true)
|
|
@@ -104,7 +106,9 @@ module Parsanol
|
|
|
104
106
|
# Continue while operators match
|
|
105
107
|
loop do
|
|
106
108
|
saved_position = source.bytepos
|
|
107
|
-
operator_match, precedence, associativity = try_match_operator(
|
|
109
|
+
operator_match, precedence, associativity = try_match_operator(
|
|
110
|
+
source, context, false
|
|
111
|
+
)
|
|
108
112
|
|
|
109
113
|
# No operator found - done with this level
|
|
110
114
|
break unless operator_match
|
|
@@ -114,7 +118,8 @@ module Parsanol
|
|
|
114
118
|
next_min = associativity == :left ? precedence + 1 : precedence
|
|
115
119
|
|
|
116
120
|
expression_parts << operator_match
|
|
117
|
-
expression_parts << climb_precedence(source, context, consume_all,
|
|
121
|
+
expression_parts << climb_precedence(source, context, consume_all,
|
|
122
|
+
next_min)
|
|
118
123
|
else
|
|
119
124
|
# Operator has lower precedence - backtrack and return
|
|
120
125
|
source.bytepos = saved_position
|
|
@@ -154,7 +159,7 @@ module Parsanol
|
|
|
154
159
|
|
|
155
160
|
# Returns string representation for debugging
|
|
156
161
|
def to_s_inner(_precedence)
|
|
157
|
-
op_list = @operator_table.map { |op, _, _| op.inspect }.join(
|
|
162
|
+
op_list = @operator_table.map { |op, _, _| op.inspect }.join(", ")
|
|
158
163
|
"infix_expression(#{@base_element.inspect}, [#{op_list}])"
|
|
159
164
|
end
|
|
160
165
|
end
|
|
@@ -28,8 +28,8 @@ module Parsanol
|
|
|
28
28
|
@bound_parslet = parser
|
|
29
29
|
|
|
30
30
|
# Pre-built error components
|
|
31
|
-
@should_start = [
|
|
32
|
-
@should_not_start = [
|
|
31
|
+
@should_start = ["Input should start with ", parser].freeze
|
|
32
|
+
@should_not_start = ["Input should not start with ", parser].freeze
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
# Tests lookahead without consuming input.
|
|
@@ -54,7 +54,10 @@ module Parsanol
|
|
|
54
54
|
context.err_at(self, source, @should_start, source.bytepos)
|
|
55
55
|
else
|
|
56
56
|
# Negative: succeed if not matched
|
|
57
|
-
|
|
57
|
+
if matched
|
|
58
|
+
return context.err_at(self, source, @should_not_start,
|
|
59
|
+
source.bytepos)
|
|
60
|
+
end
|
|
58
61
|
|
|
59
62
|
ok(nil)
|
|
60
63
|
end
|
|
@@ -67,7 +70,7 @@ module Parsanol
|
|
|
67
70
|
# @param prec [Integer] precedence
|
|
68
71
|
# @return [String]
|
|
69
72
|
def to_s_inner(prec)
|
|
70
|
-
symbol = @positive ?
|
|
73
|
+
symbol = @positive ? "&" : "!"
|
|
71
74
|
"#{symbol}#{@bound_parslet.to_s(prec)}"
|
|
72
75
|
end
|
|
73
76
|
|
data/lib/parsanol/atoms/re.rb
CHANGED