Skip to content

Commit

Permalink
add more builtin function and give same error
Browse files Browse the repository at this point in the history
  • Loading branch information
gravataLonga committed Jul 2, 2022
1 parent 2f5de21 commit 64ae552
Show file tree
Hide file tree
Showing 17 changed files with 393 additions and 127 deletions.
122 changes: 79 additions & 43 deletions evaluator/builtins_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package evaluator

import (
"fmt"
"ninja/object"
"testing"
)
Expand All @@ -14,68 +15,103 @@ func TestBuiltinFunctions(t *testing.T) {
{`len("")`, 0, false},
{`len("four")`, 4, false},
{`len("hello world")`, 11, false},
{`len(1)`, "argument to `len` not supported, got INTEGER", false},
{`len("one", "two")`, "wrong number of arguments. got=2, want=1", false},
{`len(1)`, "TypeError: len() expected argument to be `ARRAY,STRING` got `INTEGER`", false},
{`len("one", "two")`, "TypeError: len() takes exactly 1 argument (2 given)", false},
{`len([1, 2, 3])`, 3, false},
{`len([])`, 0, false},
{`puts("hello", "world!")`, nil, true},
{`first([1, 2, 3])`, 1, false},
{`first([])`, nil, false},
{`first(1)`, "argument to `first` must be ARRAY, got INTEGER", false},
{`first(1)`, "TypeError: first() expected argument #1 to be `ARRAY` got `INTEGER`", false},
{`last([1, 2, 3])`, 3, false},
{`last([])`, nil, false},
{`last(1)`, "argument to `last` must be ARRAY, got INTEGER", false},
{`last(1)`, "TypeError: last() expected argument #1 to be `ARRAY` got `INTEGER`", false},
{`rest([1, 2, 3])`, []int{2, 3}, false},
{`rest([])`, nil, false},
{`push([], 1)`, []int{1}, false},
{`push(1, 1)`, "argument to `push` must be ARRAY, got INTEGER", false},
{`push(1, 1)`, "TypeError: push() expected argument #1 to be `ARRAY` got `INTEGER`", false},
}

