Class: Ibex::Frontend::Parser
- Inherits:
-
Object
- Object
- Ibex::Frontend::Parser
- Defined in:
- lib/ibex/frontend/parser.rb,
sig/ibex/frontend/parser.rbs
Overview
Public grammar parser backed by Ibex's generated LR frontend.
Constant Summary collapse
- DEFAULT_MAX_DIAGNOSTICS =
20- STRICT_LEXICAL_DIAGNOSTIC_LIMIT =
1
Instance Attribute Summary collapse
- #implementation ⇒ GeneratedParser readonly
Instance Method Summary collapse
- #diagnostic_key(diagnostic) ⇒ [ String, String, Integer, Integer, String ]
- #diagnostic_sort_key(diagnostic) ⇒ [ String, Integer, Integer, String, String ]
- #enrich_rule_documentation(node) ⇒ AST::Root, AST::Fragment
-
#initialize(source, file: "(grammar)", mode: :default) ⇒ Parser
constructor
A new instance of Parser.
- #lexical_diagnostics_for(max_diagnostics) ⇒ Array[Diagnostic]
- #merge_diagnostics(lexical, syntax, limit) ⇒ Array[Diagnostic]
- #parse ⇒ AST::Root
-
#parse_document ⇒ SourceDocument
Parse the grammar and return its lossless source model.
-
#parse_fragment ⇒ AST::Fragment
Parse an explicit fragment without resolving its includes.
- #parse_node ⇒ AST::Root, AST::Fragment
-
#parse_source_document ⇒ SourceDocument
Parse either a root grammar or an explicit fragment and return its lossless source model.
-
#parse_with_diagnostics(max_diagnostics: DEFAULT_MAX_DIAGNOSTICS) ⇒ ParseResult
Parse with conservative boundary recovery and collect multiple errors.
- #root_diagnostic_result(node, diagnostics) ⇒ [ AST::Root?, Array[Diagnostic] ]
- #source_document! ⇒ SourceDocument
- #tokenize_source(source, file) ⇒ Array[Token]
Constructor Details
#initialize(source, file: "(grammar)", mode: :default) ⇒ Parser
Returns a new instance of Parser.
23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/ibex/frontend/parser.rb', line 23 def initialize(source, file: "(grammar)", mode: :default) raise ArgumentError, "mode must be :default or :extended" unless %i[default extended].include?(mode) @mode = mode @lexical_diagnostics = [] #: Array[Diagnostic] @parse_error_message = nil @source_document_error_message = nil tokens = source.is_a?(Array) ? source : tokenize_source(source, file) @tokens = tokens @implementation = GeneratedParser.new(tokens, mode: mode) end |
Instance Attribute Details
#implementation ⇒ GeneratedParser (readonly)
11 12 13 |
# File 'lib/ibex/frontend/parser.rb', line 11 def implementation @implementation end |
Instance Method Details
#diagnostic_key(diagnostic) ⇒ [ String, String, Integer, Integer, String ]
189 190 191 192 |
# File 'lib/ibex/frontend/parser.rb', line 189 def diagnostic_key(diagnostic) location = diagnostic.location [diagnostic.phase.to_s, location.file, location.line, location.column, diagnostic.] end |
#diagnostic_sort_key(diagnostic) ⇒ [ String, Integer, Integer, String, String ]
195 196 197 198 |
# File 'lib/ibex/frontend/parser.rb', line 195 def diagnostic_sort_key(diagnostic) location = diagnostic.location [location.file, location.line, location.column, diagnostic.phase.to_s, diagnostic.code] end |
#enrich_rule_documentation(node) ⇒ AST::Root, AST::Fragment
130 131 132 133 134 135 |
# File 'lib/ibex/frontend/parser.rb', line 130 def enrich_rule_documentation(node) source_document = @source_document return node unless source_document RuleDocumentation.enrich(node, source_document) end |
#lexical_diagnostics_for(max_diagnostics) ⇒ Array[Diagnostic]
168 169 170 171 172 173 174 175 176 177 |
# File 'lib/ibex/frontend/parser.rb', line 168 def lexical_diagnostics_for(max_diagnostics) return @lexical_diagnostics if @lexical_diagnostics.empty? || max_diagnostics == 1 source_document = @source_document return @lexical_diagnostics.first(max_diagnostics) unless source_document _, diagnostics = Lexer.new(source_document.source, file: source_document.file) .tokenize_document_recovering(max_diagnostics: max_diagnostics) diagnostics end |
#merge_diagnostics(lexical, syntax, limit) ⇒ Array[Diagnostic]
180 181 182 183 184 185 186 |
# File 'lib/ibex/frontend/parser.rb', line 180 def merge_diagnostics(lexical, syntax, limit) (lexical + syntax) .uniq { |diagnostic| diagnostic_key(diagnostic) } .sort_by { |diagnostic| diagnostic_sort_key(diagnostic) } .first(limit) .freeze end |
#parse ⇒ AST::Root
36 37 38 39 40 41 |
# File 'lib/ibex/frontend/parser.rb', line 36 def parse node = parse_node return node if node.is_a?(AST::Root) raise Ibex::Error, "#{node.loc}: fragment input requires Parser#parse_fragment" end |
#parse_document ⇒ SourceDocument
Parse the grammar and return its lossless source model.
54 55 56 57 58 59 |
# File 'lib/ibex/frontend/parser.rb', line 54 def parse_document parsed_document = @parsed_document return parsed_document if parsed_document && parsed_document.ast.is_a?(AST::Root) @parsed_document = source_document!.with_ast(parse) end |
#parse_fragment ⇒ AST::Fragment
Parse an explicit fragment without resolving its includes.
45 46 47 48 49 50 |
# File 'lib/ibex/frontend/parser.rb', line 45 def parse_fragment node = parse_node return node if node.is_a?(AST::Fragment) raise Ibex::Error, "#{node.loc}: root grammar input cannot be parsed as a fragment" end |
#parse_node ⇒ AST::Root, AST::Fragment
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/ibex/frontend/parser.rb', line 111 def parse_node lexical = @lexical_diagnostics.first raise Ibex::Error, lexical.to_s if lexical = @parse_error_message raise Ibex::Error, if node = @parsed_node return node if node begin @parsed_node = enrich_rule_documentation(@implementation.parse) rescue Ibex::Error => e @parse_error_message = e..dup.freeze raise end end |
#parse_source_document ⇒ SourceDocument
Parse either a root grammar or an explicit fragment and return its lossless source model.
63 64 65 66 67 68 |
# File 'lib/ibex/frontend/parser.rb', line 63 def parse_source_document parsed_document = @parsed_document return parsed_document if parsed_document @parsed_document = source_document!.with_ast(parse_node) end |
#parse_with_diagnostics(max_diagnostics: DEFAULT_MAX_DIAGNOSTICS) ⇒ ParseResult
Parse with conservative boundary recovery and collect multiple errors.
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/ibex/frontend/parser.rb', line 72 def parse_with_diagnostics(max_diagnostics: DEFAULT_MAX_DIAGNOSTICS) unless max_diagnostics.is_a?(Integer) && max_diagnostics.positive? raise ArgumentError, "max_diagnostics must be a positive integer" end lexical_diagnostics = lexical_diagnostics_for(max_diagnostics) if @source_document.nil? && @lexical_diagnostics.any? return ParseResult.new(diagnostics: lexical_diagnostics, ast: nil, document: nil) end recovery = DiagnosticRecovery.new( @tokens, mode: @mode, max_diagnostics: max_diagnostics ) node, syntax_diagnostics = recovery.parse node = enrich_rule_documentation(node) if node ast, syntax_diagnostics = root_diagnostic_result(node, syntax_diagnostics) diagnostics = merge_diagnostics(lexical_diagnostics, syntax_diagnostics, max_diagnostics) document = if diagnostics.empty? && ast @source_document&.with_ast(ast) else @source_document end ParseResult.new(diagnostics: diagnostics, ast: ast, document: document) end |
#root_diagnostic_result(node, diagnostics) ⇒ [ AST::Root?, Array[Diagnostic] ]
139 140 141 142 143 144 145 146 147 |
# File 'lib/ibex/frontend/parser.rb', line 139 def root_diagnostic_result(node, diagnostics) return [node, diagnostics] unless node.is_a?(AST::Fragment) diagnostic = Diagnostic.new( code: "frontend.syntax_error", phase: :syntax, message: "fragment input requires Parser#parse_fragment", location: node.loc ) [nil, diagnostics + [diagnostic]] end |
#source_document! ⇒ SourceDocument
100 101 102 103 104 105 106 107 108 |
# File 'lib/ibex/frontend/parser.rb', line 100 def source_document! = @source_document_error_message raise Ibex::Error, if source_document = @source_document raise ArgumentError, "source document parsing requires String source" unless source_document source_document end |
#tokenize_source(source, file) ⇒ Array[Token]
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/ibex/frontend/parser.rb', line 150 def tokenize_source(source, file) document, diagnostics = Lexer.new(source, file: file).tokenize_document_recovering( max_diagnostics: STRICT_LEXICAL_DIAGNOSTIC_LIMIT ) @source_document = document @lexical_diagnostics = diagnostics document.tokens rescue Ibex::Error => e location = Location.new(file: file, line: 1, column: 1) @lexical_diagnostics = [ Diagnostic.new(code: "frontend.lexical_error", phase: :lexical, message: e., location: location, rendered: e.) ] @source_document_error_message = e..dup.freeze [Token.new(type: :eof, value: nil, location: location, span: nil)] end |