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}}