pimatic
Version:
A home automation server and framework for the Raspberry PI running on node.js
265 lines (242 loc) • 7.85 kB
text/coffeescript
###
variables AST Builder
===========
Builds a Abstract Syntax Tree (AST) from a variable expression token sequence.
###
cassert = require 'cassert'
assert = require 'assert'
util = require 'util'
Promise = require 'bluebird'
_ = require 'lodash'
S = require 'string'
class Expression
class AddExpression extends Expression
constructor: (, ) -> #nop
evaluate: (cache) ->
return .evaluate(cache, yes).then( (val1) =>
.evaluate(cache, yes).then( (val2) => parseFloat(val1) + parseFloat(val2) )
)
toString: -> "add(#{@left.toString()}, #{@right.toString()})"
getUnit: ->
leftUnit = .getUnit()
rightUnit = .getUnit()
if leftUnit?
return leftUnit
else
return rightUnit
class SubExpression extends Expression
constructor: (, ) -> #nop
evaluate: (cache) ->
return .evaluate(cache, yes).then( (val1) =>
.evaluate(cache, yes).then( (val2) => parseFloat(val1) - parseFloat(val2) )
)
toString: -> "sub(#{@left.toString()}, #{@right.toString()})"
getUnit: ->
leftUnit = .getUnit()
rightUnit = .getUnit()
if leftUnit? and leftUnit.length > 0
return leftUnit
else
return rightUnit
class MulExpression extends Expression
constructor: (, ) -> #nop
evaluate: (cache) ->
return .evaluate(cache, yes).then( (val1) =>
.evaluate(cache, yes).then( (val2) => parseFloat(val1) * parseFloat(val2) )
)
toString: -> "mul(#{@left.toString()}, #{@right.toString()})"
getUnit: ->
leftUnit = .getUnit()
rightUnit = .getUnit()
if leftUnit? and leftUnit.length > 0
if rightUnit? and rightUnit.length > 0
return "#{leftUnit}*#{rightUnit}"
else
return leftUnit
else
return rightUnit
class DivExpression extends Expression
constructor: (, ) -> #nop
evaluate: (cache) ->
return .evaluate(cache, yes).then( (val1) =>
.evaluate(cache, yes).then( (val2) => parseFloat(val1) / parseFloat(val2) )
)
toString: -> "div(#{@left.toString()}, #{@right.toString()})"
getUnit: ->
leftUnit = .getUnit()
rightUnit = .getUnit()
if leftUnit? and leftUnit.length > 0
if rightUnit? and rightUnit.length > 0
return "#{leftUnit}/#{rightUnit}"
else
return leftUnit
else
if rightUnit? and rightUnit.length > 0
return "1/#{rightUnit}"
else
return null
class NumberExpression extends Expression
constructor: () -> #nop
evaluate: (cache) -> Promise.resolve
toString: -> "num(#{@value})"
getUnit: -> null
class VariableExpression extends Expression
constructor: () -> #nop
evaluate: (cache, expectNumeric) ->
name = .name
val = cache[name]
return Promise.resolve().then( =>
if cache[name]?
if cache[name].value? then return cache[name].value
else throw new Error("Dependency cycle detected for variable #{name}")
else
cache[name] = {}
return .getUpdatedValue(cache).then( (value) =>
cache[name].value = value
return value
)
).then( (val) =>
if expectNumeric
if val isnt null
numVal = parseFloat(val)
else
numVal = 0
if isNaN(numVal)
throw new Error("Expected variable #{@variable.name} to have a numeric value.")
return numVal
else return val
)
getUnit: -> .unit
toString: -> "var(#{@variable.name})"
class FunctionCallExpression extends Expression
constructor: (, , ) -> #nop
evaluate: (cache) ->
context = {
units: _.map(, (a) -> a.getUnit() )
}
return Promise
.map(, ( (a) -> a.evaluate(cache) ), {concurrency: 1})
.then( (args) => .exec.apply(context, args) )
toString: ->
argsStr = (
if .length > 0 then _.reduce(, (l,r) -> "#{l.toString()}, #{r.toString()}" )
else ""
)
return "fun(#{@name}, [#{argsStr}])"
getUnit: ->
if .unit?
return .unit()
return ''
class StringExpression extends Expression
constructor: () -> #nop
evaluate: -> Promise.resolve
toString: -> "str('#{@value}')"
getUnit: -> null
class StringConcatExpression extends Expression
constructor: (, ) -> #nop
evaluate: (cache) ->
return .evaluate(cache).then( (val1) =>
.evaluate(cache).then( (val2) => "#{val1}#{val2}" )
)
toString: -> "con(#{@left.toString()}, #{@right.toString()})"
getUnit: -> null
class ExpressionTreeBuilder
constructor: (, ) ->
assert ? and typeof is "object"
assert ? and typeof is "object"
_nextToken: ->
if < .length
= [++]
else
= ''
build: () ->
= 0
return
_buildExpression: () ->
left =
return
_buildExpressionPrime: (left) ->
switch
when '+'
right =
return
when '-'
right =
return
when ')', '', ','
return left
else assert false, "unexpected token: '#{@token}'"
_buildTerm: () ->
left =
return
_buildTermPrime: (left) ->
switch
when '*'
right =
return
when '/'
right =
return
when '+', '-', ')', '', ','
return left
else
right =
return
_buildFactor: () ->
switch
when is '('
expr =
cassert is ')'
return expr
when
numberExpr = new NumberExpression()
return numberExpr
when
varName = .substr(1)
variable = [varName]
unless variable? then throw new Error("Could not find variable #{@token}")
varExpr = new VariableExpression(variable)
return varExpr
when
str = [1....length-1]
strExpr = new StringExpression(str)
return strExpr
when .match(/[_a-zA-Z][_a-zA-Z0-9]*/)?
funcName =
func = [funcName]
unless func? then throw new Error("Could not find function #{funcName}")
cassert is '('
args = []
while isnt ')'
args.push
cassert in [')', ',']
if is ','
cassert is ')'
funcCallExpr = new FunctionCallExpression(funcName, func, args)
return funcCallExpr
else assert false, "unexpected token: '#{@token}'"
_isStringToken: -> (.length > 0 and [0] is '"')
_isVariableToken: -> (.length > 0 and [0] is '$')
_isNumberToken: -> (typeof is "number")
module.exports = {
AddExpression
SubExpression
MulExpression
DivExpression
NumberExpression
VariableExpression
ExpressionTreeBuilder
}