Skip to content

Commit

Permalink
Added variables and assignment, as well as blocks with multiple state…
Browse files Browse the repository at this point in the history
…ments.
  • Loading branch information
zzorn committed May 25, 2012
1 parent 61cee10 commit e1cc700
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 46 deletions.
4 changes: 4 additions & 0 deletions src/main/scala/org/shapefun/parser/Context.scala
Expand Up @@ -12,6 +12,10 @@ trait Context {

def getVariable(identifier: Symbol): AnyRef

def addVariable(identifier: Symbol, value: AnyRef)

def setVariable(identifier: Symbol, value: AnyRef)

def getFunction(identifier: Symbol): Func

def getFunctionOnObject(hostType: Class[_], symbol: Symbol): Func
Expand Down
96 changes: 65 additions & 31 deletions src/main/scala/org/shapefun/parser/ShapeLangParser.scala
Expand Up @@ -21,70 +21,96 @@ class ShapeLangParser extends Parser {
}
}

def InputLine = rule { WhiteSpace ~ Expression ~ EOI }
def InputLine = rule { BlockContents ~ WhiteSpace ~ EOI }

def Expression: Rule1[Expr] = OrExpr
def BlockContents: Rule1[Expr] = rule {
zeroOrMore(Statement, StatementSeparator) ~~> {(statements) => Block(statements)} ~ optional(" ;")
}

def StatementSeparator: Rule0 = rule {
NonNewlineWhiteSpace ~ "\n" ~ WhiteSpace |
" ;"
}

def Statement: Rule1[SyntaxNode] = rule {
VarDef |
VarAssig |
Expression
}

def VarDef: Rule1[SyntaxNode] = rule {
" var" ~ VariableName ~ " =" ~ Expression ~~> {(name: Symbol, initialValue: Expr) => VarDefinition(name, initialValue)}
}

def VarAssig: Rule1[SyntaxNode] = rule {
VariableName ~ AssignmentOp ~ Expression ~~> {(name: Symbol, op: Symbol, value: Expr) => VarAssignment(name, op, value)}
}
def AssignmentOp: Rule1[Symbol] = rule {
WhiteSpace ~ group("+=" | "-=" | "*=" | "/=" | "=") ~> {s => Symbol(s)}
}


def Expression: Rule1[Expr] = OrExpr

