From 3c94bde4dcd53c9d24836e46b8cd8a7ee639d628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Barzowski?= Date: Thu, 7 Sep 2017 11:56:11 -0400 Subject: [PATCH] String indexing, change of string representation (#34) * String indexing, change of string representation Also std.pow --- builtins.go | 35 +++++++++---- interpreter.go | 13 +++-- testdata/pow.golden | 1 + testdata/pow.input | 1 + testdata/pow2.golden | 1 + testdata/pow2.input | 1 + testdata/pow3.golden | 1 + testdata/pow3.input | 1 + testdata/pow4.golden | 1 + testdata/pow4.input | 1 + testdata/pow5.golden | 1 + testdata/pow5.input | 1 + testdata/pow6.golden | 1 + testdata/pow6.input | 2 + testdata/pow7.golden | 1 + testdata/pow7.input | 2 + testdata/std_substr.golden | 1 + testdata/std_substr.input | 1 + testdata/string_comparison1.golden | 1 + testdata/string_comparison1.input | 1 + testdata/string_comparison2.golden | 1 + testdata/string_comparison2.input | 1 + testdata/string_comparison3.golden | 1 + testdata/string_comparison3.input | 1 + testdata/string_comparison4.golden | 1 + testdata/string_comparison4.input | 1 + testdata/string_comparison5.golden | 1 + testdata/string_comparison5.input | 1 + testdata/string_comparison6.golden | 1 + testdata/string_comparison6.input | 1 + testdata/string_comparison7.golden | 1 + testdata/string_comparison7.input | 1 + testdata/string_index.golden | 1 + testdata/string_index.input | 1 + testdata/string_index2.golden | 1 + testdata/string_index2.input | 1 + testdata/string_index_negative.golden | 1 + testdata/string_index_negative.input | 1 + testdata/string_index_out_of_bounds.golden | 1 + testdata/string_index_out_of_bounds.input | 1 + value.go | 58 +++++++++++++++++++++- 41 files changed, 128 insertions(+), 18 deletions(-) create mode 100644 testdata/pow.golden create mode 100644 testdata/pow.input create mode 100644 testdata/pow2.golden create mode 100644 testdata/pow2.input create mode 100644 testdata/pow3.golden create mode 100644 testdata/pow3.input create mode 100644 testdata/pow4.golden create mode 100644 testdata/pow4.input create mode 100644 testdata/pow5.golden create mode 100644 testdata/pow5.input create mode 100644 testdata/pow6.golden create mode 100644 testdata/pow6.input create mode 100644 testdata/pow7.golden create mode 100644 testdata/pow7.input create mode 100644 testdata/std_substr.golden create mode 100644 testdata/std_substr.input create mode 100644 testdata/string_comparison1.golden create mode 100644 testdata/string_comparison1.input create mode 100644 testdata/string_comparison2.golden create mode 100644 testdata/string_comparison2.input create mode 100644 testdata/string_comparison3.golden create mode 100644 testdata/string_comparison3.input create mode 100644 testdata/string_comparison4.golden create mode 100644 testdata/string_comparison4.input create mode 100644 testdata/string_comparison5.golden create mode 100644 testdata/string_comparison5.input create mode 100644 testdata/string_comparison6.golden create mode 100644 testdata/string_comparison6.input create mode 100644 testdata/string_comparison7.golden create mode 100644 testdata/string_comparison7.input create mode 100644 testdata/string_index.golden create mode 100644 testdata/string_index.input create mode 100644 testdata/string_index2.golden create mode 100644 testdata/string_index2.input create mode 100644 testdata/string_index_negative.golden create mode 100644 testdata/string_index_negative.input create mode 100644 testdata/string_index_out_of_bounds.golden create mode 100644 testdata/string_index_out_of_bounds.input diff --git a/builtins.go b/builtins.go index 0f0e7d0ef..7096ffb35 100644 --- a/builtins.go +++ b/builtins.go @@ -50,7 +50,7 @@ func builtinPlus(e *evaluator, xp, yp potentialValue) (value, error) { if err != nil { return nil, err } - return makeValueString(left.value + right.value), nil + return concatStrings(left, right), nil case valueObject: right, err := e.evaluateObject(yp) if err != nil { @@ -74,7 +74,7 @@ func builtinMinus(e *evaluator, xp, yp potentialValue) (value, error) { return makeValueNumber(x.value - y.value), nil } -func builtinGreater(e *evaluator, xp, yp potentialValue) (value, error) { +func builtinLess(e *evaluator, xp, yp potentialValue) (value, error) { x, err := e.evaluate(xp) if err != nil { return nil, err @@ -85,20 +85,20 @@ func builtinGreater(e *evaluator, xp, yp potentialValue) (value, error) { if err != nil { return nil, err } - return makeValueBoolean(left.value > right.value), nil + return makeValueBoolean(left.value < right.value), nil case *valueString: right, err := e.evaluateString(yp) if err != nil { return nil, err } - return makeValueBoolean(left.value > right.value), nil + return makeValueBoolean(stringLessThan(left, right)), nil default: return nil, e.typeErrorGeneral(x) } } -func builtinLess(e *evaluator, xp, yp potentialValue) (value, error) { - return builtinGreater(e, yp, xp) +func builtinGreater(e *evaluator, xp, yp potentialValue) (value, error) { + return builtinLess(e, yp, xp) } func builtinGreaterEq(e *evaluator, xp, yp potentialValue) (value, error) { @@ -144,7 +144,7 @@ func builtinLength(e *evaluator, xp potentialValue) (value, error) { case *valueArray: num = len(x.elements) case *valueString: - num = len(x.value) + num = x.length() case *valueFunction: num = len(x.parameters()) default: @@ -273,7 +273,7 @@ func primitiveEquals(e *evaluator, xp potentialValue, yp potentialValue) (value, if err != nil { return nil, err } - return makeValueBoolean(left.value == right.value), nil + return makeValueBoolean(stringEqual(left, right)), nil case *valueNull: return makeValueBoolean(true), nil case *valueFunction: @@ -358,13 +358,25 @@ func builtinObjectHasEx(e *evaluator, objp potentialValue, fnamep potentialValue return nil, err } for _, fieldname := range objectFields(obj, hidden.value) { - if fieldname == fname.value { + if fieldname == string(fname.value) { return makeValueBoolean(true), nil } } return makeValueBoolean(false), nil } +func builtinPow(e *evaluator, basep potentialValue, expp potentialValue) (value, error) { + base, err := e.evaluateNumber(basep) + if err != nil { + return nil, err + } + exp, err := e.evaluateNumber(expp) + if err != nil { + return nil, err + } + return makeDoubleCheck(e, math.Pow(base.value, exp.value)) +} + type unaryBuiltin func(*evaluator, potentialValue) (value, error) type binaryBuiltin func(*evaluator, potentialValue, potentialValue) (value, error) type ternaryBuiltin func(*evaluator, potentialValue, potentialValue, potentialValue) (value, error) @@ -451,8 +463,8 @@ var bopBuiltins = []*BinaryBuiltin{ ast.BopLess: &BinaryBuiltin{name: "operator<,", function: builtinLess, parameters: ast.Identifiers{"x", "y"}}, ast.BopLessEq: &BinaryBuiltin{name: "operator<=", function: builtinLessEq, parameters: ast.Identifiers{"x", "y"}}, - ast.BopManifestEqual: todo, - ast.BopManifestUnequal: todo, + // bopManifestEqual: , + // bopManifestUnequal: , ast.BopBitwiseAnd: todo, ast.BopBitwiseXor: todo, @@ -490,4 +502,5 @@ var funcBuiltins = map[string]evalCallable{ "atan": &UnaryBuiltin{name: "atan", function: builtinAtan, parameters: ast.Identifiers{"x"}}, "log": &UnaryBuiltin{name: "log", function: builtinLog, parameters: ast.Identifiers{"x"}}, "exp": &UnaryBuiltin{name: "exp", function: builtinExp, parameters: ast.Identifiers{"x"}}, + "pow": &BinaryBuiltin{name: "pow", function: builtinPow, parameters: ast.Identifiers{"base", "exp"}}, } diff --git a/interpreter.go b/interpreter.go index 095ac4074..4a8607d48 100644 --- a/interpreter.go +++ b/interpreter.go @@ -313,7 +313,7 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error) var fieldName string switch fieldNameValue := fieldNameValue.(type) { case *valueString: - fieldName = fieldNameValue.value + fieldName = fieldNameValue.getString() case *valueNull: // Omitted field. continue @@ -339,7 +339,7 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error) if err != nil { return nil, err } - return nil, e.Error(msg.value) + return nil, e.Error(msg.getString()) case *ast.Index: targetValue, err := e.evalInCurrentContext(ast.Target) @@ -353,11 +353,14 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error) switch target := targetValue.(type) { // TODO(sbarzowski) better error handling if bad index type case valueObject: - indexString := index.(*valueString).value + indexString := index.(*valueString).getString() return target.index(e, indexString) case *valueArray: indexInt := int(index.(*valueNumber).value) return e.evaluate(target.elements[indexInt]) + case *valueString: + indexInt := int(index.(*valueNumber).value) + return target.index(e, indexInt) } return nil, e.Error(fmt.Sprintf("Value non indexable: %v", reflect.TypeOf(targetValue))) @@ -417,7 +420,7 @@ func (i *interpreter) evaluate(a ast.Node, context *TraceContext) (value, error) if err != nil { return nil, err } - return superIndex(e, i.stack.getSelfBinding(), indexStr.value) + return superIndex(e, i.stack.getSelfBinding(), indexStr.getString()) case *ast.Function: return &valueFunction{ @@ -614,7 +617,7 @@ func (i *interpreter) manifestJSON(trace *TraceElement, v value, multiline bool, } case *valueString: - buf.WriteString(unparseString(v.value)) + buf.WriteString(unparseString(v.getString())) default: return makeRuntimeError( diff --git a/testdata/pow.golden b/testdata/pow.golden new file mode 100644 index 000000000..d7b1c440c --- /dev/null +++ b/testdata/pow.golden @@ -0,0 +1 @@ +1024 diff --git a/testdata/pow.input b/testdata/pow.input new file mode 100644 index 000000000..794817670 --- /dev/null +++ b/testdata/pow.input @@ -0,0 +1 @@ +std.pow(2, 10) diff --git a/testdata/pow2.golden b/testdata/pow2.golden new file mode 100644 index 000000000..29d6383b5 --- /dev/null +++ b/testdata/pow2.golden @@ -0,0 +1 @@ +100 diff --git a/testdata/pow2.input b/testdata/pow2.input new file mode 100644 index 000000000..50e61a630 --- /dev/null +++ b/testdata/pow2.input @@ -0,0 +1 @@ +std.pow(10, 2) diff --git a/testdata/pow3.golden b/testdata/pow3.golden new file mode 100644 index 000000000..3a2e3f498 --- /dev/null +++ b/testdata/pow3.golden @@ -0,0 +1 @@ +-1 diff --git a/testdata/pow3.input b/testdata/pow3.input new file mode 100644 index 000000000..878999398 --- /dev/null +++ b/testdata/pow3.input @@ -0,0 +1 @@ +std.pow(-1, 3) diff --git a/testdata/pow4.golden b/testdata/pow4.golden new file mode 100644 index 000000000..7ab1e7dd0 --- /dev/null +++ b/testdata/pow4.golden @@ -0,0 +1 @@ +RUNTIME ERROR: Not a number diff --git a/testdata/pow4.input b/testdata/pow4.input new file mode 100644 index 000000000..99a08d361 --- /dev/null +++ b/testdata/pow4.input @@ -0,0 +1 @@ +std.pow(-1, 0.2) diff --git a/testdata/pow5.golden b/testdata/pow5.golden new file mode 100644 index 000000000..065bb2892 --- /dev/null +++ b/testdata/pow5.golden @@ -0,0 +1 @@ +1.1486983549970351 diff --git a/testdata/pow5.input b/testdata/pow5.input new file mode 100644 index 000000000..d1230be9c --- /dev/null +++ b/testdata/pow5.input @@ -0,0 +1 @@ +std.pow(2, 0.2) diff --git a/testdata/pow6.golden b/testdata/pow6.golden new file mode 100644 index 000000000..34f720403 --- /dev/null +++ b/testdata/pow6.golden @@ -0,0 +1 @@ +179754255558423237941456473541041914055900576989066323324790225489927405882855355678287600996768561249516179005117487900995816670389973592784065259754879901383672166545582558984633189841112133404180226485873094649946308637416044832879263264479292996247265876810814717146304544813220293475007274508971205984256 diff --git a/testdata/pow6.input b/testdata/pow6.input new file mode 100644 index 000000000..fbf2449f7 --- /dev/null +++ b/testdata/pow6.input @@ -0,0 +1,2 @@ +// roughly the largest possible double +std.pow(1.1, 7447.081) diff --git a/testdata/pow7.golden b/testdata/pow7.golden new file mode 100644 index 000000000..a4fc2c4c8 --- /dev/null +++ b/testdata/pow7.golden @@ -0,0 +1 @@ +RUNTIME ERROR: Overflow diff --git a/testdata/pow7.input b/testdata/pow7.input new file mode 100644 index 000000000..503f3ea70 --- /dev/null +++ b/testdata/pow7.input @@ -0,0 +1,2 @@ +// slightly more than the largest possible double +std.pow(1.1, 7447.082) diff --git a/testdata/std_substr.golden b/testdata/std_substr.golden new file mode 100644 index 000000000..9de863498 --- /dev/null +++ b/testdata/std_substr.golden @@ -0,0 +1 @@ +"bc" diff --git a/testdata/std_substr.input b/testdata/std_substr.input new file mode 100644 index 000000000..f4a319773 --- /dev/null +++ b/testdata/std_substr.input @@ -0,0 +1 @@ +std.substr("abcd", 1, 2) diff --git a/testdata/string_comparison1.golden b/testdata/string_comparison1.golden new file mode 100644 index 000000000..27ba77dda --- /dev/null +++ b/testdata/string_comparison1.golden @@ -0,0 +1 @@ +true diff --git a/testdata/string_comparison1.input b/testdata/string_comparison1.input new file mode 100644 index 000000000..598c800e3 --- /dev/null +++ b/testdata/string_comparison1.input @@ -0,0 +1 @@ +"a" < "b" diff --git a/testdata/string_comparison2.golden b/testdata/string_comparison2.golden new file mode 100644 index 000000000..c508d5366 --- /dev/null +++ b/testdata/string_comparison2.golden @@ -0,0 +1 @@ +false diff --git a/testdata/string_comparison2.input b/testdata/string_comparison2.input new file mode 100644 index 000000000..fb3427e40 --- /dev/null +++ b/testdata/string_comparison2.input @@ -0,0 +1 @@ +"a" > "b" diff --git a/testdata/string_comparison3.golden b/testdata/string_comparison3.golden new file mode 100644 index 000000000..c508d5366 --- /dev/null +++ b/testdata/string_comparison3.golden @@ -0,0 +1 @@ +false diff --git a/testdata/string_comparison3.input b/testdata/string_comparison3.input new file mode 100644 index 000000000..3f05d52f8 --- /dev/null +++ b/testdata/string_comparison3.input @@ -0,0 +1 @@ +"a" == "b" diff --git a/testdata/string_comparison4.golden b/testdata/string_comparison4.golden new file mode 100644 index 000000000..27ba77dda --- /dev/null +++ b/testdata/string_comparison4.golden @@ -0,0 +1 @@ +true diff --git a/testdata/string_comparison4.input b/testdata/string_comparison4.input new file mode 100644 index 000000000..ada251a60 --- /dev/null +++ b/testdata/string_comparison4.input @@ -0,0 +1 @@ +"a" < "aa" diff --git a/testdata/string_comparison5.golden b/testdata/string_comparison5.golden new file mode 100644 index 000000000..c508d5366 --- /dev/null +++ b/testdata/string_comparison5.golden @@ -0,0 +1 @@ +false diff --git a/testdata/string_comparison5.input b/testdata/string_comparison5.input new file mode 100644 index 000000000..ea3f6104d --- /dev/null +++ b/testdata/string_comparison5.input @@ -0,0 +1 @@ +"aa" < "a" diff --git a/testdata/string_comparison6.golden b/testdata/string_comparison6.golden new file mode 100644 index 000000000..27ba77dda --- /dev/null +++ b/testdata/string_comparison6.golden @@ -0,0 +1 @@ +true diff --git a/testdata/string_comparison6.input b/testdata/string_comparison6.input new file mode 100644 index 000000000..49ab986b9 --- /dev/null +++ b/testdata/string_comparison6.input @@ -0,0 +1 @@ +"ą" < "ć" diff --git a/testdata/string_comparison7.golden b/testdata/string_comparison7.golden new file mode 100644 index 000000000..c508d5366 --- /dev/null +++ b/testdata/string_comparison7.golden @@ -0,0 +1 @@ +false diff --git a/testdata/string_comparison7.input b/testdata/string_comparison7.input new file mode 100644 index 000000000..0a4bc4ae9 --- /dev/null +++ b/testdata/string_comparison7.input @@ -0,0 +1 @@ +"ą" < "z" diff --git a/testdata/string_index.golden b/testdata/string_index.golden new file mode 100644 index 000000000..231f150c5 --- /dev/null +++ b/testdata/string_index.golden @@ -0,0 +1 @@ +"a" diff --git a/testdata/string_index.input b/testdata/string_index.input new file mode 100644 index 000000000..92298eb9c --- /dev/null +++ b/testdata/string_index.input @@ -0,0 +1 @@ +"abcd"[0] diff --git a/testdata/string_index2.golden b/testdata/string_index2.golden new file mode 100644 index 000000000..5775b9f8c --- /dev/null +++ b/testdata/string_index2.golden @@ -0,0 +1 @@ +"d" diff --git a/testdata/string_index2.input b/testdata/string_index2.input new file mode 100644 index 000000000..9e3f617da --- /dev/null +++ b/testdata/string_index2.input @@ -0,0 +1 @@ +"abcd"[3] diff --git a/testdata/string_index_negative.golden b/testdata/string_index_negative.golden new file mode 100644 index 000000000..70d96093a --- /dev/null +++ b/testdata/string_index_negative.golden @@ -0,0 +1 @@ +RUNTIME ERROR: Index -1 out of bounds, not within [0, 4) diff --git a/testdata/string_index_negative.input b/testdata/string_index_negative.input new file mode 100644 index 000000000..1b1a5b2db --- /dev/null +++ b/testdata/string_index_negative.input @@ -0,0 +1 @@ +"abcd"[-1] diff --git a/testdata/string_index_out_of_bounds.golden b/testdata/string_index_out_of_bounds.golden new file mode 100644 index 000000000..d2d1c7332 --- /dev/null +++ b/testdata/string_index_out_of_bounds.golden @@ -0,0 +1 @@ +RUNTIME ERROR: Index 4 out of bounds, not within [0, 4) diff --git a/testdata/string_index_out_of_bounds.input b/testdata/string_index_out_of_bounds.input new file mode 100644 index 000000000..6d0aac683 --- /dev/null +++ b/testdata/string_index_out_of_bounds.input @@ -0,0 +1 @@ +"abcd"[4] diff --git a/value.go b/value.go index 5f3569070..59eb31f39 100644 --- a/value.go +++ b/value.go @@ -61,11 +61,65 @@ func (v *valueBase) aValue() {} type valueString struct { valueBase - value string + // We use rune slices instead of strings for quick indexing + value []rune +} + +func (s *valueString) index(e *evaluator, index int) (value, error) { + if 0 <= index && index < s.length() { + return makeValueString(string(s.value[index])), nil + } + return nil, e.Error(fmt.Sprintf("Index %d out of bounds, not within [0, %v)", index, s.length())) +} + +func concatStrings(a, b *valueString) *valueString { + result := make([]rune, 0, len(a.value)+len(b.value)) + for _, r := range a.value { + result = append(result, r) + } + for _, r := range b.value { + result = append(result, r) + } + return &valueString{value: result} +} + +func stringLessThan(a, b *valueString) bool { + var length int + if len(a.value) < len(b.value) { + length = len(a.value) + } else { + length = len(b.value) + } + for i := 0; i < length; i++ { + if a.value[i] != b.value[i] { + return a.value[i] < b.value[i] + } + } + return len(a.value) < len(b.value) +} + +func stringEqual(a, b *valueString) bool { + if len(a.value) != len(b.value) { + return false + } + for i := 0; i < len(a.value); i++ { + if a.value[i] != b.value[i] { + return false + } + } + return true +} + +func (s *valueString) length() int { + return len(s.value) +} + +func (s *valueString) getString() string { + return string(s.value) } func makeValueString(v string) *valueString { - return &valueString{value: v} + return &valueString{value: []rune(v)} } func (*valueString) typename() string {