Skip to content

Commit

Permalink
Fix issue with negative numbers.
Browse files Browse the repository at this point in the history
Add short description of jet.
  • Loading branch information
jhsx committed Aug 29, 2016
1 parent c20dd50 commit fd3b9c0
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 37 deletions.
3 changes: 3 additions & 0 deletions constructors.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ func (t *Template) newInclude(pos Pos, line int, name, pipe Expression) *Include

func (t *Template) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error) {
n := &NumberNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeNumber, Pos: pos}, Text: text}
// todo: optimize
switch typ {
case itemCharConstant:
_rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0])
Expand Down Expand Up @@ -214,9 +215,11 @@ func (t *Template) newNumber(pos Pos, text string, typ itemType) (*NumberNode, e
}
}
}

if !n.IsInt && !n.IsUint && !n.IsFloat {
return nil, fmt.Errorf("illegal number syntax: %q", text)
}

return n, nil
}

Expand Down
42 changes: 36 additions & 6 deletions eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -974,12 +974,40 @@ func (st *Runtime) evalMultiplicativeExpression(node *MultiplicativeExprNode) re
}

func (st *Runtime) evalAdditiveExpression(node *AdditiveExprNode) reflect.Value {
left, right := st.evalPrimaryExpressionGroup(node.Left), st.evalPrimaryExpressionGroup(node.Right)

isAdditive := node.Operator.typ == itemAdd
if node.Left == nil {
right := st.evalPrimaryExpressionGroup(node.Right)
kind := right.Kind()
// todo: optimize
// todo:
if isInt(kind) {
if isAdditive {
return reflect.ValueOf(+right.Int())
} else {
return reflect.ValueOf(-right.Int())
}
} else if isUint(kind) {
if isAdditive {
return right
} else {
return reflect.ValueOf(-int64(right.Uint()))
}
} else if isFloat(kind) {
if isAdditive {
return reflect.ValueOf(+right.Float())
} else {
return reflect.ValueOf(-right.Float())
}
}
node.Left.errorf("a non numeric value in additive expression")
}

left, right := st.evalPrimaryExpressionGroup(node.Left), st.evalPrimaryExpressionGroup(node.Right)
kind := left.Kind()
// if the left value is not a float and the right is, we need to promote the left value to a float before the calculation
// this is necessary for expressions like 4+1.23
needFloatPromotion := !isFloat(kind) && isFloat(right.Kind())
needFloatPromotion := !isFloat(kind) && kind != reflect.String && isFloat(right.Kind())
if needFloatPromotion {
if isInt(kind) {
if isAdditive {
Expand Down Expand Up @@ -1077,14 +1105,16 @@ func (st *Runtime) evalBaseExpressionGroup(node Node) reflect.Value {
return resolved
case NodeNumber:
node := node.(*NumberNode)
if node.IsUint {
return reflect.ValueOf(&node.Uint64).Elem()
if node.IsFloat {
return reflect.ValueOf(&node.Float64).Elem()
}

if node.IsInt {
return reflect.ValueOf(&node.Int64).Elem()
}
if node.IsFloat {
return reflect.ValueOf(&node.Float64).Elem()

if node.IsUint {
return reflect.ValueOf(&node.Uint64).Elem()
}
}
node.errorf("unexpected node type %s in unary expression evaluating", node)
Expand Down
11 changes: 8 additions & 3 deletions eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ func TestEvalActionNode(t *testing.T) {
RunJetTest(t, data, nil, "actionNode_AddIntString", `{{ 2+"1" }}`, "3")
RunJetTest(t, data, nil, "actionNode_AddStringInt", `{{ "1"+2 }}`, "12")

RunJetTest(t, data, nil, "actionNode_NumberNegative", `{{ -5 }}`, "-5")
RunJetTest(t, data, nil, "actionNode_NumberNegative_1", `{{ 1 + -5 }}`, fmt.Sprint(1+-5))

//this is an error RunJetTest(t, data, nil, "actionNode_AddStringInt", `{{ "1"-2 }}`, "12")

RunJetTest(t, data, nil, "actionNode_Mult", `{{ 4*4 }}`, fmt.Sprint(4*4))
Expand Down Expand Up @@ -266,6 +269,8 @@ func TestEvalDefaultFuncs(t *testing.T) {
RunJetTest(t, nil, nil, "DefaultFuncs_urlEscape", `<h1>{{url: "<h1>Hello Buddy!</h1>"}}</h1>`, `<h1>%3Ch1%3EHello+Buddy%21%3C%2Fh1%3E</h1>`)

RunJetTest(t, nil, &User{"Mario Santos", "mario@gmail.com"}, "DefaultFuncs_json", `{{. |writeJson}}`, "{\"Name\":\"Mario Santos\",\"Email\":\"mario@gmail.com\"}\n")

RunJetTest(t, nil, nil, "DefaultFuncs_replace", `{{replace("My Name Is", " ", "_", -1)}}`, "My_Name_Is")
}

func TestEvalIssetAndTernaryExpression(t *testing.T) {
Expand Down Expand Up @@ -330,9 +335,9 @@ func TestFileResolve(t *testing.T) {
RunJetTestWithSet(t, set, nil, nil, "extension.jet.html", "", "extension.jet.html")
RunJetTestWithSet(t, set, nil, nil, "./sub/subextend", "", "simple - simple.jet - extension.jet.html")
RunJetTestWithSet(t, set, nil, nil, "./sub/extend", "", "simple - simple.jet - extension.jet.html")
for key, _ := range set.templates {
t.Log(key)
}
//for key, _ := range set.templates {
// t.Log(key)
//}
}

func TestIncludeIfNotExists(t *testing.T) {
Expand Down
47 changes: 40 additions & 7 deletions lex.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ type lexer struct {
lastPos Pos // position of most recent item returned by nextItem
items chan item // channel of scanned items
parenDepth int // nesting depth of ( ) exprs
lastType itemType
}

// next returns the next rune in the input.
Expand Down Expand Up @@ -169,6 +170,7 @@ func (l *lexer) backup() {

// emit passes an item back to the client.
func (l *lexer) emit(t itemType) {
l.lastType = t
l.items <- item{t, l.start, l.input[l.start:l.pos]}
l.start = l.pos
}
Expand Down Expand Up @@ -336,8 +338,39 @@ func lexInsideAction(l *lexer) stateFn {
case r == '%':
l.emit(itemMod)
case r == '-':

if r := l.peek(); '0' <= r && r <= '9' &&
itemAdd != l.lastType &&
itemMinus != l.lastType &&
itemNumber != l.lastType &&
itemIdentifier != l.lastType &&
itemString != l.lastType &&
itemRawString != l.lastType &&
itemCharConstant != l.lastType &&
itemBool != l.lastType &&
itemField != l.lastType &&
itemChar != l.lastType &&
itemTrans != l.lastType {
l.backup()
return lexNumber
}
l.emit(itemMinus)
case r == '+':
if r := l.peek(); '0' <= r && r <= '9' &&
itemAdd != l.lastType &&
itemMinus != l.lastType &&
itemNumber != l.lastType &&
itemIdentifier != l.lastType &&
itemString != l.lastType &&
itemRawString != l.lastType &&
itemCharConstant != l.lastType &&
itemBool != l.lastType &&
itemField != l.lastType &&
itemChar != l.lastType &&
itemTrans != l.lastType {
l.backup()
return lexNumber
}
l.emit(itemAdd)
case r == '?':
l.emit(itemTernary)
Expand Down Expand Up @@ -565,13 +598,13 @@ func (l *lexer) scanNumber() bool {
if l.accept(".") {
l.acceptRun(digits)
}
//if l.accept("eE") {
// l.accept("+-")
// l.acceptRun("0123456789")
//}
// Is it imaginary?
//l.accept("i")
// Next thing mustn't be alphanumeric.
if l.accept("eE") {
l.accept("+-")
l.acceptRun("0123456789")
}
//Is it imaginary?
l.accept("i")
//Next thing mustn't be alphanumeric.
if isAlphaNumeric(l.peek()) {
l.next()
return false
Expand Down
32 changes: 22 additions & 10 deletions lex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,26 @@ func TestLexer(t *testing.T) {
lexerTestCase(t, `{{ .Field }}`, itemLeftDelim, itemField, itemRightDelim)
lexerTestCase(t, `{{ "value" }}`, itemLeftDelim, itemString, itemRightDelim)
lexerTestCase(t, `{{ call: value }}`, itemLeftDelim, itemIdentifier, itemColon, itemIdentifier, itemRightDelim)
lexerTestCase(t, `{{.NumberOfRepairedDevices+1}}`, itemLeftDelim, itemField, itemAdd, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.NumberOfRepairedDevices-1}}`, itemLeftDelim, itemField, itemMinus, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.NumberOfRepairedDevices*1}}`, itemLeftDelim, itemField, itemMul, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.NumberOfRepairedDevices/1}}`, itemLeftDelim, itemField, itemDiv, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.NumberOfRepairedDevices%1}}`, itemLeftDelim, itemField, itemMod, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.NumberOfRepairedDevices=1}}`, itemLeftDelim, itemField, itemAssign, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.NumberOfRepairedDevices:=1}}`, itemLeftDelim, itemField, itemAssign, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.NumberOfRepairedDevices!1}}`, itemLeftDelim, itemField, itemNot, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.NumberOfRepairedDevices==1}}`, itemLeftDelim, itemField, itemEquals, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.NumberOfRepairedDevices&&1}}`, itemLeftDelim, itemField, itemAnd, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.Ex+1}}`, itemLeftDelim, itemField, itemAdd, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.Ex-1}}`, itemLeftDelim, itemField, itemMinus, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.Ex*1}}`, itemLeftDelim, itemField, itemMul, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.Ex/1}}`, itemLeftDelim, itemField, itemDiv, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.Ex%1}}`, itemLeftDelim, itemField, itemMod, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.Ex=1}}`, itemLeftDelim, itemField, itemAssign, itemNumber, itemRightDelim)
lexerTestCase(t, `{{Ex:=1}}`, itemLeftDelim, itemIdentifier, itemAssign, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.Ex!1}}`, itemLeftDelim, itemField, itemNot, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.Ex==1}}`, itemLeftDelim, itemField, itemEquals, itemNumber, itemRightDelim)
lexerTestCase(t, `{{.Ex&&1}}`, itemLeftDelim, itemField, itemAnd, itemNumber, itemRightDelim)

}

func TestLexNegatives(t *testing.T) {

lexerTestCase(t, `{{ -1 }}`, itemLeftDelim, itemNumber, itemRightDelim)
lexerTestCase(t, `{{ 5 + -1 }}`, itemLeftDelim, itemNumber, itemAdd, itemNumber, itemRightDelim)
lexerTestCase(t, `{{ 5 * -1 }}`, itemLeftDelim, itemNumber, itemMul, itemNumber, itemRightDelim)
lexerTestCase(t, `{{ 5 / +1 }}`, itemLeftDelim, itemNumber, itemDiv, itemNumber, itemRightDelim)
lexerTestCase(t, `{{ 5 % -1 }}`, itemLeftDelim, itemNumber, itemMod, itemNumber, itemRightDelim)
lexerTestCase(t, `{{ 5 == -1000 }}`, itemLeftDelim, itemNumber, itemEquals, itemNumber, itemRightDelim)

}
14 changes: 6 additions & 8 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,11 +572,15 @@ func (t *Template) multiplicativeExpression(context string) (left Expression, en

func (t *Template) unaryExpression(context string) (Expression, item) {
next := t.nextNonSpace()
if next.typ == itemNot {
switch next.typ {
case itemNot:
expr, endToken := t.comparativeExpression(context)
return t.newNotExpr(expr.Position(), t.lex.lineNumber(), expr), endToken
case itemMinus, itemAdd:
return t.newAdditiveExpr(next.pos, t.lex.lineNumber(), nil, t.operand(), next), t.nextNonSpace()
default:
t.backup()
}
t.backup()
operand := t.operand()
return operand, t.nextNonSpace()
}
Expand Down Expand Up @@ -831,9 +835,6 @@ func (t *Template) parseArguments() (args []Expression) {

func (t *Template) checkPipeline(pipe *PipeNode, context string) {

// GetProductById productId -> Field Name -> html
// GetProductById productId -> Method GetCategories -> Select

// Reject empty pipelines
if len(pipe.Cmds) == 0 {
t.errorf("missing value for %s", context)
Expand All @@ -854,9 +855,6 @@ func (t *Template) parseControl(allowElseIf bool, context string) (pos Pos, line

expression = t.assignmentOrExpression(context)
pos = expression.Position()
//if expression == nil {
// println("nil here",t.lex.input[0:t.lex.pos])
//}
if expression.Type() == NodeSet {
set = expression.(*SetNode)
if context != "range" {
Expand Down
9 changes: 7 additions & 2 deletions template.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Jet is faster and dynamic template engine for the Go programming language, set features
// includes very fast execution, dynamic and flexible language, inheritance, low number of allocations,
// special interfaces to allow even further optimizations.
//
package jet

import (
Expand All @@ -28,8 +32,9 @@ import (
"text/template"
)

// Set responsible to load and cache templates, also holds some runtime data
// passed to Runtime at evaluating time.
// Set is responsible to load,invoke parse and cache templates and relations
// every jet template is associated with one set.
// create a set with jet.NewSet(escapeeFn) returns a pointer to the Set
type Set struct {
dirs []string // directories for look to template files
templates map[string]*Template // parsed templates
Expand Down
4 changes: 3 additions & 1 deletion testData/additive_expression.jet
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{{ 1+2+2+2 }}
{{ 1 + -5 }}
===
{{1 + 2 + 2 + 2}}
{{1 + 2 + 2 + 2}}
{{1 + -5}}

0 comments on commit fd3b9c0

Please sign in to comment.