diff --git a/interscript.gemspec b/interscript.gemspec index 78e09f4c..ee445e14 100644 --- a/interscript.gemspec +++ b/interscript.gemspec @@ -35,5 +35,5 @@ Gem::Specification.new do |spec| spec.add_dependency "thor" spec.add_dependency "interscript-maps", "~> #{Interscript::VERSION.split(".")[0, 2].join(".")}.0a" spec.add_dependency "text" - spec.add_dependency "parslet", "~> 2.0" + spec.add_dependency "parsanol", "~> 1.3" end diff --git a/lib/interscript/compiler.rb b/lib/interscript/compiler.rb index 6b1a799a..7e8556bd 100644 --- a/lib/interscript/compiler.rb +++ b/lib/interscript/compiler.rb @@ -10,7 +10,7 @@ class Interscript::Compiler def self.call(map, **kwargs) # DSL.parse locates, dispatches .isc and caches the parsed document — - # calling parse_isc here re-ran a ~30 s Parslet parse on every + # calling parse_isc here re-ran a full grammar parse on every # compiler instantiation. map = Interscript::DSL.parse(map) if String === map compiler = new diff --git a/lib/interscript/dsl.rb b/lib/interscript/dsl.rb index b085b021..14f6de9f 100644 --- a/lib/interscript/dsl.rb +++ b/lib/interscript/dsl.rb @@ -40,7 +40,7 @@ def self.parse(map_name, reverse: true) end end # ISC documents route through the ISC parser, not the .imp DSL. - # The parse is expensive (Parslet over the whole map) — cache it like + # The parse is expensive (the whole grammar over the map) — cache it like # the .imp path does, or every transliterate call re-parses the map. return @cache[map_name] = Interscript::Compiler.parse_isc(path) if path.end_with?(".isc") diff --git a/lib/interscript/isc.rb b/lib/interscript/isc.rb index a1aeb766..ff3284eb 100644 --- a/lib/interscript/isc.rb +++ b/lib/interscript/isc.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "parslet" +require "parsanol" module Interscript module Isc diff --git a/lib/interscript/isc/document_builder.rb b/lib/interscript/isc/document_builder.rb index 886778b6..232ef42b 100644 --- a/lib/interscript/isc/document_builder.rb +++ b/lib/interscript/isc/document_builder.rb @@ -71,10 +71,12 @@ def build def unquote(fragment) return "" if fragment.nil? return fragment.to_s unless fragment.is_a?(Hash) - return fragment.to_s unless fragment.key?(:string) + # Direct decode for the overwhelmingly common shape — routing + # every quoted string through Transform pattern matching + # dominated large-map builds. + return Transform.decode_string_parts(fragment[:string]) if fragment.key?(:string) - out = @transform.apply(fragment) - out.is_a?(Items::StringValue) ? out.value : out.to_s + fragment.to_s end def unescape_braces(text) @@ -325,17 +327,19 @@ def extract_dependency(item) }.compact end - # Convert a parslet tree fragment into a concrete Item object via Transform. + # Convert a grammar tree fragment into a concrete Item object via + # Transform. Reuses the builder's Transform instance — allocating + # one per fragment dominated large-map builds. def materialize(fragment) case fragment when Hash if fragment.key?(:concatenation) - Transform.new.apply(fragment) + @transform.apply(fragment) else - Transform.new.apply(concatenation: [fragment]) + @transform.apply(concatenation: [fragment]) end when Array - Transform.new.apply(concatenation: fragment) + @transform.apply(concatenation: fragment) when NilClass Items::None.new else diff --git a/lib/interscript/isc/grammar.rb b/lib/interscript/isc/grammar.rb index 7d0d1a37..a4f6ce3b 100644 --- a/lib/interscript/isc/grammar.rb +++ b/lib/interscript/isc/grammar.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "parslet" +require "parsanol" module Interscript module Isc diff --git a/lib/interscript/isc/grammar/concerns/aliases.rb b/lib/interscript/isc/grammar/concerns/aliases.rb index 0dd1fc03..c595ceda 100644 --- a/lib/interscript/isc/grammar/concerns/aliases.rb +++ b/lib/interscript/isc/grammar/concerns/aliases.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "parslet" +require "parsanol" module Interscript module Isc @@ -8,7 +8,7 @@ module Grammar module Concerns # Aliases block: named expressions reused throughout the system. module Aliases - include Parslet + include Parsanol rule(:aliases_block) do str("aliases") >> whitespace? >> diff --git a/lib/interscript/isc/grammar/concerns/dependencies.rb b/lib/interscript/isc/grammar/concerns/dependencies.rb index c0e865b6..2c908dd4 100644 --- a/lib/interscript/isc/grammar/concerns/dependencies.rb +++ b/lib/interscript/isc/grammar/concerns/dependencies.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "parslet" +require "parsanol" module Interscript module Isc @@ -8,7 +8,7 @@ module Grammar module Concerns # Dependencies: references to other systems whose stages may be invoked. module Dependencies - include Parslet + include Parsanol rule(:dependency_decl) do str("dependency") >> whitespace >> diff --git a/lib/interscript/isc/grammar/concerns/items.rb b/lib/interscript/isc/grammar/concerns/items.rb index 9ff16ff6..add11250 100644 --- a/lib/interscript/isc/grammar/concerns/items.rb +++ b/lib/interscript/isc/grammar/concerns/items.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "parslet" +require "parsanol" module Interscript module Isc @@ -8,7 +8,7 @@ module Grammar module Concerns # Item expressions: the building blocks of rule matches and targets. module Items - include Parslet + include Parsanol rule(:item_atom) do quoted_string | diff --git a/lib/interscript/isc/grammar/concerns/metadata.rb b/lib/interscript/isc/grammar/concerns/metadata.rb index 5b4f8587..1b79aa45 100644 --- a/lib/interscript/isc/grammar/concerns/metadata.rb +++ b/lib/interscript/isc/grammar/concerns/metadata.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "parslet" +require "parsanol" module Interscript module Isc @@ -8,7 +8,7 @@ module Grammar module Concerns # Metadata block: identity + provenance + lifecycle of a system. module Metadata - include Parslet + include Parsanol rule(:metadata_block) do str("metadata") >> whitespace? >> diff --git a/lib/interscript/isc/grammar/concerns/primitives.rb b/lib/interscript/isc/grammar/concerns/primitives.rb index befe7770..554cd5d5 100644 --- a/lib/interscript/isc/grammar/concerns/primitives.rb +++ b/lib/interscript/isc/grammar/concerns/primitives.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "parslet" +require "parsanol" module Interscript module Isc @@ -9,7 +9,7 @@ module Concerns # Lexical primitives shared by every other concern. # Mirrors the structure of LutaML LML's Concerns::Primitives. module Primitives - include Parslet + include Parsanol # -- Whitespace and comments @@ -74,9 +74,16 @@ module Primitives end # Double-quoted strings: \\uXXXX, \\n, etc. are interpreted. + # Escape-free stretches are captured as ONE run slice — a + # per-char capture allocates a hash + slice per character + # (~700k nodes for a large map) and the Transform pattern + # match grinds on them even under the native parse engine. + rule(:plain_run) do + (str("\\") | str('"')).absent? >> any + end rule(:double_quoted_string) do str('"') >> - (escape_sequence | (str('"').absent? >> any).as(:char)).repeat.as(:string) >> + (escape_sequence | plain_run.repeat(1).as(:run)).repeat.as(:string) >> str('"') end diff --git a/lib/interscript/isc/grammar/concerns/stages.rb b/lib/interscript/isc/grammar/concerns/stages.rb index 5838a386..70844407 100644 --- a/lib/interscript/isc/grammar/concerns/stages.rb +++ b/lib/interscript/isc/grammar/concerns/stages.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "parslet" +require "parsanol" module Interscript module Isc @@ -8,7 +8,7 @@ module Grammar module Concerns # Stages: ordered transformation pipelines. module Stages - include Parslet + include Parsanol rule(:stage_block) do str("stage") >> diff --git a/lib/interscript/isc/grammar/concerns/system.rb b/lib/interscript/isc/grammar/concerns/system.rb index c3598c7a..7f51c9cb 100644 --- a/lib/interscript/isc/grammar/concerns/system.rb +++ b/lib/interscript/isc/grammar/concerns/system.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "parslet" +require "parsanol" module Interscript module Isc @@ -8,7 +8,7 @@ module Grammar module Concerns # Top-level system block. module System - include Parslet + include Parsanol # A block-item is any of the top-level constructs that may appear # inside `system "" { ... }`. diff --git a/lib/interscript/isc/grammar/concerns/tests.rb b/lib/interscript/isc/grammar/concerns/tests.rb index b49cad7f..5ffd1d84 100644 --- a/lib/interscript/isc/grammar/concerns/tests.rb +++ b/lib/interscript/isc/grammar/concerns/tests.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "parslet" +require "parsanol" module Interscript module Isc @@ -8,7 +8,7 @@ module Grammar module Concerns # Tests block: normative input/output pairs. module Tests - include Parslet + include Parsanol rule(:tests_block) do str("tests") >> whitespace? >> diff --git a/lib/interscript/isc/grammar/core.rb b/lib/interscript/isc/grammar/core.rb index 790a553a..2ddfd8ed 100644 --- a/lib/interscript/isc/grammar/core.rb +++ b/lib/interscript/isc/grammar/core.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "parslet" +require "parsanol" module Interscript module Isc @@ -8,7 +8,7 @@ module Grammar # Core grammar: composes every concern into a single Parser ancestor. # Mirrors the structure of LutaML LML's Grammar::Core. module Core - include Parslet + include Parsanol include Concerns::Primitives include Concerns::Items include Concerns::Metadata diff --git a/lib/interscript/isc/parser.rb b/lib/interscript/isc/parser.rb index 78c81d80..b0f551a1 100644 --- a/lib/interscript/isc/parser.rb +++ b/lib/interscript/isc/parser.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "parslet" +require "parsanol" module Interscript module Isc @@ -11,7 +11,7 @@ module Isc # # The returned tree is a parslet-shaped hash — lists of hashes with # symbol keys. Convert to a domain object via Interscript::Isc::DocumentBuilder. - class Parser < Parslet::Parser + class Parser < Parsanol::Parser include Grammar::Core root :isc_source @@ -22,7 +22,7 @@ def self.parse(source, filename: nil) def parse_with_callbacks(source, filename: nil) Normalizer.normalize(parse(source)) - rescue Parslet::ParseFailed => e + rescue Parsanol::ParseFailed => e raise ParseError.new(e.message, filename: filename, source: source, cause: e) end end diff --git a/lib/interscript/isc/transform.rb b/lib/interscript/isc/transform.rb index c988dafa..2d5047e3 100644 --- a/lib/interscript/isc/transform.rb +++ b/lib/interscript/isc/transform.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "parslet" +require "parsanol" module Interscript module Isc @@ -9,7 +9,7 @@ module Isc # # String escapes are unescaped here. Items are flattened. Constraints # are tagged by kind. - class Transform < Parslet::Transform + class Transform < Parsanol::Transform # String atom: parslet gives us a parslet slice for simple strings, # or an array of pieces (escape-sequence fragments interleaved with # raw chars) for strings containing escapes. Flatten both to a single @@ -18,7 +18,7 @@ class Transform < Parslet::Transform rule(string: sequence(:parts)) do Items::StringValue.new(Transform.decode_string_parts(parts)) end - rule(char: simple(:c)) { c.to_s } + rule(run: simple(:s)) { s.to_s } rule(identifier: simple(:i)) { i.to_s } @@ -52,13 +52,13 @@ class Transform < Parslet::Transform # Shared decoder for the pieces of a :string capture — used by the # transform rules above and by the Normalizer's scalar folding. A - # fragment without an escape key is a raw character slice. + # fragment without an escape key is a raw run slice. def self.decode_string_parts(parts) Array(parts).map do |p| case p when Hash - if p.key?(:char) - p[:char].to_s + if p.key?(:run) + p[:run].to_s elsif p.key?(:newline) "\n" elsif p.key?(:carriage_return)