require ‘q/scope’ require ‘q/syntax’

grammar Q

rule statements
  statements:(ws* statement:(comment / statement) ws*)* ws* {
    def eval scope
      statements.elements.each do |statement|
        statement.statement.eval scope
      end

      scope['_']
    end
  }
end

rule statement
  expression ws* ';' {
    def eval scope
      scope['_'] = expression.eval scope
    end
  }
end

rule expression
  call / assignment / conditional / binomial
end

rule function
  '(' arguments:(ws* identifier ws*)* ')' '{' statements '}' <Q::Syntax::Function>
end

rule assignment
  destination:(self / identifier) ws* '<:' ws* expression {
    def eval scope
      scope['_'] = scope[destination.text_value] = expression.eval scope
    end
  }
end

rule call
  name:(function / self / identifier) ws* '(' arguments:(ws* argument:expression ws*)* ')' {
    def eval scope
      scope['_'] = name.eval(scope).call(callscope(scope))
    end

    def callscope scope
      cscope = Q::Scope.new scope

      cscope.args = arguments.elements.map.each do |argument|
        argument.argument.eval(scope)
      end

      cscope
    end
  }
end

rule binomial
  head:monomial ws* tail:(ws* operator:binomial_operator ws* feet:monomial)*
  {
    def eval scope
      if has_feet?
        return operator.apply(scope, head, feet)
      end

      scope['_'] = head.eval(scope)
    end

    def has_feet?
      not tail.nil? and not tail.elements.first.nil? and not tail.elements.first.feet.nil?
    end

    def feet
      return nil if not has_feet?

      tail.elements.first.feet
    end

    def operator
      return nil if not has_feet?

      tail.elements.first.operator
    end
  }
end

rule monomial
  head:primary ws* tail:(ws* operator:monomial_operator ws* feet:monomial)*
  {
    def eval scope
      if has_feet?
        feet = tail.elements.first.feet
        return operator.apply(scope, head, feet)
      end

      scope['_'] = head.eval(scope)
    end

    def has_feet?
      not tail.nil? and not tail.elements.first.nil? and not tail.elements.first.feet.nil?
    end

    def operator
      return nil if not has_feet?

      tail.elements.first.operator
    end
  }
end

rule conditional
  'if' ws* '[' condition:(conditional_expression / statements) ']' ws*
  'then' ws* '[' consequence:(conditional_expression / statements) ']'
  otherwise:(ws* 'else' ws* '[' consequence:(conditional_expression / statements) ']')? <Q::Syntax::Conditional>
end

rule conditional_expression
  ws* expression ws* &']' {
    def eval scope
      expression.eval scope
    end
  }
end

rule primary
  call / function / self / unary / value / identifier / '(' ws* expression ws* ')' {
    def eval scope
      scope['_'] = expression.eval(scope)
    end
  }
end

rule binomial_operator
  minus / plus / comparison_operators
end

rule monomial_operator
  slash / star
end

rule unary
  negative / negation
end

rule negation
  '!' ws* primary {
    def eval scope
      not primary.eval(scope)
    end
  }
end

rule negative
  '-' ws* primary {
    def eval scope
      - primary.eval(scope)
    end
  }
end

rule comparison_operators
  lt / lte / gt / gte / neq / eq
end

rule lt
  '<' {
    def apply scope, a, b
      a.eval(scope) < b.eval(scope)
    end
  }
end

rule gt
  '>' {
    def apply scope, a, b
      a.eval(scope) > b.eval(scope)
    end
  }
end

rule lte
  '<=' {
    def apply scope, a, b
      a.eval(scope) <= b.eval(scope)
    end
  }
end

rule gte
  '>=' {
    def apply scope, a, b
      a.eval(scope) >= b.eval(scope)
    end
  }
end

rule eq
  '=' {
    def apply scope, a, b
      a.eval(scope) == b.eval(scope)
    end
  }
end

rule neq
  '!=' {
    def apply scope, a, b
      a.eval(scope) != b.eval(scope)
    end
  }
end

rule plus
  '+' {
    def apply scope, a, b
      scope['_'] = a.eval(scope) + b.eval(scope)
    end
  }
end

rule minus
  '-' {
    def apply scope, a, b
      scope['_'] = a.eval(scope) - b.eval(scope)
    end
  }
end

rule star
  '*' {
    def apply scope, a, b
      scope['_'] = a.eval(scope) * b.eval(scope)
    end
  }
end

rule slash
  '/' {
    def apply scope, a, b
      scope['_'] = a.eval(scope) / b.eval(scope)
    end
  }
end

rule value
  number / string / truth / lie / nil
end

rule truth
  'true' {
    def eval scope
      return true
    end
  }
end

rule lie
  'false' {
    def eval scope
      return false
    end
  }
end

rule nil
  'nil' {
    def eval scope
      return nil
    end
  }
end

rule string
  single_quote_string / double_quote_string
end

rule single_quote_string
  "'" content:("\\'" / (!"'" .))* "'" {
    def eval scope
      content.text_value.gsub '\\\'', "'"
    end
  }
end

rule double_quote_string
  '"' content:('\"' / (!'"' .))* '"' {
    def eval scope
      content.text_value.gsub '\"', '"'
    end
  }
end

rule number
  [0-9]+ point:'.'? [0-9]*
  {
    def eval scope
      if point.empty?
        return scope['_'] = text_value.to_i
      end

      scope['_'] = text_value.to_f
    end
  }
end

rule identifier
  [a-zA-Z_] [a-zA-Z0-9_]* ('?' / '!')? {
    def eval scope
      scope['_'] = scope[text_value]
    end
  }
end

rule self
  '@' {
    def eval scope
      scope.this
    end
  }
end

rule comment
  '#' (!("\n" / "\r") .)* ("\n" / "\r")+ {
    def eval scope
      # NO-OP, this is a comment for crying out loud! (:
    end
  }
end

rule ws
  ' ' / "\n" / "\r"
end

end