Lrama v0.6.0
This commit is contained in:
parent
9cf1c2bb0c
commit
12b69bf515
@ -1,5 +1,25 @@
|
|||||||
# NEWS for Lrama
|
# NEWS for Lrama
|
||||||
|
|
||||||
|
## Lrama 0.6.0 (2023-12-25)
|
||||||
|
|
||||||
|
### User defined parameterizing rules
|
||||||
|
|
||||||
|
Allow to define parameterizing rule by `%rule` directive.
|
||||||
|
|
||||||
|
```
|
||||||
|
%rule pair(X, Y): X Y { $$ = $1 + $2; }
|
||||||
|
;
|
||||||
|
|
||||||
|
%%
|
||||||
|
|
||||||
|
program: stmt
|
||||||
|
;
|
||||||
|
|
||||||
|
stmt: pair(ODD, EVEN) <num>
|
||||||
|
| pair(EVEN, ODD) <num>
|
||||||
|
;
|
||||||
|
```
|
||||||
|
|
||||||
## Lrama 0.5.11 (2023-12-02)
|
## Lrama 0.5.11 (2023-12-02)
|
||||||
|
|
||||||
### Type specification of parameterizing rules
|
### Type specification of parameterizing rules
|
||||||
|
@ -8,6 +8,10 @@ require "lrama/grammar/printer"
|
|||||||
require "lrama/grammar/reference"
|
require "lrama/grammar/reference"
|
||||||
require "lrama/grammar/rule"
|
require "lrama/grammar/rule"
|
||||||
require "lrama/grammar/rule_builder"
|
require "lrama/grammar/rule_builder"
|
||||||
|
require "lrama/grammar/parameterizing_rule_builder"
|
||||||
|
require "lrama/grammar/parameterizing_rule_resolver"
|
||||||
|
require "lrama/grammar/parameterizing_rule_rhs_builder"
|
||||||
|
require "lrama/grammar/parameterizing_rule"
|
||||||
require "lrama/grammar/symbol"
|
require "lrama/grammar/symbol"
|
||||||
require "lrama/grammar/type"
|
require "lrama/grammar/type"
|
||||||
require "lrama/grammar/union"
|
require "lrama/grammar/union"
|
||||||
@ -36,6 +40,7 @@ module Lrama
|
|||||||
@rule_builders = []
|
@rule_builders = []
|
||||||
@rules = []
|
@rules = []
|
||||||
@sym_to_rules = {}
|
@sym_to_rules = {}
|
||||||
|
@parameterizing_resolver = ParameterizingRuleResolver.new
|
||||||
@empty_symbol = nil
|
@empty_symbol = nil
|
||||||
@eof_symbol = nil
|
@eof_symbol = nil
|
||||||
@error_symbol = nil
|
@error_symbol = nil
|
||||||
@ -69,7 +74,7 @@ module Lrama
|
|||||||
return sym
|
return sym
|
||||||
end
|
end
|
||||||
|
|
||||||
if sym = @symbols.find {|s| s.id == id }
|
if (sym = @symbols.find {|s| s.id == id })
|
||||||
return sym
|
return sym
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -129,6 +134,10 @@ module Lrama
|
|||||||
@rule_builders << builder
|
@rule_builders << builder
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_parameterizing_rule_builder(builder)
|
||||||
|
@parameterizing_resolver.add_parameterizing_rule_builder(builder)
|
||||||
|
end
|
||||||
|
|
||||||
def prologue_first_lineno=(prologue_first_lineno)
|
def prologue_first_lineno=(prologue_first_lineno)
|
||||||
@aux.prologue_first_lineno = prologue_first_lineno
|
@aux.prologue_first_lineno = prologue_first_lineno
|
||||||
end
|
end
|
||||||
@ -310,7 +319,7 @@ module Lrama
|
|||||||
|
|
||||||
def setup_rules
|
def setup_rules
|
||||||
@rule_builders.each do |builder|
|
@rule_builders.each do |builder|
|
||||||
builder.setup_rules
|
builder.setup_rules(@parameterizing_resolver)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -350,56 +359,21 @@ module Lrama
|
|||||||
@accept_symbol = term
|
@accept_symbol = term
|
||||||
end
|
end
|
||||||
|
|
||||||
# 1. Add $accept rule to the top of rules
|
|
||||||
# 2. Extract action in the middle of RHS into new Empty rule
|
|
||||||
# 3. Append id and extract action then create Rule
|
|
||||||
#
|
|
||||||
# Bison 3.8.2 uses different orders for symbol number and rule number
|
|
||||||
# when a rule has actions in the middle of a rule.
|
|
||||||
#
|
|
||||||
# For example,
|
|
||||||
#
|
|
||||||
# `program: $@1 top_compstmt`
|
|
||||||
#
|
|
||||||
# Rules are ordered like below,
|
|
||||||
#
|
|
||||||
# 1 $@1: ε
|
|
||||||
# 2 program: $@1 top_compstmt
|
|
||||||
#
|
|
||||||
# Symbols are ordered like below,
|
|
||||||
#
|
|
||||||
# 164 program
|
|
||||||
# 165 $@1
|
|
||||||
#
|
|
||||||
def normalize_rules
|
def normalize_rules
|
||||||
# 1. Add $accept rule to the top of rules
|
# Add $accept rule to the top of rules
|
||||||
accept = @accept_symbol
|
|
||||||
eof = @eof_symbol
|
|
||||||
lineno = @rule_builders.first ? @rule_builders.first.line : 0
|
lineno = @rule_builders.first ? @rule_builders.first.line : 0
|
||||||
@rules << Rule.new(id: @rule_counter.increment, _lhs: accept.id, _rhs: [@rule_builders.first.lhs, eof.id], token_code: nil, lineno: lineno)
|
@rules << Rule.new(id: @rule_counter.increment, _lhs: @accept_symbol.id, _rhs: [@rule_builders.first.lhs, @eof_symbol.id], token_code: nil, lineno: lineno)
|
||||||
|
|
||||||
setup_rules
|
setup_rules
|
||||||
|
|
||||||
@rule_builders.each do |builder|
|
@rule_builders.each do |builder|
|
||||||
# Extract actions in the middle of RHS into new rules.
|
|
||||||
builder.midrule_action_rules.each do |rule|
|
|
||||||
@rules << rule
|
|
||||||
end
|
|
||||||
|
|
||||||
builder.rules.each do |rule|
|
builder.rules.each do |rule|
|
||||||
add_nterm(id: rule._lhs)
|
|
||||||
@rules << rule
|
|
||||||
end
|
|
||||||
|
|
||||||
builder.parameterizing_rules.each do |rule|
|
|
||||||
add_nterm(id: rule._lhs, tag: rule.lhs_tag)
|
add_nterm(id: rule._lhs, tag: rule.lhs_tag)
|
||||||
@rules << rule
|
@rules << rule
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
builder.midrule_action_rules.each do |rule|
|
@rules.sort_by!(&:id)
|
||||||
add_nterm(id: rule._lhs)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Collect symbols from rules
|
# Collect symbols from rules
|
||||||
|
@ -1,12 +1,29 @@
|
|||||||
require "forwardable"
|
require "forwardable"
|
||||||
|
require "lrama/grammar/code/initial_action_code"
|
||||||
|
require "lrama/grammar/code/no_reference_code"
|
||||||
|
require "lrama/grammar/code/printer_code"
|
||||||
|
require "lrama/grammar/code/rule_action"
|
||||||
|
|
||||||
module Lrama
|
module Lrama
|
||||||
class Grammar
|
class Grammar
|
||||||
class Code < Struct.new(:type, :token_code, keyword_init: true)
|
class Code
|
||||||
extend Forwardable
|
extend Forwardable
|
||||||
|
|
||||||
def_delegators "token_code", :s_value, :line, :column, :references
|
def_delegators "token_code", :s_value, :line, :column, :references
|
||||||
|
|
||||||
|
attr_reader :type, :token_code
|
||||||
|
|
||||||
|
def initialize(type:, token_code:)
|
||||||
|
@type = type
|
||||||
|
@token_code = token_code
|
||||||
|
end
|
||||||
|
|
||||||
|
def ==(other)
|
||||||
|
self.class == other.class &&
|
||||||
|
self.type == other.type &&
|
||||||
|
self.token_code == other.token_code
|
||||||
|
end
|
||||||
|
|
||||||
# $$, $n, @$, @n are translated to C code
|
# $$, $n, @$, @n are translated to C code
|
||||||
def translated_code
|
def translated_code
|
||||||
t_code = s_value.dup
|
t_code = s_value.dup
|
||||||
@ -17,7 +34,7 @@ module Lrama
|
|||||||
|
|
||||||
str = reference_to_c(ref)
|
str = reference_to_c(ref)
|
||||||
|
|
||||||
t_code[first_column..last_column] = str
|
t_code[first_column...last_column] = str
|
||||||
end
|
end
|
||||||
|
|
||||||
return t_code
|
return t_code
|
||||||
@ -31,8 +48,3 @@ module Lrama
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
require "lrama/grammar/code/initial_action_code"
|
|
||||||
require "lrama/grammar/code/no_reference_code"
|
|
||||||
require "lrama/grammar/code/printer_code"
|
|
||||||
require "lrama/grammar/code/rule_action"
|
|
||||||
|
@ -2,7 +2,7 @@ module Lrama
|
|||||||
class Grammar
|
class Grammar
|
||||||
class Code
|
class Code
|
||||||
class PrinterCode < Code
|
class PrinterCode < Code
|
||||||
def initialize(type: nil, token_code: nil, tag: nil)
|
def initialize(type:, token_code:, tag:)
|
||||||
super(type: type, token_code: token_code)
|
super(type: type, token_code: token_code)
|
||||||
@tag = tag
|
@tag = tag
|
||||||
end
|
end
|
||||||
|
@ -2,7 +2,7 @@ module Lrama
|
|||||||
class Grammar
|
class Grammar
|
||||||
class Code
|
class Code
|
||||||
class RuleAction < Code
|
class RuleAction < Code
|
||||||
def initialize(type: nil, token_code: nil, rule: nil)
|
def initialize(type:, token_code:, rule:)
|
||||||
super(type: type, token_code: token_code)
|
super(type: type, token_code: token_code)
|
||||||
@rule = rule
|
@rule = rule
|
||||||
end
|
end
|
||||||
|
6
tool/lrama/lib/lrama/grammar/parameterizing_rule.rb
Normal file
6
tool/lrama/lib/lrama/grammar/parameterizing_rule.rb
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module Lrama
|
||||||
|
class Grammar
|
||||||
|
class ParameterizingRule < Struct.new(:rules, :token, keyword_init: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
34
tool/lrama/lib/lrama/grammar/parameterizing_rule_builder.rb
Normal file
34
tool/lrama/lib/lrama/grammar/parameterizing_rule_builder.rb
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
module Lrama
|
||||||
|
class Grammar
|
||||||
|
class ParameterizingRuleBuilder
|
||||||
|
attr_reader :name, :parameters, :rhs
|
||||||
|
|
||||||
|
def initialize(name, parameters, rhs)
|
||||||
|
@name = name
|
||||||
|
@parameters = parameters
|
||||||
|
@rhs = rhs
|
||||||
|
@required_parameters_count = parameters.count
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_rules(token, actual_args, rule_counter, lhs_tag, line, rule_builders)
|
||||||
|
validate_argument_number!(token)
|
||||||
|
lhs = lhs(actual_args)
|
||||||
|
@rhs.map do |rhs|
|
||||||
|
rhs.build_rules(token, actual_args, parameters, rule_counter, lhs, lhs_tag, line, rule_builders)
|
||||||
|
end.flatten
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def validate_argument_number!(token)
|
||||||
|
unless @required_parameters_count == token.args.count
|
||||||
|
raise "Invalid number of arguments. expect: #{@required_parameters_count} actual: #{token.args.count}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def lhs(actual_args)
|
||||||
|
Lrama::Lexer::Token::Ident.new(s_value: "#{name}_#{actual_args.map(&:s_value).join('_')}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
30
tool/lrama/lib/lrama/grammar/parameterizing_rule_resolver.rb
Normal file
30
tool/lrama/lib/lrama/grammar/parameterizing_rule_resolver.rb
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
module Lrama
|
||||||
|
class Grammar
|
||||||
|
class ParameterizingRuleResolver
|
||||||
|
def initialize
|
||||||
|
@parameterizing_rule_builders = []
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_parameterizing_rule_builder(builder)
|
||||||
|
@parameterizing_rule_builders << builder
|
||||||
|
end
|
||||||
|
|
||||||
|
def defined?(name)
|
||||||
|
!rule_builders(name).empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_rules(token, rule_counter, lhs_tag, line)
|
||||||
|
builder = rule_builders(token.s_value).last
|
||||||
|
raise "Unknown parameterizing rule #{token.s_value} at line #{token.line}" unless builder
|
||||||
|
|
||||||
|
builder.build_rules(token, token.args, rule_counter, lhs_tag, line, @parameterizing_rule_builders)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def rule_builders(name)
|
||||||
|
@parameterizing_rule_builders.select { |builder| builder.name == name }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,53 @@
|
|||||||
|
module Lrama
|
||||||
|
class Grammar
|
||||||
|
class ParameterizingRuleRhsBuilder
|
||||||
|
attr_accessor :symbols, :user_code, :precedence_sym
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@symbols = []
|
||||||
|
@user_code = nil
|
||||||
|
@precedence_sym = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_rules(token, actual_args, parameters, rule_counter, lhs, lhs_tag, line, rule_builders)
|
||||||
|
nested_rules = build_nested_rules(token, actual_args, parameters, rule_counter, lhs_tag, line, rule_builders)
|
||||||
|
rule = Rule.new(id: rule_counter.increment, _lhs: lhs, _rhs: rhs(token, actual_args, parameters, nested_rules.last), lhs_tag: lhs_tag, token_code: user_code, precedence_sym: precedence_sym, lineno: line)
|
||||||
|
ParameterizingRule.new(rules: nested_rules.map(&:rules) + [rule], token: lhs)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def build_nested_rules(token, actual_args, parameters, rule_counter, lhs_tag, line, rule_builders)
|
||||||
|
symbols.each_with_index.map do |sym, i|
|
||||||
|
next unless sym.is_a?(Lexer::Token::InstantiateRule)
|
||||||
|
|
||||||
|
builder = rule_builders.select { |builder| builder.name == sym.s_value }.last
|
||||||
|
raise "Unknown parameterizing rule #{token.s_value} at line #{token.line}" unless builder
|
||||||
|
|
||||||
|
builder.build_rules(sym, nested_actual_args(actual_args, parameters, i), rule_counter, lhs_tag, line, rule_builders)
|
||||||
|
end.flatten.compact
|
||||||
|
end
|
||||||
|
|
||||||
|
def nested_actual_args(actual_args, parameters, idx)
|
||||||
|
symbols[idx].args.map do |arg|
|
||||||
|
i = parameters.index { |parameter| parameter.s_value == arg.s_value }
|
||||||
|
i.nil? ? arg : actual_args[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def rhs(token, actual_args, parameters, nested_rule)
|
||||||
|
symbols.map do |sym|
|
||||||
|
if sym.is_a?(Lexer::Token::InstantiateRule)
|
||||||
|
sym.args.map do |arg|
|
||||||
|
idx = parameters.index { |parameter| parameter.s_value == arg.s_value }
|
||||||
|
idx.nil? ? sym : nested_rule&.token
|
||||||
|
end
|
||||||
|
else
|
||||||
|
idx = parameters.index { |parameter| parameter.s_value == sym.s_value }
|
||||||
|
idx.nil? ? sym : actual_args[idx]
|
||||||
|
end
|
||||||
|
end.flatten
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -51,22 +51,14 @@ module Lrama
|
|||||||
freeze_rhs
|
freeze_rhs
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup_rules
|
def setup_rules(parameterizing_resolver)
|
||||||
preprocess_references unless @skip_preprocess_references
|
preprocess_references unless @skip_preprocess_references
|
||||||
process_rhs
|
process_rhs(parameterizing_resolver)
|
||||||
build_rules
|
build_rules
|
||||||
end
|
end
|
||||||
|
|
||||||
def parameterizing_rules
|
|
||||||
@parameterizing_rules
|
|
||||||
end
|
|
||||||
|
|
||||||
def midrule_action_rules
|
|
||||||
@midrule_action_rules
|
|
||||||
end
|
|
||||||
|
|
||||||
def rules
|
def rules
|
||||||
@rules
|
@parameterizing_rules + @midrule_action_rules + @rules
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -95,9 +87,9 @@ module Lrama
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# rhs is a mixture of variety type of tokens like `Ident`, `Parameterizing`, `UserCode` and so on.
|
# rhs is a mixture of variety type of tokens like `Ident`, `InstantiateRule`, `UserCode` and so on.
|
||||||
# `#process_rhs` replaces some kind of tokens to `Ident` so that all `@replaced_rhs` are `Ident` or `Char`.
|
# `#process_rhs` replaces some kind of tokens to `Ident` so that all `@replaced_rhs` are `Ident` or `Char`.
|
||||||
def process_rhs
|
def process_rhs(parameterizing_resolver)
|
||||||
return if @replaced_rhs
|
return if @replaced_rhs
|
||||||
|
|
||||||
@replaced_rhs = []
|
@replaced_rhs = []
|
||||||
@ -109,12 +101,17 @@ module Lrama
|
|||||||
@replaced_rhs << token
|
@replaced_rhs << token
|
||||||
when Lrama::Lexer::Token::Ident
|
when Lrama::Lexer::Token::Ident
|
||||||
@replaced_rhs << token
|
@replaced_rhs << token
|
||||||
when Lrama::Lexer::Token::Parameterizing
|
when Lrama::Lexer::Token::InstantiateRule
|
||||||
|
if parameterizing_resolver.defined?(token.rule_name)
|
||||||
|
parameterizing = parameterizing_resolver.build_rules(token, @rule_counter, @lhs_tag, line)
|
||||||
|
@parameterizing_rules = @parameterizing_rules + parameterizing.map(&:rules).flatten
|
||||||
|
@replaced_rhs = @replaced_rhs + parameterizing.map(&:token).flatten.uniq
|
||||||
|
else
|
||||||
|
# TODO: Delete when the standard library will defined as a grammar file.
|
||||||
parameterizing = ParameterizingRules::Builder.new(token, @rule_counter, @lhs_tag, user_code, precedence_sym, line)
|
parameterizing = ParameterizingRules::Builder.new(token, @rule_counter, @lhs_tag, user_code, precedence_sym, line)
|
||||||
parameterizing.build.each do |r|
|
@parameterizing_rules = @parameterizing_rules + parameterizing.build
|
||||||
@parameterizing_rules << r
|
|
||||||
end
|
|
||||||
@replaced_rhs << parameterizing.build_token
|
@replaced_rhs << parameterizing.build_token
|
||||||
|
end
|
||||||
when Lrama::Lexer::Token::UserCode
|
when Lrama::Lexer::Token::UserCode
|
||||||
prefix = token.referred ? "@" : "$@"
|
prefix = token.referred ? "@" : "$@"
|
||||||
new_token = Lrama::Lexer::Token::Ident.new(s_value: prefix + @midrule_action_counter.increment.to_s)
|
new_token = Lrama::Lexer::Token::Ident.new(s_value: prefix + @midrule_action_counter.increment.to_s)
|
||||||
@ -124,7 +121,7 @@ module Lrama
|
|||||||
rule_builder.lhs = new_token
|
rule_builder.lhs = new_token
|
||||||
rule_builder.user_code = token
|
rule_builder.user_code = token
|
||||||
rule_builder.complete_input
|
rule_builder.complete_input
|
||||||
rule_builder.setup_rules
|
rule_builder.setup_rules(parameterizing_resolver)
|
||||||
|
|
||||||
@rule_builders_for_derived_rules << rule_builder
|
@rule_builders_for_derived_rules << rule_builder
|
||||||
else
|
else
|
||||||
@ -146,8 +143,15 @@ module Lrama
|
|||||||
else
|
else
|
||||||
candidates = rhs.each_with_index.select {|token, i| token.referred_by?(ref_name) }
|
candidates = rhs.each_with_index.select {|token, i| token.referred_by?(ref_name) }
|
||||||
|
|
||||||
raise "Referring symbol `#{ref_name}` is duplicated. #{token}" if candidates.size >= 2
|
if candidates.size >= 2
|
||||||
raise "Referring symbol `#{ref_name}` is not found. #{token}" unless referring_symbol = candidates.first
|
location = token.location.partial_location(ref.first_column, ref.last_column)
|
||||||
|
raise location.generate_error_message("Referring symbol `#{ref_name}` is duplicated.")
|
||||||
|
end
|
||||||
|
|
||||||
|
unless (referring_symbol = candidates.first)
|
||||||
|
location = token.location.partial_location(ref.first_column, ref.last_column)
|
||||||
|
raise location.generate_error_message("Referring symbol `#{ref_name}` is not found.")
|
||||||
|
end
|
||||||
|
|
||||||
ref.index = referring_symbol[1] + 1
|
ref.index = referring_symbol[1] + 1
|
||||||
end
|
end
|
||||||
@ -167,7 +171,7 @@ module Lrama
|
|||||||
end
|
end
|
||||||
|
|
||||||
def flush_user_code
|
def flush_user_code
|
||||||
if c = @user_code
|
if (c = @user_code)
|
||||||
@rhs << c
|
@rhs << c
|
||||||
@user_code = nil
|
@user_code = nil
|
||||||
end
|
end
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
require "strscan"
|
require "strscan"
|
||||||
|
require "lrama/lexer/grammar_file"
|
||||||
require "lrama/lexer/location"
|
require "lrama/lexer/location"
|
||||||
require "lrama/lexer/token"
|
require "lrama/lexer/token"
|
||||||
|
|
||||||
@ -28,10 +29,12 @@ module Lrama
|
|||||||
%error-token
|
%error-token
|
||||||
%empty
|
%empty
|
||||||
%code
|
%code
|
||||||
|
%rule
|
||||||
)
|
)
|
||||||
|
|
||||||
def initialize(text)
|
def initialize(grammar_file)
|
||||||
@scanner = StringScanner.new(text)
|
@grammar_file = grammar_file
|
||||||
|
@scanner = StringScanner.new(grammar_file.text)
|
||||||
@head_column = @head = @scanner.pos
|
@head_column = @head = @scanner.pos
|
||||||
@head_line = @line = 1
|
@head_line = @line = 1
|
||||||
@status = :initial
|
@status = :initial
|
||||||
@ -57,8 +60,9 @@ module Lrama
|
|||||||
|
|
||||||
def location
|
def location
|
||||||
Location.new(
|
Location.new(
|
||||||
|
grammar_file: @grammar_file,
|
||||||
first_line: @head_line, first_column: @head_column,
|
first_line: @head_line, first_column: @head_column,
|
||||||
last_line: @line, last_column: column
|
last_line: line, last_column: column
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -78,8 +82,7 @@ module Lrama
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@head_line = line
|
reset_first_position
|
||||||
@head_column = column
|
|
||||||
|
|
||||||
case
|
case
|
||||||
when @scanner.eos?
|
when @scanner.eos?
|
||||||
@ -117,6 +120,8 @@ module Lrama
|
|||||||
def lex_c_code
|
def lex_c_code
|
||||||
nested = 0
|
nested = 0
|
||||||
code = ''
|
code = ''
|
||||||
|
reset_first_position
|
||||||
|
|
||||||
while !@scanner.eos? do
|
while !@scanner.eos? do
|
||||||
case
|
case
|
||||||
when @scanner.scan(/{/)
|
when @scanner.scan(/{/)
|
||||||
@ -140,14 +145,14 @@ module Lrama
|
|||||||
@line += @scanner.matched.count("\n")
|
@line += @scanner.matched.count("\n")
|
||||||
when @scanner.scan(/'.*?'/)
|
when @scanner.scan(/'.*?'/)
|
||||||
code += %Q(#{@scanner.matched})
|
code += %Q(#{@scanner.matched})
|
||||||
else
|
when @scanner.scan(/[^\"'\{\}\n]+/)
|
||||||
if @scanner.scan(/[^\"'\{\}\n#{@end_symbol}]+/)
|
code += @scanner.matched
|
||||||
|
when @scanner.scan(/#{Regexp.escape(@end_symbol)}/)
|
||||||
code += @scanner.matched
|
code += @scanner.matched
|
||||||
else
|
else
|
||||||
code += @scanner.getch
|
code += @scanner.getch
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
raise ParseError, "Unexpected code: #{code}."
|
raise ParseError, "Unexpected code: #{code}."
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -166,9 +171,14 @@ module Lrama
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reset_first_position
|
||||||
|
@head_line = line
|
||||||
|
@head_column = column
|
||||||
|
end
|
||||||
|
|
||||||
def newline
|
def newline
|
||||||
@line += 1
|
@line += 1
|
||||||
@head = @scanner.pos + 1
|
@head = @scanner.pos
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
21
tool/lrama/lib/lrama/lexer/grammar_file.rb
Normal file
21
tool/lrama/lib/lrama/lexer/grammar_file.rb
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
module Lrama
|
||||||
|
class Lexer
|
||||||
|
class GrammarFile
|
||||||
|
attr_reader :path, :text
|
||||||
|
|
||||||
|
def initialize(path, text)
|
||||||
|
@path = path
|
||||||
|
@text = text
|
||||||
|
end
|
||||||
|
|
||||||
|
def ==(other)
|
||||||
|
self.class == other.class &&
|
||||||
|
self.path == other.path
|
||||||
|
end
|
||||||
|
|
||||||
|
def lines
|
||||||
|
@lines ||= text.split("\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,9 +1,10 @@
|
|||||||
module Lrama
|
module Lrama
|
||||||
class Lexer
|
class Lexer
|
||||||
class Location
|
class Location
|
||||||
attr_reader :first_line, :first_column, :last_line, :last_column
|
attr_reader :grammar_file, :first_line, :first_column, :last_line, :last_column
|
||||||
|
|
||||||
def initialize(first_line:, first_column:, last_line:, last_column:)
|
def initialize(grammar_file:, first_line:, first_column:, last_line:, last_column:)
|
||||||
|
@grammar_file = grammar_file
|
||||||
@first_line = first_line
|
@first_line = first_line
|
||||||
@first_column = first_column
|
@first_column = first_column
|
||||||
@last_line = last_line
|
@last_line = last_line
|
||||||
@ -12,11 +13,85 @@ module Lrama
|
|||||||
|
|
||||||
def ==(other)
|
def ==(other)
|
||||||
self.class == other.class &&
|
self.class == other.class &&
|
||||||
|
self.grammar_file == other.grammar_file &&
|
||||||
self.first_line == other.first_line &&
|
self.first_line == other.first_line &&
|
||||||
self.first_column == other.first_column &&
|
self.first_column == other.first_column &&
|
||||||
self.last_line == other.last_line &&
|
self.last_line == other.last_line &&
|
||||||
self.last_column == other.last_column
|
self.last_column == other.last_column
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def partial_location(left, right)
|
||||||
|
offset = -first_column
|
||||||
|
new_first_line = -1
|
||||||
|
new_first_column = -1
|
||||||
|
new_last_line = -1
|
||||||
|
new_last_column = -1
|
||||||
|
|
||||||
|
_text.each.with_index do |line, index|
|
||||||
|
new_offset = offset + line.length + 1
|
||||||
|
|
||||||
|
if offset <= left && left <= new_offset
|
||||||
|
new_first_line = first_line + index
|
||||||
|
new_first_column = left - offset
|
||||||
|
end
|
||||||
|
|
||||||
|
if offset <= right && right <= new_offset
|
||||||
|
new_last_line = first_line + index
|
||||||
|
new_last_column = right - offset
|
||||||
|
end
|
||||||
|
|
||||||
|
offset = new_offset
|
||||||
|
end
|
||||||
|
|
||||||
|
Location.new(
|
||||||
|
grammar_file: grammar_file,
|
||||||
|
first_line: new_first_line, first_column: new_first_column,
|
||||||
|
last_line: new_last_line, last_column: new_last_column
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
"#{path} (#{first_line},#{first_column})-(#{last_line},#{last_column})"
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_error_message(error_message)
|
||||||
|
<<~ERROR.chomp
|
||||||
|
#{path}:#{first_line}:#{first_column}: #{error_message}
|
||||||
|
#{line_with_carets}
|
||||||
|
ERROR
|
||||||
|
end
|
||||||
|
|
||||||
|
def line_with_carets
|
||||||
|
<<~TEXT
|
||||||
|
#{text}
|
||||||
|
#{carets}
|
||||||
|
TEXT
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def path
|
||||||
|
grammar_file.path
|
||||||
|
end
|
||||||
|
|
||||||
|
def blanks
|
||||||
|
(text[0...first_column] or raise "#{first_column} is invalid").gsub(/[^\t]/, ' ')
|
||||||
|
end
|
||||||
|
|
||||||
|
def carets
|
||||||
|
blanks + '^' * (last_column - first_column)
|
||||||
|
end
|
||||||
|
|
||||||
|
def text
|
||||||
|
@text ||= _text.join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def _text
|
||||||
|
@_text ||=begin
|
||||||
|
range = (first_line - 1)...last_line
|
||||||
|
grammar_file.lines[range] or raise "#{range} is invalid"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
require 'lrama/lexer/token/char'
|
require 'lrama/lexer/token/char'
|
||||||
require 'lrama/lexer/token/ident'
|
require 'lrama/lexer/token/ident'
|
||||||
require 'lrama/lexer/token/parameterizing'
|
require 'lrama/lexer/token/instantiate_rule'
|
||||||
require 'lrama/lexer/token/tag'
|
require 'lrama/lexer/token/tag'
|
||||||
require 'lrama/lexer/token/user_code'
|
require 'lrama/lexer/token/user_code'
|
||||||
|
|
||||||
|
18
tool/lrama/lib/lrama/lexer/token/instantiate_rule.rb
Normal file
18
tool/lrama/lib/lrama/lexer/token/instantiate_rule.rb
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
module Lrama
|
||||||
|
class Lexer
|
||||||
|
class Token
|
||||||
|
class InstantiateRule < Token
|
||||||
|
attr_accessor :args
|
||||||
|
|
||||||
|
def initialize(s_value:, alias_name: nil, location: nil, args: [])
|
||||||
|
super s_value: s_value, alias_name: alias_name, location: location
|
||||||
|
@args = args
|
||||||
|
end
|
||||||
|
|
||||||
|
def rule_name
|
||||||
|
s_value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,34 +0,0 @@
|
|||||||
module Lrama
|
|
||||||
class Lexer
|
|
||||||
class Token
|
|
||||||
class Parameterizing < Token
|
|
||||||
attr_accessor :args
|
|
||||||
|
|
||||||
def initialize(s_value:, alias_name: nil, location: nil, args: [])
|
|
||||||
super s_value: s_value, alias_name: alias_name, location: location
|
|
||||||
@args = args
|
|
||||||
end
|
|
||||||
|
|
||||||
def option?
|
|
||||||
%w(option ?).include?(self.s_value)
|
|
||||||
end
|
|
||||||
|
|
||||||
def nonempty_list?
|
|
||||||
%w(nonempty_list +).include?(self.s_value)
|
|
||||||
end
|
|
||||||
|
|
||||||
def list?
|
|
||||||
%w(list *).include?(self.s_value)
|
|
||||||
end
|
|
||||||
|
|
||||||
def separated_nonempty_list?
|
|
||||||
%w(separated_nonempty_list).include?(self.s_value)
|
|
||||||
end
|
|
||||||
|
|
||||||
def separated_list?
|
|
||||||
%w(separated_list).include?(self.s_value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -35,27 +35,27 @@ module Lrama
|
|||||||
# It need to wrap an identifier with brackets to use ".-" for identifiers
|
# It need to wrap an identifier with brackets to use ".-" for identifiers
|
||||||
when scanner.scan(/\$(<[a-zA-Z0-9_]+>)?\$/) # $$, $<long>$
|
when scanner.scan(/\$(<[a-zA-Z0-9_]+>)?\$/) # $$, $<long>$
|
||||||
tag = scanner[1] ? Lrama::Lexer::Token::Tag.new(s_value: scanner[1]) : nil
|
tag = scanner[1] ? Lrama::Lexer::Token::Tag.new(s_value: scanner[1]) : nil
|
||||||
return Lrama::Grammar::Reference.new(type: :dollar, name: "$", ex_tag: tag, first_column: start, last_column: scanner.pos - 1)
|
return Lrama::Grammar::Reference.new(type: :dollar, name: "$", ex_tag: tag, first_column: start, last_column: scanner.pos)
|
||||||
when scanner.scan(/\$(<[a-zA-Z0-9_]+>)?(\d+)/) # $1, $2, $<long>1
|
when scanner.scan(/\$(<[a-zA-Z0-9_]+>)?(\d+)/) # $1, $2, $<long>1
|
||||||
tag = scanner[1] ? Lrama::Lexer::Token::Tag.new(s_value: scanner[1]) : nil
|
tag = scanner[1] ? Lrama::Lexer::Token::Tag.new(s_value: scanner[1]) : nil
|
||||||
return Lrama::Grammar::Reference.new(type: :dollar, index: Integer(scanner[2]), ex_tag: tag, first_column: start, last_column: scanner.pos - 1)
|
return Lrama::Grammar::Reference.new(type: :dollar, index: Integer(scanner[2]), ex_tag: tag, first_column: start, last_column: scanner.pos)
|
||||||
when scanner.scan(/\$(<[a-zA-Z0-9_]+>)?([a-zA-Z_][a-zA-Z0-9_]*)/) # $foo, $expr, $<long>program (named reference without brackets)
|
when scanner.scan(/\$(<[a-zA-Z0-9_]+>)?([a-zA-Z_][a-zA-Z0-9_]*)/) # $foo, $expr, $<long>program (named reference without brackets)
|
||||||
tag = scanner[1] ? Lrama::Lexer::Token::Tag.new(s_value: scanner[1]) : nil
|
tag = scanner[1] ? Lrama::Lexer::Token::Tag.new(s_value: scanner[1]) : nil
|
||||||
return Lrama::Grammar::Reference.new(type: :dollar, name: scanner[2], ex_tag: tag, first_column: start, last_column: scanner.pos - 1)
|
return Lrama::Grammar::Reference.new(type: :dollar, name: scanner[2], ex_tag: tag, first_column: start, last_column: scanner.pos)
|
||||||
when scanner.scan(/\$(<[a-zA-Z0-9_]+>)?\[([a-zA-Z_.][-a-zA-Z0-9_.]*)\]/) # $expr.right, $expr-right, $<long>program (named reference with brackets)
|
when scanner.scan(/\$(<[a-zA-Z0-9_]+>)?\[([a-zA-Z_.][-a-zA-Z0-9_.]*)\]/) # $[expr.right], $[expr-right], $<long>[expr.right] (named reference with brackets)
|
||||||
tag = scanner[1] ? Lrama::Lexer::Token::Tag.new(s_value: scanner[1]) : nil
|
tag = scanner[1] ? Lrama::Lexer::Token::Tag.new(s_value: scanner[1]) : nil
|
||||||
return Lrama::Grammar::Reference.new(type: :dollar, name: scanner[2], ex_tag: tag, first_column: start, last_column: scanner.pos - 1)
|
return Lrama::Grammar::Reference.new(type: :dollar, name: scanner[2], ex_tag: tag, first_column: start, last_column: scanner.pos)
|
||||||
|
|
||||||
# @ references
|
# @ references
|
||||||
# It need to wrap an identifier with brackets to use ".-" for identifiers
|
# It need to wrap an identifier with brackets to use ".-" for identifiers
|
||||||
when scanner.scan(/@\$/) # @$
|
when scanner.scan(/@\$/) # @$
|
||||||
return Lrama::Grammar::Reference.new(type: :at, name: "$", first_column: start, last_column: scanner.pos - 1)
|
return Lrama::Grammar::Reference.new(type: :at, name: "$", first_column: start, last_column: scanner.pos)
|
||||||
when scanner.scan(/@(\d+)/) # @1
|
when scanner.scan(/@(\d+)/) # @1
|
||||||
return Lrama::Grammar::Reference.new(type: :at, index: Integer(scanner[1]), first_column: start, last_column: scanner.pos - 1)
|
return Lrama::Grammar::Reference.new(type: :at, index: Integer(scanner[1]), first_column: start, last_column: scanner.pos)
|
||||||
when scanner.scan(/@([a-zA-Z][a-zA-Z0-9_]*)/) # @foo, @expr (named reference without brackets)
|
when scanner.scan(/@([a-zA-Z][a-zA-Z0-9_]*)/) # @foo, @expr (named reference without brackets)
|
||||||
return Lrama::Grammar::Reference.new(type: :at, name: scanner[1], first_column: start, last_column: scanner.pos - 1)
|
return Lrama::Grammar::Reference.new(type: :at, name: scanner[1], first_column: start, last_column: scanner.pos)
|
||||||
when scanner.scan(/@\[([a-zA-Z_.][-a-zA-Z0-9_.]*)\]/) # @expr.right, @expr-right (named reference with brackets)
|
when scanner.scan(/@\[([a-zA-Z_.][-a-zA-Z0-9_.]*)\]/) # @[expr.right], @[expr-right] (named reference with brackets)
|
||||||
return Lrama::Grammar::Reference.new(type: :at, name: scanner[1], first_column: start, last_column: scanner.pos - 1)
|
return Lrama::Grammar::Reference.new(type: :at, name: scanner[1], first_column: start, last_column: scanner.pos)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -156,7 +156,7 @@ module Lrama
|
|||||||
nl = true
|
nl = true
|
||||||
end
|
end
|
||||||
|
|
||||||
if r = state.default_reduction_rule
|
if (r = state.default_reduction_rule)
|
||||||
nl = true
|
nl = true
|
||||||
s = "$default".ljust(max_len)
|
s = "$default".ljust(max_len)
|
||||||
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
module Lrama
|
module Lrama
|
||||||
VERSION = "0.5.12".freeze
|
VERSION = "0.6.0".freeze
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user