tmud-3.0.0/benchmark/
tmud-3.0.0/cmd/
tmud-3.0.0/cmd/objects/
tmud-3.0.0/cmd/tiny/
tmud-3.0.0/doc/SQLite/
tmud-3.0.0/doc/SQLite3/
tmud-3.0.0/doc/TernaryTrie/
tmud-3.0.0/farts/
tmud-3.0.0/lib/
tmud-3.0.0/lib/engine/
tmud-3.0.0/lib/farts/
tmud-3.0.0/logs/
#
# 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