Permalink
Browse files

First very simple grammar draft, based on kschiess/wt

  • Loading branch information...
1 parent 45f4c41 commit d7fe5319ea403222cc2b6eebe295174de0a3482d @txus committed Apr 21, 2011
View
@@ -1,3 +1,8 @@
module Brainscript
- # Your code goes here...
end
+
+require 'brainscript/ast'
+require 'brainscript/parser'
+require 'brainscript/transform'
+
+require 'brainscript/compiler'
View
@@ -0,0 +1,17 @@
+# AST nodes
+module Brainscript::AST
+ class IntLit < Struct.new(:int)
+ def compile
+ int
+ end
+ end
+
+ class Expression < Struct.new(:left, :op, :right)
+ def compile
+ left.compile.send(op, right.compile)
+ end
+ end
+
+ class Assign < Struct.new(:ident, :exp)
+ end
+end
@@ -0,0 +1,23 @@
+# The entry point and main controller.
+#
+class Brainscript::Compiler
+
+ # Compiles source and returns compilation result.
+ #
+ # @param <String> the source code.
+ def compile(source)
+ ast = transformer.apply(parser.parse(source))
+ ast.compile
+ end
+
+ private
+
+ def parser
+ Brainscript::Parser.new
+ end
+
+ def transformer
+ Brainscript::Transform.new
+ end
+
+end
View
@@ -0,0 +1,44 @@
+require 'parslet'
+
+class Brainscript::Parser < Parslet::Parser
+
+ root(:expression)
+ rule(:expression) { assignment | aexpression }
+
+ rule(:assignment) { identifier >> s('=') >> expression.as(:exp) }
+
+ rule(:aexpression) {
+ mexpression.as(:left) >> c('+-', :op) >> expression.as(:right) |
+ mexpression
+ }
+ rule(:mexpression) {
+ atom.as(:left) >> c('*/', :op) >> mexpression.as(:right) |
+ atom }
+ rule(:atom) { integer | s('(') >> expression >> s(')') }
+
+ rule(:identifier) {
+ (match['a-z'] >> match['\w\d'].repeat).as(:ident) >> space?
+ }
+ rule(:integer) { c('0-9', :int) }
+
+ rule(:space?) { space.maybe }
+ rule(:space) { match['\\s'].repeat }
+
+ private
+
+ # Defines a string followed by any number of spaces.
+ #
+ def s(str)
+ str(str) >> space?
+ end
+
+ # Defines a set of characters followed by any number of spaces.
+ #
+ def c(chars, name=nil)
+ if name
+ match[chars].as(name) >> space?
+ else
+ match[chars] >> space?
+ end
+ end
+end
@@ -0,0 +1,10 @@
+class Brainscript::Transform < Parslet::Transform
+ include Brainscript::AST
+
+ rule(int: simple(:int)) {
+ IntLit.new(Integer(int)) }
+ rule(left: simple(:left), op: simple(:op), right: simple(:right)) {
+ Expression.new(left, op, right) }
+ rule(ident: simple(:ident), exp: simple(:exp)) {
+ Assign.new(ident, exp) }
+end
@@ -0,0 +1,9 @@
+require 'test_helper'
+
+class CompilerTest < MiniTest::Unit::TestCase
+
+ def test_simple_addition
+ assert_evaluates 3, '1 + 2'
+ end
+
+end
@@ -0,0 +1,19 @@
+require 'test_helper'
+
+class ParserTest < MiniTest::Unit::TestCase
+
+ def test_expression
+ tokenizes '1+2', as: {left: {int: '1'}, right: {int: '2'}, op: '+'}
+ tokenizes '1-2', as: {left: {int: '1'}, right: {int: '2'}, op: '-'}
+ tokenizes '1 * 2', as: {left: {int: '1'}, right: {int: '2'}, op: '*'}
+ tokenizes '1 / 2', as: {left: {int: '1'}, right: {int: '2'}, op: '/'}
+
+ tokenizes '1*2 + 3'
+ tokenizes '1 * (1 + 3)'
+ end
+
+ def test_assignment
+ tokenizes 'a = 1', as: {ident: 'a', exp: {int: '1'}}
+ end
+
+end
@@ -0,0 +1,19 @@
+require 'test_helper'
+
+class TransformTest < MiniTest::Unit::TestCase
+
+ include Brainscript::AST
+
+ def test_integer_literals
+ assert_equal IntLit.new(1), transform(int: 1)
+ end
+
+ def test_arithmetic_expressions
+ assert_equal Expression.new('l', '+', 'r'), transform(left: 'l', op: '+', right: 'r')
+ end
+
+ def test_assignments
+ assert_equal Assign.new('a', 'e'), transform(ident: 'a', exp: 'e')
+ end
+
+end
View
@@ -1,9 +0,0 @@
-require 'test_helper'
-
-class BrainscriptTest < MiniTest::Unit::TestCase
-
- def test_assert_works
- assert true
- end
-
-end
View
@@ -3,3 +3,52 @@
require 'mocha'
require 'brainscript'
+
+module CompilerAssertions
+
+ def assert_evaluates(expected, code)
+ assert_equal expected, compiler.compile(code)
+ end
+
+ private
+
+ def compiler
+ Brainscript::Compiler.new
+ end
+end
+
+module ParserAssertions
+
+ def tokenizes(input, options = {})
+ as = options.delete(:as)
+ caller.first.match /`(.*)\'/
+ rule = $1.split("_")[1..-1].join("_").to_sym
+
+ im = parser.send(rule).parse(input)
+
+ as.nil? ? refute_nil(im)
+ : assert_equal(as, im)
+ rescue Parslet::ParseFailed
+ flunk "#{rule} rule could not tokenize #{input}"
+ end
+
+ private
+
+ def parser
+ Brainscript::Parser.new
+ end
+end
+
+module TransformAssertions
+
+ def transform(tree)
+ Brainscript::Transform.new.apply(tree)
+ end
+
+end
+
+class MiniTest::Unit::TestCase
+ include CompilerAssertions
+ include ParserAssertions
+ include TransformAssertions
+end

0 comments on commit d7fe531

Please sign in to comment.