From 9a9184149f3276591a9739bb8c834e94aad8c162 Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Fri, 22 Jun 2018 16:57:32 +0200 Subject: [PATCH 01/11] add(eval on interface variables) --- eval.go | 105 +++++++++++++++++++++----------------------------------- 1 file changed, 39 insertions(+), 66 deletions(-) diff --git a/eval.go b/eval.go index b4fe5ad..518483b 100644 --- a/eval.go +++ b/eval.go @@ -15,6 +15,7 @@ package jet import ( + "errors" "fmt" "io" "reflect" @@ -991,86 +992,58 @@ func (st *Runtime) evalMultiplicativeExpression(node *MultiplicativeExprNode) re return left } +func getInterfaceIntFloatAsString(src reflect.Value) (string, error) { + switch res := src.Interface().(type) { + case int: + return strconv.Itoa(res), nil + case float64: + return strconv.FormatFloat(res, 'E', -1, 64), nil + } + return "", errors.New("a non numeric value in additive expression") +} + +func getInterfaceStringAsString(src reflect.Value) (string, error) { + switch res := src.Interface().(type) { + case string: + return res, nil + } + return "", errors.New("a non numeric value in additive expression") +} + func (st *Runtime) evalAdditiveExpression(node *AdditiveExprNode) reflect.Value { 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()) + + if rightValue, err := getInterfaceIntFloatAsString(right); err == nil { + if rightRes, err := strconv.ParseFloat(rightValue, 64); err == nil { + if isAdditive { + return reflect.ValueOf(+rightRes) + } else { + return reflect.ValueOf(-rightRes) + } } } + 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) && kind != reflect.String && isFloat(right.Kind()) - if needFloatPromotion { - if isInt(kind) { - if isAdditive { - left = reflect.ValueOf(float64(left.Int()) + right.Float()) - } else { - left = reflect.ValueOf(float64(left.Int()) - right.Float()) - } - } else if isUint(kind) { - if isAdditive { - left = reflect.ValueOf(float64(left.Uint()) + right.Float()) - } else { - left = reflect.ValueOf(float64(left.Uint()) - right.Float()) - } - } else { - node.Left.errorf("a non numeric value in additive expression") - } - } else { - if isInt(kind) { - if isAdditive { - left = reflect.ValueOf(left.Int() + toInt(right)) - } else { - left = reflect.ValueOf(left.Int() - toInt(right)) - } - } else if isFloat(kind) { - if isAdditive { - left = reflect.ValueOf(left.Float() + toFloat(right)) - } else { - left = reflect.ValueOf(left.Float() - toFloat(right)) - } - } else if isUint(kind) { - if isAdditive { - left = reflect.ValueOf(left.Uint() + toUint(right)) - } else { - left = reflect.ValueOf(left.Uint() - toUint(right)) - } - } else if kind == reflect.String { - if isAdditive { - left = reflect.ValueOf(left.String() + fmt.Sprint(right)) - } else { - node.Right.errorf("minus signal is not allowed with strings") + if leftValue, err := getInterfaceIntFloatAsString(left); err == nil { + if rightValue, err := getInterfaceIntFloatAsString(right); err == nil { + if leftRes, err := strconv.ParseFloat(leftValue, 64); err == nil { + if rightRes, err := strconv.ParseFloat(rightValue, 64); err == nil { + if isAdditive { + return reflect.ValueOf(leftRes + rightRes) + } else { + return reflect.ValueOf(leftRes - rightRes) + } + } } - } else { - node.Left.errorf("a non numeric value in additive expression") } } + node.Left.errorf("unhandled value in additive expression") return left } From 00379f3efb1d971b739116254b7c83f663ae1023 Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Fri, 22 Jun 2018 17:25:34 +0200 Subject: [PATCH 02/11] add(eval on interface strings) --- eval.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/eval.go b/eval.go index 518483b..85d8f76 100644 --- a/eval.go +++ b/eval.go @@ -1042,6 +1042,14 @@ func (st *Runtime) evalAdditiveExpression(node *AdditiveExprNode) reflect.Value } } } + } else if leftValue, err := getInterfaceStringAsString(left); err == nil { + if rightValue, err := getInterfaceStringAsString(right); err == nil { + if isAdditive { + return reflect.ValueOf(leftValue + rightValue) + } else { + node.Left.errorf("cannot substract two strings") + } + } } node.Left.errorf("unhandled value in additive expression") From 3ba693e955a6710539991d66ad8f6dd6a911c140 Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Mon, 25 Jun 2018 10:54:12 +0200 Subject: [PATCH 03/11] add(isset works on everything) --- eval.go | 71 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 32 deletions(-) diff --git a/eval.go b/eval.go index 85d8f76..05cee0f 100644 --- a/eval.go +++ b/eval.go @@ -673,6 +673,40 @@ func notNil(v reflect.Value) bool { } } +func ParseByType(baseExpression reflect.Value, indexExpression reflect.Value, indexType reflect.Type) (bool, error) { + switch baseExpression.Kind() { + case reflect.Map: + key := baseExpression.Type().Key() + if !indexType.AssignableTo(key) { + if indexType.ConvertibleTo(key) { + indexExpression = indexExpression.Convert(key) + } else { + return false, errors.New(indexType.String() + " is not assignable|convertible to map key " + key.String()) + } + } + return notNil(baseExpression.MapIndex(indexExpression)), nil + case reflect.Array, reflect.String, reflect.Slice: + if canNumber(indexType.Kind()) { + i := int(castInt64(indexExpression)) + return i >= 0 && i < baseExpression.Len(), nil + } else { + return false, errors.New("non numeric value in index expression kind " + baseExpression.Kind().String()) + } + case reflect.Struct: + if canNumber(indexType.Kind()) { + i := int(castInt64(indexExpression)) + return i >= 0 && i < baseExpression.NumField(), nil + } else if indexType.Kind() == reflect.String { + return notNil(getFieldOrMethodValue(indexExpression.String(), baseExpression)), nil + } else { + return false, errors.New("non numeric value in index expression kind " + baseExpression.Kind().String()) + } + default: + return ParseByType(reflect.ValueOf(baseExpression.Interface()), indexExpression, indexType) + } + return false, errors.New("indexing is not supported in value type " + baseExpression.Kind().String()) +} + func (st *Runtime) isSet(node Node) bool { nodeType := node.Type() @@ -695,39 +729,12 @@ func (st *Runtime) isSet(node Node) bool { baseExpression = baseExpression.Elem() } - switch baseExpression.Kind() { - case reflect.Map: - key := baseExpression.Type().Key() - if !indexType.AssignableTo(key) { - if indexType.ConvertibleTo(key) { - indexExpression = indexExpression.Convert(key) - } else { - node.errorf("%s is not assignable|convertible to map key %s", indexType.String(), key.String()) - } - } - value := baseExpression.MapIndex(indexExpression) - return notNil(value) - case reflect.Array, reflect.String, reflect.Slice: - if canNumber(indexType.Kind()) { - i := int(castInt64(indexExpression)) - return i >= 0 && i < baseExpression.Len() - } else { - node.errorf("non numeric value in index expression kind %s", baseExpression.Kind().String()) - } - case reflect.Struct: - if canNumber(indexType.Kind()) { - i := int(castInt64(indexExpression)) - return i >= 0 && i < baseExpression.NumField() - } else if indexType.Kind() == reflect.String { - fieldValue := getFieldOrMethodValue(indexExpression.String(), baseExpression) - return notNil(fieldValue) - - } else { - node.errorf("non numeric value in index expression kind %s", baseExpression.Kind().String()) - } - default: - node.errorf("indexing is not supported in value type %s", baseExpression.Kind().String()) + ret, err := ParseByType(baseExpression, indexExpression, indexType) + if err != nil { + node.errorf(err.Error()) } + return ret + case NodeIdentifier: value := st.Resolve(node.String()) return notNil(value) From 4ca7d27e171c4076c5edeedf7b42fd5ddfcf7faf Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Wed, 27 Jun 2018 17:06:41 +0200 Subject: [PATCH 04/11] add(filter block and format, perl addition), fix(recursive interface array) --- constructors.go | 4 + default.go | 6 +- eval.go | 266 ++++++++++++++++++++++++++++++++++------------- lex.go | 3 + node.go | 15 +++ parse.go | 6 ++ template.go | 4 + utils/visitor.go | 7 ++ 8 files changed, 236 insertions(+), 75 deletions(-) diff --git a/constructors.go b/constructors.go index f6d20e9..0ab3912 100644 --- a/constructors.go +++ b/constructors.go @@ -114,6 +114,10 @@ func (t *Template) newElse(pos Pos, line int) *elseNode { return &elseNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: nodeElse, Pos: pos, Line: line}} } +func (t *Template) newFilter(pos Pos, line int, set *SetNode, pipe Expression, list, elseList *ListNode) *FilterNode { + return &FilterNode{BranchNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeFilter, Pos: pos, Line: line}, Set: set, Expression: pipe, List: list, ElseList: elseList}} +} + func (t *Template) newIf(pos Pos, line int, set *SetNode, pipe Expression, list, elseList *ListNode) *IfNode { return &IfNode{BranchNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeIf, Pos: pos, Line: line}, Set: set, Expression: pipe, List: list, ElseList: elseList}} } diff --git a/default.go b/default.go index fc26062..5056d0e 100644 --- a/default.go +++ b/default.go @@ -52,6 +52,10 @@ func init() { "unsafe": reflect.ValueOf(SafeWriter(unsafePrinter)), "writeJson": reflect.ValueOf(jsonRenderer), "json": reflect.ValueOf(json.Marshal), + "format": reflect.ValueOf(Func(func(a Arguments) reflect.Value { + a.RequireNumOfArguments("format", 1, -1) + return reflect.ValueOf("format:" + a.Get(0).String()) + })), "isset": reflect.ValueOf(Func(func(a Arguments) reflect.Value { a.RequireNumOfArguments("isset", 1, -1) for i := 0; i < len(a.argExpr); i++ { @@ -76,7 +80,7 @@ func init() { return reflect.ValueOf(expression.NumField()) } - a.Panicf("inválid value type %s in len builtin", expression.Type()) + a.Panicf("invalid value type %s in len builtin", expression.Type()) return reflect.Value{} })), "includeIfExists": reflect.ValueOf(Func(func(a Arguments) reflect.Value { diff --git a/eval.go b/eval.go index 05cee0f..6549886 100644 --- a/eval.go +++ b/eval.go @@ -21,6 +21,7 @@ import ( "reflect" "runtime" "strconv" + "strings" "sync" "github.com/CloudyKit/fastprinter" @@ -373,18 +374,97 @@ func (st *Runtime) executeYieldBlock(block *BlockNode, blockParam, yieldParam *B } } +type FilterType int + +const ( + FilterUndefined FilterType = iota //Plain text. + FilterFormat +) + +type TextFilter struct { + action FilterType + value string + text []byte +} + +var optionText *TextFilter = NewTextFilter() + +func NewTextFilter() *TextFilter { + var ot TextFilter + ot.Reset() + return &ot +} + +func (ot *TextFilter) isEnabled() bool { + return ot.action != FilterUndefined +} + +func (ot *TextFilter) Reset() { + ot.action = FilterUndefined + ot.value = "" + ot.text = []byte{} +} + +func (ot *TextFilter) SetText(src []byte) { + ot.text = append(ot.text, src...) +} + +func (ot *TextFilter) SetValue(src string) { + if src != "" { + tocompare := "format:" + if strings.HasPrefix(src, tocompare) { + pos := strings.Index(src, tocompare) + if pos > -1 { + src = src[len(tocompare):len(src)] + } + ot.action = FilterFormat + ot.value = src + } + } +} + +func (ot *TextFilter) FormatOutput() []byte { + var value interface{} + var out []byte + var err error + + for _, line := range strings.Split(strings.TrimSuffix(string(ot.text), "\n"), "\n") { + mytext := line + line = strings.Replace(line, " ", "", -1) + line = strings.Replace(line, "\t", "", -1) + if line != "" { + if value, err = strconv.Atoi(mytext); err != nil { + value, err = strconv.ParseFloat(mytext, 64) + } + if err != nil { + value = mytext + } + out = append(out, []byte(fmt.Sprintf(ot.value, value))...) + } + out = append(out, '\n') + } + return out +} + func (st *Runtime) executeList(list *ListNode) { inNewSCOPE := false + if list == nil { + return + } + for i := 0; i < len(list.Nodes); i++ { node := list.Nodes[i] switch node.Type() { - case NodeText: node := node.(*TextNode) - _, err := st.Writer.Write(node.Text) - if err != nil { - node.error(err) + if optionText.isEnabled() == false { + _, err := st.Writer.Write(node.Text) + if err != nil { + node.error(err) + } + } else { + optionText.SetText(node.Text) } case NodeAction: node := node.(*ActionNode) @@ -402,16 +482,53 @@ func (st *Runtime) executeList(list *ListNode) { if node.Pipe != nil { v, safeWriter := st.evalPipelineExpression(node.Pipe) if !safeWriter && v.IsValid() { - if v.Type().Implements(rendererType) { - v.Interface().(Renderer).Render(st) - } else { - _, err := fastprinter.PrintValue(st.escapeeWriter, v) - if err != nil { - node.error(err) + if optionText.isEnabled() == false { + if v.Type().Implements(rendererType) { + v.Interface().(Renderer).Render(st) + } else { + _, err := fastprinter.PrintValue(st.escapeeWriter, v) + if err != nil { + node.error(err) + } } + } else { + tmp := []byte(fmt.Sprintf("%v", v.Interface())) + optionText.SetText(tmp) } } } + case NodeFilter: + node := node.(*FilterNode) + var isLet bool + if node.Set != nil { + if node.Set.Let { + isLet = true + st.newScope() + st.executeLetList(node.Set) + } else { + st.executeSetList(node.Set) + } + } + + mynode := st.evalPrimaryExpressionGroup(node.Expression) + + optionText.SetValue(mynode.String()) + + st.executeList(node.List) + + out := optionText.FormatOutput() + + _, err := st.Writer.Write(out) + if err != nil { + node.error(err) + } + + optionText.Reset() + + if isLet { + st.releaseScope() + } + case NodeIf: node := node.(*IfNode) var isLet bool @@ -554,6 +671,40 @@ var ( valueBoolFALSE = reflect.ValueOf(false) ) +func ParseIndexExpr(baseExpression reflect.Value, indexExpression reflect.Value, indexType reflect.Type) (reflect.Value, error) { + switch baseExpression.Kind() { + case reflect.Map: + key := baseExpression.Type().Key() + if !indexType.AssignableTo(key) { + if indexType.ConvertibleTo(key) { + indexExpression = indexExpression.Convert(key) + } else { + return baseExpression, errors.New(indexType.String() + " is not assignable|convertible to map key " + key.String()) + } + } + return baseExpression.MapIndex(indexExpression), nil + case reflect.Array, reflect.String, reflect.Slice: + if canNumber(indexType.Kind()) { + index := int(castInt64(indexExpression)) + if 0 <= index && index < baseExpression.Len() { + return baseExpression.Index(index), nil + } + return baseExpression, fmt.Errorf("%s index out of range (index: %d, len: %d)", baseExpression.Kind().String(), index, baseExpression.Len()) + } + return baseExpression, errors.New("non numeric value in index expression kind " + baseExpression.Kind().String()) + case reflect.Struct: + if canNumber(indexType.Kind()) { + return baseExpression.Field(int(castInt64(indexExpression))), nil + } else if indexType.Kind() == reflect.String { + return getFieldOrMethodValue(indexExpression.String(), baseExpression), nil + } + return baseExpression, errors.New("non numeric value in index expression kind " + baseExpression.Kind().String()) + case reflect.Interface: + return ParseIndexExpr(reflect.ValueOf(baseExpression.Interface()), indexExpression, indexType) + } + return baseExpression, errors.New("indexing is not supported in value type " + baseExpression.Kind().String()) +} + func (st *Runtime) evalPrimaryExpressionGroup(node Expression) reflect.Value { switch node.Type() { case NodeAdditiveExpr: @@ -596,39 +747,11 @@ func (st *Runtime) evalPrimaryExpressionGroup(node Expression) reflect.Value { baseExpression = baseExpression.Elem() } - switch baseExpression.Kind() { - case reflect.Map: - key := baseExpression.Type().Key() - if !indexType.AssignableTo(key) { - if indexType.ConvertibleTo(key) { - indexExpression = indexExpression.Convert(key) - } else { - node.errorf("%s is not assignable|convertible to map key %s", indexType.String(), key.String()) - } - } - return baseExpression.MapIndex(indexExpression) - case reflect.Array, reflect.String, reflect.Slice: - if canNumber(indexType.Kind()) { - index := int(castInt64(indexExpression)) - if 0 <= index && index < baseExpression.Len() { - return baseExpression.Index(index) - } else { - node.errorf("%s index out of range (index: %d, len: %d)", baseExpression.Kind().String(), index, baseExpression.Len()) - } - } else { - node.errorf("non numeric value in index expression kind %s", baseExpression.Kind().String()) - } - case reflect.Struct: - if canNumber(indexType.Kind()) { - return baseExpression.Field(int(castInt64(indexExpression))) - } else if indexType.Kind() == reflect.String { - return getFieldOrMethodValue(indexExpression.String(), baseExpression) - } else { - node.errorf("non numeric value in index expression kind %s", baseExpression.Kind().String()) - } - default: - node.errorf("indexing is not supported in value type %s", baseExpression.Kind().String()) + ret, err := ParseIndexExpr(baseExpression, indexExpression, indexType) + if err != nil { + node.errorf(err.Error()) } + return ret case NodeSliceExpr: node := node.(*SliceExprNode) baseExpression := st.evalPrimaryExpressionGroup(node.Base) @@ -1000,21 +1123,13 @@ func (st *Runtime) evalMultiplicativeExpression(node *MultiplicativeExprNode) re } func getInterfaceIntFloatAsString(src reflect.Value) (string, error) { - switch res := src.Interface().(type) { - case int: - return strconv.Itoa(res), nil - case float64: - return strconv.FormatFloat(res, 'E', -1, 64), nil + value := fmt.Sprintf("%v", src.Interface()) + if _, err := strconv.Atoi(value); err == nil { + return value, nil + } else if _, err := strconv.ParseFloat(value, 64); err == nil { + return value, nil } - return "", errors.New("a non numeric value in additive expression") -} - -func getInterfaceStringAsString(src reflect.Value) (string, error) { - switch res := src.Interface().(type) { - case string: - return res, nil - } - return "", errors.New("a non numeric value in additive expression") + return "", errors.New("a non numeric value") } func (st *Runtime) evalAdditiveExpression(node *AdditiveExprNode) reflect.Value { @@ -1037,26 +1152,27 @@ func (st *Runtime) evalAdditiveExpression(node *AdditiveExprNode) reflect.Value } left, right := st.evalPrimaryExpressionGroup(node.Left), st.evalPrimaryExpressionGroup(node.Right) - if leftValue, err := getInterfaceIntFloatAsString(left); err == nil { - if rightValue, err := getInterfaceIntFloatAsString(right); err == nil { - if leftRes, err := strconv.ParseFloat(leftValue, 64); err == nil { - if rightRes, err := strconv.ParseFloat(rightValue, 64); err == nil { - if isAdditive { - return reflect.ValueOf(leftRes + rightRes) - } else { - return reflect.ValueOf(leftRes - rightRes) - } - } - } - } - } else if leftValue, err := getInterfaceStringAsString(left); err == nil { - if rightValue, err := getInterfaceStringAsString(right); err == nil { + leftValue, errLeft := getInterfaceIntFloatAsString(left) + rightValue, errRight := getInterfaceIntFloatAsString(right) + + if errLeft == nil && errRight == nil { + leftRes, errLeftFloat := strconv.ParseFloat(leftValue, 64) + rightRes, errRightFloat := strconv.ParseFloat(rightValue, 64) + if errLeftFloat == nil && errRightFloat == nil { if isAdditive { - return reflect.ValueOf(leftValue + rightValue) + return reflect.ValueOf(leftRes + rightRes) } else { - node.Left.errorf("cannot substract two strings") + return reflect.ValueOf(leftRes - rightRes) } } + } else { + leftRes := fmt.Sprintf("%v", left.Interface()) + rightRes := fmt.Sprintf("%v", right.Interface()) + if isAdditive { + return reflect.ValueOf(leftRes + rightRes) + } else { + node.Left.errorf("two strings in substraction") + } } node.Left.errorf("unhandled value in additive expression") @@ -1348,10 +1464,10 @@ func checkEquality(v1, v2 reflect.Value) bool { } return true case reflect.Interface: - if v1.IsNil() || v2.IsNil() { + if kind == v2.Kind() && (v1.IsNil() || v2.IsNil()) { return v1.IsNil() == v2.IsNil() } - return checkEquality(v1.Elem(), v2.Elem()) + return checkEquality(reflect.ValueOf(v1.Interface()), reflect.ValueOf(v2.Interface())) case reflect.Ptr: return v1.Pointer() == v2.Pointer() case reflect.Struct: @@ -1413,6 +1529,8 @@ func castBoolean(v reflect.Value) bool { return true case reflect.Map, reflect.Slice, reflect.String: return v.Len() > 0 + case reflect.Interface: + return castBoolean(reflect.ValueOf(v.Interface())) default: if isInt(kind) { return v.Int() > 0 diff --git a/lex.go b/lex.go index 09f9dbe..2f9f1a2 100644 --- a/lex.go +++ b/lex.go @@ -100,6 +100,7 @@ const ( itemNot itemMSG itemTrans + itemFilter ) var key = map[string]itemType{ @@ -123,6 +124,8 @@ var key = map[string]itemType{ "content": itemContent, "msg": itemMSG, "trans": itemTrans, + + "filter": itemFilter, } const eof = -1 diff --git a/node.go b/node.go index bd7412e..0119fd0 100644 --- a/node.go +++ b/node.go @@ -79,6 +79,7 @@ const ( nodeEnd //An end action. Not added to tree. NodeField //A field or method name. NodeIdentifier //An identifier; always a function name. + NodeFilter //A filter action NodeIf //An if action. NodeList //A list of Nodes. NodePipe //A pipeline of commands. @@ -425,6 +426,15 @@ func (b *BranchNode) String() string { return fmt.Sprintf("{{range %s}}%s{{else}}%s{{end}}", s, b.List, b.ElseList) } return fmt.Sprintf("{{range %s}}%s{{end}}", s, b.List) + } else if b.NodeType == NodeFilter { + s := "" + if b.Set != nil { + s = b.Set.String() + ";" + } + if b.ElseList != nil { + return fmt.Sprintf("{{filter %s%s}}%s{{else}}%s{{end}}", s, b.Expression, b.List, b.ElseList) + } + return fmt.Sprintf("{{filter %s%s}}%s{{end}}", s, b.Expression, b.List) } else { s := "" if b.Set != nil { @@ -437,6 +447,11 @@ func (b *BranchNode) String() string { } } +// FilterNode represents a {{filter}} action and its commands. +type FilterNode struct { + BranchNode +} + // IfNode represents an {{if}} action and its commands. type IfNode struct { BranchNode diff --git a/parse.go b/parse.go index 4b0c1a3..e340077 100644 --- a/parse.go +++ b/parse.go @@ -479,6 +479,10 @@ func (t *Template) textOrAction() Node { return nil } +func (t *Template) filterControl() Node { + return t.newFilter(t.parseControl(true, "filter")) +} + func (t *Template) action() (n Node) { switch token := t.nextNonSpace(); token.typ { case itemElse: @@ -497,6 +501,8 @@ func (t *Template) action() (n Node) { return t.parseInclude() case itemYield: return t.parseYield() + case itemFilter: + return t.filterControl() } t.backup() diff --git a/template.go b/template.go index 8afaaf5..0c88d5c 100644 --- a/template.go +++ b/template.go @@ -19,6 +19,7 @@ package jet import ( + "os" "fmt" "io" "io/ioutil" @@ -380,6 +381,9 @@ func (scope VarMap) SetWriter(name string, v SafeWriter) VarMap { // Execute executes the template in the w Writer func (t *Template) Execute(w io.Writer, variables VarMap, data interface{}) error { + if w == nil { + w = os.Stdout + } return t.ExecuteI18N(nil, w, variables, data) } diff --git a/utils/visitor.go b/utils/visitor.go index 454f45c..9c58ed7 100644 --- a/utils/visitor.go +++ b/utils/visitor.go @@ -45,6 +45,8 @@ func (vc VisitorContext) Visit(node jet.Node) { vc.visitChainNode(node) case *jet.CommandNode: vc.visitCommandNode(node) + case *jet.FilterNode: + vc.visitFilterNode(node) case *jet.IfNode: vc.visitIfNode(node) case *jet.PipeNode: @@ -125,9 +127,14 @@ func (vc VisitorContext) visitPipeNode(pipeNode *jet.PipeNode) { } } +func (vc VisitorContext) visitFilterNode(filterNode *jet.IfNode) { + vc.visitBranchNode(&filterNode.BranchNode) +} + func (vc VisitorContext) visitIfNode(ifNode *jet.IfNode) { vc.visitBranchNode(&ifNode.BranchNode) } + func (vc VisitorContext) visitBranchNode(branchNode *jet.BranchNode) { if branchNode.Set != nil { vc.visitNode(branchNode.Set) From 4a1b6e25998bc9215e16d61ea97d3a25a041f41f Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Wed, 4 Jul 2018 11:20:49 +0200 Subject: [PATCH 05/11] add(switch, default), fix(bugs) --- constructors.go | 16 +++++++ eval.go | 119 ++++++++++++++++++++++++++++++++++++++++++------ lex.go | 17 +++++-- node.go | 46 ++++++++++++++++++- parse.go | 61 ++++++++++++++++++++++++- 5 files changed, 238 insertions(+), 21 deletions(-) diff --git a/constructors.go b/constructors.go index 0ab3912..d307208 100644 --- a/constructors.go +++ b/constructors.go @@ -86,6 +86,10 @@ func (t *Template) newNil(pos Pos) *NilNode { return &NilNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeNil, Pos: pos}} } +func (t *Template) newUndefined(pos Pos) *UndefinedNode { + return &UndefinedNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeUndefined, Pos: pos}} +} + func (t *Template) newField(pos Pos, ident string) *FieldNode { return &FieldNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeField, Pos: pos}, Ident: strings.Split(ident[1:], ".")} //[1:] to drop leading period } @@ -114,10 +118,22 @@ func (t *Template) newElse(pos Pos, line int) *elseNode { return &elseNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: nodeElse, Pos: pos, Line: line}} } +func (t *Template) newDefault(pos Pos, line int, pipe Expression, list *ListNode) *DefaultNode { + return &DefaultNode{BranchNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeDefault, Pos: pos, Line: line}, Expression: pipe, List: list}} +} + func (t *Template) newFilter(pos Pos, line int, set *SetNode, pipe Expression, list, elseList *ListNode) *FilterNode { return &FilterNode{BranchNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeFilter, Pos: pos, Line: line}, Set: set, Expression: pipe, List: list, ElseList: elseList}} } +func (t *Template) newCase(pos Pos, line int, pipe Expression, list *ListNode) *CaseNode { + return &CaseNode{BranchNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeCase, Pos: pos, Line: line}, Expression: pipe, List: list}} +} + +func (t *Template) newSwitch(pos Pos, line int, set *SetNode, pipe Expression, list, elseList *ListNode) *SwitchNode { + return &SwitchNode{BranchNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeSwitch, Pos: pos, Line: line}, Set: set, Expression: pipe, List: list, ElseList: elseList}} +} + func (t *Template) newIf(pos Pos, line int, set *SetNode, pipe Expression, list, elseList *ListNode) *IfNode { return &IfNode{BranchNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeIf, Pos: pos, Line: line}, Set: set, Expression: pipe, List: list, ElseList: elseList}} } diff --git a/eval.go b/eval.go index 6549886..18699bd 100644 --- a/eval.go +++ b/eval.go @@ -193,6 +193,25 @@ func (state *Runtime) setValue(name string, val reflect.Value) bool { return true } +func (state *Runtime) getAssignedValue(name string) (reflect.Value, error) { + sc := state.scope + + // try to resolve variables in the current scope + _, ok := sc.variables[name] + + // if not found walks parent scopes + for !ok && sc.parent != nil { + sc = sc.parent + _, ok = sc.variables[name] + } + + if ok { + return sc.variables[name], nil + } + + return reflect.ValueOf(nil), errors.New("Variable " + name + " is not set") +} + // Resolve resolves a value from the execution context func (state *Runtime) Resolve(name string) reflect.Value { @@ -239,7 +258,13 @@ func (st *Runtime) recover(err *error) { } } -func (st *Runtime) executeSet(left Expression, right reflect.Value) { +func (st *Runtime) executeSet(left Expression, right reflect.Value, isdefault bool) { + if isdefault == true { + _, err := st.getAssignedValue(left.String()) + if err == nil { + return + } + } typ := left.Type() if typ == NodeIdentifier { st.setValue(left.(*IdentifierNode).Ident, right) @@ -279,18 +304,18 @@ RESTART: } } -func (st *Runtime) executeSetList(set *SetNode) { +func (st *Runtime) executeSetList(set *SetNode, isdefault bool) { if set.IndexExprGetLookup { value := st.evalPrimaryExpressionGroup(set.Right[0]) - st.executeSet(set.Left[0], value) + st.executeSet(set.Left[0], value, isdefault) if value.IsValid() { - st.executeSet(set.Left[1], valueBoolTRUE) + st.executeSet(set.Left[1], valueBoolTRUE, isdefault) } else { - st.executeSet(set.Left[1], valueBoolFALSE) + st.executeSet(set.Left[1], valueBoolFALSE, isdefault) } } else { for i := 0; i < len(set.Left); i++ { - st.executeSet(set.Left[i], st.evalPrimaryExpressionGroup(set.Right[i])) + st.executeSet(set.Left[i], st.evalPrimaryExpressionGroup(set.Right[i]), isdefault) } } } @@ -446,6 +471,62 @@ func (ot *TextFilter) FormatOutput() []byte { return out } +func (st *Runtime) executeSwitch(list *ListNode, value reflect.Value) { + var defaultNode *CaseNode = nil + var found = false + + for i := 0; i < len(list.Nodes); i++ { + node := list.Nodes[i] + switch node.Type() { + case NodeCase: + node := node.(*CaseNode) + + if node.Expression.Type() == NodeUndefined { + defaultNode = node + } else { + myvalue := st.evalPrimaryExpressionGroup(node.Expression) + + left := fmt.Sprintf("%v", value) + right := fmt.Sprintf("%v", myvalue) + + if left == right { + found = true + st.executeList(node.List) + } + } + } + } + if found == false && defaultNode != nil { + st.executeList(defaultNode.List) + } +} + +func (st *Runtime) executeDefault(list *ListNode) { + for i := 0; i < len(list.Nodes); i++ { + node := list.Nodes[i] + switch node.Type() { + case NodeAction: + node := node.(*ActionNode) + + if node.Pipe != nil || node.Set == nil || node.Set.Let == true { + node.errorf("unexpected data in default block") + } + + st.executeSetList(node.Set, true) + + case NodeText: + node := node.(*TextNode) + for _, value := range node.String() { + if value != '\n' && value != '\t' && value != ' ' { + node.errorf("unexpected data in default block") + } + } + default: + node.errorf("unexpected data in default block") + } + } +} + func (st *Runtime) executeList(list *ListNode) { inNewSCOPE := false @@ -476,7 +557,7 @@ func (st *Runtime) executeList(list *ListNode) { } st.executeLetList(node.Set) } else { - st.executeSetList(node.Set) + st.executeSetList(node.Set, false) } } if node.Pipe != nil { @@ -497,6 +578,18 @@ func (st *Runtime) executeList(list *ListNode) { } } } + case NodeDefault: + node := node.(*DefaultNode) + + st.executeDefault(node.List) + + case NodeSwitch: + node := node.(*SwitchNode) + + value := st.evalPrimaryExpressionGroup(node.Expression) + + st.executeSwitch(node.List, value) + case NodeFilter: node := node.(*FilterNode) var isLet bool @@ -506,7 +599,7 @@ func (st *Runtime) executeList(list *ListNode) { st.newScope() st.executeLetList(node.Set) } else { - st.executeSetList(node.Set) + st.executeSetList(node.Set, false) } } @@ -538,7 +631,7 @@ func (st *Runtime) executeList(list *ListNode) { st.newScope() st.executeLetList(node.Set) } else { - st.executeSetList(node.Set) + st.executeSetList(node.Set, false) } } @@ -585,10 +678,10 @@ func (st *Runtime) executeList(list *ListNode) { } } else { if isKeyVal { - st.executeSet(node.Set.Left[0], indexValue) - st.executeSet(node.Set.Left[1], rangeValue) + st.executeSet(node.Set.Left[0], indexValue, false) + st.executeSet(node.Set.Left[1], rangeValue, false) } else { - st.executeSet(node.Set.Left[0], rangeValue) + st.executeSet(node.Set.Left[0], rangeValue, false) } } } else { @@ -824,7 +917,7 @@ func ParseByType(baseExpression reflect.Value, indexExpression reflect.Value, in } else { return false, errors.New("non numeric value in index expression kind " + baseExpression.Kind().String()) } - default: + case reflect.Interface: return ParseByType(reflect.ValueOf(baseExpression.Interface()), indexExpression, indexType) } return false, errors.New("indexing is not supported in value type " + baseExpression.Kind().String()) diff --git a/lex.go b/lex.go index 2f9f1a2..de932a0 100644 --- a/lex.go +++ b/lex.go @@ -101,19 +101,26 @@ const ( itemMSG itemTrans itemFilter + itemDefault + itemSwitch + itemCase ) var key = map[string]itemType{ "extends": itemExtends, "import": itemImport, + "default": itemDefault, "include": itemInclude, "block": itemBlock, "yield": itemYield, - "else": itemElse, - "end": itemEnd, - "if": itemIf, + "else": itemElse, + "end": itemEnd, + "if": itemIf, + "switch": itemSwitch, + + "case": itemCase, "range": itemRange, "nil": itemNil, @@ -124,8 +131,8 @@ var key = map[string]itemType{ "content": itemContent, "msg": itemMSG, "trans": itemTrans, - - "filter": itemFilter, + + "filter": itemFilter, } const eof = -1 diff --git a/node.go b/node.go index 0119fd0..0c37eff 100644 --- a/node.go +++ b/node.go @@ -79,7 +79,10 @@ const ( nodeEnd //An end action. Not added to tree. NodeField //A field or method name. NodeIdentifier //An identifier; always a function name. - NodeFilter //A filter action + NodeDefault //A default action + NodeCase //A case action + NodeSwitch //A switch action + NodeFilter //A filter action NodeIf //An if action. NodeList //A list of Nodes. NodePipe //A pipeline of commands. @@ -106,6 +109,7 @@ const ( NodeIndexExpr NodeSliceExpr endExpressions + NodeUndefined ) // Nodes. @@ -219,6 +223,15 @@ func (i *IdentifierNode) String() string { return i.Ident } +// UndefinedNode holds an undefined identifier +type UndefinedNode struct { + NodeBase +} + +func (u *UndefinedNode) String() string { + return "" +} + // NilNode holds the special identifier 'nil' representing an untyped nil constant. type NilNode struct { NodeBase @@ -435,6 +448,18 @@ func (b *BranchNode) String() string { return fmt.Sprintf("{{filter %s%s}}%s{{else}}%s{{end}}", s, b.Expression, b.List, b.ElseList) } return fmt.Sprintf("{{filter %s%s}}%s{{end}}", s, b.Expression, b.List) + } else if b.NodeType == NodeSwitch { + s := "" + if b.Set != nil { + s = b.Set.String() + ";" + } + return fmt.Sprintf("{{switch %s%s}}%s{{end}}", s, b.Expression, b.List) + } else if b.NodeType == NodeCase { + s := "" + if b.Set != nil { + s = b.Set.String() + ";" + } + return fmt.Sprintf("{{case %s%s}}%s{{end}}", s, b.Expression, b.List) } else { s := "" if b.Set != nil { @@ -452,6 +477,16 @@ type FilterNode struct { BranchNode } +// CaseNode represents a {{case}} action and its commands. +type CaseNode struct { + BranchNode +} + +// SwitchNode represents a {{switch}} action and its commands. +type SwitchNode struct { + BranchNode +} + // IfNode represents an {{if}} action and its commands. type IfNode struct { BranchNode @@ -557,6 +592,15 @@ func (t *YieldNode) String() string { return fmt.Sprintf("{{yield %s(%s) %s}}", t.Name, t.Parameters, t.Expression) } +// DefaultNode represents a {{default}} action and its commands. +type DefaultNode struct { + BranchNode +} + +func (t *DefaultNode) String() string { + return "{{default}}" +} + // IncludeNode represents a {{include }} action. type IncludeNode struct { NodeBase diff --git a/parse.go b/parse.go index e340077..86b0705 100644 --- a/parse.go +++ b/parse.go @@ -425,7 +425,6 @@ func (t *Template) parseInclude() Node { t.backup() pipe = t.expression("include") t.expect(itemRightDelim, "include invocation") - } return t.newInclude(name.Position(), t.lex.lineNumber(), name, pipe) @@ -480,7 +479,57 @@ func (t *Template) textOrAction() Node { } func (t *Template) filterControl() Node { - return t.newFilter(t.parseControl(true, "filter")) + return t.newFilter(t.parseControl(false, "filter")) +} + +func (t *Template) parseDefault() (pos Pos, line int, expression Expression, list *ListNode) { + line = t.lex.lineNumber() + + expression = t.operand() + pos = expression.Position() + t.nextNonSpace() + + if expression.Type() == NodeUndefined { + t.backup() + } + + var next Node + list, next = t.itemList() + switch next.Type() { + case nodeEnd: //done + } + return pos, line, expression, list +} + +func (t *Template) defaultControl() Node { + return t.newDefault(t.parseDefault()) +} + +func (t *Template) switchControl() Node { + return t.newSwitch(t.parseControl(false, "switch")) +} + +func (t *Template) parseCase() (pos Pos, line int, expression Expression, list *ListNode) { + line = t.lex.lineNumber() + + expression = t.operand() + pos = expression.Position() + t.nextNonSpace() + + if expression.Type() == NodeUndefined { + t.backup() + } + + var next Node + list, next = t.itemList() + switch next.Type() { + case nodeEnd: //done + } + return pos, line, expression, list +} + +func (t *Template) caseControl() Node { + return t.newCase(t.parseCase()) } func (t *Template) action() (n Node) { @@ -503,6 +552,12 @@ func (t *Template) action() (n Node) { return t.parseYield() case itemFilter: return t.filterControl() + case itemDefault: + return t.defaultControl() + case itemSwitch: + return t.switchControl() + case itemCase: + return t.caseControl() } t.backup() @@ -993,6 +1048,8 @@ func (t *Template) term() Node { t.error(err) } return t.newString(token.pos, token.val, s) + default: + return t.newUndefined(token.pos) } t.backup() return nil From 40c243c47adb4e51422fe0b124d22a5d01c645da Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Wed, 4 Jul 2018 12:35:49 +0200 Subject: [PATCH 06/11] fix(test files) --- eval_test.go | 2 +- utils/visitor.go | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/eval_test.go b/eval_test.go index d9dc5f7..1a05f51 100644 --- a/eval_test.go +++ b/eval_test.go @@ -174,7 +174,7 @@ func TestEvalActionNode(t *testing.T) { RunJetTest(t, data, nil, "actionNode_Add3Minus", `{{ 2+1+4-3 }}`, fmt.Sprint(2+1+4-3)) RunJetTest(t, data, nil, "actionNode_AddIntString", `{{ 2+"1" }}`, "3") - RunJetTest(t, data, nil, "actionNode_AddStringInt", `{{ "1"+2 }}`, "12") + RunJetTest(t, data, nil, "actionNode_AddStringInt", `{{ "1"+2 }}`, "3") RunJetTest(t, data, nil, "actionNode_NumberNegative", `{{ -5 }}`, "-5") RunJetTest(t, data, nil, "actionNode_NumberNegative_1", `{{ 1 + -5 }}`, fmt.Sprint(1+-5)) diff --git a/utils/visitor.go b/utils/visitor.go index 9c58ed7..f2b6f36 100644 --- a/utils/visitor.go +++ b/utils/visitor.go @@ -47,6 +47,12 @@ func (vc VisitorContext) Visit(node jet.Node) { vc.visitCommandNode(node) case *jet.FilterNode: vc.visitFilterNode(node) + case *jet.SwitchNode: + vc.visitSwitchNode(node) + case *jet.CaseNode: + vc.visitCaseNode(node) + case *jet.DefaultNode: + vc.visitDefaultNode(node) case *jet.IfNode: vc.visitIfNode(node) case *jet.PipeNode: @@ -127,10 +133,22 @@ func (vc VisitorContext) visitPipeNode(pipeNode *jet.PipeNode) { } } -func (vc VisitorContext) visitFilterNode(filterNode *jet.IfNode) { +func (vc VisitorContext) visitFilterNode(filterNode *jet.FilterNode) { vc.visitBranchNode(&filterNode.BranchNode) } +func (vc VisitorContext) visitDefaultNode(defaultNode *jet.DefaultNode) { + vc.visitBranchNode(&defaultNode.BranchNode) +} + +func (vc VisitorContext) visitSwitchNode(switchNode *jet.SwitchNode) { + vc.visitBranchNode(&switchNode.BranchNode) +} + +func (vc VisitorContext) visitCaseNode(caseNode *jet.CaseNode) { + vc.visitBranchNode(&caseNode.BranchNode) +} + func (vc VisitorContext) visitIfNode(ifNode *jet.IfNode) { vc.visitBranchNode(&ifNode.BranchNode) } From 5c617220154a3f5f63f8de98835de2e9c47a0d5f Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Mon, 9 Jul 2018 17:25:35 +0200 Subject: [PATCH 07/11] fix(nits) --- .gitignore | 2 +- eval.go | 29 ++++++----------------------- template.go | 4 ---- 3 files changed, 7 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index 3d72576..4befed3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ .DS_Store -.idea \ No newline at end of file +.idea diff --git a/eval.go b/eval.go index 18699bd..966bc2a 100644 --- a/eval.go +++ b/eval.go @@ -480,7 +480,6 @@ func (st *Runtime) executeSwitch(list *ListNode, value reflect.Value) { switch node.Type() { case NodeCase: node := node.(*CaseNode) - if node.Expression.Type() == NodeUndefined { defaultNode = node } else { @@ -507,13 +506,10 @@ func (st *Runtime) executeDefault(list *ListNode) { switch node.Type() { case NodeAction: node := node.(*ActionNode) - if node.Pipe != nil || node.Set == nil || node.Set.Let == true { node.errorf("unexpected data in default block") } - st.executeSetList(node.Set, true) - case NodeText: node := node.(*TextNode) for _, value := range node.String() { @@ -580,16 +576,11 @@ func (st *Runtime) executeList(list *ListNode) { } case NodeDefault: node := node.(*DefaultNode) - st.executeDefault(node.List) - case NodeSwitch: node := node.(*SwitchNode) - value := st.evalPrimaryExpressionGroup(node.Expression) - st.executeSwitch(node.List, value) - case NodeFilter: node := node.(*FilterNode) var isLet bool @@ -602,26 +593,18 @@ func (st *Runtime) executeList(list *ListNode) { st.executeSetList(node.Set, false) } } - mynode := st.evalPrimaryExpressionGroup(node.Expression) - optionText.SetValue(mynode.String()) - st.executeList(node.List) - out := optionText.FormatOutput() - _, err := st.Writer.Write(out) if err != nil { node.error(err) } - optionText.Reset() - if isLet { st.releaseScope() } - case NodeIf: node := node.(*IfNode) var isLet bool @@ -764,7 +747,7 @@ var ( valueBoolFALSE = reflect.ValueOf(false) ) -func ParseIndexExpr(baseExpression reflect.Value, indexExpression reflect.Value, indexType reflect.Type) (reflect.Value, error) { +func (st *Runtime) parseIndexExpr(baseExpression reflect.Value, indexExpression reflect.Value, indexType reflect.Type) (reflect.Value, error) { switch baseExpression.Kind() { case reflect.Map: key := baseExpression.Type().Key() @@ -793,7 +776,7 @@ func ParseIndexExpr(baseExpression reflect.Value, indexExpression reflect.Value, } return baseExpression, errors.New("non numeric value in index expression kind " + baseExpression.Kind().String()) case reflect.Interface: - return ParseIndexExpr(reflect.ValueOf(baseExpression.Interface()), indexExpression, indexType) + return st.parseIndexExpr(reflect.ValueOf(baseExpression.Interface()), indexExpression, indexType) } return baseExpression, errors.New("indexing is not supported in value type " + baseExpression.Kind().String()) } @@ -840,7 +823,7 @@ func (st *Runtime) evalPrimaryExpressionGroup(node Expression) reflect.Value { baseExpression = baseExpression.Elem() } - ret, err := ParseIndexExpr(baseExpression, indexExpression, indexType) + ret, err := st.parseIndexExpr(baseExpression, indexExpression, indexType) if err != nil { node.errorf(err.Error()) } @@ -889,7 +872,7 @@ func notNil(v reflect.Value) bool { } } -func ParseByType(baseExpression reflect.Value, indexExpression reflect.Value, indexType reflect.Type) (bool, error) { +func (st *Runtime) parseByType(baseExpression reflect.Value, indexExpression reflect.Value, indexType reflect.Type) (bool, error) { switch baseExpression.Kind() { case reflect.Map: key := baseExpression.Type().Key() @@ -918,7 +901,7 @@ func ParseByType(baseExpression reflect.Value, indexExpression reflect.Value, in return false, errors.New("non numeric value in index expression kind " + baseExpression.Kind().String()) } case reflect.Interface: - return ParseByType(reflect.ValueOf(baseExpression.Interface()), indexExpression, indexType) + return st.parseByType(reflect.ValueOf(baseExpression.Interface()), indexExpression, indexType) } return false, errors.New("indexing is not supported in value type " + baseExpression.Kind().String()) } @@ -945,7 +928,7 @@ func (st *Runtime) isSet(node Node) bool { baseExpression = baseExpression.Elem() } - ret, err := ParseByType(baseExpression, indexExpression, indexType) + ret, err := st.parseByType(baseExpression, indexExpression, indexType) if err != nil { node.errorf(err.Error()) } diff --git a/template.go b/template.go index 0c88d5c..8afaaf5 100644 --- a/template.go +++ b/template.go @@ -19,7 +19,6 @@ package jet import ( - "os" "fmt" "io" "io/ioutil" @@ -381,9 +380,6 @@ func (scope VarMap) SetWriter(name string, v SafeWriter) VarMap { // Execute executes the template in the w Writer func (t *Template) Execute(w io.Writer, variables VarMap, data interface{}) error { - if w == nil { - w = os.Stdout - } return t.ExecuteI18N(nil, w, variables, data) } From 0bdec2f345d563cd53bcfbac55ad5818b2bf81b8 Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Tue, 10 Jul 2018 17:30:51 +0200 Subject: [PATCH 08/11] add(size|default(20) --- constructors.go | 4 -- default.go | 6 ++- eval.go | 105 ++++++++++++++++++------------------------------ lex.go | 2 - node.go | 10 ----- parse.go | 6 --- 6 files changed, 44 insertions(+), 89 deletions(-) diff --git a/constructors.go b/constructors.go index d307208..828ef69 100644 --- a/constructors.go +++ b/constructors.go @@ -118,10 +118,6 @@ func (t *Template) newElse(pos Pos, line int) *elseNode { return &elseNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: nodeElse, Pos: pos, Line: line}} } -func (t *Template) newDefault(pos Pos, line int, pipe Expression, list *ListNode) *DefaultNode { - return &DefaultNode{BranchNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeDefault, Pos: pos, Line: line}, Expression: pipe, List: list}} -} - func (t *Template) newFilter(pos Pos, line int, set *SetNode, pipe Expression, list, elseList *ListNode) *FilterNode { return &FilterNode{BranchNode{NodeBase: NodeBase{TemplateName: t.Name, NodeType: NodeFilter, Pos: pos, Line: line}, Set: set, Expression: pipe, List: list, ElseList: elseList}} } diff --git a/default.go b/default.go index 5056d0e..3669caa 100644 --- a/default.go +++ b/default.go @@ -52,9 +52,13 @@ func init() { "unsafe": reflect.ValueOf(SafeWriter(unsafePrinter)), "writeJson": reflect.ValueOf(jsonRenderer), "json": reflect.ValueOf(json.Marshal), + "default": reflect.ValueOf(Func(func(a Arguments) reflect.Value { + a.RequireNumOfArguments("default", 1, -1) + return a.Get(0) + })), "format": reflect.ValueOf(Func(func(a Arguments) reflect.Value { a.RequireNumOfArguments("format", 1, -1) - return reflect.ValueOf("format:" + a.Get(0).String()) + return a.Get(0) })), "isset": reflect.ValueOf(Func(func(a Arguments) reflect.Value { a.RequireNumOfArguments("isset", 1, -1) diff --git a/eval.go b/eval.go index 966bc2a..a5d07ba 100644 --- a/eval.go +++ b/eval.go @@ -193,25 +193,6 @@ func (state *Runtime) setValue(name string, val reflect.Value) bool { return true } -func (state *Runtime) getAssignedValue(name string) (reflect.Value, error) { - sc := state.scope - - // try to resolve variables in the current scope - _, ok := sc.variables[name] - - // if not found walks parent scopes - for !ok && sc.parent != nil { - sc = sc.parent - _, ok = sc.variables[name] - } - - if ok { - return sc.variables[name], nil - } - - return reflect.ValueOf(nil), errors.New("Variable " + name + " is not set") -} - // Resolve resolves a value from the execution context func (state *Runtime) Resolve(name string) reflect.Value { @@ -259,11 +240,8 @@ func (st *Runtime) recover(err *error) { } func (st *Runtime) executeSet(left Expression, right reflect.Value, isdefault bool) { - if isdefault == true { - _, err := st.getAssignedValue(left.String()) - if err == nil { - return - } + if isdefault == true && st.evalDefaultPrimaryExpression(left) == false { + return } typ := left.Type() if typ == NodeIdentifier { @@ -320,21 +298,32 @@ func (st *Runtime) executeSetList(set *SetNode, isdefault bool) { } } +func (st *Runtime) executeLet(key Expression, value reflect.Value, isdefault bool) { + if isdefault == true && st.evalDefaultPrimaryExpression(key) == false { + fmt.Printf("RETUNR") + return + } + if st.variables == nil { + st.variables = make(VarMap) + } + st.variables[key.(*IdentifierNode).Ident] = value +} + func (st *Runtime) executeLetList(set *SetNode) { if set.IndexExprGetLookup { value := st.evalPrimaryExpressionGroup(set.Right[0]) - st.variables[set.Left[0].(*IdentifierNode).Ident] = value + st.executeLet(set.Left[0], value, false) if value.IsValid() { - st.variables[set.Left[1].(*IdentifierNode).Ident] = valueBoolTRUE + st.executeLet(set.Left[1], valueBoolTRUE, false) } else { - st.variables[set.Left[1].(*IdentifierNode).Ident] = valueBoolFALSE + st.executeLet(set.Left[1], valueBoolFALSE, false) } } else { for i := 0; i < len(set.Left); i++ { - st.variables[set.Left[i].(*IdentifierNode).Ident] = st.evalPrimaryExpressionGroup(set.Right[i]) + st.executeLet(set.Left[i], st.evalPrimaryExpressionGroup(set.Right[i]), false) } } } @@ -434,17 +423,11 @@ func (ot *TextFilter) SetText(src []byte) { ot.text = append(ot.text, src...) } -func (ot *TextFilter) SetValue(src string) { +func (ot *TextFilter) SetValue(value reflect.Value) { + src := value.String() if src != "" { - tocompare := "format:" - if strings.HasPrefix(src, tocompare) { - pos := strings.Index(src, tocompare) - if pos > -1 { - src = src[len(tocompare):len(src)] - } - ot.action = FilterFormat - ot.value = src - } + ot.action = FilterFormat + ot.value = src } } @@ -500,29 +483,6 @@ func (st *Runtime) executeSwitch(list *ListNode, value reflect.Value) { } } -func (st *Runtime) executeDefault(list *ListNode) { - for i := 0; i < len(list.Nodes); i++ { - node := list.Nodes[i] - switch node.Type() { - case NodeAction: - node := node.(*ActionNode) - if node.Pipe != nil || node.Set == nil || node.Set.Let == true { - node.errorf("unexpected data in default block") - } - st.executeSetList(node.Set, true) - case NodeText: - node := node.(*TextNode) - for _, value := range node.String() { - if value != '\n' && value != '\t' && value != ' ' { - node.errorf("unexpected data in default block") - } - } - default: - node.errorf("unexpected data in default block") - } - } -} - func (st *Runtime) executeList(list *ListNode) { inNewSCOPE := false @@ -574,9 +534,6 @@ func (st *Runtime) executeList(list *ListNode) { } } } - case NodeDefault: - node := node.(*DefaultNode) - st.executeDefault(node.List) case NodeSwitch: node := node.(*SwitchNode) value := st.evalPrimaryExpressionGroup(node.Expression) @@ -593,8 +550,9 @@ func (st *Runtime) executeList(list *ListNode) { st.executeSetList(node.Set, false) } } - mynode := st.evalPrimaryExpressionGroup(node.Expression) - optionText.SetValue(mynode.String()) + + value := st.evalPrimaryExpressionGroup(node.Expression) + optionText.SetValue(value) st.executeList(node.List) out := optionText.FormatOutput() _, err := st.Writer.Write(out) @@ -1398,7 +1356,22 @@ func (st *Runtime) evalCommandPipeExpression(node *CommandNode, value reflect.Va return term, false } +func (st *Runtime) evalDefaultPrimaryExpression(myexpr Expression) (ret bool) { + defer func() { + if r := recover(); r != nil { + ret = true + } + }() + st.evalPrimaryExpressionGroup(myexpr) + return false +} + func (st *Runtime) evalPipelineExpression(node *PipeNode) (value reflect.Value, safeWriter bool) { + if len(node.Cmds) == 2 && strings.HasPrefix(node.Cmds[1].BaseExpr.String(), "default") { + value = st.evalPrimaryExpressionGroup(node.Cmds[1].BaseExpr) + st.executeLet(node.Cmds[0].BaseExpr, value, true) + return reflect.ValueOf(nil), true + } value, safeWriter = st.evalCommandExpression(node.Cmds[0]) for i := 1; i < len(node.Cmds); i++ { if safeWriter { diff --git a/lex.go b/lex.go index de932a0..be1cf2a 100644 --- a/lex.go +++ b/lex.go @@ -101,7 +101,6 @@ const ( itemMSG itemTrans itemFilter - itemDefault itemSwitch itemCase ) @@ -110,7 +109,6 @@ var key = map[string]itemType{ "extends": itemExtends, "import": itemImport, - "default": itemDefault, "include": itemInclude, "block": itemBlock, "yield": itemYield, diff --git a/node.go b/node.go index 0c37eff..fb9dd95 100644 --- a/node.go +++ b/node.go @@ -79,7 +79,6 @@ const ( nodeEnd //An end action. Not added to tree. NodeField //A field or method name. NodeIdentifier //An identifier; always a function name. - NodeDefault //A default action NodeCase //A case action NodeSwitch //A switch action NodeFilter //A filter action @@ -592,15 +591,6 @@ func (t *YieldNode) String() string { return fmt.Sprintf("{{yield %s(%s) %s}}", t.Name, t.Parameters, t.Expression) } -// DefaultNode represents a {{default}} action and its commands. -type DefaultNode struct { - BranchNode -} - -func (t *DefaultNode) String() string { - return "{{default}}" -} - // IncludeNode represents a {{include }} action. type IncludeNode struct { NodeBase diff --git a/parse.go b/parse.go index 86b0705..a74f1c9 100644 --- a/parse.go +++ b/parse.go @@ -501,10 +501,6 @@ func (t *Template) parseDefault() (pos Pos, line int, expression Expression, lis return pos, line, expression, list } -func (t *Template) defaultControl() Node { - return t.newDefault(t.parseDefault()) -} - func (t *Template) switchControl() Node { return t.newSwitch(t.parseControl(false, "switch")) } @@ -552,8 +548,6 @@ func (t *Template) action() (n Node) { return t.parseYield() case itemFilter: return t.filterControl() - case itemDefault: - return t.defaultControl() case itemSwitch: return t.switchControl() case itemCase: From 801cbfe081a83a3bc0a018295bae1f82f565e686 Mon Sep 17 00:00:00 2001 From: Maxime Leroy Date: Wed, 11 Jul 2018 11:30:35 +0200 Subject: [PATCH 09/11] fix(format, default) --- eval.go | 52 +++++++++++++++++++++++++++++------------------- utils/visitor.go | 6 ------ 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/eval.go b/eval.go index a5d07ba..4997cf9 100644 --- a/eval.go +++ b/eval.go @@ -300,7 +300,6 @@ func (st *Runtime) executeSetList(set *SetNode, isdefault bool) { func (st *Runtime) executeLet(key Expression, value reflect.Value, isdefault bool) { if isdefault == true && st.evalDefaultPrimaryExpression(key) == false { - fmt.Printf("RETUNR") return } if st.variables == nil { @@ -540,19 +539,18 @@ func (st *Runtime) executeList(list *ListNode) { st.executeSwitch(node.List, value) case NodeFilter: node := node.(*FilterNode) - var isLet bool - if node.Set != nil { - if node.Set.Let { - isLet = true - st.newScope() - st.executeLetList(node.Set) - } else { - st.executeSetList(node.Set, false) - } + value := st.evalPrimaryExpressionGroup(node.Expression) + pos := strings.Index(node.Expression.String(), "(") + if pos <= -1 { + node.errorf("unexpected error") + } + funcname := node.Expression.String()[0:pos] + + switch funcname { + case "format": + optionText.SetValue(value) } - value := st.evalPrimaryExpressionGroup(node.Expression) - optionText.SetValue(value) st.executeList(node.List) out := optionText.FormatOutput() _, err := st.Writer.Write(out) @@ -560,9 +558,6 @@ func (st *Runtime) executeList(list *ListNode) { node.error(err) } optionText.Reset() - if isLet { - st.releaseScope() - } case NodeIf: node := node.(*IfNode) var isLet bool @@ -1367,12 +1362,29 @@ func (st *Runtime) evalDefaultPrimaryExpression(myexpr Expression) (ret bool) { } func (st *Runtime) evalPipelineExpression(node *PipeNode) (value reflect.Value, safeWriter bool) { - if len(node.Cmds) == 2 && strings.HasPrefix(node.Cmds[1].BaseExpr.String(), "default") { - value = st.evalPrimaryExpressionGroup(node.Cmds[1].BaseExpr) - st.executeLet(node.Cmds[0].BaseExpr, value, true) - return reflect.ValueOf(nil), true + + for i := 0; i < len(node.Cmds); i++ { + if strings.HasPrefix(node.Cmds[i].BaseExpr.String(), "default") { + if i < 1 && value.IsValid() == false { + node.errorf("wrong default order, value should be placed before") + } + if value.IsValid() == false && st.evalDefaultPrimaryExpression(node.Cmds[i-1].BaseExpr) == false { + value = st.evalPrimaryExpressionGroup(node.Cmds[i-1].BaseExpr) + node.Cmds = append(node.Cmds[:i-1], node.Cmds[i+1:]...) + } else { + if value.IsValid() == false { + value = st.evalPrimaryExpressionGroup(node.Cmds[i].BaseExpr) + } + node.Cmds = append(node.Cmds[:i], node.Cmds[i+1:]...) + } + i = 0 + } } - value, safeWriter = st.evalCommandExpression(node.Cmds[0]) + + if value.IsValid() == false { + value, safeWriter = st.evalCommandExpression(node.Cmds[0]) + } + for i := 1; i < len(node.Cmds); i++ { if safeWriter { node.Cmds[i].errorf("unexpected command %s, writer command should be the last command", node.Cmds[i]) diff --git a/utils/visitor.go b/utils/visitor.go index f2b6f36..9acad08 100644 --- a/utils/visitor.go +++ b/utils/visitor.go @@ -51,8 +51,6 @@ func (vc VisitorContext) Visit(node jet.Node) { vc.visitSwitchNode(node) case *jet.CaseNode: vc.visitCaseNode(node) - case *jet.DefaultNode: - vc.visitDefaultNode(node) case *jet.IfNode: vc.visitIfNode(node) case *jet.PipeNode: @@ -137,10 +135,6 @@ func (vc VisitorContext) visitFilterNode(filterNode *jet.FilterNode) { vc.visitBranchNode(&filterNode.BranchNode) } -func (vc VisitorContext) visitDefaultNode(defaultNode *jet.DefaultNode) { - vc.visitBranchNode(&defaultNode.BranchNode) -} - func (vc VisitorContext) visitSwitchNode(switchNode *jet.SwitchNode) { vc.visitBranchNode(&switchNode.BranchNode) } From 03bf8f67ee0f49d8cc0fe3c92a68b34009348b2f Mon Sep 17 00:00:00 2001 From: Daniel Lohse Date: Sat, 21 Jul 2018 11:04:41 +0200 Subject: [PATCH 10/11] Minor non-code fixes to whitespace --- lex.go | 3 +-- node.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lex.go b/lex.go index be1cf2a..7b6ee09 100644 --- a/lex.go +++ b/lex.go @@ -117,8 +117,7 @@ var key = map[string]itemType{ "end": itemEnd, "if": itemIf, "switch": itemSwitch, - - "case": itemCase, + "case": itemCase, "range": itemRange, "nil": itemNil, diff --git a/node.go b/node.go index fb9dd95..02fcbf9 100644 --- a/node.go +++ b/node.go @@ -675,7 +675,7 @@ func (s *CallExprNode) String() string { return fmt.Sprintf("%s(%s)", s.BaseExpr, arguments) } -// TernaryExprNod represents a ternary expression, +// TernaryExprNode represents a ternary expression, // ex: expression '?' expression ':' expression type TernaryExprNode struct { NodeBase From 26f4d762e11d4af1756b725ab68362ffce6be49b Mon Sep 17 00:00:00 2001 From: Daniel Lohse Date: Sat, 21 Jul 2018 11:05:45 +0200 Subject: [PATCH 11/11] Add basic unit tests for switch/case parsing and evaluation --- eval_test.go | 10 ++++++++++ parse_test.go | 1 + testData/switch_case.jet | 7 +++++++ 3 files changed, 18 insertions(+) create mode 100644 testData/switch_case.jet diff --git a/eval_test.go b/eval_test.go index 1a05f51..6dfb1a7 100644 --- a/eval_test.go +++ b/eval_test.go @@ -293,6 +293,16 @@ func TestEvalRangeNode(t *testing.T) { RunJetTest(t, data, nil, "Range_ExpressionValueIf", `{{range i, user:=users}}

{{if i == 0 || i == 2}}{{i}}: {{end}}{{user.Name}}{{user.Email}}

{{end}}`, resultString2) } +func TestEvalSwitchNode(t *testing.T) { + var data = make(VarMap) + data.Set("a", 10) + RunJetTest(t, data, nil, "Switch_Expression_1", `{{switch a}}{{case 10}}a is 10{{end}}{{end}}`, "a is 10") + RunJetTest(t, data, nil, "Switch_Expression_2", `{{switch a}}{{end}}`, "") + RunJetTest(t, data, nil, "Switch_Expression_3", `{{switch a}}{{case 15}}a is 15{{end}}{{case}}a is something else: {{a}}{{end}}{{end}}`, "a is something else: 10") + RunJetTest(t, data, nil, "Switch_Expression_4", `{{switch a}}text before first case is ignored{{case 10}}a is 10{{end}}{{end}}`, "a is 10") + RunJetTest(t, data, nil, "Switch_Expression_5", `{{switch a}}{* comments before first case are ignored *}{{case 10}}a is 10{{end}}{{end}}`, "a is 10") +} + func TestEvalDefaultFuncs(t *testing.T) { RunJetTest(t, nil, nil, "DefaultFuncs_safeHtml", `

{{"

Hello Buddy!

" |safeHtml}}`, `

<h1>Hello Buddy!</h1>

`) RunJetTest(t, nil, nil, "DefaultFuncs_safeHtml2", `

{{safeHtml: "

Hello Buddy!

"}}`, `

<h1>Hello Buddy!</h1>

`) diff --git a/parse_test.go b/parse_test.go index fb83ad4..4878584 100644 --- a/parse_test.go +++ b/parse_test.go @@ -74,6 +74,7 @@ func TestParseTemplateControl(t *testing.T) { p := ParserTestCase{T: t} p.TestPrintFile("if.jet") p.TestPrintFile("range.jet") + p.TestPrintFile("switch_case.jet") } func TestParseTemplateExpressions(t *testing.T) { diff --git a/testData/switch_case.jet b/testData/switch_case.jet new file mode 100644 index 0000000..6d8c11c --- /dev/null +++ b/testData/switch_case.jet @@ -0,0 +1,7 @@ +{{ switch a }}{{ end }} +{{ switch a }}{{ case 10 }}a is 10{{ end }}{{ end }} +{{ switch a }}{{ case 20 }}a is 20{{ end }}{{ case }}a is something else{{ end }}{{ end }} +=== +{{switch a}}{{end}} +{{switch a}}{{case 10}}a is 10{{end}}{{end}} +{{switch a}}{{case 20}}a is 20{{end}}{{case }}a is something else{{end}}{{end}}