diff --git a/Makefile b/Makefile index 834d84b4..90246a4c 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,7 @@ ci-test-all: cd waroot && go run ../main.go run hello.wa make -C ./waroot/examples ci-test-all + wa -v clean: -rm a.out* diff --git a/internal/app/appssa/appssa.go b/internal/app/appssa/appssa.go index ccdf747f..31ca5d0e 100644 --- a/internal/app/appssa/appssa.go +++ b/internal/app/appssa/appssa.go @@ -9,6 +9,7 @@ import ( "wa-lang.org/wa/internal/3rdparty/cli" "wa-lang.org/wa/internal/app/appbase" + "wa-lang.org/wa/internal/ast" "wa-lang.org/wa/internal/loader" "wa-lang.org/wa/internal/ssa" ) @@ -20,6 +21,10 @@ var CmdSsa = &cli.Command{ Flags: []cli.Flag{ appbase.MakeFlag_target(), appbase.MakeFlag_tags(), + &cli.BoolFlag{ + Name: "ast", + Usage: "print ast", + }, }, Action: func(c *cli.Context) error { if c.NArg() == 0 { @@ -28,7 +33,7 @@ var CmdSsa = &cli.Command{ } opt := appbase.BuildOptions(c) - err := SSARun(opt, c.Args().First()) + err := SSARun(opt, c.Args().First(), c.Bool("ast")) if err != nil { fmt.Println(err) os.Exit(1) @@ -37,13 +42,21 @@ var CmdSsa = &cli.Command{ }, } -func SSARun(opt *appbase.Option, filename string) error { +func SSARun(opt *appbase.Option, filename string, printAST bool) error { cfg := opt.Config() prog, err := loader.LoadProgram(cfg, filename) if err != nil { return err } + if printAST { + mainPkg := prog.Pkgs[prog.Manifest.MainPkg] + for _, f := range mainPkg.Files { + ast.Print(prog.Fset, f) + } + return nil + } + prog.SSAMainPkg.WriteTo(os.Stdout) var funcNames []string diff --git a/internal/types/builtins.go b/internal/types/builtins.go index ae3a156d..84ffa5be 100644 --- a/internal/types/builtins.go +++ b/internal/types/builtins.go @@ -19,6 +19,12 @@ import ( // but x.expr is not set. If the call is invalid, the result is // false, and *x is undefined. func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ bool) { + for i, arg := range call.Args { + if expr := check.tryFixOperatorCall(arg); expr != nil { + call.Args[i] = expr + } + } + // append is the only built-in that permits the use of ... for the last argument bin := predeclaredFuncs[id] if call.Ellipsis.IsValid() && id != _Append { diff --git a/internal/types/call.go b/internal/types/call.go index 82b23cef..f566584c 100644 --- a/internal/types/call.go +++ b/internal/types/call.go @@ -14,6 +14,12 @@ import ( func (check *Checker) call(x *operand, e *ast.CallExpr) exprKind { check.resolveExprOrTypeOrGenericCall(x, e) + for i, arg := range e.Args { + if expr := check.tryFixOperatorCall(arg); expr != nil { + e.Args[i] = expr + } + } + switch x.mode { case invalid: check.use(e.Args...) diff --git a/internal/types/expr.go b/internal/types/expr.go index 803d488c..6c1ffa6c 100644 --- a/internal/types/expr.go +++ b/internal/types/expr.go @@ -100,10 +100,6 @@ func (check *Checker) unary(x *operand, e *ast.UnaryExpr, op token.Token) { return } - if check.tryUnaryOperatorCall(x, e) { - return - } - if !check.op(unaryOpPredicates, x, op) { x.mode = invalid return @@ -780,10 +776,6 @@ func (check *Checker) binary(x *operand, e *ast.BinaryExpr, lhs, rhs ast.Expr, o return } - if check.tryBinaryOperatorCall(x, &y, lhs, rhs, op) { - return - } - if isShift(op) { check.shift(x, &y, e, op) return diff --git a/internal/types/operator.go b/internal/types/operator.go index c8bb4a4d..58a51b93 100644 --- a/internal/types/operator.go +++ b/internal/types/operator.go @@ -140,11 +140,14 @@ func (check *Checker) tryFixOperatorCall(expr ast.Expr) ast.Expr { switch expr := expr.(type) { case *ast.BinaryExpr: var x, y operand + check.rawExpr(&x, expr.X, nil) + check.rawExpr(&y, expr.Y, nil) if check.tryBinaryOperatorCall(&x, &y, expr.X, expr.Y, expr.Op) { return x.expr } case *ast.UnaryExpr: var x operand + check.rawExpr(&x, expr.X, nil) if check.tryUnaryOperatorCall(&x, expr) { return x.expr } @@ -186,12 +189,21 @@ func (check *Checker) tryUnaryOperatorCall(x *operand, e *ast.UnaryExpr) bool { x.mode = value x.typ = fn.typ.(*Signature).results.vars[0].typ - x.expr = &ast.CallExpr{ - Fun: &ast.SelectorExpr{ - X: &ast.Ident{Name: "#" + fn.pkg.path}, - Sel: &ast.Ident{Name: fn.pkg.name}, - }, - Args: []ast.Expr{e.X}, + + if fn.pkg == check.pkg { + x.expr = &ast.CallExpr{ + Fun: &ast.Ident{Name: "#{func}:" + fn.name}, + Args: []ast.Expr{e.X}, + } + } else { + check.ensureOperatorCallPkgImported(fn.pkg) + x.expr = &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{Name: "#{pkg}:" + fn.pkg.path}, + Sel: &ast.Ident{Name: fn.name}, + }, + Args: []ast.Expr{e.X}, + } } check.hasCallOrRecv = true @@ -221,39 +233,39 @@ func (check *Checker) tryBinaryOperatorCall( return false } - var fnMached *Func + var fnMatched *Func for _, fn := range xFuncs { if err := check.tryBinOpFunc(fn, x, y, lhs, rhs); err == nil { - fnMached = fn + fnMatched = fn break } } - if fnMached == nil { + if fnMatched == nil { for _, fn := range yFuncs { if err := check.tryBinOpFunc(fn, x, y, lhs, rhs); err == nil { - fnMached = fn + fnMatched = fn break } } } - if fnMached == nil { + if fnMatched == nil { return false } x.mode = value - x.typ = fnMached.typ.(*Signature).results.vars[0].typ - if fnMached.pkg == check.pkg { - // TODO(chai): 当前包/外部名字屏蔽 + x.typ = fnMatched.typ.(*Signature).results.vars[0].typ + + if fnMatched.pkg == check.pkg { x.expr = &ast.CallExpr{ - Fun: &ast.Ident{Name: fnMached.name}, + Fun: &ast.Ident{Name: "#{func}:" + fnMatched.name}, Args: []ast.Expr{lhs, rhs}, } } else { + check.ensureOperatorCallPkgImported(fnMatched.pkg) x.expr = &ast.CallExpr{ - // TODO(chai): 未导入包修复 Fun: &ast.SelectorExpr{ - X: &ast.Ident{Name: fnMached.pkg.name}, - Sel: &ast.Ident{Name: fnMached.name}, + X: &ast.Ident{Name: "#{pkg}:" + fnMatched.pkg.path}, + Sel: &ast.Ident{Name: fnMatched.name}, }, Args: []ast.Expr{lhs, rhs}, } @@ -331,3 +343,29 @@ func (check *Checker) getBinOpFuncs(x *Named, op token.Token) []*Func { } return nil } + +// 确保运算符重载的函数对应的包被导入 +func (check *Checker) ensureOperatorCallPkgImported(pkg *Package) { + assert(pkg != check.pkg) + + pkgname := "#{pkg}:" + pkg.path + for _, x := range check.pkg.imports { + if x.path == pkg.path && x.name == pkgname { + return + } + } + + obj := NewPkgName(token.NoPos, check.pkg, pkgname, pkg) + obj.setNode(&ast.ImportSpec{ + Name: &ast.Ident{Name: pkgname}, + Path: &ast.BasicLit{Kind: token.STRING, Value: pkg.path}, + Comment: &ast.CommentGroup{ + List: []*ast.Comment{ + {Text: "internal: only for operator overloading call"}, + }, + }, + }) + + check.declare(check.pkg.scope, nil, obj, token.NoPos) + check.pkg.imports = append(check.pkg.imports, pkg) +} diff --git a/internal/types/resolver.go b/internal/types/resolver.go index deb83876..d46bdcb3 100644 --- a/internal/types/resolver.go +++ b/internal/types/resolver.go @@ -586,7 +586,7 @@ func (check *Checker) resolveExprOrTypeOrGenericCall(x *operand, e *ast.CallExpr if fnObj, ok := obj.(*Func); ok && len(fnObj.generic) != 0 { for _, genericFnObj := range fnObj.generic { if err := check.tryGenericCall(x, genericFnObj, e); err == nil { - eCall.Name = genericFnObj.name + eCall.Name = "#{func}:" + genericFnObj.name check.exprOrType(x, e.Fun) return } diff --git a/internal/types/scope.go b/internal/types/scope.go index 065772a6..4ed5aed1 100644 --- a/internal/types/scope.go +++ b/internal/types/scope.go @@ -67,7 +67,16 @@ func (s *Scope) Child(i int) *Scope { return s.children[i] } // Lookup returns the object in scope s with the given name if such an // object exists; otherwise the result is nil. func (s *Scope) Lookup(name string) Object { - return s.elems[name] + if obj, ok := s.elems[name]; ok { + return obj + } + if s.parent == Universe { + if strings.HasPrefix(name, "#{func}:") { + return s.elems[name[len("#{func}:"):]] + } + } + + return nil } // LookupParent follows the parent chain of scopes starting with s until @@ -85,6 +94,13 @@ func (s *Scope) LookupParent(name string, pos token.Pos) (*Scope, Object) { if obj := s.elems[name]; obj != nil && (!pos.IsValid() || obj.scopePos() <= pos) { return s, obj } + if s.parent == Universe { + if strings.HasPrefix(name, "#{func}:") { + if obj, ok := s.elems[name[len("#{func}:"):]]; ok { + return s, obj + } + } + } } return nil, nil } diff --git a/internal/types/stmt.go b/internal/types/stmt.go index 62e35c12..8b1ac014 100644 --- a/internal/types/stmt.go +++ b/internal/types/stmt.go @@ -167,6 +167,12 @@ func assignOp(op token.Token) token.Token { } func (check *Checker) suspendedCall(keyword string, call *ast.CallExpr) { + for i, x := range call.Args { + if expr := check.tryFixOperatorCall(x); expr != nil { + call.Args[i] = expr + } + } + var x operand var msg string switch check.rawExpr(&x, call, nil) { @@ -343,6 +349,10 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { check.errorf(x.pos(), "%s %s", &x, msg) case *ast.IncDecStmt: + if expr := check.tryFixOperatorCall(s.X); expr != nil { + s.X = expr + } + var op token.Token switch s.Tok { case token.INC: @@ -372,6 +382,12 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { check.assignVar(s.X, &x) case *ast.AssignStmt: + for i, x := range s.Rhs { + if expr := check.tryFixOperatorCall(x); expr != nil { + s.Rhs[i] = expr + } + } + switch s.Tok { case token.ASSIGN, token.DEFINE: if len(s.Lhs) == 0 { @@ -408,6 +424,11 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { check.suspendedCall("defer", s.Call) case *ast.ReturnStmt: + for i, x := range s.Results { + if expr := check.tryFixOperatorCall(x); expr != nil { + s.Results[i] = expr + } + } res := check.sig.results if res.Len() > 0 { // function returns results @@ -461,6 +482,11 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { defer check.closeScope() check.simpleStmt(s.Init) + + if expr := check.tryFixOperatorCall(s.Cond); expr != nil { + s.Cond = expr + } + var x operand check.expr(&x, s.Cond) if x.mode != invalid && !isBoolean(x.typ) { @@ -486,6 +512,10 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { check.simpleStmt(s.Init) var x operand if s.Tag != nil { + if expr := check.tryFixOperatorCall(s.Tag); expr != nil { + s.Tag = expr + } + check.expr(&x, s.Tag) // By checking assignment of x to an invisible temporary // (as a compiler would), we get all the relevant checks. @@ -539,6 +569,9 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { var rhs ast.Expr switch guard := s.Assign.(type) { case *ast.ExprStmt: + if expr := check.tryFixOperatorCall(guard.X); expr != nil { + guard.X = expr + } rhs = guard.X case *ast.AssignStmt: if len(guard.Lhs) != 1 || guard.Tok != token.DEFINE || len(guard.Rhs) != 1 { @@ -552,6 +585,12 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { return } + for i, x := range guard.Rhs { + if expr := check.tryFixOperatorCall(x); expr != nil { + guard.Rhs[i] = expr + } + } + if lhs.Name == "_" { // _ := x.(type) is an invalid short variable declaration check.softErrorf(lhs.Pos(), "no new variable on left side of :=") @@ -644,6 +683,10 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { check.simpleStmt(s.Init) if s.Cond != nil { + if expr := check.tryFixOperatorCall(s.Cond); expr != nil { + s.Cond = expr + } + var x operand check.expr(&x, s.Cond) if x.mode != invalid && !isBoolean(x.typ) { diff --git a/waroot/VERSION b/waroot/VERSION index f78dc365..e88c34ff 100644 --- a/waroot/VERSION +++ b/waroot/VERSION @@ -1 +1 @@ -v0.10.0 \ No newline at end of file +v0.11.0 \ No newline at end of file diff --git a/waroot/changelog.md b/waroot/changelog.md index 580dcae2..9d1d70ba 100644 --- a/waroot/changelog.md +++ b/waroot/changelog.md @@ -1,6 +1,10 @@ # 版本日志 - (dev) +- v0.11.0 (2024-04-13) + - 完善 `math/{Sin,Cos,Sqrt}` 等 API + - 增加 `math/vector`/`math/matrix`/`net` 标准库 + - 实验性增加运算符重载功能 - v0.10.0 (2024-03-29) - 补充基本类型读写胶水 - 修正 I64、U64 不应导出等一些错误 diff --git a/waroot/examples/p5-hello/src/main.wa b/waroot/examples/p5-hello/src/main.wa index c3c4a792..437935e3 100644 --- a/waroot/examples/p5-hello/src/main.wa +++ b/waroot/examples/p5-hello/src/main.wa @@ -3,8 +3,9 @@ import "js/p5" func init { - p5.CreateCanvas(400, 400) - p5.Background(220, 220, 220) + p5.CreateCanvas(400) + p5.Background(220) + p5.Stroke(p5.RED) } func Draw { diff --git a/waroot/src/apple/add.wa b/waroot/src/apple/add.wa index ad331db9..079d62a4 100644 --- a/waroot/src/apple/add.wa +++ b/waroot/src/apple/add.wa @@ -9,9 +9,20 @@ func IncF64(arg_IncF64_f: f64) => f64 { return arg_IncF64_f + 1 } -type MyInt :int +#wa:operator + MyInt_add +type MyInt :struct { + V: int +} #wa:generic AddF64 -func MyInt.Add(a: int) {} +func MyInt.Add(a: int) { + this.V += a +} -func MyInt.AddF64(a: f64) {} +func MyInt.AddF64(a: f64) { + this.V += int(a) +} + +func MyInt_add(x, y: MyInt) => int { + return x.V + y.V +} diff --git a/waroot/src/js/p5/color.wa b/waroot/src/js/p5/color.wa new file mode 100644 index 00000000..392f870f --- /dev/null +++ b/waroot/src/js/p5/color.wa @@ -0,0 +1,11 @@ +// 版权 @2024 凹语言 作者。保留所有权利。 + +import ( + "image/color" +) + +global ( + RED = color.RGBAFrom(u8(255), u8(0), u8(0), 0xff) + GREEN = color.RGBAFrom(u8(0), u8(255), u8(0), 0xff) + BLUE = color.RGBAFrom(u8(0), u8(0), u8(255), 0xff) +) diff --git a/waroot/src/js/p5/p5.wa b/waroot/src/js/p5/p5.wa index d3634266..917d781c 100644 --- a/waroot/src/js/p5/p5.wa +++ b/waroot/src/js/p5/p5.wa @@ -35,6 +35,7 @@ func init { } // 创建/调整画布大小 +#wa:generic CreateCanvasSquare func CreateCanvas(width, height: int) { Width = width Height = height @@ -43,7 +44,19 @@ func CreateCanvas(width, height: int) { p5CanvasBuffer = image.NewRGBA(width, height) } +// 调整画布大小 +func CreateCanvasSquare(wh: int) { + width, height := wh, wh + + Width = width + Height = height + + p5Canvas.SetWidthHeight(i32(width), i32(height)) + p5CanvasBuffer = image.NewRGBA(width, height) +} + // 设置画布背景 +#wa:generic BackgroundColor BackgroundRGBA func Background(red, green, blue: int) { p5BackgroundColor = color.RGBAFrom( u8(red), u8(green), u8(blue), @@ -57,13 +70,47 @@ func Background(red, green, blue: int) { } } +// 设置画布背景, 小于256时用灰度 +func BackgroundColor(v: color.Gray) { + p5BackgroundColor = color.RGBAFrom( + u8(v), u8(v), u8(v), + 0xff, + ) + + for y := 0; y < Height; y++ { + for x := 0; x < Width; x++ { + p5CanvasBuffer.SetRGBA(x, y, p5BackgroundColor) + } + } +} + +// 设置画布背景, 小于256时用灰度 +func BackgroundRGBA(v: color.RGBA) { + p5BackgroundColor = v + for y := 0; y < Height; y++ { + for x := 0; x < Width; x++ { + p5CanvasBuffer.SetRGBA(x, y, p5BackgroundColor) + } + } +} + // 设置画笔颜色 +#wa:generic StrokeColor StrokeRGBA func Stroke(red, green, blue: int) { p5StrokeColor = color.RGBAFrom( u8(red), u8(green), u8(blue), 0xff, ) } +func StrokeColor(v: color.Gray) { + p5StrokeColor = color.RGBAFrom( + u8(v), u8(v), u8(v), + 0xff, + ) +} +func StrokeRGBA(v: color.RGBA) { + p5StrokeColor = v +} // 设置填充颜色 func Fill(red, green, blue: int) {