for _, tt := range tests {
evaluated := testEval(tt.input, t)
for i, tt := range tests {
t.Run(fmt.Sprintf("TestBuiltinFunctions[%d]", i), func(t *testing.T) {
evaluated := testEval(tt.input, t)

switch expected := tt.expected.(type) {
case int:
testIntegerObject(t, evaluated, int64(expected))
case nil:
if tt.nullable {
if evaluated != nil {
t.Errorf("Test must return nil. Got: %T", evaluated)
switch expected := tt.expected.(type) {
case int:
testIntegerObject(t, evaluated, int64(expected))
case nil:
if tt.nullable {
if evaluated != nil {
t.Fatalf("Test must return nil. Got: %T", evaluated)
}
} else {
testNullObject(t, evaluated)
}

continue
}
testNullObject(t, evaluated)

case string:
errObj, ok := evaluated.(*object.Error)
if !ok {
t.Errorf("object is not Error. got=%T (%+v)",
evaluated, evaluated)
continue
}
if errObj.Message != expected {
t.Errorf("wrong error message. expected=%q, got=%q",
expected, errObj.Message)
}
case []int:
array, ok := evaluated.(*object.Array)
if !ok {
t.Errorf("obj not Array. got=%T (%+v)", evaluated, evaluated)
continue
}
case string:
errObj, ok := evaluated.(*object.Error)
if !ok {
t.Fatalf("object is not Error. got=%T (%+v)",
evaluated, evaluated)
}
if errObj.Message != expected {
t.Errorf("wrong error message. expected=%q, got=%q",
expected, errObj.Message)
}
case []int:
array, ok := evaluated.(*object.Array)
if !ok {
t.Fatalf("obj not Array. got=%T (%+v)", evaluated, evaluated)
}

if len(array.Elements) != len(expected) {
t.Errorf("wrong num of elements. want=%d, got=%d",
len(expected), len(array.Elements))
continue
}
if len(array.Elements) != len(expected) {
t.Fatalf("wrong num of elements. want=%d, got=%d",
len(expected), len(array.Elements))
}

for i, expectedElem := range expected {
testIntegerObject(t, array.Elements[i], int64(expectedElem))
for i, expectedElem := range expected {
testIntegerObject(t, array.Elements[i], int64(expectedElem))
}
}
})
}
}

func TestBuiltinTime(t *testing.T) {
evaluated := testEval(`time()`, t)

if _, ok := evaluated.(*object.Integer); !ok {
t.Fatalf("builtin time() expected got integer. Got: %T", evaluated)
}
}

func TestBuiltinRand(t *testing.T) {
evaluated := testEval(`rand()`, t)

if _, ok := evaluated.(*object.Float); !ok {
t.Fatalf("builtin rand() expected got float. Got: %T", evaluated)
}
}

func TestArgs(t *testing.T) {
object.Arguments = []string{"test", "hello"}
input := `args();`

evaluated := testEval(input, t)

arr, ok := evaluated.(*object.Array)
if !ok {
t.Fatalf("TestArgs expected array got %s", evaluated.Type())
}

if len(arr.Elements) != 2 {
t.Errorf("Elements expect to be 2 length. Got: %d", len(arr.Elements))
}

}
arg1, _ := arr.Elements[0].(*object.String)
arg2, _ := arr.Elements[1].(*object.String)

if arg1.Value != "test" || arg2.Value != "hello" {
t.Errorf("state of env isn't equal")
}
}
76 changes: 51 additions & 25 deletions evaluator/float_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package evaluator

import (
"fmt"
"ninja/object"
"testing"
)
Expand Down Expand Up @@ -35,9 +36,11 @@ func TestEvalFloatExpression(t *testing.T) {
{`var add = function() {return 1.2;}; [add(), add()][0] + 1.3`, 2.5},
}

for _, tt := range tests {
evaluated := testEval(tt.input, t)
testFloatObject(t, evaluated, tt.expected)
for i, tt := range tests {
t.Run(fmt.Sprintf("TestEvalFloatExpression[%d]", i), func(t *testing.T) {
evaluated := testEval(tt.input, t)
testFloatObject(t, evaluated, tt.expected)
})
}
}

Expand Down Expand Up @@ -80,18 +83,20 @@ func TestErrorFloatHandling(t *testing.T) {
},
}

for _, tt := range tests {
evaluated := testEval(tt.input, t)
for i, tt := range tests {
t.Run(fmt.Sprintf("TestErrorFloatHandling[%d]", i), func(t *testing.T) {
evaluated := testEval(tt.input, t)

errObj, ok := evaluated.(*object.Error)
if !ok {
t.Errorf("no error object returned. got=%T(%+v)", evaluated, evaluated)
continue
}
errObj, ok := evaluated.(*object.Error)
if !ok {
t.Fatalf("no error object returned. got=%T(%+v)", evaluated, evaluated)
}

if errObj.Message != tt.expectedMessage {
t.Errorf("wrong error message. expected=%q, got=%q", tt.expectedMessage, errObj.Message)
}
})

if errObj.Message != tt.expectedMessage {
t.Errorf("wrong error message. expected=%q, got=%q", tt.expectedMessage, errObj.Message)
}
}
}

Expand All @@ -112,12 +117,26 @@ func TestFloatMethod(t *testing.T) {
`-1.1.abs()`,
1.1,
},
{
`0.8.round()`,
1.0,
},
{
`0.4.round()`,
0.0,
},
{
`0.05.round()`,
0.0,
},
}

for _, tt := range tests {
evaluated := testEval(tt.input, t)
for i, tt := range tests {
t.Run(fmt.Sprintf("TestFloatMethod[%d]", i), func(t *testing.T) {
evaluated := testEval(tt.input, t)

testObjectLiteral(t, evaluated, tt.expected)
testObjectLiteral(t, evaluated, tt.expected)
})
}
}

Expand All @@ -138,18 +157,25 @@ func TestFloatMethodWrongUsage(t *testing.T) {
`1.1.abs(1)`,
"method abs not accept any arguments. got: [1]",
},
{
`1.1.round(1)`,
"method round not accept any arguments. got: [1]",
},
}