def OrExpr: Rule1[Expr] = rule {
XorExpr ~ zeroOrMore(
"or " ~ XorExpr ~~> ((a:Expr, b:Expr) => BooleanOp(a, 'or, b).asInstanceOf[Expr])
" or" ~ XorExpr ~~> ((a:Expr, b:Expr) => BooleanOp(a, 'or, b).asInstanceOf[Expr])
)
}

def XorExpr: Rule1[Expr] = rule {
AndExpr ~ zeroOrMore(
"xor " ~ AndExpr ~~> ((a:Expr, b:Expr) => BooleanOp(a, 'xor, b).asInstanceOf[Expr])
" xor" ~ AndExpr ~~> ((a:Expr, b:Expr) => BooleanOp(a, 'xor, b).asInstanceOf[Expr])
)
}

def AndExpr: Rule1[Expr] = rule {
NotExpr ~ zeroOrMore(
"and " ~ NotExpr ~~> ((a:Expr, b:Expr) => BooleanOp(a, 'and, b).asInstanceOf[Expr])
" and" ~ NotExpr ~~> ((a:Expr, b:Expr) => BooleanOp(a, 'and, b).asInstanceOf[Expr])
)
}

def NotExpr: Rule1[Expr] = rule {
"not " ~ EqualityExpr ~~> {expr => Not(expr)} |
" not" ~ EqualityExpr ~~> {expr => Not(expr)} |
EqualityExpr
}

def EqualityExpr: Rule1[Expr] = rule {
ComparisonExpr ~ EqualitySymbol ~ ComparisonExpr ~~> {(a, sym, b) => EqualityComparisonOp(a, sym, b)} |
ComparisonExpr
ComparisonExpr
}
def EqualitySymbol: Rule1[Symbol] = rule { group("==" | "!=") ~> {s => Symbol(s)} ~ WhiteSpace }
def EqualitySymbol: Rule1[Symbol] = rule { WhiteSpace ~ group("==" | "!=") ~> {s => Symbol(s)} }


def ComparisonExpr: Rule1[Expr] = rule {
Range ~ ComparisonSymbol ~ Range ~ ComparisonSymbol ~ Range ~~> {(a, sym1, b, sym2, c) => ComparisonOp(a, sym1, b, sym2, c)} |
Range ~ ComparisonSymbol ~ Range ~~> {(a, sym, b) => ComparisonOp(a, sym, b)} |
Range
}
def ComparisonSymbol: Rule1[Symbol] = rule { group("<=" | ">=" | "<" | ">" ) ~> {s => Symbol(s)} ~ WhiteSpace }
def ComparisonSymbol: Rule1[Symbol] = rule { WhiteSpace ~ group("<=" | ">=" | "<" | ">" ) ~> {s => Symbol(s)} }

def Range: Rule1[Expr] = rule {
RangeStartEndInclusive ~ "steps " ~ TermExpr ~~> {(start, end, inclusive, steps) => RangeExpr(start, end, inclusive, steps=steps)} |
RangeStartEndInclusive ~ "step " ~ TermExpr ~~> {(start, end, inclusive, step) => RangeExpr(start, end, inclusive, step=step)} |
RangeStartEndInclusive ~ " steps" ~ TermExpr ~~> {(start, end, inclusive, steps) => RangeExpr(start, end, inclusive, steps=steps)} |
RangeStartEndInclusive ~ " step" ~ TermExpr ~~> {(start, end, inclusive, step) => RangeExpr(start, end, inclusive, step=step)} |
RangeStartEndInclusive ~~> {(start, end, inclusive) => RangeExpr(start, end, inclusive)} |
TermExpr
}
def RangeStartEndInclusive: Rule3[Expr, Expr, Boolean] = rule {
TermExpr ~ "... " ~ TermExpr ~ push(true) |
TermExpr ~ ".. " ~ TermExpr ~ push(false)
TermExpr ~ " ..." ~ TermExpr ~ push(true) |
TermExpr ~ " .." ~ TermExpr ~ push(false)
}

def TermExpr: Rule1[Expr] = rule {
Term ~ zeroOrMore(
"+ " ~ Term ~~> ((a:Expr, b:Expr) => NumberOp('plus, a, b).asInstanceOf[Expr])
| "- " ~ Term ~~> ((a:Expr, b:Expr) => NumberOp('minus, a, b).asInstanceOf[Expr])
" +" ~ Term ~~> ((a:Expr, b:Expr) => NumberOp('plus, a, b).asInstanceOf[Expr])
| " -" ~ Term ~~> ((a:Expr, b:Expr) => NumberOp('minus, a, b).asInstanceOf[Expr])
)
}

def Term: Rule1[Expr] = rule {
Factor ~ zeroOrMore(
"* " ~ Factor ~~> ((a:Expr, b:Expr) => NumberOp('mul, a, b).asInstanceOf[Expr])
| "/ " ~ Factor ~~> ((a:Expr, b:Expr) => NumberOp('div, a, b).asInstanceOf[Expr])
" *" ~ Factor ~~> ((a:Expr, b:Expr) => NumberOp('mul, a, b).asInstanceOf[Expr])
| " /" ~ Factor ~~> ((a:Expr, b:Expr) => NumberOp('div, a, b).asInstanceOf[Expr])
)
}

Expand All @@ -100,57 +126,65 @@ class ShapeLangParser extends Parser {
BooleanConst |
Parens |
If |
VarInc |
VarDec |
VariableRef
}


def If: Rule1[Expr] = rule {
"if " ~ Expression ~ "then " ~ Expression ~ "else " ~ Expression ~~> {(c: Expr, t: Expr, e: Expr) => IfExpr(c, t, e)}
" if" ~ Expression ~ " then" ~ Expression ~ " else" ~ Expression ~~> {(c: Expr, t: Expr, e: Expr) => IfExpr(c, t, e)}
}

def NegativeExpr: Rule1[Expr] = rule { "- " ~ Factor ~~> {exp => Neg(exp)} }
def NegativeExpr: Rule1[Expr] = rule { " -" ~ Factor ~~> {exp => Neg(exp)} }

def VarInc: Rule1[Expr] = rule {VariableName ~ " ++" ~~> {name => IncDecOp(name, increment = true)}}
def VarDec: Rule1[Expr] = rule {VariableName ~ " --" ~~> {name => IncDecOp(name, increment = false)}}

def Parens: Rule1[Expr] = rule { "( " ~ Expression ~ ") " }
def Parens: Rule1[Expr] = rule { " (" ~ Expression ~ " )" }

def VariableRef: Rule1[Expr] = rule { Identifier ~~> {s => VarRefExpr(s)} ~ WhiteSpace }
def VariableRef: Rule1[Expr] = rule { VariableName ~~> {s => VarRefExpr(s)} }

def Call: Rule1[Expr] = rule {
FirstCall ~ zeroOrMore(
". " ~ CallIdAndParams ~~>
" ." ~ CallIdAndParams ~~>
{(obj: Expr, ident: Symbol, params: List[Expr]) =>
CallExpr(Some(obj), ident, params).asInstanceOf[Expr]}
)
}
def FirstCall: Rule1[Expr] = rule {
optional(Callable ~ ". ") ~ CallIdAndParams ~~>
optional(Callable ~ " .") ~ CallIdAndParams ~~>
{(obj, ident, params) =>
CallExpr(obj, ident, params)}
}
def CallIdAndParams: Rule2[Symbol, List[Expr]] = rule { Identifier ~ "( " ~ zeroOrMore(CallParam, separator=", ") ~ ") " }
def CallIdAndParams: Rule2[Symbol, List[Expr]] = rule { Identifier ~ " (" ~ zeroOrMore(CallParam, separator=" ,") ~ " )" }
def CallParam: Rule1[Expr] = rule { Expression }

def Identifier: Rule1[Symbol] = rule { group(LetterOrUnderscore ~ zeroOrMore(LetterOrUnderscore | Digit)) ~> {s => Symbol(s) } }
def VariableName: Rule1[Symbol] = Identifier // TODO: Exclude keywords
def Identifier: Rule1[Symbol] = rule { WhiteSpace ~ group(LetterOrUnderscore ~ zeroOrMore(LetterOrUnderscore | Digit)) ~> {s => Symbol(s) } }
def LetterOrUnderscore = rule { "a" - "z" | "A" - "Z" | "_" }

def BooleanConst: Rule1[Expr] = rule {
"true " ~> {_ => True } |
"false " ~> {_ => False}
" true" ~> {_ => True } |
" false" ~> {_ => False}
}

def Number: Rule1[Expr] = rule { group(Integer ~ optional(Fraction)) ~> (s => {Num(s.toDouble)}) } ~ WhiteSpace
def Number: Rule1[Expr] = rule { WhiteSpace ~ group(Integer ~ optional(Fraction)) ~> (s => {Num(s.toDouble)}) }
def Integer: Rule0 = rule { optional("-") ~ (("1" - "9") ~ Digits | Digit) } // No leading zero in an integer, except if there is just one digit
def Fraction: Rule0 = rule { "." ~ Digits }
def Digits: Rule0 = rule { oneOrMore(Digit) }
def Digit: Rule0 = rule { "0" - "9" }

def WhiteSpace: Rule0 = rule { zeroOrMore(anyOf(" \n\r\t\f")) }
def WhiteSpace: Rule0 = rule { zeroOrMore(anyOf("\n\r\t\f ").label("whitespace")) }
def NonNewlineWhiteSpace: Rule0 = rule { zeroOrMore(anyOf("\t\f ").label("NonNewlineWhitespace")) }


/**
* We redefine the default string-to-rule conversion to also match trailing whitespace if the string ends with a blank.
*/
override implicit def toRule(string: String) =
if (string.endsWith(" "))
str(string.trim) ~ WhiteSpace
if (string.startsWith(" "))
WhiteSpace ~ str(string.substring(1))
else
str(string)

Expand Down
26 changes: 22 additions & 4 deletions src/main/scala/org/shapefun/parser/SimpleContext.scala
@@ -1,24 +1,42 @@
package org.shapefun.parser

import func.Func
import org.shapefun.utils.ParameterChecker


/**
*
*/
case class SimpleContext(values: Map[Symbol, AnyRef] = Map(),
case class SimpleContext(externalValues: Map[Symbol, AnyRef] = Map(),
functions: List[Func] = Nil,
classTypes: List[ClassInfo] = Nil ) extends Context {

private var addedVariables = Map[Symbol, AnyRef]()

private val functionsByName = Map[Symbol, Func]() ++ (functions map (f => f.identifier -> f))
private val classTypesByType = Map[Class[_], ClassInfo]() ++ (classTypes map (t => t.classType -> t))

def hasVariable(identifier: Symbol) = values.contains(identifier)
def hasVariable(identifier: Symbol) = externalValues.contains(identifier) || addedVariables.contains(identifier)
def hasVariableInCurrentScope(identifier: Symbol) = addedVariables.contains(identifier)

def getVariable(identifier: Symbol): AnyRef = {
if (!values.contains(identifier)) throw new CalculationError("No variable with the name '" + identifier.name + "' found")
if (!hasVariable(identifier)) throw new CalculationError("No variable with the name '" + identifier.name + "' found")

externalValues.getOrElse(identifier, addedVariables(identifier))
}

def addVariable(identifier: Symbol, value: AnyRef) {
ParameterChecker.requireNotNull(value, 'value)
if (hasVariableInCurrentScope(identifier)) throw new CalculationError("A variable with the name '" + identifier.name + "' already exists in the current scope")

addedVariables += identifier -> value
}

def setVariable(identifier: Symbol, value: AnyRef) {
ParameterChecker.requireNotNull(value, 'value)
if (!addedVariables.contains(identifier)) throw new CalculationError("No editable variable with the name '" + identifier.name + "' found")

values(identifier)
addedVariables += identifier -> value
}


Expand Down
22 changes: 22 additions & 0 deletions src/main/scala/org/shapefun/parser/syntaxtree/Block.scala
@@ -0,0 +1,22 @@
package org.shapefun.parser.syntaxtree

import org.shapefun.parser.Context

/**
*
*/
case class Block(statements: List[SyntaxNode]) extends Expr {

def returnType(): Class[_] = if (statements.isEmpty) classOf[Unit] else statements.last.returnType()

def checkTypes() {
statements foreach {_.checkTypes()}
}

def calculate(context: Context): AnyRef = {
var result: AnyRef = Unit
statements foreach {s => result = s.calculate(context)}
result
}

}
9 changes: 1 addition & 8 deletions src/main/scala/org/shapefun/parser/syntaxtree/Expr.scala
Expand Up @@ -6,14 +6,7 @@ import org.shapefun.parser.{CalculationError, Context}
/**
*
*/
trait Expr {

def returnType(): Class[_]

def checkTypes()

def calculate(context: Context): AnyRef

trait Expr extends SyntaxNode {

protected def ensureIsAssignable(required: Class[_], expression: Expr) {
if (!required.isAssignableFrom(expression.returnType()))
Expand Down
28 changes: 28 additions & 0 deletions src/main/scala/org/shapefun/parser/syntaxtree/IncDecOp.scala
@@ -0,0 +1,28 @@
package org.shapefun.parser.syntaxtree

import org.shapefun.parser.Context
import org.shapefun.utils.ParameterChecker

/**
*
*/
case class IncDecOp(identifier: Symbol, increment: Boolean) extends Expr {
ParameterChecker.requireIsIdentifier(identifier, 'identifier)

def checkTypes() {
// TODO: Get variable type from type checking context, check that it is numeric
}

def returnType(): Class[_] = {
// TODO: Get variable type from type checking context
null
}

def calculate(context: Context): AnyRef = {
val oldBoxedVal: AnyRef = context.getVariable(identifier)
val oldValue = Double.unbox(oldBoxedVal)
val newValue = Double.box(if (increment) oldValue + 1 else oldValue - 1)
context.setVariable(identifier, newValue)
oldBoxedVal
}
}
17 changes: 17 additions & 0 deletions src/main/scala/org/shapefun/parser/syntaxtree/SyntaxNode.scala
@@ -0,0 +1,17 @@
package org.shapefun.parser.syntaxtree

import org.shapefun.parser.Context

/**
*
*/
trait SyntaxNode {

def checkTypes()

def returnType(): Class[_]

def calculate(context: Context): AnyRef


}
42 changes: 42 additions & 0 deletions src/main/scala/org/shapefun/parser/syntaxtree/VarAssignment.scala
@@ -0,0 +1,42 @@
package org.shapefun.parser.syntaxtree

import org.shapefun.parser.Context
import org.shapefun.utils.ParameterChecker

/**
*
*/
case class VarAssignment(identifier: Symbol, assignmentOp: Symbol, valueExpr: Expr) extends SyntaxNode {
ParameterChecker.requireIsIdentifier(identifier, 'identifier)

def checkTypes() {
valueExpr.checkTypes()
// TODO: Get variable type from type checking context,check that value matches
}

def returnType(): Class[_] = {
// TODO: Get variable type from type checking context
null
}

def calculate(context: Context): AnyRef = {

val value = valueExpr.calculate(context)

val newValue = if (assignmentOp == '=) value
else {
val oldValue = Double.unbox(context.getVariable(identifier))
val numValue = Double.unbox(value)
Double.box(assignmentOp match {
case '+= => oldValue + numValue
case '-= => oldValue - numValue
case '*= => oldValue * numValue
case '/= => oldValue / numValue
})
}

context.setVariable(identifier, newValue)

newValue
}
}

0 comments on commit e1cc700

Please sign in to comment.