#!/usr/bin/ruby class Tokenizer TOKEN_TYPES = [ [:function, /\bfunction\b/i], [:sub, /\bsub\b/i], [:end, /\bend\b/i], [:as, /\bas\b/i], [:typename, /\blong\b/i], [:identifier, /\b[a-zA-Z]+\b/], [:integer, /\b[0-9]+\b/], [:string, /".*"/], [:oparen, /\(/], [:cparen, /\)/], [:comma, /,/], [:quote, /'/], ] def initialize(code) @code = code end def tokenize tokens = [] begin until @code.empty? tokens << tokenize_one_token @code = @code.strip end rescue RuntimeError => e puts tokens.join("\n") raise end tokens end def tokenize_one_token TOKEN_TYPES.each do |type, re| re = /\A(#{re})/ if @code =~ re value = $1 @code = @code[value.length..-1] return Token.new(type, value) end end raise RuntimeError.new( "Couldn't match token on #{@code.inspect}") end end Token = Struct.new(:type, :value) class Parser def initialize(tokens) @tokens = tokens end def parse parse_function end def parse_function consume(:function) name = consume(:identifier).value arg_names = parse_arg_names consume(:as) rtype = consume(:typename).value body = parse_expr consume(:end) consume(:function) FunctionNode.new(name, rtype, arg_names, body) end def parse_arg_names arg_names = [] consume(:oparen) if peek(:identifier) arg_names << consume(:identifier).value while peek(:comma) consume(:comma) arg_names << consume(:identifier).value end end consume(:cparen) arg_names end def parse_expr if peek(:integer) parse_integer elsif peek(:string) parse_string elsif peek(:identifier) && peek(:oparen, 1) parse_call elsif peek(:identifier) && peek(:string, 1) parse_stmt else parse_var_ref end end def parse_stmt name = consume(:identifier).value arg_exprs = consume(:string).value CallNode.new(name, arg_exprs) end def peek(expected_type, offset=0) @tokens.fetch(offset).type == expected_type end def consume(expected_type) token = @tokens.shift if token.type == expected_type token else raise RuntimeError.new( "Expected token type #{expected_type.inspect} but got #{token.type.inspect}") end end end FunctionNode = Struct.new(:name, :type, :arg_names, :body) StringNode = Struct.new(:value) CallNode = Struct.new(:name, :arg_exprs) class Generator def generate(node) case node when FunctionNode "%s %s(%s) { return %s ; }" % [ node.type.downcase, node.name, node.arg_names.join(','), generate(node.body), ] when CallNode "%s(%s)" % [ node.name, node.arg_exprs ] when StringNode node.value else raise RuntimeError.new("Unexpected node type: #{node.class}") end end end tokens = Tokenizer.new(File.read("hello.bas")).tokenize #puts "Tokens:\n" #puts tokens.join("\n") tree = Parser.new(tokens).parse #puts "\nAST:\n" #puts tree RUNTIME = "#include \n#define PRINT(a) printf(a)\n" CMAIN = "int main(void) { PBMain(); return 0; }" generated = Generator.new.generate(tree) #puts "\nGenerated:\n" #puts generated #puts "\nGenerated with preamble/postamble:\n" puts [RUNTIME, generated, CMAIN].join("\n")