for _, tt := range tests {
evaluated := testEval(tt.input, t)
for i, tt := range tests {
t.Run(fmt.Sprintf("TestFloatMethodWrongUsage[%d]", i), func(t *testing.T) {
evaluated := testEval(tt.input, t)

errObj, ok := evaluated.(*object.Error)
if !ok {
t.Fatalf("no error object returned. got=%T(%+v)", evaluated, evaluated)
}

errObj, ok := evaluated.(*object.Error)
if !ok {
t.Fatalf("no error object returned. got=%T(%+v)", evaluated, evaluated)
}
if errObj.Message != tt.expectedErrorMessage {
t.Errorf("erro expected \"%s\". Got: %s", tt.expectedErrorMessage, errObj.Message)
}
})

if errObj.Message != tt.expectedErrorMessage {
t.Errorf("erro expected \"%s\". Got: %s", tt.expectedErrorMessage, errObj.Message)
}
}
}
2 changes: 1 addition & 1 deletion evaluator/string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func TestStringMethodSplit(t *testing.T) {
`[a;b, c;d]`,
},
{
`"a;b,c;d" + "other,nice".split(",")`,
`("a;b,c;d" + "other,nice").split(",")`,
`[a;b, c;dother, nice]`,
},
{
Expand Down
4 changes: 4 additions & 0 deletions object/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ func (e *Error) Inspect() string { return "ERROR: " + e.Message }
func NewErrorFormat(format string, a ...interface{}) *Error {
return &Error{Message: fmt.Sprintf(format, a...)}
}

func NewError(message string) *Error {
return &Error{Message: message}
}
9 changes: 7 additions & 2 deletions object/float.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,13 @@ func (f *Float) Call(objectCall *ast.ObjectCall, method string, env *Environment
argStr := InspectArguments(args...)
return NewErrorFormat("method abs not accept any arguments. got: %s", argStr)
}
var absT float64 = math.Abs(f.Value)
return &Float{Value: absT}
return &Float{Value: math.Abs(f.Value)}
case "round":
if len(args) > 0 {
argStr := InspectArguments(args...)
return NewErrorFormat("method round not accept any arguments. got: %s", argStr)
}
return &Float{Value: math.Round(f.Value)}
}
return NewErrorFormat("method %s not exists on integer object.", method)

Expand Down
18 changes: 8 additions & 10 deletions object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,18 @@ type Hashable interface {
}

const (
NULL_OBJ = "NULL"
ERROR_OBJ = "ERROR"

NULL_OBJ = "NULL"
ERROR_OBJ = "ERROR"
RETURN_VALUE_OBJ = "RETURN_VALUE"
BREAK_VALUE_OBJ = "BREAK_VALUE"
FUNCTION_OBJ = "FUNCTION"
BUILTIN_OBJ = "BUILTIN"

INTEGER_OBJ = "INTEGER"
FLOAT_OBJ = "FLOAT"
BOOLEAN_OBJ = "BOOLEAN"
STRING_OBJ = "STRING"
ARRAY_OBJ = "ARRAY"
HASH_OBJ = "HASH"
INTEGER_OBJ = "INTEGER"
FLOAT_OBJ = "FLOAT"
BOOLEAN_OBJ = "BOOLEAN"
STRING_OBJ = "STRING"
ARRAY_OBJ = "ARRAY"
HASH_OBJ = "HASH"
)

func IsError(o Object) bool {
Expand Down
10 changes: 10 additions & 0 deletions object/state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package object

import "io"

var (
Arguments []string
StandardInput io.Reader
StandardOutput io.Writer
ExitFunction func(int)
)
24 changes: 24 additions & 0 deletions stdlib/args.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package stdlib

import (
"ninja/object"
"ninja/typing"
)

func Args(args ...object.Object) object.Object {

err := typing.Check(
"args", args,
typing.ExactArgs(0),
)

if err != nil {
return object.NewError(err.Error())
}

elements := make([]object.Object, len(object.Arguments))
for i, arg := range object.Arguments {
elements[i] = &object.String{Value: arg}
}
return &object.Array{Elements: elements}
}
Loading

0 comments on commit 64ae552

Please sign in to comment.