Permalink
Browse files

Basic pattern matching

  • Loading branch information...
1 parent 6f2ffc8 commit 97216269a6badc4a60ac12d147abf45dd064e626 @txus committed Feb 22, 2014
Showing with 133 additions and 4 deletions.
  1. +60 −1 lib/lambra/bytecode_compiler.rb
  2. +40 −2 lib/lambra/syntax/ast.rb
  3. +33 −0 spec/compiler_spec.rb
  4. +0 −1 spec/spec_helper.rb
@@ -3,7 +3,7 @@ class BytecodeCompiler
attr_reader :generator
alias g generator
- SPECIAL_FORMS = %w(def fn let spawn receive)
+ SPECIAL_FORMS = %w(def fn let spawn receive match)
PRIMITIVE_FORMS = %w(println + - / * send sleep self)
def initialize(generator=nil)
@@ -119,6 +119,8 @@ def visit_SpecialForm(car, cdr)
when 'receive'
Lambra::AST::Receive.new(cdr).accept(self)
+ when 'match'
+ Lambra::AST::Match.new(cdr).accept(self)
end
end
@@ -315,6 +317,63 @@ def visit_Receive(o)
g.send :call, arguments.count
end
+ def visit_Match(o)
+ o.expression.accept(self)
+ success = g.new_label
+ done = g.new_label
+
+ o.patterns.each do |pattern|
+ failure = g.new_label
+ g.dup_top
+ pattern.accept(self, success, failure)
+ failure.set!
+ end
+
+ g.push_self
+ g.push_cpath_top
+ g.find_const :ArgumentError
+ g.push_literal "Pattern match failed"
+ g.send :new, 1
+ g.send :raise, 1, true
+ g.goto done
+
+ success.set!
+ done.set!
+ end
+
+ def visit_ValuePattern(o, success, failure)
+ o.value.accept(self)
+ g.swap_stack
+ g.send :==, 1
+ g.gif failure
+
+ # Pattern match succeeded!
+ o.actions.each_with_index do |action, idx|
+ action.accept(self)
+ g.pop unless o.actions.count - 1 == idx
+ end
+ g.goto success
+ end
+
+ def visit_SymbolPattern(o, success, _)
+ unbound = o.value.name == :_
+
+ args_vector = Lambra::AST::Vector.new(o.value.line, o.value.column, unbound ? [] : [o.value])
+ arguments = Lambra::AST::ClosureArguments.new(args_vector.line, args_vector.column, args_vector)
+ body = o.actions.size > 1 ? Lambra::AST::Sequence.new(o.actions.first.line, o.actions.first.column, o.actions[1..-1]) : o.actions.first
+ closure = Lambra::AST::Closure.new(arguments.line, arguments.column, arguments, body)
+
+ closure.accept(self)
+ g.swap_stack
+ if unbound
+ g.pop
+ g.send :call, 0
+ else
+ g.send :call, 1
+ end
+ g.goto success
+ end
+
def finalize
g.local_names = g.state.scope.local_names
g.local_count = g.state.scope.local_count
View
@@ -3,9 +3,9 @@
module Lambra
module AST
module Visitable
- def accept(visitor)
+ def accept(visitor, *args)
name = self.class.name.split("::").last
- visitor.send "visit_#{name}", self
+ visitor.send "visit_#{name}", self, *args
end
end
@@ -145,6 +145,44 @@ def initialize(clauses)
@pattern, *@actions = clauses
end
end
+
+ class Match < Node
+ class Pattern < Node
+ include Scope
+
+ def self.from(list)
+ car, *cdr = list.elements
+ case car
+ when Number, String, Character, Keyword
+ ValuePattern.new(car, cdr)
+ when Symbol
+ SymbolPattern.new(car, cdr)
+ else
+ raise "Can't generate pattern from #{car.inspect}"
+ end
+ end
+
+ attr_reader :value, :actions
+
+ def initialize(value, actions)
+ @value = value
+ @actions = actions
+ end
+ end
+
+ class ValuePattern < Pattern
+ end
+
+ class SymbolPattern < Pattern
+ end
+
+ attr_reader :expression, :patterns
+
+ def initialize((expression, *patterns))
+ @expression = expression
+ @patterns = patterns.map { |list| Pattern.from(list) }
+ end
+ end
end
end
View
@@ -44,6 +44,39 @@
end
end
+ describe 'match' do
+ it 'can match literal values' do
+ %q{
+ (match 42
+ (42 "foo"))
+ }.should eval_to "foo"
+ end
+
+ it 'can have a bound catch all pattern' do
+ %q{
+ (match 42
+ (99 99)
+ (x (+ x 3)))
+ }.should eval_to 45
+ end
+
+ it 'can have an unbound catch all pattern' do
+ %q{
+ (match 42
+ (_ 99))
+ }.should eval_to 99
+ end
+
+ it 'can fail the pattern match' do
+ proc {
+ Lambra::Compiler.eval %q{
+ (match 42
+ (99 99))
+ }
+ }.should raise_error ArgumentError
+ end
+ end
+
describe 'receive' do
it 'blocks until a new message arrives' do
%Q{
View
@@ -1,3 +1,2 @@
$: << File.expand_path('../../lib', __FILE__)
-
require 'lambra'

0 comments on commit 9721626

Please sign in to comment.