TeensyMud - 'A ruby mud server.'

Browse repository
back

farts_parser.y

#
# file:: farts_parser.y
# author:: Jon A. Lambert
# version:: 2.8.0
# date:: 01/19/2006
#
# This source code copyright (C) 2005, 2006 by Jon A. Lambert
# All rights reserved.
#
# Released under the terms of the TeensyMUD Public License
# See LICENSE file for additional information.
#

class Farts::Parser

prechigh
nonassoc UMINUS NOT
nonassoc GT GE LT LE
nonassoc EQ NE
left AND
left OR
preclow


rule

program : stmts
{ result = ProgramSyntaxNode.new( @sc.lineno, val[0] ) }

stmts :
{ result = [] }
| stmts stmt
{ result.push val[1] }

stmt : expr
| command
| if
| END { result = EndSyntaxNode.new( @sc.lineno, true) }
| END TRUE { result = EndSyntaxNode.new( @sc.lineno, true) }
| END FALSE { result = EndSyntaxNode.new( @sc.lineno, false) }
| COMMENT { result = CommentSyntaxNode.new( @sc.lineno, val[0]) }

if : IF expr stmts else ENDIF
{ result = IfSyntaxNode.new( @sc.lineno, val[1], val[2], val[3] ) }

else : ELSE stmts { result = val[1] }
| { result = nil }

command : ID { result = CommandSyntaxNode.new( @sc.lineno, val[0] , nil ) }
| ID STRING { result = CommandSyntaxNode.new( @sc.lineno, val[0], val[1] ) }

expr : NOT expr { result = CallSyntaxNode.new( @sc.lineno, '!', [val[1]] ) }
| expr EQ expr { result = CallSyntaxNode.new( @sc.lineno, '==', [val[0], val[2]] ) }
| expr NE expr { result = CallSyntaxNode.new( @sc.lineno, '!=', [val[0], val[2]] ) }
| expr GT expr { result = CallSyntaxNode.new( @sc.lineno, '>', [val[0], val[2]] ) }
| expr GE expr { result = CallSyntaxNode.new( @sc.lineno, '>=', [val[0], val[2]] ) }
| expr LT expr { result = CallSyntaxNode.new( @sc.lineno, '<', [val[0], val[2]] ) }
| expr LE expr { result = CallSyntaxNode.new( @sc.lineno, '<=', [val[0], val[2]] ) }
| expr AND expr { result = CallSyntaxNode.new( @sc.lineno, '&&', [val[0], val[2]] ) }
| expr OR expr { result = CallSyntaxNode.new( @sc.lineno, '||', [val[0], val[2]] ) }
| LPAREN expr RPAREN
| SUB expr =UMINUS
| function
| atom

atom : NUMBER { result = LiteralSyntaxNode.new( @sc.lineno, val[0] ) }
| FLOAT { result = LiteralSyntaxNode.new( @sc.lineno, val[0] ) }
| STRING { result = LiteralSyntaxNode.new( @sc.lineno, val[0] ) }
| ACTOR { result = [LocalVarSyntaxNode.new( @sc.lineno, val[0] )] }
| ACTOR SEND ID { result = [AttributeSyntaxNode.new( @sc.lineno, val[0], val[2])] }
| THIS { result = LocalVarSyntaxNode.new( @sc.lineno, val[0] ) }
| THIS SEND ID { result = AttributeSyntaxNode.new( @sc.lineno, val[0], val[2] ) }
| ARGS { result = LocalVarSyntaxNode.new( @sc.lineno, val[0] ) }
| ARGS SEND ID { result = AttributeSyntaxNode.new( @sc.lineno, val[0], val[2] ) }

function : ID LPAREN args RPAREN
{ result = CallSyntaxNode.new( @sc.lineno, val[0], *val[2] ) }

args :
| expr { result = [val] }
| args COMMA expr { result.push(val[2]) }

end

---- header
#
# file:: farts_parser.rb
# author:: Jon A. Lambert
# version:: 2.8.0
# date:: 01/19/2006
#
# This source code copyright (C) 2005, 2006 by Jon A. Lambert
# All rights reserved.
#
# Released under the terms of the TeensyMUD Public License
# See LICENSE file for additional information.
#
$:.unshift "lib" if !$:.include? "lib"
$:.unshift "vendor" if !$:.include? "vendor"

if $0 == __FILE__
Dir.chdir("../..")
$:.unshift "../../lib"
end
require 'farts/farts_lexer'
require 'farts/farts_lib'

---- inner

def initialize
@scope = {}
end

