mirror of
https://github.com/hanzoai/expr.git
synced 2026-09-24 16:58:58 +00:00
Add String() to nodes
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func Dump(node Node) string {
|
||||
return dump(reflect.ValueOf(node), "")
|
||||
}
|
||||
|
||||
func dump(v reflect.Value, ident string) string {
|
||||
if !v.IsValid() {
|
||||
return "nil"
|
||||
}
|
||||
t := v.Type()
|
||||
switch t.Kind() {
|
||||
case reflect.Struct:
|
||||
out := t.Name() + "{\n"
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if isPrivate(f.Name) {
|
||||
continue
|
||||
}
|
||||
s := v.Field(i)
|
||||
out += fmt.Sprintf("%v%v: %v,\n", ident+"\t", f.Name, dump(s, ident+"\t"))
|
||||
}
|
||||
return out + ident + "}"
|
||||
case reflect.Slice:
|
||||
if v.Len() == 0 {
|
||||
return t.String() + "{}"
|
||||
}
|
||||
out := t.String() + "{\n"
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
s := v.Index(i)
|
||||
out += fmt.Sprintf("%v%v,", ident+"\t", dump(s, ident+"\t"))
|
||||
if i+1 < v.Len() {
|
||||
out += "\n"
|
||||
}
|
||||
}
|
||||
return out + "\n" + ident + "}"
|
||||
case reflect.Ptr:
|
||||
return dump(v.Elem(), ident)
|
||||
case reflect.Interface:
|
||||
return dump(reflect.ValueOf(v.Interface()), ident)
|
||||
|
||||
case reflect.String:
|
||||
return fmt.Sprintf("%q", v)
|
||||
default:
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
|
||||
var isCapital = regexp.MustCompile("^[A-Z]")
|
||||
|
||||
func isPrivate(s string) bool {
|
||||
return !isCapital.Match([]byte(s))
|
||||
}
|
||||
@@ -14,6 +14,7 @@ type Node interface {
|
||||
SetLocation(file.Location)
|
||||
Type() reflect.Type
|
||||
SetType(reflect.Type)
|
||||
String() string
|
||||
}
|
||||
|
||||
func Patch(node *Node, newNode Node) {
|
||||
|
||||
+147
-44
@@ -1,59 +1,162 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/antonmedv/expr/parser/operator"
|
||||
"github.com/antonmedv/expr/parser/utils"
|
||||
)
|
||||
|
||||
func Dump(node Node) string {
|
||||
return dump(reflect.ValueOf(node), "")
|
||||
func (n *NilNode) String() string {
|
||||
return "nil"
|
||||
}
|
||||
|
||||
func dump(v reflect.Value, ident string) string {
|
||||
if !v.IsValid() {
|
||||
func (n *IdentifierNode) String() string {
|
||||
return n.Value
|
||||
}
|
||||
|
||||
func (n *IntegerNode) String() string {
|
||||
return fmt.Sprintf("%d", n.Value)
|
||||
}
|
||||
|
||||
func (n *FloatNode) String() string {
|
||||
return fmt.Sprintf("%v", n.Value)
|
||||
}
|
||||
|
||||
func (n *BoolNode) String() string {
|
||||
return fmt.Sprintf("%t", n.Value)
|
||||
}
|
||||
|
||||
func (n *StringNode) String() string {
|
||||
return fmt.Sprintf("%q", n.Value)
|
||||
}
|
||||
|
||||
func (n *ConstantNode) String() string {
|
||||
if n.Value == nil {
|
||||
return "nil"
|
||||
}
|
||||
t := v.Type()
|
||||
switch t.Kind() {
|
||||
case reflect.Struct:
|
||||
out := t.Name() + "{\n"
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if isPrivate(f.Name) {
|
||||
continue
|
||||
}
|
||||
s := v.Field(i)
|
||||
out += fmt.Sprintf("%v%v: %v,\n", ident+"\t", f.Name, dump(s, ident+"\t"))
|
||||
}
|
||||
return out + ident + "}"
|
||||
case reflect.Slice:
|
||||
if v.Len() == 0 {
|
||||
return t.String() + "{}"
|
||||
}
|
||||
out := t.String() + "{\n"
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
s := v.Index(i)
|
||||
out += fmt.Sprintf("%v%v,", ident+"\t", dump(s, ident+"\t"))
|
||||
if i+1 < v.Len() {
|
||||
out += "\n"
|
||||
}
|
||||
}
|
||||
return out + "\n" + ident + "}"
|
||||
case reflect.Ptr:
|
||||
return dump(v.Elem(), ident)
|
||||
case reflect.Interface:
|
||||
return dump(reflect.ValueOf(v.Interface()), ident)
|
||||
|
||||
case reflect.String:
|
||||
return fmt.Sprintf("%q", v)
|
||||
default:
|
||||
return fmt.Sprintf("%v", v)
|
||||
b, err := json.Marshal(n.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
var isCapital = regexp.MustCompile("^[A-Z]")
|
||||
func (n *UnaryNode) String() string {
|
||||
op := ""
|
||||
if n.Operator == "not" {
|
||||
op = fmt.Sprintf("%s ", n.Operator)
|
||||
} else {
|
||||
op = fmt.Sprintf("%s", n.Operator)
|
||||
}
|
||||
if _, ok := n.Node.(*BinaryNode); ok {
|
||||
return fmt.Sprintf("%s(%s)", op, n.Node.String())
|
||||
}
|
||||
return fmt.Sprintf("%s%s", op, n.Node.String())
|
||||
}
|
||||
|
||||
func isPrivate(s string) bool {
|
||||
return !isCapital.Match([]byte(s))
|
||||
func (n *BinaryNode) String() string {
|
||||
var left, right string
|
||||
if b, ok := n.Left.(*BinaryNode); ok && operator.Less(b.Operator, n.Operator) {
|
||||
left = fmt.Sprintf("(%s)", n.Left.String())
|
||||
} else {
|
||||
left = n.Left.String()
|
||||
}
|
||||
if b, ok := n.Right.(*BinaryNode); ok && operator.Less(b.Operator, n.Operator) {
|
||||
right = fmt.Sprintf("(%s)", n.Right.String())
|
||||
} else {
|
||||
right = n.Right.String()
|
||||
}
|
||||
return fmt.Sprintf("%s %s %s", left, n.Operator, right)
|
||||
}
|
||||
|
||||
func (n *ChainNode) String() string {
|
||||
return n.Node.String()
|
||||
}
|
||||
|
||||
func (n *MemberNode) String() string {
|
||||
if n.Optional {
|
||||
if str, ok := n.Property.(*StringNode); ok && utils.IsValidIdentifier(str.Value) {
|
||||
return fmt.Sprintf("%s?.%s", n.Node.String(), str.Value)
|
||||
} else {
|
||||
return fmt.Sprintf("get(%s, %s)", n.Node.String(), n.Property.String())
|
||||
}
|
||||
}
|
||||
if str, ok := n.Property.(*StringNode); ok && utils.IsValidIdentifier(str.Value) {
|
||||
if _, ok := n.Node.(*PointerNode); ok {
|
||||
return fmt.Sprintf(".%s", str.Value)
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", n.Node.String(), str.Value)
|
||||
}
|
||||
return fmt.Sprintf("%s[%s]", n.Node.String(), n.Property.String())
|
||||
}
|
||||
|
||||
func (n *SliceNode) String() string {
|
||||
return fmt.Sprintf("%s[%s:%s]", n.Node.String(), n.From.String(), n.To.String())
|
||||
}
|
||||
|
||||
func (n *CallNode) String() string {
|
||||
arguments := make([]string, len(n.Arguments))
|
||||
for i, arg := range n.Arguments {
|
||||
arguments[i] = arg.String()
|
||||
}
|
||||
return fmt.Sprintf("%s(%s)", n.Callee.String(), strings.Join(arguments, ", "))
|
||||
}
|
||||
|
||||
func (n *BuiltinNode) String() string {
|
||||
arguments := make([]string, len(n.Arguments))
|
||||
for i, arg := range n.Arguments {
|
||||
arguments[i] = arg.String()
|
||||
}
|
||||
return fmt.Sprintf("%s(%s)", n.Name, strings.Join(arguments, ", "))
|
||||
}
|
||||
|
||||
func (n *ClosureNode) String() string {
|
||||
return n.Node.String()
|
||||
}
|
||||
|
||||
func (n *PointerNode) String() string {
|
||||
return "#"
|
||||
}
|
||||
|
||||
func (n *ConditionalNode) String() string {
|
||||
var cond, exp1, exp2 string
|
||||
if _, ok := n.Cond.(*ConditionalNode); ok {
|
||||
cond = fmt.Sprintf("(%s)", n.Cond.String())
|
||||
} else {
|
||||
cond = n.Cond.String()
|
||||
}
|
||||
if _, ok := n.Exp1.(*ConditionalNode); ok {
|
||||
exp1 = fmt.Sprintf("(%s)", n.Exp1.String())
|
||||
} else {
|
||||
exp1 = n.Exp1.String()
|
||||
}
|
||||
if _, ok := n.Exp2.(*ConditionalNode); ok {
|
||||
exp2 = fmt.Sprintf("(%s)", n.Exp2.String())
|
||||
} else {
|
||||
exp2 = n.Exp2.String()
|
||||
}
|
||||
return fmt.Sprintf("%s ? %s : %s", cond, exp1, exp2)
|
||||
}
|
||||
|
||||
func (n *ArrayNode) String() string {
|
||||
nodes := make([]string, len(n.Nodes))
|
||||
for i, node := range n.Nodes {
|
||||
nodes[i] = node.String()
|
||||
}
|
||||
return fmt.Sprintf("[%s]", strings.Join(nodes, ", "))
|
||||
}
|
||||
|
||||
func (n *MapNode) String() string {
|
||||
pairs := make([]string, len(n.Pairs))
|
||||
for i, pair := range n.Pairs {
|
||||
pairs[i] = pair.String()
|
||||
}
|
||||
return fmt.Sprintf("{%s}", strings.Join(pairs, ", "))
|
||||
}
|
||||
|
||||
func (n *PairNode) String() string {
|
||||
return fmt.Sprintf("%s: %s", n.Key.String(), n.Value.String())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package ast_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/antonmedv/expr/ast"
|
||||
"github.com/antonmedv/expr/parser"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPrint(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{`nil`, `nil`},
|
||||
{`true`, `true`},
|
||||
{`false`, `false`},
|
||||
{`1`, `1`},
|
||||
{`1.1`, `1.1`},
|
||||
{`"a"`, `"a"`},
|
||||
{`'a'`, `"a"`},
|
||||
{`a`, `a`},
|
||||
{`a.b`, `a.b`},
|
||||
{`a[0]`, `a[0]`},
|
||||
{`a["the b"]`, `a["the b"]`},
|
||||
{`a.b[0]`, `a.b[0]`},
|
||||
{`a?.b`, `a?.b`},
|
||||
{`-a`, `-a`},
|
||||
{`!a`, `!a`},
|
||||
{`not a`, `not a`},
|
||||
{`a + b`, `a + b`},
|
||||
{`a + b * c`, `a + b * c`},
|
||||
{`(a + b) * c`, `(a + b) * c`},
|
||||
{`a * (b + c)`, `a * (b + c)`},
|
||||
{`-(a + b) * c`, `-(a + b) * c`},
|
||||
{`a == b`, `a == b`},
|
||||
{`a matches b`, `a matches b`},
|
||||
{`a in b`, `a in b`},
|
||||
{`a not in b`, `not (a in b)`},
|
||||
{`a and b`, `a and b`},
|
||||
{`a or b`, `a or b`},
|
||||
{`a or b and c`, `a or b and c`},
|
||||
{`a or (b and c)`, `a or b and c`},
|
||||
{`(a or b) and c`, `(a or b) and c`},
|
||||
{`a ? b : c`, `a ? b : c`},
|
||||
{`a ? b : c ? d : e`, `a ? b : (c ? d : e)`},
|
||||
{`(a ? b : c) ? d : e`, `(a ? b : c) ? d : e`},
|
||||
{`a ? (b ? c : d) : e`, `a ? (b ? c : d) : e`},
|
||||
{`func()`, `func()`},
|
||||
{`func(a)`, `func(a)`},
|
||||
{`func(a, b)`, `func(a, b)`},
|
||||
{`{}`, `{}`},
|
||||
{`{a: b}`, `{"a": b}`},
|
||||
{`{a: b, c: d}`, `{"a": b, "c": d}`},
|
||||
{`[]`, `[]`},
|
||||
{`[a]`, `[a]`},
|
||||
{`[a, b]`, `[a, b]`},
|
||||
{`len(a)`, `len(a)`},
|
||||
{`map(a, # > 0)`, `map(a, # > 0)`},
|
||||
{`map(a, {# > 0})`, `map(a, # > 0)`},
|
||||
{`map(a, .b)`, `map(a, .b)`},
|
||||
{`a.b()`, `a.b()`},
|
||||
{`a.b(c)`, `a.b(c)`},
|
||||
{`a[1:-1]`, `a[1:-1]`},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
tree, err := parser.Parse(tt.input)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, tt.want, tree.Node.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrint_MemberNode(t *testing.T) {
|
||||
node := &ast.MemberNode{
|
||||
Node: &ast.IdentifierNode{
|
||||
Value: "a",
|
||||
},
|
||||
Property: &ast.StringNode{Value: "b c"},
|
||||
Optional: true,
|
||||
}
|
||||
require.Equal(t, `get(a, "b c")`, node.String())
|
||||
}
|
||||
|
||||
func TestPrint_ConstantNode(t *testing.T) {
|
||||
tests := []struct {
|
||||
input interface{}
|
||||
want string
|
||||
}{
|
||||
{nil, `nil`},
|
||||
{true, `true`},
|
||||
{false, `false`},
|
||||
{1, `1`},
|
||||
{1.1, `1.1`},
|
||||
{"a", `"a"`},
|
||||
{[]int{1, 2, 3}, `[1,2,3]`},
|
||||
{map[string]int{"a": 1}, `{"a":1}`},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.want, func(t *testing.T) {
|
||||
node := &ast.ConstantNode{
|
||||
Value: tt.input,
|
||||
}
|
||||
require.Equal(t, tt.want, node.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package lexer
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/antonmedv/expr/parser/utils"
|
||||
)
|
||||
|
||||
type stateFn func(*lexer) stateFn
|
||||
@@ -11,7 +13,7 @@ func root(l *lexer) stateFn {
|
||||
case r == eof:
|
||||
l.emitEOF()
|
||||
return nil
|
||||
case IsSpace(r):
|
||||
case utils.IsSpace(r):
|
||||
l.ignore()
|
||||
return root
|
||||
case r == '\'' || r == '"':
|
||||
@@ -43,7 +45,7 @@ func root(l *lexer) stateFn {
|
||||
case r == '.':
|
||||
l.backup()
|
||||
return dot
|
||||
case IsAlphaNumeric(r):
|
||||
case utils.IsAlphaNumeric(r):
|
||||
l.backup()
|
||||
return identifier
|
||||
default:
|
||||
@@ -91,7 +93,7 @@ func (l *lexer) scanNumber() bool {
|
||||
l.acceptRun(digits)
|
||||
}
|
||||
// Next thing mustn't be alphanumeric.
|
||||
if IsAlphaNumeric(l.peek()) {
|
||||
if utils.IsAlphaNumeric(l.peek()) {
|
||||
l.next()
|
||||
return false
|
||||
}
|
||||
@@ -113,7 +115,7 @@ func identifier(l *lexer) stateFn {
|
||||
loop:
|
||||
for {
|
||||
switch r := l.next(); {
|
||||
case IsAlphaNumeric(r):
|
||||
case utils.IsAlphaNumeric(r):
|
||||
// absorb
|
||||
default:
|
||||
l.backup()
|
||||
@@ -141,7 +143,7 @@ func not(l *lexer) stateFn {
|
||||
// Get the next word.
|
||||
for {
|
||||
r := l.next()
|
||||
if IsAlphaNumeric(r) {
|
||||
if utils.IsAlphaNumeric(r) {
|
||||
// absorb
|
||||
} else {
|
||||
l.backup()
|
||||
|
||||
@@ -3,22 +3,9 @@ package lexer
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func IsSpace(r rune) bool {
|
||||
return unicode.IsSpace(r)
|
||||
}
|
||||
|
||||
func IsAlphaNumeric(r rune) bool {
|
||||
return IsAlphabetic(r) || unicode.IsDigit(r)
|
||||
}
|
||||
|
||||
func IsAlphabetic(r rune) bool {
|
||||
return r == '_' || r == '$' || unicode.IsLetter(r)
|
||||
}
|
||||
|
||||
var (
|
||||
newlineNormalizer = strings.NewReplacer("\r\n", "\n", "\r", "\n")
|
||||
)
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package operator
|
||||
|
||||
type Associativity int
|
||||
|
||||
const (
|
||||
Left Associativity = iota + 1
|
||||
Right
|
||||
)
|
||||
|
||||
type Operator struct {
|
||||
Precedence int
|
||||
Associativity Associativity
|
||||
}
|
||||
|
||||
func Less(a, b string) bool {
|
||||
return Binary[a].Precedence < Binary[b].Precedence
|
||||
}
|
||||
|
||||
var Unary = map[string]Operator{
|
||||
"not": {50, Left},
|
||||
"!": {50, Left},
|
||||
"-": {90, Left},
|
||||
"+": {90, Left},
|
||||
}
|
||||
|
||||
var Binary = map[string]Operator{
|
||||
"|": {0, Left},
|
||||
"or": {10, Left},
|
||||
"||": {10, Left},
|
||||
"and": {15, Left},
|
||||
"&&": {15, Left},
|
||||
"==": {20, Left},
|
||||
"!=": {20, Left},
|
||||
"<": {20, Left},
|
||||
">": {20, Left},
|
||||
">=": {20, Left},
|
||||
"<=": {20, Left},
|
||||
"in": {20, Left},
|
||||
"matches": {20, Left},
|
||||
"contains": {20, Left},
|
||||
"startsWith": {20, Left},
|
||||
"endsWith": {20, Left},
|
||||
"..": {25, Left},
|
||||
"+": {30, Left},
|
||||
"-": {30, Left},
|
||||
"*": {60, Left},
|
||||
"/": {60, Left},
|
||||
"%": {60, Left},
|
||||
"**": {100, Right},
|
||||
"^": {100, Right},
|
||||
"??": {500, Left},
|
||||
}
|
||||
+10
-72
@@ -4,61 +4,15 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
. "github.com/antonmedv/expr/ast"
|
||||
"github.com/antonmedv/expr/builtin"
|
||||
"github.com/antonmedv/expr/file"
|
||||
. "github.com/antonmedv/expr/parser/lexer"
|
||||
"github.com/antonmedv/expr/parser/operator"
|
||||
"github.com/antonmedv/expr/parser/utils"
|
||||
)
|
||||
|
||||
type associativity int
|
||||
|
||||
const (
|
||||
left associativity = iota + 1
|
||||
right
|
||||
)
|
||||
|
||||
type operator struct {
|
||||
precedence int
|
||||
associativity associativity
|
||||
}
|
||||
|
||||
var unaryOperators = map[string]operator{
|
||||
"not": {50, left},
|
||||
"!": {50, left},
|
||||
"-": {90, left},
|
||||
"+": {90, left},
|
||||
}
|
||||
|
||||
var binaryOperators = map[string]operator{
|
||||
"|": {0, left},
|
||||
"or": {10, left},
|
||||
"||": {10, left},
|
||||
"and": {15, left},
|
||||
"&&": {15, left},
|
||||
"==": {20, left},
|
||||
"!=": {20, left},
|
||||
"<": {20, left},
|
||||
">": {20, left},
|
||||
">=": {20, left},
|
||||
"<=": {20, left},
|
||||
"in": {20, left},
|
||||
"matches": {20, left},
|
||||
"contains": {20, left},
|
||||
"startsWith": {20, left},
|
||||
"endsWith": {20, left},
|
||||
"..": {25, left},
|
||||
"+": {30, left},
|
||||
"-": {30, left},
|
||||
"*": {60, left},
|
||||
"/": {60, left},
|
||||
"%": {60, left},
|
||||
"**": {100, right},
|
||||
"^": {100, right},
|
||||
"??": {500, left},
|
||||
}
|
||||
|
||||
var predicates = map[string]struct {
|
||||
arity int
|
||||
}{
|
||||
@@ -172,8 +126,8 @@ func (p *parser) parseExpression(precedence int) Node {
|
||||
opToken = p.current
|
||||
}
|
||||
|
||||
if op, ok := binaryOperators[opToken.Value]; ok {
|
||||
if op.precedence >= precedence {
|
||||
if op, ok := operator.Binary[opToken.Value]; ok {
|
||||
if op.Precedence >= precedence {
|
||||
p.next()
|
||||
|
||||
if opToken.Value == "|" {
|
||||
@@ -187,10 +141,10 @@ func (p *parser) parseExpression(precedence int) Node {
|
||||
}
|
||||
|
||||
var nodeRight Node
|
||||
if op.associativity == left {
|
||||
nodeRight = p.parseExpression(op.precedence + 1)
|
||||
if op.Associativity == operator.Left {
|
||||
nodeRight = p.parseExpression(op.Precedence + 1)
|
||||
} else {
|
||||
nodeRight = p.parseExpression(op.precedence)
|
||||
nodeRight = p.parseExpression(op.Precedence)
|
||||
}
|
||||
|
||||
nodeLeft = &BinaryNode{
|
||||
@@ -253,9 +207,9 @@ func (p *parser) parsePrimary() Node {
|
||||
token := p.current
|
||||
|
||||
if token.Is(Operator) {
|
||||
if op, ok := unaryOperators[token.Value]; ok {
|
||||
if op, ok := operator.Unary[token.Value]; ok {
|
||||
p.next()
|
||||
expr := p.parseExpression(op.precedence)
|
||||
expr := p.parseExpression(op.Precedence)
|
||||
node := &UnaryNode{
|
||||
Operator: token.Value,
|
||||
Node: expr,
|
||||
@@ -510,7 +464,7 @@ func (p *parser) parsePostfixExpression(node Node) Node {
|
||||
|
||||
if propertyToken.Kind != Identifier &&
|
||||
// Operators like "not" and "matches" are valid methods or property names.
|
||||
(propertyToken.Kind != Operator || !isValidIdentifier(propertyToken.Value)) {
|
||||
(propertyToken.Kind != Operator || !utils.IsValidIdentifier(propertyToken.Value)) {
|
||||
p.error("expected name")
|
||||
}
|
||||
|
||||
@@ -662,19 +616,3 @@ func (p *parser) parseArguments() []Node {
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
func isValidIdentifier(str string) bool {
|
||||
if len(str) == 0 {
|
||||
return false
|
||||
}
|
||||
h, w := utf8.DecodeRuneInString(str)
|
||||
if !IsAlphabetic(h) {
|
||||
return false
|
||||
}
|
||||
for _, r := range str[w:] {
|
||||
if !IsAlphaNumeric(r) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func IsValidIdentifier(str string) bool {
|
||||
if len(str) == 0 {
|
||||
return false
|
||||
}
|
||||
h, w := utf8.DecodeRuneInString(str)
|
||||
if !IsAlphabetic(h) {
|
||||
return false
|
||||
}
|
||||
for _, r := range str[w:] {
|
||||
if !IsAlphaNumeric(r) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func IsSpace(r rune) bool {
|
||||
return unicode.IsSpace(r)
|
||||
}
|
||||
|
||||
func IsAlphaNumeric(r rune) bool {
|
||||
return IsAlphabetic(r) || unicode.IsDigit(r)
|
||||
}
|
||||
|
||||
func IsAlphabetic(r rune) bool {
|
||||
return r == '_' || r == '$' || unicode.IsLetter(r)
|
||||
}
|
||||
Reference in New Issue
Block a user