Class: Expressir::Express::Parser::Parser

Inherits:
Parsanol::Parser
  • Object
show all
Defined in:
lib/expressir/express/parser.rb

Constant Summary collapse

LARGE_FILE_THRESHOLD =

Threshold for using memory-bounded fresh parse (bytes) Files above this use parse_fresh which has no packrat cache

1024 * 1024
KEYWORDS =
%i[
  ABS ABSTRACT ACOS AGGREGATE ALIAS AND ANDOR ARRAY AS ASIN ATAN BAG BASED_ON
  BEGIN BINARY BLENGTH BOOLEAN BY CASE CONSTANT CONST_E COS DERIVE DIV ELSE
  END END_ALIAS END_CASE END_CONSTANT END_ENTITY END_FUNCTION END_IF
  END_LOCAL END_PROCEDURE END_REPEAT END_RULE END_SCHEMA
  END_SUBTYPE_CONSTRAINT END_TYPE ENTITY ENUMERATION ESCAPE EXISTS EXP
  EXTENSIBLE FALSE FIXED FOR FORMAT FROM FUNCTION GENERIC GENERIC_ENTITY
  HIBOUND HIINDEX IF IN INSERT INTEGER INVERSE LENGTH LIKE LIST LOBOUND LOCAL
  LOG LOG10 LOG2 LOGICAL LOINDEX MOD NOT NUMBER NVL ODD OF ONEOF OPTIONAL OR
  OTHERWISE PI PROCEDURE QUERY REAL REFERENCE REMOVE RENAMED REPEAT RETURN
  ROLESOF RULE SCHEMA SELECT SELF SET SIN SIZEOF SKIP SQRT STRING SUBTYPE
  SUBTYPE_CONSTRAINT SUPERTYPE TAN THEN TO TRUE TYPE TYPEOF TOTAL_OVER UNIQUE
  UNKNOWN UNTIL USE USEDIN VALUE VALUE_IN VALUE_UNIQUE VAR WITH WHERE WHILE
  XOR
].freeze
@@cached_grammar_json =

Cache for native parser grammar (class-level)

nil
@@cached_grammar_atom_id =
nil
@@cached_parser =

Cache for parser instance (avoids ~7ms overhead of Parser.new)

nil
@@parser_mutex =
Mutex.new
@@cached_schema_grammar_json =

Cache for schemaDecl grammar JSON (used for streaming parse)

nil

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.cached_grammar_jsonObject

Get cached grammar JSON for native parsing



54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/expressir/express/parser.rb', line 54

def self.cached_grammar_json
  return @@cached_grammar_json if @@cached_grammar_json

  parser = new
  atom = parser.syntax

  # Cache by atom object_id
  if atom.object_id != @@cached_grammar_atom_id
    @@cached_grammar_atom_id = atom.object_id
    @@cached_grammar_json = Parsanol::Native.serialize_grammar(atom)
  end

  @@cached_grammar_json
end

.cached_parserObject

Get cached parser instance (thread-safe) Reusing the parser avoids the overhead of reinitializing all rule definitions



26
27
28
29
30
31
32
# File 'lib/expressir/express/parser.rb', line 26

def self.cached_parser
  return @@cached_parser if @@cached_parser

  @@parser_mutex.synchronize do
    @@cached_parser ||= new
  end
end

.cached_schema_grammar_jsonObject

Get cached grammar JSON for schemaDecl (used for streaming parse)



70
71
72
73
74
75
76
# File 'lib/expressir/express/parser.rb', line 70

def self.cached_schema_grammar_json
  return @@cached_schema_grammar_json if @@cached_schema_grammar_json

  schema_atom = cached_parser.schemaDecl
  @@cached_schema_grammar_json = Parsanol::Native.serialize_grammar(schema_atom)
  @@cached_schema_grammar_json
end

.clear_parser_cacheObject

Clear the cached parser (useful for testing or after grammar changes)



35
36
37
38
39
# File 'lib/expressir/express/parser.rb', line 35

def self.clear_parser_cache
  @@parser_mutex.synchronize do
    @@cached_parser = nil
  end
end

.native_available?Boolean

Check if native parsing is available

Returns:

  • (Boolean)


42
43
44
45
46
47
48
49
50
51
# File 'lib/expressir/express/parser.rb', line 42

def self.native_available?
  return @native_available unless @native_available.nil?

  @native_available = begin
    require "parsanol/native"
    Parsanol::Native.available?
  rescue LoadError
    false
  end
end

.parse_native(source) ⇒ Hash, Array

Parse using native engine with Rust-side transformation (fastest)

This method provides ~17x speedup over pure Ruby parsing. The transformation happens in Rust using to_parslet_compatible, producing Parslet-compatible output that Builder.build can consume directly.

Parameters:

  • source (String)

    EXPRESS source code to parse

Returns:

  • (Hash, Array)

    Transformed AST in Parslet-compatible format

Raises:

  • (LoadError)

    If native parser is not available



87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/expressir/express/parser.rb', line 87

def self.parse_native(source)
  unless native_available?
    raise LoadError, "Native parser not available"
  end

  grammar_atom = cached_parser.syntax
  # Use fresh-parse (no cache) for large files to bound memory
  if source.bytesize > LARGE_FILE_THRESHOLD
    Parsanol::Native.parse_fresh(grammar_atom, source)
  else
    Parsanol::Native.parse(grammar_atom, source)
  end
end

Instance Method Details

#cstr(atom) ⇒ Object



105
106
107
# File 'lib/expressir/express/parser.rb', line 105

def cstr(atom)
  cts(str(atom).as(:str))
end

#cts(atom) ⇒ Object



101
102
103
# File 'lib/expressir/express/parser.rb', line 101

def cts(atom)
  spaces >> atom
end

#keyword_rule(str) ⇒ Object



125
126
127
128
129
130
# File 'lib/expressir/express/parser.rb', line 125

def keyword_rule(str)
  key_chars = str.to_s.chars
  key_chars
    .collect! { |char| match("[#{char}#{char.downcase}]") }
    .reduce(:>>) >> match["a-zA-Z0-9_"].absent?
end