From ce7e5cc980218feb8d13b714b71d3deac468698f Mon Sep 17 00:00:00 2001 From: Daniel Kang <me@daniel.gs> Date: Wed, 9 Jan 2019 17:18:37 -0800 Subject: [PATCH] type conversion builtin functions: string(), int(), bool(), float(), char() --- ast/undefined_lit.go | 21 ++++++++ compiler/compiler.go | 4 ++ objects/array.go | 2 +- objects/builtin_convert.go | 104 +++++++++++++++++++++++++++++++++++++ objects/builtins.go | 20 +++++++ objects/char.go | 6 +-- objects/float.go | 3 +- objects/map.go | 2 +- objects/undefined.go | 2 + parser/parser.go | 15 +++--- token/tokens.go | 2 + vm/vm_builtin_test.go | 56 ++++++++++++++++++++ 12 files changed, 224 insertions(+), 13 deletions(-) create mode 100644 ast/undefined_lit.go create mode 100644 objects/builtin_convert.go diff --git a/ast/undefined_lit.go b/ast/undefined_lit.go new file mode 100644 index 0000000..8d0c7d2 --- /dev/null +++ b/ast/undefined_lit.go @@ -0,0 +1,21 @@ +package ast + +import "github.com/d5/tengo/scanner" + +type UndefinedLit struct { + TokenPos scanner.Pos +} + +func (e *UndefinedLit) exprNode() {} + +func (e *UndefinedLit) Pos() scanner.Pos { + return e.TokenPos +} + +func (e *UndefinedLit) End() scanner.Pos { + return e.TokenPos + 9 // len(undefined) == 9 +} + +func (e *UndefinedLit) String() string { + return "undefined" +} diff --git a/compiler/compiler.go b/compiler/compiler.go index 73ae8de..55dc00b 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -162,6 +162,10 @@ func (c *Compiler) Compile(node ast.Node) error { } case *ast.StringLit: c.emit(OpConstant, c.addConstant(&objects.String{Value: node.Value})) + case *ast.CharLit: + c.emit(OpConstant, c.addConstant(&objects.Char{Value: node.Value})) + case *ast.UndefinedLit: + c.emit(OpNull) case *ast.UnaryExpr: if err := c.Compile(node.Expr); err != nil { return err diff --git a/objects/array.go b/objects/array.go index 5cc3fdf..4b6f55d 100644 --- a/objects/array.go +++ b/objects/array.go @@ -46,7 +46,7 @@ func (o *Array) Copy() Object { } func (o *Array) IsFalsy() bool { - return false + return len(o.Value) == 0 } func (o *Array) Equals(x Object) bool { diff --git a/objects/builtin_convert.go b/objects/builtin_convert.go new file mode 100644 index 0000000..c1d6cca --- /dev/null +++ b/objects/builtin_convert.go @@ -0,0 +1,104 @@ +package objects + +import ( + "errors" + "strconv" +) + +func builtinString(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, errors.New("wrong number of arguments") + } + + switch arg := args[0].(type) { + case *String: + return arg, nil + case *Undefined: + return undefined, nil + default: + return &String{Value: arg.String()}, nil + } +} + +func builtinInt(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, errors.New("wrong number of arguments") + } + + switch arg := args[0].(type) { + case *Int: + return arg, nil + case *Float: + return &Int{Value: int64(arg.Value)}, nil + case *Char: + return &Int{Value: int64(arg.Value)}, nil + case *Bool: + if arg.Value { + return &Int{Value: 1}, nil + } + return &Int{Value: 0}, nil + case *String: + n, err := strconv.ParseInt(arg.Value, 10, 64) + if err == nil { + return &Int{Value: n}, nil + } + } + + return undefined, nil +} + +func builtinFloat(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, errors.New("wrong number of arguments") + } + + switch arg := args[0].(type) { + case *Float: + return arg, nil + case *Int: + return &Float{Value: float64(arg.Value)}, nil + case *String: + f, err := strconv.ParseFloat(arg.Value, 64) + if err == nil { + return &Float{Value: f}, nil + } + } + + return undefined, nil +} + +func builtinBool(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, errors.New("wrong number of arguments") + } + + switch arg := args[0].(type) { + case *Bool: + return arg, nil + default: + return &Bool{Value: !arg.IsFalsy()}, nil + } +} + +func builtinChar(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, errors.New("wrong number of arguments") + } + + switch arg := args[0].(type) { + case *Char: + return arg, nil + case *Int: + return &Char{Value: rune(arg.Value)}, nil + case *String: + rs := []rune(arg.Value) + switch len(rs) { + case 0: + return &Char{}, nil + case 1: + return &Char{Value: rs[0]}, nil + } + } + + return undefined, nil +} diff --git a/objects/builtins.go b/objects/builtins.go index 4f83b58..6dad33b 100644 --- a/objects/builtins.go +++ b/objects/builtins.go @@ -22,4 +22,24 @@ var Builtins = []struct { Name: "append", Func: builtinAppend, }, + { + Name: "string", + Func: builtinString, + }, + { + Name: "int", + Func: builtinInt, + }, + { + Name: "bool", + Func: builtinBool, + }, + { + Name: "float", + Func: builtinFloat, + }, + { + Name: "char", + Func: builtinChar, + }, } diff --git a/objects/char.go b/objects/char.go index a674894..340e3c5 100644 --- a/objects/char.go +++ b/objects/char.go @@ -1,8 +1,6 @@ package objects import ( - "fmt" - "github.com/d5/tengo/token" ) @@ -11,7 +9,7 @@ type Char struct { } func (o *Char) String() string { - return fmt.Sprintf("%q", string(o.Value)) + return string(o.Value) } func (o *Char) TypeName() string { @@ -27,7 +25,7 @@ func (o *Char) Copy() Object { } func (o *Char) IsFalsy() bool { - return false + return o.Value == 0 } func (o *Char) Equals(x Object) bool { diff --git a/objects/float.go b/objects/float.go index 0714477..ecbe8a4 100644 --- a/objects/float.go +++ b/objects/float.go @@ -1,6 +1,7 @@ package objects import ( + "math" "strconv" "github.com/d5/tengo/token" @@ -68,7 +69,7 @@ func (o *Float) Copy() Object { } func (o *Float) IsFalsy() bool { - return false + return math.IsNaN(o.Value) } func (o *Float) Equals(x Object) bool { diff --git a/objects/map.go b/objects/map.go index 260661e..a0eab33 100644 --- a/objects/map.go +++ b/objects/map.go @@ -38,7 +38,7 @@ func (o *Map) Copy() Object { } func (o *Map) IsFalsy() bool { - return false + return len(o.Value) == 0 } func (o *Map) Get(key string) (Object, bool) { diff --git a/objects/undefined.go b/objects/undefined.go index 98b27b8..61a035f 100644 --- a/objects/undefined.go +++ b/objects/undefined.go @@ -2,6 +2,8 @@ package objects import "github.com/d5/tengo/token" +var undefined = &Undefined{} + type Undefined struct{} func (o Undefined) TypeName() string { diff --git a/parser/parser.go b/parser/parser.go index eb4a50a..37439df 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -4,7 +4,6 @@ import ( "fmt" "io" "strconv" - "unicode/utf8" "github.com/d5/tengo/ast" "github.com/d5/tengo/scanner" @@ -286,9 +285,8 @@ func (p *Parser) parseOperand() ast.Expr { return x case token.Char: - v, _ := utf8.DecodeRuneInString(p.tokenLit) x := &ast.CharLit{ - Value: v, + Value: rune(p.tokenLit[1]), ValuePos: p.pos, Literal: p.tokenLit, } @@ -323,6 +321,11 @@ func (p *Parser) parseOperand() ast.Expr { p.next() return x + case token.Undefined: + x := &ast.UndefinedLit{TokenPos: p.pos} + p.next() + return x + case token.LParen: lparen := p.pos p.next() @@ -485,10 +488,10 @@ func (p *Parser) parseStmt() (stmt ast.Stmt) { } switch p.token { - case // simple statements + case // simple statements token.Func, token.Ident, token.Int, token.Float, token.Char, token.String, token.True, token.False, token.LParen, // operands - token.LBrace, token.LBrack, // composite types - token.Add, token.Sub, token.Mul, token.And, token.Xor, token.Not: // unary operators + token.LBrace, token.LBrack, // composite types + token.Add, token.Sub, token.Mul, token.And, token.Xor, token.Not: // unary operators s := p.parseSimpleStmt(false) p.expectSemi() return s diff --git a/token/tokens.go b/token/tokens.go index 2fd549f..19e2f19 100644 --- a/token/tokens.go +++ b/token/tokens.go @@ -78,6 +78,7 @@ const ( True False In + Undefined _keywordEnd ) @@ -150,6 +151,7 @@ var tokens = [...]string{ True: "true", False: "false", In: "in", + Undefined: "undefined", } func (tok Token) String() string { diff --git a/vm/vm_builtin_test.go b/vm/vm_builtin_test.go index 098639c..aa2f028 100644 --- a/vm/vm_builtin_test.go +++ b/vm/vm_builtin_test.go @@ -17,4 +17,60 @@ func TestBuiltinFunction(t *testing.T) { expect(t, `out = append([1, 2, 3], 4)`, ARR{1, 2, 3, 4}) expect(t, `out = append([1, 2, 3], 4, 5, 6)`, ARR{1, 2, 3, 4, 5, 6}) expect(t, `out = append([1, 2, 3], "foo", false)`, ARR{1, 2, 3, "foo", false}) + + expect(t, `out = int(1)`, 1) + expect(t, `out = int(1.8)`, 1) + expect(t, `out = int("-522")`, -522) + expect(t, `out = int(true)`, 1) + expect(t, `out = int(false)`, 0) + expect(t, `out = int('8')`, 56) + expect(t, `out = int([1])`, undefined()) + expect(t, `out = int({a: 1})`, undefined()) + expect(t, `out = int(undefined)`, undefined()) + + expect(t, `out = string(1)`, "1") + expect(t, `out = string(1.8)`, "1.8") + expect(t, `out = string("-522")`, "-522") + expect(t, `out = string(true)`, "true") + expect(t, `out = string(false)`, "false") + expect(t, `out = string('8')`, "8") + expect(t, `out = string([1,8.1,true,3])`, "[1, 8.1, true, 3]") + expect(t, `out = string({b: "foo"})`, `{b: "foo"}`) + expect(t, `out = string(undefined)`, undefined()) // not "undefined" + + expect(t, `out = float(1)`, 1.0) + expect(t, `out = float(1.8)`, 1.8) + expect(t, `out = float("-52.2")`, -52.2) + expect(t, `out = float(true)`, undefined()) + expect(t, `out = float(false)`, undefined()) + expect(t, `out = float('8')`, undefined()) + expect(t, `out = float([1,8.1,true,3])`, undefined()) + expect(t, `out = float({a: 1, b: "foo"})`, undefined()) + expect(t, `out = float(undefined)`, undefined()) + + expect(t, `out = char(56)`, '8') + expect(t, `out = char(1.8)`, undefined()) + expect(t, `out = char("-52.2")`, undefined()) + expect(t, `out = char(true)`, undefined()) + expect(t, `out = char(false)`, undefined()) + expect(t, `out = char('8')`, '8') + expect(t, `out = char([1,8.1,true,3])`, undefined()) + expect(t, `out = char({a: 1, b: "foo"})`, undefined()) + expect(t, `out = char(undefined)`, undefined()) + + expect(t, `out = bool(1)`, true) // non-zero integer: true + expect(t, `out = bool(0)`, false) // zero: true + expect(t, `out = bool(1.8)`, true) // all floats (except for NaN): true + expect(t, `out = bool(0.0)`, true) // all floats (except for NaN): true + expect(t, `out = bool("false")`, true) // non-empty string: true + expect(t, `out = bool("")`, false) // empty string: false + expect(t, `out = bool(true)`, true) // true: true + expect(t, `out = bool(false)`, false) // false: false + expect(t, `out = bool('8')`, true) // non-zero chars: true + expect(t, `out = bool(char(0))`, false) // zero char: false + expect(t, `out = bool([1])`, true) // non-empty arrays: true + expect(t, `out = bool([])`, false) // empty array: false + expect(t, `out = bool({a: 1})`, true) // non-empty maps: true + expect(t, `out = bool({})`, false) // empty maps: false + expect(t, `out = bool(undefined)`, false) // undefined: false }