Skip to content

Commit

Permalink
Add initial Crystal-based back end.
Browse files Browse the repository at this point in the history
  • Loading branch information
DanilaFe committed Nov 11, 2018
1 parent 0ac987d commit 9977f32
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 0 deletions.
2 changes: 2 additions & 0 deletions shard.yml
Expand Up @@ -13,6 +13,8 @@ targets:
main: src/sim/pegasus_sim.cr
pegasus-c:
main: src/c/pegasus_c.cr
pegasus-crystal:
main: src/crystal/pegasus_crystal.cr

crystal: 0.26.1

Expand Down
38 changes: 38 additions & 0 deletions src/crystal/pegasus_crystal.cr
@@ -0,0 +1,38 @@
require "../pegasus/language_def.cr"
require "../pegasus/json.cr"
require "option_parser"
require "ecr"

module Pegasus
module Language
class LanguageData
def output(io, prefix)
ECR.embed "src/crystal/pegasus_crystal_template.ecr", io
end
end
end
end

prefix = "Pegasus::Generated"
file_name = "parser"
stdout = false

OptionParser.parse! do |parser|
parser.banner = "Usage: pegasus-crystal [arguments]"
parser.on("-S", "--standard-out", "Combines the header and implementation files, and prints to standard out") { stdout = true }
parser.on("-P PREFIX", "--prefix PREFIX", "Specify the prefix for generated code") do |p|
prefix = p
end
parser.on("-f FILE", "--file-name=FILE", "Sets output file name") { |file| file_name = file }
parser.on("-h", "--help", "Displays this message") { puts parser }
end

data = Pegasus::Language::LanguageData.from_json STDIN
if stdout
data.output STDOUT, prefix
else
file = File.open(file_name + ".cr", mode = "w")
data.output file, prefix
file.close
end

130 changes: 130 additions & 0 deletions src/crystal/pegasus_crystal_template.ecr
@@ -0,0 +1,130 @@
module Pegasus::Generated
MAX_TERMINAL = <%= @max_terminal %>
LEX_FINAL_TABLE = [ <% @lex_final_table.each do |final| %> <%= final %>_i64, <% end %> ]
LEX_STATE_TABLE = [ <% @lex_state_table.each do |state| %>
[ <% state.each do |transition| %> <%= transition %>_i64, <% end %> ],<%- end %>
]
PARSE_ACTION_TABLE = [ <% @parse_action_table.each do |state| %>
[ <% state.each do |transition| %> <%= transition %>_i64, <% end %> ],<%- end %>
]
PARSE_STATE_TABLE = [ <% @parse_state_table.each do |state| %>
[ <% state.each do |transition| %> <%= transition %>_i64, <% end %> ],<%- end %>
]
ITEMS = <% if @items.size == 0 %> [] of Tuple(Int64, Int64) <% else %> [ <% @items.each do |item| %>
{ <%= item.head.id %>_i64, <%= item.body.size %>_i64 },<%- end %>
] <%- end %>

extend self

abstract class Tree
abstract def table_index
end

class NonterminalTree < Tree
getter nonterminal_id : Int64
getter children : Array(Tree)

def initialize(@nonterminal_id, @children = [] of Tree)
end

def table_index
nonterminal_id + 1 + MAX_TERMINAL + 1
end

def name
case nonterminal_id<% @nonterminals.each do |nt| %>
when <%= nt[1].id %>_i64
<%= nt[0].dump -%>
<%- end %>
else
"???"
end
end
end

class TerminalTree < Tree
getter terminal_id : Int64
getter string : String

def initialize(@terminal_id, @string)
end

def table_index
terminal_id + 1
end
end

class Token
getter terminal_id : Int64
getter string : String

def initialize(@terminal_id, @string)
end
end

def lex(string)
index = 0
tokens = [] of Token
bytes = string.bytes

while index < bytes.size
start_index = index
last_match_index = -1
last_pattern = -1_i64
state = 1

while index < bytes.size
state = LEX_STATE_TABLE[state][bytes[index]]
id = LEX_FINAL_TABLE[state]

break if state == 0
next if id == 0

last_match_index = index
last_pattern = id
index += 1
end

raise "Invalid character at position #{index}" if last_match_index == -1
tokens << Token.new(last_pattern - 1, string[start_index..last_match_index])
end

return tokens
end

def parse(tokens)
tree_stack = [ ] of Tree
state_stack = [ 1_i64 ]
index = 0

while true
break if tree_stack.last?.try(&.as?(NonterminalTree)).try(&.nonterminal_id) == 0
token = tokens[index]?
action = PARSE_ACTION_TABLE[state_stack.last][token.try(&.terminal_id.+(1)) || 0_i64]
raise "Invalid token #{token.try &.string}" if action == -1

if action == 0
raise "Unexpected end of file" unless token
tree_stack << TerminalTree.new token.terminal_id, token.string
index += 1
else
item = ITEMS[action - 1]
tree = NonterminalTree.new item[0]

item[1].times do
tree.children.insert 0, tree_stack.pop
state_stack.pop
end

tree_stack << tree
end

state_stack << PARSE_STATE_TABLE[state_stack.last][tree_stack.last.table_index]
end
return tree_stack.last
end

def process(string)
parse(lex(string))
end
end

0 comments on commit 9977f32

Please sign in to comment.