diff --git a/Gemfile b/Gemfile index 0f5983a..1addd3e 100644 --- a/Gemfile +++ b/Gemfile @@ -2,3 +2,4 @@ source "http://rubygems.org" # Specify your gem's dependencies in schemer.gemspec gemspec +gem 'parslet', :git => 'git://github.com/txus/parslet', :branch => 'improve_rspec_matcher' diff --git a/Gemfile.lock b/Gemfile.lock index 55d483e..72f5c1f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,8 +1,15 @@ +GIT + remote: git://github.com/txus/parslet + revision: 4c7962d1cf4425674099962fb6f51c3b5b057533 + branch: improve_rspec_matcher + specs: + parslet (1.1.1) + blankslate (~> 2.0) + PATH remote: . specs: schemer (0.0.1) - parslet GEM remote: http://rubygems.org/ @@ -20,8 +27,6 @@ GEM rake (>= 0.8.1) open_gem (1.4.2) launchy (~> 0.3.5) - parslet (1.1.1) - blankslate (~> 2.0) rake (0.8.7) rb-fsevent (0.3.10) rspec (2.5.0) @@ -40,6 +45,7 @@ PLATFORMS DEPENDENCIES guard guard-rspec + parslet! rb-fsevent rspec (~> 2.5.0) schemer! diff --git a/examples/bintree.scm b/examples/bintree.scm new file mode 100644 index 0000000..2e1ae8b --- /dev/null +++ b/examples/bintree.scm @@ -0,0 +1,44 @@ +; Abstraction of a binary tree. Each tree is recursively defined as a list +; with the entry (data), left subtree and right subtree. Left and right can +; be null. +; +(define (entry tree) (car tree)) +(define (left-branch tree) (cadr tree)) +(define (right-branch tree) (caddr tree)) +(define (make-tree entry left right) + (list entry left right)) + +(define (make-new-set) + '()) + +(define (element-of-set? x set) + (cond + ((null? set) #f) + ((= x (entry set)) #t) + ((< x (entry set)) (element-of-set? x (left-branch set))) + ((> x (entry set)) (element-of-set? x (right-branch set))))) + +(define (adjoin-set x set) + (cond + ((null? set) (make-tree x '() '())) + ((= x (entry set)) set) + ((< x (entry set)) + (make-tree (entry set) + (adjoin-set x (left-branch set)) + (right-branch set))) + ((> x (entry set)) + (make-tree (entry set) + (left-branch set) + (adjoin-set x (right-branch set)))))) + + +(define myset + (adjoin-set 25 + (adjoin-set 13 + (adjoin-set 72 + (adjoin-set 4 (make-new-set)))))) + +(write (element-of-set? 4 myset)) +(write (element-of-set? 5 myset)) +(write (element-of-set? 26 myset)) +(write (element-of-set? 25 myset)) diff --git a/examples/closure.scm b/examples/closure.scm new file mode 100644 index 0000000..6c5ff5f --- /dev/null +++ b/examples/closure.scm @@ -0,0 +1,13 @@ +(define (make-withdraw balance) + (lambda (amount) + (if (>= balance amount) + (begin (set! balance (- balance amount)) + balance) + 'no-funds))) + +(define W1 (make-withdraw 100)) +(define W2 (make-withdraw 500)) +(write (W1 20)) +(write (W2 30)) +(write (W1 80)) +(write (W2 100)) diff --git a/examples/equality.scm b/examples/equality.scm new file mode 100644 index 0000000..28cea00 --- /dev/null +++ b/examples/equality.scm @@ -0,0 +1,13 @@ +(write (eqv? #f #t)) +(write (eqv? #f #f)) +(write (eqv? '() '())) +(write (eqv? 5 (+ 1 4))) +(write (eqv? 6 #f)) + +(define zara 'zara) +(write (eqv? zara 'zara)) +(write (eqv? 'zara 'zara)) + +(define joe '(1 2 3)) +(write (eqv? joe '(1 2 3))) +(write (eqv? joe joe)) diff --git a/examples/func.scm b/examples/func.scm new file mode 100644 index 0000000..3ccb9e2 --- /dev/null +++ b/examples/func.scm @@ -0,0 +1,13 @@ +(define (map proc lst) + (if (null? lst) + '() + (cons (proc (car lst)) (map proc (cdr lst))))) + +(define (filter proc lst) + (cond + ((null? lst) '()) + ((proc (car lst)) (cons (car lst) (filter proc (cdr lst)))) + (else (filter proc (cdr lst))))) + +(write (map (lambda (x) (* x x)) '(1 2 3 4))) +(write (filter (lambda (x) (> x 2)) '(1 2 3 4))) diff --git a/examples/let.scm b/examples/let.scm new file mode 100644 index 0000000..954d6f9 --- /dev/null +++ b/examples/let.scm @@ -0,0 +1,5 @@ +(let ((x 2) (y 3)) + (let ((x 7) (z 8)) + (write (+ x z)) + (write (+ x y))) + (write (+ x y))) diff --git a/examples/list.scm b/examples/list.scm new file mode 100644 index 0000000..754aacb --- /dev/null +++ b/examples/list.scm @@ -0,0 +1,29 @@ +(define (list-length lst) + (if (null? lst) + 0 + (+ 1 (list-length (cdr lst))))) + +(define (append lst1 lst2) + (if (null? lst1) + lst2 + (cons (car lst1) (append (cdr lst1) lst2)))) + +(define (list-reverse lst) + (if (null? lst) + '() + (append (list-reverse (cdr lst)) (list (car lst)) ))) + +; Takes a list and returns a list of (cons elem elem) for each elem in the +; given list. +; +(define (pairify lst) + (if (null? lst) + '() + (cons + (cons (car lst) (car lst)) + (pairify (cdr lst))))) + +(write (list-length (list 1 2 3 4 5))) +(write (append '(1 2 3 4) (list 9))) +(write (list-reverse (list 1 2 3 4 5))) +(write (pairify (list 1 2 3 4))) diff --git a/examples/queue.scm b/examples/queue.scm new file mode 100644 index 0000000..64d6831 --- /dev/null +++ b/examples/queue.scm @@ -0,0 +1,47 @@ +; queue data structure example from SICP 3.3.2 +; +(define (front-ptr queue) (car queue)) +(define (rear-ptr queue) (cdr queue)) +(define (set-front-ptr! queue item) (set-car! queue item)) +(define (set-rear-ptr! queue item) (set-cdr! queue item)) + +(define (empty-queue? queue) (null? (front-ptr queue))) + + +(define (make-queue) (cons '() '())) + +(define (front-queue queue) + (if (empty-queue? queue) + (write 'ERROR) + (car (front-ptr queue)))) + +(define (insert-queue! queue item) + (let ((new-pair (cons item '()))) + (cond ((empty-queue? queue) + (set-front-ptr! queue new-pair) + (set-rear-ptr! queue new-pair) + queue) + (else + (set-cdr! (rear-ptr queue) new-pair) + (set-rear-ptr! queue new-pair) + queue)))) + +(define (delete-queue! queue) + (cond ((empty-queue? queue) + (write 'ERROR)) + (else + (set-front-ptr! queue (cdr (front-ptr queue))) + queue))) + +; Note: the output here will expose the internal representation of the queue +; as a cons of the front pointer and rear pointer. The contents of the queue +; are visible in the front pointer. +; +(define q (make-queue)) +(write (insert-queue! q 'a)) +(write (insert-queue! q 'b)) +(write (delete-queue! q)) +(write (insert-queue! q 'c)) +(write (insert-queue! q 'd)) +(write (delete-queue! q)) + diff --git a/examples/simple_func.scm b/examples/simple_func.scm new file mode 100644 index 0000000..d860bbb --- /dev/null +++ b/examples/simple_func.scm @@ -0,0 +1,6 @@ +(define (func a b) + (lambda (x) + (+ a b x))) + +(write ((func 4 5) 10)) + diff --git a/examples/ycombinator.scm b/examples/ycombinator.scm new file mode 100644 index 0000000..3df9b65 --- /dev/null +++ b/examples/ycombinator.scm @@ -0,0 +1,19 @@ +; The applicative-order Y combinator +; +(define Y + (lambda (X) + ((lambda (procedure) + (X (lambda (arg) ((procedure procedure) arg)))) + (lambda (procedure) + (X (lambda (arg) ((procedure procedure) arg))))))) + +(define F* + (lambda (func-arg) + (lambda (n) + (if (zero? n) + 1 + (* n (func-arg (- n 1))))))) + +(define fact (Y F*)) +(write (fact 8)) + diff --git a/lib/schemer.rb b/lib/schemer.rb index b3c9524..8c5a812 100644 --- a/lib/schemer.rb +++ b/lib/schemer.rb @@ -1,4 +1,6 @@ require 'schemer/lexer' +require 'schemer/ast' +require 'schemer/parser' module Schemer # Your code goes here... diff --git a/lib/schemer/ast.rb b/lib/schemer/ast.rb new file mode 100644 index 0000000..8192c87 --- /dev/null +++ b/lib/schemer/ast.rb @@ -0,0 +1,78 @@ +module Schemer + module AST + + class CharacterLiteral + attr_reader :value + + def initialize(character) + @value = character.bytes.first + end + + def inspect + "#" + end + end + + class Identifier + attr_reader :value + + def initialize(identifier) + @value = identifier + end + + def inspect + "#" + end + end + + class Expression + attr_reader :proc, :args + + def initialize(expression) + @proc = expression[:proc] + @args = expression[:args].empty? ? nil : expression[:args] + end + + def inspect + "#" + end + end + + class AddOperator + def inspect + "#" + end + end + class SubtractOperator + def inspect + "#" + end + end + class MultiplyOperator + def inspect + "#" + end + end + class DivideOperator + def inspect + "#" + end + end + class GtOperator + def inspect + "#" + end + end + class LtOperator + def inspect + "#" + end + end + class EqualOperator + def inspect + "#" + end + end + + end +end diff --git a/lib/schemer/lexer.rb b/lib/schemer/lexer.rb index 2f5a23c..f0c7987 100644 --- a/lib/schemer/lexer.rb +++ b/lib/schemer/lexer.rb @@ -2,12 +2,12 @@ module Schemer class Lexer < Parslet::Parser alias_method :`, :str - rule(:lparen) { `(` } - rule(:rparen) { `)` } - rule(:space) { match('\s').repeat(1) } rule(:space?) { space.maybe } + rule(:lparen) { `(` >> space? } + rule(:rparen) { space? >> `)` } + rule(:text) { any.repeat } rule :single_quote_string do @@ -21,31 +21,36 @@ class Lexer < Parslet::Parser rule(:string) { single_quote_string | double_quote_string } rule(:letter) { match('[a-zA-Z]') } - rule(:underscore) { `_` } - rule(:dash) { `-` } - rule(:dot) { `.` } - rule(:number) { match('\d') } - rule(:numeric) { number.repeat(1) >> (dot >> number.repeat(1)).maybe } + rule(:dot) { `.` } + rule(:special_symbol) { `_` | `-` | `?` } + + + rule(:integer) { match('\d').repeat(1) } + rule(:float) { integer.repeat(1) >> dot >> integer.repeat(1) } + rule(:numeric) { float.as(:float) | integer.as(:integer) } rule(:character) { `#\\` >> letter.as(:char) } rule(:boolean) { `#` >> (`t` | `f`).as(:boolean) } - rule(:literal) { numeric.as(:numeric) | string | character | boolean } + rule(:literal) { numeric | string | character | boolean } rule(:quote) { `'` >> list.as(:quoted_list) } - rule(:list) { lparen >> (space? >> (symbol | literal | expression)).repeat(1) >> space? >> rparen } + rule(:vector) { `#` >> list.as(:vector) } + rule(:pair) { lparen >> arg >> space >> dot >> space >> args >> rparen } + rule(:list) { lparen >> args >> rparen } - rule(:symbol) { (letter >> (letter | underscore | dash | number).repeat(0)).as(:symbol) } + rule(:symbol) { (letter >> (letter | integer | special_symbol).repeat(0)).as(:identifier) } rule(:operator) { [`+`, `-`, `*`, `/`, `>`, `<`, `=`].inject(:|) } - rule(:arg) { (symbol | literal | expression | quote) } + + rule(:arg) { (symbol | quote | literal | expression | pair | vector | list) } rule(:args) { (arg >> space?).repeat.as(:args) } rule(:newline) { str("\n") } rule(:comment) { `;`.repeat(1,3) >> (`\n`.absnt? >> any).repeat.as(:comment) } - rule(:expression) { (lparen >> space? >> (symbol | operator | expression).as(:proc) >> (space? >> args).maybe >> space? >> rparen).as(:expression) } + rule(:expression) { (lparen >> (symbol | operator.as(:operator) | expression).as(:proc) >> (space? >> args).maybe >> rparen).as(:expression) } - rule(:body) { (quote | expression | comment | space).repeat } + rule(:body) { (quote | expression | comment | space).repeat(0) } root :body end diff --git a/lib/schemer/parser.rb b/lib/schemer/parser.rb new file mode 100644 index 0000000..6c6bc95 --- /dev/null +++ b/lib/schemer/parser.rb @@ -0,0 +1,37 @@ +module Schemer + class Parser < Parslet::Transform + + rule(:string => simple(:string)) { string } + + rule(:integer => simple(:integer)) { integer.to_i } + rule(:float => simple(:float)) { float.to_f } + + rule(:char => simple(:char)) { AST::CharacterLiteral.new(char) } + + rule(:boolean => simple(:boolean)) { boolean == 't' } + + rule(:identifier => simple(:identifier)) { AST::Identifier.new(identifier) } + + rule(:operator => simple(:operator)) do + case operator + when '+' + AST::AddOperator.new + when '-' + AST::SubtractOperator.new + when '*' + AST::MultiplyOperator.new + when '/' + AST::DivideOperator.new + when '>' + AST::GtOperator.new + when '<' + AST::LtOperator.new + when '=' + AST::EqualOperator.new + end + end + + rule(:expression => subtree(:expression)) { AST::Expression.new(expression) } + + end +end diff --git a/schemer.gemspec b/schemer.gemspec index db035c2..d9a9733 100644 --- a/schemer.gemspec +++ b/schemer.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |s| s.rubyforge_project = "schemer" - s.add_runtime_dependency 'parslet' + # s.add_runtime_dependency 'parslet' s.add_development_dependency 'rspec', '~> 2.5.0' s.add_development_dependency 'guard' s.add_development_dependency 'guard-rspec' diff --git a/spec/schemer/lexer_spec.rb b/spec/schemer/lexer_spec.rb index 1834b92..2839025 100644 --- a/spec/schemer/lexer_spec.rb +++ b/spec/schemer/lexer_spec.rb @@ -4,8 +4,8 @@ module Schemer describe Lexer do subject { Lexer.new } - its(:lparen) { should parse('(') } - its(:rparen) { should parse(')') } + its(:lparen) { should parse('( ') } + its(:rparen) { should parse(' )') } its(:space) { should parse(" \n") } its(:text) { should parse(' this should be treated as text!! oh yeah I think it should ') } @@ -13,30 +13,61 @@ module Schemer its(:symbol) { should parse('some_symbol') } its(:symbol) { should parse('s423-ome_symbol') } + its(:symbol) { should parse('s423-ome_symbol?') } its(:string) { should parse("'single-quoted string'") } its(:string) { should parse(%q{'some " complex string'}) } its(:string) { should parse(%q{"some ' complex string"}) } its(:args) { should parse('arg1 arg2 "arg3"') } + its(:args) { should parse("arg1 arg2\n \"arg3\"") } its(:comment) { should parse('; some comment!! "whoo"') } its(:comment) { should parse(';; some comment!! "whoo"') } its(:comment) { should parse(';;; some comment!! "whoo"') } - its(:literal) { should parse('123') } - its(:literal) { should parse('99.9') } - its(:literal) { should parse('"hey"') } - its(:literal) { should parse('#\z') } - its(:literal) { should parse('#t') } + its(:integer) { should parse('123') } + its(:float) { should parse('99.9') } + + its(:literal) { should parse('123').as(:integer => '123') } + its(:literal) { should parse('99.9').as(:float => '99.9') } + its(:literal) { should parse('"hey"').as(:string => 'hey') } + its(:literal) { should parse('#\z').as(:char => 'z') } + its(:literal) { should parse('#t').as(:boolean => 't') } its(:quote) { should parse("'(1 2 3)") } + its(:quote) { should parse("'()") } + its(:vector) { should parse("#(1 '(2 4) 3)") } + + its(:pair) { should parse('(1 . 2)') } + its(:pair) { should parse('(1 . (2 3))') } + its(:list) { should parse('(1 2 3)') } + its(:list) { should parse('()') } + its(:list) { should parse('( )') } its(:expression) { should parse('(define some "string" #t)') } its(:expression) { should parse(%q{((lambda some arg) (get_some_proc) yeah "string" ((proc-proc) 'another-string'))}) } its(:expression) { should parse("((lambda) 'foo')") } + its(:expression) { should parse("((lambda) (1 2 3))") } + its(:expression) { should parse("((lambda) '(1 2 3))") } its(:expression) { should parse('((lambda) (bar))') } + its(:expression) { should(parse("(lambda (define (make-new-set?) '()) (define (make-new-set?) '(2 3)))").as do |output| + output[:expression][:args].should have(2).expressions + output[:expression][:args].first[:expression][:args].should include(:quoted_list => {:args => []}) + output[:expression][:args].last[:expression][:args].should include(:quoted_list => {:args => [{:integer =>"2"}, {:integer => "3"}]}) + end) } + + # Regression tests + + describe "Regression tests from examples/ folder" do + Dir["examples/*.scm"].each do |filename| + file = File.read(filename) + it "parses #{filename}" do + subject.should parse(file) + end + end + end end end diff --git a/spec/schemer/parser_spec.rb b/spec/schemer/parser_spec.rb new file mode 100644 index 0000000..12261a8 --- /dev/null +++ b/spec/schemer/parser_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +module Schemer + describe Parser do + subject { Parser.new } + + let(:text) do +""" + +; Abstraction of a binary tree. Each tree is recursively defined as a list +; with the entry (data), left subtree and right subtree. Left and right can +; be null. +; +(define (entry tree) (car tree)) +(define (left-branch tree) (cadr tree)) +(define (right-branch tree) (caddr tree)) +(define (make-tree entry left right) + (list entry left right)) + +(define (make-new-set) + '()) + +(define (element-of-set? x set) + (cond + ((null? set) #f) + ((= x (entry set)) #t) + ((< x (entry set)) (element-of-set? x (left-branch set))) + ((> x (entry set)) (element-of-set? x (right-branch set))))) + +(define (adjoin-set x set) + (cond + ((null? set) (make-tree x '() '())) + ((= x (entry set)) set) + ((< x (entry set)) + (make-tree (entry set) + (adjoin-set x (left-branch set)) + (right-branch set))) + ((> x (entry set)) + (make-tree (entry set) + (left-branch set) + (adjoin-set x (right-branch set)))))) + + +(define myset + (adjoin-set 25 + (adjoin-set 13 + (adjoin-set 72 + (adjoin-set 4 (make-new-set)))))) + +(write (element-of-set? 4 myset)) +(write (element-of-set? 5 myset)) +(write (element-of-set? 26 myset)) +(write (element-of-set? 25 myset)) +""" + end + + it 'craps' do + lexer = Lexer.new + tree = lexer.parse text + hey = Parser.new.apply tree + end + + end +end