diff --git a/hello.bas b/hello.bas new file mode 100644 index 0000000..2e8998b --- /dev/null +++ b/hello.bas @@ -0,0 +1,3 @@ +Function PBMain() as Long + PRINT "Hello, world!" +End Function diff --git a/neopb.c b/neopb.c new file mode 100644 index 0000000..a90fec7 --- /dev/null +++ b/neopb.c @@ -0,0 +1,6 @@ +#include + +int parse(); + +int main() { +} diff --git a/pb2c.rb b/pb2c.rb new file mode 100755 index 0000000..c9d43b1 --- /dev/null +++ b/pb2c.rb @@ -0,0 +1,162 @@ +#!/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") +