Add lexer modes and $mode() code expansion

This commit is contained in:
josh committed 2022-10-09 22:49:01 -04:00
1 parent b2d11321fe
commit 02be6de48e
6 files changed
+145 -38

No files matched your search

+18
View File
@@ -26,6 +26,17 @@ class Propane
private
def process_grammar!
# Assign default pattern mode to patterns without a mode assigned.
found_default = false
@grammar.patterns.each do |pattern|
if pattern.mode.nil?
pattern.mode = "default"
found_default = true
end
end
unless found_default
raise Error.new("No patterns found for default mode")
end
# Add EOF token.
@grammar.tokens << Token.new("$EOF", nil)
tokens_by_name = {}
@@ -152,6 +163,13 @@ class Propane
def expand_code(code)
code.gsub(/\$token\(([$\w]+)\)/) do |match|
"TOKEN_#{Token.code_name($1)}"
end.gsub(/\$mode\(([a-zA-Z_][a-zA-Z_0-9]*)\)/) do |match|
mode_name = $1
mode_id = @lexer.mode_id(mode_name)
unless mode_id
raise Error.new("Lexer mode '#{mode_name}' not found")
end
"m_mode = #{mode_id}u"
end
end
+57 -34
View File
@@ -1,51 +1,74 @@
class Propane
class Lexer
# @return [DFA]
# Lexer DFA.
attr_accessor :dfa
def initialize(grammar)
@grammar = grammar
@dfa = DFA.new(grammar.patterns)
end
def build_tables
@modes = @grammar.patterns.group_by do |pattern|
pattern.mode
end.transform_values do |patterns|
{dfa: DFA.new(patterns)}
end
@modes.each_with_index do |(mode_name, mode_info), index|
mode_info[:id] = index
end
transition_table = []
state_table = []
states = @dfa.enumerate
states.each do |state, id|
token =
if state.accepts.nil?
@grammar.tokens.size
elsif state.accepts.drop?
TOKEN_DROP
elsif state.accepts.token
state.accepts.token.id
else
@grammar.tokens.size
end
code_id =
if state.accepts && state.accepts.code_id
state.accepts.code_id
else
0xFFFF_FFFF
end
state_table << {
transition_table_index: transition_table.size,
n_transitions: state.transitions.size,
token: token,
code_id: code_id,
mode_table = []
@modes.each do |mode_name, mode_info|
state_table_offset = state_table.size
mode_table << {
state_table_offset: state_table_offset,
}
state.transitions.each do |transition|
transition_table << {
first: transition.code_point_range.first,
last: transition.code_point_range.last,
destination: states[transition.destination],
states = mode_info[:dfa].enumerate
states.each do |state, id|
token =
if state.accepts.nil?
@grammar.tokens.size
elsif state.accepts.drop?
TOKEN_DROP
elsif state.accepts.token
state.accepts.token.id
else
@grammar.tokens.size
end
code_id =
if state.accepts && state.accepts.code_id
state.accepts.code_id
else
0xFFFF_FFFF
end
state_table << {
transition_table_index: transition_table.size,
n_transitions: state.transitions.size,
token: token,
code_id: code_id,
}
state.transitions.each do |transition|
transition_table << {
first: transition.code_point_range.first,
last: transition.code_point_range.last,
destination: states[transition.destination] + state_table_offset,
}
end
end
end
[transition_table, state_table]
[transition_table, state_table, mode_table]
end
# Get ID for a mode.
#
# @param mode_name [String]
# Mode name.
#
# @return [Integer, nil]
# Mode ID.
def mode_id(mode_name)
if mode_info = @modes[mode_name]
mode_info[:id]
end
end
end
+1 -1
View File
@@ -28,7 +28,7 @@ class Propane
# @return [String, nil]
# Lexer mode for this pattern.
attr_reader :mode
attr_accessor :mode
# Construct a Pattern.
#