def parse( str )
@sc = Farts::Lexer.new(str)
@yydebug = true if $DEBUG
do_parse
end

def next_token
@sc.next_token
end

def on_error( t, val, values )
raise Racc::ParseError, "Error: #{@sc.lineno}:#{@sc.tokenpos} syntax error at '#{val}'"
end

---- footer


module Farts

class SyntaxNode
attr :lineno

def initialize( lineno )
@lineno = lineno
end

def exec_list(intp, nodes)
v = nil
nodes.each do |i|
v = i.execute(intp)
break if intp.hitbreak == true
end
v
end

def fart_err(msg)
raise "Error at #{lineno}: #{msg}"
end
end

class ProgramSyntaxNode < SyntaxNode

def initialize( lineno, tree )
super lineno
@tree = tree
end

def execute(vars)
intp = Interpreter.new(vars)
exec_list(intp, @tree)
end
end

class EndSyntaxNode < SyntaxNode

def initialize( lineno, val )
super lineno
@val = val
end

def execute(intp)
intp.hitbreak = true
intp.retval = val
end
end

class CommentSyntaxNode < SyntaxNode

def initialize( lineno, val )
super lineno
@val = val
end

def execute(intp)
end
end

class CallSyntaxNode < SyntaxNode

def initialize( lineno, func, args )
super lineno
@funcname = func
@args = args
end

def execute(intp)
arg = @args.collect {|i| i.execute(intp) }
begin
case @funcname
when "||"
arg[0] || arg[1]
when "&&"
arg[0] && arg[1]
when "!="
arg[0] != arg[1]
when "!"
!arg[0]
else
if arg.empty? || !arg[0].respond_to?(@funcname)
intp.call_lib_function(@funcname, arg) do
fart_err("undefined function '#{@funcname}'")
end
else
recv = arg.shift
recv.send(@funcname, *arg)
end
end
rescue ArgumentError
pp self
pp arg
fart_err($!.message)
end
end
end

class CommandSyntaxNode < SyntaxNode

def initialize( lineno, cmd, args )
super lineno
@cmd = cmd
@args = args
end

def execute(intp)
begin
if @args
intp.vars["this"].parse(@cmd + " " + @args)
else
intp.vars["this"].parse(@cmd)
end
rescue Exception
pp self
fart_err($!.message)
end
end
end

class IfSyntaxNode < SyntaxNode

def initialize( lineno, condition, stmts_true, stmts_false )
super lineno
@condition = condition
@stmts_true = stmts_true
@stmts_false = stmts_false
end

def execute(intp)
if @condition.execute(intp)
exec_list(intp, @stmts_true)
else
exec_list(intp, @stmts_false) if @stmts_false
end
end
end

class LocalVarSyntaxNode < SyntaxNode

def initialize( lineno, vname )
super lineno
@vname = vname
end

def execute( intp )
if intp.vars.has_key?(@vname)
intp.vars[@vname]
else
fart_err("unknown local variable '#{@vname}'")
end
end
end

class AttributeSyntaxNode < SyntaxNode

def initialize( lineno, vname, vattr )
super lineno
@vname = vname
@vattr = vattr
end

def execute(intp)
begin
if intp.vars.has_key?(@vname)
intp.vars[@vname].send(@vattr.intern)
else
fart_err("unknown local variable '#{@vname}'")
end
rescue NameError
fart_err($!.message)
end
end
end

class LiteralSyntaxNode < SyntaxNode

def initialize( lineno, val )
super lineno
@val = val
end

def execute( intp )
@val.class == String ? @val.dup : @val
end
end

# The Interpreter class is an instance of a machine to execute a program
class Interpreter
attr_accessor :hitbreak, :retval, :vars

# Construct an interpreter machine
# [+vars+] A hash table of attribute name/value pairs.
# Currently we support 'actor' and 'this', where they are the first
# two parameters of an event respectively.
def initialize(vars)
@vars = vars # hash table of attribute_name/value pairs
@hitbreak = false
@retval = true
@lib = Lib.new
end

def call_lib_function( fname, args )
if @lib.respond_to?(fname)
@lib.send(fname, *args)
else
yield
end
end

end

end


#
# FARTS testing
#
if $0 == __FILE__
require 'pp'
begin
fart = nil
str =""
File.open('farts/myprog.fart') {|f|
str = f.read
}
fart = Farts::Parser.new.parse( str )
pp fart
vars = { "actor" => "foo", "this" => "bar"}
fart.execute(vars)

rescue Racc::ParseError, Exception
log.error $!
exit
end
end