diff --git a/constructors.go b/constructors.go index c735229..f6d20e9 100644 --- a/constructors.go +++ b/constructors.go @@ -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]) @@ -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 } diff --git a/eval.go b/eval.go index 843d477..062dcea 100644 --- a/eval.go +++ b/eval.go @@ -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 { @@ -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) diff --git a/eval_test.go b/eval_test.go index 97e8fc6..25de6d7 100644 --- a/eval_test.go +++ b/eval_test.go @@ -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)) @@ -266,6 +269,8 @@ func TestEvalDefaultFuncs(t *testing.T) { RunJetTest(t, nil, nil, "DefaultFuncs_urlEscape", `

{{url: "

Hello Buddy!

"}}`, `

%3Ch1%3EHello+Buddy%21%3C%2Fh1%3E

`) 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) { @@ -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) { diff --git a/lex.go b/lex.go index ee61d64..0246cfb 100644 --- a/lex.go +++ b/lex.go @@ -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. @@ -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 } @@ -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) @@ -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 diff --git a/lex_test.go b/lex_test.go index e8403c4..a26909a 100644 --- a/lex_test.go +++ b/lex_test.go @@ -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) + } diff --git a/parse.go b/parse.go index f1cd4c6..4737288 100644 --- a/parse.go +++ b/parse.go @@ -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() } @@ -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) @@ -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" { diff --git a/template.go b/template.go index 3180456..fffd919 100644 --- a/template.go +++ b/template.go @@ -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 ( @@ -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 diff --git a/testData/additive_expression.jet b/testData/additive_expression.jet index 32945da..85209a4 100644 --- a/testData/additive_expression.jet +++ b/testData/additive_expression.jet @@ -1,3 +1,5 @@ {{ 1+2+2+2 }} +{{ 1 + -5 }} === -{{1 + 2 + 2 + 2}} \ No newline at end of file +{{1 + 2 + 2 + 2}} +{{1 + -5}}