diff --git a/src/core/call.go b/src/core/call.go new file mode 100644 index 0000000..871bb0f --- /dev/null +++ b/src/core/call.go @@ -0,0 +1,35 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/ssa" + "git.urbach.dev/cli/q/src/types" +) + +// call calls a function. +func (f *Function) call(fn *ssa.Function, args []ssa.Value, source ssa.Source) ssa.Value { + call := f.Append(&ssa.Call{ + Func: fn, + Arguments: args, + Source: source, + }) + + typ := call.Type() + structure, isStructType := types.Unwrap(typ).(*types.Struct) + + if isStructType { + var fields []ssa.Value + + for _, field := range structure.Fields { + ssaField := f.Append(&ssa.Field{ + Tuple: call, + Index: int(field.Index), + }) + + fields = append(fields, ssaField) + } + + return f.makeStruct(typ, fields, source) + } + + return call +} \ No newline at end of file diff --git a/src/core/compileAssert.go b/src/core/compileAssert.go index 204487d..02b12cb 100644 --- a/src/core/compileAssert.go +++ b/src/core/compileAssert.go @@ -21,11 +21,12 @@ func (f *Function) compileAssert(assert *ast.Assert) error { f.AddBlock(elseBlock) crash := f.Env.Function("run", "crash") - elseBlock.Append(&ssa.Call{Func: &ssa.Function{ + fn := &ssa.Function{ FunctionRef: crash, Typ: crash.Type, - }}) + } + f.call(fn, nil, assert.Condition.Source()) f.Dependencies.Add(crash) f.AddBlock(thenBlock) return nil diff --git a/src/core/compileCondition.go b/src/core/compileCondition.go index 9263067..d4dd9e2 100644 --- a/src/core/compileCondition.go +++ b/src/core/compileCondition.go @@ -61,29 +61,32 @@ func (f *Function) compileCondition(condition *expression.Expression, thenBlock return err } - comparison := conditionValue.(*ssa.BinaryOp) - left := comparison.Left - - if left.Type() == types.Error { - right := comparison.Right.(*ssa.Int) - - switch { - case condition.Token.Kind == token.NotEqual && right.Int == 0: - for _, protected := range thenBlock.Protected[left] { - thenBlock.Unidentify(protected) + binaryOp, isBinaryOp := conditionValue.(*ssa.BinaryOp) + + if isBinaryOp && binaryOp.Op.IsComparison() { + left := binaryOp.Left + + if left.Type() == types.Error { + right := binaryOp.Right.(*ssa.Int) + + switch { + case condition.Token.Kind == token.NotEqual && right.Int == 0: + for _, protected := range thenBlock.Protected[left] { + thenBlock.Unidentify(protected) + } + + thenBlock.Unprotect(left) + elseBlock.Unidentify(left) + elseBlock.Unprotect(left) + case condition.Token.Kind == token.Equal && right.Int == 0: + for _, protected := range elseBlock.Protected[left] { + elseBlock.Unidentify(protected) + } + + elseBlock.Unprotect(left) + thenBlock.Unidentify(left) + thenBlock.Unprotect(left) } - - thenBlock.Unprotect(left) - elseBlock.Unidentify(left) - elseBlock.Unprotect(left) - case condition.Token.Kind == token.Equal && right.Int == 0: - for _, protected := range elseBlock.Protected[left] { - elseBlock.Unidentify(protected) - } - - elseBlock.Unprotect(left) - thenBlock.Unidentify(left) - thenBlock.Unprotect(left) } } diff --git a/src/core/decomposeSlice.go b/src/core/decomposeSlice.go index e66553b..64d0bd5 100644 --- a/src/core/decomposeSlice.go +++ b/src/core/decomposeSlice.go @@ -1,47 +1,26 @@ package core import ( - "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/ssa" - "git.urbach.dev/cli/q/src/token" "git.urbach.dev/cli/q/src/types" ) // decomposeSlice decomposes a slices to its pointer, type and length. -func (f *Function) decomposeSlice(addressValue ssa.Value, source token.Source) (ssa.Value, types.Type, ssa.Value, error) { +func (f *Function) decomposeSlice(addressValue ssa.Value) (ssa.Value, types.Type, ssa.Value, error) { addressType := types.Unwrap(addressValue.Type()) switch addressType.(type) { case *types.Struct: - switch value := addressValue.(type) { - case *ssa.Struct: - pointer := value.Arguments[0] - length := value.Arguments[1] - return pointer, pointer.Type(), length, nil + structure, isStructure := addressValue.(*ssa.Struct) - case *ssa.Call: - resource, isResource := addressValue.Type().(*types.Resource) - - if isResource { - return nil, nil, nil, errors.New(&ResourceNotConsumed{TypeName: resource.Name()}, f.File, source) - } - - pointer := f.Append(&ssa.Field{ - Tuple: value, - Index: 0, - }) - - length := f.Append(&ssa.Field{ - Tuple: value, - Index: 1, - }) - - return pointer, pointer.Type(), length, nil - - default: + if !isStructure { panic("not implemented") } + pointer := structure.Arguments[0] + length := structure.Arguments[1] + return pointer, pointer.Type(), length, nil + case *types.Pointer: return addressValue, addressType, nil, nil diff --git a/src/core/delete.go b/src/core/delete.go index 864c1b3..aeeeee3 100644 --- a/src/core/delete.go +++ b/src/core/delete.go @@ -20,14 +20,13 @@ func (f *Function) delete(value ssa.Value) (ssa.Value, error) { Int: typ.Size(), }) - call := f.Append(&ssa.Call{ - Func: &ssa.Function{ - FunctionRef: free, - Typ: free.Type, - }, - Arguments: []ssa.Value{value, size}, - }) + fn := &ssa.Function{ + FunctionRef: free, + Typ: free.Type, + } + args := []ssa.Value{value, size} + call := f.call(fn, args, ssa.Source{}) return call, nil case *types.Struct: diff --git a/src/core/errors_test.go b/src/core/errors_test.go index d3234c6..dae0b2e 100644 --- a/src/core/errors_test.go +++ b/src/core/errors_test.go @@ -75,7 +75,6 @@ var errs = []struct { {"ResourceAlreadyConsumed3.q", &core.UnknownIdentifier{Name: "x"}}, {"ResourceNotConsumed.q", &core.ResourceNotConsumed{TypeName: "!int64"}}, {"ResourceNotConsumed2.q", &core.ResourceNotConsumed{TypeName: "!int64"}}, - {"ResourceNotConsumed3.q", &core.ResourceNotConsumed{TypeName: "![]int64"}}, {"ResourcePartiallyConsumed.q", &core.ResourcePartiallyConsumed{TypeName: "!int64"}}, {"ResourceTypeMismatch.q", &core.TypeMismatch{Encountered: "int64", Expected: "!int64", ParameterName: "_", IsReturn: false}}, {"ReturnCountMismatch.q", &core.ReturnCountMismatch{Count: 1, ExpectedCount: 0}}, diff --git a/src/core/evaluateArray.go b/src/core/evaluateArray.go index d36a105..f0f44a6 100644 --- a/src/core/evaluateArray.go +++ b/src/core/evaluateArray.go @@ -17,7 +17,7 @@ func (f *Function) evaluateArray(expr *expression.Expression) (ssa.Value, error) return nil, err } - addressValue, addressType, length, err := f.decomposeSlice(addressValue, address.Source()) + addressValue, addressType, length, err := f.decomposeSlice(addressValue) if err != nil { return nil, err diff --git a/src/core/evaluateBinary.go b/src/core/evaluateBinary.go index 1eb75b8..a335c04 100644 --- a/src/core/evaluateBinary.go +++ b/src/core/evaluateBinary.go @@ -1,6 +1,8 @@ package core import ( + "fmt" + "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/optimizer" @@ -35,14 +37,34 @@ func (f *Function) evaluateBinary(expr *expression.Expression) (ssa.Value, error return nil, err } - _, leftIsStruct := leftValue.Type().(*types.Struct) - _, rightIsStruct := rightValue.Type().(*types.Struct) + _, leftIsStruct := types.Unwrap(leftValue.Type()).(*types.Struct) + _, rightIsStruct := types.Unwrap(rightValue.Type()).(*types.Struct) + + if leftIsStruct && rightIsStruct { + switch expr.Token.Kind { + case token.Concat: + return f.evaluateStringOp("concat", leftValue, rightValue, expr.Source()) + case token.Equal: + return f.evaluateStringOp("equal", leftValue, rightValue, expr.Source()) + case token.NotEqual: + equal, err := f.evaluateStringOp("equal", leftValue, rightValue, expr.Source()) + + if err != nil { + return nil, err + } - if leftIsStruct && rightIsStruct && expr.Token.Kind == token.Concat { - return f.evaluateConcat(leftValue, rightValue, expr.Source()) + v := f.Append(&ssa.UnaryOp{ + Op: token.Not, + Operand: equal, + Source: expr.Source(), + }) + + return v, nil + } } if leftIsStruct || rightIsStruct { + fmt.Println(leftValue.Type().Name(), rightValue.Type().Name()) return nil, errors.New(InvalidStructOperation, f.File, expr.Token) } diff --git a/src/core/evaluateCall.go b/src/core/evaluateCall.go index 9afc7d3..08e872a 100644 --- a/src/core/evaluateCall.go +++ b/src/core/evaluateCall.go @@ -48,11 +48,7 @@ func (f *Function) evaluateCall(expr *expression.Expression) (ssa.Value, error) f.runAll("init") } - v := f.Append(&ssa.Call{ - Func: ssaFunc, - Arguments: args, - Source: expr.Source(), - }) + v := f.call(ssaFunc, args, expr.Source()) if f == f.Env.Init && fn == f.Env.Main { f.runAll("exit") diff --git a/src/core/evaluateConcat.go b/src/core/evaluateConcat.go deleted file mode 100644 index 0b1fb72..0000000 --- a/src/core/evaluateConcat.go +++ /dev/null @@ -1,24 +0,0 @@ -package core - -import ( - "git.urbach.dev/cli/q/src/ssa" -) - -// evaluateConcat concatenates two strings. -func (f *Function) evaluateConcat(left ssa.Value, right ssa.Value, source ssa.Source) (ssa.Value, error) { - concat := f.Env.Function("strings", "concat") - f.Dependencies.Add(concat) - leftStruct := left.(*ssa.Struct) - rightStruct := right.(*ssa.Struct) - - call := f.Append(&ssa.Call{ - Func: &ssa.Function{ - FunctionRef: concat, - Typ: concat.Type, - }, - Arguments: []ssa.Value{leftStruct.Arguments[0], leftStruct.Arguments[1], rightStruct.Arguments[0], rightStruct.Arguments[1]}, - Source: source, - }) - - return call, nil -} \ No newline at end of file diff --git a/src/core/evaluateNew.go b/src/core/evaluateNew.go index 180f73b..29cbec2 100644 --- a/src/core/evaluateNew.go +++ b/src/core/evaluateNew.go @@ -44,16 +44,15 @@ func (f *Function) evaluateNew(expr *expression.Expression) (ssa.Value, error) { malloc := f.Env.Function("mem", "alloc") f.Dependencies.Add(malloc) - call := f.Append(&ssa.Call{ - Func: &ssa.Function{ - FunctionRef: malloc, - Typ: &types.Function{ - Output: []types.Type{mallocType}, - }, + fn := &ssa.Function{ + FunctionRef: malloc, + Typ: &types.Function{ + Output: []types.Type{mallocType}, }, - Arguments: []ssa.Value{sizeInBytes}, - Source: expr.Source(), - }) + } + + args := []ssa.Value{sizeInBytes} + call := f.call(fn, args, expr.Source()) if isSlice { structure := &ssa.Struct{ diff --git a/src/core/evaluateStringOp.go b/src/core/evaluateStringOp.go new file mode 100644 index 0000000..3f26bfe --- /dev/null +++ b/src/core/evaluateStringOp.go @@ -0,0 +1,27 @@ +package core + +import ( + "git.urbach.dev/cli/q/src/ssa" +) + +// evaluateStringOp calls a function with two strings. +func (f *Function) evaluateStringOp(op string, left ssa.Value, right ssa.Value, source ssa.Source) (ssa.Value, error) { + concat := f.Env.Function("strings", op) + f.Dependencies.Add(concat) + leftStruct := left.(*ssa.Struct) + rightStruct := right.(*ssa.Struct) + + fn := &ssa.Function{ + FunctionRef: concat, + Typ: concat.Type, + } + + args := []ssa.Value{ + leftStruct.Arguments[0], + leftStruct.Arguments[1], + rightStruct.Arguments[0], + rightStruct.Arguments[1], + } + + return f.call(fn, args, source), nil +} \ No newline at end of file diff --git a/src/core/testdata/ResourceNotConsumed3.q b/src/core/testdata/ResourceNotConsumed3.q deleted file mode 100644 index c34c9ba..0000000 --- a/src/core/testdata/ResourceNotConsumed3.q +++ /dev/null @@ -1,7 +0,0 @@ -main() { - x := acquire()[1..] -} - -acquire() -> ![]int { - return new(int, 2) -} \ No newline at end of file diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go index c690c98..76eb5d9 100644 --- a/src/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -24,6 +24,7 @@ func Scan(build *config.Build) (*core.Environment, error) { go func() { s.queueDirectory(filepath.Join(global.Library, "run"), "run") + s.queueDirectory(filepath.Join(global.Library, "strings"), "strings") s.queue(build.Files...) s.group.Wait() close(s.constants) diff --git a/tests/strings-concat-operator.q b/tests/strings-concat-operator.q new file mode 100644 index 0000000..9e155f1 --- /dev/null +++ b/tests/strings-concat-operator.q @@ -0,0 +1,4 @@ +main() { + assert "Hello" ++ "World" == "HelloWorld" + assert "Hello" ++ "World" != "WorldHello" +} \ No newline at end of file diff --git a/tests/strings-concat.q b/tests/strings-concat.q index 2d84c97..e33f5d2 100644 --- a/tests/strings-concat.q +++ b/tests/strings-concat.q @@ -1,7 +1,7 @@ import strings main() { - combined := "Hello" ++ "World" - assert strings.equal(combined, "HelloWorld") - assert !strings.equal(combined, "WorldHello") + combined := strings.concat("Hello", "World") + assert combined == "HelloWorld" + assert combined != "WorldHello" } \ No newline at end of file diff --git a/tests/strings-cut.q b/tests/strings-cut.q index 0855c89..b51a399 100644 --- a/tests/strings-cut.q +++ b/tests/strings-cut.q @@ -3,8 +3,8 @@ import strings main() { hello, world, err := strings.cut("Hello World", " ") assert err == 0 - assert strings.equal(hello, "Hello") - assert strings.equal(world, "World") + assert hello == "Hello" + assert world == "World" _, _, err := strings.cut("世界", ":") assert err != 0 diff --git a/tests/strings-equal-operator.q b/tests/strings-equal-operator.q new file mode 100644 index 0000000..72a887f --- /dev/null +++ b/tests/strings-equal-operator.q @@ -0,0 +1,6 @@ +main() { + assert "world" != "universe" + assert "world" == "world" + assert "世界" != "宇宙" + assert "世界" == "世界" +} \ No newline at end of file diff --git a/tests/tests_test.go b/tests/tests_test.go index 80ccaee..8a1ecfc 100644 --- a/tests/tests_test.go +++ b/tests/tests_test.go @@ -113,12 +113,14 @@ var tests = []run{ {"slice", nil, "", "Hello World", 0}, {"file-read", nil, "", "", 0}, {"strings-equal", nil, "", "", 0}, + {"strings-equal-operator", nil, "", "", 0}, {"strings-index", nil, "", "", 0}, {"strings-cut", nil, "", "", 0}, {"strings-trim", nil, "", "", 0}, {"strings-prefix", nil, "", "", 0}, {"strings-suffix", nil, "", "", 0}, {"strings-concat", nil, "", "", 0}, + {"strings-concat-operator", nil, "", "", 0}, {"strings-parse-int", nil, "", "", 0}, {"reorder-add", nil, "", "", 0}, {"reorder-values", nil, "", "", 0},