Repository: goplus/xgo Branch: main Commit: 98c85f111d9b Files: 844 Total size: 2.5 MB Directory structure: gitextract_31rdv4zt/ ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.yml │ │ ├── config.yml │ │ └── enhancement.yml │ ├── codecov.yml │ ├── dependabot.yml │ ├── workflows/ │ │ ├── check_goreleaser_config.py │ │ ├── go.yml │ │ └── release-build.yml │ └── xgopilot.yml ├── .gitignore ├── .goreleaser.yaml ├── CLAUDE.md ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── all.bash ├── all.bat ├── ast/ │ ├── ast.go │ ├── ast_xgo.go │ ├── commentmap.go │ ├── filter.go │ ├── fromgo/ │ │ ├── gopast.go │ │ ├── gopast_test.go │ │ ├── typeparams/ │ │ │ ├── typeparams_go117.go │ │ │ └── typeparams_go118.go │ │ └── typeparams_test.go │ ├── gopq/ │ │ ├── dom.go │ │ ├── gopq.go │ │ └── helper.go │ ├── goptest/ │ │ └── gopq.go │ ├── import.go │ ├── mod/ │ │ └── deps.go │ ├── print.go │ ├── resolve.go │ ├── scope.go │ ├── togo/ │ │ ├── goast.go │ │ └── goast_test.go │ └── walk.go ├── builtin/ │ └── doc.xgo ├── cl/ │ ├── _testc/ │ │ └── hello/ │ │ ├── in.xgo │ │ └── out.go │ ├── _testgop/ │ │ ├── _matrix/ │ │ │ └── in.xgo │ │ ├── append1/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── append2/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── autoref-2484/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── builtin/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── domaintext-html/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── domaintext-huh/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── domaintext-json/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── domaintext-md/ │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── domaintext-regexp/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── domaintext-tpl/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── domaintpl/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── dql1/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── dql2/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── dql3/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── dql4/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── dql5/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── dql6/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── dql7/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── enumlines-rdr/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── enumlines-stdin/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── errwrap1/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── errwrap2/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── errwrap3/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── fatal/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── for-in/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── for-range/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── implicit-cast-2439/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── kwargs1/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── kwargs2/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── kwargs3/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── kwargs4/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── list-compr1/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── list-compr2/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── map-compr-cond1/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── map-compr-cond2/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── map-compr1/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── map-field-access1/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── map-field-access2/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── optparam/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── optparam2/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── rangeexpr/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── repeatuntil/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── select-compr-twovalue1/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── select-compr-twovalue2/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── select-compr1/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── structtag/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── tuplelit/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── tupletype1/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ ├── tupletype2/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ └── unit/ │ │ ├── in.xgo │ │ └── out.go │ ├── _testpy/ │ │ ├── _matrix/ │ │ │ └── in.xgo │ │ ├── hello/ │ │ │ ├── in.xgo │ │ │ └── out.go │ │ └── pycall/ │ │ ├── in.xgo │ │ └── out.go │ ├── _testspx/ │ │ ├── basic/ │ │ │ ├── Game.tgmx │ │ │ ├── Kai.tspx │ │ │ └── out.go │ │ ├── clsinit1/ │ │ │ ├── Rect.gox │ │ │ └── out.go │ │ ├── clsinit2/ │ │ │ ├── Rect.gox │ │ │ └── out.go │ │ ├── execgsh/ │ │ │ ├── demo.gsh │ │ │ └── out.go │ │ ├── gshself/ │ │ │ ├── demo.gsh │ │ │ └── out.go │ │ ├── init/ │ │ │ ├── init.tspx │ │ │ └── out.go │ │ ├── multiworks/ │ │ │ ├── foo_prompt.gox │ │ │ ├── hello_tool.gox │ │ │ ├── main_mcp.gox │ │ │ └── out.go │ │ ├── newobj/ │ │ │ ├── Kai_spx.gox │ │ │ ├── main_spx.gox │ │ │ └── out.go │ │ ├── nogame/ │ │ │ ├── bar.tspx │ │ │ └── out.go │ │ ├── singlework/ │ │ │ ├── Kai_spx.gox │ │ │ ├── main_spx.gox │ │ │ └── out.go │ │ └── xgoinit_dup/ │ │ ├── Spr_spx.gox │ │ ├── main_spx.gox │ │ └── out.go │ ├── builtin.go │ ├── builtin_test.go │ ├── c.go │ ├── classfile.go │ ├── cltest/ │ │ ├── cltest.go │ │ ├── error_msg.go │ │ ├── recorder.go │ │ └── spx.go │ ├── compile.go │ ├── compile_spx_test.go │ ├── compile_test.go │ ├── compile_testdir_test.go │ ├── compile_xgo_test.go │ ├── error_msg_test.go │ ├── expr.go │ ├── func_type_and_var.go │ ├── internal/ │ │ ├── .gitignore │ │ ├── dql/ │ │ │ └── dql.go │ │ ├── gop-in-go/ │ │ │ └── foo/ │ │ │ ├── foo.xgo │ │ │ ├── foo_test.xgo │ │ │ └── footest_test.xgo │ │ ├── huh/ │ │ │ └── huh.go │ │ ├── llgo-hello/ │ │ │ └── hello.go │ │ ├── mcp/ │ │ │ └── classfile.go │ │ ├── overload/ │ │ │ ├── bar/ │ │ │ │ └── bar.go │ │ │ └── foo/ │ │ │ └── foo.go │ │ ├── spx/ │ │ │ ├── game.go │ │ │ ├── pkg/ │ │ │ │ └── pkg.go │ │ │ └── sprite.go │ │ ├── spx2/ │ │ │ └── spx2.go │ │ ├── spx3/ │ │ │ ├── jwt/ │ │ │ │ └── jwt.go │ │ │ └── spx3.go │ │ ├── spx4/ │ │ │ ├── game.go │ │ │ ├── pkg/ │ │ │ │ └── pkg.go │ │ │ └── sprite.go │ │ ├── test/ │ │ │ ├── case.go │ │ │ └── match.go │ │ ├── testutil/ │ │ │ └── testutil.go │ │ ├── typesutil/ │ │ │ ├── api.go │ │ │ ├── api_test.go │ │ │ ├── mode.go │ │ │ └── mode_go123.go │ │ └── unit/ │ │ └── unit.go │ ├── outline/ │ │ └── outline.go │ ├── recorder.go │ ├── run_test.go │ ├── stmt.go │ ├── typeparams.go │ └── typeparams_test.go ├── cmd/ │ ├── chore/ │ │ ├── goptestgo/ │ │ │ └── goptestgo.go │ │ ├── xgobuiltingen/ │ │ │ ├── builtin.gox │ │ │ ├── builtingen.gox │ │ │ ├── helper.xgo │ │ │ └── reference.gox │ │ ├── xgofullspec/ │ │ │ └── fullspec.xgo │ │ └── xgominispec/ │ │ └── minispec.xgo │ ├── hdq/ │ │ ├── fetch_cmd.gox │ │ ├── list_cmd.gox │ │ ├── main_app.gox │ │ └── xgo_autogen.go │ ├── internal/ │ │ ├── base/ │ │ │ ├── base.go │ │ │ └── pass.go │ │ ├── bug/ │ │ │ └── bug.go │ │ ├── build/ │ │ │ └── build.go │ │ ├── clean/ │ │ │ └── clean.go │ │ ├── deps/ │ │ │ └── deps.go │ │ ├── doc/ │ │ │ └── doc.go │ │ ├── env/ │ │ │ └── env.go │ │ ├── gengo/ │ │ │ └── go.go │ │ ├── gopfmt/ │ │ │ └── fmt.go │ │ ├── gopget/ │ │ │ └── get.go │ │ ├── help/ │ │ │ └── help.go │ │ ├── install/ │ │ │ └── install.go │ │ ├── list/ │ │ │ └── list.go │ │ ├── mod/ │ │ │ ├── download.go │ │ │ ├── init.go │ │ │ ├── mod.go │ │ │ └── tidy.go │ │ ├── run/ │ │ │ └── run.go │ │ ├── serve/ │ │ │ └── serve.go │ │ ├── test/ │ │ │ ├── test.go │ │ │ └── testflag.go │ │ ├── version/ │ │ │ └── ver.go │ │ └── watch/ │ │ └── watch.go │ ├── make.go │ ├── make_test.go │ └── xgo/ │ ├── bug_cmd.gox │ ├── build_cmd.gox │ ├── clean_cmd.gox │ ├── doc_cmd.gox │ ├── env_cmd.gox │ ├── fmt_cmd.gox │ ├── get_cmd.gox │ ├── go_cmd.gox │ ├── install_cmd.gox │ ├── main_app.gox │ ├── mod_cmd.gox │ ├── mod_download_cmd.gox │ ├── mod_init_cmd.gox │ ├── mod_tidy_cmd.gox │ ├── run_cmd.gox │ ├── serve_cmd.gox │ ├── test_cmd.gox │ ├── version_cmd.gox │ ├── watch_cmd.gox │ └── xgo_autogen.go ├── demo/ │ ├── _llgo/ │ │ ├── callpy/ │ │ │ └── callpy.xgo │ │ ├── chello/ │ │ │ └── hello.xgo │ │ ├── cpphello/ │ │ │ └── cpphello.xgo │ │ ├── defer/ │ │ │ └── defer.xgo │ │ ├── errors/ │ │ │ └── errors.xgo │ │ ├── go.mod │ │ ├── go.sum │ │ ├── goroutine/ │ │ │ └── goroutine.xgo │ │ ├── hello/ │ │ │ └── hello.xgo │ │ ├── hellollgo/ │ │ │ ├── README.md │ │ │ └── hello.xgo │ │ ├── matrix/ │ │ │ └── matrix.xgo │ │ ├── pyhello/ │ │ │ └── hello.xgo │ │ ├── pymax/ │ │ │ └── pymax.xgo │ │ ├── pyprint/ │ │ │ └── print.xgo │ │ ├── pytensor/ │ │ │ └── tensor.xgo │ │ ├── qsort/ │ │ │ └── qsort.xgo │ │ ├── reflect/ │ │ │ └── reflect.xgo │ │ ├── sqlitedemo/ │ │ │ └── sqlitedemo.xgo │ │ ├── statistics/ │ │ │ └── statistics.xgo │ │ └── tetris/ │ │ └── tetris.xgo │ ├── _tinygo/ │ │ ├── blink/ │ │ │ └── blink.xgo │ │ ├── go.mod │ │ └── sortdemo/ │ │ └── sort.xgo │ ├── clsinit/ │ │ └── Rect.gox │ ├── domaintext/ │ │ └── domaintext.xgo │ ├── dql-fs/ │ │ └── fsq.xgo │ ├── dql-json/ │ │ └── jq.xgo │ ├── dql-links/ │ │ └── links.xgo │ ├── dql-xgo/ │ │ └── xgoq.xgo │ ├── dql-yaml/ │ │ └── yq.xgo │ ├── fullspec/ │ │ ├── mixgo-complex/ │ │ │ ├── bar.go │ │ │ ├── foo.xgo │ │ │ └── xgo_autogen.go │ │ ├── overloadfunc1/ │ │ │ └── add.xgo │ │ ├── overloadfunc2/ │ │ │ └── mul.xgo │ │ ├── overloadmethod/ │ │ │ └── method.xgo │ │ ├── overloadop1/ │ │ │ └── overloadop.xgo │ │ ├── overloadop2/ │ │ │ └── overloadop.xgo │ │ └── tpl-gen-ast/ │ │ └── gen_calc_ast.xgo │ ├── gsh-exec/ │ │ ├── exec.gsh │ │ └── foo/ │ │ └── foo.xgo │ ├── kwargs/ │ │ └── run.xgo │ ├── lambda1/ │ │ └── lambda.xgo │ ├── mapliteral/ │ │ └── mapliteral.xgo │ ├── mixgo/ │ │ ├── README.md │ │ ├── a.go │ │ ├── b.xgo │ │ └── xgo_autogen.go │ ├── sliceliteral/ │ │ └── sliceliteral.xgo │ ├── stringtrans/ │ │ └── transform.xgo │ ├── tpl-calc/ │ │ └── calc.xgo │ ├── tpl-calc-dump/ │ │ └── calc_dump.xgo │ ├── tpl-intlist/ │ │ └── ints.xgo │ ├── tpl-natural-lang/ │ │ └── nlang.xgo │ ├── tpl-parser-demo/ │ │ └── demo.xgo │ ├── tpl-pseudo/ │ │ ├── gauss.pseudo │ │ └── pseudo.gox │ ├── tpl-vcalc/ │ │ └── variant_calc.xgo │ ├── tupletype/ │ │ └── tuple.xgo │ ├── typeasparamsfunc/ │ │ ├── col.go │ │ └── typeAsParamsFunc.xgo │ ├── typeasparamsmethod/ │ │ ├── col.go │ │ └── typeAsParamsMethod.xgo │ ├── typeparamscast/ │ │ ├── foo.go │ │ └── typecast.xgo │ ├── unit-test/ │ │ ├── foo.xgo │ │ └── foo_test.gox │ ├── unitliteral/ │ │ └── unitlit.xgo │ ├── xgo-calc/ │ │ └── calc.xgo │ ├── xgo-parser/ │ │ └── parser.xgo │ ├── xgo-sample/ │ │ ├── a.xgo │ │ ├── b.xgo │ │ └── cpkag/ │ │ └── b/ │ │ └── ab.go │ ├── xgo-scanner/ │ │ ├── rpncalc/ │ │ │ └── rpncalc.xgo │ │ ├── scanner.xgo │ │ └── simplecalc/ │ │ └── calc.xgo │ └── xgo-typeof/ │ └── typeof.xgo ├── doc/ │ ├── _testdata/ │ │ ├── gopoFn/ │ │ │ ├── in.go │ │ │ └── out.expect │ │ ├── gopoMethod/ │ │ │ ├── in.go │ │ │ └── out.expect │ │ ├── overloadFn/ │ │ │ ├── in.go │ │ │ └── out.expect │ │ ├── overloadMethod/ │ │ │ ├── in.go │ │ │ └── out.expect │ │ ├── xgoOverloadFn/ │ │ │ ├── in.go │ │ │ └── out.expect │ │ └── xgoOverloadMethod/ │ │ ├── in.go │ │ └── out.expect │ ├── builtin.md │ ├── classfile.md │ ├── code-coverage.md │ ├── contributing.md │ ├── docs.md │ ├── domian-text-lit.md │ ├── fncall.md │ ├── func-closure.md │ ├── goodbye-printf.md │ ├── map.md │ ├── overload.md │ ├── slice.md │ ├── spec/ │ │ └── mini/ │ │ └── mini.xgo │ ├── spec-mini.md │ ├── spec.md │ ├── string.md │ ├── struct-vs-tuple.md │ ├── xgo-vs-go.md │ ├── z_gop.go │ ├── z_test.go │ └── z_transform.go ├── dql/ │ ├── README.md │ ├── dql.go │ ├── fetcher/ │ │ ├── fetch.go │ │ ├── github.com/ │ │ │ ├── issueTask/ │ │ │ │ ├── issueTask.xgo │ │ │ │ └── xgo_autogen.go │ │ │ └── repoList/ │ │ │ ├── repoList.xgo │ │ │ └── xgo_autogen.go │ │ ├── hrefs/ │ │ │ ├── hrefs.xgo │ │ │ └── xgo_autogen.go │ │ ├── pkg.go.dev/ │ │ │ └── importedBy/ │ │ │ ├── importedBy.xgo │ │ │ └── xgo_autogen.go │ │ └── pytorch.org/ │ │ └── fndoc/ │ │ ├── fndoc.xgo │ │ └── xgo_autogen.go │ ├── fs/ │ │ └── fs.go │ ├── golang/ │ │ ├── golang.go │ │ └── parse.go │ ├── html/ │ │ ├── html.go │ │ ├── html_test.go │ │ ├── node.go │ │ └── text.go │ ├── json/ │ │ └── json.go │ ├── maps/ │ │ ├── maps.go │ │ └── node.go │ ├── reflects/ │ │ ├── node.go │ │ └── reflects.go │ ├── xgo/ │ │ ├── parse.go │ │ └── xgo.go │ ├── xml/ │ │ ├── node.go │ │ ├── text.go │ │ └── xml.go │ └── yaml/ │ └── yaml.go ├── encoding/ │ ├── csv/ │ │ └── csv.go │ ├── fs/ │ │ └── fs.go │ ├── golang/ │ │ └── golang.go │ ├── html/ │ │ └── html.go │ ├── json/ │ │ └── json.go │ ├── regexp/ │ │ └── regexp.go │ ├── regexposix/ │ │ └── regexp.go │ ├── xgo/ │ │ └── xgo.go │ ├── xml/ │ │ └── xml.go │ └── yaml/ │ └── yaml.go ├── env/ │ ├── build.go │ ├── goenv.go │ ├── gop_nonwindows.go │ ├── path.go │ ├── path_test.go │ ├── sys_others.go │ ├── sys_plan9.go │ ├── sys_windows.go │ ├── version.go │ └── version_test.go ├── format/ │ ├── format.go │ ├── formatutil/ │ │ ├── _testdata/ │ │ │ ├── format/ │ │ │ │ ├── basic/ │ │ │ │ │ ├── in.data │ │ │ │ │ └── out.expect │ │ │ │ └── nondecl/ │ │ │ │ ├── in.data │ │ │ │ └── out.expect │ │ │ ├── rearrange/ │ │ │ │ ├── noeol/ │ │ │ │ │ ├── in.data │ │ │ │ │ └── out.expect │ │ │ │ └── nondecl/ │ │ │ │ ├── in.data │ │ │ │ └── out.expect │ │ │ └── splitstmts/ │ │ │ └── basic/ │ │ │ ├── in.data │ │ │ └── out.expect │ │ ├── format_gop.go │ │ └── format_test.go │ └── internal.go ├── go.mod ├── go.sum ├── make.bash ├── make.bat ├── parser/ │ ├── _instance/ │ │ ├── instance1/ │ │ │ ├── cmd.xgo │ │ │ └── parser.expect │ │ ├── instance2/ │ │ │ ├── cmd.xgo │ │ │ └── parser.expect │ │ ├── instance3/ │ │ │ ├── cmd.xgo │ │ │ └── parser.expect │ │ ├── instance4/ │ │ │ ├── cmd.xgo │ │ │ └── parser.expect │ │ └── instance5/ │ │ ├── cmd.xgo │ │ └── parser.expect │ ├── _nofmt/ │ │ ├── cmdlinestyle1/ │ │ │ ├── cmd.xgo │ │ │ └── parser.expect │ │ ├── cmdlinestyle2/ │ │ │ ├── cmd2.xgo │ │ │ └── parser.expect │ │ ├── cmdlinestyle3/ │ │ │ ├── cmd.xgo │ │ │ └── parser.expect │ │ ├── exists/ │ │ │ ├── exists.xgo │ │ │ └── parser.expect │ │ ├── forloop/ │ │ │ ├── forloop.xgo │ │ │ └── parser.expect │ │ ├── listcompr/ │ │ │ ├── listcompr.xgo │ │ │ └── parser.expect │ │ ├── matrix1/ │ │ │ ├── matrix.xgo │ │ │ └── parser.expect │ │ ├── printvariadic/ │ │ │ ├── parser.expect │ │ │ └── printv.xgo │ │ ├── rangeexpr1/ │ │ │ ├── parser.expect │ │ │ └── rangeexpr.xgo │ │ ├── selectdata/ │ │ │ ├── parser.expect │ │ │ └── select.xgo │ │ ├── structtag/ │ │ │ ├── parser.expect │ │ │ └── tag.xgo │ │ └── tupletype/ │ │ ├── parser.expect │ │ └── tuple.xgo │ ├── _testdata/ │ │ ├── append1/ │ │ │ ├── append.xgo │ │ │ └── parser.expect │ │ ├── append2/ │ │ │ ├── append.xgo │ │ │ └── parser.expect │ │ ├── arrowop/ │ │ │ ├── arrowop.xgo │ │ │ └── parser.expect │ │ ├── autoprop/ │ │ │ ├── goto.xgo │ │ │ └── parser.expect │ │ ├── build/ │ │ │ ├── build.xgo │ │ │ └── parser.expect │ │ ├── c2gohello/ │ │ │ ├── hello.xgo │ │ │ └── parser.expect │ │ ├── classfile_init1/ │ │ │ ├── Rect.gox │ │ │ └── parser.expect │ │ ├── classfile_init2/ │ │ │ ├── Rect.gox │ │ │ └── parser.expect │ │ ├── cmdlinestyle1/ │ │ │ ├── cmd.xgo │ │ │ └── parser.expect │ │ ├── cmdlinestyle2/ │ │ │ ├── cmd2.xgo │ │ │ └── parser.expect │ │ ├── cmdlinestyle3/ │ │ │ ├── cmd3.xgo │ │ │ └── parser.expect │ │ ├── cmdlinestyle4/ │ │ │ ├── cmd4.xgo │ │ │ └── parser.expect │ │ ├── collection/ │ │ │ ├── collection.xgo │ │ │ └── parser.expect │ │ ├── complit/ │ │ │ ├── complit.xgo │ │ │ └── parser.expect │ │ ├── domainhuh/ │ │ │ ├── huh.xgo │ │ │ └── parser.expect │ │ ├── domaintext/ │ │ │ ├── parser.expect │ │ │ └── tpl.xgo │ │ ├── domaintpl/ │ │ │ ├── parser.expect │ │ │ └── tpl.xgo │ │ ├── dql1/ │ │ │ ├── dql.xgo │ │ │ └── parser.expect │ │ ├── dql2/ │ │ │ ├── dql.xgo │ │ │ └── parser.expect │ │ ├── dql3/ │ │ │ ├── dql.xgo │ │ │ └── parser.expect │ │ ├── embedded1/ │ │ │ ├── embtype.xgo │ │ │ └── parser.expect │ │ ├── envop1/ │ │ │ ├── envop.xgo │ │ │ └── parser.expect │ │ ├── envop2/ │ │ │ ├── envop.xgo │ │ │ └── parser.expect │ │ ├── errwrap1/ │ │ │ ├── errwrap.xgo │ │ │ └── parser.expect │ │ ├── errwrap2/ │ │ │ ├── errwrap2.xgo │ │ │ └── parser.expect │ │ ├── errwrap3/ │ │ │ ├── errwrap3.xgo │ │ │ └── parser.expect │ │ ├── exists/ │ │ │ ├── exists.xgo │ │ │ └── parser.expect │ │ ├── fnbody/ │ │ │ ├── fnbody.xgo │ │ │ └── parser.expect │ │ ├── fncall/ │ │ │ ├── fncall.xgo │ │ │ └── parser.expect │ │ ├── forloop/ │ │ │ ├── forloop.xgo │ │ │ └── parser.expect │ │ ├── funcdecl1/ │ │ │ ├── fndecl.xgo │ │ │ └── parser.expect │ │ ├── funcdecl2/ │ │ │ ├── fndecl.xgo │ │ │ └── parser.expect │ │ ├── funcdecl3/ │ │ │ ├── fndecl.xgo │ │ │ └── parser.expect │ │ ├── funcdoc/ │ │ │ ├── funcdoc.xgo │ │ │ └── parser.expect │ │ ├── funclit/ │ │ │ ├── funclit.xgo │ │ │ └── parser.expect │ │ ├── functype/ │ │ │ ├── dummy/ │ │ │ │ └── dummy.md │ │ │ ├── functype.go │ │ │ └── parser.expect │ │ ├── gmxtest/ │ │ │ ├── foo.gmx │ │ │ └── parser.expect │ │ ├── goto1/ │ │ │ ├── goto.xgo │ │ │ └── parser.expect │ │ ├── goto2/ │ │ │ ├── goto.xgo │ │ │ └── parser.expect │ │ ├── goxtest1/ │ │ │ ├── bar.gox │ │ │ └── parser.expect │ │ ├── goxtest2/ │ │ │ ├── bar.gox │ │ │ └── parser.expect │ │ ├── kwargs1/ │ │ │ ├── kwargs.xgo │ │ │ └── parser.expect │ │ ├── lambda1/ │ │ │ ├── lambda.xgo │ │ │ └── parser.expect │ │ ├── lambda2/ │ │ │ ├── lambda2.xgo │ │ │ └── parser.expect │ │ ├── lambda3/ │ │ │ ├── lambda3.xgo │ │ │ └── parser.expect │ │ ├── lambda4/ │ │ │ ├── lambda4.xgo │ │ │ └── parser.expect │ │ ├── listcompr/ │ │ │ ├── listcompr.xgo │ │ │ └── parser.expect │ │ ├── mapfunc/ │ │ │ ├── map.xgo │ │ │ └── parser.expect │ │ ├── matrix1/ │ │ │ ├── matrix.xgo │ │ │ └── parser.expect │ │ ├── matrix2/ │ │ │ ├── matrix.xgo │ │ │ └── parser.expect │ │ ├── mytest/ │ │ │ ├── mytest.xgo │ │ │ └── parser.expect │ │ ├── optparam/ │ │ │ ├── optparam.xgo │ │ │ └── parser.expect │ │ ├── overload1/ │ │ │ ├── overload.xgo │ │ │ └── parser.expect │ │ ├── overload2/ │ │ │ ├── overload2.xgo │ │ │ └── parser.expect │ │ ├── overloadop/ │ │ │ ├── op_overload.xgo │ │ │ └── parser.expect │ │ ├── printvariadic/ │ │ │ ├── parser.expect │ │ │ └── printv.xgo │ │ ├── pystr/ │ │ │ ├── parser.expect │ │ │ └── pystr.xgo │ │ ├── rangeexpr1/ │ │ │ ├── parser.expect │ │ │ └── rangeexpr.xgo │ │ ├── rangeexpr2/ │ │ │ ├── parser.expect │ │ │ └── rangeexpr.xgo │ │ ├── rangeexpr3/ │ │ │ ├── parser.expect │ │ │ └── rangeexpr.xgo │ │ ├── rational/ │ │ │ ├── parser.expect │ │ │ └── rational.xgo │ │ ├── selectdata/ │ │ │ ├── parser.expect │ │ │ └── select.xgo │ │ ├── slice1/ │ │ │ ├── parser.expect │ │ │ └── slice.xgo │ │ ├── slice2/ │ │ │ ├── parser.expect │ │ │ └── slice2.xgo │ │ ├── spxtest/ │ │ │ ├── foo.spx │ │ │ └── parser.expect │ │ ├── staticmthd1/ │ │ │ ├── parser.expect │ │ │ └── static_method.xgo │ │ ├── staticmthd2/ │ │ │ ├── a.gox │ │ │ └── parser.expect │ │ ├── stdtype/ │ │ │ ├── parser.expect │ │ │ └── stdtype.xgo │ │ ├── stringex1/ │ │ │ ├── parser.expect │ │ │ └── string_lit.xgo │ │ ├── stringex2/ │ │ │ ├── parser.expect │ │ │ └── string_lit.xgo │ │ ├── stringex3/ │ │ │ ├── parser.expect │ │ │ └── string_lit.xgo │ │ ├── tuplelit/ │ │ │ ├── parser.expect │ │ │ └── tuplelit.xgo │ │ ├── tupletype/ │ │ │ ├── parser.expect │ │ │ └── tupletype.xgo │ │ ├── typeof/ │ │ │ ├── parser.expect │ │ │ └── typeof.xgo │ │ ├── typeswitch/ │ │ │ ├── parser.expect │ │ │ └── typeswitch.xgo │ │ └── unit/ │ │ ├── parser.expect │ │ └── step.xgo │ ├── _testexpr/ │ │ └── lambda/ │ │ ├── in.xgo │ │ └── out.expect │ ├── fsx/ │ │ ├── fsys.go │ │ └── memfs/ │ │ ├── fs.go │ │ └── memfs.go │ ├── interface.go │ ├── parser.go │ ├── parser_test.go │ ├── parser_xgo.go │ ├── parserdir_go118_test.go │ ├── parserdir_test.go │ └── parsertest/ │ └── parsertest.go ├── printer/ │ ├── _testdata/ │ │ ├── 02-Var-and-operator/ │ │ │ └── var_and_op.xgo │ │ ├── 03-Import-go-package/ │ │ │ └── import.xgo │ │ ├── 04-Func/ │ │ │ └── func.xgo │ │ ├── 05-Closure/ │ │ │ └── closure.xgo │ │ ├── 06-String-Map-Array-Slice/ │ │ │ └── datastruct.xgo │ │ ├── 07-MapLit/ │ │ │ └── maplit.xgo │ │ ├── 08-SliceLit/ │ │ │ └── slicelit.xgo │ │ ├── 09-IfElse-SwitchCase/ │ │ │ └── flow.xgo │ │ ├── 10-List-comprehension/ │ │ │ └── list_comprehens.xgo │ │ ├── 11-Map-comprehension/ │ │ │ └── map_comprehens.xgo │ │ ├── 12-Select-comprehension/ │ │ │ └── select.xgo │ │ ├── 12-Select-comprehension2/ │ │ │ └── findscore.xgo │ │ ├── 13-Exists-comprehension/ │ │ │ └── exists.xgo │ │ ├── 14-Using-goplus-in-Go/ │ │ │ └── foo/ │ │ │ ├── foo.xgo │ │ │ ├── foo_test.xgo │ │ │ └── footest_test.xgo │ │ ├── 15-ErrWrap/ │ │ │ └── err_wrap.xgo │ │ ├── 16-Fib/ │ │ │ └── fib.xgo │ │ ├── 17-Fibtc/ │ │ │ └── fibtc.xgo │ │ ├── 18-Rational/ │ │ │ └── rational.xgo │ │ ├── 21-Break-continue-goto/ │ │ │ └── flow.xgo │ │ ├── 22-For-loop/ │ │ │ └── for.xgo │ │ ├── 23-Defer/ │ │ │ └── defer.xgo │ │ ├── 24-Goroutine/ │ │ │ └── goroutine.xgo │ │ ├── 25-Struct/ │ │ │ └── struct.xgo │ │ ├── 26-Method/ │ │ │ └── method.xgo │ │ ├── 27-Func-Set/ │ │ │ └── func.xgo │ │ ├── 28-Chan/ │ │ │ └── chan.xgo │ │ ├── 29-CompareToNil/ │ │ │ └── ref.xgo │ │ ├── 30-Recover/ │ │ │ └── recover.xgo │ │ ├── 31-Builtin-Typecast/ │ │ │ └── builtin_and_typecast.xgo │ │ ├── 32-Import-gop-package/ │ │ │ └── import_gop_pkg.xgo │ │ ├── 33-Interface/ │ │ │ └── shape.xgo │ │ ├── 34-Type-assert/ │ │ │ └── type_assert.xgo │ │ ├── 35-Chan-select/ │ │ │ └── select.xgo │ │ ├── 36-Auto-Property/ │ │ │ └── autoprop.xgo │ │ ├── 37-Cmdline/ │ │ │ └── cmdline.xgo │ │ ├── 38-Overload-operator/ │ │ │ └── overload_op.xgo │ │ ├── 39-Lambda-expression/ │ │ │ └── lambda.xgo │ │ ├── 40-Deduce-struct-type/ │ │ │ └── deduce.xgo │ │ ├── 41-UDT-RangeForEach/ │ │ │ └── udt_range.xgo │ │ ├── 42-UDT-RangeIterator/ │ │ │ └── udt_range_iter.xgo │ │ └── 43-RangeExpr/ │ │ └── rangeexpr.xgo │ ├── bugfix_test.go │ ├── nodes.go │ ├── printer.go │ ├── printer_test.go │ └── xgo_test.go ├── scanner/ │ ├── gop.go │ └── scanner.go ├── test/ │ └── classfile.go ├── token/ │ ├── internal/ │ │ └── tokenutil/ │ │ ├── lines_go118.go │ │ ├── lines_go120.go │ │ ├── lines_go121.go │ │ └── lines_test.go │ ├── token.go │ ├── token_test.go │ └── types.go ├── tool/ │ ├── _gendeps.go │ ├── build_install_run.go │ ├── gengo.go │ ├── imp.go │ ├── load.go │ ├── outline.go │ └── tidy.go ├── tpl/ │ ├── README.md │ ├── ast/ │ │ └── ast.go │ ├── cl/ │ │ └── compile.go │ ├── matcher/ │ │ └── match.go │ ├── parser/ │ │ ├── _testdata/ │ │ │ ├── adjoin/ │ │ │ │ ├── in.xgo │ │ │ │ └── out.expect │ │ │ ├── pseudo/ │ │ │ │ ├── in.xgo │ │ │ │ └── out.expect │ │ │ ├── simple1/ │ │ │ │ ├── in.xgo │ │ │ │ └── out.expect │ │ │ └── simple2/ │ │ │ ├── in.xgo │ │ │ └── out.expect │ │ ├── parser.go │ │ ├── parser_test.go │ │ └── parsertest/ │ │ └── parsertest.go │ ├── scanner/ │ │ ├── _testdata/ │ │ │ ├── cstr/ │ │ │ │ ├── go.expect │ │ │ │ ├── gop.expect │ │ │ │ ├── in.xgo │ │ │ │ └── tpl.expect │ │ │ ├── num/ │ │ │ │ ├── go.expect │ │ │ │ ├── gop.expect │ │ │ │ ├── in.xgo │ │ │ │ └── tpl.expect │ │ │ ├── pow/ │ │ │ │ ├── go.expect │ │ │ │ ├── gop.expect │ │ │ │ ├── in.xgo │ │ │ │ └── tpl.expect │ │ │ ├── rat/ │ │ │ │ ├── go.expect │ │ │ │ ├── gop.expect │ │ │ │ ├── in.xgo │ │ │ │ └── tpl.expect │ │ │ └── unit/ │ │ │ ├── go.expect │ │ │ ├── gop.expect │ │ │ ├── in.xgo │ │ │ └── tpl.expect │ │ ├── error.go │ │ ├── scandir_test.go │ │ ├── scanner.go │ │ ├── scanner_test.go │ │ └── scannertest/ │ │ └── scannertest.go │ ├── token/ │ │ ├── token.go │ │ ├── token_test.go │ │ └── types.go │ ├── tpl.go │ ├── types/ │ │ └── types.go │ └── variant/ │ ├── builtin/ │ │ └── builtin.go │ ├── delay/ │ │ └── delay.go │ ├── math/ │ │ └── math.go │ ├── module.go │ ├── time/ │ │ └── time.go │ └── variant.go └── x/ ├── build/ │ ├── _testdata/ │ │ ├── hello/ │ │ │ ├── hello.expect │ │ │ └── main.xgo │ │ ├── multi/ │ │ │ ├── Rect.gox │ │ │ ├── main.xgo │ │ │ └── multi.expect │ │ └── pkg/ │ │ ├── pkg.expect │ │ └── pkg.xgo │ ├── build.go │ └── build_test.go ├── fakenet/ │ └── conn.go ├── format/ │ ├── README.md │ ├── _testdata/ │ │ ├── collection/ │ │ │ ├── format.expect │ │ │ └── index.xgo │ │ ├── gopsyntax/ │ │ │ ├── format.expect │ │ │ └── index.xgo │ │ └── syntax/ │ │ ├── format.expect │ │ └── index.xgo │ ├── format.go │ ├── gopstyle.go │ ├── gopstyle_test.go │ ├── gopstyledir_test.go │ └── stmt_expr_or_type.go ├── fsnotify/ │ └── fsnotify.go ├── gocmd/ │ ├── build_install.go │ ├── gocmd.go │ └── run.go ├── jsonrpc2/ │ ├── conn.go │ ├── frame.go │ ├── internal/ │ │ └── stack/ │ │ ├── parse.go │ │ ├── process.go │ │ ├── stack.go │ │ └── stacktest/ │ │ └── stacktest.go │ ├── jsonrpc2.go │ ├── jsonrpc2test/ │ │ ├── cases/ │ │ │ └── testcase.go │ │ ├── jsonrpc2_test.go │ │ ├── pipe.go │ │ └── pipe_test.go │ ├── messages.go │ ├── serve.go │ ├── stdio/ │ │ ├── server.go │ │ └── stdio.go │ └── wire.go ├── langserver/ │ ├── client.go │ ├── serve_dial.go │ └── server.go ├── typesutil/ │ ├── api.go │ ├── builtin_test.go │ ├── check.go │ ├── check_test.go │ ├── code_string.go │ ├── codes.go │ ├── eval.go │ ├── exprstring.go │ ├── exprstring_test.go │ ├── gopinfo.go │ ├── info_test.go │ ├── internal/ │ │ └── typesutil/ │ │ └── types.go │ └── typeparams/ │ └── typeparams.go ├── watcher/ │ ├── changes.go │ └── watch.go ├── xgoenv/ │ └── env.go └── xgoprojs/ ├── proj.go └── proj_test.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ *.expect text eol=lf *.golden text eol=lf *.xgo text eol=lf *.gop text eol=lf *.go text eol=lf *.gox text eol=lf ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.yml ================================================ name: Bug Report description: Create a report to help us improve body: - type: markdown attributes: value: | ⚠️ Make sure to browse the opened and closed issues before submit your issue. - type: textarea id: sample attributes: label: "The following program `sample.gop` triggers an unexpected result" value: | // add a sample render: coffee validations: required: true - type: textarea id: expected attributes: label: Expected result render: console validations: required: true - type: textarea id: got attributes: label: Got description: |- ```console $ gop run ./sample.gop // output ``` placeholder: $ xgo run ./sample.gop render: console validations: required: true - type: input id: version attributes: label: XGo Version description: Can be a tag or a hash. validations: required: true - type: textarea id: additional attributes: label: Additional Notes description: Use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown) if needed. validations: required: false ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ contact_links: - name: Visit the XGo website url: https://xgo.dev about: Much help can be found there ================================================ FILE: .github/ISSUE_TEMPLATE/enhancement.yml ================================================ name: Feature request description: Propose a change to XGo body: - type: markdown attributes: value: | ⚠️ Make sure to browse the opened and closed issues before submit your issue. - type: textarea id: proposal attributes: label: Proposal description: Write your feature request in the form of a proposal to be considered for implementation. validations: required: true - type: textarea id: background attributes: label: Background description: Describe the background problem or need that led to this feature request. validations: required: true - type: textarea id: workarounds attributes: label: Workarounds description: Are there any current workarounds that you're using that others in similar positions should know about? validations: required: true ================================================ FILE: .github/codecov.yml ================================================ coverage: ignore: - "builtin" - "ast" - "test" - "scanner" - "format" - "demo" - "demo/unit-test" - "doc/spec" - "cmd" - "cl/cltest" - "cl/outline" - "cl/internal/dql" - "cl/internal/huh" - "cl/internal/llgo-hello" - "cl/internal/mcp" - "cl/internal/overload" - "cl/internal/spx" - "cl/internal/spx2" - "cl/internal/spx3" - "cl/internal/test" - "cl/internal/unit" - "parser/fsx" - "parser/iox" - "parser/parsertest" - "x/jsonrpc2" - "x/jsonrpc2/jsonrpc2test" - "x/watcher" - "x/langserver" - "x/fsnotify" - "x/fakenet" - "x/typesutil" - "x/gocmd" - "x/gopenv" - "tool" - "encoding" - "tpl" - "dql" ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: github-actions directory: / labels: - dependabot - actions schedule: interval: daily - package-ecosystem: "gomod" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "daily" ================================================ FILE: .github/workflows/check_goreleaser_config.py ================================================ import os import subprocess import yaml files = [f for f in os.listdir('.') if not f.startswith( ".") and f not in ["VERSION"]] files.sort() # filter out the files that are ignored by git files = [f for f in files if subprocess.call( ["git", "ls-files", "--error-unmatch", f], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0] gorel = yaml.load(open(".goreleaser.yaml", "r"), Loader=yaml.FullLoader) scfiles = [f["source"] for f in gorel["snapcrafts"][0]["extra_files"]] scfiles.sort() failed = False if files != scfiles: failed = True print("Files in snapcraft are different from the ones in the repo") print("Update .goreleaser.yaml in the snapcraft section:") for f in files: print(f" - source: \"{f}\"") print(f" destination: \"{f}\"") nfpms_files = [f["src"] for f in gorel["nfpms"][0]["contents"] if f.get("type") != "symlink"] nfpms_files.sort() if files != nfpms_files: failed = True print("Files in nfpms are different from the ones in the repo") print("Update .goreleaser.yaml in the nfpms section:") for f in files: print(f" - src: \"{f}\"") print(f" dst: \"/usr/lib/{{{{ .ProjectName }}}}/{f}\"") # Check archives[0].files archives_files = gorel["archives"][0].get("files", []) archives_files.sort() if files != archives_files: failed = True print("Files in archives are different from the ones in the repo") print("Update .goreleaser.yaml in the archives section:") for f in files: print(f" - \"{f}\"") if failed: exit(1) print(".goreleaser checks passed") ================================================ FILE: .github/workflows/go.yml ================================================ name: XGo CI on: push: branches: - "*" pull_request: branches: - "*" jobs: Check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Check goreleaser files run: | pip install --no-input pyyaml python .github/workflows/check_goreleaser_config.py TestXGoInstaller: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Test XGo installer run: | git config --global user.email "build-robot@xgo.dev" git config --global user.name "build robot" go test -v cmd/make_test.go Test: strategy: matrix: os: - ubuntu-latest - windows-latest - macos-latest go: - 1.24.x - 1.25.x runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v6 with: go-version: ${{ matrix.go }} - name: Run testcases run: go test -v -coverprofile="coverage.txt" -covermode=atomic ./... - name: Codecov uses: codecov/codecov-action@v5 with: slug: goplus/xgo FormatCheck: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set up Go uses: actions/setup-go@v6 with: go-version: "1.24.2" - name: Check formatting run: | if [ -n "$(go fmt ./... | grep -v xgo_autogen)" ]; then echo "Some files are not properly formatted. Please run 'go fmt ./...'" exit 1 fi ./all.bash cd cmd/xgo; xgo fmt -t ./... ================================================ FILE: .github/workflows/release-build.yml ================================================ name: Release Build on: push: tags: - "v*" jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Check goreleaser files run: | pip install --no-input pyyaml python .github/workflows/check_goreleaser_config.py - name: Install Snapcraft uses: samuelmeuli/action-snapcraft@v3 - name: Checkout tag run: | git fetch --depth=1 origin +refs/tags/*:refs/tags/* tag_name="${GITHUB_REF##*/}" echo Current tag: $tag_name git checkout $tag_name echo "TAG_NAME=${tag_name}" >> $GITHUB_ENV - name: Check pre-release run: | tag="${TAG_NAME}" # check stable version format vX.Y.Z if ! [[ $tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "This is a pre-release: $tag" echo "IS_PRERELEASE=true" >> $GITHUB_ENV fi - name: Set up Go uses: actions/setup-go@v6 with: go-version: 1.24.x - name: Set up QEMU uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Log in to GitHub Container Registry uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ github.token }} - name: Release with goreleaser uses: goreleaser/goreleaser-action@v7 with: # either 'goreleaser' (default) or 'goreleaser-pro' distribution: goreleaser version: latest args: release --clean -p 4 env: GITHUB_TOKEN: ${{ github.token }} DOCKER_IMAGE_REPO: ghcr.io/${{ github.repository }} WINGET_PKGS_PRIVATE_KEY: ${{ secrets.WINGET_PKGS_PRIVATE_KEY }} - name: Upload deb/rpm to Fury.io if: env.IS_PRERELEASE != 'true' run: | for file in .dist/*.{deb,rpm} do echo "Uploading $file to Fury.io" CODE=`curl --write-out '%{http_code}' --output /dev/null -sS -F package=@$file https://$FURY_TOKEN@push.fury.io/$GITHUB_REPOSITORY_OWNER/` if [ "$CODE" != "200" ]; then echo "Upload failed with code $CODE" exit 1 fi done env: FURY_TOKEN: ${{ secrets.FURY_TOKEN }} ================================================ FILE: .github/xgopilot.yml ================================================ claude: model: "claude-4.6-opus" ================================================ FILE: .gitignore ================================================ # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so *.txt *.cache .DS_Store test.db go.json x.mod # gop # gopfmt # goptestgo # Folders _obj _test _old _t # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go coverage.txt .xgo/ .gop/ gop_autogen*.go xgo_autogen*.go !dql/fetcher/**/xgo_autogen.go !cmd/xgo/gop_autogen*.go !cmd/xgo/xgo_autogen*.go !cmd/hdq/xgo_autogen*.go !demo/fullspec/mixgo-complex/xgo_autogen.go !demo/mixgo/xgo_autogen.go go.json go.work* format.result *.cache *.exe *.test *.prof .vscode .idea cmd/qfmt/qfmt bin/ go-num/ _todo*.go .dist/ ================================================ FILE: .goreleaser.yaml ================================================ version: 2 env: - DOCKER_IMAGE_REPO={{ envOrDefault "DOCKER_IMAGE_REPO" "xgo" }} dist: .dist before: hooks: - go mod download builds: - id: xgo main: ./cmd/xgo binary: bin/xgo flags: - -trimpath ldflags: - -X github.com/goplus/xgo/env.buildVersion=v{{.Version}} - -X github.com/goplus/xgo/env.buildDate={{.CommitDate}} env: - CGO_ENABLED=0 goos: - linux - windows - darwin mod_timestamp: "{{ .CommitTimestamp }}" - id: gop main: ./cmd/xgo binary: bin/gop flags: - -trimpath ldflags: - -X github.com/goplus/xgo/env.buildVersion=v{{.Version}} - -X github.com/goplus/xgo/env.buildDate={{.CommitDate}} env: - CGO_ENABLED=0 goos: - linux - windows - darwin mod_timestamp: "{{ .CommitTimestamp }}" archives: - format: tar.gz name_template: >- {{.ProjectName}}{{.Version}}.{{.Os}}-{{.Arch}} {{- if .Arm}}v{{.Arm}}{{end}} format_overrides: - goos: windows format: zip files: - "CLAUDE.md" - "CODE_OF_CONDUCT.md" - "Dockerfile" - "LICENSE" - "Makefile" - "README.md" - "all.bash" - "all.bat" - "ast" - "tpl" - "tool" - "builtin" - "cl" - "cmd" - "doc" - "dql" - "encoding" - "env" - "format" - "go.mod" - "go.sum" - "make.bash" - "make.bat" - "parser" - "printer" - "scanner" - "test" - "demo" - "token" - "x" changelog: sort: asc filters: exclude: - "^docs:" - "^test:" dockers_v2: - id: xgo dockerfile: Dockerfile images: - "{{ .Env.DOCKER_IMAGE_REPO }}" tags: - "{{ .Version }}" - "{{ .Major }}.{{ .Minor }}" - "{{ .Major }}" - latest platforms: - linux/386 - linux/amd64 - linux/arm64 build_args: USE_GORELEASER_ARTIFACTS: "1" extra_files: - ./ winget: - name: goplus homepage: "https://xgo.dev/" publisher: goplus publisher_url: https://github.com/goplus/xgo publisher_support_url: "https://github.com/goplus/xgo/issues/new" package_identifier: goplus.xgo path: "manifests/g/goplus/xgo/{{.Version}}" tags: - golang - go - xgo - goplus - programming - language - compiler - interpreter - data science - engineering - education short_description: The XGo Programming Language description: | The XGo programming language is designed for engineering, STEM education, and data science. - For engineering: working in the simplest language that can be mastered by children. - For STEM education: studying an engineering language that can be used for work in the future. - For data science: communicating with engineers in the same language. license: Apache-2.0 skip_upload: auto release_notes: "{{.Changelog}}" release_notes_url: "https://github.com/{{ .Env.GITHUB_REPOSITORY_OWNER }}/xgo/releases/tag/v{{.Version}}" dependencies: - package_identifier: GoLang.Go minimum_version: 1.18.0 repository: owner: goplus name: winget-pkgs branch: "{{.ProjectName}}-v{{.Version}}" git: url: "git@github.com:{{ .Env.GITHUB_REPOSITORY_OWNER }}/winget-pkgs.git" private_key: "{{ .Env.WINGET_PKGS_PRIVATE_KEY }}" pull_request: enabled: true draft: true base: owner: microsoft name: winget-pkgs branch: master nfpms: - package_name: xgo vendor: goplus homepage: https://xgo.dev/ maintainer: Li Jie license: Apache-2.0 description: | The XGo programming language is designed for engineering, STEM education, and data science. - For engineering: working in the simplest language that can be mastered by children. - For STEM education: studying an engineering language that can be used for work in the future. - For data science: communicating with engineers in the same language. formats: - "deb" - "rpm" overrides: deb: dependencies: - "golang-go (>= 1.18.0)" rpm: dependencies: - "golang-bin >= 1.18.0" file_name_template: >- {{ .ProjectName }}{{.Version}}.{{ .Os }}-{{ .Arch }} {{- if .Arm }}v{{ .Arm }}{{ end }} bindir: /usr/lib/{{ .ProjectName }} contents: # source folder - src: "CLAUDE.md" dst: "/usr/lib/{{ .ProjectName }}/CLAUDE.md" - src: "CODE_OF_CONDUCT.md" dst: "/usr/lib/{{ .ProjectName }}/CODE_OF_CONDUCT.md" - src: "Dockerfile" dst: "/usr/lib/{{ .ProjectName }}/Dockerfile" - src: "LICENSE" dst: "/usr/lib/{{ .ProjectName }}/LICENSE" - src: "Makefile" dst: "/usr/lib/{{ .ProjectName }}/Makefile" - src: "README.md" dst: "/usr/lib/{{ .ProjectName }}/README.md" - src: "all.bash" dst: "/usr/lib/{{ .ProjectName }}/all.bash" - src: "all.bat" dst: "/usr/lib/{{ .ProjectName }}/all.bat" - src: "ast" dst: "/usr/lib/{{ .ProjectName }}/ast" - src: "tpl" dst: "/usr/lib/{{ .ProjectName }}/tpl" - src: "dql" dst: "/usr/lib/{{ .ProjectName }}/dql" - src: "tool" dst: "/usr/lib/{{ .ProjectName }}/tool" - src: "builtin" dst: "/usr/lib/{{ .ProjectName }}/builtin" - src: "cl" dst: "/usr/lib/{{ .ProjectName }}/cl" - src: "cmd" dst: "/usr/lib/{{ .ProjectName }}/cmd" - src: "doc" dst: "/usr/lib/{{ .ProjectName }}/doc" - src: "encoding" dst: "/usr/lib/{{ .ProjectName }}/encoding" - src: "env" dst: "/usr/lib/{{ .ProjectName }}/env" - src: "format" dst: "/usr/lib/{{ .ProjectName }}/format" - src: "go.mod" dst: "/usr/lib/{{ .ProjectName }}/go.mod" - src: "go.sum" dst: "/usr/lib/{{ .ProjectName }}/go.sum" - src: "make.bash" dst: "/usr/lib/{{ .ProjectName }}/make.bash" - src: "make.bat" dst: "/usr/lib/{{ .ProjectName }}/make.bat" - src: "parser" dst: "/usr/lib/{{ .ProjectName }}/parser" - src: "printer" dst: "/usr/lib/{{ .ProjectName }}/printer" - src: "scanner" dst: "/usr/lib/{{ .ProjectName }}/scanner" - src: "test" dst: "/usr/lib/{{ .ProjectName }}/test" - src: "demo" dst: "/usr/lib/{{ .ProjectName }}/demo" - src: "token" dst: "/usr/lib/{{ .ProjectName }}/token" - src: "x" dst: "/usr/lib/{{ .ProjectName }}/x" # symlinks to binaries - src: "/usr/lib/{{ .ProjectName }}/bin/xgo" dst: /usr/bin/xgo type: symlink - src: "/usr/lib/{{ .ProjectName }}/bin/xgo" dst: /usr/bin/gop type: symlink snapcrafts: - id: xgo name: xgo title: The XGo Programming Language summary: The XGo Programming Language description: | The XGo programming language is designed for engineering, STEM education, and data science. - For engineering: working in the simplest language that can be mastered by children. - For STEM education: studying an engineering language that can be used for work in the future. - For data science: communicating with engineers in the same language. confinement: classic license: Apache-2.0 name_template: >- {{ .ProjectName }}{{.Version}}.{{ .Os }}-{{ .Arch }} {{- if .Arm }}v{{ .Arm }}{{ end }} extra_files: # source folder - source: "CLAUDE.md" destination: "CLAUDE.md" - source: "CODE_OF_CONDUCT.md" destination: "CODE_OF_CONDUCT.md" - source: "Dockerfile" destination: "Dockerfile" - source: "LICENSE" destination: "LICENSE" - source: "Makefile" destination: "Makefile" - source: "README.md" destination: "README.md" - source: "all.bash" destination: "all.bash" - source: "all.bat" destination: "all.bat" - source: "ast" destination: "ast" - source: "tpl" destination: "tpl" - source: "dql" destination: "dql" - source: "tool" destination: "tool" - source: "builtin" destination: "builtin" - source: "cl" destination: "cl" - source: "cmd" destination: "cmd" - source: "doc" destination: "doc" - source: "encoding" destination: "encoding" - source: "env" destination: "env" - source: "format" destination: "format" - source: "go.mod" destination: "go.mod" - source: "go.sum" destination: "go.sum" - source: "make.bash" destination: "make.bash" - source: "make.bat" destination: "make.bat" - source: "parser" destination: "parser" - source: "printer" destination: "printer" - source: "scanner" destination: "scanner" - source: "test" destination: "test" - source: "demo" destination: "demo" - source: "token" destination: "token" - source: "x" destination: "x" apps: xgo: command: "xgo" aliases: ["xgo", "gop"] environment: XGOROOT: "$SNAP" checksum: name_template: "{{ .ProjectName }}{{ .Version }}.checksums.txt" release: prerelease: auto ================================================ FILE: CLAUDE.md ================================================ # XGo Project AI Assistant Guide ## Project Overview **XGo** is the first AI-native programming language that integrates software engineering into a unified whole. **Key Characteristics**: - Easy to learn with smaller syntax set than Go and Python - Ready for large projects with unified ecosystem integration ## My Role & Your Role - **My Role**: XGo language developer/contributor - **Your Role**: Senior programming language development assistant specializing in syntax design and compiler implementation ## Workflow & Collaboration Style ### Adding New Syntax Features When implementing new language syntax, follow this three-phase approach: **IMPORTANT**: Each phase must be implemented in a separate pull request. Do NOT mix phases in a single PR. This separation ensures: - Clear review focus (grammar vs semantics vs documentation) - Easier rollback if issues are found - Better git history and maintainability - Allows grammar to be reviewed independently from implementation details #### Phase 1: Grammar Definition (First Pull Request) **Scope**: AST, parser, and printer modifications ONLY - **AST**: Define new node types in `ast/` directory (if needed - often existing nodes can be reused) - **Parser**: Implement parsing rules in `parser/` directory to recognize the new syntax - **Printer**: Add formatting support for new syntax (inverse of parsing) in `printer/` directory - **Testing**: Add test cases in `parser/_testdata/` for new syntax - **Note**: Printer shares test cases with parser - do NOT create separate test files in `printer/_testdata/` - **What NOT to include**: Do NOT add any code generation or semantic logic in `cl/` package - that belongs in Phase 2 #### Phase 2: Semantic Implementation (Second Pull Request) **Scope**: Code generation via `cl` package ONLY - **Code Generation**: Implement semantics using `github.com/goplus/gogen` package - **Type Safety**: Leverage gogen's type information maintenance for semantic correctness - **Testing**: Add comprehensive test cases in `cl/_testgop/` covering various usage scenarios - **Prerequisite**: Phase 1 PR must be merged before starting Phase 2 #### Phase 3: Documentation (Third Pull Request) **Scope**: User-facing documentation updates ONLY - **Quick Start Guide**: Add feature documentation to `doc/docs.md` with practical examples - **Table of Contents**: Update TOC in quick start to include new feature section - **Language Specification**: Update specification documents (see Language Specification Structure below) - **Examples**: Provide clear, runnable code examples demonstrating the feature - **Prerequisite**: Phase 2 PR must be merged before starting Phase 3 ### Language Specification Structure XGo maintains two levels of language specifications to serve different user needs: #### MiniSpec (Recommended Best Practices) - **Purpose**: Simplified syntax set representing recommended best practices - **Audience**: All XGo users - everyone should learn and apply this subset - **Characteristics**: Simple, Turing-complete, and sufficient for elegant implementation of any business requirements - **Files to update**: - `doc/spec-mini.md` - MiniSpec documentation in markdown format - `doc/spec/mini/mini.xgo` - MiniSpec grammar definition in XGo TPL (EBNF-like) syntax #### FullSpec (Complete Language Syntax) - **Purpose**: Complete syntax set including all language features - **Audience**: Experts and library designers who need advanced features - **Characteristics**: Comprehensive syntax including specialized features beyond MiniSpec - **Files to update**: - `doc/spec.md` - FullSpec documentation in markdown format #### Determining Spec Classification for New Syntax When adding new syntax to XGo, you must determine whether it belongs in the MiniSpec or FullSpec: **Add to MiniSpec if the syntax**: - Represents a recommended best practice for general use - Is simple and intuitive for most users - Solves common programming problems elegantly - Should be learned by all XGo developers **Add to FullSpec only if the syntax**: - Is specialized for advanced use cases (e.g., library design) - Adds complexity that most users don't need - Provides alternative ways to accomplish tasks already covered in MiniSpec - Is primarily intended for expert developers **Update Process**: 1. Determine the appropriate specification level (MiniSpec or FullSpec) 2. Update the corresponding markdown documentation file(s) 3. If adding to MiniSpec, also update the TPL grammar file (`doc/spec/mini/mini.xgo`) 4. Ensure examples demonstrate the new syntax clearly ### Communication Protocol - When I request syntax additions, first confirm the exact grammar specification - Always consider backward compatibility with existing Go code - For ambiguous requirements, ask clarifying questions about: - Precedence and associativity rules - Error handling expectations - Integration with existing type system ## Technical Specifications ### Compiler Architecture - **Target**: XGo compiles to Go code, not machine code - **Foundation**: Built on `github.com/goplus/gogen` for robust Go AST generation - **Key Benefit**: gogen maintains type information, ensuring both syntactic and semantic correctness ## Quality Standards ### Code Requirements - Maintain full compatibility with existing Go ecosystem - Ensure new syntax doesn't break existing XGo/Go code - Follow Go idioms in generated code - Provide comprehensive error messages - **Code Formatting**: Run `go fmt` on any changed source files before committing ### Documentation Expectations - Update language specification documents - Add examples to Quick Start guide - Document any limitations or special considerations ### Testing Requirements - **Phase 1**: 100% test coverage for new syntax parsing in `parser/_testdata/` - **Phase 2**: Comprehensive test coverage for semantic implementation in `cl/_testgop/` covering: - Common usage scenarios - Edge cases and error conditions - Integration with existing type system - **Phase 3**: Documentation validation - Ensure all code examples in documentation are runnable and correct - Verify documentation accurately reflects implemented behavior - Check that TOC links work correctly ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at conduct@xgo.dev. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. ================================================ FILE: Dockerfile ================================================ ARG BASE_IMAGE=golang:1.24-bookworm FROM $BASE_IMAGE AS build ARG TARGETPLATFORM ARG USE_GORELEASER_ARTIFACTS=0 WORKDIR /usr/local/src/xgo COPY . . RUN << EOF set -eux XGOROOT=/usr/local/xgo mkdir -p "$XGOROOT" if [ "$USE_GORELEASER_ARTIFACTS" -eq 1 ]; then cp -rp "$TARGETPLATFORM"/* "$XGOROOT"/ else git ls-tree --full-tree --name-only -r HEAD | grep -vE "^\." | xargs -I {} cp --parents {} "$XGOROOT"/ ./all.bash mv bin "$XGOROOT"/ fi EOF FROM $BASE_IMAGE ENV XGOROOT=/usr/local/xgo COPY --from=build $XGOROOT/ $XGOROOT/ ENV PATH=$XGOROOT/bin:$PATH WORKDIR /xgo ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: Makefile ================================================ NAME := gop RELEASE_VERSION := `git describe --tags` BUILD_ROOT_DIR := build-dir .PHONY: clean all all: build clean: rm -rf $(BUILD_ROOT_DIR)/* rm -f bin/* build: go run cmd/make.go -build dist: $(MAKE) clean mkdir -p bin/ go build -o $(BUILD_ROOT_DIR)/make cmd/make.go $(MAKE) build-all build-all: darwin-amd64.zip darwin-arm64.zip linux-386.zip linux-amd64.zip \ linux-armv7.zip windows-386.zip windows-amd64.zip windows-armv7.zip windows-arm64.zip build-dist: @mkdir -p bin/ @rm -rf bin/* $(BUILD_ROOT_DIR)/make -build %.zip: % @echo "Building $(NAME)-$(RELEASE_VERSION)-$@" @rm -f $(BUILD_ROOT_DIR)/$(NAME)-$(RELEASE_VERSION)-$@ zip -r $(BUILD_ROOT_DIR)/$(NAME)-$(RELEASE_VERSION)-$@ . -x ".*" -x "*/.*" -x "$(BUILD_ROOT_DIR)/*" @echo "$(NAME)-$(RELEASE_VERSION)-$@ Done" darwin-amd64: $(MAKE) GOARCH=amd64 GOOS=darwin BUILD_DIR=$(BUILD_ROOT_DIR)/$@/bin build-dist darwin-arm64: $(MAKE) GOARCH=arm64 GOOS=darwin BUILD_DIR=$(BUILD_ROOT_DIR)/$@/bin build-dist linux-386: $(MAKE) GOARCH=386 GOOS=linux BUILD_DIR=$(BUILD_ROOT_DIR)/$@/bin build-dist linux-amd64: $(MAKE) GOARCH=amd64 GOOS=linux BUILD_DIR=$(BUILD_ROOT_DIR)/$@/bin build-dist linux-armv7: $(MAKE) GOARCH=arm GOOS=linux GOARM=7 BUILD_DIR=$(BUILD_ROOT_DIR)/$@/bin build-dist windows-386: $(MAKE) GOARCH=386 GOOS=windows EXE_SUFFIX=.exe BUILD_DIR=$(BUILD_ROOT_DIR)/$@/bin build-dist windows-amd64: $(MAKE) GOARCH=amd64 GOOS=windows EXE_SUFFIX=.exe BUILD_DIR=$(BUILD_ROOT_DIR)/$@/bin build-dist windows-armv7: $(MAKE) GOARCH=arm GOOS=windows EXE_SUFFIX=.exe GOARM=7 BUILD_DIR=$(BUILD_ROOT_DIR)/$@/bin build-dist windows-arm64: $(MAKE) GOARCH=arm64 GOOS=windows EXE_SUFFIX=.exe BUILD_DIR=$(BUILD_ROOT_DIR)/$@/bin build-dist ================================================ FILE: README.md ================================================

The XGo Programming Language

[xgo.dev](https://xgo.dev) | [Docs](doc/docs.md) | [XGo vs. Go](doc/xgo-vs-go.md) | [Tutorials](https://tutorial.xgo.dev) | [Playground](https://play.xgo.dev) | [XGo REPL (iXGo)](https://repl.xgo.dev) | [Contributing & compiler design](doc/contributing.md)
[![Build Status](https://github.com/goplus/xgo/actions/workflows/go.yml/badge.svg)](https://github.com/goplus/xgo/actions/workflows/go.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/goplus/xgo)](https://goreportcard.com/report/github.com/goplus/xgo) [![Coverage Status](https://codecov.io/gh/goplus/xgo/branch/main/graph/badge.svg)](https://codecov.io/gh/goplus/xgo) [![GitHub release](https://img.shields.io/github/v/tag/goplus/xgo.svg?label=release)](https://github.com/goplus/xgo/releases) [![Discord](https://img.shields.io/badge/Discord-online-success.svg?logo=discord&logoColor=white)](https://discord.com/invite/mYjWCJDcAr)
XGo is a programming language that reads like plain English. But it's also incredibly powerful — it lets you leverage assets from C/C++, Go, Python, and JavaScript/TypeScript, creating a unified software engineering ecosystem. ``` XGo := C * Go * Python * JavaScript + Scratch ``` Our vision is to **enable everyone to become a builder of the world**. #### Easy to learn * Simple and easy to understand * Smaller syntax set than Go and Python in best practices #### Ready for large projects * Integrate C/C++, Go, Python, and JavaScript/TypeScript into a unified ecosystem * Derived from Go and easy to build large projects from its good engineering foundation The XGo programming language is designed for engineering, STEM education, and data science. * **For engineering**: working in the simplest language that can be mastered by children. * **For STEM education**: studying an engineering language that can be used for work in the future. * **For data science**: communicating with engineers in the same language. For more details, see [Quick Start](doc/docs.md). ## Key Features of XGo * Approaching natural language expression and intuitive (see [How XGo simplifies Go's expressions](#how-xgo-simplifies-gos-expressions)). * Smallest but Turing-complete syntax set in best practices (see [The XGo Mini Specification](doc/spec-mini.md)). * Fully compatible with [Go](https://github.com/golang/go) and can mix Go/XGo code in the same package (see [The XGo Full Specification](doc/spec.md) and [Go/XGo Hybrid Programming](doc/docs.md#gogo-hybrid-programming)). * Integrating with the C ecosystem including Python/JavaScript and providing limitless possibilities based on [LLGo](https://github.com/goplus/llgo) (see [Importing C/C++ and Python libraries](#importing-cc-and-python-libraries)). * Does not support DSL (Domain-Specific Languages), but supports SDF (Specific Domain Friendliness) (see [XGo Classfiles](#xgo-classfiles) and [Domain Text Literals](doc/domian-text-lit.md)). ## How XGo simplifies Go's expressions Different from the function call style of most languages, XGo recommends command style code: ```coffee println "Hello world" ``` To emphasize our preference for command style, we introduce `echo` as an alias for `println`: ```coffee echo "Hello world" ``` For more discussion on coding style, see https://tutorial.xgo.dev/hello-world. Code style is just the first step. We have made many efforts to make the code more intuitive and closer to natural language expression. These include: | Go code | XGo code | Note | | ---- | ---- | ---- | | package main

import "fmt"

func main() {
    fmt.Println("Hi")
} | import "fmt"

fmt.Println("Hi")
| Program structure: XGo allows omitting `package main` and `func main` | | fmt.Println("Hi") | echo("Hi") | [More builtin functions](doc/builtin.md): It simplifies the expression of the most common tasks | | fmt.Println("Hi") | echo "Hi" | [Command-line](doc/fncall.md) style code: It reduces the number of parentheses in the code as much as possible, making it closer to natural language | | name := "Ken"
fmt.Printf(
  "Hi %s\n", name) | name := "Ken"
echo "Hi ${name}" | [Goodbye printf](doc/goodbye-printf.md), use `${expr}` in [string](doc/string.md) literals | | a := []int{1, 2, 3} | a := [1, 2, 3] | [List/Slice](doc/slice.md) literals | | a = append(a, 4)
a = append(a, 5, 6, 7) | a <- 4
a <- 5, 6, 7 | Append values to a list | | a := map[string]int{
    "Monday": 1,
    "Tuesday": 2,
} | a := {
    "Monday": 1,
    "Tuesday": 2,
} | [Map](doc/map.md) literals | | OnStart(func() {
    ...
}) | onStart => {
    ...
} | [Lambda](doc/func-closure.md) expressions | | Play("1.mp3", &Options{Loop: true}) | play "1.mp3", loop = true | Python-like [keyword arguments](doc/func-closure.md#keyword-arguments) (kwargs) | | type Rect struct {
    Width  float64
    Height float64
}
| type Rect (width, height float64) | [Tuples vs. Structs](doc/struct-vs-tuple.md): We encourage using tuples to implement UDTs instead of structs. | | type Rect struct {
    Width  float64
    Height float64
}

func (this *Rect) Area() float64 {
    return this.Width * this.Height
} | var (
    Width  float64
    Height float64
)

func Area() float64 {
    return Width * Height
} | [XGo Classfiles](doc/classfile.md): We can express OOP with global variables and functions. | For more details, see [The XGo Mini Specification](doc/spec-mini.md). ## Importing C/C++ and Python libraries XGo can choose different Go compilers as its underlying support. Currently known supported Go compilers include: * [go](https://go.dev/) (The official Go compiler supported by Google) * [llgo](https://github.com/goplus/llgo) (The Go compiler supported by the XGo team) * [tinygo](https://tinygo.org/) (A Go compiler for small places) Currently, XGo defaults to using [go](https://go.dev/) as its underlying support, but in the future, it will be [llgo](https://github.com/goplus/llgo). LLGo is a Go compiler based on [LLVM](https://llvm.org/) in order to better integrate Go with the C ecosystem including Python and JavaScript. It aims to expand the boundaries of Go/XGo, providing limitless possibilities such as: * Game development * AI and data science * WebAssembly * Embedded development * ... If you wish to use [llgo](https://github.com/goplus/llgo), specify the `-llgo` flag when initializing an XGo module: ```sh xgo mod init -llgo YourModulePath ``` This will generate a `go.mod` file with the following contents (It may vary slightly depending on the versions of local XGo and LLGo): ```go module YourModulePath go 1.21 // llgo 1.0 require github.com/goplus/lib v0.2.0 ``` Based on LLGo, XGo can import libraries written in C/C++ and Python. Here is an example (see [chello](demo/_llgo/chello/hello.xgo)) of printing `Hello world` using C's `printf`: ```go import "c" c.printf c"Hello world\n" ``` Here, `c"Hello world\n"` is a syntax supported by XGo, representing a null-terminated C-style string. To run this example, you can: ```sh cd YourModulePath # set work directory to your module xgo mod tidy # for generating go.sum file xgo run . ``` And here is an example (see [pyhello](demo/_llgo/pyhello/hello.xgo)) of printing `Hello world` using Python's `print`: ```go import "py/std" std.print py"Hello world" ``` Here, `py"Hello world"` is a syntax supported by XGo, representing a Python string. Here are more examples of XGo calling C/C++ and Python libraries: * [pytensor](demo/_llgo/pytensor/tensor.xgo): a simple demo using [py/torch](https://pkg.go.dev/github.com/goplus/lib/py/torch) * [tetris](demo/_llgo/tetris/tetris.xgo): a tetris game based on [c/raylib](https://pkg.go.dev/github.com/goplus/lib/c/raylib) * [sqlitedemo](demo/_llgo/sqlitedemo/sqlitedemo.xgo): a demo using [c/sqlite](https://pkg.go.dev/github.com/goplus/lib/c/sqlite) To find out more about LLGo/XGo's support for C/C++ and Python in detail, please refer to homepage of [llgo](https://github.com/goplus/llgo). ## XGo Classfiles ``` One language can change the whole world. XGo is a "DSL" for all domains. ``` Rob Pike once said that if he could only introduce one feature to Go, he would choose `interface` instead of `goroutine`. `classfile` (and `class framework`) is as important to XGo as `interface` is to Go. In the design philosophy of XGo, we do not recommend `DSL` (Domain Specific Language). But `SDF` (Specific Domain Friendliness) is very important. The XGo philosophy about `SDF` is: ``` Don't define a language for specific domain. Abstract domain knowledge for it. ``` XGo introduces `classfile` and `class framework` to abstract domain knowledge. * [What's Classfile?](doc/classfile.md#whats-classfile) * [Dive into XGo Classfiles](doc/classfile.md) Sound a bit abstract? Let's see some XGo class frameworks. * STEM Education: [spx: A Scratch Compatible 2D Game Engine](https://github.com/goplus/spx) * AI Programming: [mcp: An XGo implementation of the Model Context Protocol (MCP)](https://github.com/goplus/mcp) * AI Programming: [mcptest: An XGo MCP Test Framework](https://github.com/goplus/mcp/tree/main/mtest) * Web Programming: [yap: Yet Another HTTP Web Framework](https://github.com/goplus/yap) * Web Programming: [yaptest: An XGo HTTP Test Framework](https://github.com/goplus/yap/tree/main/ytest) * Web Programming: [ydb: An XGo Database Framework](https://github.com/goplus/yap/tree/main/ydb) * CLI Programming: [cobra: A Commander for modern XGo CLI interactions](https://github.com/goplus/cobra) * CLI Programming: [gsh: An alternative to write shell scripts](https://github.com/qiniu/x/tree/main/gsh) * Unit Test: [test: Unit Test](doc/classfile.md#class-framework-unit-test) ### yap: Yet Another HTTP Web Framework This classfile has the file suffix `.yap`. Create a file named [get.yap](https://github.com/goplus/yap/blob/main/demo/classfile2_hello/get.yap) with the following content: ```go html `Hello, YAP!` ``` Execute the following commands: ```sh xgo mod init hello xgo get github.com/goplus/yap@latest xgo mod tidy xgo run . ``` A simplest web program is running now. At this time, if you visit http://localhost:8080, you will get: ``` Hello, YAP! ``` YAP uses filenames to define routes. `get.yap`'s route is `get "/"` (GET homepage), and `get_p_#id.yap`'s route is `get "/p/:id"` (In fact, the filename can also be `get_p_:id.yap`, but it is not recommended because `:` is not allowed to exist in filenames under Windows). Let's create a file named [get_p_#id.yap](https://github.com/goplus/yap/blob/main/demo/classfile2_hello/get_p_%23id.yap) with the following content: ```coffee json { "id": ${id}, } ``` Execute `xgo run .` and visit http://localhost:8080/p/123, you will get: ``` {"id": "123"} ``` See [yap: Yet Another HTTP Web Framework](https://github.com/goplus/yap) for more details. ### spx: A Scratch Compatible 2D Game Engine ![Screen Shot1](https://github.com/goplus/spx/blob/v1/tutorial/01-Weather/1.jpg) ![Screen Shot2](https://github.com/goplus/spx/blob/v1/tutorial/01-Weather/2.jpg) Through this example you can learn how to implement dialogues between multiple actors. Here are some codes in [Kai.spx](https://github.com/goplus/spx/blob/v1/tutorial/01-Weather/Kai.spx): ```coffee onStart => { say "Where do you come from?", 2 broadcast "1" } onMsg "2", => { say "What's the climate like in your country?", 3 broadcast "3" } ``` We call `onStart` and `onMsg` to listen events. `onStart` is called when the program is started. And `onMsg` is called when someone calls `broadcast` to broadcast a message. When the program starts, Kai says `Where do you come from?`, and then broadcasts the message `1`. Who will recieve this message? Let's see codes in [Jaime.spx](https://github.com/goplus/spx/blob/v1/tutorial/01-Weather/Jaime.spx): ```coffee onMsg "1", => { say "I come from England.", 2 broadcast "2" } ``` Yes, Jaime recieves the message `1` and says `I come from England.`. Then he broadcasts the message `2`. Kai recieves it and says `What's the climate like in your country?`. The following procedures are very similar. In this way you can implement dialogues between multiple actors. See [spx: A Scratch Compatible 2D Game Engine](https://github.com/goplus/spx) for more details. ### gsh: XGo DevOps Tools Yes, now you can write `shell script` in XGo. It supports all shell commands. Let's create a file named [example.gsh](https://github.com/qiniu/x/blob/main/gsh/demo/hello/example.gsh) and write the following code: ```coffee mkdir "testgsh" ``` Don't need a `go.mod` file, just enter `xgo run ./example.gsh` directly to run. See [gsh: XGo DevOps Tools](https://github.com/qiniu/x/tree/main/gsh) for more details. ## How to install Note: Requires go1.19 or later ### on Windows ```sh winget install goplus.xgo ``` ### on Debian/Ubuntu ```sh sudo bash -c ' echo "deb [trusted=yes] https://pkgs.xgo.dev/apt/ /" > /etc/apt/sources.list.d/goplus.list' sudo apt update sudo apt install xgo ``` ### on RedHat/CentOS/Fedora ```sh sudo bash -c 'echo -e "[goplus]\nname=XGo Repo\nbaseurl=https://pkgs.xgo.dev/yum/\nenabled=1\ngpgcheck=0" > /etc/yum.repos.d/goplus.repo' sudo yum install xgo ``` ### on macOS/Linux (Homebrew) Install via [brew](https://brew.sh/) ```sh $ brew install xgo ``` ### from source code ```bash git clone https://github.com/goplus/xgo.git cd xgo # On mac/linux run: ./all.bash # On Windows run: all.bat ``` ## XGo Applications ### Game Programming * [A Scratch Compatible 2D Game Engine](https://github.com/goplus/spx) * [Aircraft War](https://github.com/goplus/AircraftWar) * [Flappy Bird](https://github.com/goplus/FlappyCalf) * [Maze Play](https://github.com/goplus/MazePlay) * [BetaGo](https://github.com/xushiwei/BetaGo) * [Gobang](https://github.com/xushiwei/Gobang) * [Dinosaur](https://github.com/xushiwei/Dinosaur) ### Web Programming * [yap: Yet Another HTTP Web Framework](https://github.com/goplus/yap) * [yaptest: HTTP Test Framework](https://github.com/goplus/yap/tree/main/ytest) * [ydb: Database Framework](https://github.com/goplus/yap#ydb-database-framework) ### DevOps Tools * [gsh: XGo DevOps Tools](https://github.com/qiniu/x/tree/main/gsh) ### Data Processing * [hdq: HTML DOM Query Language for XGo](https://github.com/goplus/hdq) ## IDE Plugins * vscode: [Go/XGo for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=goplus.gop) ## Contributing The XGo project welcomes all contributors. We appreciate your help! For more details, see [Contributing & compiler design](doc/contributing.md). ## Give a Star! ⭐ If you like or are using XGo to learn or start your projects, please give it a star. Thanks! ================================================ FILE: all.bash ================================================ #! /usr/bin/env bash # # Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # set -ex go run cmd/make.go --install --regtest --autoproxy ================================================ FILE: all.bat ================================================ go run cmd/make.go --install --regtest --autoproxy ================================================ FILE: ast/ast.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package ast declares the types used to represent syntax trees for XGo // packages. package ast import ( "go/ast" "github.com/goplus/xgo/token" ) // ---------------------------------------------------------------------------- // Interfaces // // There are 3 main classes of nodes: Expressions and type nodes, // statement nodes, and declaration nodes. The node names usually // match the corresponding Go spec production names to which they // correspond. The node fields correspond to the individual parts // of the respective productions. // // All nodes contain position information marking the beginning of // the corresponding source text segment; it is accessible via the // Pos accessor method. Nodes may contain additional position info // for language constructs where comments may be found between parts // of the construct (typically any larger, parenthesized subpart). // That position information is needed to properly position comments // when printing the construct. // Node interface: all node types implement the Node interface. type Node = ast.Node // Expr interface: all expression nodes implement the Expr interface. type Expr interface { Node exprNode() } // Stmt interface: all statement nodes implement the Stmt interface. type Stmt interface { Node stmtNode() } // Decl interface: all declaration nodes implement the Decl interface. type Decl interface { Node declNode() } // ---------------------------------------------------------------------------- // Comments // A Comment node represents a single //-style 、#-style or /*-style comment. type Comment = ast.Comment // A CommentGroup represents a sequence of comments // with no other tokens and no empty lines between. type CommentGroup = ast.CommentGroup // ---------------------------------------------------------------------------- // Expressions and types // A Field represents a Field declaration list in a struct type, // a method list in an interface type, or a parameter/result declaration // in a signature. // Field.Names is nil for unnamed parameters (parameter lists which only contain types) // and embedded struct fields. In the latter case, the field name is the type name. type Field struct { Doc *CommentGroup // associated documentation; or nil Names []*Ident // field/method/parameter names; or nil Type Expr // field/method/parameter type Tag *BasicLit // field tag; or nil Comment *CommentGroup // line comments; or nil Optional token.Pos // position of "?", or NoPos if not optional } // Pos returns position of first character belonging to the node. func (f *Field) Pos() token.Pos { if len(f.Names) > 0 { return f.Names[0].Pos() } return f.Type.Pos() } // End returns position of first character immediately after the node. func (f *Field) End() token.Pos { if f.Tag != nil { return f.Tag.End() } if f.Optional.IsValid() { return f.Optional + 1 } return f.Type.End() } // A FieldList represents a list of Fields, enclosed by parentheses or braces. type FieldList struct { Opening token.Pos // position of opening parenthesis/brace, if any List []*Field // field list; or nil Closing token.Pos // position of closing parenthesis/brace, if any } // Pos returns position of first character belonging to the node. func (f *FieldList) Pos() token.Pos { if f.Opening.IsValid() { return f.Opening } // the list should not be empty in this case; // be conservative and guard against bad ASTs if len(f.List) > 0 { return f.List[0].Pos() } return token.NoPos } // End returns position of first character immediately after the node. func (f *FieldList) End() token.Pos { if f.Closing.IsValid() { return f.Closing + 1 } // the list should not be empty in this case; // be conservative and guard against bad ASTs if n := len(f.List); n > 0 { return f.List[n-1].End() } return token.NoPos } // NumFields returns the number of parameters or struct fields represented by a FieldList. func (f *FieldList) NumFields() int { n := 0 if f != nil { for _, g := range f.List { m := len(g.Names) if m == 0 { m = 1 } n += m } } return n } // An expression is represented by a tree consisting of one // or more of the following concrete expression nodes. type ( // A BadExpr node is a placeholder for expressions containing // syntax errors for which no correct expression nodes can be // created. BadExpr struct { From, To token.Pos // position range of bad expression } // An Ident node represents an identifier. Ident struct { NamePos token.Pos // identifier position Name string // identifier name Obj *Object // denoted object; or nil } // An Ellipsis node stands for the "..." type in a // parameter list or the "..." length in an array type. Ellipsis struct { Ellipsis token.Pos // position of "..." Elt Expr // ellipsis element type (parameter lists only); or nil } // A FuncLit node represents a function literal. FuncLit struct { Type *FuncType // function type Body *BlockStmt // function body } // A CompositeLit node represents a composite literal. CompositeLit struct { Type Expr // literal type; or nil Lbrace token.Pos // position of "{" Elts []Expr // list of composite elements; or nil Rbrace token.Pos // position of "}" Incomplete bool // true if (source) expressions are missing in the Elts list } // A ParenExpr node represents a parenthesized expression. ParenExpr struct { Lparen token.Pos // position of "(" X Expr // parenthesized expression Rparen token.Pos // position of ")" } // A SelectorExpr node represents an expression followed by a selector. // Sel may not be a simple identifier. For example: // - x.field // - x."field-name" // - x.$attr // - x.$"attr-name" // - x.0, x.1, ... // - x.* SelectorExpr struct { X Expr // expression Sel *Ident // field selector (it may not be a simple identifier) } // An IndexExpr node represents an expression followed by an index. IndexExpr struct { X Expr // expression Lbrack token.Pos // position of "[" Index Expr // index expression Rbrack token.Pos // position of "]" } // An IndexListExpr node represents an expression followed by multiple // indices. IndexListExpr struct { X Expr // expression Lbrack token.Pos // position of "[" Indices []Expr // index expressions Rbrack token.Pos // position of "]" } // A SliceExpr node represents an expression followed by slice indices. SliceExpr struct { X Expr // expression Lbrack token.Pos // position of "[" Low Expr // begin of slice range; or nil High Expr // end of slice range; or nil Max Expr // maximum capacity of slice; or nil Slice3 bool // true if 3-index slice (2 colons present) Rbrack token.Pos // position of "]" } // A TypeAssertExpr node represents an expression followed by a // type assertion. TypeAssertExpr struct { X Expr // expression Lparen token.Pos // position of "(" Type Expr // asserted type; nil means type switch X.(type) Rparen token.Pos // position of ")" } // A StarExpr node represents an expression of the form "*" Expression. // Semantically it could be a unary "*" expression, or a pointer type. StarExpr struct { Star token.Pos // position of "*" X Expr // operand } // A UnaryExpr node represents a unary expression. // Unary "*" expressions are represented via StarExpr nodes. UnaryExpr struct { OpPos token.Pos // position of Op Op token.Token // operator X Expr // operand } // A BinaryExpr node represents a binary expression. BinaryExpr struct { X Expr // left operand OpPos token.Pos // position of Op Op token.Token // operator Y Expr // right operand } // A KeyValueExpr node represents (key : value) pairs // in composite literals. KeyValueExpr struct { Key Expr Colon token.Pos // position of ":" Value Expr } ) // ChanDir is the direction of a channel type is indicated by a bit // mask including one or both of the following constants. type ChanDir int const ( // SEND - ChanDir SEND ChanDir = 1 << iota // RECV - ChanDir RECV ) // A type is represented by a tree consisting of one // or more of the following type-specific expression // nodes. type ( // An ArrayType node represents an array or slice type. ArrayType struct { Lbrack token.Pos // position of "[" Len Expr // Ellipsis node for [...]T array types, nil for slice types Elt Expr // element type } // A StructType node represents a struct type. StructType struct { Struct token.Pos // position of "struct" keyword Fields *FieldList // list of field declarations Incomplete bool // true if (source) fields are missing in the Fields list } // Pointer types are represented via StarExpr nodes. // A FuncType node represents a function type. FuncType struct { Func token.Pos // position of "func" keyword (token.NoPos if there is no "func") TypeParams *FieldList // type parameters; or nil Params *FieldList // (incoming) parameters; non-nil Results *FieldList // (outgoing) results; or nil } // An InterfaceType node represents an interface type. InterfaceType struct { Interface token.Pos // position of "interface" keyword Methods *FieldList // list of methods Incomplete bool // true if (source) methods are missing in the Methods list } // A MapType node represents a map type. MapType struct { Map token.Pos // position of "map" keyword Key Expr Value Expr } // A ChanType node represents a channel type. ChanType struct { Begin token.Pos // position of "chan" keyword or "<-" (whichever comes first) Arrow token.Pos // position of "<-" (token.NoPos if there is no "<-") Dir ChanDir // channel direction Value Expr // value type } // A TupleType node represents a tuple type. // Tuple types are syntactic sugar for anonymous structs with ordinal field names. // Examples: // () ≡ struct{} // (T) ≡ T (degenerates to the type itself) // (T0, T1, ..., TN) ≡ struct{ _0 T0; _1 T1; ...; _N TN } // (name0 T0, name1 T1, ..., nameN TN) ≡ struct{ _0 T0; _1 T1; ...; _N TN } // Named fields are compile-time aliases only; runtime uses ordinal fields. TupleType struct { Lparen token.Pos // position of "(" Fields *FieldList // tuple element types (and optional names) Rparen token.Pos // position of ")" } ) // Pos and End implementations for expression/type nodes. // Pos returns position of first character belonging to the node. func (x *BadExpr) Pos() token.Pos { return x.From } // Pos returns position of first character belonging to the node. func (x *Ident) Pos() token.Pos { return x.NamePos } // Pos returns position of first character belonging to the node. func (x *Ellipsis) Pos() token.Pos { return x.Ellipsis } // Pos returns position of first character belonging to the node. func (x *FuncLit) Pos() token.Pos { return x.Type.Pos() } // Pos returns position of first character belonging to the node. func (x *CompositeLit) Pos() token.Pos { if x.Type != nil { return x.Type.Pos() } return x.Lbrace } // Pos returns position of first character belonging to the node. func (x *ParenExpr) Pos() token.Pos { return x.Lparen } // Pos returns position of first character belonging to the node. func (x *SelectorExpr) Pos() token.Pos { return x.X.Pos() } // Pos returns position of first character belonging to the node. func (x *IndexExpr) Pos() token.Pos { return x.X.Pos() } // Pos returns position of first character belonging to the node. func (x *IndexListExpr) Pos() token.Pos { return x.X.Pos() } // Pos returns position of first character belonging to the node. func (x *SliceExpr) Pos() token.Pos { return x.X.Pos() } // Pos returns position of first character belonging to the node. func (x *TypeAssertExpr) Pos() token.Pos { return x.X.Pos() } // Pos returns position of first character belonging to the node. func (x *StarExpr) Pos() token.Pos { return x.Star } // Pos returns position of first character belonging to the node. func (x *UnaryExpr) Pos() token.Pos { return x.OpPos } // Pos returns position of first character belonging to the node. func (x *BinaryExpr) Pos() token.Pos { return x.X.Pos() } // Pos returns position of first character belonging to the node. func (x *KeyValueExpr) Pos() token.Pos { return x.Key.Pos() } // Pos returns position of first character belonging to the node. func (x *ArrayType) Pos() token.Pos { return x.Lbrack } // Pos returns position of first character belonging to the node. func (x *StructType) Pos() token.Pos { return x.Struct } // Pos returns position of first character belonging to the node. func (x *FuncType) Pos() token.Pos { if x.Func.IsValid() || x.Params == nil { // see issue 3870 return x.Func } return x.Params.Pos() // interface method declarations have no "func" keyword } // Pos returns position of first character belonging to the node. func (x *InterfaceType) Pos() token.Pos { return x.Interface } // Pos returns position of first character belonging to the node. func (x *MapType) Pos() token.Pos { return x.Map } // Pos returns position of first character belonging to the node. func (x *ChanType) Pos() token.Pos { return x.Begin } // Pos returns position of first character belonging to the node. func (x *TupleType) Pos() token.Pos { return x.Lparen } // End returns position of first character immediately after the node. func (x *BadExpr) End() token.Pos { return x.To } // End returns position of first character immediately after the node. func (x *Ident) End() token.Pos { if x.Implicit() { // implicitly declared return x.NamePos } return x.NamePos + token.Pos(len(x.Name)) } // Implicit reports whether the identifier was implicitly declared func (x *Ident) Implicit() bool { o := x.Obj return o != nil && o.Kind >= implicitBase } // End returns position of first character immediately after the node. func (x *Ellipsis) End() token.Pos { if x.Elt != nil { return x.Elt.End() } return x.Ellipsis + 3 // len("...") } // End returns position of first character immediately after the node. func (x *FuncLit) End() token.Pos { return x.Body.End() } // End returns position of first character immediately after the node. func (x *CompositeLit) End() token.Pos { return x.Rbrace + 1 } // End returns position of first character immediately after the node. func (x *ParenExpr) End() token.Pos { return x.Rparen + 1 } // End returns position of first character immediately after the node. func (x *SelectorExpr) End() token.Pos { return x.Sel.End() } // End returns position of first character immediately after the node. func (x *IndexExpr) End() token.Pos { return x.Rbrack + 1 } // End returns position of first character immediately after the node. func (x *IndexListExpr) End() token.Pos { return x.Rbrack + 1 } // End returns position of first character immediately after the node. func (x *SliceExpr) End() token.Pos { return x.Rbrack + 1 } // End returns position of first character immediately after the node. func (x *TypeAssertExpr) End() token.Pos { return x.Rparen + 1 } // End returns position of first character immediately after the node. func (x *StarExpr) End() token.Pos { return x.X.End() } // End returns position of first character immediately after the node. func (x *UnaryExpr) End() token.Pos { return x.X.End() } // End returns position of first character immediately after the node. func (x *BinaryExpr) End() token.Pos { return x.Y.End() } // End returns position of first character immediately after the node. func (x *KeyValueExpr) End() token.Pos { return x.Value.End() } // End returns position of first character immediately after the node. func (x *ArrayType) End() token.Pos { return x.Elt.End() } // End returns position of first character immediately after the node. func (x *StructType) End() token.Pos { return x.Fields.End() } // End returns position of first character immediately after the node. func (x *FuncType) End() token.Pos { if x.Results != nil { return x.Results.End() } return x.Params.End() } // End returns position of first character immediately after the node. func (x *InterfaceType) End() token.Pos { return x.Methods.End() } // End returns position of first character immediately after the node. func (x *MapType) End() token.Pos { return x.Value.End() } // End returns position of first character immediately after the node. func (x *ChanType) End() token.Pos { return x.Value.End() } // End returns position of first character immediately after the node. func (x *TupleType) End() token.Pos { return x.Rparen + 1 } // exprNode() ensures that only expression/type nodes can be // assigned to an Expr. func (*BadExpr) exprNode() {} func (*Ident) exprNode() {} func (*Ellipsis) exprNode() {} func (*FuncLit) exprNode() {} func (*CompositeLit) exprNode() {} func (*ParenExpr) exprNode() {} func (*SelectorExpr) exprNode() {} func (*IndexExpr) exprNode() {} func (*IndexListExpr) exprNode() {} func (*SliceExpr) exprNode() {} func (*TypeAssertExpr) exprNode() {} func (*StarExpr) exprNode() {} func (*UnaryExpr) exprNode() {} func (*BinaryExpr) exprNode() {} func (*KeyValueExpr) exprNode() {} func (*ArrayType) exprNode() {} func (*StructType) exprNode() {} func (*FuncType) exprNode() {} func (*InterfaceType) exprNode() {} func (*MapType) exprNode() {} func (*ChanType) exprNode() {} func (*TupleType) exprNode() {} // ---------------------------------------------------------------------------- // Convenience functions for Idents // NewIdent creates a new Ident without position. // Useful for ASTs generated by code other than the XGo parser. func NewIdent(name string) *Ident { return &Ident{token.NoPos, name, nil} } // NewIdentEx creates a new Ident with position and with the given kind. func NewIdentEx(pos token.Pos, name string, kind ObjKind) *Ident { return &Ident{pos, name, NewObj(kind, name)} } // IsExported reports whether name starts with an upper-case letter. func IsExported(name string) bool { return token.IsExported(name) } // IsExported reports whether id starts with an upper-case letter. func (x *Ident) IsExported() bool { return token.IsExported(x.Name) } func (x *Ident) String() string { if x != nil { return x.Name } return "" } // ---------------------------------------------------------------------------- // Statements // A statement is represented by a tree consisting of one // or more of the following concrete statement nodes. type ( // A BadStmt node is a placeholder for statements containing // syntax errors for which no correct statement nodes can be // created. // BadStmt struct { From, To token.Pos // position range of bad statement } // A DeclStmt node represents a declaration in a statement list. DeclStmt struct { Decl Decl // *GenDecl with CONST, TYPE, or VAR token } // An EmptyStmt node represents an empty statement. // The "position" of the empty statement is the position // of the immediately following (explicit or implicit) semicolon. // EmptyStmt struct { Semicolon token.Pos // position of following ";" Implicit bool // if set, ";" was omitted in the source } // A LabeledStmt node represents a labeled statement. LabeledStmt struct { Label *Ident Colon token.Pos // position of ":" Stmt Stmt } // An ExprStmt node represents a (stand-alone) expression // in a statement list. // ExprStmt struct { X Expr // expression } // An IncDecStmt node represents an increment or decrement statement. IncDecStmt struct { X Expr TokPos token.Pos // position of Tok Tok token.Token // INC or DEC } // An AssignStmt node represents an assignment or // a short variable declaration. // AssignStmt struct { Lhs []Expr // left hand side expressions TokPos token.Pos // position of Tok Tok token.Token // assignment token, DEFINE Rhs []Expr // right hand side expressions } // A GoStmt node represents a go statement. GoStmt struct { Go token.Pos // position of "go" keyword Call *CallExpr } // A DeferStmt node represents a defer statement. DeferStmt struct { Defer token.Pos // position of "defer" keyword Call *CallExpr } // A ReturnStmt node represents a return statement. ReturnStmt struct { Return token.Pos // position of "return" keyword Results []Expr // result expressions; or nil } // A BranchStmt node represents a break, continue, goto, // or fallthrough statement. // BranchStmt struct { TokPos token.Pos // position of Tok Tok token.Token // keyword token (BREAK, CONTINUE, GOTO, FALLTHROUGH) Label *Ident // label name; or nil } // A BlockStmt node represents a braced statement list. BlockStmt struct { Lbrace token.Pos // position of "{" List []Stmt Rbrace token.Pos // position of "}", if any (may be absent due to syntax error) } // An IfStmt node represents an if statement. IfStmt struct { If token.Pos // position of "if" keyword Init Stmt // initialization statement; or nil Cond Expr // condition Body *BlockStmt Else Stmt // else branch; or nil } // A CaseClause represents a case of an expression or type switch statement. CaseClause struct { Case token.Pos // position of "case" or "default" keyword List []Expr // list of expressions or types; nil means default case Colon token.Pos // position of ":" Body []Stmt // statement list; or nil } // A SwitchStmt node represents an expression switch statement. SwitchStmt struct { Switch token.Pos // position of "switch" keyword Init Stmt // initialization statement; or nil Tag Expr // tag expression; or nil Body *BlockStmt // CaseClauses only } // A TypeSwitchStmt node represents a type switch statement. TypeSwitchStmt struct { Switch token.Pos // position of "switch" keyword Init Stmt // initialization statement; or nil Assign Stmt // x := y.(type) or y.(type) Body *BlockStmt // CaseClauses only } // A CommClause node represents a case of a select statement. CommClause struct { Case token.Pos // position of "case" or "default" keyword Comm Stmt // send or receive statement; nil means default case Colon token.Pos // position of ":" Body []Stmt // statement list; or nil } // A SelectStmt node represents a select statement. SelectStmt struct { Select token.Pos // position of "select" keyword Body *BlockStmt // CommClauses only } // A ForStmt represents a `for init; cond; post { ... }` statement. ForStmt struct { For token.Pos // position of "for" keyword Init Stmt // initialization statement; or nil Cond Expr // condition; or nil Post Stmt // post iteration statement; or nil Body *BlockStmt } // A RangeStmt represents a for statement with a range clause. RangeStmt struct { For token.Pos // position of "for" keyword Key, Value Expr // Key, Value may be nil TokPos token.Pos // position of Tok; invalid if Key == nil Tok token.Token // ILLEGAL if Key == nil, ASSIGN, DEFINE X Expr // value to range over Body *BlockStmt NoRangeOp bool } ) // Pos and End implementations for statement nodes. // Pos returns position of first character belonging to the node. func (s *BadStmt) Pos() token.Pos { return s.From } // Pos returns position of first character belonging to the node. func (s *DeclStmt) Pos() token.Pos { return s.Decl.Pos() } // Pos returns position of first character belonging to the node. func (s *EmptyStmt) Pos() token.Pos { return s.Semicolon } // Pos returns position of first character belonging to the node. func (s *LabeledStmt) Pos() token.Pos { return s.Label.Pos() } // Pos returns position of first character belonging to the node. func (s *ExprStmt) Pos() token.Pos { return s.X.Pos() } // Pos returns position of first character belonging to the node. func (s *SendStmt) Pos() token.Pos { return s.Chan.Pos() } // Pos returns position of first character belonging to the node. func (s *IncDecStmt) Pos() token.Pos { return s.X.Pos() } // Pos returns position of first character belonging to the node. func (s *AssignStmt) Pos() token.Pos { return s.Lhs[0].Pos() } // Pos returns position of first character belonging to the node. func (s *GoStmt) Pos() token.Pos { return s.Go } // Pos returns position of first character belonging to the node. func (s *DeferStmt) Pos() token.Pos { return s.Defer } // Pos returns position of first character belonging to the node. func (s *ReturnStmt) Pos() token.Pos { return s.Return } // Pos returns position of first character belonging to the node. func (s *BranchStmt) Pos() token.Pos { return s.TokPos } // Pos returns position of first character belonging to the node. func (s *BlockStmt) Pos() token.Pos { return s.Lbrace } // Pos returns position of first character belonging to the node. func (s *IfStmt) Pos() token.Pos { return s.If } // Pos returns position of first character belonging to the node. func (s *CaseClause) Pos() token.Pos { return s.Case } // Pos returns position of first character belonging to the node. func (s *SwitchStmt) Pos() token.Pos { return s.Switch } // Pos returns position of first character belonging to the node. func (s *TypeSwitchStmt) Pos() token.Pos { return s.Switch } // Pos returns position of first character belonging to the node. func (s *CommClause) Pos() token.Pos { return s.Case } // Pos returns position of first character belonging to the node. func (s *SelectStmt) Pos() token.Pos { return s.Select } // Pos returns position of first character belonging to the node. func (s *ForStmt) Pos() token.Pos { return s.For } // Pos returns position of first character belonging to the node. func (s *RangeStmt) Pos() token.Pos { return s.For } // End returns position of first character immediately after the node. func (s *BadStmt) End() token.Pos { return s.To } // End returns position of first character immediately after the node. func (s *DeclStmt) End() token.Pos { return s.Decl.End() } // End returns position of first character immediately after the node. func (s *EmptyStmt) End() token.Pos { if s.Implicit { return s.Semicolon } return s.Semicolon + 1 /* len(";") */ } // End returns position of first character immediately after the node. func (s *LabeledStmt) End() token.Pos { return s.Stmt.End() } // End returns position of first character immediately after the node. func (s *ExprStmt) End() token.Pos { return s.X.End() } // End returns position of first character immediately after the node. func (s *IncDecStmt) End() token.Pos { return s.TokPos + 2 /* len("++") */ } // End returns position of first character immediately after the node. func (s *AssignStmt) End() token.Pos { return s.Rhs[len(s.Rhs)-1].End() } // End returns position of first character immediately after the node. func (s *GoStmt) End() token.Pos { return s.Call.End() } // End returns position of first character immediately after the node. func (s *DeferStmt) End() token.Pos { return s.Call.End() } // End returns position of first character immediately after the node. func (s *ReturnStmt) End() token.Pos { if n := len(s.Results); n > 0 { return s.Results[n-1].End() } return s.Return + 6 // len("return") } // End returns position of first character immediately after the node. func (s *BranchStmt) End() token.Pos { if s.Label != nil { return s.Label.End() } return token.Pos(int(s.TokPos) + len(s.Tok.String())) } // End returns position of first character immediately after the node. func (s *BlockStmt) End() token.Pos { if s.Rbrace.IsValid() { return s.Rbrace + 1 } if n := len(s.List); n > 0 { return s.List[n-1].End() } return s.Lbrace + 1 } // End returns position of first character immediately after the node. func (s *IfStmt) End() token.Pos { if s.Else != nil { return s.Else.End() } return s.Body.End() } // End returns position of first character immediately after the node. func (s *CaseClause) End() token.Pos { if n := len(s.Body); n > 0 { return s.Body[n-1].End() } return s.Colon + 1 } // End returns position of first character immediately after the node. func (s *SwitchStmt) End() token.Pos { return s.Body.End() } // End returns position of first character immediately after the node. func (s *TypeSwitchStmt) End() token.Pos { return s.Body.End() } // End returns position of first character immediately after the node. func (s *CommClause) End() token.Pos { if n := len(s.Body); n > 0 { return s.Body[n-1].End() } return s.Colon + 1 } // End returns position of first character immediately after the node. func (s *SelectStmt) End() token.Pos { return s.Body.End() } // End returns position of first character immediately after the node. func (s *ForStmt) End() token.Pos { return s.Body.End() } // End returns position of first character immediately after the node. func (s *RangeStmt) End() token.Pos { return s.Body.End() } // stmtNode() ensures that only statement nodes can be // assigned to a Stmt. func (*BadStmt) stmtNode() {} func (*DeclStmt) stmtNode() {} func (*EmptyStmt) stmtNode() {} func (*LabeledStmt) stmtNode() {} func (*ExprStmt) stmtNode() {} func (*SendStmt) stmtNode() {} func (*IncDecStmt) stmtNode() {} func (*AssignStmt) stmtNode() {} func (*GoStmt) stmtNode() {} func (*DeferStmt) stmtNode() {} func (*ReturnStmt) stmtNode() {} func (*BranchStmt) stmtNode() {} func (*BlockStmt) stmtNode() {} func (*IfStmt) stmtNode() {} func (*CaseClause) stmtNode() {} func (*SwitchStmt) stmtNode() {} func (*TypeSwitchStmt) stmtNode() {} func (*CommClause) stmtNode() {} func (*SelectStmt) stmtNode() {} func (*ForStmt) stmtNode() {} func (*RangeStmt) stmtNode() {} // ---------------------------------------------------------------------------- // Declarations // A Spec node represents a single (non-parenthesized) import, // constant, type, or variable declaration. type ( // The Spec type stands for any of *ImportSpec, *ValueSpec, and *TypeSpec. Spec interface { Node specNode() } // An ImportSpec node represents a single package import. ImportSpec struct { Doc *CommentGroup // associated documentation; or nil Name *Ident // local package name (including "."); or nil Path *BasicLit // import path Comment *CommentGroup // line comments; or nil EndPos token.Pos // end of spec (overrides Path.Pos if nonzero) } // A ValueSpec node represents a constant or variable declaration // (ConstSpec or VarSpec production). // ValueSpec struct { Doc *CommentGroup // associated documentation; or nil Names []*Ident // value names (len(Names) > 0) Type Expr // value type; or nil Tag *BasicLit // classfile field tag; or nil Values []Expr // initial values; or nil Comment *CommentGroup // line comments; or nil } // A TypeSpec node represents a type declaration (TypeSpec production). TypeSpec struct { Doc *CommentGroup // associated documentation; or nil Name *Ident // type name TypeParams *FieldList // type parameters; or nil Assign token.Pos // position of '=', if any Type Expr // *Ident, *ParenExpr, *SelectorExpr, *StarExpr, or any of the *XxxTypes Comment *CommentGroup // line comments; or nil } ) // Pos and End implementations for spec nodes. // Pos returns position of first character belonging to the node. func (s *ImportSpec) Pos() token.Pos { if s.Name != nil { return s.Name.Pos() } return s.Path.Pos() } // Pos returns position of first character belonging to the node. func (s *ValueSpec) Pos() token.Pos { if len(s.Names) == 0 { return s.Type.Pos() } return s.Names[0].Pos() } // Pos returns position of first character belonging to the node. func (s *TypeSpec) Pos() token.Pos { return s.Name.Pos() } // End returns position of first character immediately after the node. func (s *ImportSpec) End() token.Pos { if s.EndPos != 0 { return s.EndPos } return s.Path.End() } // End returns position of first character immediately after the node. func (s *ValueSpec) End() token.Pos { if n := len(s.Values); n > 0 { return s.Values[n-1].End() } if s.Type != nil { return s.Type.End() } return s.Names[len(s.Names)-1].End() } // End returns position of first character immediately after the node. func (s *TypeSpec) End() token.Pos { return s.Type.End() } // specNode() ensures that only spec nodes can be // assigned to a Spec. func (*ImportSpec) specNode() {} func (*ValueSpec) specNode() {} func (*TypeSpec) specNode() {} // A declaration is represented by one of the following declaration nodes. type ( // A BadDecl node is a placeholder for declarations containing // syntax errors for which no correct declaration nodes can be // created. // BadDecl struct { From, To token.Pos // position range of bad declaration } // A GenDecl node (generic declaration node) represents an import, // constant, type or variable declaration. A valid Lparen position // (Lparen.IsValid()) indicates a parenthesized declaration. // // Relationship between Tok value and Specs element type: // // token.IMPORT *ImportSpec // token.CONST *ValueSpec // token.TYPE *TypeSpec // token.VAR *ValueSpec // GenDecl struct { Doc *CommentGroup // associated documentation; or nil TokPos token.Pos // position of Tok Tok token.Token // IMPORT, CONST, TYPE, VAR Lparen token.Pos // position of '(', if any Specs []Spec Rparen token.Pos // position of ')', if any } // A FuncDecl node represents a function declaration. FuncDecl struct { Doc *CommentGroup // associated documentation; or nil Recv *FieldList // receiver (methods); or nil (functions) Name *Ident // function/method name Type *FuncType // function signature: parameters, results, and position of "func" keyword Body *BlockStmt // function body; or nil for external (non-Go) function Operator bool // is operator or not Shadow bool // is a shadow entry IsClass bool // recv set by class Static bool // recv is static (class method) } ) // Pos and End implementations for declaration nodes. // Pos returns position of first character belonging to the node. func (d *BadDecl) Pos() token.Pos { return d.From } // Pos returns position of first character belonging to the node. func (d *GenDecl) Pos() token.Pos { return d.TokPos } // Pos returns position of first character belonging to the node. func (d *FuncDecl) Pos() token.Pos { return d.Type.Pos() } // End returns position of first character immediately after the node. func (d *BadDecl) End() token.Pos { return d.To } // End returns position of first character immediately after the node. func (d *GenDecl) End() token.Pos { if d.Rparen.IsValid() { return d.Rparen + 1 } return d.Specs[0].End() } // End returns position of first character immediately after the node. func (d *FuncDecl) End() token.Pos { if d.Body != nil { return d.Body.End() } return d.Type.End() } // declNode() ensures that only declaration nodes can be // assigned to a Decl. func (*BadDecl) declNode() {} func (*GenDecl) declNode() {} func (*FuncDecl) declNode() {} // ---------------------------------------------------------------------------- // A Package node represents a set of source files // collectively building an XGo package. type Package struct { Name string // package name Imports map[string]*Object // map of package id -> package object Files map[string]*File // XGo source files by filename GoFiles map[string]*ast.File // Go source files by filename } // Pos returns position of first character belonging to the node. func (p *Package) Pos() token.Pos { return token.NoPos } // End returns position of first character immediately after the node. func (p *Package) End() token.Pos { return token.NoPos } // ---------------------------------------------------------------------------- ================================================ FILE: ast/ast_xgo.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ast import ( "github.com/goplus/xgo/token" ) // ----------------------------------------------------------------------------- // OverloadFuncDecl node represents an overload function declaration: // // `func name = (overloadFuncs)` // `func (T).nameOrOp = (overloadFuncs)` // // here overloadFunc represents // // `func(params) results {...}` // `funcName` // `(*T).methodName` type OverloadFuncDecl struct { Doc *CommentGroup // associated documentation; or nil Func token.Pos // position of "func" keyword Recv *FieldList // receiver (methods); or nil (functions) Name *Ident // function/method name Assign token.Pos // position of token "=" Lparen token.Pos // position of "(" Funcs []Expr // overload functions. here `Expr` can be *FuncLit, *Ident or *SelectorExpr Rparen token.Pos // position of ")" Operator bool // is operator or not IsClass bool // recv set by class } // Pos - position of first character belonging to the node. func (p *OverloadFuncDecl) Pos() token.Pos { return p.Func } // End - position of first character immediately after the node. func (p *OverloadFuncDecl) End() token.Pos { return p.Rparen + 1 } func (*OverloadFuncDecl) declNode() {} // ----------------------------------------------------------------------------- // A CallExpr node represents an expression followed by an argument list. // The argument list may include positional arguments (Args) and/or // keyword arguments (Kwargs). type CallExpr struct { Fun Expr // function expression Lparen token.Pos // position of "(" Args []Expr // positional arguments; or nil Ellipsis token.Pos // position of "..." (token.NoPos if there is no "...") Kwargs []*KwargExpr // keyword arguments; or nil Rparen token.Pos // position of ")" NoParenEnd token.Pos } // Pos returns position of first character belonging to the node. func (x *CallExpr) Pos() token.Pos { return x.Fun.Pos() } // End returns position of first character immediately after the node. func (x *CallExpr) End() token.Pos { if x.NoParenEnd != token.NoPos { return x.NoParenEnd } return x.Rparen + 1 } func (*CallExpr) exprNode() {} // IsCommand returns if a CallExpr is a command style CallExpr or not. func (x *CallExpr) IsCommand() bool { return x.NoParenEnd != token.NoPos } // A KwargExpr node represents a keyword argument expression. type KwargExpr struct { Name *Ident // argument name Value Expr // argument value } func (p *KwargExpr) Pos() token.Pos { return p.Name.Pos() } func (p *KwargExpr) End() token.Pos { return p.Value.End() } func (p *KwargExpr) exprNode() {} // ----------------------------------------------------------------------------- // A DomainTextLit node represents a domain-specific text literal. // https://github.com/goplus/xgo/issues/2143 // // domainTag`...` // domainTag`> arg1, arg2, ... // ... // ` type DomainTextLit struct { Domain *Ident // domain name ValuePos token.Pos // literal position Value string // literal string; e.g. `\m\n\o` Extra any // *DomainTextLitEx or *xgo/tpl/ast.File, optional } // DomainTextLitEx represents extra information for domain text literal. type DomainTextLitEx struct { Args []Expr // domain text arguments; or nil RawPos token.Pos // position of the first character of the raw string Raw string // raw string without backquote } // Pos returns position of first character belonging to the node. func (x *DomainTextLit) Pos() token.Pos { return x.Domain.NamePos } // End returns position of first character immediately after the node. func (x *DomainTextLit) End() token.Pos { return token.Pos(int(x.ValuePos) + len(x.Value)) } func (*DomainTextLit) exprNode() {} // ----------------------------------------------------------------------------- // A BasicLit node represents a literal of basic type. type BasicLit struct { ValuePos token.Pos // literal position Kind token.Token // token.INT, token.FLOAT, token.IMAG, token.RAT, token.CHAR, token.STRING, token.CSTRING Value string // literal string; e.g. 42, 0x7f, 3.14, 1e-9, 2.4i, 3r, 'a', '\x7f', "foo" or `\m\n\o` Extra *StringLitEx // optional (only available when Kind == token.STRING) } type StringLitEx struct { Parts []any // can be (val string) or (xval Expr) } // NextPartPos - position of first character of next part. // pos - position of this part (not including quote character). func NextPartPos(pos token.Pos, part any) (nextPos token.Pos) { switch v := part.(type) { case string: // normal string literal or end with "$$" return pos + token.Pos(len(v)) case Expr: return v.End() } panic("NextPartPos: unexpected parameters") } // Pos returns position of first character belonging to the node. func (x *BasicLit) Pos() token.Pos { return x.ValuePos } // End returns position of first character immediately after the node. func (x *BasicLit) End() token.Pos { return token.Pos(int(x.ValuePos) + len(x.Value)) } func (*BasicLit) exprNode() {} // ----------------------------------------------------------------------------- // A NumberUnitLit node represents a number with unit. type NumberUnitLit struct { ValuePos token.Pos // literal position Kind token.Token // token.INT or token.FLOAT Value string // literal string of the number; e.g. 42, 0x7f, 3.14, 1e-9 Unit string // unit string of the number; e.g. "px", "em", "rem" } func (*NumberUnitLit) exprNode() {} func (x *NumberUnitLit) Pos() token.Pos { return x.ValuePos } func (x *NumberUnitLit) End() token.Pos { return token.Pos(int(x.ValuePos) + len(x.Value) + len(x.Unit)) } // ----------------------------------------------------------------------------- // A EnvExpr node represents a ${name} expression. type EnvExpr struct { TokPos token.Pos // position of "$" Lbrace token.Pos // position of "{" Name *Ident // name Rbrace token.Pos // position of "}" } // Pos - position of first character belonging to the node. func (p *EnvExpr) Pos() token.Pos { return p.TokPos } // End - position of first character immediately after the node. func (p *EnvExpr) End() token.Pos { if p.Rbrace != token.NoPos { return p.Rbrace } return p.Name.End() } // HasBrace checks is this EnvExpr ${name} or $name. func (p *EnvExpr) HasBrace() bool { return p.Rbrace != token.NoPos } func (*EnvExpr) exprNode() {} // ----------------------------------------------------------------------------- // AnySelectorExpr represents `X.**.Sel` expression, which selects any field // named Sel in the nested object X. // Sel may not be a simple identifier. For example: // - x.**.field // - x.**."field-name" // - x.**.* type AnySelectorExpr struct { X Expr // expression TokPos token.Pos // position of "**" Sel *Ident // field selector (it may not be a simple identifier) } // Pos - position of first character belonging to the node. func (p *AnySelectorExpr) Pos() token.Pos { return p.X.Pos() } // End - position of first character immediately after the node. func (p *AnySelectorExpr) End() token.Pos { return p.Sel.End() } func (*AnySelectorExpr) exprNode() {} // ----------------------------------------------------------------------------- // A CondExpr node represents a conditional expression: `expr @ cond`. // - ns@(condExpr) // - ns@fn(args) // - ns@"elem-name" (Cond will be a *Ident with name "elem-name", not a *BasicLit) // - ns@name type CondExpr struct { X Expr // expression OpPos token.Pos // position of "@" Cond Expr // condition expression (can be *CallExpr, *ParenExpr or *Ident) } // Pos - position of first character belonging to the node. func (p *CondExpr) Pos() token.Pos { return p.X.Pos() } // End - position of first character immediately after the node. func (p *CondExpr) End() token.Pos { return p.Cond.End() } func (*CondExpr) exprNode() {} // ----------------------------------------------------------------------------- // A SliceLit node represents a slice literal. type SliceLit struct { Lbrack token.Pos // position of "[" Elts []Expr // list of slice elements; or nil Rbrack token.Pos // position of "]" Incomplete bool // true if (source) expressions are missing in the Elts list } // Pos - position of first character belonging to the node. func (p *SliceLit) Pos() token.Pos { return p.Lbrack } // End - position of first character immediately after the node. func (p *SliceLit) End() token.Pos { return p.Rbrack + 1 } func (*SliceLit) exprNode() {} // ----------------------------------------------------------------------------- // A TupleLit node represents a tuple literal. type TupleLit struct { Lparen token.Pos // position of "(" Elts []Expr // list of tuple elements; or nil Ellipsis token.Pos // position of "..." (token.NoPos if there is no "...") Rparen token.Pos // position of ")" } // Pos - position of first character belonging to the node. func (p *TupleLit) Pos() token.Pos { return p.Lparen } // End - position of first character immediately after the node. func (p *TupleLit) End() token.Pos { return p.Rparen + 1 } func (*TupleLit) exprNode() {} // ----------------------------------------------------------------------------- // A MatrixLit node represents a matrix literal. type MatrixLit struct { Lbrack token.Pos // position of "[" Elts [][]Expr // list of matrix elements Rbrack token.Pos // position of "]" Incomplete bool // true if (source) expressions are missing in the Elts list } // Pos - position of first character belonging to the node. func (p *MatrixLit) Pos() token.Pos { return p.Lbrack } // End - position of first character immediately after the node. func (p *MatrixLit) End() token.Pos { return p.Rbrack + 1 } func (*MatrixLit) exprNode() {} // ----------------------------------------------------------------------------- // A ElemEllipsis node represents a matrix row elements. type ElemEllipsis struct { Elt Expr // ellipsis element Ellipsis token.Pos // position of "..." } // Pos - position of first character belonging to the node. func (p *ElemEllipsis) Pos() token.Pos { return p.Elt.Pos() } // End - position of first character immediately after the node. func (p *ElemEllipsis) End() token.Pos { return p.Ellipsis + 3 } func (*ElemEllipsis) exprNode() {} // ----------------------------------------------------------------------------- // ErrWrapExpr represents `expr!`, `expr?` or `expr?:defaultValue`. type ErrWrapExpr struct { X Expr Tok token.Token // ! or ? TokPos token.Pos Default Expr // can be nil } // Pos - position of first character belonging to the node. func (p *ErrWrapExpr) Pos() token.Pos { return p.X.Pos() } // End - position of first character immediately after the node. func (p *ErrWrapExpr) End() token.Pos { if p.Default != nil { return p.Default.End() } return p.TokPos + 1 } func (*ErrWrapExpr) exprNode() {} // ----------------------------------------------------------------------------- // LambdaExpr represents one of the following expressions: // // `(x, y, ...) => exprOrExprTuple` // `x => exprOrExprTuple` // `=> exprOrExprTuple` // // here exprOrExprTuple represents // // `expr` // `(expr1, expr2, ...)` type LambdaExpr struct { First token.Pos Lhs []*Ident Rarrow token.Pos Rhs []Expr Last token.Pos LhsHasParen bool RhsHasParen bool } // LambdaExpr2 represents one of the following expressions: // // `(x, y, ...) => { ... }` // `x => { ... }` // `=> { ... }` type LambdaExpr2 struct { First token.Pos Lhs []*Ident Rarrow token.Pos Body *BlockStmt LhsHasParen bool } // Pos - position of first character belonging to the node. func (p *LambdaExpr) Pos() token.Pos { return p.First } // End - position of first character immediately after the node. func (p *LambdaExpr) End() token.Pos { return p.Last } // Pos - position of first character belonging to the node. func (p *LambdaExpr2) Pos() token.Pos { return p.First } // End - position of first character immediately after the node. func (p *LambdaExpr2) End() token.Pos { if p.Body == nil { return p.Rarrow + 2 } return p.Body.End() } func (*LambdaExpr) exprNode() {} func (*LambdaExpr2) exprNode() {} // ----------------------------------------------------------------------------- // A RangeExpr node represents a range expression. type RangeExpr struct { First Expr // start of composite elements; or nil To token.Pos // position of ":" Last Expr // end of composite elements Colon2 token.Pos // position of ":" or token.NoPos Expr3 Expr // step (or max) of composite elements; or nil } // Pos - position of first character belonging to the node. func (p *RangeExpr) Pos() token.Pos { if p.First != nil { return p.First.Pos() } return p.To } // End - position of first character immediately after the node. func (p *RangeExpr) End() token.Pos { if p.Expr3 != nil { return p.Expr3.End() } if p.Colon2 != token.NoPos { return p.Colon2 + 1 } if p.Last != nil { return p.Last.End() } return p.To + 1 } func (*RangeExpr) exprNode() {} // ----------------------------------------------------------------------------- // ForPhrase represents `for k, v in container if init; cond` phrase. type ForPhrase struct { For token.Pos // position of "for" keyword Key, Value *Ident // Key may be nil TokPos token.Pos // position of "in" operator X Expr // value to range over IfPos token.Pos // position of if or comma; or NoPos Init Stmt // initialization statement; or nil Cond Expr // value filter, can be nil } // Pos returns position of first character belonging to the node. func (p *ForPhrase) Pos() token.Pos { return p.For } // End returns position of first character immediately after the node. func (p *ForPhrase) End() token.Pos { if p.Cond != nil { return p.Cond.End() } return p.X.End() } func (p *ForPhrase) exprNode() {} // ComprehensionExpr represents one of the following expressions: // // `[vexpr for k1, v1 in container1, cond1 ...]` or // `{vexpr for k1, v1 in container1, cond1 ...}` or // `{kexpr: vexpr for k1, v1 in container1, cond1 ...}` or // `{for k1, v1 in container1, cond1 ...}` type ComprehensionExpr struct { Lpos token.Pos // position of "[" or "{" Tok token.Token // token.LBRACK '[' or token.LBRACE '{' Elt Expr // *KeyValueExpr or Expr or nil Fors []*ForPhrase Rpos token.Pos // position of "]" or "}" } // Pos - position of first character belonging to the node. func (p *ComprehensionExpr) Pos() token.Pos { return p.Lpos } // End - position of first character immediately after the node. func (p *ComprehensionExpr) End() token.Pos { return p.Rpos + 1 } func (*ComprehensionExpr) exprNode() {} // ----------------------------------------------------------------------------- // A ForPhraseStmt represents a for statement with a for..in clause. type ForPhraseStmt struct { *ForPhrase Body *BlockStmt } // Pos - position of first character belonging to the node. func (p *ForPhraseStmt) Pos() token.Pos { return p.For } // End - position of first character immediately after the node. func (p *ForPhraseStmt) End() token.Pos { return p.Body.End() } func (*ForPhraseStmt) stmtNode() {} // ----------------------------------------------------------------------------- // A SendStmt node represents a send statement. type SendStmt struct { Chan Expr Arrow token.Pos // position of "<-" Values []Expr // len(Values) must > 0 Ellipsis token.Pos // position of "..." } // End returns position of first character immediately after the node. func (s *SendStmt) End() token.Pos { if s.Ellipsis != token.NoPos { return s.Ellipsis + 3 } vals := s.Values return vals[len(vals)-1].End() } // ----------------------------------------------------------------------------- // A File node represents an XGo source file. // // The Comments list contains all comments in the source file in order of // appearance, including the comments that are pointed to from other nodes // via Doc and Comment fields. // // For correct printing of source code containing comments (using packages // go/format and go/printer), special care must be taken to update comments // when a File's syntax tree is modified: For printing, comments are interspersed // between tokens based on their position. If syntax tree nodes are // removed or moved, relevant comments in their vicinity must also be removed // (from the File.Comments list) or moved accordingly (by updating their // positions). A CommentMap may be used to facilitate some of these operations. // // Whether and how a comment is associated with a node depends on the // interpretation of the syntax tree by the manipulating program: Except for Doc // and Comment comments directly associated with nodes, the remaining comments // are "free-floating" (see also issues #18593, #20744). type File struct { Doc *CommentGroup // associated documentation; or nil Package token.Pos // position of "package" keyword; or NoPos Name *Ident // package name Decls []Decl // top-level declarations; or nil Imports []*ImportSpec // imports in this file Comments []*CommentGroup // list of all comments in the source file Code []byte ShadowEntry *FuncDecl // indicate the module entry point. NoPkgDecl bool // no `package xxx` declaration IsClass bool // is a classfile (including normal .gox file) IsProj bool // is a project classfile IsNormalGox bool // is a normal .gox file } // There is no entrypoint func to indicate the module entry point. func (f *File) HasShadowEntry() bool { return f.ShadowEntry != nil } // HasPkgDecl checks if `package xxx` exists or not. func (f *File) HasPkgDecl() bool { return f.Package != token.NoPos } // ClassFieldsDecl returns the class fields declaration. func (f *File) ClassFieldsDecl() *GenDecl { if f.IsClass { for _, decl := range f.Decls { if g, ok := decl.(*GenDecl); ok { if g.Tok == token.VAR { return g } continue } break } } return nil } // Pos returns position of first character belonging to the node. func (f *File) Pos() token.Pos { if f.Package != token.NoPos { return f.Package } // if no package clause, name records the position of the first token in the file return f.Name.NamePos } // End returns position of first character immediately after the node. func (f *File) End() token.Pos { if f.ShadowEntry != nil { // has shadow entry return f.ShadowEntry.End() } for n := len(f.Decls) - 1; n >= 0; n-- { d := f.Decls[n] if fn, ok := d.(*FuncDecl); ok && fn.Shadow { // skip shadow functions like Classfname (see cl.astFnClassfname) continue } return d.End() } if f.Package != token.NoPos { // has package clause return f.Name.End() } return f.Name.Pos() } // ----------------------------------------------------------------------------- ================================================ FILE: ast/commentmap.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ast import ( "bytes" "fmt" "sort" "github.com/goplus/xgo/token" ) type byPos []*CommentGroup func (a byPos) Len() int { return len(a) } func (a byPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() } func (a byPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] } // sortComments sorts the list of comment groups in source order. func sortComments(list []*CommentGroup) { // TODO(gri): Does it make sense to check for sorted-ness // first (because we know that sorted-ness is // very likely)? if orderedList := byPos(list); !sort.IsSorted(orderedList) { sort.Sort(orderedList) } } // A CommentMap maps an AST node to a list of comment groups // associated with it. See NewCommentMap for a description of // the association. type CommentMap map[Node][]*CommentGroup func (cmap CommentMap) addComment(n Node, c *CommentGroup) { list := cmap[n] if len(list) == 0 { list = []*CommentGroup{c} } else { list = append(list, c) } cmap[n] = list } // nodeList returns the list of nodes of the AST n in source order. func nodeList(n Node) []Node { var list []Node Inspect(n, func(n Node) bool { // don't collect comments switch n.(type) { case nil, *CommentGroup, *Comment: return false } list = append(list, n) return true }) // Note: The current implementation assumes that Inspect traverses the // AST in depth-first and thus _source_ order. If AST traversal // does not follow source order, the sorting call below will be // required. // sort.Sort(byInterval(list)) return list } // A commentListReader helps iterating through a list of comment groups. type commentListReader struct { fset *token.FileSet list []*CommentGroup index int comment *CommentGroup // comment group at current index pos, end token.Position // source interval of comment group at current index } func (r *commentListReader) eol() bool { return r.index >= len(r.list) } func (r *commentListReader) next() { if !r.eol() { r.comment = r.list[r.index] r.pos = r.fset.Position(r.comment.Pos()) r.end = r.fset.Position(r.comment.End()) r.index++ } } // A nodeStack keeps track of nested nodes. // A node lower on the stack lexically contains the nodes higher on the stack. type nodeStack []Node // push pops all nodes that appear lexically before n // and then pushes n on the stack. func (s *nodeStack) push(n Node) { s.pop(n.Pos()) *s = append((*s), n) } // pop pops all nodes that appear lexically before pos // (i.e., whose lexical extent has ended before or at pos). // It returns the last node popped. func (s *nodeStack) pop(pos token.Pos) (top Node) { i := len(*s) for i > 0 && (*s)[i-1].End() <= pos { top = (*s)[i-1] i-- } *s = (*s)[0:i] return top } // NewCommentMap creates a new comment map by associating comment groups // of the comments list with the nodes of the AST specified by node. // // A comment group g is associated with a node n if: // // - g starts on the same line as n ends // - g starts on the line immediately following n, and there is // at least one empty line after g and before the next node // - g starts before n and is not associated to the node before n // via the previous rules // // NewCommentMap tries to associate a comment group to the "largest" // node possible: For instance, if the comment is a line comment // trailing an assignment, the comment is associated with the entire // assignment rather than just the last operand in the assignment. func NewCommentMap(fset *token.FileSet, node Node, comments []*CommentGroup) CommentMap { if len(comments) == 0 { return nil // no comments to map } cmap := make(CommentMap) // set up comment reader r tmp := make([]*CommentGroup, len(comments)) copy(tmp, comments) // don't change incoming comments sortComments(tmp) r := commentListReader{fset: fset, list: tmp} // !r.eol() because len(comments) > 0 r.next() // create node list in lexical order nodes := nodeList(node) nodes = append(nodes, nil) // append sentinel // set up iteration variables var ( p Node // previous node pend token.Position // end of p pg Node // previous node group (enclosing nodes of "importance") pgend token.Position // end of pg stack nodeStack // stack of node groups ) for _, q := range nodes { var qpos token.Position if q != nil { qpos = fset.Position(q.Pos()) // current node position } else { // set fake sentinel position to infinity so that // all comments get processed before the sentinel const infinity = 1 << 30 qpos.Offset = infinity qpos.Line = infinity } // process comments before current node for r.end.Offset <= qpos.Offset { // determine recent node group if top := stack.pop(r.comment.Pos()); top != nil { pg = top pgend = fset.Position(pg.End()) } // Try to associate a comment first with a node group // (i.e., a node of "importance" such as a declaration); // if that fails, try to associate it with the most recent // node. // TODO(gri) try to simplify the logic below var assoc Node switch { case pg != nil && (pgend.Line == r.pos.Line || pgend.Line+1 == r.pos.Line && r.end.Line+1 < qpos.Line): // 1) comment starts on same line as previous node group ends, or // 2) comment starts on the line immediately after the // previous node group and there is an empty line before // the current node // => associate comment with previous node group assoc = pg case p != nil && (pend.Line == r.pos.Line || pend.Line+1 == r.pos.Line && r.end.Line+1 < qpos.Line || q == nil): // same rules apply as above for p rather than pg, // but also associate with p if we are at the end (q == nil) assoc = p default: // otherwise, associate comment with current node if q == nil { // we can only reach here if there was no p // which would imply that there were no nodes panic("internal error: no comments should be associated with sentinel") } assoc = q } cmap.addComment(assoc, r.comment) if r.eol() { return cmap } r.next() } // update previous node p = q pend = fset.Position(p.End()) // update previous node group if we see an "important" node switch q.(type) { case *File, *Field, Decl, Spec, Stmt: stack.push(q) } } return cmap } // Update replaces an old node in the comment map with the new node // and returns the new node. Comments that were associated with the // old node are associated with the new node. func (cmap CommentMap) Update(old, new Node) Node { if list := cmap[old]; len(list) > 0 { delete(cmap, old) cmap[new] = append(cmap[new], list...) } return new } // Filter returns a new comment map consisting of only those // entries of cmap for which a corresponding node exists in // the AST specified by node. func (cmap CommentMap) Filter(node Node) CommentMap { umap := make(CommentMap) Inspect(node, func(n Node) bool { if g := cmap[n]; len(g) > 0 { umap[n] = g } return true }) return umap } // Comments returns the list of comment groups in the comment map. // The result is sorted in source order. func (cmap CommentMap) Comments() []*CommentGroup { list := make([]*CommentGroup, 0, len(cmap)) for _, e := range cmap { list = append(list, e...) } sortComments(list) return list } func summary(list []*CommentGroup) string { const maxLen = 40 var buf bytes.Buffer // collect comments text loop: for _, group := range list { // Note: CommentGroup.Text() does too much work for what we // need and would only replace this innermost loop. // Just do it explicitly. for _, comment := range group.List { if buf.Len() >= maxLen { break loop } buf.WriteString(comment.Text) } } // truncate if too long if buf.Len() > maxLen { buf.Truncate(maxLen - 3) buf.WriteString("...") } // replace any invisibles with blanks bytes := buf.Bytes() for i, b := range bytes { switch b { case '\t', '\n', '\r': bytes[i] = ' ' } } return string(bytes) } func (cmap CommentMap) String() string { var buf bytes.Buffer fmt.Fprintln(&buf, "CommentMap {") for node, comment := range cmap { // print name of identifiers; print node type for other nodes var s string if ident, ok := node.(*Ident); ok { s = ident.Name } else { s = fmt.Sprintf("%T", node) } fmt.Fprintf(&buf, "\t%p %20s: %s\n", node, s, summary(comment)) } fmt.Fprintln(&buf, "}") return buf.String() } ================================================ FILE: ast/filter.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ast import ( "sort" "github.com/goplus/xgo/token" ) // ---------------------------------------------------------------------------- // Export filtering // exportFilter is a special filter function to extract exported nodes. func exportFilter(name string) bool { return IsExported(name) } // FileExports trims the AST for a Go source file in place such that // only exported nodes remain: all top-level identifiers which are not exported // and their associated information (such as type, initial value, or function // body) are removed. Non-exported fields and methods of exported types are // stripped. The File.Comments list is not changed. // // FileExports reports whether there are exported declarations. func FileExports(src *File) bool { return filterFile(src, exportFilter, true) } // PackageExports trims the AST for a Go package in place such that // only exported nodes remain. The pkg.Files list is not changed, so that // file names and top-level package comments don't get lost. // // PackageExports reports whether there are exported declarations; // it returns false otherwise. func PackageExports(pkg *Package) bool { return filterPackage(pkg, exportFilter, true) } // ---------------------------------------------------------------------------- // General filtering // Filter type type Filter func(string) bool func filterIdentList(list []*Ident, f Filter) []*Ident { j := 0 for _, x := range list { if f(x.Name) { list[j] = x j++ } } return list[0:j] } // fieldName assumes that x is the type of an anonymous field and // returns the corresponding field name. If x is not an acceptable // anonymous field, the result is nil. func fieldName(x Expr) *Ident { switch t := x.(type) { case *Ident: return t case *SelectorExpr: if _, ok := t.X.(*Ident); ok { return t.Sel } case *StarExpr: return fieldName(t.X) } return nil } func filterFieldList(fields *FieldList, filter Filter, export bool) (removedFields bool) { if fields == nil { return false } list := fields.List j := 0 for _, f := range list { var keepField bool if len(f.Names) == 0 { // anonymous field name := fieldName(f.Type) keepField = name != nil && filter(name.Name) } else { n := len(f.Names) f.Names = filterIdentList(f.Names, filter) if len(f.Names) < n { removedFields = true } keepField = len(f.Names) > 0 } if keepField { if export { filterType(f.Type, filter, export) } list[j] = f j++ } } if j < len(list) { removedFields = true } fields.List = list[0:j] return } func filterCompositeLit(lit *CompositeLit, filter Filter, export bool) { n := len(lit.Elts) lit.Elts = filterExprList(lit.Elts, filter, export) if len(lit.Elts) < n { lit.Incomplete = true } } func filterExprList(list []Expr, filter Filter, export bool) []Expr { j := 0 for _, exp := range list { switch x := exp.(type) { case *CompositeLit: filterCompositeLit(x, filter, export) case *KeyValueExpr: if x, ok := x.Key.(*Ident); ok && !filter(x.Name) { continue } if x, ok := x.Value.(*CompositeLit); ok { filterCompositeLit(x, filter, export) } } list[j] = exp j++ } return list[0:j] } func filterParamList(fields *FieldList, filter Filter, export bool) bool { if fields == nil { return false } var b bool for _, f := range fields.List { if filterType(f.Type, filter, export) { b = true } } return b } func filterType(typ Expr, f Filter, export bool) bool { switch t := typ.(type) { case *Ident: return f(t.Name) case *ParenExpr: return filterType(t.X, f, export) case *ArrayType: return filterType(t.Elt, f, export) case *StructType: if filterFieldList(t.Fields, f, export) { t.Incomplete = true } return len(t.Fields.List) > 0 case *FuncType: b1 := filterParamList(t.Params, f, export) b2 := filterParamList(t.Results, f, export) return b1 || b2 case *InterfaceType: if filterFieldList(t.Methods, f, export) { t.Incomplete = true } return len(t.Methods.List) > 0 case *MapType: b1 := filterType(t.Key, f, export) b2 := filterType(t.Value, f, export) return b1 || b2 case *ChanType: return filterType(t.Value, f, export) case *TupleType: if filterFieldList(t.Fields, f, export) { // Note: TupleType doesn't have an Incomplete field like StructType } return t.Fields != nil && len(t.Fields.List) > 0 } return false } func filterSpec(spec Spec, f Filter, export bool) bool { switch s := spec.(type) { case *ValueSpec: s.Names = filterIdentList(s.Names, f) s.Values = filterExprList(s.Values, f, export) if len(s.Names) > 0 { if export { filterType(s.Type, f, export) } return true } case *TypeSpec: if f(s.Name.Name) { if export { filterType(s.Type, f, export) } return true } if !export { // For general filtering (not just exports), // filter type even if name is not filtered // out. // If the type contains filtered elements, // keep the declaration. return filterType(s.Type, f, export) } } return false } func filterSpecList(list []Spec, f Filter, export bool) []Spec { j := 0 for _, s := range list { if filterSpec(s, f, export) { list[j] = s j++ } } return list[0:j] } // FilterDecl trims the AST for a Go declaration in place by removing // all names (including struct field and interface method names, but // not from parameter lists) that don't pass through the filter f. // // FilterDecl reports whether there are any declared names left after // filtering. func FilterDecl(decl Decl, f Filter) bool { return filterDecl(decl, f, false) } func filterDecl(decl Decl, f Filter, export bool) bool { switch d := decl.(type) { case *GenDecl: d.Specs = filterSpecList(d.Specs, f, export) return len(d.Specs) > 0 case *FuncDecl: return f(d.Name.Name) } return false } // FilterFile trims the AST for a Go file in place by removing all // names from top-level declarations (including struct field and // interface method names, but not from parameter lists) that don't // pass through the filter f. If the declaration is empty afterwards, // the declaration is removed from the AST. Import declarations are // always removed. The File.Comments list is not changed. // // FilterFile reports whether there are any top-level declarations // left after filtering. func FilterFile(src *File, f Filter) bool { return filterFile(src, f, false) } func filterFile(src *File, f Filter, export bool) bool { j := 0 for _, d := range src.Decls { if filterDecl(d, f, export) { src.Decls[j] = d j++ } } src.Decls = src.Decls[0:j] return j > 0 } // FilterPackage trims the AST for a Go package in place by removing // all names from top-level declarations (including struct field and // interface method names, but not from parameter lists) that don't // pass through the filter f. If the declaration is empty afterwards, // the declaration is removed from the AST. The pkg.Files list is not // changed, so that file names and top-level package comments don't get // lost. // // FilterPackage reports whether there are any top-level declarations // left after filtering. func FilterPackage(pkg *Package, f Filter) bool { return filterPackage(pkg, f, false) } func filterPackage(pkg *Package, f Filter, export bool) bool { hasDecls := false for _, src := range pkg.Files { if filterFile(src, f, export) { hasDecls = true } } return hasDecls } // ---------------------------------------------------------------------------- // Merging of package files // The MergeMode flags control the behavior of MergePackageFiles. type MergeMode uint const ( // FilterFuncDuplicates - If set, duplicate function declarations are excluded. FilterFuncDuplicates MergeMode = 1 << iota // FilterUnassociatedComments - If set, comments that are not associated with a specific // AST node (as Doc or Comment) are excluded. FilterUnassociatedComments // FilterImportDuplicates - If set, duplicate import declarations are excluded. FilterImportDuplicates ) // nameOf returns the function (foo) or method name (foo.bar) for // the given function declaration. If the AST is incorrect for the // receiver, it assumes a function instead. func nameOf(f *FuncDecl) string { if r := f.Recv; r != nil && len(r.List) == 1 { // looks like a correct receiver declaration t := r.List[0].Type // dereference pointer receiver types if p, _ := t.(*StarExpr); p != nil { t = p.X } // the receiver type must be a type name if p, _ := t.(*Ident); p != nil { return p.Name + "." + f.Name.Name } // otherwise assume a function instead } return f.Name.Name } // separator is an empty //-style comment that is interspersed between // different comment groups when they are concatenated into a single group var separator = &Comment{Slash: token.NoPos, Text: "//"} // MergePackageFiles creates a file AST by merging the ASTs of the // files belonging to a package. The mode flags control merging behavior. func MergePackageFiles(pkg *Package, mode MergeMode) *File { // Count the number of package docs, comments and declarations across // all package files. Also, compute sorted list of filenames, so that // subsequent iterations can always iterate in the same order. ndocs := 0 ncomments := 0 ndecls := 0 filenames := make([]string, len(pkg.Files)) i := 0 for filename, f := range pkg.Files { filenames[i] = filename i++ if f.Doc != nil { ndocs += len(f.Doc.List) + 1 // +1 for separator } ncomments += len(f.Comments) ndecls += len(f.Decls) } sort.Strings(filenames) // Collect package comments from all package files into a single // CommentGroup - the collected package documentation. In general // there should be only one file with a package comment; but it's // better to collect extra comments than drop them on the floor. var doc *CommentGroup var pos token.Pos if ndocs > 0 { list := make([]*Comment, ndocs-1) // -1: no separator before first group i := 0 for _, filename := range filenames { f := pkg.Files[filename] if f.Doc != nil { if i > 0 { // not the first group - add separator list[i] = separator i++ } for _, c := range f.Doc.List { list[i] = c i++ } if f.Package > pos { // Keep the maximum package clause position as // position for the package clause of the merged // files. pos = f.Package } } } doc = &CommentGroup{List: list} } // Collect declarations from all package files. var decls []Decl if ndecls > 0 { decls = make([]Decl, ndecls) funcs := make(map[string]int) // map of func name -> decls index i := 0 // current index n := 0 // number of filtered entries for _, filename := range filenames { f := pkg.Files[filename] for _, d := range f.Decls { if mode&FilterFuncDuplicates != 0 { // A language entity may be declared multiple // times in different package files; only at // build time declarations must be unique. // For now, exclude multiple declarations of // functions - keep the one with documentation. // // TODO(gri): Expand this filtering to other // entities (const, type, vars) if // multiple declarations are common. if f, isFun := d.(*FuncDecl); isFun { name := nameOf(f) if j, exists := funcs[name]; exists { // function declared already if decls[j] != nil && decls[j].(*FuncDecl).Doc == nil { // existing declaration has no documentation; // ignore the existing declaration decls[j] = nil } else { // ignore the new declaration d = nil } n++ // filtered an entry } else { funcs[name] = i } } } decls[i] = d i++ } } // Eliminate nil entries from the decls list if entries were // filtered. We do this using a 2nd pass in order to not disturb // the original declaration order in the source (otherwise, this // would also invalidate the monotonically increasing position // info within a single file). if n > 0 { i = 0 for _, d := range decls { if d != nil { decls[i] = d i++ } } decls = decls[0:i] } } // Collect import specs from all package files. var imports []*ImportSpec if mode&FilterImportDuplicates != 0 { seen := make(map[string]bool) for _, filename := range filenames { f := pkg.Files[filename] for _, imp := range f.Imports { if path := imp.Path.Value; !seen[path] { // TODO: consider handling cases where: // - 2 imports exist with the same import path but // have different local names (one should probably // keep both of them) // - 2 imports exist but only one has a comment // - 2 imports exist and they both have (possibly // different) comments imports = append(imports, imp) seen[path] = true } } } } else { // Iterate over filenames for deterministic order. for _, filename := range filenames { f := pkg.Files[filename] imports = append(imports, f.Imports...) } } // Collect comments from all package files. var comments []*CommentGroup if mode&FilterUnassociatedComments == 0 { comments = make([]*CommentGroup, ncomments) i := 0 for _, filename := range filenames { f := pkg.Files[filename] i += copy(comments[i:], f.Comments) } } // TODO(gri) need to compute unresolved identifiers! return &File{ doc, pos, NewIdent(pkg.Name), decls, imports, comments, nil, nil, false, false, false, false, } } ================================================ FILE: ast/fromgo/gopast.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package fromgo import ( "go/ast" "go/token" "log" "reflect" gopast "github.com/goplus/xgo/ast" goptoken "github.com/goplus/xgo/token" "github.com/goplus/xgo/ast/fromgo/typeparams" ) // ---------------------------------------------------------------------------- func gopExpr(val ast.Expr) gopast.Expr { if val == nil { return nil } switch v := val.(type) { case *ast.Ident: return gopIdent(v) case *ast.SelectorExpr: return &gopast.SelectorExpr{ X: gopExpr(v.X), Sel: gopIdent(v.Sel), } case *ast.SliceExpr: return &gopast.SliceExpr{ X: gopExpr(v.X), Lbrack: v.Lbrack, Low: gopExpr(v.Low), High: gopExpr(v.High), Max: gopExpr(v.Max), Slice3: v.Slice3, Rbrack: v.Rbrack, } case *ast.StarExpr: return &gopast.StarExpr{ Star: v.Star, X: gopExpr(v.X), } case *ast.MapType: return &gopast.MapType{ Map: v.Map, Key: gopType(v.Key), Value: gopType(v.Value), } case *ast.StructType: return &gopast.StructType{ Struct: v.Struct, Fields: gopFieldList(v.Fields), } case *ast.FuncType: return gopFuncType(v) case *ast.InterfaceType: return &gopast.InterfaceType{ Interface: v.Interface, Methods: gopFieldList(v.Methods), } case *ast.ArrayType: return &gopast.ArrayType{ Lbrack: v.Lbrack, Len: gopExpr(v.Len), Elt: gopType(v.Elt), } case *ast.ChanType: return &gopast.ChanType{ Begin: v.Begin, Arrow: v.Arrow, Dir: gopast.ChanDir(v.Dir), Value: gopType(v.Value), } case *ast.BasicLit: return gopBasicLit(v) case *ast.BinaryExpr: return &gopast.BinaryExpr{ X: gopExpr(v.X), OpPos: v.OpPos, Op: goptoken.Token(v.Op), Y: gopExpr(v.Y), } case *ast.UnaryExpr: return &gopast.UnaryExpr{ OpPos: v.OpPos, Op: goptoken.Token(v.Op), X: gopExpr(v.X), } case *ast.CallExpr: return &gopast.CallExpr{ Fun: gopExpr(v.Fun), Lparen: v.Lparen, Args: gopExprs(v.Args), Ellipsis: v.Ellipsis, Rparen: v.Rparen, } case *ast.IndexExpr: return &gopast.IndexExpr{ X: gopExpr(v.X), Lbrack: v.Lbrack, Index: gopExpr(v.Index), Rbrack: v.Rbrack, } case *typeparams.IndexListExpr: return &gopast.IndexListExpr{ X: gopExpr(v.X), Lbrack: v.Lbrack, Indices: gopExprs(v.Indices), Rbrack: v.Rbrack, } case *ast.ParenExpr: return &gopast.ParenExpr{ Lparen: v.Lparen, X: gopExpr(v.X), Rparen: v.Rparen, } case *ast.CompositeLit: return &gopast.CompositeLit{ Type: gopType(v.Type), Lbrace: v.Lbrace, Elts: gopExprs(v.Elts), Rbrace: v.Rbrace, } case *ast.FuncLit: return &gopast.FuncLit{ Type: gopFuncType(v.Type), Body: &gopast.BlockStmt{}, // skip closure body } case *ast.TypeAssertExpr: return &gopast.TypeAssertExpr{ X: gopExpr(v.X), Lparen: v.Lparen, Type: gopType(v.Type), Rparen: v.Rparen, } case *ast.KeyValueExpr: return &gopast.KeyValueExpr{ Key: gopExpr(v.Key), Colon: v.Colon, Value: gopExpr(v.Value), } case *ast.Ellipsis: return &gopast.Ellipsis{ Ellipsis: v.Ellipsis, Elt: gopExpr(v.Elt), } } log.Panicln("gopExpr: unknown expr -", reflect.TypeOf(val)) return nil } func gopExprs(vals []ast.Expr) []gopast.Expr { n := len(vals) if n == 0 { return nil } ret := make([]gopast.Expr, n) for i, v := range vals { ret[i] = gopExpr(v) } return ret } // ---------------------------------------------------------------------------- func gopFuncType(v *ast.FuncType) *gopast.FuncType { return &gopast.FuncType{ Func: v.Func, TypeParams: gopFieldList(typeparams.ForFuncType(v)), Params: gopFieldList(v.Params), Results: gopFieldList(v.Results), } } func gopType(v ast.Expr) gopast.Expr { return gopExpr(v) } func gopBasicLit(v *ast.BasicLit) *gopast.BasicLit { if v == nil { return nil } return &gopast.BasicLit{ ValuePos: v.ValuePos, Kind: goptoken.Token(v.Kind), Value: v.Value, } } func gopIdent(v *ast.Ident) *gopast.Ident { if v == nil { return nil } return &gopast.Ident{ NamePos: v.NamePos, Name: v.Name, Obj: &gopast.Object{Data: v}, } } // CheckIdent checks if an XGo ast.Ident is converted from a Go ast.Ident or not. // If it is, CheckIdent returns the original Go ast.Ident object. func CheckIdent(v *gopast.Ident) (id *ast.Ident, ok bool) { if o := v.Obj; o != nil && o.Kind == 0 && o.Data != nil { id, ok = o.Data.(*ast.Ident) } return } func gopIdents(names []*ast.Ident) []*gopast.Ident { ret := make([]*gopast.Ident, len(names)) for i, v := range names { ret[i] = gopIdent(v) } return ret } // ---------------------------------------------------------------------------- func gopField(v *ast.Field) *gopast.Field { return &gopast.Field{ Names: gopIdents(v.Names), Type: gopType(v.Type), Tag: gopBasicLit(v.Tag), } } func gopFieldList(v *ast.FieldList) *gopast.FieldList { if v == nil { return nil } list := make([]*gopast.Field, len(v.List)) for i, item := range v.List { list[i] = gopField(item) } return &gopast.FieldList{Opening: v.Opening, List: list, Closing: v.Closing} } func gopFuncDecl(v *ast.FuncDecl) *gopast.FuncDecl { return &gopast.FuncDecl{ Doc: v.Doc, Recv: gopFieldList(v.Recv), Name: gopIdent(v.Name), Type: gopFuncType(v.Type), Body: &gopast.BlockStmt{}, // ignore function body } } // ---------------------------------------------------------------------------- func gopImportSpec(spec *ast.ImportSpec) *gopast.ImportSpec { return &gopast.ImportSpec{ Name: gopIdent(spec.Name), Path: gopBasicLit(spec.Path), EndPos: spec.EndPos, } } func gopTypeSpec(spec *ast.TypeSpec) *gopast.TypeSpec { return &gopast.TypeSpec{ Name: gopIdent(spec.Name), TypeParams: gopFieldList(typeparams.ForTypeSpec(spec)), Assign: spec.Assign, Type: gopType(spec.Type), } } func gopValueSpec(spec *ast.ValueSpec) *gopast.ValueSpec { return &gopast.ValueSpec{ Names: gopIdents(spec.Names), Type: gopType(spec.Type), Values: gopExprs(spec.Values), } } func gopGenDecl(v *ast.GenDecl) *gopast.GenDecl { specs := make([]gopast.Spec, len(v.Specs)) for i, spec := range v.Specs { switch v.Tok { case token.IMPORT: specs[i] = gopImportSpec(spec.(*ast.ImportSpec)) case token.TYPE: specs[i] = gopTypeSpec(spec.(*ast.TypeSpec)) case token.VAR, token.CONST: specs[i] = gopValueSpec(spec.(*ast.ValueSpec)) default: log.Panicln("gopGenDecl: unknown spec -", v.Tok) } } return &gopast.GenDecl{ Doc: v.Doc, TokPos: v.TokPos, Tok: goptoken.Token(v.Tok), Lparen: v.Lparen, Specs: specs, Rparen: v.Rparen, } } // ---------------------------------------------------------------------------- func gopDecl(decl ast.Decl) gopast.Decl { switch v := decl.(type) { case *ast.GenDecl: return gopGenDecl(v) case *ast.FuncDecl: return gopFuncDecl(v) } log.Panicln("gopDecl: unknown decl -", reflect.TypeOf(decl)) return nil } func gopDecls(decls []ast.Decl) []gopast.Decl { ret := make([]gopast.Decl, len(decls)) for i, decl := range decls { ret[i] = gopDecl(decl) } return ret } // ---------------------------------------------------------------------------- const ( KeepFuncBody = 1 << iota KeepCgo ) // ASTFile converts a Go ast.File into an XGo ast.File object. func ASTFile(f *ast.File, mode int) *gopast.File { if (mode & KeepFuncBody) != 0 { log.Panicln("ASTFile: doesn't support keeping func body now") } if (mode & KeepCgo) != 0 { log.Panicln("ASTFile: doesn't support keeping cgo now") } return &gopast.File{ Doc: f.Doc, Package: f.Package, Name: gopIdent(f.Name), Decls: gopDecls(f.Decls), } } // ---------------------------------------------------------------------------- ================================================ FILE: ast/fromgo/gopast_test.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package fromgo import ( "bytes" "go/ast" "go/parser" "go/token" "testing" gopast "github.com/goplus/xgo/ast" "github.com/goplus/xgo/format" ) func testAST(t *testing.T, from, to string) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, "foo.go", from, 0) if err != nil { t.Fatal("parser.ParseFile:", err) } gopf := ASTFile(f, 0) var b bytes.Buffer err = format.Node(&b, fset, gopf) if err != nil { t.Fatal("format.Node:", err) } result := b.String() if to != result { t.Fatalf("\nResult:\n%s\nExpected:\n%s\n", result, to) } } func test(t *testing.T, src string) { testAST(t, src, src) } func testPanic(t *testing.T, panicMsg string, doPanic func()) { t.Run(panicMsg, func(t *testing.T) { defer func() { if e := recover(); e == nil { t.Fatal("testPanic: no error?") } else if msg := e.(string); msg != panicMsg { t.Fatalf("\nResult:\n%s\nExpected Panic:\n%s\n", msg, panicMsg) } }() doPanic() }) } func TestErrASTFile(t *testing.T) { testPanic(t, "ASTFile: doesn't support keeping cgo now\n", func() { ASTFile(nil, KeepCgo) }) testPanic(t, "ASTFile: doesn't support keeping func body now\n", func() { ASTFile(nil, KeepFuncBody) }) } func TestErrDecl(t *testing.T) { testPanic(t, "gopDecl: unknown decl - \n", func() { gopDecl(nil) }) testPanic(t, "gopGenDecl: unknown spec - ILLEGAL\n", func() { gopGenDecl(&ast.GenDecl{ Specs: []ast.Spec{nil}, }) }) } func TestErrExpr(t *testing.T) { testPanic(t, "gopExpr: unknown expr - *ast.BadExpr\n", func() { gopExpr(&ast.BadExpr{}) }) } func TestBasic(t *testing.T) { test(t, `package main import "fmt" type a struct { v map[int]chan int arr *[2]func() i interface{} } var b = &a{ arr: &[2]func(){ nil, func() {}, }, } const c = (10 + 20) * 2 var d = b.arr[1] var e = b.arr[:1] var f = a.i.(func() (int))() func foo(v ...interface{}) {} `) } func TestMethod(t *testing.T) { test(t, `package main type foo int func (a foo) Str() (string) {} `) } func TestCheckIdent(t *testing.T) { if _, ok := CheckIdent(&gopast.Ident{}); ok { t.Fatal("CheckIdent: found?") } if _, ok := CheckIdent(&gopast.Ident{Obj: &gopast.Object{ Data: &ast.Ident{}, }}); !ok { t.Fatal("CheckIdent: not found?") } } ================================================ FILE: ast/fromgo/typeparams/typeparams_go117.go ================================================ //go:build !go1.18 // +build !go1.18 package typeparams import ( "go/ast" "go/token" ) func unsupported() { panic("type parameters are unsupported at this go version") } // IndexListExpr is a placeholder type, as type parameters are not supported at // this Go version. Its methods panic on use. type IndexListExpr struct { ast.Expr X ast.Expr // expression Lbrack token.Pos // position of "[" Indices []ast.Expr // index expressions Rbrack token.Pos // position of "]" } func (*IndexListExpr) Pos() token.Pos { unsupported(); return token.NoPos } func (*IndexListExpr) End() token.Pos { unsupported(); return token.NoPos } // ForFuncType returns an empty field list, as type parameters are not // supported at this Go version. func ForFuncType(*ast.FuncType) *ast.FieldList { return nil } // ForTypeSpec returns an empty field list, as type parameters are not // supported at this Go version. func ForTypeSpec(*ast.TypeSpec) *ast.FieldList { return nil } ================================================ FILE: ast/fromgo/typeparams/typeparams_go118.go ================================================ //go:build go1.18 // +build go1.18 package typeparams import "go/ast" // IndexListExpr is an alias for ast.IndexListExpr. type IndexListExpr = ast.IndexListExpr // ForFuncType returns n.TypeParams. func ForFuncType(n *ast.FuncType) *ast.FieldList { if n == nil { return nil } return n.TypeParams } // ForTypeSpec returns n.TypeParams. func ForTypeSpec(n *ast.TypeSpec) *ast.FieldList { if n == nil { return nil } return n.TypeParams } ================================================ FILE: ast/fromgo/typeparams_test.go ================================================ //go:build go1.18 // +build go1.18 package fromgo import "testing" func TestIndexListExpr(t *testing.T) { test(t, `package main type N1 = T1[int] type N2 = T2[string, int] var f = test[string, int](1, 2) `) } ================================================ FILE: ast/gopq/dom.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package gopq import ( "github.com/goplus/xgo/ast" "github.com/goplus/xgo/token" ) // ----------------------------------------------------------------------------- type astPackages map[string]*ast.Package func (p astPackages) Pos() token.Pos { return token.NoPos } func (p astPackages) End() token.Pos { return token.NoPos } func (p astPackages) ForEach(filter func(node Node) error) error { for _, pkg := range p { node := astPackage{pkg} if err := filter(node); err == ErrBreak { return err } } return nil } func (p astPackages) Obj() any { return p } // ----------------------------------------------------------------------------- type astPackage struct { *ast.Package } func (p astPackage) ForEach(filter func(node Node) error) error { for _, file := range p.Files { node := astFile{file} if err := filter(node); err == ErrBreak { return err } } return nil } func (p astPackage) Obj() any { return p.Package } // ----------------------------------------------------------------------------- type astFile struct { *ast.File } func (p astFile) ForEach(filter func(node Node) error) error { for _, decl := range p.Decls { node := &astDecl{decl} if err := filter(node); err == ErrBreak { return err } } return nil } func (p astFile) Obj() any { return p.File } // ----------------------------------------------------------------------------- type astDecl struct { ast.Decl } func (p *astDecl) ForEach(filter func(node Node) error) error { if decl, ok := p.Decl.(*ast.GenDecl); ok { for _, spec := range decl.Specs { node := &astSpec{spec} if err := filter(node); err == ErrBreak { return err } } } return nil } func (p *astDecl) Obj() any { return p.Decl } // ----------------------------------------------------------------------------- type astSpec struct { ast.Spec } func (p *astSpec) ForEach(filter func(node Node) error) error { return nil } func (p *astSpec) Obj() any { return p.Spec } // ----------------------------------------------------------------------------- func visitStmt(stmt ast.Stmt, filter func(node Node) error) error { if stmt != nil { return filter(&astStmt{stmt}) } return nil } type astStmt struct { ast.Stmt } func (p *astStmt) ForEach(filter func(node Node) error) error { switch stmt := p.Stmt.(type) { case *ast.IfStmt: if err := visitStmt(stmt.Init, filter); err == ErrBreak { return err } if err := filter(&astStmt{stmt.Body}); err == ErrBreak { return err } if err := visitStmt(stmt.Else, filter); err == ErrBreak { return err } case *ast.BlockStmt: for _, stmt := range stmt.List { node := &astStmt{stmt} if err := filter(node); err == ErrBreak { return err } } } // TODO(xsw): visit other stmts return nil } func (p *astStmt) Obj() any { return p.Stmt } // ----------------------------------------------------------------------------- type astExpr struct { ast.Expr } func (p *astExpr) ForEach(filter func(node Node) error) error { return nil } func (p *astExpr) Obj() any { return p.Expr } // ----------------------------------------------------------------------------- ================================================ FILE: ast/gopq/gopq.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package gopq import ( "errors" "io/fs" "syscall" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/parser/fsx" "github.com/goplus/xgo/token" ) // ----------------------------------------------------------------------------- const ( GopPackage = true ) var ( // ErrBreak - break ErrBreak = syscall.ELOOP // ErrNotFound - not found ErrNotFound = errors.New("not found") // ErrTooManyNodes - too may nodes ErrTooManyNodes = errors.New("too many nodes") // ErrUnexpectedNode - unexpected node ErrUnexpectedNode = errors.New("unexpected node") ) // Node - node interface type Node interface { ast.Node ForEach(filter func(node Node) error) error Obj() any } // NodeEnum - node enumerator type NodeEnum interface { ForEach(filter func(node Node) error) error } // NodeSet - node set type NodeSet struct { Data NodeEnum Err error } // FromFile calls ParseFile for a single file and returns *ast.File node set. func FromFile(fset *token.FileSet, filename string, src any, mode parser.Mode) (doc NodeSet, err error) { file, err := parser.ParseFile(fset, filename, src, mode) if err != nil { return } return NodeSet{Data: &oneNode{astFile{file}}}, nil } // FromFSFile calls ParseFSFile for a single file and returns *ast.File node set. func FromFSFile( fset *token.FileSet, fs fsx.FileSystem, filename string, src any, mode parser.Mode) (doc NodeSet, err error) { file, err := parser.ParseFSFile(fset, fs, filename, src, mode) if err != nil { return } return NodeSet{Data: &oneNode{astFile{file}}}, nil } // FromDir calls ParseFile for all XGo files in the directory specified by path // and returns a map of package name -> package AST with all the packages found. // // If filter != nil, only the XGo files with fs.FileInfo entries passing through // the filter are considered. The mode bits are passed to ParseFile unchanged. // Position information is recorded in fset, which must not be nil. // // If the directory couldn't be read, a nil map and the respective error are // returned. If a parse error occurred, a non-nil but incomplete map and the // first error encountered are returned. func FromDir( fset *token.FileSet, path string, filter func(fs.FileInfo) bool, mode parser.Mode) (doc NodeSet, err error) { pkgs, err := parser.ParseDir(fset, path, filter, mode) if err != nil { return } return NodeSet{Data: &oneNode{astPackages(pkgs)}}, nil } // FromFSDir calls ParseFile for all XGo files in the directory specified by path // and returns a map of package name -> package AST with all the packages found. // // If filter != nil, only the XGo files with fs.FileInfo entries passing through // the filter are considered. The mode bits are passed to ParseFile unchanged. // Position information is recorded in fset, which must not be nil. // // If the directory couldn't be read, a nil map and the respective error are // returned. If a parse error occurred, a non-nil but incomplete map and the // first error encountered are returned. func FromFSDir( fset *token.FileSet, fs parser.FileSystem, path string, filter func(fs.FileInfo) bool, mode parser.Mode) (doc NodeSet, err error) { pkgs, err := parser.ParseFSDir(fset, fs, path, parser.Config{Filter: filter, Mode: mode}) if err != nil { return } return NodeSet{Data: &oneNode{astPackages(pkgs)}}, nil } // Ok returns if node set is valid or not. func (p NodeSet) Ok() bool { return p.Err == nil } // ----------------------------------------------------------------------------- // FuncDecl returns *ast.FuncDecl node set. func (p NodeSet) FuncDecl__0() NodeSet { return p.Match(func(node Node) bool { if node, ok := node.(*astDecl); ok { _, ok = node.Decl.(*ast.FuncDecl) return ok } return false }) } // FuncDecl returns *ast.FuncDecl node set. func (p NodeSet) FuncDecl__1(name string) NodeSet { return p.Match(func(node Node) bool { if node, ok := node.(*astDecl); ok { if fn, ok := node.Decl.(*ast.FuncDecl); ok { return fn.Name.Name == name } } return false }) } // GenDecl returns *ast.GenDecl node set. func (p NodeSet) GenDecl__0(tok token.Token) NodeSet { return p.Match(func(node Node) bool { if node, ok := node.(*astDecl); ok { if decl, ok := node.Decl.(*ast.GenDecl); ok { return decl.Tok == tok } } return false }) } // TypeSpec returns *ast.TypeSpec node set. func (p NodeSet) TypeSpec() NodeSet { return p.Match(func(node Node) bool { if node, ok := node.(*astSpec); ok { _, ok = node.Spec.(*ast.TypeSpec) return ok } return false }) } // ValueSpec returns *ast.ValueSpec node set. func (p NodeSet) ValueSpec() NodeSet { return p.Match(func(node Node) bool { if node, ok := node.(*astSpec); ok { _, ok = node.Spec.(*ast.ValueSpec) return ok } return false }) } // ImportSpec returns *ast.ImportSpec node set. func (p NodeSet) ImportSpec() NodeSet { return p.Match(func(node Node) bool { if node, ok := node.(*astSpec); ok { _, ok = node.Spec.(*ast.ImportSpec) return ok } return false }) } // ExprStmt returns *ast.ExprStmt node set. func (p NodeSet) ExprStmt() NodeSet { return p.Match(func(node Node) bool { if node, ok := node.(*astStmt); ok { _, ok = node.Stmt.(*ast.ExprStmt) return ok } return false }) } // AssignStmt returns *ast.AssignStmt node set. func (p NodeSet) AssignStmt() NodeSet { return p.Match(func(node Node) bool { if node, ok := node.(*astStmt); ok { _, ok = node.Stmt.(*ast.AssignStmt) return ok } return false }) } // CallExpr returns *ast.CallExpr node set. func (p NodeSet) CallExpr__0() NodeSet { return p.Match(func(node Node) bool { if node, ok := node.(*astExpr); ok { _, ok = node.Expr.(*ast.CallExpr) return ok } return false }) } // CallExpr returns *ast.CallExpr node set. func (p NodeSet) CallExpr__1(name string) NodeSet { return p.Match(func(node Node) bool { if node, ok := node.(*astExpr); ok { if expr, ok := node.Expr.(*ast.CallExpr); ok { return getName(expr.Fun, true) == name } } return false }) } // CompositeLit returns *ast.CompositeLit node set. func (p NodeSet) CompositeLit__0() NodeSet { return p.Match(func(node Node) bool { if node, ok := node.(*astExpr); ok { _, ok = node.Expr.(*ast.CompositeLit) return ok } return false }) } // CompositeLit returns *ast.CompositeLit node set. func (p NodeSet) CompositeLit__1(name string) NodeSet { return p.Match(func(node Node) bool { if node, ok := node.(*astExpr); ok { if lit, ok := node.Expr.(*ast.CompositeLit); ok && lit.Type != nil { return getName(lit.Type, true) == name } } return false }) } // ----------------------------------------------------------------------------- func (p NodeSet) Gop_Enum(callback func(node NodeSet)) { if p.Err == nil { p.Data.ForEach(func(node Node) error { t := NodeSet{Data: &oneNode{node}} callback(t) return nil }) } } func (p NodeSet) ForEach(callback func(node NodeSet)) { p.Gop_Enum(callback) } // ----------------------------------------------------------------------------- type oneNode struct { Node } func (p *oneNode) ForEach(filter func(node Node) error) error { return filter(p.Node) } func (p *oneNode) Cached() int { return 1 } // One returns the first node as a node set. func (p NodeSet) One() NodeSet { if _, ok := p.Data.(*oneNode); ok { return p } node, err := p.CollectOne__0() if err != nil { return NodeSet{Err: err} } return NodeSet{Data: &oneNode{node}} } // One creates a node set that only contains a signle node. func One__0(node Node) NodeSet { return NodeSet{Data: &oneNode{node}} } // One creates a node set that only contains a signle node. func One__1(f *ast.File) NodeSet { return NodeSet{Data: &oneNode{astFile{f}}} } // One creates a node set that only contains a signle node. func One__2(pkg *ast.Package) NodeSet { return NodeSet{Data: &oneNode{astPackage{pkg}}} } // ----------------------------------------------------------------------------- type fixNodes struct { nodes []Node } func (p *fixNodes) ForEach(filter func(node Node) error) error { for _, node := range p.nodes { if filter(node) == ErrBreak { return ErrBreak } } return nil } func (p *fixNodes) Cached() int { return len(p.nodes) } // Nodes creates a fixed node set. func Nodes(nodes ...Node) NodeSet { return NodeSet{Data: &fixNodes{nodes}} } // ----------------------------------------------------------------------------- type cached interface { Cached() int } // Cache caches node set. func (p NodeSet) Cache() NodeSet { if _, ok := p.Data.(cached); ok { return p } nodes, err := p.Collect() if err != nil { return NodeSet{Err: err} } return NodeSet{Data: &fixNodes{nodes}} } // ----------------------------------------------------------------------------- type anyNodes struct { data NodeEnum } func (p *anyNodes) ForEach(filter func(node Node) error) error { return p.data.ForEach(func(node Node) error { return anyForEach(node, filter) }) } func anyForEach(p Node, filter func(node Node) error) error { if err := filter(p); err == ErrBreak { return err } return p.ForEach(func(node Node) error { return anyForEach(node, filter) }) } // Any returns deeply visiting node set. func (p NodeSet) Any() (ret NodeSet) { if p.Err != nil { return p } return NodeSet{Data: &anyNodes{p.Data}} } // ----------------------------------------------------------------------------- type childNodes struct { data NodeEnum } func (p *childNodes) ForEach(filter func(node Node) error) error { return p.data.ForEach(func(node Node) error { return node.ForEach(filter) }) } // Child returns child node set. func (p NodeSet) Child() NodeSet { if p.Err != nil { return p } return NodeSet{Data: &childNodes{p.Data}} } // ----------------------------------------------------------------------------- type bodyNodes struct { data NodeEnum } func (p *bodyNodes) ForEach(filter func(node Node) error) error { return p.data.ForEach(func(node Node) error { switch node := node.(type) { case *astDecl: if fn, ok := node.Decl.(*ast.FuncDecl); ok && fn.Body != nil { return filter(&astStmt{fn.Body}) } } return ErrNotFound }) } // Body returns body node set. func (p NodeSet) Body() NodeSet { if p.Err != nil { return p } return NodeSet{Data: &bodyNodes{p.Data}} } // ----------------------------------------------------------------------------- type xNodes struct { data NodeEnum } func (p *xNodes) ForEach(filter func(node Node) error) error { return p.data.ForEach(func(node Node) error { switch node := node.(type) { case *astStmt: switch stmt := node.Stmt.(type) { case *ast.ExprStmt: return filter(&astExpr{stmt.X}) } case *astExpr: switch expr := node.Expr.(type) { case *ast.SelectorExpr: return filter(&astExpr{expr.X}) case *ast.UnaryExpr: return filter(&astExpr{expr.X}) } } return ErrNotFound }) } // X returns x node set. func (p NodeSet) X() NodeSet { if p.Err != nil { return p } return NodeSet{Data: &xNodes{p.Data}} } // ----------------------------------------------------------------------------- type funNodes struct { data NodeEnum } func (p *funNodes) ForEach(filter func(node Node) error) error { return p.data.ForEach(func(node Node) error { switch node := node.(type) { case *astExpr: switch expr := node.Expr.(type) { case *ast.CallExpr: return filter(&astExpr{expr.Fun}) } } return ErrNotFound }) } // Fun returns fun node set. func (p NodeSet) Fun() NodeSet { if p.Err != nil { return p } return NodeSet{Data: &funNodes{p.Data}} } // ----------------------------------------------------------------------------- type argNodes struct { data NodeEnum i int varg bool } func (p *argNodes) ForEach(filter func(node Node) error) error { return p.data.ForEach(func(node Node) error { switch node := node.(type) { case *astExpr: switch expr := node.Expr.(type) { case *ast.CallExpr: args := expr.Args if p.varg { for i, n := p.i, len(args); i < n; i++ { if err := filter(&astExpr{args[i]}); err == ErrBreak { return err } } return nil } else { return filter(&astExpr{args[p.i]}) } } } return ErrNotFound }) } // Arg returns args[i] node set. func (p NodeSet) Arg(i int) NodeSet { if p.Err != nil { return p } return NodeSet{Data: &argNodes{p.Data, i, false}} } // Varg returns args[i:] node set. func (p NodeSet) Varg(i int) NodeSet { if p.Err != nil { return p } return NodeSet{Data: &argNodes{p.Data, i, true}} } // ----------------------------------------------------------------------------- type ieltNodes struct { data NodeEnum i int } func (p *ieltNodes) ForEach(filter func(node Node) error) error { return p.data.ForEach(func(node Node) error { switch node := node.(type) { case *astExpr: switch expr := node.Expr.(type) { case *ast.CompositeLit: if i := p.i; i == -1 { for _, elt := range expr.Elts { if err := filter(&astExpr{elt}); err == ErrBreak { return err } } } else { return filter(&astExpr{expr.Elts[i]}) } } } return ErrNotFound }) } // Elt returns elts[i] node set. func (p NodeSet) Elt__0(i int) NodeSet { if p.Err != nil { return p } return NodeSet{Data: &ieltNodes{p.Data, i}} } // Elt returns elts[:] node set. func (p NodeSet) Elt__1() NodeSet { if p.Err != nil { return p } return NodeSet{Data: &ieltNodes{p.Data, -1}} } // ----------------------------------------------------------------------------- type eltNodes struct { data NodeEnum name string } func (p *eltNodes) ForEach(filter func(node Node) error) error { return p.data.ForEach(func(node Node) error { switch node := node.(type) { case *astExpr: switch expr := node.Expr.(type) { case *ast.CompositeLit: if elt, ok := getElt(expr.Elts, p.name); ok { return filter(&astExpr{elt}) } } } return ErrNotFound }) } // Elt returns elts[name] node set. func (p NodeSet) Elt__2(name string) NodeSet { if p.Err != nil { return p } return NodeSet{Data: &eltNodes{p.Data, name}} } // ----------------------------------------------------------------------------- type rhsNodes struct { data NodeEnum i int } func (p *rhsNodes) ForEach(filter func(node Node) error) error { return p.data.ForEach(func(node Node) error { switch node := node.(type) { case *astStmt: switch stmt := node.Stmt.(type) { case *ast.AssignStmt: return filter(&astExpr{stmt.Rhs[p.i]}) } } return ErrNotFound }) } // Rhs returns rhs[i] node set. func (p NodeSet) Rhs(i int) NodeSet { if p.Err != nil { return p } return NodeSet{Data: &rhsNodes{p.Data, i}} } // ----------------------------------------------------------------------------- type matchedNodes struct { data NodeEnum match func(node Node) bool } func (p *matchedNodes) ForEach(filter func(node Node) error) error { return p.data.ForEach(func(node Node) error { if p.match(node) { return filter(node) } return ErrNotFound }) } // Match filters the node set. func (p NodeSet) Match(match func(node Node) bool) (ret NodeSet) { if p.Err != nil { return p } return NodeSet{Data: &matchedNodes{p.Data, match}} } // ----------------------------------------------------------------------------- // Name returns names of the node set. func (p NodeSet) Name() []string { return p.ToString(NameOf) } // ToString returns string values of the node set. func (p NodeSet) ToString(str func(node Node) string) (items []string) { if p.Err != nil { return nil } p.Data.ForEach(func(node Node) error { items = append(items, str(node)) return nil }) return } // Collect collects all nodes of the node set. func (p NodeSet) Collect() (items []Node, err error) { if p.Err != nil { return nil, p.Err } p.Data.ForEach(func(node Node) error { items = append(items, node) return nil }) return } // CollectOne collects one node of a node set. // If exactly is true, it returns ErrTooManyNodes when node set is more than one. func (p NodeSet) CollectOne__1(exactly bool) (item Node, err error) { if p.Err != nil { return nil, p.Err } err = ErrNotFound if exactly { p.Data.ForEach(func(node Node) error { if err == ErrNotFound { item, err = node, nil return nil } err = ErrTooManyNodes return ErrBreak }) } else { p.Data.ForEach(func(node Node) error { item, err = node, nil return ErrBreak }) } return } // CollectOne returns the first node. func (p NodeSet) CollectOne__0() (item Node, err error) { return p.CollectOne__1(false) } // ----------------------------------------------------------------------------- ================================================ FILE: ast/gopq/helper.go ================================================ /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package gopq import ( "strconv" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/token" ) // ----------------------------------------------------------------------------- // Funcs returns all `*ast.FuncDecl` nodes. func (p NodeSet) Funcs() NodeSet { return p.Any().FuncDecl__0().Cache() } // ----------------------------------------------------------------------------- func (p NodeSet) UnquotedString__1(exactly bool) (ret string, err error) { item, err := p.CollectOne__1(exactly) if err != nil { return } if lit, ok := item.Obj().(*ast.BasicLit); ok && lit.Kind == token.STRING { return strconv.Unquote(lit.Value) } return "", ErrUnexpectedNode } func (p NodeSet) UnquotedString__0() (ret string, err error) { return p.UnquotedString__1(false) } // ----------------------------------------------------------------------------- func (p NodeSet) UnquotedStringElts__1(exactly bool) (ret []string, err error) { item, err := p.CollectOne__1(exactly) if err != nil { return } if lit, ok := item.Obj().(*ast.CompositeLit); ok { ret = make([]string, len(lit.Elts)) for i, elt := range lit.Elts { if lit, ok := elt.(*ast.BasicLit); ok && lit.Kind == token.STRING { if ret[i], err = strconv.Unquote(lit.Value); err != nil { return } } else { return nil, ErrUnexpectedNode } } return } return nil, ErrUnexpectedNode } func (p NodeSet) UnquotedStringElts__0() (ret []string, err error) { return p.UnquotedStringElts__1(false) } // ----------------------------------------------------------------------------- func (p NodeSet) Positions__1(exactly bool) (ret []token.Pos, err error) { item, err := p.CollectOne__1(exactly) if err != nil { return } switch o := item.Obj().(type) { case *ast.CompositeLit: return []token.Pos{o.Pos(), o.End(), o.Lbrace, o.Rbrace}, nil case ast.Node: return []token.Pos{o.Pos(), o.End()}, nil } return nil, ErrUnexpectedNode } func (p NodeSet) Positions__0() (ret []token.Pos, err error) { return p.Positions__1(false) } // ----------------------------------------------------------------------------- func (p NodeSet) EltLen__1(exactly bool) (ret int, err error) { item, err := p.CollectOne__1(exactly) if err != nil { return } if lit, ok := item.Obj().(*ast.CompositeLit); ok { return len(lit.Elts), nil } return 0, ErrUnexpectedNode } func (p NodeSet) EltLen__0() (ret int, err error) { return p.EltLen__1(false) } // ----------------------------------------------------------------------------- func (p NodeSet) Ident__1(exactly bool) (ret string, err error) { item, err := p.CollectOne__1(exactly) if err != nil { return } if ident, ok := item.Obj().(*ast.Ident); ok { return ident.Name, nil } return "", ErrUnexpectedNode } func (p NodeSet) Ident__0() (ret string, err error) { return p.Ident__1(false) } // ----------------------------------------------------------------------------- func getElt(elts []ast.Expr, name string) (ast.Expr, bool) { for _, elt := range elts { if kv, ok := elt.(*ast.KeyValueExpr); ok { if ident, ok := kv.Key.(*ast.Ident); ok && ident.Name == name { return kv.Value, true } } } return nil, false } // ----------------------------------------------------------------------------- // NameOf returns name of an ast node. func NameOf(node Node) string { return getName(node.Obj(), false) } func getName(v any, useEmpty bool) string { switch v := v.(type) { case *ast.FuncDecl: return v.Name.Name case *ast.ImportSpec: n := v.Name if n == nil { return "" } return n.Name case *ast.Ident: return v.Name case *ast.SelectorExpr: return getName(v.X, useEmpty) + "." + v.Sel.Name } if useEmpty { return "" } panic("node doesn't contain the `name` property") } // ----------------------------------------------------------------------------- func CodeOf(fset *token.FileSet, f *ast.File, start, end token.Pos) string { pos := fset.Position(start) n := int(end - start) return string(f.Code[pos.Offset : pos.Offset+n]) } // ----------------------------------------------------------------------------- ================================================ FILE: ast/goptest/gopq.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package goptest import ( "go/token" "github.com/goplus/xgo/ast/gopq" "github.com/goplus/xgo/parser/fsx/memfs" ) const ( GopPackage = "github.com/goplus/xgo/ast/gopq" ) // ----------------------------------------------------------------------------- // New creates a nodeset object that represents an XGo dom tree. func New(script string) (gopq.NodeSet, error) { fset := token.NewFileSet() fs := memfs.SingleFile("/foo", "bar.xgo", script) return gopq.FromFSDir(fset, fs, "/foo", nil, 0) } // ----------------------------------------------------------------------------- ================================================ FILE: ast/import.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ast import ( "sort" "strconv" "github.com/goplus/xgo/token" ) // SortImports sorts runs of consecutive import lines in import blocks in f. // It also removes duplicate imports when it is possible to do so without data loss. func SortImports(fset *token.FileSet, f *File) { for _, d := range f.Decls { d, ok := d.(*GenDecl) if !ok || d.Tok != token.IMPORT { // Not an import declaration, so we're done. // Imports are always first. break } if !d.Lparen.IsValid() { // Not a block: sorted by default. continue } // Identify and sort runs of specs on successive lines. i := 0 specs := d.Specs[:0] for j, s := range d.Specs { if j > i && lineAt(fset, s.Pos()) > 1+lineAt(fset, d.Specs[j-1].End()) { // j begins a new run. End this one. specs = append(specs, sortSpecs(fset, f, d.Specs[i:j])...) i = j } } specs = append(specs, sortSpecs(fset, f, d.Specs[i:])...) d.Specs = specs // Deduping can leave a blank line before the rparen; clean that up. if len(d.Specs) > 0 { lastSpec := d.Specs[len(d.Specs)-1] lastLine := lineAt(fset, lastSpec.Pos()) rParenLine := lineAt(fset, d.Rparen) for rParenLine > lastLine+1 { rParenLine-- fset.File(d.Rparen).MergeLine(rParenLine) } } } } func lineAt(fset *token.FileSet, pos token.Pos) int { return fset.PositionFor(pos, false).Line } func importPath(s Spec) string { t, err := strconv.Unquote(s.(*ImportSpec).Path.Value) if err == nil { return t } return "" } func importName(s Spec) string { n := s.(*ImportSpec).Name if n == nil { return "" } return n.Name } func importComment(s Spec) string { c := s.(*ImportSpec).Comment if c == nil { return "" } return c.Text() } // collapse indicates whether prev may be removed, leaving only next. func collapse(prev, next Spec) bool { if importPath(next) != importPath(prev) || importName(next) != importName(prev) { return false } return prev.(*ImportSpec).Comment == nil } type posSpan struct { Start token.Pos End token.Pos } type cgPos struct { left bool // true if comment is to the left of the spec, false otherwise. cg *CommentGroup } func sortSpecs(fset *token.FileSet, f *File, specs []Spec) []Spec { // Can't short-circuit here even if specs are already sorted, // since they might yet need deduplication. // A lone import, however, may be safely ignored. if len(specs) <= 1 { return specs } // Record positions for specs. pos := make([]posSpan, len(specs)) for i, s := range specs { pos[i] = posSpan{s.Pos(), s.End()} } // Identify comments in this range. begSpecs := pos[0].Start endSpecs := pos[len(pos)-1].End beg := fset.File(begSpecs).LineStart(lineAt(fset, begSpecs)) endLine := lineAt(fset, endSpecs) endFile := fset.File(endSpecs) var end token.Pos if endLine == endFile.LineCount() { end = endSpecs } else { end = endFile.LineStart(endLine + 1) // beginning of next line } first := len(f.Comments) last := -1 for i, g := range f.Comments { if g.End() >= end { break } // g.End() < end if beg <= g.Pos() { // comment is within the range [beg, end[ of import declarations if i < first { first = i } if i > last { last = i } } } var comments []*CommentGroup if last >= 0 { comments = f.Comments[first : last+1] } // Assign each comment to the import spec on the same line. importComments := map[*ImportSpec][]cgPos{} specIndex := 0 for _, g := range comments { for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() { specIndex++ } var left bool // A block comment can appear before the first import spec. if specIndex == 0 && pos[specIndex].Start > g.Pos() { left = true } else if specIndex+1 < len(specs) && // Or it can appear on the left of an import spec. lineAt(fset, pos[specIndex].Start)+1 == lineAt(fset, g.Pos()) { specIndex++ left = true } s := specs[specIndex].(*ImportSpec) importComments[s] = append(importComments[s], cgPos{left: left, cg: g}) } // Sort the import specs by import path. // Remove duplicates, when possible without data loss. // Reassign the import paths to have the same position sequence. // Reassign each comment to the spec on the same line. // Sort the comments by new position. sort.Slice(specs, func(i, j int) bool { ipath := importPath(specs[i]) jpath := importPath(specs[j]) if ipath != jpath { return ipath < jpath } iname := importName(specs[i]) jname := importName(specs[j]) if iname != jname { return iname < jname } return importComment(specs[i]) < importComment(specs[j]) }) // Dedup. Thanks to our sorting, we can just consider // adjacent pairs of imports. deduped := specs[:0] for i, s := range specs { if i == len(specs)-1 || !collapse(s, specs[i+1]) { deduped = append(deduped, s) } else { p := s.Pos() fset.File(p).MergeLine(lineAt(fset, p)) } } specs = deduped // Fix up comment positions for i, s := range specs { s := s.(*ImportSpec) if s.Name != nil { s.Name.NamePos = pos[i].Start } s.Path.ValuePos = pos[i].Start s.EndPos = pos[i].End for _, g := range importComments[s] { for _, c := range g.cg.List { if g.left { c.Slash = pos[i].Start - 1 } else { // An import spec can have both block comment and a line comment // to its right. In that case, both of them will have the same pos. // But while formatting the AST, the line comment gets moved to // after the block comment. c.Slash = pos[i].End } } } } sort.Slice(comments, func(i, j int) bool { return comments[i].Pos() < comments[j].Pos() }) return specs } ================================================ FILE: ast/mod/deps.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package mod import ( "strconv" "strings" goast "go/ast" gotoken "go/token" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/token" ) // ---------------------------------------------------------------------------- type Deps struct { HandlePkg func(pkgPath string) } func (p Deps) Load(pkg *ast.Package, withXgoStd bool) { for _, f := range pkg.Files { p.LoadFile(f, withXgoStd) } for _, f := range pkg.GoFiles { p.LoadGoFile(f) } } func (p Deps) LoadGoFile(f *goast.File) { for _, imp := range f.Imports { path := imp.Path if path.Kind == gotoken.STRING { if s, err := strconv.Unquote(path.Value); err == nil { if s == "C" { continue } p.HandlePkg(s) } } } } func (p Deps) LoadFile(f *ast.File, withXgoStd bool) { for _, imp := range f.Imports { path := imp.Path if path.Kind == token.STRING { if s, err := strconv.Unquote(path.Value); err == nil { p.xgoPkgPath(s, withXgoStd) } } } } func (p Deps) xgoPkgPath(s string, withXgoStd bool) { if strings.HasPrefix(s, "xgo/") || strings.HasPrefix(s, "gop/") { if !withXgoStd { return } s = "github.com/goplus/xgo/" + s[4:] } else if strings.HasPrefix(s, "C") { if len(s) == 1 { s = "github.com/goplus/libc" } else if s[1] == '/' { s = s[2:] if strings.IndexByte(s, '/') < 0 { s = "github.com/goplus/" + s } } } p.HandlePkg(s) } // ---------------------------------------------------------------------------- ================================================ FILE: ast/print.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // This file contains printing support for ASTs. package ast import ( "fmt" "io" "os" "reflect" "github.com/goplus/xgo/token" ) // A FieldFilter may be provided to Fprint to control the output. type FieldFilter func(name string, value reflect.Value) bool // NotNilFilter returns true for field values that are not nil; // it returns false otherwise. func NotNilFilter(_ string, v reflect.Value) bool { switch v.Kind() { case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: return !v.IsNil() } return true } // Fprint prints the (sub-)tree starting at AST node x to w. // If fset != nil, position information is interpreted relative // to that file set. Otherwise positions are printed as integer // values (file set specific offsets). // // A non-nil FieldFilter f may be provided to control the output: // struct fields for which f(fieldname, fieldvalue) is true are // printed; all others are filtered from the output. Unexported // struct fields are never printed. func Fprint(w io.Writer, fset *token.FileSet, x any, f FieldFilter) error { return fprint(w, fset, x, f) } func fprint(w io.Writer, fset *token.FileSet, x any, f FieldFilter) (err error) { // setup printer p := printer{ output: w, fset: fset, filter: f, ptrmap: make(map[any]int), last: '\n', // force printing of line number on first line } // install error handler defer func() { if e := recover(); e != nil { err = e.(localError).err // re-panics if it's not a localError } }() // print x if x == nil { p.printf("nil\n") return } p.print(reflect.ValueOf(x)) p.printf("\n") return } // Print prints x to standard output, skipping nil fields. // Print(fset, x) is the same as Fprint(os.Stdout, fset, x, NotNilFilter). func Print(fset *token.FileSet, x any) error { return Fprint(os.Stdout, fset, x, NotNilFilter) } type printer struct { output io.Writer fset *token.FileSet filter FieldFilter ptrmap map[any]int // *T -> line number indent int // current indentation level last byte // the last byte processed by Write line int // current line number } var indent = []byte(". ") func (p *printer) Write(data []byte) (n int, err error) { var m int for i, b := range data { // invariant: data[0:n] has been written if b == '\n' { m, err = p.output.Write(data[n : i+1]) n += m if err != nil { return } p.line++ } else if p.last == '\n' { _, err = fmt.Fprintf(p.output, "%6d ", p.line) if err != nil { return } for j := p.indent; j > 0; j-- { _, err = p.output.Write(indent) if err != nil { return } } } p.last = b } if len(data) > n { m, err = p.output.Write(data[n:]) n += m } return } // localError wraps locally caught errors so we can distinguish // them from genuine panics which we don't want to return as errors. type localError struct { err error } // printf is a convenience wrapper that takes care of print errors. func (p *printer) printf(format string, args ...any) { if _, err := fmt.Fprintf(p, format, args...); err != nil { panic(localError{err}) } } // Implementation note: Print is written for AST nodes but could be // used to print arbitrary data structures; such a version should // probably be in a different package. // // Note: This code detects (some) cycles created via pointers but // not cycles that are created via slices or maps containing the // same slice or map. Code for general data structures probably // should catch those as well. func (p *printer) print(x reflect.Value) { if !NotNilFilter("", x) { p.printf("nil") return } switch x.Kind() { case reflect.Interface: p.print(x.Elem()) case reflect.Map: p.printf("%s (len = %d) {", x.Type(), x.Len()) if x.Len() > 0 { p.indent++ p.printf("\n") for _, key := range x.MapKeys() { p.print(key) p.printf(": ") p.print(x.MapIndex(key)) p.printf("\n") } p.indent-- } p.printf("}") case reflect.Ptr: p.printf("*") // type-checked ASTs may contain cycles - use ptrmap // to keep track of objects that have been printed // already and print the respective line number instead ptr := x.Interface() if line, exists := p.ptrmap[ptr]; exists { p.printf("(obj @ %d)", line) } else { p.ptrmap[ptr] = p.line p.print(x.Elem()) } case reflect.Array: p.printf("%s {", x.Type()) if x.Len() > 0 { p.indent++ p.printf("\n") for i, n := 0, x.Len(); i < n; i++ { p.printf("%d: ", i) p.print(x.Index(i)) p.printf("\n") } p.indent-- } p.printf("}") case reflect.Slice: if s, ok := x.Interface().([]byte); ok { p.printf("%#q", s) return } p.printf("%s (len = %d) {", x.Type(), x.Len()) if x.Len() > 0 { p.indent++ p.printf("\n") for i, n := 0, x.Len(); i < n; i++ { p.printf("%d: ", i) p.print(x.Index(i)) p.printf("\n") } p.indent-- } p.printf("}") case reflect.Struct: t := x.Type() p.printf("%s {", t) p.indent++ first := true for i, n := 0, t.NumField(); i < n; i++ { // exclude non-exported fields because their // values cannot be accessed via reflection if name := t.Field(i).Name; IsExported(name) { value := x.Field(i) if p.filter == nil || p.filter(name, value) { if first { p.printf("\n") first = false } p.printf("%s: ", name) p.print(value) p.printf("\n") } } } p.indent-- p.printf("}") default: v := x.Interface() switch v := v.(type) { case string: // print strings in quotes p.printf("%q", v) return case token.Pos: // position values can be printed nicely if we have a file set if p.fset != nil { p.printf("%s", p.fset.Position(v)) return } } // default p.printf("%v", v) } } ================================================ FILE: ast/resolve.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // This file implements NewPackage. package ast /* TODO(xsw): remove import ( "fmt" "strconv" "github.com/goplus/xgo/scanner" "github.com/goplus/xgo/token" ) type pkgBuilder struct { fset *token.FileSet errors scanner.ErrorList } func (p *pkgBuilder) error(pos token.Pos, msg string) { p.errors.Add(p.fset.Position(pos), msg) } func (p *pkgBuilder) errorf(pos token.Pos, format string, args ...any) { p.error(pos, fmt.Sprintf(format, args...)) } func (p *pkgBuilder) declare(scope, altScope *Scope, obj *Object) { alt := scope.Insert(obj) if alt == nil && altScope != nil { // see if there is a conflicting declaration in altScope alt = altScope.Lookup(obj.Name) } if alt != nil { prevDecl := "" if pos := alt.Pos(); pos.IsValid() { prevDecl = fmt.Sprintf("\n\tprevious declaration at %s", p.fset.Position(pos)) } p.error(obj.Pos(), fmt.Sprintf("%s redeclared in this block%s", obj.Name, prevDecl)) } } func resolve(scope *Scope, ident *Ident) bool { for ; scope != nil; scope = scope.Outer { if obj := scope.Lookup(ident.Name); obj != nil { ident.Obj = obj return true } } return false } // An Importer resolves import paths to package Objects. // The imports map records the packages already imported, // indexed by package id (canonical import path). // An Importer must determine the canonical import path and // check the map to see if it is already present in the imports map. // If so, the Importer can return the map entry. Otherwise, the // Importer should load the package data for the given path into // a new *Object (pkg), record pkg in the imports map, and then // return pkg. type Importer func(imports map[string]*Object, path string) (pkg *Object, err error) // NewPackage creates a new Package node from a set of File nodes. It resolves // unresolved identifiers across files and updates each file's Unresolved list // accordingly. If a non-nil importer and universe scope are provided, they are // used to resolve identifiers not declared in any of the package files. Any // remaining unresolved identifiers are reported as undeclared. If the files // belong to different packages, one package name is selected and files with // different package names are reported and then ignored. // The result is a package node and a scanner.ErrorList if there were errors. func NewPackage(fset *token.FileSet, files map[string]*File, importer Importer, universe *Scope) (*Package, error) { var p pkgBuilder p.fset = fset // complete package scope pkgName := "" pkgScope := NewScope(universe) for _, file := range files { // package names must match switch name := file.Name.Name; { case pkgName == "": pkgName = name case name != pkgName: p.errorf(file.Package, "package %s; expected %s", name, pkgName) continue // ignore this file } // collect top-level file objects in package scope for _, obj := range file.Scope.Objects { p.declare(pkgScope, nil, obj) } } // package global mapping of imported package ids to package objects imports := make(map[string]*Object) // complete file scopes with imports and resolve identifiers for _, file := range files { // ignore file if it belongs to a different package // (error has already been reported) if file.Name.Name != pkgName { continue } // build file scope by processing all imports importErrors := false fileScope := NewScope(pkgScope) for _, spec := range file.Imports { if importer == nil { importErrors = true continue } path, _ := strconv.Unquote(spec.Path.Value) pkg, err := importer(imports, path) if err != nil { p.errorf(spec.Path.Pos(), "could not import %s (%s)", path, err) importErrors = true continue } // TODO(gri) If a local package name != "." is provided, // global identifier resolution could proceed even if the // import failed. Consider adjusting the logic here a bit. // local name overrides imported package name name := pkg.Name if spec.Name != nil { name = spec.Name.Name } // add import to file scope if name == "." { // merge imported scope with file scope for _, obj := range pkg.Data.(*Scope).Objects { p.declare(fileScope, pkgScope, obj) } } else if name != "_" { // declare imported package object in file scope // (do not re-use pkg in the file scope but create // a new object instead; the Decl field is different // for different files) obj := NewObj(Pkg, name) obj.Decl = spec obj.Data = pkg.Data p.declare(fileScope, pkgScope, obj) } } // resolve identifiers if importErrors { // don't use the universe scope without correct imports // (objects in the universe may be shadowed by imports; // with missing imports, identifiers might get resolved // incorrectly to universe objects) pkgScope.Outer = nil } i := 0 for _, ident := range file.Unresolved { if !resolve(fileScope, ident) { p.errorf(ident.Pos(), "undeclared name: %s", ident.Name) file.Unresolved[i] = ident i++ } } file.Unresolved = file.Unresolved[0:i] pkgScope.Outer = universe // reset universe scope } p.errors.Sort() return &Package{pkgName, pkgScope, imports, files, nil}, p.errors.Err() } */ ================================================ FILE: ast/scope.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // This file implements scopes and the objects they contain. package ast import ( "bytes" "fmt" "github.com/goplus/xgo/token" ) // A Scope maintains the set of named language entities declared // in the scope and a link to the immediately surrounding (outer) // scope. type Scope struct { Outer *Scope Objects map[string]*Object } // NewScope creates a new scope nested in the outer scope. func NewScope(outer *Scope) *Scope { const n = 4 // initial scope capacity return &Scope{outer, make(map[string]*Object, n)} } // Lookup returns the object with the given name if it is // found in scope s, otherwise it returns nil. Outer scopes // are ignored. func (s *Scope) Lookup(name string) *Object { return s.Objects[name] } // Insert attempts to insert a named object obj into the scope s. // If the scope already contains an object alt with the same name, // Insert leaves the scope unchanged and returns alt. Otherwise // it inserts obj and returns nil. func (s *Scope) Insert(obj *Object) (alt *Object) { if alt = s.Objects[obj.Name]; alt == nil { s.Objects[obj.Name] = obj } return } // Debugging support func (s *Scope) String() string { var buf bytes.Buffer fmt.Fprintf(&buf, "scope %p {", s) if s != nil && len(s.Objects) > 0 { fmt.Fprintln(&buf) for _, obj := range s.Objects { fmt.Fprintf(&buf, "\t%s %s\n", obj.Kind, obj.Name) } } fmt.Fprintf(&buf, "}\n") return buf.String() } // ---------------------------------------------------------------------------- // Objects // An Object describes a named language entity such as a package, // constant, type, variable, function (incl. methods), or label. // // The Data fields contains object-specific data: // // Kind Data type Data value // Pkg *Scope package scope // Con int iota for the respective declaration type Object struct { Kind ObjKind Name string // declared name Decl any // corresponding Field, XxxSpec, FuncDecl, LabeledStmt, AssignStmt, Scope; or nil Data any // object-specific data; or nil Type any // placeholder for type information; may be nil } // NewObj creates a new object of a given kind and name. func NewObj(kind ObjKind, name string) *Object { return &Object{Kind: kind, Name: name} } // Pos computes the source position of the declaration of an object name. // The result may be an invalid position if it cannot be computed // (obj.Decl may be nil or not correct). func (obj *Object) Pos() token.Pos { name := obj.Name switch d := obj.Decl.(type) { case *Field: for _, n := range d.Names { if n.Name == name { return n.Pos() } } case *ImportSpec: if d.Name != nil && d.Name.Name == name { return d.Name.Pos() } return d.Path.Pos() case *ValueSpec: for _, n := range d.Names { if n.Name == name { return n.Pos() } } case *TypeSpec: if d.Name.Name == name { return d.Name.Pos() } case *FuncDecl: if d.Name.Name == name { return d.Name.Pos() } case *LabeledStmt: if d.Label.Name == name { return d.Label.Pos() } case *AssignStmt: for _, x := range d.Lhs { if ident, isIdent := x.(*Ident); isIdent && ident.Name == name { return ident.Pos() } } case *Scope: // predeclared object - nothing to do for now } return token.NoPos } // ObjKind describes what an object represents. type ObjKind int // The list of possible Object kinds. const ( Bad ObjKind = iota // for error handling Pkg // package Con // constant Typ // type Var // variable Fun // function or method Lbl // label implicitBase ImplicitPkg = ObjKind(implicitBase) ImplicitFun = ObjKind(implicitBase + 1) ) var objKindStrings = [...]string{ Bad: "bad", Pkg: "package", Con: "const", Typ: "type", Var: "var", Fun: "func", Lbl: "label", ImplicitPkg: "package", ImplicitFun: "func", } func (kind ObjKind) String() string { return objKindStrings[kind] } ================================================ FILE: ast/togo/goast.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package togo import ( "go/ast" "go/token" "log" "reflect" gopast "github.com/goplus/xgo/ast" goptoken "github.com/goplus/xgo/token" ) // ---------------------------------------------------------------------------- func goExpr(val gopast.Expr) ast.Expr { if val == nil { return nil } switch v := val.(type) { case *gopast.Ident: return goIdent(v) case *gopast.SelectorExpr: return &ast.SelectorExpr{ X: goExpr(v.X), Sel: goIdent(v.Sel), } case *gopast.SliceExpr: return &ast.SliceExpr{ X: goExpr(v.X), Lbrack: v.Lbrack, Low: goExpr(v.Low), High: goExpr(v.High), Max: goExpr(v.Max), Slice3: v.Slice3, Rbrack: v.Rbrack, } case *gopast.StarExpr: return &ast.StarExpr{ Star: v.Star, X: goExpr(v.X), } case *gopast.MapType: return &ast.MapType{ Map: v.Map, Key: goType(v.Key), Value: goType(v.Value), } case *gopast.StructType: return &ast.StructType{ Struct: v.Struct, Fields: goFieldList(v.Fields), } case *gopast.FuncType: return goFuncType(v) case *gopast.InterfaceType: return &ast.InterfaceType{ Interface: v.Interface, Methods: goFieldList(v.Methods), } case *gopast.ArrayType: return &ast.ArrayType{ Lbrack: v.Lbrack, Len: goExpr(v.Len), Elt: goType(v.Elt), } case *gopast.ChanType: return &ast.ChanType{ Begin: v.Begin, Arrow: v.Arrow, Dir: ast.ChanDir(v.Dir), Value: goType(v.Value), } case *gopast.BasicLit: return goBasicLit(v) case *gopast.BinaryExpr: return &ast.BinaryExpr{ X: goExpr(v.X), OpPos: v.OpPos, Op: token.Token(v.Op), Y: goExpr(v.Y), } case *gopast.UnaryExpr: return &ast.UnaryExpr{ OpPos: v.OpPos, Op: token.Token(v.Op), X: goExpr(v.X), } case *gopast.CallExpr: return &ast.CallExpr{ Fun: goExpr(v.Fun), Lparen: v.Lparen, Args: goExprs(v.Args), Ellipsis: v.Ellipsis, Rparen: v.Rparen, } case *gopast.IndexExpr: return &ast.IndexExpr{ X: goExpr(v.X), Lbrack: v.Lbrack, Index: goExpr(v.Index), Rbrack: v.Rbrack, } case *gopast.ParenExpr: return &ast.ParenExpr{ Lparen: v.Lparen, X: goExpr(v.X), Rparen: v.Rparen, } case *gopast.CompositeLit: return &ast.CompositeLit{ Type: goType(v.Type), Lbrace: v.Lbrace, Elts: goExprs(v.Elts), Rbrace: v.Rbrace, } case *gopast.FuncLit: return &ast.FuncLit{ Type: goFuncType(v.Type), Body: &ast.BlockStmt{}, // skip closure body } case *gopast.TypeAssertExpr: return &ast.TypeAssertExpr{ X: goExpr(v.X), Lparen: v.Lparen, Type: goType(v.Type), Rparen: v.Rparen, } case *gopast.KeyValueExpr: return &ast.KeyValueExpr{ Key: goExpr(v.Key), Colon: v.Colon, Value: goExpr(v.Value), } case *gopast.Ellipsis: return &ast.Ellipsis{ Ellipsis: v.Ellipsis, Elt: goExpr(v.Elt), } } log.Panicln("goExpr: unknown expr -", reflect.TypeOf(val)) return nil } func goExprs(vals []gopast.Expr) []ast.Expr { n := len(vals) if n == 0 { return nil } ret := make([]ast.Expr, n) for i, v := range vals { ret[i] = goExpr(v) } return ret } // ---------------------------------------------------------------------------- func goFuncType(v *gopast.FuncType) *ast.FuncType { return &ast.FuncType{ Func: v.Func, Params: goFieldList(v.Params), Results: goFieldList(v.Results), } } func goType(v gopast.Expr) ast.Expr { return goExpr(v) } func goBasicLit(v *gopast.BasicLit) *ast.BasicLit { if v == nil { return nil } return &ast.BasicLit{ ValuePos: v.ValuePos, Kind: token.Token(v.Kind), Value: v.Value, } } func goIdent(v *gopast.Ident) *ast.Ident { if v == nil { return nil } return &ast.Ident{ NamePos: v.NamePos, Name: v.Name, } } func goIdents(names []*gopast.Ident) []*ast.Ident { ret := make([]*ast.Ident, len(names)) for i, v := range names { ret[i] = goIdent(v) } return ret } // ---------------------------------------------------------------------------- func goField(v *gopast.Field) *ast.Field { return &ast.Field{ Names: goIdents(v.Names), Type: goType(v.Type), Tag: goBasicLit(v.Tag), } } func goFieldList(v *gopast.FieldList) *ast.FieldList { if v == nil { return nil } list := make([]*ast.Field, len(v.List)) for i, item := range v.List { list[i] = goField(item) } return &ast.FieldList{Opening: v.Opening, List: list, Closing: v.Closing} } func goFuncDecl(v *gopast.FuncDecl) *ast.FuncDecl { return &ast.FuncDecl{ Recv: goFieldList(v.Recv), Name: goIdent(v.Name), Type: goFuncType(v.Type), Body: &ast.BlockStmt{}, // ignore function body } } // ---------------------------------------------------------------------------- func goImportSpec(spec *gopast.ImportSpec) *ast.ImportSpec { return &ast.ImportSpec{ Name: goIdent(spec.Name), Path: goBasicLit(spec.Path), EndPos: spec.EndPos, } } func goTypeSpec(spec *gopast.TypeSpec) *ast.TypeSpec { return &ast.TypeSpec{ Name: goIdent(spec.Name), Assign: spec.Assign, Type: goType(spec.Type), } } func goValueSpec(spec *gopast.ValueSpec) *ast.ValueSpec { return &ast.ValueSpec{ Names: goIdents(spec.Names), Type: goType(spec.Type), Values: goExprs(spec.Values), } } func goGenDecl(v *gopast.GenDecl) *ast.GenDecl { specs := make([]ast.Spec, len(v.Specs)) for i, spec := range v.Specs { switch v.Tok { case goptoken.IMPORT: specs[i] = goImportSpec(spec.(*gopast.ImportSpec)) case goptoken.TYPE: specs[i] = goTypeSpec(spec.(*gopast.TypeSpec)) case goptoken.VAR, goptoken.CONST: specs[i] = goValueSpec(spec.(*gopast.ValueSpec)) default: log.Panicln("goGenDecl: unknown spec -", v.Tok) } } return &ast.GenDecl{ TokPos: v.TokPos, Tok: token.Token(v.Tok), Lparen: v.Lparen, Specs: specs, Rparen: v.Rparen, } } // ---------------------------------------------------------------------------- func goDecl(decl gopast.Decl) ast.Decl { switch v := decl.(type) { case *gopast.GenDecl: return goGenDecl(v) case *gopast.FuncDecl: return goFuncDecl(v) } log.Panicln("goDecl: unknown decl -", reflect.TypeOf(decl)) return nil } func goDecls(decls []gopast.Decl) []ast.Decl { ret := make([]ast.Decl, len(decls)) for i, decl := range decls { ret[i] = goDecl(decl) } return ret } // ---------------------------------------------------------------------------- const ( KeepFuncBody = 1 << iota ) func ASTFile(f *gopast.File, mode int) *ast.File { if (mode & KeepFuncBody) != 0 { log.Panicln("ASTFile: doesn't support keeping func body now") } return &ast.File{ Package: f.Package, Name: goIdent(f.Name), Decls: goDecls(f.Decls), } } // ---------------------------------------------------------------------------- ================================================ FILE: ast/togo/goast_test.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package togo import ( "bytes" "go/format" "go/token" "testing" gopast "github.com/goplus/xgo/ast" "github.com/goplus/xgo/parser" ) func testAST(t *testing.T, from, to string) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, "foo.go", from, 0) if err != nil { t.Fatal("parser.ParseFile:", err) } gopf := ASTFile(f, 0) var b bytes.Buffer err = format.Node(&b, fset, gopf) if err != nil { t.Fatal("format.Node:", err) } result := b.String() if to != result { t.Fatalf("\nResult:\n%s\nExpected:\n%s\n", result, to) } } func test(t *testing.T, src string) { testAST(t, src, src) } func testPanic(t *testing.T, panicMsg string, doPanic func()) { t.Run(panicMsg, func(t *testing.T) { defer func() { if e := recover(); e == nil { t.Fatal("testPanic: no error?") } else if msg := e.(string); msg != panicMsg { t.Fatalf("\nResult:\n%s\nExpected Panic:\n%s\n", msg, panicMsg) } }() doPanic() }) } func TestErrASTFile(t *testing.T) { testPanic(t, "ASTFile: doesn't support keeping func body now\n", func() { ASTFile(nil, KeepFuncBody) }) } func TestErrDecl(t *testing.T) { testPanic(t, "goDecl: unknown decl - \n", func() { goDecl(nil) }) testPanic(t, "goGenDecl: unknown spec - ILLEGAL\n", func() { goGenDecl(&gopast.GenDecl{ Specs: []gopast.Spec{nil}, }) }) } func TestErrExpr(t *testing.T) { testPanic(t, "goExpr: unknown expr - *ast.BadExpr\n", func() { goExpr(&gopast.BadExpr{}) }) } func TestBasic(t *testing.T) { test(t, `package main import "fmt" type a struct { v map[int]chan int arr *[2]func() i interface{} } var b = &a{ arr: &[2]func(){ nil, func() {}, }, } const c = (10 + 20) * 2 var d = b.arr[1] var e = b.arr[:1] var f = a.i.(func() (int))() func foo(v ...interface{}) {} `) } ================================================ FILE: ast/walk.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ast import ( "fmt" ) // Visitor - A Visitor's Visit method is invoked for each node encountered by Walk. // If the result visitor w is not nil, Walk visits each of the children // of node with the visitor w, followed by a call of w.Visit(nil). type Visitor interface { Visit(node Node) (w Visitor) } func walkList[N Node](v Visitor, list []N) { for _, node := range list { Walk(v, node) } } // TODO(gri): Investigate if providing a closure to Walk leads to // simpler use (and may help eliminate Inspect in turn). // Walk traverses an AST in depth-first order: It starts by calling // v.Visit(node); node must not be nil. If the visitor w returned by // v.Visit(node) is not nil, Walk is invoked recursively with visitor // w for each of the non-nil children of node, followed by a call of // w.Visit(nil). func Walk(v Visitor, node Node) { if v = v.Visit(node); v == nil { return } // walk children // (the order of the cases matches the order // of the corresponding node types in ast.go) switch n := node.(type) { // Comments and fields case *Comment: // nothing to do case *CommentGroup: walkList(v, n.List) case *Field: if n.Doc != nil { Walk(v, n.Doc) } walkList(v, n.Names) if n.Type != nil { Walk(v, n.Type) } if n.Tag != nil { Walk(v, n.Tag) } if n.Comment != nil { Walk(v, n.Comment) } case *FieldList: walkList(v, n.List) // Expressions case *BadExpr, *Ident, *NumberUnitLit: // nothing to do case *BasicLit: if n.Extra != nil { // XGo extended for _, part := range n.Extra.Parts { if e, ok := part.(Expr); ok { Walk(v, e) } } } case *DomainTextLit: Walk(v, n.Domain) if e := n.Extra; e != nil { switch e := e.(type) { case *StringLitEx: for _, part := range e.Parts { if e, ok := part.(Expr); ok { Walk(v, e) } } } } case *Ellipsis: if n.Elt != nil { Walk(v, n.Elt) } case *FuncLit: Walk(v, n.Type) Walk(v, n.Body) case *CompositeLit: if n.Type != nil { Walk(v, n.Type) } walkList(v, n.Elts) case *ParenExpr: Walk(v, n.X) case *SelectorExpr: Walk(v, n.X) Walk(v, n.Sel) case *IndexExpr: Walk(v, n.X) Walk(v, n.Index) case *IndexListExpr: Walk(v, n.X) walkList(v, n.Indices) case *SliceExpr: Walk(v, n.X) if n.Low != nil { Walk(v, n.Low) } if n.High != nil { Walk(v, n.High) } if n.Max != nil { Walk(v, n.Max) } case *TypeAssertExpr: Walk(v, n.X) if n.Type != nil { Walk(v, n.Type) } case *CallExpr: Walk(v, n.Fun) walkList(v, n.Args) walkList(v, n.Kwargs) case *StarExpr: Walk(v, n.X) case *UnaryExpr: Walk(v, n.X) case *BinaryExpr: Walk(v, n.X) Walk(v, n.Y) case *KwargExpr: Walk(v, n.Name) Walk(v, n.Value) case *KeyValueExpr: Walk(v, n.Key) Walk(v, n.Value) // Types case *ArrayType: if n.Len != nil { Walk(v, n.Len) } Walk(v, n.Elt) case *StructType: Walk(v, n.Fields) case *FuncType: if n.TypeParams != nil { Walk(v, n.TypeParams) } if n.Params != nil { Walk(v, n.Params) } if n.Results != nil { Walk(v, n.Results) } case *InterfaceType: Walk(v, n.Methods) case *MapType: Walk(v, n.Key) Walk(v, n.Value) case *ChanType: Walk(v, n.Value) case *TupleType: if n.Fields != nil { Walk(v, n.Fields) } // Statements case *BadStmt: // nothing to do case *DeclStmt: Walk(v, n.Decl) case *EmptyStmt: // nothing to do case *LabeledStmt: Walk(v, n.Label) Walk(v, n.Stmt) case *ExprStmt: Walk(v, n.X) case *SendStmt: Walk(v, n.Chan) walkList(v, n.Values) case *IncDecStmt: Walk(v, n.X) case *AssignStmt: walkList(v, n.Lhs) walkList(v, n.Rhs) case *GoStmt: Walk(v, n.Call) case *DeferStmt: Walk(v, n.Call) case *ReturnStmt: walkList(v, n.Results) case *BranchStmt: if n.Label != nil { Walk(v, n.Label) } case *BlockStmt: walkList(v, n.List) case *IfStmt: if n.Init != nil { Walk(v, n.Init) } Walk(v, n.Cond) Walk(v, n.Body) if n.Else != nil { Walk(v, n.Else) } case *CaseClause: walkList(v, n.List) walkList(v, n.Body) case *SwitchStmt: if n.Init != nil { Walk(v, n.Init) } if n.Tag != nil { Walk(v, n.Tag) } Walk(v, n.Body) case *TypeSwitchStmt: if n.Init != nil { Walk(v, n.Init) } Walk(v, n.Assign) Walk(v, n.Body) case *CommClause: if n.Comm != nil { Walk(v, n.Comm) } walkList(v, n.Body) case *SelectStmt: Walk(v, n.Body) case *ForStmt: if n.Init != nil { Walk(v, n.Init) } if n.Cond != nil { Walk(v, n.Cond) } if n.Post != nil { Walk(v, n.Post) } Walk(v, n.Body) case *RangeStmt: if n.Key != nil { Walk(v, n.Key) } if n.Value != nil { Walk(v, n.Value) } Walk(v, n.X) Walk(v, n.Body) // Declarations case *ImportSpec: if n.Doc != nil { Walk(v, n.Doc) } if n.Name != nil { Walk(v, n.Name) } Walk(v, n.Path) if n.Comment != nil { Walk(v, n.Comment) } case *ValueSpec: if n.Doc != nil { Walk(v, n.Doc) } walkList(v, n.Names) if n.Type != nil { Walk(v, n.Type) } walkList(v, n.Values) if n.Comment != nil { Walk(v, n.Comment) } case *TypeSpec: if n.Doc != nil { Walk(v, n.Doc) } Walk(v, n.Name) if n.TypeParams != nil { Walk(v, n.TypeParams) } Walk(v, n.Type) if n.Comment != nil { Walk(v, n.Comment) } case *BadDecl: // nothing to do case *GenDecl: if n.Doc != nil { Walk(v, n.Doc) } walkList(v, n.Specs) case *FuncDecl: if !n.Shadow { // not a shadow entry if n.Doc != nil { Walk(v, n.Doc) } if n.Recv != nil { Walk(v, n.Recv) } Walk(v, n.Name) Walk(v, n.Type) } if n.Body != nil { Walk(v, n.Body) } // Files and packages case *File: if n.Doc != nil { Walk(v, n.Doc) } if !n.NoPkgDecl { Walk(v, n.Name) } walkList(v, n.Decls) // don't walk n.Comments - they have been // visited already through the individual // nodes case *Package: for _, f := range n.Files { Walk(v, f) } // XGo extended expr and stmt case *SliceLit: walkList(v, n.Elts) case *TupleLit: walkList(v, n.Elts) case *LambdaExpr: walkList(v, n.Lhs) walkList(v, n.Rhs) case *LambdaExpr2: walkList(v, n.Lhs) Walk(v, n.Body) case *ForPhrase: if n.Key != nil { Walk(v, n.Key) } if n.Value != nil { Walk(v, n.Value) } if n.Init != nil { Walk(v, n.Init) } if n.Cond != nil { Walk(v, n.Cond) } Walk(v, n.X) case *ComprehensionExpr: if n.Elt != nil { Walk(v, n.Elt) } walkList(v, n.Fors) case *ForPhraseStmt: Walk(v, n.ForPhrase) Walk(v, n.Body) case *RangeExpr: if n.First != nil { Walk(v, n.First) } if n.Last != nil { Walk(v, n.Last) } if n.Expr3 != nil { Walk(v, n.Expr3) } case *ErrWrapExpr: Walk(v, n.X) if n.Default != nil { Walk(v, n.Default) } case *OverloadFuncDecl: if n.Doc != nil { Walk(v, n.Doc) } if n.Recv != nil { Walk(v, n.Recv) } Walk(v, n.Name) walkList(v, n.Funcs) case *EnvExpr: Walk(v, n.Name) case *AnySelectorExpr: Walk(v, n.X) Walk(v, n.Sel) case *CondExpr: Walk(v, n.X) Walk(v, n.Cond) default: panic(fmt.Sprintf("ast.Walk: unexpected node type %T", n)) } v.Visit(nil) } type inspector func(Node) bool func (f inspector) Visit(node Node) Visitor { if f(node) { return f } return nil } // Inspect traverses an AST in depth-first order: It starts by calling // f(node); node must not be nil. If f returns true, Inspect invokes f // recursively for each of the non-nil children of node, followed by a // call of f(nil). func Inspect(node Node, f func(Node) bool) { Walk(inspector(f), node) } ================================================ FILE: builtin/doc.xgo ================================================ package builtin import ( "github.com/qiniu/x/osx" "io" "os" "reflect" ) // Echo formats using the default formats for its operands and writes to standard output. // Spaces are always added between operands and a newline is appended. // It returns the number of bytes written and any write error encountered. func Echo(a ...any) (n int, err error) // Blines returns a BLineReader that reads lines from the given reader. func Blines(r io.Reader) osx.BLineReader // Type returns the reflection [Type] that represents the dynamic type of i. // If i is a nil interface value, Type returns nil. func Type(i any) reflect.Type // Print formats using the default formats for its operands and writes to standard output. // Spaces are added between operands when neither is a string. // It returns the number of bytes written and any write error encountered. func Print(a ...any) (n int, err error) // Println formats using the default formats for its operands and writes to standard output. // Spaces are always added between operands and a newline is appended. // It returns the number of bytes written and any write error encountered. func Println(a ...any) (n int, err error) // Printf formats according to a format specifier and writes to standard output. // It returns the number of bytes written and any write error encountered. func Printf(format string, a ...any) (n int, err error) // Errorf formats according to a format specifier and returns the string as a // value that satisfies error. // // If the format specifier includes a %w verb with an error operand, // the returned error will implement an Unwrap method returning the operand. // If there is more than one %w verb, the returned error will implement an // Unwrap method returning a []error containing all the %w operands in the // order they appear in the arguments. // It is invalid to supply the %w verb with an operand that does not implement // the error interface. The %w verb is otherwise a synonym for %v. func Errorf(format string, a ...any) error // Fprint formats using the default formats for its operands and writes to w. // Spaces are added between operands when neither is a string. // It returns the number of bytes written and any write error encountered. func Fprint(w io.Writer, a ...any) (n int, err error) // Fprintln formats using the default formats for its operands and writes to w. // Spaces are always added between operands and a newline is appended. // It returns the number of bytes written and any write error encountered. func Fprintln(w io.Writer, a ...any) (n int, err error) // Fprintf formats according to a format specifier and writes to w. // It returns the number of bytes written and any write error encountered. func Fprintf(w io.Writer, format string, a ...any) (n int, err error) // Sprint formats using the default formats for its operands and returns the resulting string. // Spaces are added between operands when neither is a string. func Sprint(a ...any) string // Sprintln formats using the default formats for its operands and returns the resulting string. // Spaces are always added between operands and a newline is appended. func Sprintln(a ...any) string // Sprintf formats according to a format specifier and returns the resulting string. func Sprintf(format string, a ...any) string // Open opens the named file in the root for reading. // See [Open] for more details. func Open(name string) (*os.File, error) // Create creates or truncates the named file in the root. // See [Create] for more details. func Create(name string) (*os.File, error) // Lines returns a LineReader that reads lines from the given reader. func Lines(r io.Reader) osx.LineReader // Errorln formats and prints to standard error. func Errorln(args ...any) // Fatal is equivalent to Errorln followed by os.Exit(1). func Fatal(args ...any) // XGo_Enum returns a LineIter that reads lines from the given reader. func (r io.Reader) XGo_Enum() osx.LineIter // String is equivalent to strconv.FormatFloat(f, 'g', -1, 64) // // FormatFloat converts the floating-point number f to a string, // according to the format fmt and precision prec. It rounds the // result assuming that the original was obtained from a floating-point // value of bitSize bits (32 for float32, 64 for float64). // // The format fmt is one of // - 'b' (-ddddp±ddd, a binary exponent), // - 'e' (-d.dddde±dd, a decimal exponent), // - 'E' (-d.ddddE±dd, a decimal exponent), // - 'f' (-ddd.dddd, no exponent), // - 'g' ('e' for large exponents, 'f' otherwise), // - 'G' ('E' for large exponents, 'f' otherwise), // - 'x' (-0xd.ddddp±ddd, a hexadecimal fraction and binary exponent), or // - 'X' (-0Xd.ddddP±ddd, a hexadecimal fraction and binary exponent). // // The precision prec controls the number of digits (excluding the exponent) // printed by the 'e', 'E', 'f', 'g', 'G', 'x', and 'X' formats. // For 'e', 'E', 'f', 'x', and 'X', it is the number of digits after the decimal point. // For 'g' and 'G' it is the maximum number of significant digits (trailing // zeros are removed). // The special precision -1 uses the smallest number of digits // necessary such that ParseFloat will return f exactly. // The exponent is written as a decimal integer; // for all formats other than 'b', it will be at least two digits. func (f float64) String() string // String is equivalent to [FormatInt](int64(i), 10). func (i int) String() string // String is equivalent to strconv.FormatInt(i, 10) // // FormatInt returns the string representation of i in the given base, // for 2 <= base <= 36. The result uses the lower-case letters 'a' to 'z' // for digit values >= 10. func (i int64) String() string // String is equivalent to strconv.FormatUint(u, 10) // // FormatUint returns the string representation of i in the given base, // for 2 <= base <= 36. The result uses the lower-case letters 'a' to 'z' // for digit values >= 10. func (u uint64) String() string // The len built-in function returns the length of v, according to its type: // // - Array: the number of elements in v. // - Pointer to array: the number of elements in *v (even if v is nil). // - Slice, or map: the number of elements in v; if v is nil, len(v) is zero. // - String: the number of bytes in v. // - Channel: the number of elements queued (unread) in the channel buffer; // if v is nil, len(v) is zero. // // For some arguments, such as a string literal or a simple array expression, the // result can be a constant. See the Go language specification's "Length and // capacity" section for details. func (s string) Len() int // Count counts the number of non-overlapping instances of substr in s. // If substr is an empty string, Count returns 1 + the number of Unicode code points in s. func (s string) Count(substr string) int // Int is equivalent to ParseInt(s, 10, 0), converted to type int. func (s string) Int() (int, error) // Int64 is equivalent to strconv.ParseInt(s, 10, 64) // // ParseInt interprets a string s in the given base (0, 2 to 36) and // bit size (0 to 64) and returns the corresponding value i. // // The string may begin with a leading sign: "+" or "-". // // If the base argument is 0, the true base is implied by the string's // prefix following the sign (if present): 2 for "0b", 8 for "0" or "0o", // 16 for "0x", and 10 otherwise. Also, for argument base 0 only, // underscore characters are permitted as defined by the Go syntax for // [integer literals]. // // The bitSize argument specifies the integer type // that the result must fit into. Bit sizes 0, 8, 16, 32, and 64 // correspond to int, int8, int16, int32, and int64. // If bitSize is below 0 or above 64, an error is returned. // // The errors that ParseInt returns have concrete type [*NumError] // and include err.Num = s. If s is empty or contains invalid // digits, err.Err = [ErrSyntax] and the returned value is 0; // if the value corresponding to s cannot be represented by a // signed integer of the given size, err.Err = [ErrRange] and the // returned value is the maximum magnitude integer of the // appropriate bitSize and sign. // // [integer literals]: https://go.dev/ref/spec#Integer_literals func (s string) Int64() (i int64, err error) // Uint64 is equivalent to strconv.ParseUint(s, 10, 64) // // ParseUint is like [ParseInt] but for unsigned numbers. // // A sign prefix is not permitted. func (s string) Uint64() (uint64, error) // Float is equivalent to strconv.ParseFloat(s, 64) // // ParseFloat converts the string s to a floating-point number // with the precision specified by bitSize: 32 for float32, or 64 for float64. // When bitSize=32, the result still has type float64, but it will be // convertible to float32 without changing its value. // // ParseFloat accepts decimal and hexadecimal floating-point numbers // as defined by the Go syntax for [floating-point literals]. // If s is well-formed and near a valid floating-point number, // ParseFloat returns the nearest floating-point number rounded // using IEEE754 unbiased rounding. // (Parsing a hexadecimal floating-point value only rounds when // there are more bits in the hexadecimal representation than // will fit in the mantissa.) // // The errors that ParseFloat returns have concrete type *NumError // and include err.Num = s. // // If s is not syntactically well-formed, ParseFloat returns err.Err = ErrSyntax. // // If s is syntactically well-formed but is more than 1/2 ULP // away from the largest floating point number of the given size, // ParseFloat returns f = ±Inf, err.Err = ErrRange. // // ParseFloat recognizes the string "NaN", and the (possibly signed) strings "Inf" and "Infinity" // as their respective special floating point values. It ignores case when matching. // // [floating-point literals]: https://go.dev/ref/spec#Floating-point_literals func (s string) Float() (float64, error) // Index returns the index of the first instance of substr in s, or -1 if substr is not present in s. func (s string) Index(substr string) int // IndexAny returns the index of the first instance of any Unicode code point // from chars in s, or -1 if no Unicode code point from chars is present in s. func (s string) IndexAny(chars string) int // IndexByte returns the index of the first instance of c in s, or -1 if c is not present in s. func (s string) IndexByte(c byte) int // IndexRune returns the index of the first instance of the Unicode code point // r, or -1 if rune is not present in s. // If r is [utf8.RuneError], it returns the first instance of any // invalid UTF-8 byte sequence. func (s string) IndexRune(r rune) int // LastIndex returns the index of the last instance of substr in s, or -1 if substr is not present in s. func (s string) LastIndex(substr string) int // LastIndexAny returns the index of the last instance of any Unicode code // point from chars in s, or -1 if no Unicode code point from chars is // present in s. func (s string) LastIndexAny(chars string) int // LastIndexByte returns the index of the last instance of c in s, or -1 if c is not present in s. func (s string) LastIndexByte(c byte) int // Contains reports whether substr is within s. func (s string) Contains(substr string) bool // ContainsAny reports whether any Unicode code points in chars are within s. func (s string) ContainsAny(chars string) bool // ContainsRune reports whether the Unicode code point r is within s. func (s string) ContainsRune(r rune) bool // Compare returns an integer comparing two strings lexicographically. // The result will be 0 if a == b, -1 if a < b, and +1 if a > b. // // Use Compare when you need to perform a three-way comparison (with // [slices.SortFunc], for example). It is usually clearer and always faster // to use the built-in string comparison operators ==, <, >, and so on. func (s string) Compare(b string) int // EqualFold reports whether s and t, interpreted as UTF-8 strings, // are equal under simple Unicode case-folding, which is a more general // form of case-insensitivity. func (s string) EqualFold(t string) bool // HasPrefix reports whether the string s begins with prefix. func (s string) HasPrefix(prefix string) bool // HasSuffix reports whether the string s ends with suffix. func (s string) HasSuffix(suffix string) bool // Quote returns a double-quoted Go string literal representing s. The // returned string uses Go escape sequences (\t, \n, \xFF, \u0100) for // control characters and non-printable characters as defined by // [IsPrint]. func (s string) Quote() string // Unquote interprets s as a single-quoted, double-quoted, // or backquoted Go string literal, returning the string value // that s quotes. (If s is single-quoted, it would be a Go // character literal; Unquote returns the corresponding // one-character string. For an empty character literal // Unquote returns the empty string.) func (s string) Unquote() (string, error) // ToTitle returns a copy of the string s with all Unicode letters mapped to // their Unicode title case. func (s string) ToTitle() string // ToUpper returns s with all Unicode letters mapped to their upper case. func (s string) ToUpper() string // ToLower returns s with all Unicode letters mapped to their lower case. func (s string) ToLower() string // Fields splits the string s around each instance of one or more consecutive white space // characters, as defined by [unicode.IsSpace], returning a slice of substrings of s or an // empty slice if s contains only white space. func (s string) Fields() []string // Repeat returns a new string consisting of count copies of the string s. // // It panics if count is negative or if the result of (len(s) * count) // overflows. func (s string) Repeat(count int) string // Split slices s into all substrings separated by sep and returns a slice of // the substrings between those separators. // // If s does not contain sep and sep is not empty, Split returns a // slice of length 1 whose only element is s. // // If sep is empty, Split splits after each UTF-8 sequence. If both s // and sep are empty, Split returns an empty slice. // // It is equivalent to [SplitN] with a count of -1. // // To split around the first instance of a separator, see [Cut]. func (s string) Split(sep string) []string // SplitAfter slices s into all substrings after each instance of sep and // returns a slice of those substrings. // // If s does not contain sep and sep is not empty, SplitAfter returns // a slice of length 1 whose only element is s. // // If sep is empty, SplitAfter splits after each UTF-8 sequence. If // both s and sep are empty, SplitAfter returns an empty slice. // // It is equivalent to [SplitAfterN] with a count of -1. func (s string) SplitAfter(sep string) []string // SplitN slices s into substrings separated by sep and returns a slice of // the substrings between those separators. // // The count determines the number of substrings to return: // - n > 0: at most n substrings; the last substring will be the unsplit remainder; // - n == 0: the result is nil (zero substrings); // - n < 0: all substrings. // // Edge cases for s and sep (for example, empty strings) are handled // as described in the documentation for [Split]. // // To split around the first instance of a separator, see [Cut]. func (s string) SplitN(sep string, n int) []string // SplitAfterN slices s into substrings after each instance of sep and // returns a slice of those substrings. // // The count determines the number of substrings to return: // - n > 0: at most n substrings; the last substring will be the unsplit remainder; // - n == 0: the result is nil (zero substrings); // - n < 0: all substrings. // // Edge cases for s and sep (for example, empty strings) are handled // as described in the documentation for [SplitAfter]. func (s string) SplitAfterN(sep string, n int) []string // Replace returns a copy of the string s with the first n // non-overlapping instances of old replaced by new. // If old is empty, it matches at the beginning of the string // and after each UTF-8 sequence, yielding up to k+1 replacements // for a k-rune string. // If n < 0, there is no limit on the number of replacements. func (s string) Replace(old, new string, n int) string // ReplaceAll returns a copy of the string s with all // non-overlapping instances of old replaced by new. // If old is empty, it matches at the beginning of the string // and after each UTF-8 sequence, yielding up to k+1 replacements // for a k-rune string. func (s string) ReplaceAll(old, new string) string // Trim returns a slice of the string s with all leading and // trailing Unicode code points contained in cutset removed. func (s string) Trim(cutset string) string // TrimSpace returns a slice of the string s, with all leading // and trailing white space removed, as defined by Unicode. func (s string) TrimSpace() string // TrimLeft returns a slice of the string s with all leading // Unicode code points contained in cutset removed. // // To remove a prefix, use [TrimPrefix] instead. func (s string) TrimLeft(cutset string) string // TrimRight returns a slice of the string s, with all trailing // Unicode code points contained in cutset removed. // // To remove a suffix, use [TrimSuffix] instead. func (s string) TrimRight(cutset string) string // TrimPrefix returns s without the provided leading prefix string. // If s doesn't start with prefix, s is returned unchanged. func (s string) TrimPrefix(prefix string) string // TrimSuffix returns s without the provided trailing suffix string. // If s doesn't end with suffix, s is returned unchanged. func (s string) TrimSuffix(suffix string) string // Capitalize returns a copy of the string str with the first letter mapped to // its upper case. func (s string) Capitalize() string // The len built-in function returns the length of v, according to its type: // // - Array: the number of elements in v. // - Pointer to array: the number of elements in *v (even if v is nil). // - Slice, or map: the number of elements in v; if v is nil, len(v) is zero. // - String: the number of bytes in v. // - Channel: the number of elements queued (unread) in the channel buffer; // if v is nil, len(v) is zero. // // For some arguments, such as a string literal or a simple array expression, the // result can be a constant. See the Go language specification's "Length and // capacity" section for details. func (v []string) Len() int // The cap built-in function returns the capacity of v, according to its type: // // - Array: the number of elements in v (same as len(v)). // - Pointer to array: the number of elements in *v (same as len(v)). // - Slice: the maximum length the slice can reach when resliced; // if v is nil, cap(v) is zero. // - Channel: the channel buffer capacity, in units of elements; // if v is nil, cap(v) is zero. // // For some arguments, such as a simple array expression, the result can be a // constant. See the Go language specification's "Length and capacity" section for // details. func (v []string) Cap() int // Join concatenates the elements of its first argument to create a single string. The separator // string sep is placed between elements in the resulting string. func (v []string) Join(sep string) string // Capitalize capitalizes the first letter of each string in the slice. func (v []string) Capitalize() []string // ToTitle title-cases all strings in the slice. func (v []string) ToTitle() []string // ToUpper upper-cases all strings in the slice. func (v []string) ToUpper() []string // ToLower lower-cases all strings in the slice. func (v []string) ToLower() []string // Repeat repeats each string in the slice count times. func (v []string) Repeat(count int) []string // Replace replaces all occurrences of old in each string in the slice with new. func (v []string) Replace(old, new string, n int) []string // ReplaceAll replaces all occurrences of old in each string in the slice with new. func (v []string) ReplaceAll(old, new string) []string // Trim removes leading and trailing white space from each string in the slice. func (v []string) Trim(cutset string) []string // TrimSpace removes leading and trailing white space from each string in the slice. func (v []string) TrimSpace() []string // TrimLeft removes leading white space from each string in the slice. func (v []string) TrimLeft(cutset string) []string // TrimRight removes trailing white space from each string in the slice. func (v []string) TrimRight(cutset string) []string // TrimPrefix removes leading prefix from each string in the slice. func (v []string) TrimPrefix(prefix string) []string // TrimSuffix removes trailing suffix from each string in the slice. func (v []string) TrimSuffix(suffix string) []string ================================================ FILE: cl/_testc/hello/in.xgo ================================================ import "c" c.Printf c"Hello, world!\n" ================================================ FILE: cl/_testc/hello/out.go ================================================ package main import "github.com/goplus/lib/c" func main() { c.Printf(c.Str("Hello, world!\n")) } ================================================ FILE: cl/_testgop/_matrix/in.xgo ================================================ echo [ 1, 2, 3 4, 5, 6 ] ================================================ FILE: cl/_testgop/append1/in.xgo ================================================ type foo struct { a []int } a := [1, 2, 3] a <- 4 echo a f := foo{a: [1, 2, 3]} f.a <- 4 echo f f2 := [2]chan int{} f2[0] <- 4 ================================================ FILE: cl/_testgop/append1/out.go ================================================ package main import "fmt" type foo struct { a []int } func main() { a := []int{1, 2, 3} a = append(a, 4) fmt.Println(a) f := foo{a: []int{1, 2, 3}} f.a = append(f.a, 4) fmt.Println(f) f2 := [2]chan int{} f2[0] <- 4 } ================================================ FILE: cl/_testgop/append2/in.xgo ================================================ a := [1, 2, 3] a <- 4, 5 echo a b := [7, 8] a <- b... echo a ================================================ FILE: cl/_testgop/append2/out.go ================================================ package main import "fmt" func main() { a := []int{1, 2, 3} a = append(a, 4, 5) fmt.Println(a) b := []int{7, 8} a = append(a, b...) fmt.Println(a) } ================================================ FILE: cl/_testgop/autoref-2484/in.xgo ================================================ type Person struct { Name string Age int } type Point struct { X, Y int } func processPerson(p *Person) { println p.Name, p.Age } func processPoint(p *Point) { println p.X, p.Y } func processSlice(s *[]int) { println len(*s) } func processMap(m *map[string]int) { println len(*m) } func processArray(a *[3]int) { println len(*a) } // Test auto-reference for typed composite literals processPerson Person{Name: "Alice", Age: 30} processPoint Point{X: 10, Y: 20} processSlice []int{1, 2, 3} processMap map[string]int{"a": 1, "b": 2} processArray [3]int{1, 2, 3} // Test that normal pointer usage still works processPerson &Person{Name: "Bob", Age: 25} processPoint &Point{X: 5, Y: 15} ================================================ FILE: cl/_testgop/autoref-2484/out.go ================================================ package main import "fmt" type Person struct { Name string Age int } type Point struct { X int Y int } func processPerson(p *Person) { fmt.Println(p.Name, p.Age) } func processPoint(p *Point) { fmt.Println(p.X, p.Y) } func processSlice(s *[]int) { fmt.Println(len(*s)) } func processMap(m *map[string]int) { fmt.Println(len(*m)) } func processArray(a *[3]int) { fmt.Println(len(*a)) } // Test auto-reference for typed composite literals func main() { processPerson(&Person{Name: "Alice", Age: 30}) processPoint(&Point{X: 10, Y: 20}) processSlice(&[]int{1, 2, 3}) processMap(&map[string]int{"a": 1, "b": 2}) processArray(&[3]int{1, 2, 3}) processPerson(&Person{Name: "Bob", Age: 25}) processPoint(&Point{X: 5, Y: 15}) } ================================================ FILE: cl/_testgop/builtin/in.xgo ================================================ a := ["hello", "world", "123"] echo a.capitalize echo contains("param-required required", "required") echo contains("param-required required", "param") ================================================ FILE: cl/_testgop/builtin/out.go ================================================ package main import ( "fmt" "github.com/qiniu/x/stringslice" "github.com/qiniu/x/stringutil" ) func main() { a := []string{"hello", "world", "123"} fmt.Println(stringslice.Capitalize(a)) fmt.Println(stringutil.Contains("param-required required", "required")) fmt.Println(stringutil.Contains("param-required required", "param")) } ================================================ FILE: cl/_testgop/domaintext-html/in.xgo ================================================ echo html`

hello

` ================================================ FILE: cl/_testgop/domaintext-html/out.go ================================================ package main import ( "fmt" "github.com/goplus/xgo/encoding/html" ) func main() { fmt.Println(html.New(`

hello

`)) } ================================================ FILE: cl/_testgop/domaintext-huh/in.xgo ================================================ import "github.com/goplus/xgo/cl/internal/huh" form := huh`> "1", 2
` form.run ================================================ FILE: cl/_testgop/domaintext-huh/out.go ================================================ package main import "github.com/goplus/xgo/cl/internal/huh" func main() { form := huh.New("
\n
\n", "1", 2) form.Run() } ================================================ FILE: cl/_testgop/domaintext-json/in.xgo ================================================ echo json`{"a":1, "b":2}` echo yaml` a: 1 b: c ` ================================================ FILE: cl/_testgop/domaintext-json/out.go ================================================ package main import ( "fmt" "github.com/goplus/xgo/encoding/json" "github.com/goplus/xgo/encoding/yaml" ) func main() { fmt.Println(json.New(`{"a":1, "b":2}`)) fmt.Println(yaml.New(` a: 1 b: c `)) } ================================================ FILE: cl/_testgop/domaintext-md/go.mod ================================================ module example go 1.22 require github.com/xushiwei/markdown v0.1.0 require github.com/yuin/goldmark v1.7.8 // indirect ================================================ FILE: cl/_testgop/domaintext-md/go.sum ================================================ github.com/xushiwei/markdown v0.1.0 h1:Vjw4MVcwSEJge9ObA/3C1lrmNYX60nJwuVKdLYrf0+o= github.com/xushiwei/markdown v0.1.0/go.mod h1:6hyvHMBrprwcCYXaw71W9WwBX1st9r+Rfsj+cKXW3EE= github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= ================================================ FILE: cl/_testgop/domaintext-md/in.xgo ================================================ import ( "os" "github.com/xushiwei/markdown" ) md := markdown` # Title Hello world ` md.convert os.Stdout ================================================ FILE: cl/_testgop/domaintext-md/out.go ================================================ package main import ( "github.com/xushiwei/markdown" "os" ) func main() { md := markdown.New(` # Title Hello world `) md.Convert(os.Stdout) } ================================================ FILE: cl/_testgop/domaintext-regexp/in.xgo ================================================ re, err := regexp`^[a-z]+\[[0-9]+\]$` echo re.matchString("adam[23]") _ = err ================================================ FILE: cl/_testgop/domaintext-regexp/out.go ================================================ package main import ( "fmt" "github.com/goplus/xgo/encoding/regexp" ) func main() { re, err := regexp.New(`^[a-z]+\[[0-9]+\]$`) fmt.Println(re.MatchString("adam[23]")) _ = err } ================================================ FILE: cl/_testgop/domaintext-tpl/in.xgo ================================================ cl, err := tpl`expr = INT % ("+" | "-")` cl.parseExpr "1+2", nil _ = err ================================================ FILE: cl/_testgop/domaintext-tpl/out.go ================================================ package main import "github.com/goplus/xgo/tpl" func main() { cl, err := tpl.NewEx(`expr = INT % ("+" | "-")`, "cl/_testgop/domaintext-tpl/in.xgo", 1, 15) cl.ParseExpr("1+2", nil) _ = err } ================================================ FILE: cl/_testgop/domaintpl/in.xgo ================================================ tpl` file = stmts => { return self } stmts = *(stmt ";") => { return [n.([]any)[0] for n in self] } stmt = varStmt | constStmt | outputStmt | inputStmt | ifStmt | whileStmt | untilStmt | assignStmt varStmt = "DECLARE" namelist ":" typeExpr constStmt = "CONSTANT" IDENT "<-" expr assignStmt = IDENT "<-" expr outputStmt = "OUTPUT" exprlist inputStmt = "INPUT" namelist ifStmt = "IF" expr "THEN" ";" stmts ?("ELSE" ";" stmts) "ENDIF" whileStmt = "WHILE" expr "DO" ";" stmts "ENDWHILE" untilStmt = "REPEAT" ";" stmts "UNTIL" expr typeExpr = "INTEGER" | "REAL" | "STRING" | "BOOLEAN" expr = binaryExpr2 % ("<" | "<=" | ">" | ">=" | "=" | "<>") binaryExpr2 = binaryExpr1 % ("+" | "-") binaryExpr1 = operand % ("*" | "/") operand = basicLit | ident | parenExpr | unaryExpr unaryExpr = "-" operand basicLit = INT | FLOAT | STRING ident = IDENT parenExpr = "(" expr ")" exprlist = expr % "," namelist = IDENT % "," ` ================================================ FILE: cl/_testgop/domaintpl/out.go ================================================ package main import "github.com/goplus/xgo/tpl" func main() { tpl.NewEx(` file = stmts => { return self } stmts = *(stmt ";") => { return [n.([]any)[0] for n in self] } stmt = varStmt | constStmt | outputStmt | inputStmt | ifStmt | whileStmt | untilStmt | assignStmt varStmt = "DECLARE" namelist ":" typeExpr constStmt = "CONSTANT" IDENT "<-" expr assignStmt = IDENT "<-" expr outputStmt = "OUTPUT" exprlist inputStmt = "INPUT" namelist ifStmt = "IF" expr "THEN" ";" stmts ?("ELSE" ";" stmts) "ENDIF" whileStmt = "WHILE" expr "DO" ";" stmts "ENDWHILE" untilStmt = "REPEAT" ";" stmts "UNTIL" expr typeExpr = "INTEGER" | "REAL" | "STRING" | "BOOLEAN" expr = binaryExpr2 % ("<" | "<=" | ">" | ">=" | "=" | "<>") binaryExpr2 = binaryExpr1 % ("+" | "-") binaryExpr1 = operand % ("*" | "/") operand = basicLit | ident | parenExpr | unaryExpr unaryExpr = "-" operand basicLit = INT | FLOAT | STRING ident = IDENT parenExpr = "(" expr ")" exprlist = expr % "," namelist = IDENT % "," `, "cl/_testgop/domaintpl/in.xgo", 1, 4, "file", func(self interface{}) interface{} { return self }, "stmts", func(self []interface{}) interface{} { return func() (_xgo_ret []interface{}) { for _, n := range self { _xgo_ret = append(_xgo_ret, n.([]interface{})[0]) } return }() }) } ================================================ FILE: cl/_testgop/dql1/in.xgo ================================================ import "github.com/goplus/xgo/cl/internal/dql" doc := dql.new echo doc.foo.**.users.*.$age echo doc."foo-name".**."elem-name".*.$"attr-name" echo doc.**.*.$name ================================================ FILE: cl/_testgop/dql1/out.go ================================================ package main import ( "fmt" "github.com/goplus/xgo/cl/internal/dql" ) func main() { doc := dql.New() fmt.Println(doc.XGo_Elem("foo").XGo_Any("users").XGo_Child().XGo_Attr__0("age")) fmt.Println(doc.XGo_Elem("foo-name").XGo_Any("elem-name").XGo_Child().XGo_Attr__0("attr-name")) fmt.Println(doc.XGo_Any("").XGo_Attr__0("name")) } ================================================ FILE: cl/_testgop/dql2/in.xgo ================================================ import "github.com/goplus/xgo/cl/internal/dql" doc := dql.new name := doc.users@($age?:100 < 18).$name! echo name ================================================ FILE: cl/_testgop/dql2/out.go ================================================ package main import ( "fmt" "github.com/goplus/xgo/cl/internal/dql" "github.com/qiniu/x/errors" ) func main() { doc := dql.New() name := func() (_xgo_ret int) { var _xgo_err error _xgo_ret, _xgo_err = dql.NodeSet_Cast(func(_xgo_yield func(*dql.Node) bool) { doc.XGo_Elem("users").XGo_Enum()(func(self dql.NodeSet) bool { if func() (_xgo_ret int) { var _xgo_err error _xgo_ret, _xgo_err = self.XGo_Attr__1("age") if _xgo_err != nil { return 100 } return }() < 18 { if _xgo_val, _xgo_err := self.XGo_first(); _xgo_err == nil { if !_xgo_yield(_xgo_val) { return false } } } return true }) }).XGo_Attr__1("name") if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "doc.users@($age?:100 < 18).$name", "cl/_testgop/dql2/in.xgo", 4, "main.main") panic(_xgo_err) } return }() fmt.Println(name) } ================================================ FILE: cl/_testgop/dql3/in.xgo ================================================ import "github.com/goplus/xgo/cl/internal/dql" doc := dql.new echo doc.*@users@`users`.$name ================================================ FILE: cl/_testgop/dql3/out.go ================================================ package main import ( "fmt" "github.com/goplus/xgo/cl/internal/dql" ) func main() { doc := dql.New() fmt.Println(doc.XGo_Child().XGo_Select("users").XGo_Select("users").XGo_Attr__0("name")) } ================================================ FILE: cl/_testgop/dql4/in.xgo ================================================ doc := xml` Line 1 Line 2 Line 3 Line 4 Line 5 Line 6 Line 7 Line 8 `! echo doc.animals.*@($class == "zebra")._dump._text ================================================ FILE: cl/_testgop/dql4/out.go ================================================ package main import ( "fmt" xml1 "github.com/goplus/xgo/dql/xml" "github.com/goplus/xgo/encoding/xml" "github.com/qiniu/x/errors" ) func main() { doc := func() (_xgo_ret xml.Object) { var _xgo_err error _xgo_ret, _xgo_err = xml.New(` Line 1 Line 2 Line 3 Line 4 Line 5 Line 6 Line 7 Line 8 `) if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "xml`\n\tLine 1\n\tLine 2\n\tLine 3\n\tLine 4\n\tLine 5\n\tLine 6\n\tLine 7\n\tLine 8\n\n`", "cl/_testgop/dql4/in.xgo", 1, "main.main") panic(_xgo_err) } return }() fmt.Println(xml1.NodeSet_Cast(func(_xgo_yield func(*xml1.Node) bool) { doc.XGo_Elem("animals").XGo_Child().XGo_Enum()(func(self xml1.NodeSet) bool { if self.XGo_Attr__0("class") == "zebra" { if _xgo_val, _xgo_err := self.XGo_first(); _xgo_err == nil { if !_xgo_yield(_xgo_val) { return false } } } return true }) }).XGo_dump().XGo_text__0()) } ================================================ FILE: cl/_testgop/dql5/in.xgo ================================================ doc := html`

Links:

`! for a in doc.body.**.a.dump { echo a.$href, a.text } ================================================ FILE: cl/_testgop/dql5/out.go ================================================ package main import ( "fmt" "github.com/goplus/xgo/encoding/html" "github.com/qiniu/x/errors" ) func main() { doc := func() (_xgo_ret html.Object) { var _xgo_err error _xgo_ret, _xgo_err = html.New(`

Links:

`) if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "html`\n

Links:

\n\n\n`", "cl/_testgop/dql5/in.xgo", 1, "main.main") panic(_xgo_err) } return }() for a := range doc.XGo_Elem("body").XGo_Any("a").Dump().XGo_Enum() { fmt.Println(a.XGo_Attr__0("href"), a.Text__0()) } } ================================================ FILE: cl/_testgop/dql6/in.xgo ================================================ doc := golang`package main var ( a, b string c int ) func add(a, b int) int { return a + b } func mul(a, b float64) float64 { return a * b } `! for fn in doc.decls.*@(self.class == "FuncDecl") { echo fn.$name } ================================================ FILE: cl/_testgop/dql6/out.go ================================================ package main import ( "fmt" golang1 "github.com/goplus/xgo/dql/golang" "github.com/goplus/xgo/dql/reflects" "github.com/goplus/xgo/encoding/golang" "github.com/qiniu/x/errors" ) func main() { doc := func() (_xgo_ret golang.Object) { var _xgo_err error _xgo_ret, _xgo_err = golang.New(`package main var ( a, b string c int ) func add(a, b int) int { return a + b } func mul(a, b float64) float64 { return a * b } `) if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "golang`package main\n\nvar (\n\ta, b string\n\tc int\n)\n\nfunc add(a, b int) int {\n\treturn a + b\n}\n\nfunc mul(a, b float64) float64 {\n\treturn a * b\n}\n`", "cl/_testgop/dql6/in.xgo", 1, "main.main") panic(_xgo_err) } return }() for fn := range golang1.NodeSet_Cast(func(_xgo_yield func(reflects.Node) bool) { doc.XGo_Elem("decls").XGo_Child().XGo_Enum()(func(self golang1.NodeSet) bool { if self.Class() == "FuncDecl" { if _xgo_val, _xgo_err := self.XGo_first(); _xgo_err == nil { if !_xgo_yield(_xgo_val) { return false } } } return true }) }).XGo_Enum() { fmt.Println(fn.XGo_Attr__0("name")) } } ================================================ FILE: cl/_testgop/dql7/in.xgo ================================================ doc := json`{ "animals": [ {"class": "gopher", "at": "Line 1"}, {"class": "armadillo", "at": "Line 2"}, {"class": "zebra", "at": "Line 3"}, {"class": "unknown", "at": "Line 4"}, {"class": "gopher", "at": "Line 5"}, {"class": "bee", "at": "Line 6"}, {"class": "gopher", "at": "Line 7"}, {"class": "zebra", "at": "Line 8"} ] } `! for animal in doc.$animals.*@($class == "zebra") { animal.$at } ================================================ FILE: cl/_testgop/dql7/out.go ================================================ package main import ( "github.com/goplus/xgo/dql/maps" "github.com/goplus/xgo/encoding/json" "github.com/qiniu/x/errors" ) func main() { doc := func() (_xgo_ret json.Object) { var _xgo_err error _xgo_ret, _xgo_err = json.New(`{ "animals": [ {"class": "gopher", "at": "Line 1"}, {"class": "armadillo", "at": "Line 2"}, {"class": "zebra", "at": "Line 3"}, {"class": "unknown", "at": "Line 4"}, {"class": "gopher", "at": "Line 5"}, {"class": "bee", "at": "Line 6"}, {"class": "gopher", "at": "Line 7"}, {"class": "zebra", "at": "Line 8"} ] } `) if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "json`{\n\t\"animals\": [\n\t\t{\"class\": \"gopher\", \"at\": \"Line 1\"},\n\t\t{\"class\": \"armadillo\", \"at\": \"Line 2\"},\n\t\t{\"class\": \"zebra\", \"at\": \"Line 3\"},\n\t\t{\"class\": \"unknown\", \"at\": \"Line 4\"},\n\t\t{\"class\": \"gopher\", \"at\": \"Line 5\"},\n\t\t{\"class\": \"bee\", \"at\": \"Line 6\"},\n\t\t{\"class\": \"gopher\", \"at\": \"Line 7\"},\n\t\t{\"class\": \"zebra\", \"at\": \"Line 8\"}\n\t]\n}\n`", "cl/_testgop/dql7/in.xgo", 1, "main.main") panic(_xgo_err) } return }() _autoGo_1, _ := doc.(map[string]any) for animal := range maps.NodeSet_Cast(func(_xgo_yield func(maps.Node) bool) { maps.New(_autoGo_1["animals"]).XGo_Child().XGo_Enum()(func(self maps.NodeSet) bool { if self.XGo_Attr__0("class") == "zebra" { if _xgo_val, _xgo_err := self.XGo_first(); _xgo_err == nil { if !_xgo_yield(_xgo_val) { return false } } } return true }) }).XGo_Enum() { animal.XGo_Attr__0("at") } } ================================================ FILE: cl/_testgop/enumlines-rdr/in.xgo ================================================ import "io" var r io.Reader for line in lines(r) { println line } ================================================ FILE: cl/_testgop/enumlines-rdr/out.go ================================================ package main import ( "fmt" "github.com/qiniu/x/osx" "io" ) var r io.Reader func main() { for _xgo_it := osx.Lines(r).XGo_Enum(); ; { var _xgo_ok bool line, _xgo_ok := _xgo_it.Next() if !_xgo_ok { break } fmt.Println(line) } } ================================================ FILE: cl/_testgop/enumlines-stdin/in.xgo ================================================ import "os" for line <- os.Stdin { println line } ================================================ FILE: cl/_testgop/enumlines-stdin/out.go ================================================ package main import ( "fmt" "github.com/qiniu/x/osx" "os" ) func main() { for _xgo_it := osx.EnumLines(os.Stdin); ; { var _xgo_ok bool line, _xgo_ok := _xgo_it.Next() if !_xgo_ok { break } fmt.Println(line) } } ================================================ FILE: cl/_testgop/errwrap1/in.xgo ================================================ func F() (a int8, b int16, err error) { a = 1 return } func F2() (err error) { c, d := F()! _ = c _ = d return } F2() ================================================ FILE: cl/_testgop/errwrap1/out.go ================================================ package main import "github.com/qiniu/x/errors" func F() (a int8, b int16, err error) { a = 1 return } func F2() (err error) { c, d := func() (_xgo_ret int8, _xgo_ret2 int16) { var _xgo_err error _xgo_ret, _xgo_ret2, _xgo_err = F() if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "F()", "cl/_testgop/errwrap1/in.xgo", 7, "main.F2") panic(_xgo_err) } return }() _ = c _ = d return } func main() { F2() } ================================================ FILE: cl/_testgop/errwrap2/in.xgo ================================================ func F() (a int8, b int16, err error) { a = 1 return } func F2() (err error) { c, d := F()? _ = c _ = d return } F2() ================================================ FILE: cl/_testgop/errwrap2/out.go ================================================ package main import "github.com/qiniu/x/errors" func F() (a int8, b int16, err error) { a = 1 return } func F2() (err error) { var _autoGo_1 int8 var _autoGo_2 int16 { var _xgo_err error _autoGo_1, _autoGo_2, _xgo_err = F() if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "F()", "cl/_testgop/errwrap2/in.xgo", 7, "main.F2") return _xgo_err } goto _autoGo_3 _autoGo_3: } c, d := _autoGo_1, _autoGo_2 _ = c _ = d return } func main() { F2() } ================================================ FILE: cl/_testgop/errwrap3/in.xgo ================================================ func BarOne() int { return 0 } func BarTwo() (int, error) { return 0, nil } func Bar = ( BarOne BarTwo ) echo bar! ================================================ FILE: cl/_testgop/errwrap3/out.go ================================================ package main import ( "fmt" "github.com/qiniu/x/errors" ) const XGoo_Bar = "BarOne,BarTwo" func BarOne() int { return 0 } func BarTwo() (int, error) { return 0, nil } func main() { fmt.Println(func() (_xgo_ret int) { var _xgo_err error _xgo_ret, _xgo_err = BarTwo() if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "bar", "cl/_testgop/errwrap3/in.xgo", 14, "main.main") panic(_xgo_err) } return }()) } ================================================ FILE: cl/_testgop/fatal/in.xgo ================================================ import "os" f, err := os.open("hello.txt") if err != nil { errorln "[WARN] an error" fatal "open file failed: ${err}" } f.close ================================================ FILE: cl/_testgop/fatal/out.go ================================================ package main import ( "github.com/qiniu/x/osx" "github.com/qiniu/x/stringutil" "os" ) func main() { f, err := os.Open("hello.txt") if err != nil { osx.Errorln("[WARN] an error") osx.Fatal(stringutil.Concat("open file failed: ", err.Error())) } f.Close() } ================================================ FILE: cl/_testgop/for-in/in.xgo ================================================ import "github.com/goplus/xgo/cl/internal/dql" doc := dql.new for user in doc.users { echo user } ================================================ FILE: cl/_testgop/for-in/out.go ================================================ package main import ( "fmt" "github.com/goplus/xgo/cl/internal/dql" ) func main() { doc := dql.New() for user := range doc.XGo_Elem("users").XGo_Enum() { fmt.Println(user) } } ================================================ FILE: cl/_testgop/for-range/in.xgo ================================================ func foo(yield func() bool) { yield() } func bar(yield func(v string) bool) { yield("World") } func weekdays(yield func(k string, v int) bool) { yield("Mon", 1) } for range foo { echo "Hi" } for v := range bar { echo v } for k, v := range weekdays { echo k, v } ================================================ FILE: cl/_testgop/for-range/out.go ================================================ package main import "fmt" func foo(yield func() bool) { yield() } func bar(yield func(v string) bool) { yield("World") } func weekdays(yield func(k string, v int) bool) { yield("Mon", 1) } func main() { for range foo { fmt.Println("Hi") } for v := range bar { fmt.Println(v) } for k, v := range weekdays { fmt.Println(k, v) } } ================================================ FILE: cl/_testgop/implicit-cast-2439/in.xgo ================================================ type BasePtr struct { } type Base struct { *BasePtr } func Walk(p *Base) {} func WalkPtr(p *BasePtr) {} type T struct { Base } func f() *T { return {} } walk new(T) walkPtr f() ================================================ FILE: cl/_testgop/implicit-cast-2439/out.go ================================================ package main type BasePtr struct { } type Base struct { *BasePtr } type T struct { Base } func Walk(p *Base) { } func WalkPtr(p *BasePtr) { } func f() *T { return &T{} } func main() { Walk(&new(T).Base) WalkPtr(f().BasePtr) } ================================================ FILE: cl/_testgop/kwargs1/in.xgo ================================================ type Options struct { Loop bool async bool } func PlaySound(path string, options *Options) { } playSound "1.mp3", loop = true playSound "2.mp3", loop = false, async = true ================================================ FILE: cl/_testgop/kwargs1/out.go ================================================ package main type Options struct { Loop bool async bool } func PlaySound(path string, options *Options) { } func main() { PlaySound("1.mp3", &Options{Loop: true}) PlaySound("2.mp3", &Options{Loop: false, async: true}) } ================================================ FILE: cl/_testgop/kwargs2/in.xgo ================================================ type Options struct { Loop bool async bool } func PlaySound(path string, options Options) { } playSound "1.mp3", loop = true playSound "2.mp3", loop = false, async = true ================================================ FILE: cl/_testgop/kwargs2/out.go ================================================ package main type Options struct { Loop bool async bool } func PlaySound(path string, options Options) { } func main() { PlaySound("1.mp3", Options{Loop: true}) PlaySound("2.mp3", Options{Loop: false, async: true}) } ================================================ FILE: cl/_testgop/kwargs3/in.xgo ================================================ type Options map[string]bool func PlaySound(options Options, paths ...string) { } playSound "1.mp3", "foo.wav", loop = false playSound "2.mp3", loop = true, async = true ================================================ FILE: cl/_testgop/kwargs3/out.go ================================================ package main type Options map[string]bool func PlaySound(options Options, paths ...string) { } func main() { PlaySound(Options{"loop": false}, "1.mp3", "foo.wav") PlaySound(Options{"loop": true, "async": true}, "2.mp3") } ================================================ FILE: cl/_testgop/kwargs4/in.xgo ================================================ import "github.com/goplus/xgo/cl/internal/testutil" func PlaySound(path string, options *testutil.Options) { } playSound "1.mp3", loop = true playSound "2.mp3", Loop = false, async = true ================================================ FILE: cl/_testgop/kwargs4/out.go ================================================ package main import "github.com/goplus/xgo/cl/internal/testutil" func PlaySound(path string, options *testutil.Options) { } func main() { PlaySound("1.mp3", &testutil.Options{Loop: true}) PlaySound("2.mp3", &testutil.Options{Loop: false, Async: true}) } ================================================ FILE: cl/_testgop/list-compr1/in.xgo ================================================ a := [1, 3.4, 5] b := [x*x for x <- a] ================================================ FILE: cl/_testgop/list-compr1/out.go ================================================ package main func main() { a := []float64{1, 3.4, 5} b := func() (_xgo_ret []float64) { for _, x := range a { _xgo_ret = append(_xgo_ret, x*x) } return }() } ================================================ FILE: cl/_testgop/list-compr2/in.xgo ================================================ arr := [1, 2, 3, 4.1, 5, 6] x := [[a, b] for a <- arr, a < b for b <- arr, b > 2] println("x:", x) ================================================ FILE: cl/_testgop/list-compr2/out.go ================================================ package main import "fmt" func main() { arr := []float64{1, 2, 3, 4.1, 5, 6} x := func() (_xgo_ret [][]float64) { for _, b := range arr { if b > 2 { for _, a := range arr { if a < b { _xgo_ret = append(_xgo_ret, []float64{a, b}) } } } } return }() fmt.Println("x:", x) } ================================================ FILE: cl/_testgop/map-compr-cond1/in.xgo ================================================ z := {v: k for k, v <- {"Hello": 1, "Hi": 3, "xsw": 5, "XGo": 7}, v > 3} ================================================ FILE: cl/_testgop/map-compr-cond1/out.go ================================================ package main func main() { z := func() (_xgo_ret map[int]string) { _xgo_ret = map[int]string{} for k, v := range map[string]int{"Hello": 1, "Hi": 3, "xsw": 5, "XGo": 7} { if v > 3 { _xgo_ret[v] = k } } return }() } ================================================ FILE: cl/_testgop/map-compr-cond2/in.xgo ================================================ z := {t: k for k, v <- {"Hello": 1, "Hi": 3, "xsw": 5, "XGo": 7}, t := v; t > 3} ================================================ FILE: cl/_testgop/map-compr-cond2/out.go ================================================ package main func main() { z := func() (_xgo_ret map[int]string) { _xgo_ret = map[int]string{} for k, v := range map[string]int{"Hello": 1, "Hi": 3, "xsw": 5, "XGo": 7} { if t := v; t > 3 { _xgo_ret[t] = k } } return }() } ================================================ FILE: cl/_testgop/map-compr1/in.xgo ================================================ y := {x: i for i, x <- ["1", "3", "5", "7", "11"]} ================================================ FILE: cl/_testgop/map-compr1/out.go ================================================ package main func main() { y := func() (_xgo_ret map[string]int) { _xgo_ret = map[string]int{} for i, x := range []string{"1", "3", "5", "7", "11"} { _xgo_ret[x] = i } return }() } ================================================ FILE: cl/_testgop/map-field-access1/in.xgo ================================================ v := map[string]map[string]any{"a": {"b": 1}} c, ok := v["b"]["c"].(int) echo c, ok c, ok = v.b.c.(int) echo c, ok ================================================ FILE: cl/_testgop/map-field-access1/out.go ================================================ package main import "fmt" func main() { v := map[string]map[string]interface{}{"a": map[string]interface{}{"b": 1}} c, ok := v["b"]["c"].(int) fmt.Println(c, ok) c, ok = v["b"]["c"].(int) fmt.Println(c, ok) } ================================================ FILE: cl/_testgop/map-field-access2/in.xgo ================================================ var v any c, ok := v["b"]["c"].(int) echo c, ok d, ok := v.b.c echo d, ok ================================================ FILE: cl/_testgop/map-field-access2/out.go ================================================ package main import "fmt" var v interface{} func main() { _autoGo_1, _ := v.(map[string]any) _autoGo_2, _ := _autoGo_1["b"].(map[string]any) c, ok := _autoGo_2["c"].(int) fmt.Println(c, ok) _autoGo_3, _ := v.(map[string]any) _autoGo_4, _ := _autoGo_3["b"].(map[string]any) d, ok := _autoGo_4["c"] fmt.Println(d, ok) } ================================================ FILE: cl/_testgop/optparam/in.xgo ================================================ func basic(a int, b int?) { println a, b } func multiple(name string, age int?, active bool?) { println name, age, active } func allOptional(x int?, y string?) { println x, y } func withVariadic(a int?, b ...string) { println a, b } type Server struct{} func (s *Server) handle(req string, opts int?) { println req, opts } basic 10, 20 multiple "Alice", 30, true allOptional 100, "test" withVariadic 5, "hello", "world" s := Server{} s.handle "request", 42 ================================================ FILE: cl/_testgop/optparam/out.go ================================================ package main import "fmt" type Server struct { } func basic(a int, __xgo_optional_b int) { fmt.Println(a, __xgo_optional_b) } func multiple(name string, __xgo_optional_age int, __xgo_optional_active bool) { fmt.Println(name, __xgo_optional_age, __xgo_optional_active) } func allOptional(__xgo_optional_x int, __xgo_optional_y string) { fmt.Println(__xgo_optional_x, __xgo_optional_y) } func withVariadic(__xgo_optional_a int, b ...string) { fmt.Println(__xgo_optional_a, b) } func (s *Server) handle(req string, __xgo_optional_opts int) { fmt.Println(req, __xgo_optional_opts) } func main() { basic(10, 20) multiple("Alice", 30, true) allOptional(100, "test") withVariadic(5, "hello", "world") s := Server{} s.handle("request", 42) } ================================================ FILE: cl/_testgop/optparam2/in.xgo ================================================ func returnValue(x int?) int { return x } func useInExpression(a int?, b int?) int { result := a + b return result * 2 } func simpleNested(outer int?) { f := func() { println outer } f() } returnValue 42 useInExpression 10, 5 simpleNested 100 ================================================ FILE: cl/_testgop/optparam2/out.go ================================================ package main import "fmt" func returnValue(__xgo_optional_x int) int { return __xgo_optional_x } func useInExpression(__xgo_optional_a int, __xgo_optional_b int) int { result := __xgo_optional_a + __xgo_optional_b return result * 2 } func simpleNested(__xgo_optional_outer int) { f := func() { fmt.Println(__xgo_optional_outer) } f() } func main() { returnValue(42) useInExpression(10, 5) simpleNested(100) } ================================================ FILE: cl/_testgop/rangeexpr/in.xgo ================================================ println [x for x <- 0:3:1] ================================================ FILE: cl/_testgop/rangeexpr/out.go ================================================ package main import ( "fmt" "github.com/qiniu/x/xgo" ) func main() { fmt.Println(func() (_xgo_ret []int) { for _xgo_it := xgo.NewRange__0(0, 3, 1).XGo_Enum(); ; { var _xgo_ok bool x, _xgo_ok := _xgo_it.Next() if !_xgo_ok { break } _xgo_ret = append(_xgo_ret, x) } return }()) } ================================================ FILE: cl/_testgop/repeatuntil/in.xgo ================================================ func RepeatUntil(cond func() bool, body func()) { for !cond() { body() } } x := 0 repeatUntil x >= 3, => { echo x x++ } repeatUntil func() bool { return false }, => {} ================================================ FILE: cl/_testgop/repeatuntil/out.go ================================================ package main import "fmt" func RepeatUntil(cond func() bool, body func()) { for !cond() { body() } } func main() { x := 0 RepeatUntil(func() bool { return x >= 3 }, func() { fmt.Println(x) x++ }) RepeatUntil(func() bool { return false }, func() { }) } ================================================ FILE: cl/_testgop/select-compr-twovalue1/in.xgo ================================================ y, ok := {i for i, x <- ["1", "3", "5", "7", "11"], x == "5"} ================================================ FILE: cl/_testgop/select-compr-twovalue1/out.go ================================================ package main func main() { y, ok := func() (_xgo_ret int, _xgo_ok bool) { for i, x := range []string{"1", "3", "5", "7", "11"} { if x == "5" { return i, true } } return }() } ================================================ FILE: cl/_testgop/select-compr-twovalue2/in.xgo ================================================ func foo() (int, bool) { return {i for i, x <- ["1", "3", "5", "7", "11"], x == "5"} } ================================================ FILE: cl/_testgop/select-compr-twovalue2/out.go ================================================ package main func foo() (int, bool) { return func() (_xgo_ret int, _xgo_ok bool) { for i, x := range []string{"1", "3", "5", "7", "11"} { if x == "5" { return i, true } } return }() } ================================================ FILE: cl/_testgop/select-compr1/in.xgo ================================================ y := {i for i, x <- ["1", "3", "5", "7", "11"], x == "5"} ================================================ FILE: cl/_testgop/select-compr1/out.go ================================================ package main func main() { y := func() (_xgo_ret int) { for i, x := range []string{"1", "3", "5", "7", "11"} { if x == "5" { return i } } return }() } ================================================ FILE: cl/_testgop/structtag/in.xgo ================================================ type Start struct { _ "Start recording meeting minutes" } ================================================ FILE: cl/_testgop/structtag/out.go ================================================ package main type Start struct { _ struct { } `_:"Start recording meeting minutes"` } ================================================ FILE: cl/_testgop/tuplelit/in.xgo ================================================ type T struct { x (int16, float32) } func dump(a (int16, float32), _ ...bool) { t := T{ x: (1, 3.14), } echo a, t } func demo(a int16, b float32) { echo a, b } ken := ("Ken", "ken@abc.com", 7) echo ken dump (1, 3.14), true dump (1, 3.14) demo (1, 3.14) pairs := [](string, int16){ ("a", 1), ("b", 2), } echo pairs ================================================ FILE: cl/_testgop/tuplelit/out.go ================================================ package main import "fmt" type T struct { x struct { X_0 int16 X_1 float32 } } func dump(a struct { X_0 int16 X_1 float32 }, _ ...bool) { t := T{x: struct { X_0 int16 X_1 float32 }{1, 3.14}} fmt.Println(a, t) } func demo(a int16, b float32) { fmt.Println(a, b) } func main() { ken := struct { X_0 string X_1 string X_2 int }{"Ken", "ken@abc.com", 7} fmt.Println(ken) dump(struct { X_0 int16 X_1 float32 }{1, 3.14}, true) dump(struct { X_0 int16 X_1 float32 }{1, 3.14}) demo(1, 3.14) pairs := []struct { X_0 string X_1 int16 }{struct { X_0 string X_1 int16 }{"a", 1}, struct { X_0 string X_1 int16 }{"b", 2}} fmt.Println(pairs) } ================================================ FILE: cl/_testgop/tupletype1/in.xgo ================================================ import "io" // Empty tuple type Empty () // Anonymous tuple types type Pair (int, string) type Triple (int, string, bool) // Named tuple types type Point (x int, y int) type Person (name string, age int) // Type shorthand syntax type Point3D (x, y, z int) type Mixed (a, b string, _ int) // Tuple as channel element type var ch chan (int, error) // Tuple as map value type var cache map[string](int, bool) // Tuple as slice element type var pairs [](string, int) // Tuple with array types (covers token.LBRACK case) type WithArray (arr []int, data [5]string) // Tuple with pointer types (covers token.MUL case) type WithPointers (*int, *string) // Tuple with function types (covers token.FUNC case) type WithFunc (fn func(int) string, callback func()) // Tuple with channel types (covers token.CHAN case) type WithChan (ch chan int, recv <-chan string) // Tuple with map types (covers token.MAP case) type WithMap (m map[string]int, lookup map[int]bool) // Tuple with struct types (covers token.STRUCT case) type WithStruct (s struct{ x int }, data struct{ name string }) // Tuple with interface types (covers token.INTERFACE case) type WithInterface (i interface{}, reader interface{ Read([]byte) int }) // Tuple with parenthesized types (covers token.LPAREN case) type WithParen ((int), (string)) // Tuple with qualified type names (covers token.PERIOD case) type WithQualified (io.Reader, io.Writer) // Tuple with mixed named and array types type MixedArray (items []int, count int) // Single named field tuple type SingleNamed (value int) var ken Person ken.0, ken.1 = "Ken", 18 ken.age++ echo "name: ${ken.name}, age: ${ken.age}" ================================================ FILE: cl/_testgop/tupletype1/out.go ================================================ package main import ( "fmt" "github.com/qiniu/x/stringutil" "io" "strconv" ) // Empty tuple type Empty struct { } // Anonymous tuple types type Pair struct { X_0 int X_1 string } type Triple struct { X_0 int X_1 string X_2 bool } // Named tuple types type Point struct { X_0 int X_1 int } type Person struct { X_0 string X_1 int } // Type shorthand syntax type Point3D struct { X_0 int X_1 int X_2 int } type Mixed struct { X_0 string X_1 string X_2 int } // Tuple with array types (covers token.LBRACK case) type WithArray struct { X_0 []int X_1 [5]string } // Tuple with pointer types (covers token.MUL case) type WithPointers struct { X_0 *int X_1 *string } // Tuple with function types (covers token.FUNC case) type WithFunc struct { X_0 func(int) string X_1 func() } // Tuple with channel types (covers token.CHAN case) type WithChan struct { X_0 chan int X_1 <-chan string } // Tuple with map types (covers token.MAP case) type WithMap struct { X_0 map[string]int X_1 map[int]bool } // Tuple with struct types (covers token.STRUCT case) type WithStruct struct { X_0 struct { x int } X_1 struct { name string } } // Tuple with interface types (covers token.INTERFACE case) type WithInterface struct { X_0 interface{} X_1 interface { Read([]byte) int } } // Tuple with parenthesized types (covers token.LPAREN case) type WithParen struct { X_0 int X_1 string } // Tuple with qualified type names (covers token.PERIOD case) type WithQualified struct { X_0 io.Reader X_1 io.Writer } // Tuple with mixed named and array types type MixedArray struct { X_0 []int X_1 int } // Single named field tuple type SingleNamed int // Tuple as channel element type var ch chan struct { X_0 int X_1 error } // Tuple as map value type var cache map[string]struct { X_0 int X_1 bool } // Tuple as slice element type var pairs []struct { X_0 string X_1 int } var ken Person func main() { ken.X_0, ken.X_1 = "Ken", 18 ken.X_1++ fmt.Println(stringutil.Concat("name: ", ken.X_0, ", age: ", strconv.Itoa(ken.X_1))) } ================================================ FILE: cl/_testgop/tupletype2/in.xgo ================================================ type Point (x, y int) type Int (x int) pt := Point{x: 2, y: 3} echo pt.x, pt.y echo Int(100) pt2 := Point(100, 200) echo pt2 pt3 := Point(y = 5, x = 3) echo pt3.0, pt3.1 ================================================ FILE: cl/_testgop/tupletype2/out.go ================================================ package main import "fmt" type Point struct { X_0 int X_1 int } type Int int func main() { pt := Point{X_0: 2, X_1: 3} fmt.Println(pt.X_0, pt.X_1) fmt.Println(Int(100)) pt2 := Point{100, 200} fmt.Println(pt2) pt3 := Point(Point{X_1: 5, X_0: 3}) fmt.Println(pt3.X_0, pt3.X_1) } ================================================ FILE: cl/_testgop/unit/in.xgo ================================================ import ( "time" "github.com/goplus/xgo/cl/internal/unit" ) func Wait(time.Duration) {} func Step(unit.Distance) {} wait 0.5µs wait 1m step 1m ================================================ FILE: cl/_testgop/unit/out.go ================================================ package main import ( "github.com/goplus/xgo/cl/internal/unit" "time" ) func Wait(time.Duration) { } func Step(unit.Distance) { } func main() { Wait(500) Wait(60000000000) Step(1000) } ================================================ FILE: cl/_testpy/_matrix/in.xgo ================================================ import ( "py/numpy" "py/std" ) a := [ [1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0], ] b := [ [9.0, 8.0, 7.0], [6.0, 5.0, 4.0], [3.0, 2.0, 1.0], ] x := numpy.add(a, b) std.print "a+b =", x ================================================ FILE: cl/_testpy/hello/in.xgo ================================================ import ( "py/std" ) std.print py"Hello, World!" ================================================ FILE: cl/_testpy/hello/out.go ================================================ package main import ( "github.com/goplus/lib/py" "github.com/goplus/lib/py/std" ) func main() { std.Print(py.Str("Hello, World!")) } ================================================ FILE: cl/_testpy/pycall/in.xgo ================================================ import ( "c" "py" "py/math" ) x := math.sqrt(py.float(2)) c.printf c"sqrt(2) = %f\n", x.float64 ================================================ FILE: cl/_testpy/pycall/out.go ================================================ package main import ( "github.com/goplus/lib/c" "github.com/goplus/lib/py" "github.com/goplus/lib/py/math" ) func main() { x := math.Sqrt(py.Float(2)) c.Printf(c.Str("sqrt(2) = %f\n"), x.Float64()) } ================================================ FILE: cl/_testspx/basic/Game.tgmx ================================================ func onInit() { for { } } initGameApp ================================================ FILE: cl/_testspx/basic/Kai.tspx ================================================ func onMsg(msg string) { for { say "Hi" } } ================================================ FILE: cl/_testspx/basic/out.go ================================================ package main import "github.com/goplus/xgo/cl/internal/spx" type Game struct { *spx.MyGame } type Kai struct { spx.Sprite *Game } func (this *Game) onInit() { for { spx.SchedNow() } } func (this *Game) MainEntry() { this.InitGameApp() } func (this *Game) Main() { spx.Gopt_MyGame_Main(this) } func (this *Kai) onMsg(msg string) { for { spx.Sched() this.Say("Hi") } } func (this *Kai) Main() { } func main() { new(Game).Main() } ================================================ FILE: cl/_testspx/clsinit1/Rect.gox ================================================ var ( w, h = 10, 20 line = 3 color int border float64 = 1.2 ) ================================================ FILE: cl/_testspx/clsinit1/out.go ================================================ package main type Rect struct { w int h int line int color int border float64 } func (this *Rect) XGo_Init() *Rect { this.w, this.h = 10, 20 this.line = 3 this.border = 1.2 return this } ================================================ FILE: cl/_testspx/clsinit2/Rect.gox ================================================ var ( w, h = 10, 20 color int border float64 = 1.2 ) echo *this ================================================ FILE: cl/_testspx/clsinit2/out.go ================================================ package main import "fmt" type Rect struct { w int h int color int border float64 } func (this *Rect) Main() { this.XGo_Init() fmt.Println(*this) } func (this *Rect) XGo_Init() *Rect { this.w, this.h = 10, 20 this.border = 1.2 return this } func main() { new(Rect).Main() } ================================================ FILE: cl/_testspx/execgsh/demo.gsh ================================================ var ( score = 100 ) echo score xgo "run", "./foo" exec "ls $HOME" ls ${HOME} ================================================ FILE: cl/_testspx/execgsh/out.go ================================================ package main import ( "fmt" "github.com/qiniu/x/gsh" ) type demo struct { gsh.App score int } func (this *demo) MainEntry() { this.XGo_Init() fmt.Println(this.score) this.XGo_Exec("xgo", "run", "./foo") this.Exec__1("ls $HOME") this.XGo_Exec("ls", this.XGo_Env("HOME")) } func (this *demo) Main() { gsh.XGot_App_Main(this) } func (this *demo) XGo_Init() *demo { this.score = 100 return this } func main() { new(demo).Main() } ================================================ FILE: cl/_testspx/gshself/demo.gsh ================================================ import "github.com/goplus/xgo/cl/internal/dql" self := dql.Node{} ls ${HOME} ================================================ FILE: cl/_testspx/gshself/out.go ================================================ package main import ( "github.com/goplus/xgo/cl/internal/dql" "github.com/qiniu/x/gsh" ) type demo struct { gsh.App } func (this *demo) MainEntry() { self := dql.Node{} this.XGo_Exec("ls", this.XGo_Env("HOME")) } func (this *demo) Main() { gsh.XGot_App_Main(this) } func main() { new(demo).Main() } ================================================ FILE: cl/_testspx/init/init.tspx ================================================ ================================================ FILE: cl/_testspx/init/out.go ================================================ package main import "github.com/goplus/xgo/cl/internal/spx" type _init struct { spx.Sprite *MyGame } type MyGame struct { *spx.MyGame } func (this *MyGame) Main() { spx.Gopt_MyGame_Main(this) } func (this *_init) Main() { } func main() { new(MyGame).Main() } ================================================ FILE: cl/_testspx/multiworks/foo_prompt.gox ================================================ return "Hi" ================================================ FILE: cl/_testspx/multiworks/hello_tool.gox ================================================ return -1 ================================================ FILE: cl/_testspx/multiworks/main_mcp.gox ================================================ server "protos" ================================================ FILE: cl/_testspx/multiworks/out.go ================================================ package main import "github.com/goplus/xgo/cl/internal/mcp" type foo struct { mcp.Prompt *Game } type Tool_hello struct { mcp.Tool *Game } type Game struct { mcp.Game foo *foo } func (this *Game) MainEntry() { this.Server("protos") } func (this *Game) Main() { _xgo_obj0 := &Tool_hello{Game: this} _xgo_lst1 := []mcp.ToolProto{_xgo_obj0} _xgo_obj1 := &foo{Game: this} this.foo = _xgo_obj1 _xgo_lst2 := []mcp.PromptProto{_xgo_obj1} mcp.Gopt_Game_Main(this, nil, _xgo_lst1, _xgo_lst2) } func (this *foo) Main(_xgo_arg0 *mcp.Tool) string { this.Prompt.Main(_xgo_arg0) return "Hi" } func (this *Tool_hello) Main(_xgo_arg0 string) int { this.Tool.Main(_xgo_arg0) return -1 } func main() { new(Game).Main() } ================================================ FILE: cl/_testspx/newobj/Kai_spx.gox ================================================ ================================================ FILE: cl/_testspx/newobj/main_spx.gox ================================================ a := new a.run b := new(Sprite) echo b.name ================================================ FILE: cl/_testspx/newobj/out.go ================================================ package main import ( "fmt" "github.com/goplus/xgo/cl/internal/spx3" ) type Kai struct { spx3.Sprite *Game } type Game struct { spx3.Game } func (this *Game) MainEntry() { a := spx3.New() a.Run() b := new(spx3.Sprite) fmt.Println(b.Name()) } func (this *Game) Main() { _xgo_obj0 := &Kai{Game: this} spx3.Gopt_Game_Main(this, _xgo_obj0) } func (this *Kai) Main(_xgo_arg0 string) { this.Sprite.Main(_xgo_arg0) } func (this *Kai) Classfname() string { return "Kai" } func (this *Kai) Classclone() spx3.Handler { _xgo_ret := *this return &_xgo_ret } func main() { new(Game).Main() } ================================================ FILE: cl/_testspx/nogame/bar.tspx ================================================ ================================================ FILE: cl/_testspx/nogame/out.go ================================================ package main import "github.com/goplus/xgo/cl/internal/spx" type bar struct { spx.Sprite *MyGame } type MyGame struct { *spx.MyGame } func (this *MyGame) Main() { spx.Gopt_MyGame_Main(this) } func (this *bar) Main() { } func main() { new(MyGame).Main() } ================================================ FILE: cl/_testspx/singlework/Kai_spx.gox ================================================ echo jwt.token("Hi") ================================================ FILE: cl/_testspx/singlework/main_spx.gox ================================================ var ( Kai Kai ) run ================================================ FILE: cl/_testspx/singlework/out.go ================================================ package main import ( "fmt" "github.com/goplus/xgo/cl/internal/spx3" "github.com/goplus/xgo/cl/internal/spx3/jwt" ) type Kai struct { spx3.Sprite *Game } type Game struct { spx3.Game Kai Kai } func (this *Game) MainEntry() { this.Run() } func (this *Game) Main() { _xgo_obj0 := &Kai{Game: this} spx3.Gopt_Game_Main(this, _xgo_obj0) } func (this *Kai) Main(_xgo_arg0 string) { this.Sprite.Main(_xgo_arg0) fmt.Println(jwt.Token("Hi")) } func (this *Kai) Classfname() string { return "Kai" } func (this *Kai) Classclone() spx3.Handler { _xgo_ret := *this return &_xgo_ret } func main() { new(Game).Main() } ================================================ FILE: cl/_testspx/xgoinit_dup/Spr_spx.gox ================================================ echo "sprite main called" ================================================ FILE: cl/_testspx/xgoinit_dup/main_spx.gox ================================================ var ( score = 100 Spr Spr ) run ================================================ FILE: cl/_testspx/xgoinit_dup/out.go ================================================ package main import ( "fmt" "github.com/goplus/xgo/cl/internal/spx3" ) type Spr struct { spx3.Sprite *Game } type Game struct { spx3.Game score int Spr Spr } func (this *Game) MainEntry() { this.XGo_Init() this.Run() } func (this *Game) Main() { _xgo_obj0 := &Spr{Game: this} spx3.Gopt_Game_Main(this, _xgo_obj0) } func (this *Game) XGo_Init() *Game { this.score = 100 return this } func (this *Spr) Main(_xgo_arg0 string) { this.Sprite.Main(_xgo_arg0) fmt.Println("sprite main called") } func (this *Spr) Classfname() string { return "Spr" } func (this *Spr) Classclone() spx3.Handler { _xgo_ret := *this return &_xgo_ret } func main() { new(Game).Main() } ================================================ FILE: cl/builtin.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cl import ( "go/token" "go/types" "github.com/goplus/gogen" ) // ----------------------------------------------------------------------------- func initMathBig(_ *gogen.Package, conf *gogen.Config, big gogen.PkgRef) { conf.UntypedBigInt = big.Ref("UntypedBigint").Type().(*types.Named) conf.UntypedBigRat = big.Ref("UntypedBigrat").Type().(*types.Named) conf.UntypedBigFloat = big.Ref("UntypedBigfloat").Type().(*types.Named) } func initBuiltinFns(builtin *types.Package, scope *types.Scope, pkg gogen.PkgRef, fns []string) { for _, fn := range fns { fnTitle := string(fn[0]-'a'+'A') + fn[1:] scope.Insert(gogen.NewOverloadFunc(token.NoPos, builtin, fn, pkg.Ref(fnTitle))) } } func initBuiltin(_ *gogen.Package, builtin *types.Package, os, fmt, ng, osx, buil, reflect gogen.PkgRef) { scope := builtin.Scope() if ng.Types != nil { typs := []string{"bigint", "bigrat", "bigfloat"} for _, typ := range typs { name := string(typ[0]-('a'-'A')) + typ[1:] scope.Insert(types.NewTypeName(token.NoPos, builtin, typ, ng.Ref(name).Type())) } scope.Insert(types.NewTypeName(token.NoPos, builtin, "uint128", ng.Ref("Uint128").Type())) scope.Insert(types.NewTypeName(token.NoPos, builtin, "int128", ng.Ref("Int128").Type())) } if fmt.Types != nil { scope.Insert(gogen.NewOverloadFunc(token.NoPos, builtin, "echo", fmt.Ref("Println"))) initBuiltinFns(builtin, scope, fmt, []string{ "print", "println", "printf", "errorf", "fprint", "fprintln", "fprintf", "sprint", "sprintln", "sprintf", }) } if os.Types != nil { initBuiltinFns(builtin, scope, os, []string{ "open", "create", }) } if osx.Types != nil { initBuiltinFns(builtin, scope, osx, []string{ "lines", "errorln", "fatal", }) scope.Insert(gogen.NewOverloadFunc(token.NoPos, builtin, "blines", osx.Ref("BLines"))) } if reflect.Types != nil { scope.Insert(gogen.NewOverloadFunc(token.NoPos, builtin, "type", reflect.Ref("TypeOf"))) } if buil.Types != nil { scope.Insert(gogen.NewOverloadFunc(token.NoPos, builtin, "newRange", buil.Ref("NewRange__0"))) } scope.Insert(types.NewTypeName(token.NoPos, builtin, "any", gogen.TyEmptyInterface)) } const ( osxPkgPath = "github.com/qiniu/x/osx" ) func (ctx *pkgCtx) newBuiltinDefault(pkg *gogen.Package, conf *gogen.Config) *types.Package { builtin := types.NewPackage("", "") fmt := pkg.Import("fmt") os := pkg.TryImport("os") reflect := pkg.TryImport("reflect") osx := pkg.TryImport(osxPkgPath) buil := pkg.TryImport("github.com/qiniu/x/xgo") ng := pkg.TryImport("github.com/qiniu/x/xgo/ng") strx := pkg.TryImport("github.com/qiniu/x/stringutil") stringslice := pkg.TryImport("github.com/qiniu/x/stringslice") pkg.TryImport("strconv") pkg.TryImport("strings") if ng.Types != nil { initMathBig(pkg, conf, ng) } initBuiltin(pkg, builtin, os, fmt, ng, osx, buil, reflect) gogen.InitBuiltin(pkg, builtin, conf) if strx.Types != nil { ti := pkg.BuiltinTI(types.Typ[types.String]) ti.AddMethods( &gogen.BuiltinMethod{Name: "Capitalize", Fn: strx.Ref("Capitalize")}, ) builtin.Scope().Insert(gogen.NewOverloadFunc(token.NoPos, builtin, "contains", strx.Ref("Contains"))) } if stringslice.Types != nil { ti := pkg.BuiltinTI(types.NewSlice(types.Typ[types.String])) ti.AddMethods( &gogen.BuiltinMethod{Name: "Capitalize", Fn: stringslice.Ref("Capitalize")}, &gogen.BuiltinMethod{Name: "ToTitle", Fn: stringslice.Ref("ToTitle")}, &gogen.BuiltinMethod{Name: "ToUpper", Fn: stringslice.Ref("ToUpper")}, &gogen.BuiltinMethod{Name: "ToLower", Fn: stringslice.Ref("ToLower")}, &gogen.BuiltinMethod{Name: "Repeat", Fn: stringslice.Ref("Repeat")}, &gogen.BuiltinMethod{Name: "Replace", Fn: stringslice.Ref("Replace")}, &gogen.BuiltinMethod{Name: "ReplaceAll", Fn: stringslice.Ref("ReplaceAll")}, &gogen.BuiltinMethod{Name: "Trim", Fn: stringslice.Ref("Trim")}, &gogen.BuiltinMethod{Name: "TrimSpace", Fn: stringslice.Ref("TrimSpace")}, &gogen.BuiltinMethod{Name: "TrimLeft", Fn: stringslice.Ref("TrimLeft")}, &gogen.BuiltinMethod{Name: "TrimRight", Fn: stringslice.Ref("TrimRight")}, &gogen.BuiltinMethod{Name: "TrimPrefix", Fn: stringslice.Ref("TrimPrefix")}, &gogen.BuiltinMethod{Name: "TrimSuffix", Fn: stringslice.Ref("TrimSuffix")}, ) } return builtin } // ----------------------------------------------------------------------------- ================================================ FILE: cl/builtin_test.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cl import ( "go/types" "log" "testing" "github.com/goplus/gogen" "github.com/goplus/gogen/packages" "github.com/goplus/mod/modfile" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/token" ) var ( goxConf = getGoxConf() ) func getGoxConf() *gogen.Config { fset := token.NewFileSet() imp := packages.NewImporter(fset) return &gogen.Config{Fset: fset, Importer: imp} } func TestEmbeddedFieldCast(t *testing.T) { var o = new(types.Struct) embeddedFieldCast(o, nil, nil, visitedT{o: none{}}) embeddedFieldCast(o, nil, nil, visitedT{}) } func TestNonClosure(t *testing.T) { tn := types.NewTypeName(0, nil, "a", nil) if !nonClosure(types.NewNamed(tn, types.Typ[types.Int], nil)) { t.Fatal("nonClosure") } } func TestLoadExpr(t *testing.T) { var ni nodeInterp if v := ni.LoadExpr(&ast.Ident{Name: "x"}); v != "" { t.Fatal("LoadExpr:", v) } } func TestProjFile(t *testing.T) { var ni nodeInterp if v := ni.ProjFile(); v != nil { t.Fatal("ProjFile:", v) } } func TestSpriteOf(t *testing.T) { proj := &gmxProject{} if getSpxObj(proj, "a") != nil { t.Fatal("spriteOf: not nil?") } } func TestGetGameClass(t *testing.T) { proj := &gmxProject{ gameIsPtr: true, hasMain_: false, gameClass_: "", gt: &modfile.Project{ Class: "*App", PkgPaths: []string{"foo/bar"}, }, } ctx := &pkgCtx{ projs: map[string]*gmxProject{".gmx": proj, ".spx": proj}, nproj: 2, } if v := proj.getGameClass(ctx); v != "BarApp" { t.Fatal("getGameClass:", v) } } func TestSimplifyPkgPath(t *testing.T) { if simplifyPkgPath("c/lua") != "github.com/goplus/lib/c/lua" { t.Fatal("simplifyPkgPath: c/lua") } if simplifyPkgPath("cpp/std") != "github.com/goplus/lib/cpp/std" { t.Fatal("simplifyPkgPath: cpp/std") } } func TestCompileLambdaExpr(t *testing.T) { ctx := &blockCtx{ pkgCtx: &pkgCtx{}, } lhs := []*ast.Ident{ast.NewIdent("x")} sig := types.NewSignatureType(nil, nil, nil, nil, nil, false) e := compileLambdaExpr(ctx, &ast.LambdaExpr{Lhs: lhs}, sig) if ce := e.(*gogen.CodeError); ce.Msg != `too many arguments in lambda expression have (x) want ()` { t.Fatal("compileLambdaExpr:", ce.Msg) } } func TestCompileLambda1(t *testing.T) { defer func() { if e := recover(); e != nil { if ce := e.(*gogen.CodeError); ce.Msg != `too many arguments in lambda expression have (x) want ()` { t.Fatal("compileLambda:", ce.Msg) } } }() ctx := &blockCtx{ pkgCtx: &pkgCtx{}, } lhs := []*ast.Ident{ast.NewIdent("x")} sig := types.NewSignatureType(nil, nil, nil, nil, nil, false) compileLambda(ctx, &ast.LambdaExpr{Lhs: lhs}, sig) } func TestCompileLambda2(t *testing.T) { defer func() { if e := recover(); e != nil { if ce := e.(*gogen.CodeError); ce.Msg != `too many arguments in lambda expression have (x) want ()` { t.Fatal("compileLambda:", ce.Msg) } } }() ctx := &blockCtx{ pkgCtx: &pkgCtx{}, } lhs := []*ast.Ident{ast.NewIdent("x")} sig := types.NewSignatureType(nil, nil, nil, nil, nil, false) compileLambda(ctx, &ast.LambdaExpr2{Lhs: lhs}, sig) } func TestCompileExpr(t *testing.T) { defer func() { if e := recover(); e != nil { if ce := e.(*gogen.CodeError); ce.Msg != "compileExpr failed: unknown - *ast.Ellipsis" { t.Fatal("compileExpr:", ce.Msg) } } }() ctx := &blockCtx{pkgCtx: &pkgCtx{}} compileExpr(ctx, 0, &ast.Ellipsis{}) } func TestCompileStmt(t *testing.T) { old := enableRecover defer func() { enableRecover = old if e := recover(); e != "compileStmt failed: unknown - *ast.BadStmt\n" { t.Fatal("compileStmt:", e) } }() enableRecover = false ctx := &blockCtx{} compileStmt(ctx, &ast.BadStmt{}) } func TestTryXGoExec(t *testing.T) { pkg := gogen.NewPackage("", "foo", goxConf) if tryXGoExec(pkg.CB(), nil) { t.Fatal("tryXGoExec") } } func TestCompileFuncAlias(t *testing.T) { ctx := &blockCtx{ pkgCtx: &pkgCtx{ syms: map[string]loader{"Foo": &baseLoader{ fn: func() {}, }}, }, } scope := types.NewScope(nil, 0, 0, "") x := ast.NewIdent("foo") if compileFuncAlias(ctx, 0, scope, x, 0) { t.Fatal("compileFuncAlias: ok?") } } func TestErrStringLit(t *testing.T) { defer func() { if e := recover(); e == nil { t.Fatal("TestErrStringLit: no panic?") } }() compileStringLitEx(nil, nil, &ast.BasicLit{ Value: "Hello", Extra: &ast.StringLitEx{ Parts: []any{1}, }, }) } func TestErrPreloadFile(t *testing.T) { pkg := gogen.NewPackage("", "foo", goxConf) ctx := &blockCtx{pkgCtx: &pkgCtx{}} t.Run("unknown decl", func(t *testing.T) { defer func() { if e := recover(); e == nil || e != "TODO - cl.preloadFile: unknown decl - *ast.BadDecl\n" { t.Fatal("TestErrPreloadFile:", e) } }() decls := []ast.Decl{ &ast.BadDecl{}, } preloadFile(pkg, ctx, &ast.File{Decls: decls}, "", true) }) } func TestErrParseTypeEmbedName(t *testing.T) { defer func() { if e := recover(); e == nil { t.Fatal("TestErrParseTypeEmbedName: no panic?") } }() parseTypeEmbedName(&ast.StructType{}) } func TestGmxCheckProjs(t *testing.T) { _, multi := gmxCheckProjs(nil, &pkgCtx{ projs: map[string]*gmxProject{ ".a": {hasMain_: true}, ".b": {hasMain_: true}, }, }) if !multi { t.Fatal("gmxCheckProjs: not multi?") } } func TestGmxCheckProjs2(t *testing.T) { _, multi := gmxCheckProjs(nil, &pkgCtx{ projs: map[string]*gmxProject{ ".a": {}, ".b": {}, }, }) if !multi { t.Fatal("gmxCheckProjs: not multi?") } } func TestNodeInterp(t *testing.T) { ni := &nodeInterp{} if v := ni.Caller(&ast.Ident{}); v != "the function call" { t.Fatal("TestNodeInterp:", v) } defer func() { if e := recover(); e == nil { log.Fatal("TestNodeInterp: no error") } }() ni.Caller(&ast.CallExpr{}) } func TestMarkAutogen(t *testing.T) { old := noMarkAutogen noMarkAutogen = false NewPackage("", &ast.Package{Files: map[string]*ast.File{ "main.t2gmx": {IsProj: true}, }}, &Config{ LookupClass: lookupClassErr, }) noMarkAutogen = old } func TestClassNameAndExt(t *testing.T) { name, clsfile, ext := ClassNameAndExt("/foo/bar.abc_yap.gox") if name != "bar_abc" || clsfile != "bar.abc" || ext != "_yap.gox" { t.Fatal("classNameAndExt:", name, ext) } name, clsfile, ext = ClassNameAndExt("/foo/get-bar_:id.yap") if name != "get_bar_id" || clsfile != "get-bar_:id" || ext != ".yap" { t.Fatal("classNameAndExt:", name, ext) } } func TestFileClassType(t *testing.T) { type testData struct { isClass bool isNormalGox bool isProj bool fileName string classType string isTest bool found bool } tests := []*testData{ {false, false, false, "abc.gop", "", false, false}, {false, false, false, "abc.xgo", "", false, false}, {false, false, false, "abc_test.gop", "", true, false}, {false, false, false, "abc_test.xgo", "", true, false}, {true, true, false, "abc.gox", "abc", false, true}, {true, true, false, "Abc.gox", "Abc", false, true}, {true, true, false, "abc_demo.gox", "abc", false, true}, {true, true, false, "Abc_demo.gox", "Abc", false, true}, {true, true, false, "main.gox", "_main", false, true}, {true, true, false, "main_demo.gox", "_main", false, true}, {true, true, false, "abc_xtest.gox", "abc", false, true}, {true, true, false, "main_xtest.gox", "_main", false, true}, {true, true, false, "abc_test.gox", "case_abc", true, true}, {true, true, false, "Abc_test.gox", "caseAbc", true, true}, {true, true, false, "main_test.gox", "case_main", true, true}, {true, false, false, "get.yap", "get", false, true}, {true, false, false, "get_p_#id.yap", "get_p_id", false, true}, {true, false, true, "main.yap", "AppV2", false, true}, {true, false, false, "abc_yap.gox", "abc", false, true}, {true, false, false, "Abc_yap.gox", "Abc", false, true}, {true, false, true, "main_yap.gox", "App", false, true}, {true, false, true, "abc_yap.gox", "abc", false, true}, {true, false, true, "Abc_yap.gox", "Abc", false, true}, {true, false, true, "main_yap.gox", "App", false, true}, {true, false, false, "abc_ytest.gox", "case_abc", true, true}, {true, false, false, "Abc_ytest.gox", "caseAbc", true, true}, {true, false, true, "main_ytest.gox", "App", true, true}, } lookupClass := func(ext string) (c *Project, ok bool) { switch ext { case ".yap": return &modfile.Project{ Ext: ".yap", Class: "AppV2", Works: []*modfile.Class{{Ext: ".yap", Class: "Handler"}}, PkgPaths: []string{"github.com/goplus/yap"}}, true case "_yap.gox": return &modfile.Project{ Ext: "_yap.gox", Class: "App", PkgPaths: []string{"github.com/goplus/yap"}}, true case "_ytest.gox": return &modfile.Project{ Ext: "_ytest.gox", Class: "App", Works: []*modfile.Class{{Ext: "_ytest.gox", Class: "Case"}}, PkgPaths: []string{"github.com/goplus/yap/ytest", "testing"}}, true } return } for _, test := range tests { _ = test.found f := &ast.File{IsClass: test.isClass, IsNormalGox: test.isNormalGox, IsProj: test.isProj} classType, isTest := GetFileClassType(f, test.fileName, lookupClass) if isTest != test.isTest { t.Fatalf("%v check classType isTest want %v, got %v.", test.fileName, test.isTest, isTest) } if classType != test.classType { t.Fatalf("%v getClassType want %v, got %v.", test.fileName, test.classType, classType) } } } func TestErrMultiStarRecv(t *testing.T) { _, _, ok := getRecvType(&ast.StarExpr{ X: &ast.StarExpr{}, }) if ok { t.Fatal("TestErrMultiStarRecv: no error?") } } func TestErrAssign(t *testing.T) { defer func() { if e := recover(); e == nil { t.Fatal("TestErrAssign: no panic?") } }() ctx := &blockCtx{} compileAssignStmt(ctx, &ast.AssignStmt{ Tok: token.DEFINE, Lhs: []ast.Expr{ &ast.SelectorExpr{ X: ast.NewIdent("foo"), Sel: ast.NewIdent("bar"), }, }, }) } func TestErrPanicToRecv(t *testing.T) { ctx := &blockCtx{ tlookup: &typeParamLookup{ []*types.TypeParam{ types.NewTypeParam(types.NewTypeName(0, nil, "t", nil), nil), }, }, } recv := &ast.FieldList{ List: []*ast.Field{ {Type: &ast.SelectorExpr{}}, }, } func() { defer func() { if e := recover(); e == nil { t.Fatal("TestErrPanicToRecv: no panic?") } }() toRecv(ctx, recv) }() } func TestCompileErrWrapExpr(t *testing.T) { defer func() { if e := recover(); e != "TODO: can't use expr? in global" { t.Fatal("TestCompileErrWrapExpr failed") } }() pkg := gogen.NewPackage("", "foo", goxConf) ctx := &blockCtx{pkg: pkg, cb: pkg.CB()} compileErrWrapExpr(ctx, 0, &ast.ErrWrapExpr{Tok: token.QUESTION}, 0) } func TestToString(t *testing.T) { defer func() { if e := recover(); e == nil { t.Fatal("toString: no error?") } }() toString(&ast.BasicLit{Kind: token.INT, Value: "1"}) } func TestGetTypeName(t *testing.T) { if getTypeName(types.Typ[types.Int]) != "int" { t.Fatal("getTypeName int failed") } defer func() { if e := recover(); e == nil { t.Fatal("getTypeName: no error?") } }() getTypeName(types.NewSlice(types.Typ[types.Int])) } func TestHandleRecover(t *testing.T) { var ctx pkgCtx ctx.handleRecover("hello", nil) if !(len(ctx.errs) == 1 && ctx.errs[0].Error() == "hello") { t.Fatal("TestHandleRecover failed:", ctx.errs) } } func TestCheckCommandWithoutArgs(t *testing.T) { if checkCommandWithoutArgs( &ast.SelectorExpr{ X: &ast.SelectorExpr{X: ast.NewIdent("foo"), Sel: ast.NewIdent("bar")}, Sel: ast.NewIdent("val"), }) != clCommandWithoutArgs { t.Fatal("TestCanAutoCall failed") } } func TestClRangeStmt(t *testing.T) { ctx := &blockCtx{ cb: &gogen.CodeBuilder{}, } stmt := &ast.RangeStmt{ Tok: token.DEFINE, X: &ast.SliceLit{}, Body: &ast.BlockStmt{}, } compileRangeStmt(ctx, stmt) stmt.Tok = token.ASSIGN stmt.Value = &ast.Ident{Name: "_"} compileRangeStmt(ctx, stmt) } // ----------------------------------------------------------------------------- func TestGetStringConst(t *testing.T) { spx := gogen.PkgRef{Types: types.NewPackage("", "foo")} if v := getStringConst(spx, "unknown"); v != "" { t.Fatal("getStringConst:", v) } } func TestSpxRef(t *testing.T) { defer func() { if e := recover(); !isError(e, "foo.bar not found") { t.Fatal("TestSpxRef:", e) } }() pkg := gogen.PkgRef{ Types: types.NewPackage("foo", "foo"), } spxRef(pkg, "bar") } func isError(e any, msg string) bool { if e != nil { if err, ok := e.(error); ok { return err.Error() == msg } if err, ok := e.(string); ok { return err == msg } } return false } func TestGmxProject(t *testing.T) { pkg := gogen.NewPackage("", "foo", goxConf) ctx := &pkgCtx{ projs: make(map[string]*gmxProject), classes: make(map[*ast.File]*gmxClass), } gmx := loadClass(ctx, pkg, "main.t2gmx", &ast.File{IsProj: true}, &Config{ LookupClass: lookupClass, }) scheds := gmx.getScheds(pkg.CB()) if len(scheds) != 2 || scheds[0] == nil || scheds[0] != scheds[1] { t.Fatal("TestGmxProject failed") } gmx.hasScheds = false if gmx.getScheds(nil) != nil { t.Fatal("TestGmxProject failed: hasScheds?") } func() { defer func() { if e := recover(); e != "class not found: .abcx" { t.Fatal("TestGmxProject failed:", e) } }() loadClass(nil, pkg, "main.abcx", &ast.File{IsProj: true}, &Config{ LookupClass: lookupClass, }) }() func() { defer func() { if e := recover(); e != "multiple project files found: main, main" { t.Fatal("TestGmxProject failed:", e) } }() loadClass(ctx, pkg, "main.t2gmx", &ast.File{IsProj: true}, &Config{ LookupClass: lookupClass, }) }() } func TestSpxLookup(t *testing.T) { defer func() { if e := recover(); e == nil { t.Fatal("TestSpxLookup failed: no error?") } }() spxLookup(nil, "foo") } func lookupClass(ext string) (c *modfile.Project, ok bool) { switch ext { case ".t2gmx", ".t2spx": return &modfile.Project{ Ext: ".t2gmx", Class: "Game", Works: []*modfile.Class{{Ext: ".t2spx", Class: "Sprite"}}, PkgPaths: []string{"github.com/goplus/xgo/cl/internal/spx2"}}, true } return } func lookupClassErr(ext string) (c *modfile.Project, ok bool) { switch ext { case ".t2gmx", ".t2spx": return &modfile.Project{ Ext: ".t2gmx", Class: "Game", Works: []*modfile.Class{{Ext: ".t2spx", Class: "Sprite"}}, PkgPaths: []string{"github.com/goplus/xgo/cl/internal/libc"}}, true } return } func TestGetGoFile(t *testing.T) { if f := genGoFile("a_test.gop", false); f != testingGoFile { t.Fatal("TestGetGoFile:", f) } if f := genGoFile("a_test.xgo", false); f != testingGoFile { t.Fatal("TestGetGoFile:", f) } if f := genGoFile("a_test.gox", true); f != testingGoFile { t.Fatal("TestGetGoFile:", f) } if f := genGoFile("a.gop", false); f != defaultGoFile { t.Fatal("TestGetGoFile:", f) } if f := genGoFile("a.xgo", false); f != defaultGoFile { t.Fatal("TestGetGoFile:", f) } } func TestErrNewType(t *testing.T) { testPanic(t, `bar redeclared in this block previous declaration at `, func() { pkg := types.NewPackage("", "foo") newType(pkg, token.NoPos, "bar") newType(pkg, token.NoPos, "bar") }) } func TestErrCompileBasicLit(t *testing.T) { testPanic(t, "compileBasicLit: invalid syntax\n", func() { ctx := &blockCtx{cb: new(gogen.CodeBuilder)} compileBasicLit(ctx, &ast.BasicLit{Kind: token.CSTRING, Value: `\\x`}) }) } func testPanic(t *testing.T, panicMsg string, doPanic func()) { t.Run(panicMsg, func(t *testing.T) { defer func() { if e := recover(); e == nil { t.Fatal("testPanic: no error?") } else if msg := e.(string); msg != panicMsg { t.Fatalf("\nResult:\n%s\nExpected Panic:\n%s\n", msg, panicMsg) } }() doPanic() }) } func TestClassFileEnd(t *testing.T) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, "get.yap", `json {"id": ${id} }`, parser.ParseXGoClass) if err != nil { t.Fatal(err) } if f.End() != f.ShadowEntry.End() { t.Fatal("class file end not shadow entry") } } // ----------------------------------------------------------------------------- ================================================ FILE: cl/c.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cl import ( "strings" ) // ----------------------------------------------------------------------------- const ( pathLibc = "github.com/goplus/lib/c" pathLibpy = "github.com/goplus/lib/py" pathLibcpp = "github.com/goplus/lib/cpp" ) func simplifyXgoPackage(pkgPath string) string { if strings.HasPrefix(pkgPath, "xgo/") || strings.HasPrefix(pkgPath, "gop/") { return "github.com/goplus/xgo/" + pkgPath[4:] } return pkgPath } func simplifyPkgPath(pkgPath string) string { switch pkgPath { case "c": return pathLibc case "py": return pathLibpy default: if strings.HasPrefix(pkgPath, "c/") { return pathLibc + pkgPath[1:] } if strings.HasPrefix(pkgPath, "py/") { return pathLibpy + pkgPath[2:] } if strings.HasPrefix(pkgPath, "cpp/") { return pathLibcpp + pkgPath[3:] } return simplifyXgoPackage(pkgPath) } } // ----------------------------------------------------------------------------- ================================================ FILE: cl/classfile.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cl import ( goast "go/ast" "go/constant" gotoken "go/token" "go/types" "log" "path" "path/filepath" "strconv" "strings" "github.com/goplus/gogen" "github.com/goplus/mod/modfile" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/token" "github.com/qiniu/x/stringutil" ) // ----------------------------------------------------------------------------- type gmxClass struct { name string // class type, empty for default project class clsfile string ext string proj *gmxProject sp *spxObj } func (p *gmxClass) getName(ctx *pkgCtx) string { tname := p.name if tname == "" { // use default project class tname = p.proj.getGameClass(ctx) } return tname } type spxObj struct { obj gogen.Ref // work base class ext string proto string // work class prototype prefix string // work class prefix feats spriteFeat // work class features clone *types.Signature // prototype of Classclone types []string // work classes (ie. .spx) } func spriteByProto(sprites []*spxObj, proto string) *spxObj { for _, sp := range sprites { if sp.proto == proto { return sp } } return nil } type gmxProject struct { gameClass_ string // .gmx game gogen.Ref // Game (project base class) sprites []*spxObj // .spx => Sprite scheds []string schedStmts []goast.Stmt // nil or len(scheds) == 2 (delayload) pkgImps []gogen.PkgRef pkgPaths []string autoimps map[string]pkgImp // auto-import statement in gox.mod gt *Project hasScheds bool gameIsPtr bool isTest bool hasMain_ bool } func (p *gmxProject) embed(chk func(name string) bool, flds []*types.Var, pkg *gogen.Package) []*types.Var { for _, sp := range p.sprites { if sp.feats&spriteEmbedded != 0 { for _, spt := range sp.types { spto := pkg.Ref(spt) // work class if chk != nil && !chk(spto.Name()) { pt := types.NewPointer(spto.Type()) // pointer to work class flds = append(flds, types.NewField(token.NoPos, pkg.Types, spto.Name(), pt, false)) } } } } return flds } type spriteFeat uint const ( spriteClassfname spriteFeat = 1 << iota spriteClassclone spriteEmbedded spriteFeat = 0x80 ) func spriteFeature(elt types.Type, sp *spxObj) { if intf, ok := elt.(*types.Interface); ok { for i, n := 0, intf.NumMethods(); i < n; i++ { switch m := intf.Method(i); m.Name() { case "Classfname": sp.feats |= spriteClassfname case "Classclone": sp.clone = m.Type().(*types.Signature) sp.feats |= spriteClassclone } } } } func spriteFeatures(game gogen.Ref, sprites []*spxObj) { if mainFn := findMethod(game, "Main"); mainFn != nil { sig := mainFn.Type().(*types.Signature) if t, ok := gogen.CheckSigFuncEx(sig); ok { if t, ok := t.(*gogen.TyTemplateRecvMethod); ok { sig = t.Func.Type().(*types.Signature) } } if n := len(sprites); n == 1 && sig.Variadic() { // single work class in := sig.Params() last := in.At(in.Len() - 1) elt := last.Type().(*types.Slice).Elem() if tn, ok := elt.(*types.Named); ok { elt = tn.Underlying() } spriteFeature(elt, sprites[0]) } else { // multiple work classes in := sig.Params() for i, narg := 1, in.Len(); i < narg; i++ { // TODO(xsw): error handling tslice := in.At(i).Type().(*types.Slice) tn := tslice.Elem().(*types.Named) sp := spriteByProto(sprites, tn.Obj().Name()) spriteFeature(tn.Underlying(), sp) } } } } func (p *gmxProject) getGameClass(ctx *pkgCtx) string { tname := p.gameClass_ // project class if tname != "" && tname != "main" { return tname } gt := p.gt tname = gt.Class // project base class if p.gameIsPtr { tname = tname[1:] } if ctx.nproj > 1 && !p.hasMain_ { tname = stringutil.Capitalize(path.Base(gt.PkgPaths[0])) + tname } return tname } func isTestClass(pkg gogen.PkgRef) bool { scope := pkg.Types.Scope() return scope.Lookup("XGoTestClass") != nil || scope.Lookup("GopTestClass") != nil } func (p *gmxProject) hasMain() bool { if !p.hasMain_ { imps := p.pkgImps p.hasMain_ = len(imps) > 0 && isTestClass(imps[0]) } return p.hasMain_ } func (p *gmxProject) getScheds(cb *gogen.CodeBuilder) []goast.Stmt { if p == nil || !p.hasScheds { return nil } if p.schedStmts == nil { p.schedStmts = make([]goast.Stmt, 2) for i, v := range p.scheds { fn := cb.Val(spxLookup(p.pkgImps, v)).Call(0).InternalStack().Pop().Val p.schedStmts[i] = &goast.ExprStmt{X: fn} } if len(p.scheds) < 2 { p.schedStmts[1] = p.schedStmts[0] } } return p.schedStmts } var ( repl = strings.NewReplacer(":", "", "#", "", "-", "_", ".", "_") ) func ClassNameAndExt(file string) (name, clsfile, ext string) { fname := filepath.Base(file) clsfile, ext = modfile.SplitFname(fname) name = clsfile if strings.ContainsAny(name, ":#-.") { name = repl.Replace(name) } return } // GetFileClassType get ast.File classType // TODO(xsw): to refactor // // Deprecated: Don't use it func GetFileClassType(file *ast.File, filename string, lookupClass func(ext string) (c *Project, ok bool)) (classType string, isTest bool) { if file.IsClass { var ext string classType, _, ext = ClassNameAndExt(filename) if file.IsNormalGox { isTest = strings.HasSuffix(ext, "_test.gox") if !isTest && classType == "main" { classType = "_main" } } else { isTest = strings.HasSuffix(ext, "test.gox") } if file.IsProj && classType == "main" { if gt, ok := lookupClass(ext); ok { classType = gt.Class } } else if isTest { classType = casePrefix + testNameSuffix(classType) } } else if strings.HasSuffix(filename, "_test.xgo") || strings.HasSuffix(filename, "_test.gop") { isTest = true } return } func isGoxTestFile(ext string) bool { return strings.HasSuffix(ext, "test.gox") } func loadClass(ctx *pkgCtx, pkg *gogen.Package, file string, f *ast.File, conf *Config) *gmxProject { tname, clsfile, ext := ClassNameAndExt(file) gt, ok := conf.LookupClass(ext) if !ok { panic("class not found: " + ext) } p, ok := ctx.projs[gt.Ext] if !ok { pkgPaths := gt.PkgPaths p = &gmxProject{pkgPaths: pkgPaths, isTest: isGoxTestFile(ext), gt: gt} ctx.projs[gt.Ext] = p p.pkgImps = make([]gogen.PkgRef, len(pkgPaths)) for i, pkgPath := range pkgPaths { p.pkgImps[i] = pkg.Import(pkgPath) } if len(gt.Import) > 0 { autoimps := make(map[string]pkgImp) for _, imp := range gt.Import { pkgi := pkg.Import(imp.Path) name := imp.Name if name == "" { name = pkgi.Types.Name() } pkgName := types.NewPkgName(token.NoPos, pkg.Types, name, pkgi.Types) autoimps[name] = pkgImp{pkgi, pkgName} } p.autoimps = autoimps } spx := p.pkgImps[0] nWork := len(gt.Works) sprites := make([]*spxObj, nWork) for i, v := range gt.Works { if nWork > 1 && v.Proto == "" { panic("should have prototype if there are multiple work classes") } obj, _ := spxRef(spx, v.Class) sp := &spxObj{obj: obj, ext: v.Ext, proto: v.Proto, prefix: v.Prefix} if v.Embedded { sp.feats |= spriteEmbedded } sprites[i] = sp } p.sprites = sprites if gt.Class != "" { p.game, p.gameIsPtr = spxRef(spx, gt.Class) spriteFeatures(p.game, sprites) } if x := getStringConst(spx, "Gop_sched"); x != "" { // keep Gop_sched p.scheds, p.hasScheds = strings.SplitN(x, ",", 2), true } } cls := &gmxClass{clsfile: clsfile, ext: ext, proj: p} if f.IsProj { if p.gameClass_ != "" { panic("multiple project files found: " + tname + ", " + p.gameClass_) } p.gameClass_ = tname p.hasMain_ = f.HasShadowEntry() if !p.isTest { ctx.nproj++ } if tname != "main" { cls.name = tname } } else { sp := getSpxObj(p, ext) tname := spName(sp, tname) sp.types = append(sp.types, tname) cls.sp = sp cls.name = tname } ctx.classes[f] = cls if debugLoad { log.Println("==> InitClass", tname, "isProj:", f.IsProj) } return p } type none = struct{} var specialNames = map[string]none{ "init": {}, "main": {}, "go": {}, "goto": {}, "type": {}, "var": {}, "import": {}, "package": {}, "interface": {}, "struct": {}, "const": {}, "func": {}, "map": {}, "for": {}, "if": {}, "else": {}, "switch": {}, "case": {}, "select": {}, "defer": {}, "range": {}, "return": {}, "break": {}, "continue": {}, "fallthrough": {}, "default": {}, } func spName(sp *spxObj, name string) string { if sp.prefix != "" { return sp.prefix + name } if _, ok := specialNames[name]; ok { name = "_" + name } return name } func getSpxObj(p *gmxProject, ext string) *spxObj { for _, sp := range p.sprites { if sp.ext == ext { return sp } } return nil } func spxLookup(pkgImps []gogen.PkgRef, name string) gogen.Ref { for _, pkg := range pkgImps { if o := pkg.TryRef(name); o != nil { return o } } panic("spxLookup: symbol not found - " + name) } func spxTryRef(spx gogen.PkgRef, typ string) (obj types.Object, isPtr bool) { if strings.HasPrefix(typ, "*") { typ, isPtr = typ[1:], true } obj = spx.TryRef(typ) return } func spxRef(spx gogen.PkgRef, typ string) (obj gogen.Ref, isPtr bool) { obj, isPtr = spxTryRef(spx, typ) if obj == nil { panic(spx.Types.Name() + "." + typ + " not found") } return } func getStringConst(spx gogen.PkgRef, name string) string { if o := spx.TryRef(name); o != nil { if c, ok := o.(*types.Const); ok { return constant.StringVal(c.Val()) } } return "" } func setBodyHandler(ctx *blockCtx) { if proj := ctx.proj; proj != nil { // in an XGo class file if scheds := proj.getScheds(ctx.cb); scheds != nil { ctx.cb.SetBodyHandler(func(body *goast.BlockStmt, kind int) { idx := 0 if len(body.List) == 0 { idx = 1 } gogen.InsertStmtFront(body, scheds[idx]) }) } } } const ( casePrefix = "case" ) func testNameSuffix(testType string) string { if c := testType[0]; c >= 'A' && c <= 'Z' { return testType } return "_" + testType } func gmxTestFunc(pkg *gogen.Package, testType string, isProj bool) { if isProj { genTestFunc(pkg, "TestMain", testType, "m", "M") } else { name := testNameSuffix(testType) genTestFunc(pkg, "Test"+name, casePrefix+name, "t", "T") } } func genTestFunc(pkg *gogen.Package, name, testType, param, paramType string) { testing := pkg.Import("testing") objT := testing.Ref(paramType) paramT := types.NewParam(token.NoPos, pkg.Types, param, types.NewPointer(objT.Type())) params := types.NewTuple(paramT) pkg.NewFunc(nil, name, params, nil, false).BodyStart(pkg). Val(pkg.Builtin().Ref("new")).Val(pkg.Ref(testType)).Call(1). MemberVal("TestMain", 0).Val(paramT).Call(1).EndStmt(). End() } func gmxCheckProjs(pkg *gogen.Package, ctx *pkgCtx) (*gmxProject, bool) { var projMain, projNoMain *gmxProject var multiMain, multiNoMain bool for _, v := range ctx.projs { if v.isTest { continue } if v.hasMain() { if projMain != nil { multiMain = true } else { projMain = v } } else { if projNoMain != nil { multiNoMain = true } else { projNoMain = v } } if v.game != nil { gmxProjMain(pkg, ctx, v) } } if projMain != nil { return projMain, multiMain } return projNoMain, multiNoMain } func gmxProjMain(pkg *gogen.Package, parent *pkgCtx, proj *gmxProject) { base := proj.game // project base class classType := proj.getGameClass(parent) // project class ld := getTypeLoader(parent, parent.syms, token.NoPos, token.NoPos, classType) if ld.typ == nil { // no project class, use default ld.typ = func() { if debugLoad { log.Println("==> Load > NewType", classType) } old, _ := pkg.SetCurFile(defaultGoFile, true) defer pkg.RestoreCurFile(old) baseType := base.Type() if proj.gameIsPtr { baseType = types.NewPointer(baseType) } flds := proj.embed(nil, []*types.Var{ types.NewField(token.NoPos, pkg.Types, base.Name(), baseType, true), }, pkg) decl := pkg.NewTypeDefs().NewType(classType) ld.typInit = func() { // decycle if debugLoad { log.Println("==> Load > InitType", classType) } old, _ := pkg.SetCurFile(defaultGoFile, true) defer pkg.RestoreCurFile(old) decl.InitType(pkg, types.NewStruct(flds, nil)) } parent.tylds = append(parent.tylds, ld) } } ld.methods = append(ld.methods, func() { old, _ := pkg.SetCurFile(defaultGoFile, true) defer pkg.RestoreCurFile(old) doInitType(ld) t := pkg.Ref(classType).Type() recv := types.NewParam(token.NoPos, pkg.Types, "this", types.NewPointer(t)) sig := types.NewSignatureType(recv, nil, nil, nil, nil, false) fn, err := pkg.NewFuncWith(token.NoPos, "Main", sig, func() gotoken.Pos { // parent.ProjFile() never be nil here return parent.ProjFile().Pos() }) if err != nil { panic(err) } parent.inits = append(parent.inits, func() { old, _ := pkg.SetCurFile(defaultGoFile, true) defer pkg.RestoreCurFile(old) cb := fn.BodyStart(pkg).Typ(base.Type()).MemberVal("Main", 0) stk := cb.InternalStack() // force remove //line comments for main func cb.SetComments(nil, false) mainFn := stk.Pop() sigParams := mainFn.Type.(*types.Signature).Params() callMain := func() { src := parent.lookupClassNode(proj.gameClass_) stk.Push(mainFn) if _, isPtr := sigParams.At(0).Type().(*types.Pointer); isPtr { cb.Val(recv, src).MemberRef(base.Name()).UnaryOp(gotoken.AND) } else { cb.Val(recv, src) // template recv method } } iobj := 0 narg := sigParams.Len() if narg > 1 { sprites := proj.sprites if len(sprites) == 1 && sprites[0].proto == "" { // no work class prototype sp := sprites[0] narg = 1 + len(sp.types) genWorkClasses(pkg, parent, cb, recv, sp, iobj, -1, callMain) } else { lstNames := make([]string, narg) for i := 1; i < narg; i++ { tslice := sigParams.At(i).Type() tn := tslice.(*types.Slice).Elem().(*types.Named) sp := spriteByProto(sprites, tn.Obj().Name()) // work class if n := len(sp.types); n > 0 { lstNames[i] = genWorkClasses(pkg, parent, cb, recv, sp, iobj, i, nil) cb.SliceLitEx(tslice, n, false).EndInit(1) iobj += n } } callMain() for i := 1; i < narg; i++ { if lstName := lstNames[i]; lstName != "" { cb.VarVal(lstName) } else { cb.Val(nil) } } } } else { callMain() } cb.Call(narg).EndStmt().End() }) }) } func genWorkClasses( pkg *gogen.Package, parent *pkgCtx, cb *gogen.CodeBuilder, recv *types.Var, sp *spxObj, iobj, ilst int, callMain func()) (lstName string) { const ( indexGame = 1 objNamePrefix = "_xgo_obj" lstNamePrefix = "_xgo_lst" ) embedded := (sp.feats&spriteEmbedded != 0) sptypes := sp.types for i, spt := range sptypes { src := parent.lookupClassNode(spt) spto := pkg.Ref(spt) objName := objNamePrefix + strconv.Itoa(iobj+i) cb.DefineVarStart(token.NoPos, objName). Val(indexGame, src).Val(recv, src).StructLit(spto.Type(), 2, true, src). UnaryOp(gotoken.AND).EndInit(1) if embedded { cb.Val(recv, src).MemberRef(spt, src).VarVal(objName, src).Assign(1) } } if ilst > 0 { lstName = lstNamePrefix + strconv.Itoa(ilst-1) cb.DefineVarStart(token.NoPos, lstName) } else { callMain() } for i, spt := range sptypes { src := parent.lookupClassNode(spt) objName := objNamePrefix + strconv.Itoa(iobj+i) cb.VarVal(objName, src) } return } func genMainFunc(pkg *gogen.Package, gameClass string) { if o := pkg.TryRef(gameClass); o != nil { // force remove //line comments for main func pkg.CB().SetComments(nil, false) // new(gameClass).Main() new := pkg.Builtin().Ref("new") pkg.NewFunc(nil, "main", nil, nil, false).BodyStart(pkg). Val(new).Val(o).Call(1).MemberVal("Main", 0).Call(0).EndStmt(). End() } } func findMethod(o types.Object, name string) *types.Func { if obj, ok := o.(*types.TypeName); ok { return findMethodByType(obj.Type(), name) } return nil } func findMethodByType(typ types.Type, name string) *types.Func { if t, ok := typ.(*types.Named); ok { for i, n := 0, t.NumMethods(); i < n; i++ { f := t.Method(i) if f.Name() == name { return f } } } return nil } func makeMainSig(recv *types.Var, f *types.Func) *types.Signature { const ( namePrefix = "_xgo_arg" ) sig := f.Type().(*types.Signature) in := sig.Params() nin := in.Len() pkg := recv.Pkg() params := make([]*types.Var, nin) for i := 0; i < nin; i++ { paramName := namePrefix + strconv.Itoa(i) params[i] = types.NewParam(token.NoPos, pkg, paramName, in.At(i).Type()) } return types.NewSignatureType(recv, nil, nil, types.NewTuple(params...), sig.Results(), false) } func genClassfname(ctx *blockCtx, c *gmxClass) { pkg := ctx.pkg recv := toRecv(ctx, ctx.classRecv) ret := types.NewTuple(pkg.NewParam(token.NoPos, "", types.Typ[types.String])) pkg.NewFunc(recv, "Classfname", nil, ret, false).BodyStart(pkg). Val(c.clsfile).Return(1). End() } func genClassclone(ctx *blockCtx, classclone *types.Signature) { const ( nameRet = "_xgo_ret" ) pkg := ctx.pkg recv := toRecv(ctx, ctx.classRecv) ret := classclone.Results() pkg.NewFunc(recv, "Classclone", nil, ret, false).BodyStart(pkg). DefineVarStart(token.NoPos, nameRet).VarVal("this").Elem().EndInit(1). VarVal(nameRet).UnaryOp(gotoken.AND).Return(1). End() } func astEmptyEntrypoint(f *ast.File) { var entry = getEntrypoint(f) var hasEntry bool for _, decl := range f.Decls { switch d := decl.(type) { case *ast.FuncDecl: if d.Name.Name == entry { hasEntry = true } } } if !hasEntry { f.Decls = append(f.Decls, &ast.FuncDecl{ Name: &ast.Ident{ Name: entry, }, Type: &ast.FuncType{ Params: &ast.FieldList{}, }, Body: &ast.BlockStmt{}, Shadow: true, }) } } func getEntrypoint(f *ast.File) string { switch { case f.IsProj: return "MainEntry" case f.IsClass: return "Main" case inMainPkg(f): return "main" default: return "init" } } func inMainPkg(f *ast.File) bool { return f.Name.Name == "main" } // ----------------------------------------------------------------------------- ================================================ FILE: cl/cltest/cltest.go ================================================ /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cltest import ( "bytes" "io/fs" "log" "os" "path" "strings" "testing" "github.com/goplus/gogen" "github.com/goplus/mod" "github.com/goplus/mod/env" "github.com/goplus/mod/modfile" "github.com/goplus/mod/xgomod" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/parser/fsx" "github.com/goplus/xgo/parser/fsx/memfs" "github.com/goplus/xgo/scanner" "github.com/goplus/xgo/token" "github.com/goplus/xgo/tool" "github.com/qiniu/x/test" ) var ( XGoRoot string XGo *env.XGo Conf *cl.Config ) func init() { XGo = &env.XGo{Version: "1.0"} gogen.SetDebug(gogen.DbgFlagAll) cl.SetDebug(cl.DbgFlagAll | cl.FlagNoMarkAutogen) fset := token.NewFileSet() imp := tool.NewImporter(nil, XGo, fset) XGoRoot, _, _ = mod.FindGoMod("") Conf = &cl.Config{ Fset: fset, Importer: imp, Recorder: gopRecorder{}, LookupClass: LookupClass, NoFileLine: true, NoAutoGenMain: true, } } // ----------------------------------------------------------------------------- func LookupClass(ext string) (c *modfile.Project, ok bool) { switch ext { case ".tgmx", ".tspx": return &modfile.Project{ Ext: ".tgmx", Class: "*MyGame", Works: []*modfile.Class{{Ext: ".tspx", Class: "Sprite"}}, PkgPaths: []string{"github.com/goplus/xgo/cl/internal/spx", "math"}}, true case ".t2gmx", ".t2spx": return &modfile.Project{ Ext: ".t2gmx", Class: "Game", Works: []*modfile.Class{ {Ext: ".t2spx", Class: "Sprite"}, }, PkgPaths: []string{"github.com/goplus/xgo/cl/internal/spx2"}}, true case ".t4gmx", ".t4spx": return &modfile.Project{ Ext: ".t4gmx", Class: "*MyGame", Works: []*modfile.Class{{Ext: ".t4spx", Class: "Sprite"}}, PkgPaths: []string{"github.com/goplus/xgo/cl/internal/spx4", "math"}}, true case ".t5gmx", ".t5spx": return &modfile.Project{ Ext: ".t5gmx", Class: "*MyGame", Works: []*modfile.Class{{Ext: ".t5spx", Class: "Sprite", Embedded: true}}, PkgPaths: []string{"github.com/goplus/xgo/cl/internal/spx4", "math"}}, true case "_spx.gox": return &modfile.Project{ Ext: "_spx.gox", Class: "Game", Works: []*modfile.Class{{Ext: "_spx.gox", Class: "Sprite"}}, PkgPaths: []string{"github.com/goplus/xgo/cl/internal/spx3", "math"}, Import: []*modfile.Import{{Path: "github.com/goplus/xgo/cl/internal/spx3/jwt"}}}, true case "_xtest.gox": return &modfile.Project{ Ext: "_xtest.gox", Class: "App", Works: []*modfile.Class{{Ext: "_xtest.gox", Class: "Case"}}, PkgPaths: []string{"github.com/goplus/xgo/test", "testing"}}, true case "_mcp.gox", "_tool.gox", "_prompt.gox": return &modfile.Project{ Ext: "_mcp.gox", Class: "Game", Works: []*modfile.Class{ {Ext: "_tool.gox", Class: "Tool", Proto: "ToolProto", Prefix: "Tool_"}, {Ext: "_prompt.gox", Class: "Prompt", Proto: "PromptProto", Embedded: true}, {Ext: "_res.gox", Class: "Resource", Proto: "ResourceProto"}, }, PkgPaths: []string{"github.com/goplus/xgo/cl/internal/mcp"}}, true case ".gsh": return &modfile.Project{ Ext: ".gsh", Class: "App", PkgPaths: []string{"github.com/qiniu/x/gsh", "math"}, }, true } return } // ----------------------------------------------------------------------------- func Named(t *testing.T, name string, gopcode, expected string) { t.Run(name, func(t *testing.T) { Do(t, gopcode, expected) }) } func Do(t *testing.T, gopcode, expected string) { DoExt(t, Conf, "main", gopcode, expected) } func DoWithFname(t *testing.T, gopcode, expected string, fname string) { fs := memfs.SingleFile("/foo", fname, gopcode) DoFS(t, Conf, fs, "/foo", nil, "main", expected) } func DoExt(t *testing.T, conf *cl.Config, pkgname, gopcode, expected string) { fs := memfs.SingleFile("/foo", "bar.xgo", gopcode) DoFS(t, conf, fs, "/foo", nil, pkgname, expected) } func Mixed(t *testing.T, pkgname, gocode, gopcode, expected string, outline ...bool) { conf := *Conf conf.Outline = (outline != nil && outline[0]) fs := memfs.TwoFiles("/foo", "a.go", gocode, "b.xgo", gopcode) DoFS(t, &conf, fs, "/foo", nil, pkgname, expected) } // ----------------------------------------------------------------------------- func DoFS( t *testing.T, conf *cl.Config, fs parser.FileSystem, dir string, filter func(fs.FileInfo) bool, pkgname string, exp any) { cl.SetDisableRecover(true) defer cl.SetDisableRecover(false) fset := conf.Fset pkgs, err := parser.ParseFSDir(fset, fs, dir, parser.Config{ Mode: parser.ParseComments, Filter: filter, }) if err != nil { scanner.PrintError(os.Stderr, err) t.Fatal("ParseFSDir:", err) } bar := pkgs[pkgname] pkg, err := cl.NewPackage("github.com/goplus/xgo/cl", bar, conf) if err != nil { t.Fatal("NewPackage:", err) } var b bytes.Buffer err = pkg.WriteTo(&b) if err != nil { t.Fatal("gogen.WriteTo failed:", err) } if expected, ok := exp.(string); ok { result := b.String() if result != expected { t.Fatalf("\nResult:\n%s\nExpected:\n%s\n", result, expected) } } else if test.Diff(t, dir+"/result.txt", b.Bytes(), exp.([]byte)) { t.Fatal(dir, ": unexpect result") } } // ----------------------------------------------------------------------------- func FromDir(t *testing.T, sel, relDir string) { dir, err := os.Getwd() if err != nil { t.Fatal("Getwd failed:", err) } dir = path.Join(dir, relDir) fis, err := os.ReadDir(dir) if err != nil { t.Fatal("ReadDir failed:", err) } for _, fi := range fis { name := fi.Name() if !fi.IsDir() || strings.HasPrefix(name, "_") { continue } t.Run(name, func(t *testing.T) { testFrom(t, dir+"/"+name, sel) }) } } func testFrom(t *testing.T, pkgDir, sel string) { if sel != "" && !strings.Contains(pkgDir, sel) { return } log.Println("Parsing", pkgDir) out := pkgDir + "/out.go" b, _ := os.ReadFile(out) filter := func(fi fs.FileInfo) bool { return fi.Name() == "in.xgo" } conf := Conf goMod := pkgDir + "/go.mod" if _, err := os.Stat(goMod); err == nil { if mod, err := xgomod.Load(pkgDir); err == nil { confCopy := *Conf confCopy.Importer = tool.NewImporter(mod, XGo, conf.Fset) conf = &confCopy } } else { confCopy := *Conf confCopy.RelativeBase = XGoRoot conf = &confCopy } DoFS(t, conf, fsx.Local, pkgDir, filter, "main", b) } // ----------------------------------------------------------------------------- ================================================ FILE: cl/cltest/error_msg.go ================================================ /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cltest import ( "os" "testing" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/parser/fsx/memfs" "github.com/goplus/xgo/scanner" ) func Error(t *testing.T, msg, src string) { ErrorEx(t, "main", "bar.xgo", msg, src) } func ErrorEx(t *testing.T, pkgname, filename, msg, src string) { fs := memfs.SingleFile("/foo", filename, src) pkgs, err := parser.ParseFSDir(Conf.Fset, fs, "/foo", parser.Config{}) if err != nil { scanner.PrintError(os.Stderr, err) t.Fatal("parser.ParseFSDir failed") } conf := *Conf conf.NoFileLine = false conf.RelativeBase = "/foo" bar := pkgs[pkgname] _, err = cl.NewPackage("", bar, &conf) if err == nil { t.Fatal("no error?") } if ret := err.Error(); ret != msg { t.Fatalf("\nError: \"%s\"\nExpected: \"%s\"\n", ret, msg) } } func ErrorAst(t *testing.T, pkgname, filename, msg, src string) { f, _ := parser.ParseFile(Conf.Fset, filename, src, parser.AllErrors) pkg := &ast.Package{ Name: pkgname, Files: map[string]*ast.File{filename: f}, } conf := *Conf conf.NoFileLine = false conf.RelativeBase = "/foo" _, err := cl.NewPackage("", pkg, &conf) if err == nil { t.Fatal("no error?") } if ret := err.Error(); ret != msg { t.Fatalf("\nError: \"%s\"\nExpected: \"%s\"\n", ret, msg) } } ================================================ FILE: cl/cltest/recorder.go ================================================ /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cltest import ( "go/types" "github.com/goplus/xgo/ast" ) type gopRecorder struct { } // Type maps expressions to their types, and for constant // expressions, also their values. Invalid expressions are // omitted. // // For (possibly parenthesized) identifiers denoting built-in // functions, the recorded signatures are call-site specific: // if the call result is not a constant, the recorded type is // an argument-specific signature. Otherwise, the recorded type // is invalid. // // The Types map does not record the type of every identifier, // only those that appear where an arbitrary expression is // permitted. For instance, the identifier f in a selector // expression x.f is found only in the Selections map, the // identifier z in a variable declaration 'var z int' is found // only in the Defs map, and identifiers denoting packages in // qualified identifiers are collected in the Uses map. func (info gopRecorder) Type(e ast.Expr, tv types.TypeAndValue) { } // Instantiate maps identifiers denoting generic types or functions to their // type arguments and instantiated type. // // For example, Instantiate will map the identifier for 'T' in the type // instantiation T[int, string] to the type arguments [int, string] and // resulting instantiated *Named type. Given a generic function // func F[A any](A), Instances will map the identifier for 'F' in the call // expression F(int(1)) to the inferred type arguments [int], and resulting // instantiated *Signature. // // Invariant: Instantiating Uses[id].Type() with Instances[id].TypeArgs // results in an equivalent of Instances[id].Type. func (info gopRecorder) Instantiate(id *ast.Ident, inst types.Instance) { } // Def maps identifiers to the objects they define (including // package names, dots "." of dot-imports, and blank "_" identifiers). // For identifiers that do not denote objects (e.g., the package name // in package clauses, or symbolic variables t in t := x.(type) of // type switch headers), the corresponding objects are nil. // // For an embedded field, Def maps the field *Var it defines. // // Invariant: Defs[id] == nil || Defs[id].Pos() == id.Pos() func (info gopRecorder) Def(id *ast.Ident, obj types.Object) { } // Use maps identifiers to the objects they denote. // // For an embedded field, Use maps the *TypeName it denotes. // // Invariant: Uses[id].Pos() != id.Pos() func (info gopRecorder) Use(id *ast.Ident, obj types.Object) { } // Implicit maps nodes to their implicitly declared objects, if any. // The following node and object types may appear: // // node declared object // // *ast.ImportSpec *PkgName for imports without renames // *ast.CaseClause type-specific *Var for each type switch case clause (incl. default) // *ast.Field anonymous parameter *Var (incl. unnamed results) func (info gopRecorder) Implicit(node ast.Node, obj types.Object) { } // Select maps selector expressions (excluding qualified identifiers) // to their corresponding selections. func (info gopRecorder) Select(e *ast.SelectorExpr, sel *types.Selection) { } // Scope maps ast.Nodes to the scopes they define. Package scopes are not // associated with a specific node but with all files belonging to a package. // Thus, the package scope can be found in the type-checked Package object. // Scopes nest, with the Universe scope being the outermost scope, enclosing // the package scope, which contains (one or more) files scopes, which enclose // function scopes which in turn enclose statement and function literal scopes. // Note that even though package-level functions are declared in the package // scope, the function scopes are embedded in the file scope of the file // containing the function declaration. // // The following node types may appear in Scopes: // // *ast.File // *ast.FuncType // *ast.TypeSpec // *ast.BlockStmt // *ast.IfStmt // *ast.SwitchStmt // *ast.TypeSwitchStmt // *ast.CaseClause // *ast.CommClause // *ast.ForStmt // *ast.RangeStmt func (info gopRecorder) Scope(n ast.Node, scope *types.Scope) { } ================================================ FILE: cl/cltest/spx.go ================================================ /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cltest import ( "bytes" "io/fs" "log" "os" "path" "strings" "testing" "github.com/goplus/mod/modfile" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/parser/fsx" "github.com/goplus/xgo/parser/fsx/memfs" "github.com/goplus/xgo/scanner" "github.com/qiniu/x/test" ) func spxParserConf() parser.Config { return parser.Config{ ClassKind: func(fname string) (isProj bool, ok bool) { ext := modfile.ClassExt(fname) c, ok := LookupClass(ext) if ok { isProj = c.IsProj(ext, fname) } return }, } } func Spx(t *testing.T, gmx, spxcode, expected string) { SpxEx(t, gmx, spxcode, expected, "index.tgmx", "bar.tspx") } func SpxEx(t *testing.T, gmx, spxcode, expected, gmxfile, spxfile string) { SpxWithConf(t, "gopSpxTest", Conf, gmx, spxcode, expected, gmxfile, spxfile, "") } func SpxEx2(t *testing.T, gmx, spxcode, expected, gmxfile, spxfile, resultFile string) { SpxWithConf(t, "gopSpxTest", Conf, gmx, spxcode, expected, gmxfile, spxfile, resultFile) } func SpxWithConf(t *testing.T, name string, conf *cl.Config, gmx, spxcode, expected, gmxfile, spxfile, resultFile string) { t.Run(name, func(t *testing.T) { fs := memfs.TwoFiles("/foo", spxfile, spxcode, gmxfile, gmx) if gmxfile == "" { fs = memfs.SingleFile("/foo", spxfile, spxcode) } SpxTest(t, fs, "/foo", spxParserConf(), conf, expected, resultFile) }) } func SpxTest(t *testing.T, fs fsx.FileSystem, dir string, parseConf parser.Config, clConf *cl.Config, exp any, resultFile string) { cl.SetDisableRecover(true) defer cl.SetDisableRecover(false) pkgs, err := parser.ParseFSDir(Conf.Fset, fs, dir, parseConf) if err != nil { scanner.PrintError(os.Stderr, err) t.Fatal("ParseFSDir:", err) } bar := pkgs["main"] pkg, err := cl.NewPackage("", bar, clConf) if err != nil { t.Fatal("NewPackage:", err) } var b bytes.Buffer err = pkg.WriteTo(&b, resultFile) if err != nil { t.Fatal("gogen.WriteTo failed:", err) } if expected, ok := exp.(string); ok { result := b.String() if result != expected { t.Fatalf("\nResult:\n%s\nExpected:\n%s\n", result, expected) } } else if test.Diff(t, dir+"/result.txt", b.Bytes(), exp.([]byte)) { t.Fatal(dir, ": unexpect result") } } func SpxErrorFS(t *testing.T, msg string, fs parser.FileSystem) { pkgs, err := parser.ParseFSDir(Conf.Fset, fs, "/foo", spxParserConf()) if err != nil { scanner.PrintError(os.Stderr, err) t.Fatal("ParseFSDir:", err) } conf := *Conf conf.RelativeBase = "/foo" conf.Recorder = nil conf.NoFileLine = false bar := pkgs["main"] _, err = cl.NewPackage("", bar, &conf) if err == nil { t.Fatal("no error?") } if ret := err.Error(); ret != msg { t.Fatalf("\nError: \"%s\"\nExpected: \"%s\"\n", ret, msg) } } func SpxErrorEx(t *testing.T, msg, gmx, spxcode, gmxfile, spxfile string) { fs := memfs.TwoFiles("/foo", spxfile, spxcode, gmxfile, gmx) pkgs, err := parser.ParseFSDir(Conf.Fset, fs, "/foo", spxParserConf()) if err != nil { scanner.PrintError(os.Stderr, err) t.Fatal("ParseFSDir:", err) } conf := *Conf conf.RelativeBase = "/foo" conf.Recorder = nil conf.NoFileLine = false bar := pkgs["main"] _, err = cl.NewPackage("", bar, &conf) if err == nil { t.Fatal("no error?") } if ret := err.Error(); ret != msg { t.Fatalf("\nError: \"%s\"\nExpected: \"%s\"\n", ret, msg) } } func SpxFromDir(t *testing.T, sel, relDir string) { dir, err := os.Getwd() if err != nil { t.Fatal("Getwd failed:", err) } dir = path.Join(dir, relDir) fis, err := os.ReadDir(dir) if err != nil { t.Fatal("ReadDir failed:", err) } for _, fi := range fis { name := fi.Name() if !fi.IsDir() || strings.HasPrefix(name, "_") { continue } t.Run(name, func(t *testing.T) { testSpxFrom(t, dir+"/"+name, sel) }) } } func testSpxFrom(t *testing.T, pkgDir, sel string) { if sel != "" && !strings.Contains(pkgDir, sel) { return } log.Println("Parsing", pkgDir) parseConf := spxParserConf() parseConf.Filter = func(fi fs.FileInfo) bool { return !strings.HasSuffix(fi.Name(), ".go") } expect, _ := os.ReadFile(pkgDir + "/out.go") SpxTest(t, fsx.Local, pkgDir, parseConf, Conf, expect, "") } ================================================ FILE: cl/compile.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package cl compiles XGo syntax trees (ast). package cl import ( "fmt" goast "go/ast" gotoken "go/token" "go/types" "log" "reflect" "sort" "strconv" "strings" "github.com/goplus/gogen" "github.com/goplus/mod/modfile" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/ast/fromgo" "github.com/goplus/xgo/token" "github.com/qiniu/x/errors" ) type dbgFlags int const ( DbgFlagLoad dbgFlags = 1 << iota DbgFlagLookup FlagNoMarkAutogen DbgFlagAll = DbgFlagLoad | DbgFlagLookup ) var ( enableRecover = true ) var ( debugLoad bool debugLookup bool noMarkAutogen bool // add const _ = true ) func SetDisableRecover(disableRecover bool) { enableRecover = !disableRecover } func SetDebug(flags dbgFlags) { debugLoad = (flags & DbgFlagLoad) != 0 debugLookup = (flags & DbgFlagLookup) != 0 noMarkAutogen = (flags & FlagNoMarkAutogen) != 0 } // ----------------------------------------------------------------------------- // Recorder represents a compiling event recorder. type Recorder interface { // Type maps expressions to their types, and for constant // expressions, also their values. Invalid expressions are // omitted. // // For (possibly parenthesized) identifiers denoting built-in // functions, the recorded signatures are call-site specific: // if the call result is not a constant, the recorded type is // an argument-specific signature. Otherwise, the recorded type // is invalid. // // The Types map does not record the type of every identifier, // only those that appear where an arbitrary expression is // permitted. For instance, the identifier f in a selector // expression x.f is found only in the Selections map, the // identifier z in a variable declaration 'var z int' is found // only in the Defs map, and identifiers denoting packages in // qualified identifiers are collected in the Uses map. Type(ast.Expr, types.TypeAndValue) // Instantiate maps identifiers denoting generic types or functions to their // type arguments and instantiated type. // // For example, Instantiate will map the identifier for 'T' in the type // instantiation T[int, string] to the type arguments [int, string] and // resulting instantiated *Named type. Given a generic function // func F[A any](A), Instances will map the identifier for 'F' in the call // expression F(int(1)) to the inferred type arguments [int], and resulting // instantiated *Signature. // // Invariant: Instantiating Uses[id].Type() with Instances[id].TypeArgs // results in an equivalent of Instances[id].Type. Instantiate(*ast.Ident, types.Instance) // Def maps identifiers to the objects they define (including // package names, dots "." of dot-imports, and blank "_" identifiers). // For identifiers that do not denote objects (e.g., the package name // in package clauses, or symbolic variables t in t := x.(type) of // type switch headers), the corresponding objects are nil. // // For an embedded field, Def maps the field *Var it defines. // // Invariant: Defs[id] == nil || Defs[id].Pos() == id.Pos() Def(id *ast.Ident, obj types.Object) // Use maps identifiers to the objects they denote. // // For an embedded field, Use maps the *TypeName it denotes. // // Invariant: Uses[id].Pos() != id.Pos() Use(id *ast.Ident, obj types.Object) // Implicit maps nodes to their implicitly declared objects, if any. // The following node and object types may appear: // // node declared object // // *ast.ImportSpec *PkgName for imports without renames // *ast.CaseClause type-specific *Var for each type switch case clause (incl. default) // *ast.Field anonymous parameter *Var (incl. unnamed results) // *ast.FunLit function literal in *ast.OverloadFuncDecl // Implicit(node ast.Node, obj types.Object) // Select maps selector expressions (excluding qualified identifiers) // to their corresponding selections. Select(*ast.SelectorExpr, *types.Selection) // Scope maps ast.Nodes to the scopes they define. Package scopes are not // associated with a specific node but with all files belonging to a package. // Thus, the package scope can be found in the type-checked Package object. // Scopes nest, with the Universe scope being the outermost scope, enclosing // the package scope, which contains (one or more) files scopes, which enclose // function scopes which in turn enclose statement and function literal scopes. // Note that even though package-level functions are declared in the package // scope, the function scopes are embedded in the file scope of the file // containing the function declaration. // // The following node types may appear in Scopes: // // *ast.File // *ast.FuncType // *ast.TypeSpec // *ast.BlockStmt // *ast.IfStmt // *ast.SwitchStmt // *ast.TypeSwitchStmt // *ast.CaseClause // *ast.CommClause // *ast.ForStmt // *ast.RangeStmt // *ast.ForPhraseStmt // *ast.ForPhrase // *ast.LambdaExpr // *ast.LambdaExpr2 // Scope(ast.Node, *types.Scope) } // ----------------------------------------------------------------------------- type Project = modfile.Project type Class = modfile.Class // Config of loading XGo packages. type Config struct { // Types provides type information for the package (optional). Types *types.Package // Fset provides source position information for syntax trees and types (required). Fset *token.FileSet // RelativeBase is the root directory of relative path. RelativeBase string // LookupClass lookups a class by specified file extension (required). // See (*github.com/goplus/mod/gopmod.Module).LookupClass. LookupClass func(ext string) (c *Project, ok bool) // An Importer resolves import paths to Packages (optional). Importer types.Importer // A Recorder records existing objects including constants, variables and // types etc (optional). Recorder Recorder // NoFileLine = true means not to generate file line comments. NoFileLine bool // NoAutoGenMain = true means not to auto generate main func is no entry. NoAutoGenMain bool // NoSkipConstant = true means to disable optimization of skipping constants. NoSkipConstant bool // Outline = true means to skip compiling function bodies. Outline bool } type nodeInterp struct { fset *token.FileSet files map[string]*ast.File relBaseDir string } func (p *nodeInterp) Position(start token.Pos) (pos token.Position) { pos = p.fset.Position(start) pos.Filename = relFile(p.relBaseDir, pos.Filename) return } func (p *nodeInterp) Caller(node ast.Node) string { if expr, ok := node.(*ast.CallExpr); ok { return p.LoadExpr(expr.Fun) } return "the function call" } func (p *nodeInterp) LoadExpr(node ast.Node) string { start := node.Pos() if start == token.NoPos { return "" } pos := p.fset.Position(start) f := p.files[pos.Filename] n := int(node.End() - start) return string(f.Code[pos.Offset : pos.Offset+n]) } func (p *nodeInterp) ProjFile() *ast.File { for _, f := range p.files { if f.IsProj { return f } } return nil } type loader interface { load() pos() token.Pos } type baseLoader struct { fn func() start token.Pos } func initLoader(ctx *pkgCtx, syms map[string]loader, start, end token.Pos, name string, fn func(), genBody bool) bool { if name == "_" { if genBody { ctx.inits = append(ctx.inits, fn) } return false } if old, ok := syms[name]; ok { oldpos := ctx.Position(old.pos()) ctx.handleErrorf( start, end, "%s redeclared in this block\n\tprevious declaration at %v", name, oldpos) return false } syms[name] = &baseLoader{start: start, fn: fn} return true } func (p *baseLoader) load() { p.fn() } func (p *baseLoader) pos() token.Pos { return p.start } type typeLoader struct { typ, typInit func() methods []func() start token.Pos } func getTypeLoader(ctx *pkgCtx, syms map[string]loader, start, end token.Pos, name string) *typeLoader { t, ok := syms[name] if ok { if start != token.NoPos { ld := t.(*typeLoader) if ld.start == token.NoPos { ld.start = start } else { ctx.handleErrorf( start, end, "%s redeclared in this block\n\tprevious declaration at %v", name, ctx.Position(ld.pos())) } return ld } } else { t = &typeLoader{start: start} syms[name] = t } return t.(*typeLoader) } func (p *typeLoader) pos() token.Pos { return p.start } func (p *typeLoader) load() { doNewType(p) doInitType(p) doInitMethods(p) } func doNewType(ld *typeLoader) { if typ := ld.typ; typ != nil { ld.typ = nil typ() } } func doInitType(ld *typeLoader) { if typInit := ld.typInit; typInit != nil { ld.typInit = nil typInit() } } func doInitMethods(ld *typeLoader) { if methods := ld.methods; methods != nil { ld.methods = nil for _, method := range methods { method() } } } type pkgCtx struct { *nodeInterp nproj int // number of non-test projects projs map[string]*gmxProject // .gmx => project classes map[*ast.File]*gmxClass overpos map[string]token.Pos // overload => pos fset *token.FileSet syms map[string]loader lbinames []any // names that should load before initXGoPkg (can be string/func or *ast.Ident/type) inits []func() tylds []*typeLoader errs errors.List generics map[string]bool // generic type record idents []*ast.Ident // toType ident recored inInst int // toType in generic instance goxMainClass string goxMain int32 // normal gox files with main func } type pkgImp struct { gogen.PkgRef pkgName *types.PkgName } type blockCtx struct { *pkgCtx proj *gmxProject pkg *gogen.Package cb *gogen.CodeBuilder imports map[string]pkgImp autoimps map[string]pkgImp lookups []gogen.PkgRef tlookup *typeParamLookup cstr_ gogen.Ref pystr_ gogen.Ref relBaseDir string classDecl *ast.GenDecl // available when isClass classRecv *ast.FieldList // available when isClass baseClass types.Object // available when isClass fileScope *types.Scope // available when isXGoFile rec *goxRecorder fileLine bool isClass bool isXgoFile bool // is XGo file or not } func (p *blockCtx) cstr() gogen.Ref { if p.cstr_ == nil { p.cstr_ = p.pkg.Import(pathLibc).Ref("Str") } return p.cstr_ } func (p *blockCtx) pystr() gogen.Ref { if p.pystr_ == nil { p.pystr_ = p.pkg.Import(pathLibpy).Ref("Str") } return p.pystr_ } func (p *blockCtx) recorder() *goxRecorder { if p.isXgoFile { return p.rec } return nil } func (p *blockCtx) findImport(name string) (pi pkgImp, ok bool) { pi, ok = p.imports[name] if !ok && p.autoimps != nil { pi, ok = p.autoimps[name] } return } func (p *pkgCtx) newCodeError(pos, end token.Pos, msg string) error { return &gogen.CodeError{Fset: p.nodeInterp, Pos: pos, End: end, Msg: msg} } func (p *pkgCtx) newCodeErrorf(pos, end token.Pos, format string, args ...any) error { return &gogen.CodeError{Fset: p.nodeInterp, Pos: pos, End: end, Msg: fmt.Sprintf(format, args...)} } func (p *pkgCtx) handleErrorf(pos, end token.Pos, format string, args ...any) { p.handleErr(p.newCodeErrorf(pos, end, format, args...)) } func (p *pkgCtx) handleErr(err error) { p.errs = append(p.errs, err) } func (p *pkgCtx) loadNamed(at *gogen.Package, t *types.Named) { o := t.Obj() if o.Pkg() == at.Types { p.loadType(o.Name()) } } func (p *pkgCtx) complete() error { return p.errs.ToError() } func (p *pkgCtx) loadType(name string) { if sym, ok := p.syms[name]; ok { if ld, ok := sym.(*typeLoader); ok { ld.load() } } } func (p *pkgCtx) loadSymbol(name string) bool { if enableRecover { defer func() { if e := recover(); e != nil { p.handleRecover(e, nil) } }() } if f, ok := p.syms[name]; ok { if ld, ok := f.(*typeLoader); ok { doNewType(ld) // create this type, but don't init return true } delete(p.syms, name) f.load() return true } return false } func (p *pkgCtx) handleRecover(e any, src ast.Node) { err := p.recoverErr(e, src) p.handleErr(err) } func (p *pkgCtx) recoverErr(e any, src ast.Node) error { err, ok := e.(error) if !ok { if src != nil { text := p.LoadExpr(src) err = p.newCodeErrorf(src.Pos(), src.End(), "compile `%v`: %v", text, e) } else { err = fmt.Errorf("%v", e) } } return err } // lookupClassFile looks up the class file by name. func (p *pkgCtx) lookupClassNode(name string) ast.Node { for f, cls := range p.classes { if name == cls.clsfile { return f.Name } } return nil } type visitedT = map[*types.Struct]none // see https://github.com/goplus/xgo/issues/2439 func embeddedFieldCast(o *types.Struct, tn *types.Named, pv *gogen.Element, visited visitedT) bool { if _, ok := visited[o]; ok { return false } visited[o] = none{} for i, n := 0, o.NumFields(); i < n; i++ { if fld := o.Field(i); fld.Embedded() { var ptr bool var ftn *types.Named switch ft := fld.Type().(type) { case *types.Named: ftn = ft case *types.Pointer: if ft, ok := ft.Elem().(*types.Named); ok { ftn, ptr = ft, true } } if ftn != nil { if ftn == tn { pv.Val = &goast.SelectorExpr{ X: pv.Val, Sel: goast.NewIdent(fld.Name()), } if !ptr { pv.Val = &goast.UnaryExpr{ Op: gotoken.AND, X: pv.Val, } } return true } if fldStruct, ok := ftn.Underlying().(*types.Struct); ok { if embeddedFieldCast(fldStruct, tn, pv, visited) { return true } } } } } return false } func implicitCast(pkg *gogen.Package, V, T types.Type, pv *gogen.Element) bool { if pv != nil { if t, ok := T.(*types.Pointer); ok { if tn, ok := t.Elem().(*types.Named); ok { if v, ok := V.(*types.Pointer); ok { if o, ok := v.Elem().Underlying().(*types.Struct); ok { return embeddedFieldCast(o, tn, pv, visitedT{}) } } } } } return false } const ( defaultGoFile = "" skippingGoFile = "_skip" testingGoFile = "_test" ) // NewPackage creates an XGo package instance. func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gogen.Package, err error) { relBaseDir := conf.RelativeBase fset := conf.Fset files := pkg.Files interp := &nodeInterp{ fset: fset, files: files, relBaseDir: relBaseDir, } ctx := &pkgCtx{ fset: fset, nodeInterp: interp, projs: make(map[string]*gmxProject), classes: make(map[*ast.File]*gmxClass), overpos: make(map[string]token.Pos), syms: make(map[string]loader), generics: make(map[string]bool), } confGox := &gogen.Config{ Types: conf.Types, Fset: fset, Importer: conf.Importer, LoadNamed: ctx.loadNamed, HandleErr: ctx.handleErr, NodeInterpreter: interp, NewBuiltin: ctx.newBuiltinDefault, DefaultGoFile: defaultGoFile, NoSkipConstant: conf.NoSkipConstant, PkgPathOsx: osxPkgPath, DbgPositioner: interp, CanImplicitCast: implicitCast, } var rec *goxRecorder if conf.Recorder != nil { rec = newRecorder(conf.Recorder) confGox.Recorder = rec defer func() { rec.Complete(p.Types.Scope()) }() } if enableRecover { defer func() { if e := recover(); e != nil { ctx.handleRecover(e, nil) err = ctx.errs.ToError() } }() } p = gogen.NewPackage(pkgPath, pkg.Name, confGox) if !noMarkAutogen { p.CB().NewConstStart(nil, "_").Val(true).EndInit(1) } // sort files type File struct { *ast.File path string } sfiles := make([]*File, 0, len(files)) for fpath, f := range files { sfiles = append(sfiles, &File{f, fpath}) } sort.Slice(sfiles, func(i, j int) bool { return sfiles[i].path < sfiles[j].path }) for _, f := range sfiles { gmx := f.File if gmx.IsClass && !gmx.IsNormalGox { if debugLoad { log.Println("==> ClassFile", f.path) } loadClass(ctx, p, f.path, gmx, conf) } } for _, f := range sfiles { fileLine := !conf.NoFileLine fileScope := types.NewScope(p.Types.Scope(), f.Pos(), f.End(), f.path) ctx := &blockCtx{ pkg: p, pkgCtx: ctx, cb: p.CB(), relBaseDir: relBaseDir, fileScope: fileScope, fileLine: fileLine, isClass: f.IsClass, rec: rec, imports: make(map[string]pkgImp), isXgoFile: true, } if rec := ctx.rec; rec != nil { rec.Scope(f.File, fileScope) } preloadXGoFile(p, ctx, f.path, f.File, conf) } proj, multi := gmxCheckProjs(p, ctx) gopSyms := make(map[string]bool) // TODO(xsw): remove this map for name := range ctx.syms { gopSyms[name] = true } gofiles := make([]*ast.File, 0, len(pkg.GoFiles)) for _, gof := range pkg.GoFiles { f := fromgo.ASTFile(gof, 0) gofiles = append(gofiles, f) ctx := &blockCtx{ pkg: p, pkgCtx: ctx, cb: p.CB(), relBaseDir: relBaseDir, imports: make(map[string]pkgImp), } preloadFile(p, ctx, f, skippingGoFile, false) } initXGoPkg(ctx, p, gopSyms) // genMain = true if it is main package and no main func var genMain bool var mainClass string if pkg.Name == "main" { _, hasMain := ctx.syms["main"] genMain = !hasMain } for _, f := range sfiles { if f.IsProj { loadFile(ctx, f.File) } } if genMain { // make classfile main func if need if proj != nil { if !multi { // only one project file mainClass = proj.getGameClass(ctx) } } else if ctx.goxMain == 1 { mainClass = ctx.goxMainClass // main func in normal gox file } } for _, f := range sfiles { if !f.IsProj { loadFile(ctx, f.File) } } if conf.Outline { for _, f := range gofiles { loadFile(ctx, f) } } for _, ld := range ctx.tylds { ld.load() } for _, load := range ctx.inits { load() } err = ctx.complete() if mainClass != "" { // generate classfile main func genMainFunc(p, mainClass) } else if genMain && !conf.NoAutoGenMain { // generate empty main func old, _ := p.SetCurFile(defaultGoFile, false) p.NewFunc(nil, "main", nil, nil, false).BodyStart(p).End() p.RestoreCurFile(old) } return } func isOverloadFunc(name string) bool { n := len(name) return n > 3 && name[n-3:n-1] == "__" } func initXGoPkg(ctx *pkgCtx, pkg *gogen.Package, gopSyms map[string]bool) { for name, f := range ctx.syms { if gopSyms[name] { continue } if _, ok := f.(*typeLoader); ok { ctx.loadType(name) } else if isOverloadFunc(name) { ctx.loadSymbol(name) } } for _, lbi := range ctx.lbinames { if name, ok := lbi.(string); ok { ctx.loadSymbol(name) } else { ctx.loadType(lbi.(*ast.Ident).Name) } } gogen.InitXGoPackageEx(pkg.Types, ctx.overpos) } func loadFile(ctx *pkgCtx, f *ast.File) { for _, decl := range f.Decls { switch d := decl.(type) { case *ast.GenDecl: switch d.Tok { case token.TYPE: for _, spec := range d.Specs { ctx.loadType(spec.(*ast.TypeSpec).Name.Name) } case token.CONST, token.VAR: for _, spec := range d.Specs { for _, name := range spec.(*ast.ValueSpec).Names { ctx.loadSymbol(name.Name) } } } case *ast.FuncDecl: if d.Recv == nil { name := d.Name.Name if name != "init" { ctx.loadSymbol(name) } } else { if name, ok := getRecvTypeName(ctx, d.Recv, false); ok { getTypeLoader(ctx, ctx.syms, token.NoPos, token.NoPos, name).load() } } } } } // gen testingGoFile for: // // *_test.xgo // *_test.gop // *test.gox func genGoFile(file string, goxTestFile bool) string { if goxTestFile || strings.HasSuffix(file, "_test.xgo") || strings.HasSuffix(file, "_test.gop") { return testingGoFile } return defaultGoFile } func preloadXGoFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, conf *Config) { var proj *gmxProject var c *gmxClass var classType, gameClass string var testType string var baseTypeName string var baseType types.Type var sp *spxObj var goxTestFile bool var parent = ctx.pkgCtx if f.IsClass { if f.IsNormalGox { classType, _, _ = ClassNameAndExt(file) if f.ShadowEntry != nil { parent.goxMainClass = classType parent.goxMain++ } } else { c = parent.classes[f] proj, ctx.proj = c.proj, c.proj classType, gameClass = c.getName(parent), proj.getGameClass(parent) ctx.autoimps = proj.autoimps goxTestFile = proj.isTest if goxTestFile { // test classfile testType = classType if !f.IsProj { classType = casePrefix + testNameSuffix(testType) } } if f.IsProj { classType = gameClass o := proj.game ctx.baseClass = o baseTypeName, baseType = o.Name(), o.Type() if proj.gameIsPtr { baseType = types.NewPointer(baseType) } } else { sp = c.sp o := sp.obj ctx.baseClass = o baseTypeName, baseType = o.Name(), o.Type() } } } goFile := genGoFile(file, goxTestFile) if classType != "" { if debugLoad { log.Println("==> Preload type", classType) } if proj != nil { ctx.lookups = make([]gogen.PkgRef, len(proj.pkgPaths)) for i, pkgPath := range proj.pkgPaths { ctx.lookups[i] = p.Import(pkgPath) } } syms := parent.syms pos := f.Pos() end := f.End() ctx.classDecl = f.ClassFieldsDecl() ld := getTypeLoader(parent, syms, pos, end, classType) ld.typ = func() { if debugLoad { log.Println("==> Load > NewType", classType) } old, _ := p.SetCurFile(goFile, true) defer p.RestoreCurFile(old) decl := p.NewTypeDefs().NewType(classType) ld.typInit = func() { // decycle if debugLoad { log.Println("==> Load > InitType", classType) } old, _ := p.SetCurFile(goFile, true) defer p.RestoreCurFile(old) pkg := p.Types var flds []*types.Var var tags []string chk := newCheckRedecl() if baseTypeName != "" { // base class (not normal classfile) flds = append(flds, types.NewField(pos, pkg, baseTypeName, baseType, true)) tags = append(tags, "") chk.chkRedecl(ctx, baseTypeName, pos, end, fieldKindClass) if sp != nil { // for work class if !goxTestFile && gameClass != "" { // has project class typ := toType(ctx, &ast.Ident{Name: gameClass}) getUnderlying(ctx, typ) // ensure type is loaded typ = types.NewPointer(typ) name := getTypeName(typ) if !chk.chkRedecl(ctx, name, pos, end, fieldKindClass) { fld := types.NewField(pos, pkg, name, typ, true) flds = append(flds, fld) tags = append(tags, "") } } } else { // embed work classes for project class flds = proj.embed(func(name string) bool { return chk.chkRedecl(ctx, name, pos, end, fieldKindClass) }, flds, p) } } rec := ctx.recorder() if classDecl := ctx.classDecl; classDecl != nil { var spec *ast.ValueSpec recvType := types.NewPointer(decl.Type()) recv := types.NewParam(token.NoPos, pkg, "this", recvType) defs := p.ClassDefsStart(recv, func(idx int, name string, typ types.Type, embed bool) { var id *ast.Ident if embed { id = parseTypeEmbedName(spec.Type) } else { id = spec.Names[idx] } pos := id.Pos() if chk.chkRedecl(ctx, name, pos, id.End(), fieldKindUser) { return } fld := types.NewField(pos, pkg, name, typ, embed) if rec != nil { rec.Def(id, fld) } flds = append(flds, fld) tags = append(tags, toFieldTag(spec.Tag)) }) for _, v := range classDecl.Specs { var pos token.Pos var names []string var fldType types.Type spec = v.(*ast.ValueSpec) if spec.Type != nil { fldType = toType(ctx, spec.Type) } if specNames := spec.Names; len(specNames) > 0 { names = makeNames(specNames) pos = specNames[0].Pos() } else { pos = spec.Type.Pos() } initExpr := makeInitExpr(ctx, spec, fldType, names) defs.NewAndInit(initExpr, pos, fldType, names...) } defs.End() } decl.InitType(p, types.NewStruct(flds, tags)) } parent.tylds = append(parent.tylds, ld) } // bugfix: see TestGoxNoFunc parent.lbinames = append(parent.lbinames, classType) ctx.classRecv = &ast.FieldList{List: []*ast.Field{{ Names: []*ast.Ident{ {NamePos: f.Pos(), Name: "this"}, }, Type: &ast.StarExpr{ Star: f.Pos(), X: ast.NewIdentEx(f.Pos(), classType, ast.ImplicitFun), }, }}} } if d := f.ShadowEntry; d != nil { d.Name.Name = getEntrypoint(f) } else if baseTypeName != "" { // isClass && not isNormalGox astEmptyEntrypoint(f) } preloadFile(p, ctx, f, goFile, !conf.Outline) if sp != nil && sp.feats != 0 { spfeats := sp.feats ld := getTypeLoader(parent, parent.syms, token.NoPos, token.NoPos, classType) if (spfeats & spriteClassfname) != 0 { // Classfname() string ld.methods = append(ld.methods, func() { old, _ := p.SetCurFile(goFile, true) defer p.RestoreCurFile(old) doInitType(ld) genClassfname(ctx, c) }) } if (spfeats & spriteClassclone) != 0 { // Classclone() clonetype ld.methods = append(ld.methods, func() { old, _ := p.SetCurFile(goFile, true) defer p.RestoreCurFile(old) doInitType(ld) genClassclone(ctx, sp.clone) }) } } if goxTestFile { parent.inits = append(parent.inits, func() { old, _ := p.SetCurFile(testingGoFile, true) gmxTestFunc(p, testType, f.IsProj) p.RestoreCurFile(old) }) } } func parseTypeEmbedName(typ ast.Expr) *ast.Ident { retry: switch t := typ.(type) { case *ast.Ident: return t case *ast.SelectorExpr: return t.Sel case *ast.StarExpr: typ = t.X goto retry } panic("TODO: parseTypeEmbedName unexpected") } func preloadFile(p *gogen.Package, ctx *blockCtx, f *ast.File, goFile string, genFnBody bool) { parent := ctx.pkgCtx classDecl := ctx.classDecl syms := parent.syms old, _ := p.SetCurFile(goFile, true) defer p.RestoreCurFile(old) preloadFuncDecl := func(d *ast.FuncDecl) { if ctx.classRecv != nil { // in class file (.spx/.gmx) if recv := d.Recv; recv == nil || len(recv.List) == 0 { d.Recv = ctx.classRecv d.IsClass = true } } name := d.Name fname := name.Name if d.Recv == nil { fn := func() { old, _ := p.SetCurFile(goFile, true) defer p.RestoreCurFile(old) loadFunc(ctx, nil, fname, d, genFnBody) } if fname == "init" { if genFnBody { if debugLoad { log.Println("==> Preload func init") } parent.inits = append(parent.inits, fn) } } else { if debugLoad { log.Println("==> Preload func", fname) } if initLoader(parent, syms, name.Pos(), name.End(), fname, fn, genFnBody) { if strings.HasPrefix(fname, "XGox_") { // XGox_xxx func ctx.lbinames = append(ctx.lbinames, fname) } } } } else if tname, ok := getRecvTypeName(parent, d.Recv, true); ok { if d.Static { if debugLoad { log.Printf("==> Preload static method %s.%s\n", tname, fname) } fname = staticMethod(tname, fname) fn := func() { old, _ := p.SetCurFile(goFile, true) defer p.RestoreCurFile(old) loadFunc(ctx, nil, fname, d, genFnBody) } initLoader(parent, syms, name.Pos(), name.End(), fname, fn, genFnBody) ctx.lbinames = append(ctx.lbinames, fname) } else { if debugLoad { log.Printf("==> Preload method %s.%s\n", tname, fname) } ld := getTypeLoader(parent, syms, token.NoPos, token.NoPos, tname) fn := func() { old, _ := p.SetCurFile(goFile, true) defer p.RestoreCurFile(old) doInitType(ld) recv := toRecv(ctx, d.Recv) loadFunc(ctx, recv, fname, d, genFnBody) } ld.methods = append(ld.methods, fn) } } } preloadConst := func(d *ast.GenDecl) { pkg := ctx.pkg cdecl := pkg.NewConstDefs(pkg.Types.Scope()) for _, spec := range d.Specs { vSpec := spec.(*ast.ValueSpec) if debugLoad { log.Println("==> Preload const", vSpec.Names) } setNamesLoader(parent, syms, vSpec.Names, func() { if c := cdecl; c != nil { cdecl = nil loadConstSpecs(ctx, c, d.Specs) for _, s := range d.Specs { v := s.(*ast.ValueSpec) removeNames(syms, v.Names) } } }) } } for _, decl := range f.Decls { switch d := decl.(type) { case *ast.GenDecl: switch d.Tok { case token.IMPORT: for _, item := range d.Specs { loadImport(ctx, item.(*ast.ImportSpec)) } case token.TYPE: for _, spec := range d.Specs { t := spec.(*ast.TypeSpec) tName := t.Name name := tName.Name if debugLoad { log.Println("==> Preload type", name) } pos := tName.Pos() end := tName.End() ld := getTypeLoader(parent, syms, pos, end, name) defs := ctx.pkg.NewTypeDefs() if goFile != skippingGoFile { // is XGo file ld.typ = func() { old, _ := p.SetCurFile(goFile, true) defer p.RestoreCurFile(old) if t.Assign != token.NoPos { // alias type if debugLoad { log.Println("==> Load > AliasType", name) } typ := defs.AliasType(name, toType(ctx, t.Type), tName) if rec := ctx.recorder(); rec != nil { if obj, ok := typ.(interface{ Obj() *types.TypeName }); ok { rec.Def(tName, obj.Obj()) } } return } if debugLoad { log.Println("==> Load > NewType", name) } decl := defs.NewType(name, tName) if t.Doc != nil { defs.SetComments(t.Doc) } else if d.Doc != nil { defs.SetComments(d.Doc) } ld.typInit = func() { // decycle if debugLoad { log.Println("==> Load > InitType", name) } decl.InitType(ctx.pkg, toType(ctx, t.Type)) if rec := ctx.recorder(); rec != nil { rec.Def(tName, decl.Type().Obj()) } } } } else { ctx.generics[name] = true ld.typ = func() { pkg := ctx.pkg.Types if t.Assign != token.NoPos { // alias type if debugLoad { log.Println("==> Load > AliasType", name) } aliasType(ctx, pkg, t.Pos(), name, t) return } if debugLoad { log.Println("==> Load > NewType", name) } named := newType(pkg, t.Pos(), name) ld.typInit = func() { // decycle if debugLoad { log.Println("==> Load > InitType", name) } initType(ctx, named, t) } } } } case token.CONST: preloadConst(d) case token.VAR: if d == classDecl { // skip class fields continue } for _, spec := range d.Specs { vSpec := spec.(*ast.ValueSpec) if debugLoad { log.Println("==> Preload var", vSpec.Names) } setNamesLoader(parent, syms, vSpec.Names, func() { if v := vSpec; v != nil { // only init once vSpec = nil old, _ := p.SetCurFile(goFile, true) defer p.RestoreCurFile(old) loadVars(ctx, v, d.Doc, true) removeNames(syms, v.Names) } }) } default: log.Panicln("TODO - tok:", d.Tok, "spec:", reflect.TypeOf(d.Specs).Elem()) } case *ast.FuncDecl: preloadFuncDecl(d) case *ast.OverloadFuncDecl: var recv *ast.Ident if ctx.classRecv != nil { // in class file (.spx/.gmx) if recv := d.Recv; recv == nil || len(recv.List) == 0 { d.Recv = &ast.FieldList{ List: []*ast.Field{ { Type: ctx.classRecv.List[0].Type.(*ast.StarExpr).X, }, }, } d.IsClass = true } } if d.Recv != nil { var ok bool recv, ok = d.Recv.List[0].Type.(*ast.Ident) if !ok { ctx.handleErrorf(d.Recv.List[0].Type.Pos(), d.Recv.List[0].Type.End(), "invalid recv type %v", ctx.LoadExpr(d.Recv.List[0].Type)) break } ctx.lbinames = append(ctx.lbinames, recv) if ctx.rec != nil { ctx.rec.ReferUse(recv, recv.Name) } } onames := make([]string, 0, 4) exov := false name := d.Name LoopFunc: for idx, fn := range d.Funcs { switch expr := fn.(type) { case *ast.Ident: if d.Recv != nil && !d.Operator && !d.IsClass { ctx.handleErrorf(expr.Pos(), expr.End(), "invalid method %v", ctx.LoadExpr(expr)) break LoopFunc } exov = true if d.IsClass { onames = append(onames, "."+expr.Name) } else { onames = append(onames, expr.Name) ctx.lbinames = append(ctx.lbinames, expr.Name) } if ctx.rec != nil { if d.IsClass { ctx.rec.ReferUse(expr, recv.Name+"."+expr.Name) } else { ctx.rec.ReferUse(expr, expr.Name) } } case *ast.SelectorExpr: if d.Recv == nil || d.IsClass { ctx.handleErrorf(expr.Pos(), expr.End(), "invalid func %v", ctx.LoadExpr(expr)) break LoopFunc } rtyp, ok := checkOverloadMethodRecvType(recv, expr.X) if !ok { ctx.handleErrorf(expr.Pos(), expr.End(), "invalid recv type %v", ctx.LoadExpr(expr.X)) break LoopFunc } onames = append(onames, "."+expr.Sel.Name) exov = true if ctx.rec != nil { ctx.rec.ReferUse(rtyp, rtyp.Name) ctx.rec.ReferUse(expr.Sel, rtyp.Name+"."+expr.Sel.Name) } case *ast.FuncLit: if d.Recv != nil && !d.Operator && !d.IsClass { ctx.handleErrorf(expr.Pos(), expr.End(), "invalid method %v", ctx.LoadExpr(expr)) break LoopFunc } name1 := overloadFuncName(name.Name, idx) onames = append(onames, "") // const XGoo_xxx = "xxxInt,,xxxFloat" ctx.lbinames = append(ctx.lbinames, name1) id := &ast.Ident{NamePos: expr.Pos(), Name: name1} if ctx.rec != nil { ctx.rec.ReferDef(id, expr) } preloadFuncDecl(&ast.FuncDecl{ Doc: d.Doc, Name: id, Type: expr.Type, Body: expr.Body, }) default: ctx.handleErrorf(expr.Pos(), expr.End(), "unknown func %v", ctx.LoadExpr(expr)) break LoopFunc } } if exov { // need XGoo_xxx oname, err := overloadName(recv, name.Name, d.Operator) if err != nil { ctx.handleErrorf(name.Pos(), name.End(), "%v", err) break } if recv != nil { ctx.overpos[recv.Name+"."+name.Name] = name.NamePos } else { ctx.overpos[name.Name] = name.NamePos } oval := strings.Join(onames, ",") preloadConst(&ast.GenDecl{ Doc: d.Doc, Tok: token.CONST, Specs: []ast.Spec{ &ast.ValueSpec{ Names: []*ast.Ident{{Name: oname}}, Values: []ast.Expr{stringLit(oval)}, }, }, }) ctx.lbinames = append(ctx.lbinames, oname) } else { ctx.overpos[name.Name] = name.NamePos } if ctx.rec != nil { ctx.rec.ReferDef(d.Name, d) } default: log.Panicf("TODO - cl.preloadFile: unknown decl - %T\n", decl) } } } func checkOverloadMethodRecvType(ot *ast.Ident, recv ast.Expr) (*ast.Ident, bool) { rtyp, _, ok := getRecvType(recv) if !ok { return nil, false } rt, ok := rtyp.(*ast.Ident) if !ok || ot.Name != rt.Name { return nil, false } return rt, true } const ( indexTable = "0123456789abcdefghijklmnopqrstuvwxyz" ) func overloadFuncName(name string, idx int) string { return name + "__" + indexTable[idx:idx+1] } func overloadName(recv *ast.Ident, name string, isOp bool) (string, error) { if isOp { if oname, ok := binaryXGoNames[name]; ok { name = oname } else { return "", fmt.Errorf("invalid overload operator %v", name) } } sep := "_" if strings.ContainsRune(name, '_') || (recv != nil && strings.ContainsRune(recv.Name, '_')) { sep = "__" } typ := "" if recv != nil { typ = recv.Name + sep } return "XGoo" + sep + typ + name, nil } func staticMethod(tname, name string) string { sep := "_" if strings.ContainsRune(name, '_') || strings.ContainsRune(tname, '_') { sep = "__" } return "XGos" + sep + tname + sep + name } func stringLit(val string) *ast.BasicLit { return &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(val)} } func newType(pkg *types.Package, pos token.Pos, name string) *types.Named { typName := types.NewTypeName(pos, pkg, name, nil) if old := pkg.Scope().Insert(typName); old != nil { log.Panicf("%s redeclared in this block\n\tprevious declaration at %v\n", name, "") } return types.NewNamed(typName, nil, nil) } func aliasType(ctx *blockCtx, pkg *types.Package, pos token.Pos, name string, t *ast.TypeSpec) { var typeParams []*types.TypeParam if t.TypeParams != nil { typeParams = toTypeParams(ctx, t.TypeParams) ctx.tlookup = &typeParamLookup{typeParams} defer func() { ctx.tlookup = nil }() org := ctx.inInst ctx.inInst = 0 defer func() { ctx.inInst = org }() } o := types.NewAlias(types.NewTypeName(pos, pkg, name, nil), toType(ctx, t.Type)) if typeParams != nil { o.SetTypeParams(typeParams) } pkg.Scope().Insert(o.Obj()) } func loadFunc(ctx *blockCtx, recv *types.Var, name string, d *ast.FuncDecl, genBody bool) { if debugLoad { if recv == nil { log.Println("==> Load func", name) } else { log.Printf("==> Load method %v.%s\n", recv.Type(), name) } } var pkg = ctx.pkg var initClass bool var sigBase *types.Signature if d.Shadow { if recv != nil && (name == "Main" || name == "MainEntry") { initClass = true // should call XGo_Init method if base := ctx.baseClass; base != nil { if f := findMethod(base, name); f != nil { sigBase = makeMainSig(recv, f) } } } } else if d.Operator { if recv != nil { // binary op if v, ok := binaryXGoNames[name]; ok { name = v } } else { // unary op if v, ok := unaryXGoNames[name]; ok { name = v at := pkg.Types arg1 := d.Type.Params.List[0] typ := toType(ctx, arg1.Type) recv = types.NewParam(arg1.Pos(), at, arg1.Names[0].Name, typ) d.Type.Params.List = nil } } } sig := sigBase if sig == nil { sig = toFuncType(ctx, d.Type, recv, d) } fn, err := pkg.NewFuncWith(d.Name.Pos(), name, sig, func() token.Pos { return d.Recv.List[0].Type.Pos() }) if err != nil { ctx.handleErr(err) return } commentFunc(ctx, fn, d) if rec := ctx.recorder(); rec != nil { rec.Def(d.Name, fn.Func) if recv == nil && name != "_" { ctx.fileScope.Insert(fn.Func) } } if genBody { if body := d.Body; body != nil { if recv != nil { file := pkg.CurFile() ctx.inits = append(ctx.inits, func() { // interface issue: #795 old := pkg.RestoreCurFile(file) loadFuncBody(ctx, fn, body, sigBase, d, initClass) pkg.RestoreCurFile(old) }) } else { loadFuncBody(ctx, fn, body, nil, d, initClass) } } } } var binaryXGoNames = map[string]string{ "+": "XGo_Add", "-": "XGo_Sub", "*": "XGo_Mul", "/": "XGo_Quo", "%": "XGo_Rem", "&": "XGo_And", "|": "XGo_Or", "^": "XGo_Xor", "<<": "XGo_Lsh", ">>": "XGo_Rsh", "&^": "XGo_AndNot", "+=": "XGo_AddAssign", "-=": "XGo_SubAssign", "*=": "XGo_MulAssign", "/=": "XGo_QuoAssign", "%=": "XGo_RemAssign", "&=": "XGo_AndAssign", "|=": "XGo_OrAssign", "^=": "XGo_XorAssign", "<<=": "XGo_LshAssign", ">>=": "XGo_RshAssign", "&^=": "XGo_AndNotAssign", "==": "XGo_EQ", "!=": "XGo_NE", "<=": "XGo_LE", "<": "XGo_LT", ">=": "XGo_GE", ">": "XGo_GT", "->": "XGo_PointTo", "<>": "XGo_PointBi", "&&": "XGo_LAnd", "||": "XGo_LOr", "<-": "XGo_Send", } var unaryXGoNames = map[string]string{ "++": "XGo_Inc", "--": "XGo_Dec", "-": "XGo_Neg", "+": "XGo_Dup", "^": "XGo_Not", "!": "XGo_LNot", "<-": "XGo_Recv", } // shouldCallXGoInit checks if XGo_Init is directly defined on the receiver // type (not through embedding). func shouldCallXGoInit(recv *types.Var) bool { typ := recv.Type() if pt, ok := typ.(*types.Pointer); ok { typ = pt.Elem() } return findMethodByType(typ, "XGo_Init") != nil } func loadFuncBody(ctx *blockCtx, fn *gogen.Func, body *ast.BlockStmt, sigBase *types.Signature, src ast.Node, initClass bool) { cb := fn.BodyStart(ctx.pkg, body) cb.SetComments(nil, false) if initClass { recv := fn.Type().(*types.Signature).Recv() if shouldCallXGoInit(recv) { // this.XGo_Init() cb.Val(recv).MemberVal("XGo_Init", 0).Call(0).EndStmt() } if sigBase != nil { // this.Sprite.Main(...) or this.Game.MainEntry(...) cb.Val(recv).MemberVal(ctx.baseClass.Name(), 0).MemberVal(fn.Name(), 0) params := sigBase.Params() n := params.Len() for i := 0; i < n; i++ { cb.Val(params.At(i)) } cb.Call(n).EndStmt() } } compileStmts(ctx, body.List) if rec := ctx.recorder(); rec != nil { switch fn := src.(type) { case *ast.FuncDecl: rec.Scope(fn.Type, cb.Scope()) case *ast.FuncLit: rec.Scope(fn.Type, cb.Scope()) } } cb.End(src) } func loadImport(ctx *blockCtx, spec *ast.ImportSpec) { if enableRecover { defer func() { if e := recover(); e != nil { ctx.handleRecover(e, spec) } }() } pkgPath := simplifyPkgPath(toString(spec.Path)) pkg := ctx.pkg.Import(pkgPath, spec) var pos token.Pos var name string var specName = spec.Name if specName != nil { name, pos = specName.Name, specName.Pos() } else { name, pos = pkg.Types.Name(), spec.Path.Pos() } pkgName := types.NewPkgName(pos, ctx.pkg.Types, name, pkg.Types) if rec := ctx.recorder(); rec != nil { if specName != nil { rec.Def(specName, pkgName) } else { rec.Implicit(spec, pkgName) } if name != "_" { ctx.fileScope.Insert(pkgName) } } if specName != nil { if name == "." { ctx.lookups = append(ctx.lookups, pkg) return } if name == "_" { pkg.MarkForceUsed(ctx.pkg) return } } ctx.imports[name] = pkgImp{pkg, pkgName} } func loadConstSpecs(ctx *blockCtx, cdecl *gogen.ConstDefs, specs []ast.Spec) { for iotav, spec := range specs { vSpec := spec.(*ast.ValueSpec) loadConsts(ctx, cdecl, vSpec, iotav) } } func loadConsts(ctx *blockCtx, cdecl *gogen.ConstDefs, v *ast.ValueSpec, iotav int) { vNames := v.Names names := makeNames(vNames) if v.Values == nil { if debugLoad { log.Println("==> Load const", names) } cdecl.Next(iotav, v.Pos(), names...) defNames(ctx, vNames, nil) return } var typ types.Type if v.Type != nil { typ = toType(ctx, v.Type) } if debugLoad { log.Println("==> Load const", names, typ) } fn := func(cb *gogen.CodeBuilder) int { for _, val := range v.Values { compileExpr(ctx, 1, val) } return len(v.Values) } cdecl.New(fn, iotav, v.Pos(), typ, names...) defNames(ctx, v.Names, nil) } func loadVars(ctx *blockCtx, v *ast.ValueSpec, doc *ast.CommentGroup, global bool) { var typ types.Type if v.Type != nil { typ = toType(ctx, v.Type) } names := makeNames(v.Names) if debugLoad { log.Println("==> Load var", typ, names) } var scope *types.Scope if global { scope = ctx.pkg.Types.Scope() } else { scope = ctx.cb.Scope() } varDefs := ctx.pkg.NewVarDefs(scope).SetComments(doc) initExpr := makeInitExpr(ctx, v, typ, names) varDefs.NewAndInit(initExpr, v.Names[0].Pos(), typ, names...) defNames(ctx, v.Names, scope) } func makeInitExpr(ctx *blockCtx, v *ast.ValueSpec, typ types.Type, names []string) gogen.F { nv := len(v.Values) if nv == 0 { return nil } return func(cb *gogen.CodeBuilder) int { if nv == 1 && len(names) == 2 { compileExpr(ctx, len(names), v.Values[0], 0) } else { for _, val := range v.Values { switch e := val.(type) { case *ast.LambdaExpr, *ast.LambdaExpr2: if len(v.Values) == 1 { sig, err := checkLambdaFuncType(ctx, e, typ, clLambaAssign, v.Names[0]) if err != nil { panic(err) } compileLambda(ctx, e, sig) } else { panic(ctx.newCodeErrorf(e.Pos(), e.End(), "lambda unsupport multiple assignment")) } case *ast.SliceLit: compileSliceLit(ctx, e, typ) case *ast.CompositeLit: compileCompositeLit(ctx, e, typ, false) default: compileExpr(ctx, 1, val) } } } return nv } } func defNames(ctx *blockCtx, names []*ast.Ident, scope *types.Scope) { if rec := ctx.recorder(); rec != nil { if scope == nil { scope = ctx.cb.Scope() } for _, name := range names { if o := scope.Lookup(name.Name); o != nil { rec.Def(name, o) } } } } func makeNames(vals []*ast.Ident) []string { names := make([]string, len(vals)) for i, v := range vals { names[i] = v.Name } return names } func removeNames(syms map[string]loader, names []*ast.Ident) { for _, name := range names { delete(syms, name.Name) } } func setNamesLoader(ctx *pkgCtx, syms map[string]loader, names []*ast.Ident, load func()) { for _, name := range names { initLoader(ctx, syms, name.Pos(), name.End(), name.Name, load, true) } } // ----------------------------------------------------------------------------- ================================================ FILE: cl/compile_spx_test.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cl_test import ( "testing" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/cl/cltest" "github.com/goplus/xgo/parser/fsx/memfs" ) func gopSpxTest(t *testing.T, gmx, spxcode, expected string) { cltest.SpxEx(t, gmx, spxcode, expected, "index.tgmx", "bar.tspx") } func gopSpxTestEx(t *testing.T, gmx, spxcode, expected, gmxfile, spxfile string) { cltest.SpxWithConf(t, "gopSpxTest", cltest.Conf, gmx, spxcode, expected, gmxfile, spxfile, "") } func gopSpxTestEx2(t *testing.T, gmx, spxcode, expected, gmxfile, spxfile, resultFile string) { cltest.SpxWithConf(t, "gopSpxTest", cltest.Conf, gmx, spxcode, expected, gmxfile, spxfile, resultFile) } func gopSpxTestExConf(t *testing.T, name string, conf *cl.Config, gmx, spxcode, expected, gmxfile, spxfile, resultFile string) { cltest.SpxWithConf(t, name, conf, gmx, spxcode, expected, gmxfile, spxfile, resultFile) } func gopSpxErrorTestEx(t *testing.T, msg, gmx, spxcode, gmxfile, spxfile string) { cltest.SpxErrorEx(t, msg, gmx, spxcode, gmxfile, spxfile) } func gopSpxErrorTestMap(t *testing.T, msg string, dirs map[string][]string, files map[string]string) { cltest.SpxErrorFS(t, msg, memfs.New(dirs, files)) } func TestSpxError(t *testing.T) { gopSpxErrorTestEx(t, `Game.tgmx:7:16: cannot use *Kai (type Kai) as type github.com/goplus/xgo/cl/internal/spx.Sprite in slice literal`, ` var ( foo []Sprite Kai *Kai ) foo = []Sprite{*Kai} `, ``, "Game.tgmx", "Kai.tspx") gopSpxErrorTestEx(t, `Game.tgmx:1:6: Game redeclared in this block previous declaration at Game.tgmx:1:1 Game.tgmx:1:1: invalid receiver type Game (Game is an interface type) Game.tgmx:1:1: invalid receiver type Game (Game is an interface type)`, `type Game interface {}`, ``, "Game.tgmx", "Kai.tspx") gopSpxErrorTestEx(t, `Kai.tspx:1:1: Kai redeclared in this block previous declaration at Game.tgmx:1:6`, `type Kai interface {}`, ``, "Game.tgmx", "Kai.tspx") gopSpxErrorTestEx(t, `Game.tgmx:6:2: userScore redeclared Game.tgmx:5:2 other declaration of userScore`, ` import "bytes" var ( Kai Kai userScore int userScore string ) `, ` println "hi" `, "Game.tgmx", "Kai.tspx") gopSpxErrorTestEx(t, `Kai.tspx:4:2: id redeclared Kai.tspx:3:2 other declaration of id`, ` var ( Kai Kai userScore int ) `, ` var ( id int id string ) println "hi" `, "Game.tgmx", "Kai.tspx") gopSpxErrorTestEx(t, `Game.t4gmx:6:2: userScore redeclared Game.t4gmx:5:2 other declaration of userScore Kai.t4spx:1:1: cannot use (type *Kai) as type github.com/goplus/xgo/cl/internal/spx4.Sprite in argument to `, ` import "bytes" var ( Kai Kai userScore int userScore string ) `, ` println "hi" `, "Game.t4gmx", "Kai.t4spx") gopSpxErrorTestMap(t, `Kai.t4spx:4:2: userScore redeclared Kai.t4spx:3:2 other declaration of userScore Greem.t4spx:1:1: cannot use (type *Greem) as type github.com/goplus/xgo/cl/internal/spx4.Sprite in argument to `, map[string][]string{ "/foo": {"Game.t4gmx", "Kai.t4spx", "Greem.t4spx"}, }, map[string]string{ "/foo/Game.t4gmx": `println "hi"`, "/foo/Kai.t4spx": `var ( Kai Kai userScore int userScore string )`, "/foo/Greem.t4spx": ``, }) gopSpxErrorTestMap(t, `Game.t4gmx:1:9: cannot use backdropName (type string) as type error in assignment`, map[string][]string{ "/foo": {"Game.t4gmx"}, }, map[string]string{ "/foo/Game.t4gmx": `println backdropName!`, }) gopSpxErrorTestEx(t, `Game.t5gmx:4:2: Kai conflicts with class name. rename the field to resolve the naming conflict. Kai.t5spx:1:1: cannot use (type *Kai) as type github.com/goplus/xgo/cl/internal/spx4.Sprite in argument to `, ` var ( Kai Kai ) `, ` println "hi" `, "Game.t5gmx", "Kai.t5spx") } func TestSpxBasic(t *testing.T) { gopSpxTest(t, ` import ( "fmt" ) const ( Foo = 1 ) func bar() { } func onInit() { Foo bar fmt.Println("Hi") } `, ``, `package main import ( "fmt" "github.com/goplus/xgo/cl/internal/spx" ) const Foo = 1 type bar struct { spx.Sprite *index } type index struct { *spx.MyGame } func (this *index) bar() { } func (this *index) onInit() { this.bar() fmt.Println("Hi") } func (this *index) MainEntry() { } func (this *index) Main() { spx.Gopt_MyGame_Main(this) } func (this *bar) Main() { } func main() { new(index).Main() } `) } func TestEnvOp(t *testing.T) { gopSpxTest(t, ` echo ${PATH}, $id `, ``, `package main import ( "fmt" "github.com/goplus/xgo/cl/internal/spx" ) type bar struct { spx.Sprite *index } type index struct { *spx.MyGame } func (this *index) MainEntry() { fmt.Println(this.Gop_Env("PATH"), this.Gop_Env("id")) } func (this *index) Main() { spx.Gopt_MyGame_Main(this) } func (this *bar) Main() { } func main() { new(index).Main() } `) } func TestSpxXGoEnv(t *testing.T) { gopSpxTest(t, ` echo "${PATH}" `, ``, `package main import ( "fmt" "github.com/goplus/xgo/cl/internal/spx" "strconv" ) type bar struct { spx.Sprite *index } type index struct { *spx.MyGame } func (this *index) MainEntry() { fmt.Println(strconv.Itoa(this.Gop_Env("PATH"))) } func (this *index) Main() { spx.Gopt_MyGame_Main(this) } func (this *bar) Main() { } func main() { new(index).Main() } `) } func TestSpxXGoExec(t *testing.T) { gopSpxTest(t, ` vim "a.txt" vim ls 10 capout => { ls } capout => { ls "-l" } `, ``, `package main import "github.com/goplus/xgo/cl/internal/spx" type bar struct { spx.Sprite *index } type index struct { *spx.MyGame } func (this *index) MainEntry() { this.Gop_Exec("vim", "a.txt") this.Gop_Exec("vim") this.Ls(10) this.Capout(func() { this.Gop_Exec("ls") }) this.Capout(func() { this.Gop_Exec("ls", "-l") }) } func (this *index) Main() { spx.Gopt_MyGame_Main(this) } func (this *bar) Main() { } func main() { new(index).Main() } `) } func TestSpxMethod(t *testing.T) { gopSpxTestEx(t, ` func onInit() { sched broadcast "msg1" TestIntValue = 1 x := round(1.2) } `, ` func onInit() { setCostume "kai-a" play "recordingWhere" say "Where do you come from?", 2 broadcast "msg2" } `, `package main import ( "github.com/goplus/xgo/cl/internal/spx" "math" ) type Game struct { *spx.MyGame } type bar struct { spx.Sprite *Game } func (this *Game) onInit() { spx.Sched() this.Broadcast__0("msg1") spx.TestIntValue = 1 x := math.Round(1.2) } func (this *Game) MainEntry() { } func (this *Game) Main() { spx.Gopt_MyGame_Main(this) } func (this *bar) onInit() { this.SetCostume("kai-a") this.Play("recordingWhere") this.Say("Where do you come from?", 2) this.Broadcast__0("msg2") } func (this *bar) Main() { } func main() { new(Game).Main() } `, "Game.tgmx", "bar.tspx") } func TestSpxVar(t *testing.T) { gopSpxTestEx(t, ` var ( Kai Kai ) func onInit() { Kai.clone() broadcast("msg1") } `, ` var ( a int ) func onInit() { a = 1 } func onCloned() { say("Hi") } `, `package main import "github.com/goplus/xgo/cl/internal/spx" type Game struct { *spx.MyGame Kai Kai } type Kai struct { spx.Sprite *Game a int } func (this *Game) onInit() { spx.Gopt_Sprite_Clone__0(this.Kai) this.Broadcast__0("msg1") } func (this *Game) MainEntry() { } func (this *Game) Main() { spx.Gopt_MyGame_Main(this) } func (this *Kai) onInit() { this.a = 1 } func (this *Kai) onCloned() { this.Say("Hi") } func (this *Kai) Main() { } func main() { new(Game).Main() } `, "Game.tgmx", "Kai.tspx") } func TestSpxRun(t *testing.T) { gopSpxTestEx(t, ` var ( Kai Kai t Sound ) var x float64 = rand(1.2) run "hzip://open.qiniu.us/weather/res.zip" `, ` println "Hi" `, `package main import ( "fmt" "github.com/goplus/xgo/cl/internal/spx" ) type Kai struct { spx.Sprite *index } type index struct { *spx.MyGame Kai Kai t spx.Sound } var x float64 = spx.Rand__1(1.2) func (this *index) MainEntry() { spx.Gopt_MyGame_Run(this, "hzip://open.qiniu.us/weather/res.zip") } func (this *index) Main() { spx.Gopt_MyGame_Main(this) } func (this *Kai) Main() { fmt.Println("Hi") } func main() { new(index).Main() } `, "index.tgmx", "Kai.tspx") } func TestSpx2(t *testing.T) { gopSpxTestEx(t, ` println("Hi") `, ` func onMsg(msg string) { } `, `package main import ( "fmt" "github.com/goplus/xgo/cl/internal/spx2" ) type Game struct { spx2.Game } type Kai struct { spx2.Sprite *Game } func (this *Game) MainEntry() { fmt.Println("Hi") } func (this *Game) Main() { (*spx2.Game).Main(&this.Game) } func (this *Kai) onMsg(msg string) { } func (this *Kai) Main() { } func main() { new(Game).Main() } `, "Game.t2gmx", "Kai.t2spx") } func TestSpxMainEntry(t *testing.T) { conf := *cltest.Conf conf.Importer = nil conf.NoAutoGenMain = false gopSpxTestExConf(t, "Nocode", &conf, ` `, ` `, `package main import "github.com/goplus/xgo/cl/internal/spx2" type Game struct { spx2.Game } type Kai struct { spx2.Sprite *Game } func (this *Game) MainEntry() { } func (this *Game) Main() { (*spx2.Game).Main(&this.Game) } func (this *Kai) Main() { } func main() { new(Game).Main() } `, "Game.t2gmx", "Kai.t2spx", "") gopSpxTestExConf(t, "OnlyGmx", &conf, ` var ( Kai Kai ) `, ` `, `package main import "github.com/goplus/xgo/cl/internal/spx2" type Game struct { spx2.Game Kai Kai } type Kai struct { spx2.Sprite *Game } func (this *Game) MainEntry() { } func (this *Game) Main() { (*spx2.Game).Main(&this.Game) } func (this *Kai) Main() { } func main() { new(Game).Main() } `, "Game.t2gmx", "Kai.t2spx", "") gopSpxTestExConf(t, "KaiAndGmx", &conf, ` var ( Kai Kai ) func MainEntry() { println "Hi" } `, ` func Main() { println "Hello" } func onMsg(msg string) { } `, `package main import ( "fmt" "github.com/goplus/xgo/cl/internal/spx2" ) type Game struct { spx2.Game Kai Kai } type Kai struct { spx2.Sprite *Game } func (this *Game) MainEntry() { fmt.Println("Hi") } func (this *Game) Main() { (*spx2.Game).Main(&this.Game) } func (this *Kai) Main() { fmt.Println("Hello") } func (this *Kai) onMsg(msg string) { } func main() { new(Game).Main() } `, "Game.t2gmx", "Kai.t2spx", "") } func TestSpxGoxBasic(t *testing.T) { gopSpxTestEx(t, ` func onInit() { for { } } `, ` func onMsg(msg string) { for { say "Hi" } } `, `package main import "github.com/goplus/xgo/cl/internal/spx" type Game struct { *spx.MyGame } type Kai struct { spx.Sprite *Game } func (this *Game) onInit() { for { spx.SchedNow() } } func (this *Game) MainEntry() { } func (this *Game) Main() { spx.Gopt_MyGame_Main(this) } func (this *Kai) onMsg(msg string) { for { spx.Sched() this.Say("Hi") } } func (this *Kai) Main() { } func main() { new(Game).Main() } `, "Game.tgmx", "Kai.tspx") } func TestSpxClone(t *testing.T) { gopSpxTestEx(t, ` var ( Kai Kai ) func onInit() { Kai.clone() broadcast("msg1") } `, ` var ( a int ) type info struct { x int y int } func onInit() { a = 1 clone clone info{1,2} clone &info{1,2} } func onCloned() { say("Hi") } `, `package main import "github.com/goplus/xgo/cl/internal/spx" type info struct { x int y int } type Game struct { *spx.MyGame Kai Kai } type Kai struct { spx.Sprite *Game a int } func (this *Game) onInit() { spx.Gopt_Sprite_Clone__0(this.Kai) this.Broadcast__0("msg1") } func (this *Game) MainEntry() { } func (this *Game) Main() { spx.Gopt_MyGame_Main(this) } func (this *Kai) onInit() { this.a = 1 spx.Gopt_Sprite_Clone__0(this) spx.Gopt_Sprite_Clone__1(this, info{1, 2}) spx.Gopt_Sprite_Clone__1(this, &info{1, 2}) } func (this *Kai) onCloned() { this.Say("Hi") } func (this *Kai) Main() { } func main() { new(Game).Main() } `, "Game.tgmx", "Kai.tspx") } func TestSpxErrorEnv(t *testing.T) { gopSpxErrorTestEx(t, `Game.t2gmx:2:9: undefined: PATH`, ` echo "${PATH}" `, ``, "Game.t2gmx", "Kai.t2spx") } func TestSpxErrorSel(t *testing.T) { gopSpxErrorTestEx(t, `Kai.tspx:2:9: this.pos undefined (type *Kai has no field or method pos)`, ` println "hi" `, ` println this.pos `, "Game.tgmx", "Kai.tspx") } func TestSpxMethodSel(t *testing.T) { gopSpxTestEx(t, ` sendMessage "Hi" `, ` func onMsg(msg string) { } `, `package main import "github.com/goplus/xgo/cl/internal/spx" type Game struct { *spx.MyGame } type Kai struct { spx.Sprite *Game } func (this *Game) MainEntry() { this.SendMessage("Hi") } func (this *Game) Main() { spx.Gopt_MyGame_Main(this) } func (this *Kai) onMsg(msg string) { } func (this *Kai) Main() { } func main() { new(Game).Main() } `, "Game.tgmx", "Kai.tspx") } func TestSpxPkgOverload(t *testing.T) { gopSpxTestEx(t, ` println "Hi" `, ` func onMsg(msg string) { this.position.add 100,200 } `, `package main import ( "fmt" "github.com/goplus/xgo/cl/internal/spx" ) type Game struct { *spx.MyGame } type Kai struct { spx.Sprite *Game } func (this *Game) MainEntry() { fmt.Println("Hi") } func (this *Game) Main() { spx.Gopt_MyGame_Main(this) } func (this *Kai) onMsg(msg string) { this.Position().Add__0(100, 200) } func (this *Kai) Main() { } func main() { new(Game).Main() } `, "Game.tgmx", "Kai.tspx") } func TestSpxSelection(t *testing.T) { gopSpxTestEx(t, ` println "hi" `, ` import "fmt" func onMsg(msg string) { fmt.println msg this.position.add 100,200 position.add 100,200 position.X += 100 println position.X this.vector.add 100,200 vector.add 100,200 vector.X += 100 vector.self.X += 100 vector.self.Y += 200 vector.self.add position.X,position.Y println vector.X println vector.self.self } `, `package main import ( "fmt" "github.com/goplus/xgo/cl/internal/spx" ) type Game struct { *spx.MyGame } type Kai struct { spx.Sprite *Game } func (this *Game) MainEntry() { fmt.Println("hi") } func (this *Game) Main() { spx.Gopt_MyGame_Main(this) } func (this *Kai) onMsg(msg string) { fmt.Println(msg) this.Position().Add__0(100, 200) this.Position().Add__0(100, 200) this.Position().X += 100 fmt.Println(this.Position().X) this.Vector().Add__0(100, 200) this.Vector().Add__0(100, 200) this.Vector().X += 100 this.Vector().Self().X += 100 this.Vector().Self().Y += 200 this.Vector().Self().Add__0(this.Position().X, this.Position().Y) fmt.Println(this.Vector().X) fmt.Println(this.Vector().Self().Self()) } func (this *Kai) Main() { } func main() { new(Game).Main() } `, "Game.tgmx", "Kai.tspx") } func TestSpxOverload(t *testing.T) { gopSpxTestEx(t, ` var ( Kai Kai ) func onInit() { Kai.onKey "hello", key => { } } `, ` var ( a int ) type Mesh struct { } func (p *Mesh) Name() string { return "hello" } var ( m1 = &Mesh{} m2 = &Mesh{} ) onKey "hello", => { } onKey "hello", key => { } onKey ["1"], => { } onKey ["2"], key => { } onKey [m1, m2], => { } onKey [m1, m2], key => { } onKey ["a"], ["b"], key => { } onKey ["a"], [m1, m2], key => { } onKey ["a"], nil, key => { } onKey 100, 200 onKey2 "hello", key => { } `, `package main import "github.com/goplus/xgo/cl/internal/spx" type Mesh struct { } type Game struct { *spx.MyGame Kai Kai } type Kai struct { spx.Sprite *Game a int } func (this *Game) onInit() { spx.Gopt_Sprite_OnKey__1(this.Kai, "hello", func(key string) { }) } func (this *Game) MainEntry() { } func (this *Game) Main() { spx.Gopt_MyGame_Main(this) } func (p *Mesh) Name() string { return "hello" } var m1 = &Mesh{} var m2 = &Mesh{} func (this *Kai) Main() { spx.Gopt_Sprite_OnKey__0(this, "hello", func() { }) spx.Gopt_Sprite_OnKey__1(this, "hello", func(key string) { }) spx.Gopt_Sprite_OnKey__2(this, []string{"1"}, func() { }) spx.Gopt_Sprite_OnKey__3(this, []string{"2"}, func(key string) { }) spx.Gopt_Sprite_OnKey__4(this, []spx.Mesher{m1, m2}, func() { }) spx.Gopt_Sprite_OnKey__5(this, []spx.Mesher{m1, m2}, func(key spx.Mesher) { }) spx.Gopt_Sprite_OnKey__6(this, []string{"a"}, []string{"b"}, func(key string) { }) spx.Gopt_Sprite_OnKey__7(this, []string{"a"}, []spx.Mesher{m1, m2}, func(key string) { }) spx.Gopt_Sprite_OnKey__6(this, []string{"a"}, nil, func(key string) { }) spx.Gopt_Sprite_OnKey__8(this, 100, 200) spx.Gopt_Sprite_OnKey2(this, "hello", func(key string) { }) } func main() { new(Game).Main() } `, "Game.tgmx", "Kai.tspx") } func TestTestClassFile(t *testing.T) { gopSpxTestEx2(t, ` println "Hi" `, ` t.log "Hi" t.run "a test", t => { t.fatal "failed" } `, `package main import ( "fmt" "github.com/goplus/xgo/test" "testing" ) type caseFoo struct { test.Case } type App struct { test.App } func (this *App) MainEntry() { fmt.Println("Hi") } func (this *caseFoo) Main() { this.T().Log("Hi") this.T().Run("a test", func(t *testing.T) { t.Fatal("failed") }) } func TestFoo(t *testing.T) { test.Gopt_Case_TestMain(new(caseFoo), t) } func TestMain(m *testing.M) { test.Gopt_App_TestMain(new(App), m) } `, "main_xtest.gox", "Foo_xtest.gox", "_test") } func TestTestClassFile2(t *testing.T) { gopSpxTestEx2(t, ` println "Hi" `, ` t.log "Hi" `, `package main import ( "github.com/goplus/xgo/test" "testing" ) type case_foo struct { test.Case } func (this *case_foo) Main() { this.T().Log("Hi") } func Test_foo(t *testing.T) { test.Gopt_Case_TestMain(new(case_foo), t) } `, "main.gox", "foo_xtest.gox", "_test") } func TestGoxNoFunc(t *testing.T) { gopClTestFile(t, ` var ( a int ) `, `package main type foo struct { a int } `, "foo.gox") } func TestGoxOverload(t *testing.T) { gopClTestFile(t, ` func addString(a, b string) string { return a + b } func addInt(a, b int) int { return a + b } func add = ( addInt func(a, b float64) float64 { return a + b } addString ) `, `package main const XGoo_Rect_add = ".addInt,,.addString" type Rect struct { } func (this *Rect) addString(a string, b string) string { return a + b } func (this *Rect) addInt(a int, b int) int { return a + b } func (this *Rect) add__1(a float64, b float64) float64 { return a + b } `, "Rect.gox") } func TestClassFileGox(t *testing.T) { gopClTestFile(t, ` var ( BaseClass Width, Height float64 *AggClass ) type BaseClass struct{ x int y int } type AggClass struct{} func Area() float64 { return Width * Height } `, `package main type BaseClass struct { x int y int } type AggClass struct { } type Rect struct { BaseClass Width float64 Height float64 *AggClass } func (this *Rect) Area() float64 { return this.Width * this.Height } `, "Rect.gox") gopClTestFile(t, ` import "bytes" var ( bytes.Buffer ) func test(){} `, `package main import "bytes" type Rect struct { bytes.Buffer } func (this *Rect) test() { } `, "Rect.gox") gopClTestFile(t, ` import "bytes" var ( *bytes.Buffer ) func test(){} `, `package main import "bytes" type Rect struct { *bytes.Buffer } func (this *Rect) test() { } `, "Rect.gox") gopClTestFile(t, ` import "bytes" var ( *bytes.Buffer "buffer" a int "a" b int ) func test(){} `, `package main import "bytes" type Rect struct { *bytes.Buffer `+"`_:\"buffer\"`"+` a int `+"`_:\"a\"`"+` b int } func (this *Rect) test() { } `, "Rect.gox") } func TestClassFileMember(t *testing.T) { gopClTestFile(t, `type Engine struct { } func (e *Engine) EnterPointerLock() { } func (e *Engine) SetEnable(b bool) { } func Engine() *Engine { return &Engine{} } func Test() { engine.setEnable true engine.enterPointerLock } `, `package main type Engine struct { } type Rect struct { } func (e *Engine) EnterPointerLock() { } func (e *Engine) SetEnable(b bool) { } func (this *Rect) Engine() *Engine { return &Engine{} } func (this *Rect) Test() { this.Engine().SetEnable(true) this.Engine().EnterPointerLock() } `, "Rect.gox") } ================================================ FILE: cl/compile_test.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cl_test import ( "os" "sync" "testing" "github.com/goplus/gogen" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/cl/cltest" ) const ( gopRootDir = ".." ) var ( gblConfLine *cl.Config ) func init() { cltest.XGo.Root = gopRootDir conf := cltest.Conf gblConfLine = &cl.Config{ Fset: conf.Fset, Importer: conf.Importer, Recorder: cltest.Conf.Recorder, LookupClass: cltest.LookupClass, NoFileLine: false, NoAutoGenMain: true, } } func gopClNamedTest(t *testing.T, name string, gopcode, expected string) { cltest.Named(t, name, gopcode, expected) } func gopClTest(t *testing.T, gopcode, expected string) { cltest.DoExt(t, cltest.Conf, "main", gopcode, expected) } func gopClTestFile(t *testing.T, gopcode, expected string, fname string) { cltest.DoWithFname(t, gopcode, expected, fname) } func gopClTestEx(t *testing.T, conf *cl.Config, pkgname, gopcode, expected string) { cltest.DoExt(t, conf, pkgname, gopcode, expected) } func gopMixedClTest(t *testing.T, pkgname, gocode, gopcode, expected string, outline ...bool) { cltest.Mixed(t, pkgname, gocode, gopcode, expected, outline...) } func TestTypeDoc(t *testing.T) { gopClTest(t, ` type ( // doc A int ) `, `package main // doc type A int `) } func TestUnsafe(t *testing.T) { gopClTest(t, ` import "unsafe" println unsafe.Sizeof(0) `, `package main import ( "fmt" "unsafe" ) func main() { fmt.Println(unsafe.Sizeof(0)) } `) } func Test_CastSlice_Issue1240(t *testing.T) { gopClTest(t, ` type fvec []float64 type foo float64 a := []float64([1, 2]) b := fvec([1, 2]) c := foo([1, 2]) d := fvec([]) println a, b, c, d `, `package main import "fmt" type fvec []float64 type foo float64 func main() { a := []float64{1, 2} b := fvec{1, 2} c := foo([]int{1, 2}) d := fvec{} fmt.Println(a, b, c, d) } `) } func TestUnderscoreRedeclared_Issue1197(t *testing.T) { gopClTest(t, ` func() (_ [2]int) { type _ int; return }() `, `package main func main() { func() (_ [2]int) { return }() } `) } func TestInterfaceBugNilUnderlying_Issue1198(t *testing.T) { gopClTest(t, ` import "runtime" type Outer interface{ Inner } type impl struct{} func New() Outer { return &impl{} } type Inner interface { DoStuff() error } func (a *impl) DoStuff() error { return nil } func main() { var outer Outer = New() } `, `package main type Outer interface { Inner } type impl struct { } type Inner interface { DoStuff() error } func (a *impl) DoStuff() error { return nil } func New() Outer { return &impl{} } func main() { var outer Outer = New() } `) } func TestInterfaceBugNilUnderlying_Issue1196(t *testing.T) { gopClTest(t, ` func main() { i := I(A{}) b := make(chan I, 1) b <- B{} var ok bool i, ok = <-b } type I interface{ M() int } type T int func (T) M() int { return 0 } type A struct{ T } type B struct{ T } `, `package main type I interface { M() int } type T int type A struct { T } type B struct { T } func main() { i := I(A{}) b := make(chan I, 1) b <- B{} var ok bool i, ok = <-b } func (T) M() int { return 0 } `) } func TestMyIntInc_Issue1195(t *testing.T) { gopClTest(t, ` type MyInt int var c MyInt c++ `, `package main type MyInt int var c MyInt func main() { c++ } `) } func TestAutoPropMixedName_Issue1194(t *testing.T) { gopClTest(t, ` type Point struct { Min, Max int } type Obj struct { bbox Point } func (o *Obj) Bbox() Point { return o.bbox } func (o *Obj) Points() [2]int{ return [2]int{o.bbox.Min, o.bbox.Max} } `, `package main type Point struct { Min int Max int } type Obj struct { bbox Point } func (o *Obj) Bbox() Point { return o.bbox } func (o *Obj) Points() [2]int { return [2]int{o.bbox.Min, o.bbox.Max} } `) } func TestShiftUntypedInt_Issue1193(t *testing.T) { gopClTest(t, ` func GetValue(shift uint) uint { return 1 << shift }`, `package main func GetValue(shift uint) uint { return 1 << shift } `) } func TestInitFunc(t *testing.T) { gopClTest(t, ` func init() {} func init() {} `, `package main func init() { } func init() { } `) } func TestSlogan(t *testing.T) { gopClTest(t, ` fields := ["engineering", "STEM education", "data science"] println "The XGo Language for", fields.join(", ") `, `package main import ( "fmt" "strings" ) func main() { fields := []string{"engineering", "STEM education", "data science"} fmt.Println("The XGo Language for", strings.Join(fields, ", ")) } `) } func TestAssignPrintln(t *testing.T) { gopClTest(t, ` p := println p "Hello world" `, `package main import "fmt" func main() { p := fmt.Println p("Hello world") } `) } func TestRedefineBuiltin(t *testing.T) { gopClTest(t, ` func main() { const a = append + len } const ( append = iota len ) `, `package main const ( append = iota len ) func main() { const a = append + len } `) } func TestTypeConvIssue804(t *testing.T) { gopClTest(t, ` c := make(chan int) d := (chan<- int)(c) e := (<-chan int)(c) f := (*int)(nil) a := c == d b := c == e `, `package main func main() { c := make(chan int) d := (chan<- int)(c) e := (<-chan int)(c) f := (*int)(nil) a := c == d b := c == e } `) } func TestUntypedFloatIssue798(t *testing.T) { gopClTest(t, ` func isPow10(x uint64) bool { switch x { case 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19: return true } return false } `, `package main func isPow10(x uint64) bool { switch x { case 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19: return true } return false } `) } func TestInterfaceIssue795(t *testing.T) { gopClTest(t, ` type I interface { a(s string) I b(s string) string } type T1 int func (t T1) a(s string) I { return t } func (T1) b(s string) string { return s } `, `package main type I interface { a(s string) I b(s string) string } type T1 int func (t T1) a(s string) I { return t } func (T1) b(s string) string { return s } `) } func TestChanRecvIssue789(t *testing.T) { gopClTest(t, ` func foo(ch chan int) (int, bool) { x, ok := (<-ch) return x, ok } `, `package main func foo(ch chan int) (int, bool) { x, ok := <-ch return x, ok } `) } func TestNamedChanCloseIssue790(t *testing.T) { gopClTest(t, ` type XChan chan int func foo(ch XChan) { close(ch) } `, `package main type XChan chan int func foo(ch XChan) { close(ch) } `) } func TestUntypedFloatIssue793(t *testing.T) { gopClTest(t, ` var a [1e1]int `, `package main var a [10]int `) } func TestUntypedFloatIssue788(t *testing.T) { gopClTest(t, ` func foo(v int) bool { return v > 1.1e5 } `, `package main func foo(v int) bool { return v > 1.1e5 } `) } func TestSwitchCompositeLitIssue801(t *testing.T) { gopClTest(t, ` type T struct { X int } switch (T{}) { case T{1}: panic("bad") } `, `package main type T struct { X int } func main() { switch (T{}) { case T{1}: panic("bad") } } `) } func TestConstIssue800(t *testing.T) { gopClTest(t, ` const ( h0_0, h0_1 = 1.0 / (iota + 1), 1.0 / (iota + 2) h1_0, h1_1 ) `, `package main const ( h0_0, h0_1 = 1.0 / (iota + 1), 1.0 / (iota + 2) h1_0, h1_1 ) `) } func TestConstIssue805(t *testing.T) { gopClTest(t, ` const ( n1 = +5 d1 = +3 q1 = +1 r1 = +2 ) const ( ret1 = n1/d1 != q1 ret2 = n1%d1 != r1 ret3 = n1/d1 != q1 || n1%d1 != r1 ) `, `package main const ( n1 = +5 d1 = +3 q1 = +1 r1 = +2 ) const ( ret1 = n1/d1 != q1 ret2 = n1%d1 != r1 ret3 = false ) `) } func TestUntypedNilIssue806(t *testing.T) { gopClTest(t, ` switch f := func() {}; f { case nil: } `, `package main func main() { switch f := func() { }; f { case nil: } } `) } func TestSwitchIssue807(t *testing.T) { gopClTest(t, ` switch { case interface{}(true): } `, `package main func main() { switch { case interface{}(true): } } `) } func TestUntypedComplexIssue799(t *testing.T) { gopClTest(t, ` const ulp1 = imag(1i + 2i / 3 - 5i / 3) const ulp2 = imag(1i + complex(0, 2) / 3 - 5i / 3) func main() { const a = (ulp1 == ulp2) } `, `package main const ulp1 = imag(1i + 2i/3 - 5i/3) const ulp2 = imag(1i + complex(0, 2)/3 - 5i/3) func main() { const a = ulp1 == ulp2 } `) } func TestUnderscoreConstAndVar(t *testing.T) { gopClTest(t, ` const ( c0 = 1 << iota _ _ _ c4 ) func i() int { return 23 } var ( _ = i() _ = i() ) `, `package main const ( c0 = 1 << iota _ _ _ c4 ) func i() int { return 23 } var _ = i() var _ = i() `) } func TestUnderscoreFuncAndMethod(t *testing.T) { gopClTest(t, ` func _() { } type T struct { _, _, _ int } func (T) _() { } func (T) _() { } `, `package main type T struct { _ int _ int _ int } func (T) _() { } func (T) _() { } func _() { } `) } func TestErrWrapIssue772(t *testing.T) { gopClTest(t, ` package main func t() (int,int,error){ return 0, 0, nil } func main() { a, b := t()! println(a, b) }`, `package main import ( "fmt" "github.com/qiniu/x/errors" ) func t() (int, int, error) { return 0, 0, nil } func main() { a, b := func() (_xgo_ret int, _xgo_ret2 int) { var _xgo_err error _xgo_ret, _xgo_ret2, _xgo_err = t() if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "t()", "/foo/bar.xgo", 9, "main.main") panic(_xgo_err) } return }() fmt.Println(a, b) } `) } func TestErrWrapIssue778(t *testing.T) { gopClTest(t, ` package main func t() error { return nil } func main() { t()! }`, `package main import "github.com/qiniu/x/errors" func t() error { return nil } func main() { func() { var _xgo_err error _xgo_err = t() if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "t()", "/foo/bar.xgo", 9, "main.main") panic(_xgo_err) } return }() } `) } func TestIssue774(t *testing.T) { gopClNamedTest(t, "InterfaceTypeAssert", ` package main import "fmt" func main() { var a AA = &A{str: "hello"} fmt.Println(a.(*A)) } type AA interface { String() string } type A struct { str string } func (a *A) String() string { return a.str } `, `package main import "fmt" type AA interface { String() string } type A struct { str string } func main() { var a AA = &A{str: "hello"} fmt.Println(a.(*A)) } func (a *A) String() string { return a.str } `) gopClNamedTest(t, "getInterface", ` package main import "fmt" func main() { a := get() fmt.Println(a.(*A)) } type AA interface { String() string } func get() AA { var a AA return a } type A struct { str string } func (a *A) String() string { return a.str } `, `package main import "fmt" type AA interface { String() string } type A struct { str string } func main() { a := get() fmt.Println(a.(*A)) } func get() AA { var a AA return a } func (a *A) String() string { return a.str } `) } func TestBlockStmt(t *testing.T) { gopClTest(t, ` package main func main() { { type T int t := T(100) println(t) } { type T string t := "hello" println(t) } } `, `package main import "fmt" func main() { { type T int t := T(100) fmt.Println(t) } { type T string t := "hello" fmt.Println(t) } } `) } func TestConstTypeConvIssue792(t *testing.T) { gopClTest(t, ` const dots = ". . . " + ". . . . . " const n = uint(len(dots)) `, `package main const dots = ". . . " + ". . . . . " const n = uint(len(dots)) `) } func TestVarInitTwoValueIssue791(t *testing.T) { gopClTest(t, ` var ( m = map[string]string{"a": "A"} a, ok = m["a"] ) `, `package main var m = map[string]string{"a": "A"} var a, ok = m["a"] `) } func TestVarAfterMain(t *testing.T) { gopClTest(t, ` package main func main() { println(i) } var i int `, `package main import "fmt" func main() { fmt.Println(i) } var i int `) gopClTest(t, ` package main func f(v float64) float64 { return v } func main() { sink = f(100) } var sink float64 `, `package main func f(v float64) float64 { return v } func main() { sink = f(100) } var sink float64 `) } func TestVarAfterMain2(t *testing.T) { gopClTest(t, ` package main func main() { println(i) } var i = 100 `, `package main import "fmt" func main() { fmt.Println(i) } var i = 100 `) } func TestVarInMain(t *testing.T) { gopClTest(t, ` package main func main() { v := []uint64{2, 3, 5} var n = len(v) println(n) }`, `package main import "fmt" func main() { v := []uint64{2, 3, 5} var n = len(v) fmt.Println(n) } `) } func TestSelect(t *testing.T) { gopClTest(t, ` func consume(xchg chan int) { select { case c := <-xchg: println(c) case xchg <- 1: println("send ok") default: println(0) } } `, `package main import "fmt" func consume(xchg chan int) { select { case c := <-xchg: fmt.Println(c) case xchg <- 1: fmt.Println("send ok") default: fmt.Println(0) } } `) } func TestTypeSwitch(t *testing.T) { gopClTest(t, ` func bar(p *interface{}) { } func foo(v interface{}) { switch t := v.(type) { case int, string: bar(&v) case bool: var x bool = t default: bar(nil) } } `, `package main func bar(p *interface{}) { } func foo(v interface{}) { switch t := v.(type) { case int, string: bar(&v) case bool: var x bool = t default: bar(nil) } } `) } func TestTypeSwitch2(t *testing.T) { gopClTest(t, ` func bar(p *interface{}) { } func foo(v interface{}) { switch bar(nil); v.(type) { case int, string: bar(&v) } } `, `package main func bar(p *interface{}) { } func foo(v interface{}) { switch bar(nil); v.(type) { case int, string: bar(&v) } } `) } func TestTypeAssert(t *testing.T) { gopClTest(t, ` func foo(v interface{}) { x := v.(int) y, ok := v.(string) } `, `package main func foo(v interface{}) { x := v.(int) y, ok := v.(string) } `) } func TestInterface(t *testing.T) { gopClTest(t, ` type Shape interface { Area() float64 } func foo(shape Shape) { shape.Area() } `, `package main type Shape interface { Area() float64 } func foo(shape Shape) { shape.Area() } `) } func TestInterfaceEmbedded(t *testing.T) { gopClTest(t, ` type Shape interface { Area() float64 } type Bar interface { Shape } `, `package main type Shape interface { Area() float64 } type Bar interface { Shape } `) } func TestInterfaceExample(t *testing.T) { gopClTest(t, ` type Shape interface { Area() float64 } type Rect struct { x, y, w, h float64 } func (p *Rect) Area() float64 { return p.w * p.h } type Circle struct { x, y, r float64 } func (p *Circle) Area() float64 { return 3.14 * p.r * p.r } func Area(shapes ...Shape) float64 { s := 0.0 for shape <- shapes { s += shape.Area() } return s } func main() { rect := &Rect{0, 0, 2, 5} circle := &Circle{0, 0, 3} println(Area(circle, rect)) } `, `package main import "fmt" type Shape interface { Area() float64 } type Rect struct { x float64 y float64 w float64 h float64 } type Circle struct { x float64 y float64 r float64 } func (p *Rect) Area() float64 { return p.w * p.h } func (p *Circle) Area() float64 { return 3.14 * p.r * p.r } func Area(shapes ...Shape) float64 { s := 0.0 for _, shape := range shapes { s += shape.Area() } return s } func main() { rect := &Rect{0, 0, 2, 5} circle := &Circle{0, 0, 3} fmt.Println(Area(circle, rect)) } `) } func TestEmbeddField(t *testing.T) { gopClTest(t, `import "math/big" type BigInt struct { *big.Int }`, `package main import "math/big" type BigInt struct { *big.Int } `) } func TestAutoProperty(t *testing.T) { gopClTest(t, `import "github.com/goplus/xgo/ast/goptest" func foo(script string) { doc := goptest.New(script)! echo doc.any.funcDecl.name echo doc.any.importSpec.name } `, `package main import ( "fmt" "github.com/goplus/xgo/ast/gopq" "github.com/goplus/xgo/ast/goptest" "github.com/qiniu/x/errors" ) func foo(script string) { doc := func() (_xgo_ret gopq.NodeSet) { var _xgo_err error _xgo_ret, _xgo_err = goptest.New(script) if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "goptest.New(script)", "/foo/bar.xgo", 4, "main.foo") panic(_xgo_err) } return }() fmt.Println(doc.Any().FuncDecl__0().Name()) fmt.Println(doc.Any().ImportSpec().Name()) } `) } func TestSimplifyAutoProperty(t *testing.T) { gopClTest(t, `import "gop/ast/goptest" func foo(script string) { doc := goptest.New(script)! println(doc.any.funcDecl.name) println(doc.any.importSpec.name) } `, `package main import ( "fmt" "github.com/goplus/xgo/ast/gopq" "github.com/goplus/xgo/ast/goptest" "github.com/qiniu/x/errors" ) func foo(script string) { doc := func() (_xgo_ret gopq.NodeSet) { var _xgo_err error _xgo_ret, _xgo_err = goptest.New(script) if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "goptest.New(script)", "/foo/bar.xgo", 4, "main.foo") panic(_xgo_err) } return }() fmt.Println(doc.Any().FuncDecl__0().Name()) fmt.Println(doc.Any().ImportSpec().Name()) } `) } func TestErrWrapBasic(t *testing.T) { gopClTest(t, ` import "strconv" func add(x, y string) (int, error) { return strconv.Atoi(x)? + strconv.Atoi(y)?, nil } `, `package main import ( "github.com/qiniu/x/errors" "strconv" ) func add(x string, y string) (int, error) { var _autoGo_1 int { var _xgo_err error _autoGo_1, _xgo_err = strconv.Atoi(x) if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "strconv.Atoi(x)", "/foo/bar.xgo", 5, "main.add") return 0, _xgo_err } goto _autoGo_2 _autoGo_2: } var _autoGo_3 int { var _xgo_err error _autoGo_3, _xgo_err = strconv.Atoi(y) if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "strconv.Atoi(y)", "/foo/bar.xgo", 5, "main.add") return 0, _xgo_err } goto _autoGo_4 _autoGo_4: } return _autoGo_1 + _autoGo_3, nil } `) } func TestErrWrapDefVal(t *testing.T) { gopClTest(t, ` import "strconv" func addSafe(x, y string) int { return strconv.Atoi(x)?:0 + strconv.Atoi(y)?:0 } `, `package main import "strconv" func addSafe(x string, y string) int { return func() (_xgo_ret int) { var _xgo_err error _xgo_ret, _xgo_err = strconv.Atoi(x) if _xgo_err != nil { return 0 } return }() + func() (_xgo_ret int) { var _xgo_err error _xgo_ret, _xgo_err = strconv.Atoi(y) if _xgo_err != nil { return 0 } return }() } `) } func TestErrWrapPanic(t *testing.T) { gopClTest(t, ` var ret int = println("Hi")! `, `package main import ( "fmt" "github.com/qiniu/x/errors" ) var ret int = func() (_xgo_ret int) { var _xgo_err error _xgo_ret, _xgo_err = fmt.Println("Hi") if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "println(\"Hi\")", "/foo/bar.xgo", 2, "main.main") panic(_xgo_err) } return }() `) } func TestErrWrapCommand(t *testing.T) { gopClTest(t, ` func mkdir(name string) error { return nil } mkdir! "foo" `, `package main import "github.com/qiniu/x/errors" func mkdir(name string) error { return nil } func main() { func() { var _xgo_err error _xgo_err = mkdir("foo") if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "mkdir \"foo\"", "/foo/bar.xgo", 6, "main.main") panic(_xgo_err) } return }() } `) } func TestErrWrapCall(t *testing.T) { gopClTest(t, ` func foo() (func(), error) { return nil, nil } foo()!() `, `package main import "github.com/qiniu/x/errors" func foo() (func(), error) { return nil, nil } func main() { func() (_xgo_ret func()) { var _xgo_err error _xgo_ret, _xgo_err = foo() if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "foo()", "/foo/bar.xgo", 6, "main.main") panic(_xgo_err) } return }()() } `) } func TestMakeAndNew(t *testing.T) { gopClTest(t, ` var a *int = new(int) var b map[string]int = make(map[string]int) var c []byte = make([]byte, 0, 2) `, `package main var a *int = new(int) var b map[string]int = make(map[string]int) var c []byte = make([]byte, 0, 2) `) } func TestVarDecl(t *testing.T) { gopClTest(t, ` var a int var x, y = 1, "Hi" `, `package main var a int var x, y = 1, "Hi" `) } func TestUint128Add(t *testing.T) { gopClTest(t, ` var x, y uint128 var z uint128 = x + y `, `package main import "github.com/qiniu/x/xgo/ng" var x, y ng.Uint128 var z ng.Uint128 = (ng.Uint128).XGo_Add__1(x, y) `) } func TestInt128Add(t *testing.T) { gopClTest(t, ` var x, y int128 var z int128 = x + y `, `package main import "github.com/qiniu/x/xgo/ng" var x, y ng.Int128 var z ng.Int128 = (ng.Int128).XGo_Add__1(x, y) `) } func TestBigIntAdd(t *testing.T) { gopClTest(t, ` var x, y bigint var z bigint = x + y `, `package main import "github.com/qiniu/x/xgo/ng" var x, y ng.Bigint var z ng.Bigint = (ng.Bigint).XGo_Add(x, y) `) } func TestBigIntLit(t *testing.T) { gopClTest(t, ` var x = 1r `, `package main import ( "github.com/qiniu/x/xgo/ng" "math/big" ) var x = ng.Bigint_Init__1(big.NewInt(1)) `) } func TestUint128Lit(t *testing.T) { gopClTest(t, ` var x uint128 = 1 `, `package main import "github.com/qiniu/x/xgo/ng" var x ng.Uint128 = ng.Uint128_Init__0(1) `) } func TestInt128Lit(t *testing.T) { gopClTest(t, ` var x int128 = 1 `, `package main import "github.com/qiniu/x/xgo/ng" var x ng.Int128 = ng.Int128_Init__0(1) `) } func TestBigRatLit(t *testing.T) { gopClTest(t, ` var x = 1/2r `, `package main import ( "github.com/qiniu/x/xgo/ng" "math/big" ) var x = ng.Bigrat_Init__2(big.NewRat(1, 2)) `) } func TestBigRatLitAdd(t *testing.T) { gopClTest(t, ` var x = 3 + 1/2r `, `package main import ( "github.com/qiniu/x/xgo/ng" "math/big" ) var x = ng.Bigrat_Init__2(big.NewRat(7, 2)) `) } func TestBigRatAdd(t *testing.T) { gogen.SetDebug(gogen.DbgFlagAll) gopClTest(t, ` var x = 3 + 1/2r var y = x + 100 var z = 100 + y `, `package main import ( "github.com/qiniu/x/xgo/ng" "math/big" ) var x = ng.Bigrat_Init__2(big.NewRat(7, 2)) var y = (ng.Bigrat).XGo_Add(x, ng.Bigrat_Init__0(100)) var z = (ng.Bigrat).XGo_Add(ng.Bigrat_Init__0(100), y) `) } func TestTypeConv(t *testing.T) { gopClTest(t, ` var a = (*struct{})(nil) var b = interface{}(nil) var c = (func())(nil) var x uint32 = uint32(0) var y *uint32 = (*uint32)(nil) `, `package main var a = (*struct { })(nil) var b = interface{}(nil) var c = (func())(nil) var x uint32 = uint32(0) var y *uint32 = (*uint32)(nil) `) } func TestStar(t *testing.T) { gopClTest(t, ` var x *uint32 = (*uint32)(nil) var y uint32 = *x `, `package main var x *uint32 = (*uint32)(nil) var y uint32 = *x `) } func TestLHS(t *testing.T) { gopClTest(t, ` type T struct { a int } func foo() *T { return nil } foo().a = 123 `, `package main type T struct { a int } func foo() *T { return nil } func main() { foo().a = 123 } `) } func TestSend(t *testing.T) { gopClTest(t, ` var x chan bool x <- true `, `package main var x chan bool func main() { x <- true } `) } func TestIncDec(t *testing.T) { gopClTest(t, ` var x uint32 x++ `, `package main var x uint32 func main() { x++ } `) } func TestAssignOp(t *testing.T) { gopClTest(t, ` var x uint32 x += 3 `, `package main var x uint32 func main() { x += 3 } `) } func TestBigIntAssignOp(t *testing.T) { gopClTest(t, ` var x bigint x += 3 `, `package main import "github.com/qiniu/x/xgo/ng" var x ng.Bigint func main() { x.XGo_AddAssign(ng.Bigint_Init__0(3)) } `) } func TestBigIntAssignOp2(t *testing.T) { gopClTest(t, ` x := 3r x *= 2 `, `package main import ( "github.com/qiniu/x/xgo/ng" "math/big" ) func main() { x := ng.Bigint_Init__1(big.NewInt(3)) x.XGo_MulAssign(ng.Bigint_Init__0(2)) } `) } func TestBigIntAssignOp3(t *testing.T) { gopClTest(t, ` x := 3r x *= 2r `, `package main import ( "github.com/qiniu/x/xgo/ng" "math/big" ) func main() { x := ng.Bigint_Init__1(big.NewInt(3)) x.XGo_MulAssign(ng.Bigint_Init__1(big.NewInt(2))) } `) } func TestCompositeLit(t *testing.T) { gopClTest(t, ` x := []float64{1, 3.4, 5} y := map[string]int{"Hello": 1, "XGo": 5} z := [...]int{1, 3, 5} a := {"Hello": 1, "XGo": 5.1} `, `package main func main() { x := []float64{1, 3.4, 5} y := map[string]int{"Hello": 1, "XGo": 5} z := [...]int{1, 3, 5} a := map[string]float64{"Hello": 1, "XGo": 5.1} } `) } func TestCompositeLit2(t *testing.T) { gopClTest(t, ` type foo struct { A int } x := []*struct{a int}{ {1}, {3}, {5}, } y := map[foo]struct{a string}{ {1}: {"Hi"}, } z := [...]foo{ {1}, {3}, {5}, } `, `package main type foo struct { A int } func main() { x := []*struct { a int }{&struct { a int }{1}, &struct { a int }{3}, &struct { a int }{5}} y := map[foo]struct { a string }{foo{1}: struct { a string }{"Hi"}} z := [...]foo{foo{1}, foo{3}, foo{5}} } `) } // deduce struct type as parameters of a function call func TestCompositeLit3(t *testing.T) { gopClTest(t, ` type Config struct { A int } func foo(conf *Config) { } func bar(conf ...Config) { } foo({A: 1}) bar({A: 2}) foo({}) bar({}) `, `package main type Config struct { A int } func foo(conf *Config) { } func bar(conf ...Config) { } func main() { foo(&Config{A: 1}) bar(Config{A: 2}) foo(&Config{}) bar(Config{}) } `) } // deduce struct type as results of a function call func TestCompositeLit4(t *testing.T) { gopClTest(t, ` type Result struct { A int } func foo() *Result { return {A: 1} } `, `package main type Result struct { A int } func foo() *Result { return &Result{A: 1} } `) } func TestCompositeLit5(t *testing.T) { gopClTest(t, ` type mymap map[float64]string var x = {1:"hello", 2:"world"} var y map[float64]string = {1:"hello", 2:"world"} var z mymap = {1:"hello", 2:"world"} `, `package main type mymap map[float64]string var x = map[int]string{1: "hello", 2: "world"} var y map[float64]string = map[float64]string{1: "hello", 2: "world"} var z mymap = mymap{1: "hello", 2: "world"} `) } func TestSliceLit(t *testing.T) { gopClTest(t, ` x := [1, 3.4, 5] y := [1] z := [] `, `package main func main() { x := []float64{1, 3.4, 5} y := []int{1} z := []interface{}{} } `) gopClTest(t, ` type vector []float64 var x = [1, 2, 3] var y []float64 = [1, 2, 3] var z vector = [1, 2, 3] `, `package main type vector []float64 var x = []int{1, 2, 3} var y []float64 = []float64{1, 2, 3} var z vector = vector{1, 2, 3} `) } func TestChan(t *testing.T) { gopClTest(t, ` a := make(chan int, 10) a <- 3 var b int = <-a x, ok := <-a `, `package main func main() { a := make(chan int, 10) a <- 3 var b int = <-a x, ok := <-a } `) } func TestKeyValModeLit(t *testing.T) { gopClTest(t, ` a := [...]float64{1, 3: 3.4, 5} b := []float64{2: 1.2, 3, 6: 4.5} `, `package main func main() { a := [...]float64{1, 3: 3.4, 5} b := []float64{2: 1.2, 3, 6: 4.5} } `) } func TestStructLit(t *testing.T) { gopClTest(t, ` type foo struct { A int B string "tag1:123" } a := struct { A int B string "tag1:123" }{1, "Hello"} b := foo{1, "Hello"} c := foo{B: "Hi"} `, `package main type foo struct { A int B string `+"`tag1:123`"+` } func main() { a := struct { A int B string `+"`tag1:123`"+` }{1, "Hello"} b := foo{1, "Hello"} c := foo{B: "Hi"} } `) } func TestStructType(t *testing.T) { gopClTest(t, ` type bar = foo type foo struct { p *bar A int B string "tag1:123" } func main() { type a struct { p *a } type b = a } `, `package main type bar = foo type foo struct { p *bar A int B string `+"`tag1:123`"+` } func main() { type a struct { p *a } type b = a } `) } func TestDeferGo(t *testing.T) { gopClTest(t, ` go println("Hi") defer println("XGo") `, `package main import "fmt" func main() { go fmt.Println("Hi") defer fmt.Println("XGo") } `) } func TestFor(t *testing.T) { gopClTest(t, ` a := [1, 3.4, 5] for i := 0; i < 3; i=i+1 { println(i) } for { println("loop") } `, `package main import "fmt" func main() { a := []float64{1, 3.4, 5} for i := 0; i < 3; i = i + 1 { fmt.Println(i) } for { fmt.Println("loop") } } `) } func TestRangeStmt(t *testing.T) { gopClTest(t, ` a := [1, 3.4, 5] for _, x := range a { println(x) } for i, x := range a { println(i, x) } var i int var x float64 for _, x = range a { println(i, x) } for i, x = range a { println(i, x) } for range a { println("Hi") } `, `package main import "fmt" func main() { a := []float64{1, 3.4, 5} for _, x := range a { fmt.Println(x) } for i, x := range a { fmt.Println(i, x) } var i int var x float64 for _, x = range a { fmt.Println(i, x) } for i, x = range a { fmt.Println(i, x) } for range a { fmt.Println("Hi") } } `) } func TestRangeStmtUDT(t *testing.T) { gopClTest(t, ` type foo struct { } func (p *foo) Gop_Enum(c func(key int, val string)) { } for k, v := range new(foo) { println(k, v) } `, `package main import "fmt" type foo struct { } func (p *foo) Gop_Enum(c func(key int, val string)) { } func main() { new(foo).Gop_Enum(func(k int, v string) { fmt.Println(k, v) }) } `) } func TestForPhraseUDT(t *testing.T) { gopClTest(t, ` type foo struct { } func (p *foo) Gop_Enum(c func(val string)) { } for v <- new(foo) { println(v) } `, `package main import "fmt" type foo struct { } func (p *foo) Gop_Enum(c func(val string)) { } func main() { new(foo).Gop_Enum(func(v string) { fmt.Println(v) }) } `) } func TestForPhraseUDT2(t *testing.T) { gopClTest(t, ` type fooIter struct { } func (p fooIter) Next() (key string, val int, ok bool) { return } type foo struct { } func (p *foo) Gop_Enum() fooIter { return fooIter{} } for k, v <- new(foo) { println(k, v) } `, `package main import "fmt" type fooIter struct { } type foo struct { } func (p fooIter) Next() (key string, val int, ok bool) { return } func (p *foo) Gop_Enum() fooIter { return fooIter{} } func main() { for _xgo_it := new(foo).Gop_Enum(); ; { var _xgo_ok bool k, v, _xgo_ok := _xgo_it.Next() if !_xgo_ok { break } fmt.Println(k, v) } } `) } func TestForPhraseUDT3(t *testing.T) { gopClTest(t, ` type foo struct { } func (p *foo) Gop_Enum(c func(val string)) { } println([v for v <- new(foo)]) `, `package main import "fmt" type foo struct { } func (p *foo) Gop_Enum(c func(val string)) { } func main() { fmt.Println(func() (_xgo_ret []string) { new(foo).Gop_Enum(func(v string) { _xgo_ret = append(_xgo_ret, v) }) return }()) } `) } func TestForPhraseUDT4(t *testing.T) { gopClTest(t, ` type fooIter struct { data *foo idx int } func (p *fooIter) Next() (key int, val string, ok bool) { if p.idx < len(p.data.key) { key, val, ok = p.data.key[p.idx], p.data.val[p.idx], true p.idx++ } return } type foo struct { key []int val []string } func newFoo() *foo { return &foo{key: [3, 7], val: ["Hi", "XGo"]} } func (p *foo) Gop_Enum() *fooIter { return &fooIter{data: p} } for k, v <- newFoo() { println(k, v) } `, `package main import "fmt" type fooIter struct { data *foo idx int } type foo struct { key []int val []string } func (p *fooIter) Next() (key int, val string, ok bool) { if p.idx < len(p.data.key) { key, val, ok = p.data.key[p.idx], p.data.val[p.idx], true p.idx++ } return } func (p *foo) Gop_Enum() *fooIter { return &fooIter{data: p} } func newFoo() *foo { return &foo{key: []int{3, 7}, val: []string{"Hi", "XGo"}} } func main() { for _xgo_it := newFoo().Gop_Enum(); ; { var _xgo_ok bool k, v, _xgo_ok := _xgo_it.Next() if !_xgo_ok { break } fmt.Println(k, v) } } `) } func TestForPhrase(t *testing.T) { gopClTest(t, ` sum := 0 for x <- [1, 3, 5, 7, 11, 13, 17] if x > 3 { sum = sum + x } for i, x <- [1, 3, 5, 7, 11, 13, 17] { sum = sum + i*x } println("sum(5,7,11,13,17):", sum) `, `package main import "fmt" func main() { sum := 0 for _, x := range []int{1, 3, 5, 7, 11, 13, 17} { if x > 3 { sum = sum + x } } for i, x := range []int{1, 3, 5, 7, 11, 13, 17} { sum = sum + i*x } fmt.Println("sum(5,7,11,13,17):", sum) } `) } func TestExistsComprehension(t *testing.T) { gopClTest(t, ` hasFive := {for x <- ["1", "3", "5", "7", "11"] if x == "5"} `, `package main func main() { hasFive := func() (_xgo_ok bool) { for _, x := range []string{"1", "3", "5", "7", "11"} { if x == "5" { return true } } return }() } `) } func TestSliceGet(t *testing.T) { gopClTest(t, ` a := [1, 3, 5, 7, 9] b := a[:3] c := a[1:] d := a[1:2:3] e := "Hello, XGo"[7:] `, `package main func main() { a := []int{1, 3, 5, 7, 9} b := a[:3] c := a[1:] d := a[1:2:3] e := "Hello, XGo"[7:] } `) } func TestIndexGetTwoValue(t *testing.T) { gopClTest(t, ` a := {"Hello": 1, "Hi": 3, "xsw": 5, "XGo": 7} x, ok := a["Hi"] y := a["XGo"] `, `package main func main() { a := map[string]int{"Hello": 1, "Hi": 3, "xsw": 5, "XGo": 7} x, ok := a["Hi"] y := a["XGo"] } `) } func TestIndexGet(t *testing.T) { gopClTest(t, ` a := [1, 3.4, 5] b := a[1] `, `package main func main() { a := []float64{1, 3.4, 5} b := a[1] } `) } func TestIndexRef(t *testing.T) { gopClTest(t, ` a := [1, 3.4, 5] a[1] = 2.1 `, `package main func main() { a := []float64{1, 3.4, 5} a[1] = 2.1 } `) } func TestIndexArrayPtrIssue784(t *testing.T) { gopClTest(t, ` type intArr [2]int func foo(a *intArr) { a[1] = 10 } `, `package main type intArr [2]int func foo(a *intArr) { a[1] = 10 } `) } func TestMemberVal(t *testing.T) { gopClTest(t, `import "strings" x := strings.NewReplacer("?", "!").Replace("hello, world???") println("x:", x) `, `package main import ( "fmt" "strings" ) func main() { x := strings.NewReplacer("?", "!").Replace("hello, world???") fmt.Println("x:", x) } `) } func TestNamedPtrMemberIssue786(t *testing.T) { gopClTest(t, ` type foo struct { req int } type pfoo *foo func bar(p pfoo) { println(p.req) } `, `package main import "fmt" type foo struct { req int } type pfoo *foo func bar(p pfoo) { fmt.Println(p.req) } `) } func TestMember(t *testing.T) { gopClTest(t, ` import "flag" a := &struct { A int B string }{1, "Hello"} x := a.A a.B = "Hi" flag.Usage = nil `, `package main import "flag" func main() { a := &struct { A int B string }{1, "Hello"} x := a.A a.B = "Hi" flag.Usage = nil } `) } func TestElem(t *testing.T) { gopClTest(t, ` func foo(a *int, b int) { b = *a *a = b } `, `package main func foo(a *int, b int) { b = *a *a = b } `) } func TestNamedPtrIssue797(t *testing.T) { gopClTest(t, ` type Bar *int func foo(a Bar) { var b int = *a } `, `package main type Bar *int func foo(a Bar) { var b int = *a } `) } func TestMethod(t *testing.T) { gopClTest(t, ` type M int func (m M) Foo() { println("foo", m) } func (M) Bar() { println("bar") } `, `package main import "fmt" type M int func (m M) Foo() { fmt.Println("foo", m) } func (M) Bar() { fmt.Println("bar") } `) } func TestCmdlineNoEOL(t *testing.T) { gopClTest(t, `println "Hi"`, `package main import "fmt" func main() { fmt.Println("Hi") } `) } func TestImport(t *testing.T) { gopClTest(t, `import "fmt" func main() { fmt.println "Hi" }`, `package main import "fmt" func main() { fmt.Println("Hi") } `) } func TestDotImport(t *testing.T) { gopClTest(t, `import . "math" var a = round(1.2) `, `package main import "math" var a = math.Round(1.2) `) } func TestLocalImport(t *testing.T) { gopClTest(t, `import "./internal/spx" var a = spx.TestIntValue `, `package main import "github.com/goplus/xgo/cl/internal/spx" var a = spx.TestIntValue `) } func TestImportUnused(t *testing.T) { gopClTest(t, `import "fmt" func main() { }`, `package main func main() { } `) } func TestImportForceUsed(t *testing.T) { gopClTest(t, `import _ "fmt" func main() { }`, `package main import _ "fmt" func main() { } `) } func TestAnonymousImport(t *testing.T) { gopClTest(t, `println("Hello") printf("Hello XGo\n") `, `package main import "fmt" func main() { fmt.Println("Hello") fmt.Printf("Hello XGo\n") } `) } func TestVarAndConst(t *testing.T) { gopClTest(t, ` const ( i = 1 x float64 = 1 ) var j int = i `, `package main const ( i = 1 x float64 = 1 ) var j int = i `) } func TestDeclStmt(t *testing.T) { gopClTest(t, `import "fmt" func main() { const ( i = 1 x float64 = 1 ) var j int = i fmt.Println("Hi") }`, `package main import "fmt" func main() { const ( i = 1 x float64 = 1 ) var j int = i fmt.Println("Hi") } `) } func TestIf(t *testing.T) { gopClTest(t, `x := 0 if t := false; t { x = 3 } else if !t { x = 5 } else { x = 7 } println("x:", x) `, `package main import "fmt" func main() { x := 0 if t := false; t { x = 3 } else if !t { x = 5 } else { x = 7 } fmt.Println("x:", x) } `) } func TestSwitch(t *testing.T) { gopClTest(t, `x := 0 switch s := "Hello"; s { default: x = 7 case "world", "hi": x = 5 case "xsw": x = 3 } println("x:", x) v := "Hello" switch { case v == "xsw": x = 3 case v == "hi", v == "world": x = 9 default: x = 11 } println("x:", x) `, `package main import "fmt" func main() { x := 0 switch s := "Hello"; s { default: x = 7 case "world", "hi": x = 5 case "xsw": x = 3 } fmt.Println("x:", x) v := "Hello" switch { case v == "xsw": x = 3 case v == "hi", v == "world": x = 9 default: x = 11 } fmt.Println("x:", x) } `) } func TestSwitchFallthrough(t *testing.T) { gopClTest(t, `v := "Hello" switch v { case "Hello": println(v) fallthrough case "hi": println(v) fallthrough default: println(v) } `, `package main import "fmt" func main() { v := "Hello" switch v { case "Hello": fmt.Println(v) fallthrough case "hi": fmt.Println(v) fallthrough default: fmt.Println(v) } } `) } func TestBranchStmt(t *testing.T) { gopClTest(t, ` a := [1, 3.4, 5] label: for i := 0; i < 3; i=i+1 { println(i) break break label continue continue label goto label } `, `package main import "fmt" func main() { a := []float64{1, 3.4, 5} label: for i := 0; i < 3; i = i + 1 { fmt.Println(i) break break label continue continue label goto label } } `) } func TestReturn(t *testing.T) { gopClTest(t, ` func foo(format string, args ...interface{}) (int, error) { return printf(format, args...) } func main() { } `, `package main import "fmt" func foo(format string, args ...interface{}) (int, error) { return fmt.Printf(format, args...) } func main() { } `) } func TestReturnExpr(t *testing.T) { gopClTest(t, ` func foo(format string, args ...interface{}) (int, error) { return 0, nil } func main() { } `, `package main func foo(format string, args ...interface{}) (int, error) { return 0, nil } func main() { } `) } func TestClosure(t *testing.T) { gopClTest(t, `import "fmt" func(v string) { fmt.Println(v) }("Hello") `, `package main import "fmt" func main() { func(v string) { fmt.Println(v) }("Hello") } `) } func TestFunc(t *testing.T) { gopClTest(t, `func foo(format string, a [10]int, args ...interface{}) { } func main() { }`, `package main func foo(format string, a [10]int, args ...interface{}) { } func main() { } `) } func TestLambdaExpr(t *testing.T) { gopClTest(t, ` func Map(c []float64, t func(float64) float64) { // ... } func Map2(c []float64, t func(float64) (float64, float64)) { // ... } Map([1.2, 3.5, 6], x => x * x) Map2([1.2, 3.5, 6], x => (x * x, x + x)) `, `package main func Map(c []float64, t func(float64) float64) { } func Map2(c []float64, t func(float64) (float64, float64)) { } func main() { Map([]float64{1.2, 3.5, 6}, func(x float64) float64 { return x * x }) Map2([]float64{1.2, 3.5, 6}, func(x float64) (float64, float64) { return x * x, x + x }) } `) gopClTest(t, `type Foo struct { Plot func(x float64) (float64, float64) } foo := &Foo{ Plot: x => (x * 2, x * x), }`, `package main type Foo struct { Plot func(x float64) (float64, float64) } func main() { foo := &Foo{Plot: func(x float64) (float64, float64) { return x * 2, x * x }} } `) gopClTest(t, ` type Fn func(x float64) (float64, float64) type Foo struct { Plot Fn } foo := &Foo{ Plot: x => (x * 2, x * x), }`, `package main type Fn func(x float64) (float64, float64) type Foo struct { Plot Fn } func main() { foo := &Foo{Plot: func(x float64) (float64, float64) { return x * 2, x * x }} } `) gopClTest(t, ` type Fn func() (int, error) func Do(fn Fn) { } Do => (100, nil) `, `package main type Fn func() (int, error) func Do(fn Fn) { } func main() { Do(func() (int, error) { return 100, nil }) } `) gopClTest(t, ` var fn func(int) (int,error) = x => (x*x, nil) `, `package main var fn func(int) (int, error) = func(x int) (int, error) { return x * x, nil } `) gopClTest(t, ` var fn func(int) (int,error) fn = x => (x*x, nil) `, `package main var fn func(int) (int, error) func main() { fn = func(x int) (int, error) { return x * x, nil } } `) } func TestLambdaExpr2(t *testing.T) { gopClTest(t, ` func Do(func()) { // ... } Do => { println "Hi" } `, `package main import "fmt" func Do(func()) { } func main() { Do(func() { fmt.Println("Hi") }) } `) gopClTest(t, ` func Do(fn func() (int, error)) { // ... } Do => { return 100, nil } `, `package main func Do(fn func() (int, error)) { } func main() { Do(func() (int, error) { return 100, nil }) } `) gopClTest(t, `type Foo struct { Plot func(x float64) (float64, float64) } foo := &Foo{ Plot: x => { return x * 2, x * x }, }`, `package main type Foo struct { Plot func(x float64) (float64, float64) } func main() { foo := &Foo{Plot: func(x float64) (float64, float64) { return x * 2, x * x }} } `) gopClTest(t, ` type Fn func(x float64) (float64, float64) type Foo struct { Plot Fn } foo := &Foo{ Plot: x => { return x * 2, x * x }, }`, `package main type Fn func(x float64) (float64, float64) type Foo struct { Plot Fn } func main() { foo := &Foo{Plot: func(x float64) (float64, float64) { return x * 2, x * x }} } `) gopClTest(t, ` type Fn func() (int, error) func Do(fn Fn) { } Do => { return 100, nil } `, `package main type Fn func() (int, error) func Do(fn Fn) { } func main() { Do(func() (int, error) { return 100, nil }) } `) gopClTest(t, ` var fn func(int) (int,error) = x => { return x * x, nil } `, `package main var fn func(int) (int, error) = func(x int) (int, error) { return x * x, nil } `) gopClTest(t, ` var fn func(int) (int,error) fn = x => { return x * x, nil } `, `package main var fn func(int) (int, error) func main() { fn = func(x int) (int, error) { return x * x, nil } } `) } func TestLambdaExpr3(t *testing.T) { gopClTest(t, ` func intSeq() func() int { i := 0 return => { i++ return i } } `, `package main func intSeq() func() int { i := 0 return func() int { i++ return i } } `) gopClTest(t, ` func intDouble() func(int) int { return i => i*2 } `, `package main func intDouble() func(int) int { return func(i int) int { return i * 2 } } `) } func TestUnnamedMainFunc(t *testing.T) { gopClTest(t, `i := 1`, `package main func main() { i := 1 } `) } func TestFuncAsParam(t *testing.T) { gopClTest(t, `import "fmt" func bar(foo func(string, ...interface{}) (int, error)) { foo("Hello, %v!\n", "XGo") } bar(fmt.Printf) `, `package main import "fmt" func bar(foo func(string, ...interface{}) (int, error)) { foo("Hello, %v!\n", "XGo") } func main() { bar(fmt.Printf) } `) } func TestFuncAsParam2(t *testing.T) { gopClTest(t, `import ( "fmt" "strings" ) func foo(x string) string { return strings.NewReplacer("?", "!").Replace(x) } func printf(format string, args ...interface{}) (n int, err error) { n, err = fmt.Printf(format, args...) return } func bar(foo func(string, ...interface{}) (int, error)) { foo("Hello, %v!\n", "XGo") } bar(printf) fmt.Println(foo("Hello, world???")) fmt.Println(printf("Hello, %v\n", "XGo")) `, `package main import ( "fmt" "strings" ) func foo(x string) string { return strings.NewReplacer("?", "!").Replace(x) } func printf(format string, args ...interface{}) (n int, err error) { n, err = fmt.Printf(format, args...) return } func bar(foo func(string, ...interface{}) (int, error)) { foo("Hello, %v!\n", "XGo") } func main() { bar(printf) fmt.Println(foo("Hello, world???")) fmt.Println(printf("Hello, %v\n", "XGo")) } `) } func TestFuncCall(t *testing.T) { gopClTest(t, `import "fmt" fmt.Println("Hello")`, `package main import "fmt" func main() { fmt.Println("Hello") } `) } func TestFuncCallEllipsis(t *testing.T) { gopClTest(t, `import "fmt" func foo(args ...interface{}) { fmt.Println(args...) } func main() { }`, `package main import "fmt" func foo(args ...interface{}) { fmt.Println(args...) } func main() { } `) } func TestFuncCallCodeOrder(t *testing.T) { gopClTest(t, `import "fmt" func main() { foo("Hello", 123) } func foo(args ...interface{}) { fmt.Println(args...) } `, `package main import "fmt" func main() { foo("Hello", 123) } func foo(args ...interface{}) { fmt.Println(args...) } `) } func TestInterfaceMethods(t *testing.T) { gopClTest(t, `package main func foo(v ...interface { Bar() }) { } func main() { }`, `package main func foo(v ...interface { Bar() }) { } func main() { } `) } func TestAssignUnderscore(t *testing.T) { gopClTest(t, `import log "fmt" _, err := log.Println("Hello") `, `package main import "fmt" func main() { _, err := fmt.Println("Hello") } `) } func TestOperator(t *testing.T) { gopClTest(t, ` a := "Hi" b := a + "!" c := 13 d := -c `, `package main func main() { a := "Hi" b := a + "!" c := 13 d := -c } `) } var ( autogen sync.Mutex ) func removeAutogenFiles() { os.Remove("./internal/gop-in-go/foo/gop_autogen.go") os.Remove("./internal/gop-in-go/foo/gop_autogen_test.go") os.Remove("./internal/gop-in-go/foo/gop_autogen2_test.go") } func TestImportGopPkg(t *testing.T) { autogen.Lock() defer autogen.Unlock() removeAutogenFiles() gopClTest(t, `import "github.com/goplus/xgo/cl/internal/gop-in-go/foo" rmap := foo.ReverseMap(map[string]int{"Hi": 1, "Hello": 2}) println(rmap) `, `package main import ( "fmt" "github.com/goplus/xgo/cl/internal/gop-in-go/foo" ) func main() { rmap := foo.ReverseMap(map[string]int{"Hi": 1, "Hello": 2}) fmt.Println(rmap) } `) } func TestCallDep(t *testing.T) { for i := 0; i < 2; i++ { gopClTest(t, ` import ( "reflect" "testing" ) func TestNew(t *testing.T) { ret := New() expected := Result{} if reflect.DeepEqual(ret, expected) { t.Fatal("Test failed:", ret, expected) } } type Repo struct { Title string } func newRepo() Repo { return {Title: "Hi"} } type Result struct { Repo Repo } func New() Result { repo := newRepo() return {Repo: repo} } `, `package main import ( "reflect" "testing" ) type Repo struct { Title string } type Result struct { Repo Repo } func TestNew(t *testing.T) { ret := New() expected := Result{} if reflect.DeepEqual(ret, expected) { t.Fatal("Test failed:", ret, expected) } } func New() Result { repo := newRepo() return Result{Repo: repo} } func newRepo() Repo { return Repo{Title: "Hi"} } `) } } func TestGoFuncInstr(t *testing.T) { gopClTest(t, `package main //go:noinline //go:uintptrescapes func test(s string, p, q uintptr, rest ...uintptr) int { return 0 }`, `package main //go:noinline //go:uintptrescapes func test(s string, p uintptr, q uintptr, rest ...uintptr) int { return 0 } `) } func TestGoTypeInstr(t *testing.T) { gopClTest(t, `package main //go:notinheap type S struct{ x int } `, `package main //go:notinheap type S struct { x int } `) } func TestNoEntrypoint(t *testing.T) { gopClTest(t, `println("init") `, `package main import "fmt" func main() { fmt.Println("init") } `) gopClTestEx(t, cltest.Conf, "bar", `package bar println("init") `, `package bar import "fmt" func init() { fmt.Println("init") } `) } func TestParentExpr(t *testing.T) { gopClTest(t, `var t1 *(int) var t2 chan (int) `, `package main var t1 *int var t2 chan int `) } func TestCommandStyle(t *testing.T) { gopClTest(t, ` println [] println {} `, `package main import "fmt" func main() { fmt.Println([]interface{}{}) fmt.Println(map[string]interface{}{}) } `) } func TestTypeLoader(t *testing.T) { gopClTest(t, `import "fmt" func (p *Point) String() string { return fmt.Sprintf("%v-%v",p.X,p.Y) } type Point struct { X int Y int } `, `package main import "fmt" type Point struct { X int Y int } func (p *Point) String() string { return fmt.Sprintf("%v-%v", p.X, p.Y) } `) } func TestCallPrintln(t *testing.T) { gopClTest(t, ` print print "hello" print("hello") println println "hello" println("hello") `, `package main import "fmt" func main() { fmt.Print() fmt.Print("hello") fmt.Print("hello") fmt.Println() fmt.Println("hello") fmt.Println("hello") } `) } func TestAnyAlias(t *testing.T) { gopClTest(t, ` var a any = 100 println(a) `, `package main import "fmt" var a interface{} = 100 func main() { fmt.Println(a) } `) } func TestMainEntry(t *testing.T) { conf := *cltest.Conf conf.NoAutoGenMain = false gopClTestEx(t, &conf, "main", ` `, `package main func main() { } `) gopClTestEx(t, &conf, "main", ` func test() { println "hello" } `, `package main import "fmt" func test() { fmt.Println("hello") } func main() { } `) gopClTestEx(t, &conf, "main", ` func main() { println "hello" } `, `package main import "fmt" func main() { fmt.Println("hello") } `) } func TestCommandNotExpr(t *testing.T) { gopClTest(t, ` println !true `, `package main import "fmt" func main() { fmt.Println(false) } `) gopClTest(t, ` a := true println !a `, `package main import "fmt" func main() { a := true fmt.Println(!a) } `) gopClTest(t, ` println !func() bool { return true }() `, `package main import "fmt" func main() { fmt.Println(!func() bool { return true }()) } `) } func TestCommentLine(t *testing.T) { gopClTestEx(t, gblConfLine, "main", ` type Point struct { x int y int } func (pt *Point) Test() { println(pt.x, pt.y) } // testPoint is test point func testPoint() { var pt Point pt.Test() } println "hello" testPoint() `, `package main import "fmt" type Point struct { x int y int } //line /foo/bar.xgo:7:1 func (pt *Point) Test() { //line /foo/bar.xgo:8:1 fmt.Println(pt.x, pt.y) } //line /foo/bar.xgo:11:1 // testPoint is test point func testPoint() { //line /foo/bar.xgo:13:1 var pt Point //line /foo/bar.xgo:14:1 pt.Test() } //line /foo/bar.xgo:17 func main() { //line /foo/bar.xgo:17:1 fmt.Println("hello") //line /foo/bar.xgo:18:1 testPoint() } `) } func TestCommentLineRoot(t *testing.T) { conf := *cltest.Conf conf.NoFileLine = false conf.RelativeBase = "/foo/root" var src = ` type Point struct { x int y int } func (pt *Point) Test() { println(pt.x, pt.y) } // testPoint is test point func testPoint() { var pt Point pt.Test() } println "hello" testPoint() ` var expected = `package main import "fmt" type Point struct { x int y int } //line ../bar.xgo:7:1 func (pt *Point) Test() { //line ../bar.xgo:8:1 fmt.Println(pt.x, pt.y) } //line ../bar.xgo:11:1 // testPoint is test point func testPoint() { //line ../bar.xgo:13:1 var pt Point //line ../bar.xgo:14:1 pt.Test() } //line ../bar.xgo:17 func main() { //line ../bar.xgo:17:1 fmt.Println("hello") //line ../bar.xgo:18:1 testPoint() } ` gopClTestEx(t, &conf, "main", src, expected) } func TestRangeScope(t *testing.T) { gopClTest(t, ` ar := []int{100, 200} for k, v := range ar { println(k, v, ar) var k, v, ar int println(ar, k, v) } `, `package main import "fmt" func main() { ar := []int{100, 200} for k, v := range ar { fmt.Println(k, v, ar) var k, v, ar int fmt.Println(ar, k, v) } } `) } func TestSelectScope(t *testing.T) { gopClTest(t, ` c1 := make(chan int) c2 := make(chan int) go func() { c1 <- 100 }() select { case i := <-c1: println i case i := <-c2: println i } `, `package main import "fmt" func main() { c1 := make(chan int) c2 := make(chan int) go func() { c1 <- 100 }() select { case i := <-c1: fmt.Println(i) case i := <-c2: fmt.Println(i) } } `) } func TestCommentVar(t *testing.T) { gopClTestEx(t, gblConfLine, "main", ` // doc a line2 var a int println a // doc b line6 var b int println b var c int println c `, `package main import "fmt" // doc a line2 var a int //line /foo/bar.xgo:4 func main() { //line /foo/bar.xgo:4:1 fmt.Println(a) //line /foo/bar.xgo:6:1 // doc b line6 var b int //line /foo/bar.xgo:8:1 fmt.Println(b) //line /foo/bar.xgo:10:1 var c int //line /foo/bar.xgo:11:1 fmt.Println(c) } `) gopClTestEx(t, gblConfLine, "main", ` func demo() { // doc a line3 var a int println a // doc b line7 var b int println b var c int println c } `, `package main import "fmt" //line /foo/bar.xgo:2:1 func demo() { //line /foo/bar.xgo:3:1 // doc a line3 var a int //line /foo/bar.xgo:5:1 fmt.Println(a) //line /foo/bar.xgo:7:1 // doc b line7 var b int //line /foo/bar.xgo:9:1 fmt.Println(b) //line /foo/bar.xgo:11:1 var c int //line /foo/bar.xgo:12:1 fmt.Println(c) } `) } func TestForPhraseScope(t *testing.T) { gopClTest(t, `sum := 0 for x <- [1, 3, 5, 7, 11, 13, 17] { sum = sum + x println x x := 200 println x }`, `package main import "fmt" func main() { sum := 0 for _, x := range []int{1, 3, 5, 7, 11, 13, 17} { sum = sum + x fmt.Println(x) x := 200 fmt.Println(x) } } `) gopClTest(t, `sum := 0 for x <- [1, 3, 5, 7, 11, 13, 17] if x > 3 { sum = sum + x println x x := 200 println x }`, `package main import "fmt" func main() { sum := 0 for _, x := range []int{1, 3, 5, 7, 11, 13, 17} { if x > 3 { sum = sum + x fmt.Println(x) x := 200 fmt.Println(x) } } } `) } func TestAddress(t *testing.T) { gopClTest(t, ` type foo struct{ c int } func (f foo) ptr() *foo { return &f } func (f foo) clone() foo { return f } type nested struct { f foo a [2]foo s []foo } func _() { getNested := func() nested { return nested{} } _ = getNested().f.c _ = getNested().a[0].c _ = getNested().s[0].c _ = getNested().f.ptr().c _ = getNested().f.clone().c _ = getNested().f.clone().ptr().c } `, `package main type foo struct { c int } type nested struct { f foo a [2]foo s []foo } func (f foo) ptr() *foo { return &f } func (f foo) clone() foo { return f } func _() { getNested := func() nested { return nested{} } _ = getNested().f.c _ = getNested().a[0].c _ = getNested().s[0].c _ = getNested().f.ptr().c _ = getNested().f.clone().c _ = getNested().f.clone().ptr().c } `) } func TestSliceLitAssign(t *testing.T) { gopClTest(t, ` var n = 1 var a []any = [10, 3.14, 200] n, a = 100, [10, 3.14, 200] echo a, n `, `package main import "fmt" var n = 1 var a []interface{} = []interface{}{10, 3.14, 200} func main() { n, a = 100, []interface{}{10, 3.14, 200} fmt.Println(a, n) } `) } func TestSliceLitReturn(t *testing.T) { gopClTest(t, ` func anyslice() (int, []any) { return 100, [10, 3.14, 200] } n, a := anyslice() echo n, a `, `package main import "fmt" func anyslice() (int, []interface{}) { return 100, []interface{}{10, 3.14, 200} } func main() { n, a := anyslice() fmt.Println(n, a) } `) } func TestCompositeLitAssign(t *testing.T) { gopClTest(t, ` var a map[any]any = {10: "A", 3.14: "B", 200: "C"} var b map[any]string = {10: "A", 3.14: "B", 200: "C"} echo a echo b var n int n, a = 1, {10: "A", 3.14: "B", 200: "C"} echo a, n n, b = 1, {10: "A", 3.14: "B", 200: "C"} echo b, n `, `package main import "fmt" var a map[interface{}]interface{} = map[interface{}]interface{}{10: "A", 3.14: "B", 200: "C"} var b map[interface{}]string = map[interface{}]string{10: "A", 3.14: "B", 200: "C"} func main() { fmt.Println(a) fmt.Println(b) var n int n, a = 1, map[interface{}]interface{}{10: "A", 3.14: "B", 200: "C"} fmt.Println(a, n) n, b = 1, map[interface{}]string{10: "A", 3.14: "B", 200: "C"} fmt.Println(b, n) } `) } func TestCompositeLitStruct(t *testing.T) { gopClTest(t, ` type T struct { s []any m map[any]any fn func(int) int } echo &T{[10, 3.14, 200], {10: "A", 3.14: "B", 200: "C"}, (x => x)} echo &T{s: [10, 3.14, 200], m: {10: "A", 3.14: "B", 200: "C"}, fn: (x => x)} `, `package main import "fmt" type T struct { s []interface{} m map[interface{}]interface{} fn func(int) int } func main() { fmt.Println(&T{[]interface{}{10, 3.14, 200}, map[interface{}]interface{}{10: "A", 3.14: "B", 200: "C"}, func(x int) int { return x }}) fmt.Println(&T{s: []interface{}{10, 3.14, 200}, m: map[interface{}]interface{}{10: "A", 3.14: "B", 200: "C"}, fn: func(x int) int { return x }}) } `) } func TestCompositeLitEx(t *testing.T) { gopClTest(t, ` var a [][]any = {[10, 3.14, 200], [100, 200]} var m map[any][]any = {10: [10, 3.14, 200]} var f map[any]func(int) int = {10: x => x} echo a echo m echo f `, `package main import "fmt" var a [][]interface{} = [][]interface{}{[]interface{}{10, 3.14, 200}, []interface{}{100, 200}} var m map[interface{}][]interface{} = map[interface{}][]interface{}{10: []interface{}{10, 3.14, 200}} var f map[interface{}]func(int) int = map[interface{}]func(int) int{10: func(x int) int { return x }} func main() { fmt.Println(a) fmt.Println(m) fmt.Println(f) } `) } func TestCommentFunc(t *testing.T) { gopClTestEx(t, gblConfLine, "main", ` import ( "strconv" ) func add(x, y string) (int, error) { return strconv.atoi(x)? + strconv.atoi(y)?, nil } func addSafe(x, y string) int { return strconv.atoi(x)?:0 + strconv.atoi(y)?:0 } echo add("100", "23")! sum, err := add("10", "abc") echo sum, err echo addSafe("10", "abc") `, `package main import ( "fmt" "github.com/qiniu/x/errors" "strconv" ) //line /foo/bar.xgo:6:1 func add(x string, y string) (int, error) { //line /foo/bar.xgo:7:1 var _autoGo_1 int //line /foo/bar.xgo:7:1 { //line /foo/bar.xgo:7:1 var _xgo_err error //line /foo/bar.xgo:7:1 _autoGo_1, _xgo_err = strconv.Atoi(x) //line /foo/bar.xgo:7:1 if _xgo_err != nil { //line /foo/bar.xgo:7:1 _xgo_err = errors.NewFrame(_xgo_err, "strconv.atoi(x)", "/foo/bar.xgo", 7, "main.add") //line /foo/bar.xgo:7:1 return 0, _xgo_err } //line /foo/bar.xgo:7:1 goto _autoGo_2 _autoGo_2: //line /foo/bar.xgo:7:1 } //line /foo/bar.xgo:7:1 var _autoGo_3 int //line /foo/bar.xgo:7:1 { //line /foo/bar.xgo:7:1 var _xgo_err error //line /foo/bar.xgo:7:1 _autoGo_3, _xgo_err = strconv.Atoi(y) //line /foo/bar.xgo:7:1 if _xgo_err != nil { //line /foo/bar.xgo:7:1 _xgo_err = errors.NewFrame(_xgo_err, "strconv.atoi(y)", "/foo/bar.xgo", 7, "main.add") //line /foo/bar.xgo:7:1 return 0, _xgo_err } //line /foo/bar.xgo:7:1 goto _autoGo_4 _autoGo_4: //line /foo/bar.xgo:7:1 } //line /foo/bar.xgo:7:1 return _autoGo_1 + _autoGo_3, nil } //line /foo/bar.xgo:10:1 func addSafe(x string, y string) int { //line /foo/bar.xgo:11:1 return func() (_xgo_ret int) { //line /foo/bar.xgo:11:1 var _xgo_err error //line /foo/bar.xgo:11:1 _xgo_ret, _xgo_err = strconv.Atoi(x) //line /foo/bar.xgo:11:1 if _xgo_err != nil { //line /foo/bar.xgo:11:1 return 0 } //line /foo/bar.xgo:11:1 return }() + func() (_xgo_ret int) { //line /foo/bar.xgo:11:1 var _xgo_err error //line /foo/bar.xgo:11:1 _xgo_ret, _xgo_err = strconv.Atoi(y) //line /foo/bar.xgo:11:1 if _xgo_err != nil { //line /foo/bar.xgo:11:1 return 0 } //line /foo/bar.xgo:11:1 return }() } //line /foo/bar.xgo:14 func main() { //line /foo/bar.xgo:14:1 fmt.Println(func() (_xgo_ret int) { //line /foo/bar.xgo:14:1 var _xgo_err error //line /foo/bar.xgo:14:1 _xgo_ret, _xgo_err = add("100", "23") //line /foo/bar.xgo:14:1 if _xgo_err != nil { //line /foo/bar.xgo:14:1 _xgo_err = errors.NewFrame(_xgo_err, "add(\"100\", \"23\")", "/foo/bar.xgo", 14, "main.main") //line /foo/bar.xgo:14:1 panic(_xgo_err) } //line /foo/bar.xgo:14:1 return }()) //line /foo/bar.xgo:16:1 sum, err := add("10", "abc") //line /foo/bar.xgo:17:1 fmt.Println(sum, err) //line /foo/bar.xgo:19:1 fmt.Println(addSafe("10", "abc")) } `) } ================================================ FILE: cl/compile_testdir_test.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cl_test import ( "testing" "github.com/goplus/xgo/cl/cltest" ) func TestTestspx(t *testing.T) { cltest.SpxFromDir(t, "", "./_testspx") } func TestTestgop(t *testing.T) { cltest.FromDir(t, "", "./_testgop") } func TestTestc(t *testing.T) { cltest.FromDir(t, "", "./_testc") } func TestTestpy(t *testing.T) { cltest.FromDir(t, "", "./_testpy") } ================================================ FILE: cl/compile_xgo_test.go ================================================ /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cl_test import ( "strings" "testing" ) func TestArrowOp(t *testing.T) { gopClTest(t, ` type foo struct { } func (a foo) -> (b foo) { println "a -> b" } func (a foo) <> (b foo) { println "a <> b" } `, `package main import "fmt" type foo struct { } func (a foo) XGo_PointTo(b foo) { fmt.Println("a -> b") } func (a foo) XGo_PointBi(b foo) { fmt.Println("a <> b") } `) } func TestMapLit(t *testing.T) { gopClTest(t, ` func foo(map[string]string) {} foo {} `, `package main func foo(map[string]string) { } func main() { foo(map[string]string{}) } `) } func TestMayBuiltinDelete(t *testing.T) { gopClTest(t, ` func Delete(a int) {} func Foo(m map[string]int) { delete(m, "a") } delete 10 `, `package main func Delete(a int) { } func Foo(m map[string]int) { delete(m, "a") } func main() { Delete(10) } `) } func TestVargCommand(t *testing.T) { gopClTest(t, ` type foo int func (f foo) Ls(args ...string) { } var f foo f.ls `, `package main type foo int func (f foo) Ls(args ...string) { } var f foo func main() { f.Ls() } `) } func TestCommandInPkg(t *testing.T) { gopClTest(t, ` func Ls(args ...string) { } ls `, `package main func Ls(args ...string) { } func main() { Ls() } `) } func TestFuncAlias(t *testing.T) { gopClTest(t, ` func Foo(a ...int) {} foo 100 foo `, `package main func Foo(a ...int) { } func main() { Foo(100) Foo() } `) } func TestOverloadOp(t *testing.T) { gopClTest(t, ` type foo struct { } func (a *foo) + (b *foo) *foo { println("a + b") return &foo{} } func (a foo) - (b foo) foo { println("a - b") return foo{} } func -(a foo) { println("-a") } func ++(a foo) { println("a++") } func (a foo) != (b foo) bool{ println("a!=b") return true } var a, b foo var c = a - b var d = -a // TODO: -a have no return value! var e = a!=b `, `package main import "fmt" type foo struct { } func (a *foo) XGo_Add(b *foo) *foo { fmt.Println("a + b") return &foo{} } func (a foo) XGo_Sub(b foo) foo { fmt.Println("a - b") return foo{} } func (a foo) XGo_NE(b foo) bool { fmt.Println("a!=b") return true } func (a foo) XGo_Neg() { fmt.Println("-a") } func (a foo) XGo_Inc() { fmt.Println("a++") } var a, b foo var c = (foo).XGo_Sub(a, b) var d = a.XGo_Neg() var e = (foo).XGo_NE(a, b) `) } func TestOverloadOp2(t *testing.T) { gopClTest(t, ` type foo struct { } func (a foo) mulInt(b int) (ret foo) { return } func (a foo) mulFoo(b foo) (ret foo) { return } func intMulFoo(a int, b foo) (ret foo) { return } func (foo).* = ( (foo).mulInt (foo).mulFoo intMulFoo ) var a, b foo println a * 10 println a * b println 10 * a `, `package main import "fmt" type foo struct { } const XGoo__foo__XGo_Mul = ".mulInt,.mulFoo,intMulFoo" func (a foo) mulInt(b int) (ret foo) { return } func (a foo) mulFoo(b foo) (ret foo) { return } func intMulFoo(a int, b foo) (ret foo) { return } var a, b foo func main() { fmt.Println((foo).mulInt(a, 10)) fmt.Println((foo).mulFoo(a, b)) fmt.Println(intMulFoo(10, a)) } `) } func TestOverloadMethod(t *testing.T) { gopClTest(t, ` type foo struct { } func (a *foo) mulInt(b int) *foo { println "mulInt" return a } func (a *foo) mulFoo(b *foo) *foo { println "mulFoo" return a } func (foo).mul = ( (foo).mulInt (foo).mulFoo ) var a, b foo var c = a.mul(100) var d = a.mul(c) `, `package main import "fmt" type foo struct { } const XGoo_foo_mul = ".mulInt,.mulFoo" func (a *foo) mulInt(b int) *foo { fmt.Println("mulInt") return a } func (a *foo) mulFoo(b *foo) *foo { fmt.Println("mulFoo") return a } var a, b foo var c = a.mulInt(100) var d = a.mulFoo(c) `) } func TestOverloadFunc(t *testing.T) { gopClTest(t, ` func add = ( func(a, b int) int { return a + b } func(a, b string) string { return a + b } ) println add(100, 7) println add("Hello", "World") `, `package main import "fmt" func add__0(a int, b int) int { return a + b } func add__1(a string, b string) string { return a + b } func main() { fmt.Println(add__0(100, 7)) fmt.Println(add__1("Hello", "World")) } `) } func TestOverloadFunc2(t *testing.T) { gopClTest(t, ` func mulInt(a, b int) int { return a * b } func mulFloat(a, b float64) float64 { return a * b } func mul = ( mulInt mulFloat ) println mul(100, 7) println mul(1.2, 3.14) `, `package main import "fmt" const XGoo_mul = "mulInt,mulFloat" func mulInt(a int, b int) int { return a * b } func mulFloat(a float64, b float64) float64 { return a * b } func main() { fmt.Println(mulInt(100, 7)) fmt.Println(mulFloat(1.2, 3.14)) } `) } func TestOverloadFunc3(t *testing.T) { gopClTest(t, ` func addInt(a, b int) int { return a + b } func addFloat(a, b float64) float64 { return a * b } func add = ( func (a,b string) string { return a + b } addInt addFloat ) println add(100, 7) println add(1.2, 3.14) `, `package main import "fmt" const XGoo_add = ",addInt,addFloat" func add__0(a string, b string) string { return a + b } func addInt(a int, b int) int { return a + b } func addFloat(a float64, b float64) float64 { return a * b } func main() { fmt.Println(addInt(100, 7)) fmt.Println(addFloat(1.2, 3.14)) } `) } func TestOverload(t *testing.T) { gopClTest(t, ` import "github.com/goplus/xgo/cl/internal/overload/foo" type Mesh struct { } func (p *Mesh) Name() string { return "hello" } var ( m1 = &Mesh{} m2 = &Mesh{} ) foo.onKey "hello", => { } foo.onKey "hello", key => { } foo.onKey ["1"], => { } foo.onKey ["2"], key => { } foo.onKey [m1, m2], => { } foo.onKey [m1, m2], key => { } foo.onKey ["a"], ["b"], key => { } foo.onKey ["a"], [m1, m2], key => { } foo.onKey ["a"], nil, key => { } foo.onKey 100, 200 n := &foo.N{} n.onKey "hello", => { } n.onKey "hello", key => { } n.onKey ["1"], => { } n.onKey ["2"], key => { } n.onKey [m1, m2], => { } n.onKey [m1, m2], key => { } n.onKey ["a"], ["b"], key => { } n.onKey ["a"], [m1, m2], key => { } n.onKey ["a"], nil, key => { } n.onKey 100, 200 `, `package main import "github.com/goplus/xgo/cl/internal/overload/foo" type Mesh struct { } func (p *Mesh) Name() string { return "hello" } var m1 = &Mesh{} var m2 = &Mesh{} func main() { foo.OnKey__0("hello", func() { }) foo.OnKey__1("hello", func(key string) { }) foo.OnKey__2([]string{"1"}, func() { }) foo.OnKey__3([]string{"2"}, func(key string) { }) foo.OnKey__4([]foo.Mesher{m1, m2}, func() { }) foo.OnKey__5([]foo.Mesher{m1, m2}, func(key foo.Mesher) { }) foo.OnKey__6([]string{"a"}, []string{"b"}, func(key string) { }) foo.OnKey__7([]string{"a"}, []foo.Mesher{m1, m2}, func(key string) { }) foo.OnKey__6([]string{"a"}, nil, func(key string) { }) foo.OnKey__8(100, 200) n := &foo.N{} n.OnKey__0("hello", func() { }) n.OnKey__1("hello", func(key string) { }) n.OnKey__2([]string{"1"}, func() { }) n.OnKey__3([]string{"2"}, func(key string) { }) n.OnKey__4([]foo.Mesher{m1, m2}, func() { }) n.OnKey__5([]foo.Mesher{m1, m2}, func(key foo.Mesher) { }) n.OnKey__6([]string{"a"}, []string{"b"}, func(key string) { }) n.OnKey__7([]string{"a"}, []foo.Mesher{m1, m2}, func(key string) { }) n.OnKey__6([]string{"a"}, nil, func(key string) { }) n.OnKey__8(100, 200) } `) } func TestMixedOverload(t *testing.T) { gopMixedClTest(t, "main", ` package main type Mesher interface { Name() string } type N struct { } func (m *N) OnKey__0(a string, fn func()) { } func (m *N) OnKey__1(a string, fn func(key string)) { } func (m *N) OnKey__2(a []string, fn func()) { } func (m *N) OnKey__3(a []string, fn func(key string)) { } func (m *N) OnKey__4(a []Mesher, fn func()) { } func (m *N) OnKey__5(a []Mesher, fn func(key Mesher)) { } func (m *N) OnKey__6(a []string, b []string, fn func(key string)) { } func (m *N) OnKey__7(a []string, b []Mesher, fn func(key string)) { } func (m *N) OnKey__8(x int, y int) { } func OnKey__0(a string, fn func()) { } func OnKey__1(a string, fn func(key string)) { } func OnKey__2(a []string, fn func()) { } func OnKey__3(a []string, fn func(key string)) { } func OnKey__4(a []Mesher, fn func()) { } func OnKey__5(a []Mesher, fn func(key Mesher)) { } func OnKey__6(a []string, b []string, fn func(key string)) { } func OnKey__7(a []string, b []Mesher, fn func(key string)) { } func OnKey__8(x int, y int) { } func OnKey__9(a, b string, fn ...func(x int) int) { } func OnKey__a(a, b string, v ...int) { } `, ` type Mesh struct { } func (p *Mesh) Name() string { return "hello" } var ( m1 = &Mesh{} m2 = &Mesh{} ) OnKey "hello", => { } OnKey "hello", key => { } OnKey ["1"], => { } OnKey ["2"], key => { } OnKey [m1, m2], => { } OnKey [m1, m2], key => { } OnKey ["a"], ["b"], key => { } OnKey ["a"], [m1, m2], key => { } OnKey ["a"], nil, key => { } OnKey 100, 200 OnKey "a", "b", x => x * x, x => { return x * 2 } OnKey "a", "b", 1, 2, 3 OnKey("a", "b", [1, 2, 3]...) n := &N{} n.onKey "hello", => { } n.onKey "hello", key => { } n.onKey ["1"], => { } n.onKey ["2"], key => { } n.onKey [m1, m2], => { } n.onKey [m1, m2], key => { } n.onKey ["a"], ["b"], key => { } n.onKey ["a"], [m1, m2], key => { } n.onKey ["a"], nil, key => { } n.onKey 100, 200 `, `package main type Mesh struct { } func (p *Mesh) Name() string { return "hello" } var m1 = &Mesh{} var m2 = &Mesh{} func main() { OnKey__0("hello", func() { }) OnKey__1("hello", func(key string) { }) OnKey__2([]string{"1"}, func() { }) OnKey__3([]string{"2"}, func(key string) { }) OnKey__4([]Mesher{m1, m2}, func() { }) OnKey__5([]Mesher{m1, m2}, func(key Mesher) { }) OnKey__6([]string{"a"}, []string{"b"}, func(key string) { }) OnKey__7([]string{"a"}, []Mesher{m1, m2}, func(key string) { }) OnKey__6([]string{"a"}, nil, func(key string) { }) OnKey__8(100, 200) OnKey__9("a", "b", func(x int) int { return x * x }, func(x int) int { return x * 2 }) OnKey__a("a", "b", 1, 2, 3) OnKey__a("a", "b", []int{1, 2, 3}...) n := &N{} n.OnKey__0("hello", func() { }) n.OnKey__1("hello", func(key string) { }) n.OnKey__2([]string{"1"}, func() { }) n.OnKey__3([]string{"2"}, func(key string) { }) n.OnKey__4([]Mesher{m1, m2}, func() { }) n.OnKey__5([]Mesher{m1, m2}, func(key Mesher) { }) n.OnKey__6([]string{"a"}, []string{"b"}, func(key string) { }) n.OnKey__7([]string{"a"}, []Mesher{m1, m2}, func(key string) { }) n.OnKey__6([]string{"a"}, nil, func(key string) { }) n.OnKey__8(100, 200) } `) } func TestMixedOverloadOp(t *testing.T) { gopMixedClTest(t, "main", `package main import "fmt" type foo struct { } func (a *foo) XGo_Add(b *foo) *foo { fmt.Println("a + b") return &foo{} } func (a foo) XGo_Sub(b foo) foo { fmt.Println("a - b") return foo{} } func (a foo) XGo_NE(b foo) bool { fmt.Println("a!=b") return true } func (a foo) XGo_Neg() *foo { fmt.Println("-a") return &foo{} } func (a foo) XGo_Inc() { fmt.Println("a++") } `, ` var a, b foo var c = a - b var d = -a var e = a!=b `, `package main var a, b foo var c = (foo).XGo_Sub(a, b) var d = a.XGo_Neg() var e = (foo).XGo_NE(a, b) `) } func TestMixedVector3(t *testing.T) { gopMixedClTest(t, "main", `package main type Vector3 struct { x, y, z float64 } func (a Vector3) XGo_Add__0(n int) Vector3 { return Vector3{} } func (a Vector3) XGo_Add__1(n float64) Vector3 { return Vector3{} } func (a Vector3) XGo_Add__2(n Vector3) Vector3 { return Vector3{} } func (a *Vector3) XGo_AddAssign(n Vector3) { } func (a Vector3) XGo_Rcast__0() int { return 0 } func (a Vector3) XGo_Rcast__1() float64 { return 0 } func Vector3_Cast__0(x int) Vector3 { return Vector3{} } func Vector3_Cast__1(x float64) Vector3 { return Vector3{} } func Vector3_Init__0(x int) Vector3 { return Vector3{} } func Vector3_Init__1(x float64) Vector3 { return Vector3{} } `, ` var a Vector3 var b int var c float64 _ = a+b _ = a+100 _ = a+c _ = 100+a _ = Vector3(b)+a _ = b+int(a) a += b a += c `, `package main var a Vector3 var b int var c float64 func main() { _ = (Vector3).XGo_Add__0(a, b) _ = (Vector3).XGo_Add__0(a, 100) _ = (Vector3).XGo_Add__1(a, c) _ = (Vector3).XGo_Add__2(Vector3_Init__0(100), a) _ = (Vector3).XGo_Add__2(Vector3_Cast__0(b), a) _ = b + a.XGo_Rcast__0() a.XGo_AddAssign(Vector3_Init__0(b)) a.XGo_AddAssign(Vector3_Init__1(c)) } `) } func TestMixedInterfaceOverload(t *testing.T) { gopMixedClTest(t, "main", ` package main type N[T any] struct { v T } func (m *N[T]) OnKey__0(a string, fn func()) { } func (m *N[T]) OnKey__1(a string, fn func(key string)) { } func (m *N[T]) OnKey__2(a []string, fn func()) { } func (m *N[T]) OnKey__3(a []string, fn func(key string)) { } type I interface { OnKey__0(a string, fn func()) OnKey__1(a string, fn func(key string)) OnKey__2(a []string, fn func()) OnKey__3(a []string, fn func(key string)) } `, ` n := &N[int]{} n.onKey "1", => { } keys := ["1","2"] n.onKey keys, key => { println key } n.onKey keys, => { println keys } var i I = n i.onKey "1", key => { println key } i.onKey ["1","2"], key => { println key } `, `package main import "fmt" func main() { n := &N[int]{} n.OnKey__0("1", func() { }) keys := []string{"1", "2"} n.OnKey__3(keys, func(key string) { fmt.Println(key) }) n.OnKey__2(keys, func() { fmt.Println(keys) }) var i I = n i.OnKey__1("1", func(key string) { fmt.Println(key) }) i.OnKey__3([]string{"1", "2"}, func(key string) { fmt.Println(key) }) } `) } func TestMixedOverloadCommand(t *testing.T) { gopMixedClTest(t, "main", `package main func Test__0() { } func Test__1(n int) { } type N struct { } func (p *N) Test__0() { } func (p *N) Test__1(n int) { }`, ` Test Test 100 var n N n.test n.test 100 `, `package main func main() { Test__0() Test__1(100) var n N n.Test__0() n.Test__1(100) } `) } func TestOverloadNamed(t *testing.T) { gopClTest(t, ` import "github.com/goplus/xgo/cl/internal/overload/bar" var a bar.Var[int] var b bar.Var[bar.M] c := bar.Var(string) d := bar.Var(bar.M) `, `package main import "github.com/goplus/xgo/cl/internal/overload/bar" var a bar.Var__0[int] var b bar.Var__1[bar.M] func main() { c := bar.XGox_Var_Cast__0[string]() d := bar.XGox_Var_Cast__1[bar.M]() } `) } func TestMixedOverloadNamed(t *testing.T) { gopMixedClTest(t, "main", `package main type M = map[string]any type basetype interface { string | int | bool | float64 } type Var__0[T basetype] struct { val T } type Var__1[T map[string]any] struct { val T } func XGox_Var_Cast__0[T basetype]() *Var__0[T] { return new(Var__0[T]) } func XGox_Var_Cast__1[T map[string]any]() *Var__1[T] { return new(Var__1[T]) } `, ` var a Var[int] var b Var[M] c := Var(string) d := Var(M) `, `package main var a Var__0[int] var b Var__1[M] func main() { c := XGox_Var_Cast__0[string]() d := XGox_Var_Cast__1[M]() } `) } func TestStringLitBasic(t *testing.T) { gopClTest(t, `echo "$$"`, `package main import "fmt" func main() { fmt.Println("$") } `) } func TestStringLitVar(t *testing.T) { gopClTest(t, ` x := 1 println "Hi, " + "a${x}b"`, `package main import ( "fmt" "github.com/qiniu/x/stringutil" "strconv" ) func main() { x := 1 fmt.Println("Hi, " + stringutil.Concat("a", strconv.Itoa(x), "b")) } `) } func TestFileOpen(t *testing.T) { gopClTest(t, ` for line <- open("foo.txt")! { println line } `, `package main import ( "fmt" "github.com/qiniu/x/errors" "github.com/qiniu/x/osx" "os" ) func main() { for _xgo_it := osx.EnumLines(func() (_xgo_ret *os.File) { var _xgo_err error _xgo_ret, _xgo_err = os.Open("foo.txt") if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "open(\"foo.txt\")", "/foo/bar.xgo", 2, "main.main") panic(_xgo_err) } return }()); ; { var _xgo_ok bool line, _xgo_ok := _xgo_it.Next() if !_xgo_ok { break } fmt.Println(line) } } `) } func TestMixedGo(t *testing.T) { gopMixedClTest(t, "main", `package main import "strconv" const n = 10 func f(v int) string { return strconv.Itoa(v) } type foo struct { v int } func (a foo) _() { } func (a foo) Str() string { return f(a.v) } func (a *foo) Bar() int { return 0 } type foo2 = foo type foo3 foo2 `, ` var a [n]int var b string = f(n) var c foo2 var d int = c.v var e = foo3{} var x string = c.str `, `package main var a [10]int var b string = f(n) var c foo2 var d int = c.v var e = foo3{} var x string = c.Str() `, true) gopMixedClTest(t, "main", `package main type Point struct { X int Y int } `, ` type T struct{} println(&T{},&Point{10,20}) `, `package main import "fmt" type T struct { } func main() { fmt.Println(&T{}, &Point{10, 20}) } `, false) } func TestTypeAsParamsFunc(t *testing.T) { gopMixedClTest(t, "main", ` package main import ( "fmt" "reflect" ) type basetype interface { int | string } func XGox_Row__0[T basetype](name string) { } func XGox_Row__1[Array any](v int) { } func XGox_Col[T any](name string) { fmt.Printf("%v: %s\n", reflect.TypeOf((*T)(nil)).Elem(), name) } type Table struct { } func Gopt_Table_XGox_Col__0[T basetype](p *Table, name string) { } func Gopt_Table_XGox_Col__1[Array any](p *Table, v int) { } `, ` var tbl *Table col string, "name" col int, "age" row string, 100 tbl.col string, "foo" tbl.col int, 100 `, `package main var tbl *Table func main() { XGox_Col[string]("name") XGox_Col[int]("age") XGox_Row__1[string](100) Gopt_Table_XGox_Col__0[string](tbl, "foo") Gopt_Table_XGox_Col__1[int](tbl, 100) } `) } func TestYaptest(t *testing.T) { gopMixedClTest(t, "main", `package main import ( "github.com/goplus/xgo/cl/internal/test" ) type Class struct { test.Case } `, `var c Class var a int c.match a, "b" `, `package main import "github.com/goplus/xgo/cl/internal/test" var c Class var a int func main() { test.Gopt_Case_MatchAny(c, a, "b") } `) } func testRangeExpr(t *testing.T, codeTpl, expect string) { for k, s := range []string{" <- ", " := range ", " = range "} { if k == 2 { codeTpl = "i:=0\n" + codeTpl expect = strings.Replace(expect, "for i := ", "i := 0\n\tfor i = ", -1) } gopClTest(t, strings.Replace(codeTpl, "$", s, -1), expect) } } func TestRangeExpr(t *testing.T) { testRangeExpr(t, ` for i $ :10 { println(i) }`, `package main import "fmt" func main() { for i := 0; i < 10; i += 1 { fmt.Println(i) } } `) testRangeExpr(t, ` for i $ 1:10:3 { println(i) }`, `package main import "fmt" func main() { for i := 1; i < 10; i += 3 { fmt.Println(i) } } `) } func TestRangeExpr2(t *testing.T) { testRangeExpr(t, ` for i $ 1:10:2 { println(i) }`, `package main import "fmt" func main() { for i := 1; i < 10; i += 2 { fmt.Println(i) } } `) } func TestRangeExpr3(t *testing.T) { testRangeExpr(t, ` for i $ 1:10 { println(i) }`, `package main import "fmt" func main() { for i := 1; i < 10; i += 1 { fmt.Println(i) } } `) } func TestRangeExpr4(t *testing.T) { testRangeExpr(t, ` for i $ :10:2 { println(i) }`, `package main import "fmt" func main() { for i := 0; i < 10; i += 2 { fmt.Println(i) } } `) } func TestRangeExpr5(t *testing.T) { gopClTest(t, ` for range :10 { println("Hi") }`, `package main import "fmt" func main() { for _xgo_k := 0; _xgo_k < 10; _xgo_k += 1 { fmt.Println("Hi") } } `) } func TestRangeExpr6(t *testing.T) { gopClTest(t, ` for _ <- :10 { println("Hi") }`, `package main import "fmt" func main() { for _xgo_k := 0; _xgo_k < 10; _xgo_k += 1 { fmt.Println("Hi") } } `) } func testRangeExpr8(t *testing.T, codeTpl, expect string) { for _, s := range []string{" <- ", " := range "} { gopClTest(t, strings.Replace(codeTpl, "$", s, -1), expect) } } func TestRangeExpr8(t *testing.T) { testRangeExpr8(t, ` type T struct{} func (t T) start() int { return 0 } func (t T) end() int{ return 3 } func (t T) step() int{ return 1 } t:=T{} for i <- t.start():t.end():t.step(){ println i } `, `package main import "fmt" type T struct { } func (t T) start() int { return 0 } func (t T) end() int { return 3 } func (t T) step() int { return 1 } func main() { t := T{} for i, _xgo_end, _xgo_step := t.start(), t.end(), t.step(); i < _xgo_end; i += _xgo_step { fmt.Println(i) } } `) } func TestRangeExpr9(t *testing.T) { testRangeExpr8(t, ` type T struct{} func (t T) start() int { return 0 } func (t T) end() int{ return 3 } func (t T) step() int{ return 1 } t:=T{} i:=0 for i =range t.start():t.end():t.step(){ println i } `, `package main import "fmt" type T struct { } func (t T) start() int { return 0 } func (t T) end() int { return 3 } func (t T) step() int { return 1 } func main() { t := T{} i := 0 for _xgo_k, _xgo_end, _xgo_step := t.start(), t.end(), t.step(); _xgo_k < _xgo_end; _xgo_k += _xgo_step { i = _xgo_k fmt.Println(i) } } `) } func TestRangeExpr10(t *testing.T) { gopClTest(t, ` for :10 { echo "Hi" } `, `package main import "fmt" func main() { for _xgo_k := 0; _xgo_k < 10; _xgo_k += 1 { fmt.Println("Hi") } } `) } func Test_RangeExpressionIf_Issue1243(t *testing.T) { gopClTest(t, ` for i <- :10, i%3 == 0 { println i }`, `package main import "fmt" func main() { for i := 0; i < 10; i += 1 { if i%3 == 0 { fmt.Println(i) } } } `) } func TestStaticMethod(t *testing.T) { gopClTest(t, ` type foo int func foo.New(a int) *foo { return new(foo) } func foo._add() *foo { return new(foo) } a := foo.new(100) `, `package main type foo int func XGos_foo_New(a int) *foo { return new(foo) } func XGos__foo___add() *foo { return new(foo) } func main() { a := XGos_foo_New(100) } `) } func TestOverlodOptions(t *testing.T) { gopMixedClTest(t, "main", ` package main type PlayOptions struct { Action int Wait bool Loop bool } type Game struct { } func (g *Game) Play__0(options *PlayOptions) { } func (g *Game) Play__1(name string, options *PlayOptions) { } `, ` g := &Game{} g.play "work", { Action: 0, Loop: true } `, `package main func main() { g := &Game{} g.Play__1("work", &PlayOptions{Action: 0, Loop: true}) } `) } func TestEmbedField(t *testing.T) { gopClTest(t, `package main type Info struct { id int } type T struct { Info id string } func demo(t *T) { t.id = "0" } func main() { } `, `package main type Info struct { id int } type T struct { Info id string } func demo(t *T) { t.id = "0" } func main() { } `) } func TestOverloadUntyped(t *testing.T) { gopMixedClTest(t, "main", ` package main type specialObj int type SpriteName string type SpriteImpl struct { } func (p *SpriteImpl) turn(v any) { } func (p *SpriteImpl) TurnTo__0(sprite *SpriteImpl) { } func (p *SpriteImpl) TurnTo__1(sprite SpriteName) { } func (p *SpriteImpl) TurnTo__2(obj specialObj) { } func (p *SpriteImpl) TurnTo__3(degree float64) { } `, ` p := &SpriteImpl{} p.turnTo 180.0 p.turnTo 180.1 `, `package main func main() { p := &SpriteImpl{} p.TurnTo__2(180.0) p.TurnTo__3(180.1) } `) } func TestOverloadUntyped2(t *testing.T) { gopMixedClTest(t, "main", ` package main type SpriteImpl struct { } func (p *SpriteImpl) Rand__0(from int, to int) { } func (p *SpriteImpl) Rand__1(from float64, to float64) { } `, ` p := &SpriteImpl{} p.rand(1.0,2.0) p.rand(float64(1),float64(2)) `, `package main func main() { p := &SpriteImpl{} p.Rand__0(1.0, 2.0) p.Rand__1(float64(1), float64(2)) } `) } func TestSliceType(t *testing.T) { gopClTest(t, ` a := [1, "a"] a[0] = [1, 2, 3] echo a `, `package main import "fmt" func main() { a := []interface{}{1, "a"} a[0] = []int{1, 2, 3} fmt.Println(a) } `) } func TestMapLitType(t *testing.T) { gopClTest(t, ` var a any = { "Monday": 1, "Tuesday": 2, } echo a `, `package main import "fmt" var a interface{} = map[string]int{"Monday": 1, "Tuesday": 2} func main() { fmt.Println(a) } `) } ================================================ FILE: cl/error_msg_test.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cl_test import ( "fmt" "path/filepath" "runtime" "testing" "github.com/goplus/xgo/cl/cltest" ) func codeErrorTest(t *testing.T, msg, src string) { cltest.ErrorEx(t, "main", "bar.xgo", msg, src) } func codeErrorTestEx(t *testing.T, pkgname, filename, msg, src string) { cltest.ErrorEx(t, pkgname, filename, msg, src) } func codeErrorTestAst(t *testing.T, pkgname, filename, msg, src string) { cltest.ErrorAst(t, pkgname, filename, msg, src) } func TestErrTplLit(t *testing.T) { codeErrorTest(t, `bar.xgo:1:18: not enough arguments to return have () want (interface{})`, "tpl`a = INT => { return }`") } func TestErrSendStmt(t *testing.T) { codeErrorTest(t, `bar.xgo:3:7: can't send multiple values to a channel`, ` var a chan int a <- 1, 2 `) } func TestErrVargCommand(t *testing.T) { codeErrorTest(t, `bar.xgo:5:1: not enough arguments in call to Ls have () want (int)`, ` func Ls(int) { } ls `) codeErrorTest(t, `bar.xgo:8:1: not enough arguments in call to f.Ls have () want (int)`, ` type foo int func (f foo) Ls(int) { } var f foo f.ls `) } func TestErrUnsafe(t *testing.T) { codeErrorTest(t, `bar.xgo:2:9: undefined: Sizeof`, ` println Sizeof(0) `) } func TestErrLambdaExpr(t *testing.T) { codeErrorTest(t, "bar.xgo:7:6: too few arguments in lambda expression\n\thave ()\n\twant (int, int)", ` func foo(func(int, int)) { } func main() { foo(=> {}) } `) codeErrorTest(t, "bar.xgo:7:6: too many arguments in lambda expression\n\thave (x, y, z)\n\twant (int, int)", ` func foo(func(int, int)) { } func main() { foo((x, y, z) => {}) } `) codeErrorTest(t, "bar.xgo:6:8: cannot use lambda literal as type int in field value to Plot", ` type Foo struct { Plot int } foo := &Foo{ Plot: x => (x * 2, x * x), } `) codeErrorTest(t, "bar.xgo:6:8: cannot use lambda literal as type int in field value to Plot", ` type Foo struct { Plot int } foo := &Foo{ Plot: x => { return x * 2, x * x }, } `) codeErrorTest(t, "bar.xgo:4:5: cannot use lambda literal as type int in argument to foo", ` func foo(int) { } foo(=> {}) `) codeErrorTest(t, "bar.xgo:4:5: cannot use lambda literal as type func() in argument to foo", ` func foo(func()) { } foo => (100) `) codeErrorTest(t, "bar.xgo:6:8: cannot use lambda literal as type func() int in field value to Plot", ` type Foo struct { Plot func() int } foo := &Foo{ Plot: x => (x * 2, x * x), } `) codeErrorTest(t, "bar.xgo:2:18: cannot use lambda literal as type func() in assignment to foo", ` var foo func() = => (100) `) codeErrorTest(t, "bar.xgo:3:7: cannot use lambda literal as type func() in assignment to foo", ` var foo func() foo = => (100) `) codeErrorTest(t, "bar.xgo:2:29: lambda unsupport multiple assignment", ` var foo, foo1 func() = nil, => {} `) codeErrorTest(t, "bar.xgo:3:15: lambda unsupport multiple assignment", ` var foo func() _, foo = nil, => {} `) codeErrorTest(t, "bar.xgo:4:9: cannot use lambda expression as type int in return statement", ` func intSeq() int { i := 0 return => { i++ return i } } `) codeErrorTest(t, "bar.xgo:6:10: cannot use i (type int) as type string in return argument", ` func intSeq() func() string { i := 0 return => { i++ return i } } `) } func TestErrErrWrap(t *testing.T) { codeErrorTest(t, "bar.xgo:2:2: undefined: a", `func main() { a! } `) } func TestErrVar(t *testing.T) { codeErrorTest(t, "bar.xgo:6:5: assignment mismatch: 1 variables but fmt.Println returns 2 values", `import "fmt" func main() { } var a = fmt.Println(1) `) codeErrorTest(t, "bar.xgo:4:5: assignment mismatch: 1 variables but 2 values", `func main() { } var a = 1, 2 `) codeErrorTest(t, "bar.xgo:2:2: undefined: foo", `func main() { foo.x = 1 } `) codeErrorTest(t, "bar.xgo:2:2: use of builtin len not in function call", `func main() { len.x = 1 } `) codeErrorTest(t, "bar.xgo:2:10: undefined: foo", `func main() { println(foo.x) } `) codeErrorTest(t, "bar.xgo:2:10: use of builtin len not in function call", `func main() { println(len.x) } `) codeErrorTest(t, "bar.xgo:2:10: undefined: foo", `func main() { println(foo) } `) codeErrorTest(t, "bar.xgo:3:20: use of builtin len not in function call", `package main func foo(v map[int]len) { } `) codeErrorTest(t, "bar.xgo:5:20: bar is not a type", `package main var bar = 1 func foo(v map[int]bar) { } `) codeErrorTest(t, "bar.xgo:2:6: use of builtin len not in function call", `func main() { new(len) } `) codeErrorTest(t, "bar.xgo:2:2: undefined: foo", `func main() { foo = 1 } `) codeErrorTest(t, "bar.xgo:2:9: cannot use _ as value", `func main() { foo := _ } `) codeErrorTest(t, "bar.xgo:2:9: use of builtin len not in function call", `func main() { foo := len } `) codeErrorTest(t, "bar.xgo:2:2: println is not a variable", `func main() { println = "hello" } `) } func TestErrImport(t *testing.T) { codeErrorTest(t, `bar.xgo:8:2: confliction: NewEncoding declared both in "encoding/base64" and "encoding/base32"`, ` import ( . "encoding/base32" . "encoding/base64" ) func foo() { NewEncoding("Hi") }`) codeErrorTest(t, "bar.xgo:5:2: cannot refer to unexported name os.undefined", ` import "os" func foo() { os.undefined }`) codeErrorTest(t, "bar.xgo:5:2: undefined: os.UndefinedObject", ` import "os" func foo() { os.UndefinedObject }`) codeErrorTest(t, "bar.xgo:2:13: undefined: testing", ` func foo(t *testing.T) { }`) codeErrorTest(t, "bar.xgo:4:12: testing.Verbose is not a type", ` import "testing" func foo(t testing.Verbose) { }`) } func TestErrConst(t *testing.T) { codeErrorTest(t, "bar.xgo:3:7: a redeclared in this block\n\tprevious declaration at bar.xgo:2:5", ` var a int const a = 1 `) codeErrorTest(t, "bar.xgo:4:2: missing value in const declaration", ` const ( a = iota b, c ) `) } func TestErrNewVar(t *testing.T) { codeErrorTest(t, "bar.xgo:3:5: a redeclared in this block\n\tprevious declaration at bar.xgo:2:5", ` var a int var a string `) } func TestErrDefineVar(t *testing.T) { codeErrorTest(t, "bar.xgo:3:1: no new variables on left side of :=\n"+ "bar.xgo:3:6: cannot use \"Hi\" (type untyped string) as type int in assignment", ` a := 1 a := "Hi" `) } func TestErrAssignMismatchT(t *testing.T) { codeErrorTest(t, `bar.xgo:2:16: cannot use []string{} (type []string) as type string in assignment`, ` var a string = []string{} `) codeErrorTest(t, `bar.xgo:2:16: cannot use [2]string{} (type [2]string) as type string in assignment`, ` var a string = [2]string{} `) codeErrorTest(t, `bar.xgo:2:16: cannot use map[int]string{} (type map[int]string) as type string in assignment`, ` var a string = map[int]string{} `) codeErrorTest(t, `bar.xgo:3:16: cannot use T{} (type T) as type string in assignment`, ` type T struct{} var a string = T{} `) codeErrorTest(t, `bar.xgo:2:16: cannot use func(){} (type func()) as type string in assignment`, ` var a string = func(){} `) } func TestErrAssign(t *testing.T) { codeErrorTest(t, `bar.xgo:8:1: assignment mismatch: 1 variables but bar returns 2 values`, ` func bar() (n int, err error) { return } x := 1 x = bar() `) codeErrorTest(t, `bar.xgo:4:1: assignment mismatch: 1 variables but 2 values`, ` x := 1 x = 1, "Hi" `) } func TestErrReturn(t *testing.T) { codeErrorTest(t, "bar.xgo:4:2: too few arguments to return\n\thave (untyped int)\n\twant (int, error)", ` func foo() (int, error) { return 1 } `) codeErrorTest(t, "bar.xgo:4:2: too many arguments to return\n\thave (untyped int, untyped int, untyped string)\n\twant (int, error)", ` func foo() (int, error) { return 1, 2, "Hi" } `) codeErrorTest(t, `bar.xgo:4:12: cannot use "Hi" (type untyped string) as type error in return argument`, ` func foo() (int, error) { return 1, "Hi" } `) codeErrorTest(t, "bar.xgo:8:2: too few arguments to return\n\thave (byte)\n\twant (int, error)", ` func bar() (v byte) { return } func foo() (int, error) { return bar() } `) codeErrorTest(t, "bar.xgo:8:2: too many arguments to return\n\thave (n int, err error)\n\twant (v byte)", ` func bar() (n int, err error) { return } func foo() (v byte) { return bar() } `) codeErrorTest(t, `bar.xgo:8:2: cannot use byte value as type error in return argument`, ` func bar() (n int, v byte) { return } func foo() (int, error) { return bar() } `) codeErrorTest(t, "bar.xgo:4:2: not enough arguments to return\n\thave ()\n\twant (byte)", ` func foo() byte { return } `) } func TestErrForRange(t *testing.T) { codeErrorTest(t, `bar.xgo:4:8: cannot assign type string to a (type int) in range`, ` a := 1 var b []string for _, a = range b { } `) } func TestErrInitFunc(t *testing.T) { codeErrorTest(t, `bar.xgo:2:6: func init must have no arguments and no return values`, ` func init(v byte) { } `) } func TestErrRecv(t *testing.T) { codeErrorTest(t, `bar.xgo:5:9: invalid receiver type a (a is a pointer type)`, ` type a *int func (p a) foo() { } `) codeErrorTest(t, `bar.xgo:2:9: invalid receiver type error (error is an interface type)`, ` func (p error) foo() { } `) codeErrorTest(t, `bar.xgo:2:9: invalid receiver type []byte ([]byte is not a defined type)`, ` func (p []byte) foo() { } `) codeErrorTest(t, `bar.xgo:2:10: invalid receiver type []byte ([]byte is not a defined type)`, ` func (p *[]byte) foo() { } `) } func TestErrEnvOp(t *testing.T) { codeErrorTest(t, `bar.xgo:2:6: operator $name undefined`, ` echo ${name} `) codeErrorTest(t, `bar.xgo:2:1: operator $id undefined`, ` $id `) } func TestErrStringLit(t *testing.T) { codeErrorTest(t, `bar.xgo:2:9: [].string undefined (type []interface{} has no field or method string)`, ` echo "${[]}" `) } func TestErrStructLit(t *testing.T) { codeErrorTest(t, `bar.xgo:3:39: too many values in struct{x int; y string}{...}`, ` x := 1 a := struct{x int; y string}{1, "Hi", 2} `) codeErrorTest(t, `bar.xgo:3:30: too few values in struct{x int; y string}{...}`, ` x := 1 a := struct{x int; y string}{1} `) codeErrorTest(t, `bar.xgo:3:33: cannot use x (type int) as type string in value of field y`, ` x := 1 a := struct{x int; y string}{1, x} `) codeErrorTest(t, `bar.xgo:2:30: z undefined (type struct{x int; y string} has no field or method z)`, ` a := struct{x int; y string}{z: 1} `) } func TestErrArray(t *testing.T) { codeErrorTest(t, `bar.xgo:3:8: non-constant array bound n`, ` var n int var a [n]int `) } func TestErrArrayLit(t *testing.T) { codeErrorTest(t, `bar.xgo:3:14: cannot use a as index which must be non-negative integer constant`, ` a := "Hi" b := [10]int{a: 1} `) codeErrorTest(t, `bar.xgo:3:20: array index 10 out of bounds [0:10]`, ` a := "Hi" b := [10]int{9: 1, 3} `) codeErrorTest(t, `bar.xgo:3:16: array index 1 out of bounds [0:1]`, ` a := "Hi" b := [1]int{1, 2} `) codeErrorTest(t, `bar.xgo:3:14: array index 12 (value 12) out of bounds [0:10]`, ` a := "Hi" b := [10]int{12: 2} `) codeErrorTest(t, `bar.xgo:3:14: cannot use a+"!" (type string) as type int in array literal`, ` a := "Hi" b := [10]int{a+"!"} `) codeErrorTest(t, `bar.xgo:3:17: cannot use a (type string) as type int in array literal`, ` a := "Hi" b := [10]int{2: a} `) } func TestErrSliceLit(t *testing.T) { codeErrorTest(t, `bar.xgo:3:12: cannot use a as index which must be non-negative integer constant`, ` a := "Hi" b := []int{a: 1} `) codeErrorTest(t, `bar.xgo:3:12: cannot use a (type string) as type int in slice literal`, ` a := "Hi" b := []int{a} `) codeErrorTest(t, `bar.xgo:3:15: cannot use a (type string) as type int in slice literal`, ` a := "Hi" b := []int{2: a} `) } func TestErrMapLit(t *testing.T) { codeErrorTest(t, `bar.xgo:4:6: cannot use 1 (type untyped int) as type string in map key`, ` func foo(map[string]string) {} foo {1: 2} `) codeErrorTest(t, `bar.xgo:2:1: invalid composite literal type int`, ` int{2} `) codeErrorTest(t, `bar.xgo:2:1: missing key in map literal`, ` map[string]int{2} `) codeErrorTest(t, `bar.xgo:2:21: cannot use 1+2 (type untyped int) as type string in map key bar.xgo:3:27: cannot use "Go" + "+" (type untyped string) as type int in map value`, ` a := map[string]int{1+2: 2} b := map[string]int{"Hi": "Go" + "+"} `) codeErrorTest(t, `bar.xgo:2:13: invalid map literal`, ` var v any = {1:2,1} `) codeErrorTest(t, `bar.xgo:2:21: invalid map literal`, ` var v map[int]int = {1:2,1} `) } func TestErrSlice(t *testing.T) { codeErrorTest(t, `bar.xgo:4:6: cannot slice a (type *byte)`, ` var a *byte x := 1 b := a[x:2] `) codeErrorTest(t, `bar.xgo:3:6: cannot slice a (type bool)`, ` a := true b := a[1:2] `) codeErrorTest(t, `bar.xgo:3:6: invalid operation a[1:2:5] (3-index slice of string)`, ` a := "Hi" b := a[1:2:5] `) } func TestErrIndex(t *testing.T) { codeErrorTest(t, `bar.xgo:3:10: assignment mismatch: 2 variables but 1 values`, ` a := "Hi" b, ok := a[1] `) codeErrorTest(t, `bar.xgo:3:6: invalid operation: a[1] (type bool does not support indexing)`, ` a := true b := a[1] `) } func TestErrIndexRef(t *testing.T) { codeErrorTest(t, `bar.xgo:3:1: cannot assign to a[1] (strings are immutable)`, ` a := "Hi" a[1] = 'e' `) } func TestErrStar(t *testing.T) { codeErrorTest(t, `bar.xgo:3:2: invalid indirect of a (type string)`, ` a := "Hi" *a = 'e' `) codeErrorTest(t, `bar.xgo:3:7: invalid indirect of a (type string)`, ` a := "Hi" b := *a `) } func TestErrCondExpr(t *testing.T) { codeErrorTest(t, `bar.xgo:5:6: assignment mismatch: 2 variables but self.XGo_first returns 1 values don't call End(), please use EndInit() instead`, ` import "github.com/goplus/xgo/cl/internal/dql" doc := dql.new2 echo doc.users@($age < 18).$name `) } func TestErrMember(t *testing.T) { codeErrorTest(t, `bar.xgo:5:6: a.* undefined (type interface{Read(p []byte) (n int, err error)} has no field or method XGo_Child)`, ` var a interface { Read(p []byte) (n int, err error) } b := a.*.$x `) codeErrorTest(t, `bar.xgo:3:6: a.$x undefined (type string has no field or method XGo_Attr)`, ` a := "Hello" b := a.$x `) codeErrorTest(t, `bar.xgo:3:6: a.x undefined (type string has no field or method x)`, ` a := "Hello" b := a.x `) codeErrorTest(t, `bar.xgo:3:6: a.1 undefined (type string has no field or method 1)`, ` a := "Hello" b := a.1 `) } func TestErrMemberRef(t *testing.T) { codeErrorTest(t, `bar.xgo:3:1: a.x undefined (type string has no field or method x)`, ` a := "Hello" a.x = 1 `) codeErrorTest(t, `bar.xgo:5:1: a.x undefined (type aaa has no field or method x)`, ` type aaa byte a := aaa(0) a.x = 1 `) codeErrorTest(t, `bar.xgo:5:1: a.z undefined (type aaa has no field or method z)`, ` type aaa struct {x int; y string} a := aaa{} a.z = 1 `) codeErrorTest(t, `bar.xgo:3:1: a.z undefined (type struct{x int; y string} has no field or method z)`, ` a := struct{x int; y string}{} a.z = 1 `) } func TestErrLabel(t *testing.T) { codeErrorTest(t, `bar.xgo:4:1: label foo already defined at bar.xgo:2:1 bar.xgo:2:1: label foo defined and not used`, `x := 1 foo: i := 1 foo: i++ `) codeErrorTest(t, `bar.xgo:2:6: label foo is not defined`, `x := 1 goto foo`) codeErrorTest(t, `bar.xgo:2:7: label foo is not defined`, `x := 1 break foo`) } func TestErrBranchStmt(t *testing.T) { codeErrorTest(t, `bar.xgo:2:2: fallthrough statement out of place`, `func foo() { fallthrough }`) } func TestErrNoEntrypoint(t *testing.T) { codeErrorTest(t, "bar.xgo:2:2: undefined: println1", `func main() { println1 "hello" } `) codeErrorTest(t, "bar.xgo:1:1: undefined: println1", `println1 "hello"`) codeErrorTest(t, "bar.xgo:2:2: undefined: println1", ` println1 "hello" `) codeErrorTest(t, "bar.xgo:2:2: undefined: println1", `package main println1 "hello" `) codeErrorTest(t, `bar.xgo:1:9: undefined: abc`, `println abc `) codeErrorTestEx(t, "bar", "bar.xgo", `bar.xgo:2:9: undefined: abc`, `package bar println abc `) } func TestErrTypeRedefine(t *testing.T) { codeErrorTest(t, "bar.xgo:9:6: Point redeclared in this block\n\tprevious declaration at bar.xgo:5:6", `import "fmt" func (p *Point) String() string { return fmt.Sprintf("%v-%v",p.X,p.Y) } type Point struct { X int Y int } type Point struct { X int Y int } `) } func TestErrSwitchDuplicate(t *testing.T) { codeErrorTest(t, "bar.xgo:4:7: duplicate case 100 in switch\n\tprevious case at bar.xgo:3:7", `var n int switch n { case 100: case 100: }`) codeErrorTest(t, "bar.xgo:4:7: duplicate case int(100) (value 100) in switch\n\tprevious case at bar.xgo:3:7", `var n int switch n { case 100: case int(100): }`) codeErrorTest(t, "bar.xgo:4:7: duplicate case 50 + 50 (value 100) in switch\n\tprevious case at bar.xgo:3:7", `var n int switch n { case 100: case 50 + 50: }`) codeErrorTest(t, "bar.xgo:5:7: duplicate case int(100) (value 100) in switch\n\tprevious case at bar.xgo:3:7", `var n interface{} switch n { case 100: case uint(100): case int(100): }`) codeErrorTest(t, "bar.xgo:4:7: duplicate case 100.0 in switch\n\tprevious case at bar.xgo:3:7", `var n interface{} switch n { case 100.0: case 100.0: }`) codeErrorTest(t, "bar.xgo:5:7: duplicate case v (value 100) in switch\n\tprevious case at bar.xgo:4:7", `var n interface{} const v = 100.0 switch n { case 100.0: case v: }`) codeErrorTest(t, "bar.xgo:5:7: duplicate case v (value \"hello\") in switch\n\tprevious case at bar.xgo:4:7", `var n interface{} const v = "hello" switch n { case "hello": case v: }`) codeErrorTest(t, `bar.xgo:4:7: duplicate case 100 in switch previous case at bar.xgo:3:7 bar.xgo:5:7: duplicate case 50 + 50 (value 100) in switch previous case at bar.xgo:3:7`, `var n int switch n { case 100: case 100: case 50 + 50: }`) codeErrorTest(t, "bar.xgo:4:2: multiple defaults in switch (first at bar.xgo:3:2)", `var n interface{} switch n { default: default: }`) codeErrorTest(t, `bar.xgo:4:2: multiple defaults in switch (first at bar.xgo:3:2) bar.xgo:5:2: multiple defaults in switch (first at bar.xgo:3:2)`, `var n interface{} switch n { default: default: default: }`) } func TestErrTypeSwitchDuplicate(t *testing.T) { codeErrorTest(t, `bar.xgo:4:7: duplicate case int in type switch previous case at bar.xgo:3:7 bar.xgo:5:7: duplicate case int in type switch previous case at bar.xgo:3:7`, `var n interface{} = 100 switch n.(type) { case int: case int: case int: } `) codeErrorTest(t, `bar.xgo:4:7: multiple nil cases in type switch (first at bar.xgo:3:7) bar.xgo:5:7: multiple nil cases in type switch (first at bar.xgo:3:7)`, `var n interface{} = 100 switch n.(type) { case nil: case nil: case nil: } `) codeErrorTest(t, `bar.xgo:4:2: multiple defaults in type switch (first at bar.xgo:3:2) bar.xgo:5:2: multiple defaults in type switch (first at bar.xgo:3:2)`, `var n interface{} = 100 switch n.(type) { default: default: default: } `) } func TestErrAutoProperty(t *testing.T) { codeErrorTest(t, `bar.xgo:4:11: cannot refer to unexported name fmt.println`, ` import "fmt" n, err := fmt.println `) } func TestFiledsNameRedecl(t *testing.T) { codeErrorTest(t, `bar.xgo:6:2: Id redeclared bar.xgo:5:2 other declaration of Id bar.xgo:7:2: Id redeclared bar.xgo:5:2 other declaration of Id bar.xgo:9:2: name redeclared bar.xgo:8:2 other declaration of name`, ` type Id struct { } type A struct { Id int Id string Id name string name string } `) } func TestErrImportPkg(t *testing.T) { root := filepath.Join(runtime.GOROOT(), "src", "fmt2") where := "GOROOT" ver := runtime.Version()[:6] if ver >= "go1.21" { where = "std" } codeErrorTest(t, fmt.Sprintf(`bar.xgo:3:2: package fmt2 is not in `+where+` (%v) `, root), ` import ( "fmt2" ) `) codeErrorTest(t, `bar.xgo:3:2: no required module provides package github.com/goplus/xgo/fmt2; to add it: go get github.com/goplus/xgo/fmt2 `, ` import ( "github.com/goplus/xgo/fmt2" ) `) } func TestErrClassFileGopx(t *testing.T) { codeErrorTestEx(t, "main", "Rect.gox", `Rect.gox:5:2: A redeclared Rect.gox:3:2 other declaration of A`, ` var ( A i int A ) type A struct{} println "hello" `) } func TestErrVarInFunc(t *testing.T) { codeErrorTest(t, `bar.xgo:6:10: not enough arguments in call to set have (untyped string) want (name string, v int) bar.xgo:7:10: undefined: a`, ` func set(name string, v int) string { return name } func test() { var a = set("box") println(a) } `) } func TestErrInt128(t *testing.T) { codeErrorTest(t, `bar.xgo:2:16: cannot use 1<<127 (type untyped int) as type github.com/qiniu/x/xgo/ng.Int128 in assignment`, ` var a int128 = 1<<127 `) codeErrorTest(t, `bar.xgo:2:13: cannot convert 1<<127 (untyped int constant 170141183460469231731687303715884105728) to type Int128`, ` a := int128(1<<127) `) codeErrorTest(t, `bar.xgo:2:13: cannot convert -1<<127-1 (untyped int constant -170141183460469231731687303715884105729) to type Int128`, ` a := int128(-1<<127-1) `) codeErrorTest(t, `bar.xgo:3:13: cannot convert b (untyped int constant -170141183460469231731687303715884105729) to type Int128`, ` const b = -1<<127-1 a := int128(b) `) } func TestErrUint128(t *testing.T) { codeErrorTest(t, `bar.xgo:2:17: cannot use 1<<128 (type untyped int) as type github.com/qiniu/x/xgo/ng.Uint128 in assignment`, ` var a uint128 = 1<<128 `) codeErrorTest(t, `bar.xgo:2:14: cannot convert 1<<128 (untyped int constant 340282366920938463463374607431768211456) to type Uint128`, ` a := uint128(1<<128) `) codeErrorTest(t, `bar.xgo:2:17: cannot use -1 (type untyped int) as type github.com/qiniu/x/xgo/ng.Uint128 in assignment`, ` var a uint128 = -1 `) codeErrorTest(t, `bar.xgo:2:14: cannot convert -1 (untyped int constant -1) to type Uint128`, ` a := uint128(-1) `) codeErrorTest(t, `bar.xgo:3:14: cannot convert b (untyped int constant -1) to type Uint128`, ` const b = -1 a := uint128(b) `) } func TestErrCompileFunc(t *testing.T) { codeErrorTest(t, "bar.xgo:2:1: compile `printf(\"%+v\\n\", int32)`: unreachable", ` printf("%+v\n", int32) `) } func TestToTypeError(t *testing.T) { codeErrorTestAst(t, "main", "bar.xgo", `bar.xgo:3:3: toType unexpected: *ast.BadExpr`, ` type a := 1 `) } func TestCompileExprError(t *testing.T) { codeErrorTestAst(t, "main", "bar.go", `bar.go:5:1: compileExpr failed: unknown - *ast.BadExpr`, ` func Foo(){} func _() { Foo( } `) codeErrorTestAst(t, "main", "bar.go", `bar.go:3:2: compileExprLHS failed: unknown - *ast.StructType`, ` func _() { struct() = nil } `) } func TestOverloadFuncDecl(t *testing.T) { codeErrorTest(t, "bar.xgo:3:2: invalid func (foo).mulInt", ` func mul = ( (foo).mulInt ) `) codeErrorTest(t, "bar.xgo:2:7: invalid recv type *foo", ` func (*foo).mul = ( (foo).mulInt ) `) codeErrorTest(t, "bar.xgo:3:2: invalid recv type (foo2)", ` func (foo).mul = ( (foo2).mulInt ) `) codeErrorTest(t, "bar.xgo:3:2: invalid method mulInt", ` func (foo).mul = ( mulInt ) `) codeErrorTest(t, "bar.xgo:3:2: invalid recv type (**foo)", ` func (foo).mul = ( (**foo).mulInt ) `) codeErrorTest(t, `bar.xgo:3:9: unknown func ("ok")`, ` func mul = ( println("ok") ) `) codeErrorTest(t, "bar.xgo:3:2: invalid method func(){}", ` func (foo).mul = ( func(){} ) `) codeErrorTest(t, "bar.xgo:2:12: invalid overload operator ++", ` func (foo).++ = ( mulInt ) `) } func TestCompositeLitError(t *testing.T) { codeErrorTest(t, `bar.xgo:2:22: cannot use 3.14 (type untyped float) as type int in slice literal`, ` var a [][]int = {[10,3.14,200],[100,200]} echo a `) codeErrorTest(t, `bar.xgo:2:17: cannot use lambda literal as type int in assignment`, ` var a []int = {(x => x)} echo a `) codeErrorTest(t, `bar.xgo:2:35: cannot use x (type int) as type string in return argument`, ` var a []func(int) string = {(x => x)} echo a `) codeErrorTest(t, `bar.xgo:2:27: cannot use lambda literal as type int in assignment to "A"`, ` var a map[any]int = {"A": x => x} `) codeErrorTest(t, `bar.xgo:2:45: cannot use x (type int) as type string in return argument`, ` var a map[any]func(int) string = {"A": x => x} `) codeErrorTest(t, `bar.xgo:2:24: cannot use lambda literal as type int in field value`, ` var a = struct{v int}{(x => x)} `) codeErrorTest(t, `bar.xgo:2:27: cannot use lambda literal as type int in field value to v`, ` var a = struct{v int}{v: (x => x)} `) } ================================================ FILE: cl/expr.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cl import ( "bytes" "errors" goast "go/ast" gotoken "go/token" "go/types" "log" "math/big" "strconv" "strings" "syscall" "github.com/goplus/gogen" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/printer" "github.com/goplus/xgo/token" tpl "github.com/goplus/xgo/tpl/ast" "github.com/qiniu/x/stringutil" ) /*----------------------------------------------------------------------------- Name context: - varVal (ident) - varRef = expr (identLHS) - pkgRef.member (selectorExpr) - pkgRef.member = expr (selectorExprLHS) - pkgRef.fn(args) (callExpr) - fn(args) (callExpr) - spx.fn(args) (callExpr) - this.member (classMember) - this.method(args) (classMember) Name lookup: - local variables - $recv members (only in class files) - package globals (variables, constants, types, imported packages etc.) - $spx package exports (only in class files) - $universe package exports (including builtins) // ---------------------------------------------------------------------------*/ const ( clIdentCanAutoCall = 1 << iota // allow auto property clIdentAllowBuiltin clIdentLHS clIdentSelectorExpr // this ident is X (not Sel) of ast.SelectorExpr clIdentGoto clCommandWithoutArgs // this expr is a command without args (eg. ls) clCommandIdent // this expr is a command and an ident (eg. mkdir "abc") clIdentInStringLitEx // this expr is an ident in a string extended literal (eg. ${PATH}) clInCallExpr ) const ( objNormal = iota objPkgRef objXGoExecOrEnv objXGoEnv = objXGoExecOrEnv objXGoExec = objXGoExecOrEnv ) func compileIdent(ctx *blockCtx, lhs int, ident *ast.Ident, flags int) (pkg gogen.PkgRef, kind int) { fvalue := (flags&clIdentSelectorExpr) != 0 || (flags&clIdentLHS) == 0 cb := ctx.cb name := ident.Name if name == "_" { if fvalue { panic(ctx.newCodeError(ident.Pos(), ident.End(), "cannot use _ as value")) } cb.VarRef(nil) return } var recv *types.Var var oldo types.Object scope := ctx.pkg.Types.Scope() at, o := cb.Scope().LookupParent(name, token.NoPos) if o != nil { if at != scope && at != types.Universe { // local object goto find } } if ctx.isClass { // in an XGo class file if recv = classRecv(cb); recv != nil { cb.Val(recv) chkFlag := flags if chkFlag&clIdentSelectorExpr != 0 { // TODO(xsw): remove this condition chkFlag = clIdentCanAutoCall } if compileMember(ctx, lhs, ident, name, chkFlag) == nil { // class member object return } cb.InternalStack().PopN(1) } } // global object if ctx.loadSymbol(name) { o, at = scope.Lookup(name), scope } if o != nil && at != types.Universe { goto find } // pkgRef object if (flags & clIdentSelectorExpr) != 0 { if pi, ok := ctx.findImport(name); ok { if rec := ctx.recorder(); rec != nil { rec.Use(ident, pi.pkgName) } return pi.PkgRef, objPkgRef } } // function alias if compileFuncAlias(ctx, lhs, scope, ident, flags) { return } // object from import . "xxx" if compilePkgRef(ctx, lhs, gogen.PkgRef{}, ident, flags, objPkgRef) { return } // universe object if obj := ctx.pkg.Builtin().TryRef(name); obj != nil { if (flags&clIdentAllowBuiltin) == 0 && isBuiltin(o) && !strings.HasPrefix(o.Name(), "print") { panic(ctx.newCodeErrorf(ident.Pos(), ident.End(), "use of builtin %s not in function call", name)) } oldo, o = o, obj } else if o == nil { // for support XGo_Exec, see TestSpxXGoExec if (clCommandIdent&flags) != 0 && recv != nil && xgoOp(cb, recv, "XGo_Exec", "Gop_Exec", ident) == nil { kind = objXGoExec return } // for support XGo_Env, see TestSpxGopEnv if (clIdentInStringLitEx&flags) != 0 && recv != nil && xgoOp(cb, recv, "XGo_Env", "Gop_Env", ident) == nil { kind = objXGoEnv return } if (clIdentGoto & flags) != 0 { l := ident.Obj.Data.(*ast.Ident) panic(ctx.newCodeErrorf(l.Pos(), l.End(), "label %v is not defined", l.Name)) } panic(ctx.newCodeErrorf(ident.Pos(), ident.End(), "undefined: %s", name)) } find: if fvalue { cb.Val(o, ident) } else { cb.VarRef(o, ident) } if rec := ctx.recorder(); rec != nil { e := cb.Get(-1) if oldo != nil && gogen.IsTypeEx(e.Type) { // for builtin object rec.recordIdent(ident, oldo) return } rec.recordIdent(ident, o) } return } /* func compileMatrixLit(ctx *blockCtx, v *ast.MatrixLit) { cb := ctx.cb ncol := -1 for _, elts := range v.Elts { switch n := len(elts); n { case 1: elt := elts[0] if e, ok := elt.(*ast.Ellipsis); ok { compileExpr(ctx, e.Elt) panic("TODO") // TODO(xsw): matrixLit with ellipsis } fallthrough default: if ncol < 0 { ncol = n } else if ncol != n { ctx.handleErrorf(elts[0].Pos(), "inconsistent matrix column count: got %v, want %v", n, ncol) } for _, elt := range elts { compileExpr(ctx, elt) } cb.SliceLitEx(...) } } } */ func compileEnvExpr(ctx *blockCtx, lhs int, v *ast.EnvExpr) { cb := ctx.cb if _, self := cb.Scope().LookupParent("self", 0); self != nil { // self.$attr name := v.Name cb.Val(self, v) // push self if compileAttr(cb, lhs, name.Name, name) == nil { return } cb.InternalStack().PopN(1) // pop self if failed } if ctx.isClass { // in an XGo class file if recv := classRecv(cb); recv != nil { if xgoOp(cb, recv, "XGo_Env", "Gop_Env", v) == nil { name := v.Name cb.Val(name.Name, name).CallWith(1, lhs, 0, v) return } } } invalidVal(cb) ctx.handleErrorf(v.Pos(), v.End(), "operator $%v undefined", v.Name) } func classRecv(cb *gogen.CodeBuilder) *types.Var { if fn := cb.Func(); fn != nil { sig := fn.Ancestor().Type().(*types.Signature) return sig.Recv() } return nil } func xgoOp(cb *gogen.CodeBuilder, recv *types.Var, op1, op2 string, src ...ast.Node) error { cb.Val(recv) kind, e := cb.Member(op1, 0, gogen.MemberFlagVal, src...) if kind == gogen.MemberInvalid { if _, e = cb.Member(op2, 0, gogen.MemberFlagVal, src...); e != nil { cb.InternalStack().PopN(1) // pop recv } } return e } func isBuiltin(o types.Object) bool { if _, ok := o.(*types.Builtin); ok { return ok } return false } func compileMember(ctx *blockCtx, lhs int, v ast.Node, name string, flags int) error { var mflag gogen.MemberFlag switch { case (flags & clIdentLHS) != 0: mflag = gogen.MemberFlagRef case (flags & clIdentCanAutoCall) != 0: mflag = gogen.MemberFlagAutoProperty default: mflag = gogen.MemberFlagMethodAlias } _, err := ctx.cb.Member(name, lhs, mflag, v) return err } func compileExprLHS(ctx *blockCtx, expr ast.Expr) { switch v := expr.(type) { case *ast.Ident: compileIdent(ctx, 1, v, clIdentLHS) case *ast.IndexExpr: compileIndexExprLHS(ctx, v) case *ast.SelectorExpr: compileSelectorExprLHS(ctx, v) case *ast.StarExpr: compileStarExprLHS(ctx, v) default: panic(ctx.newCodeErrorf(v.Pos(), v.End(), "compileExprLHS failed: unknown - %T", expr)) } if rec := ctx.recorder(); rec != nil { rec.recordExpr(ctx, expr, true) } } func identOrSelectorFlags(inFlags []int) (flags int, cmdNoArgs bool) { if inFlags == nil { return clIdentCanAutoCall, false } flags = inFlags[0] if flags&clInCallExpr != 0 { return } if cmdNoArgs = (flags & clCommandWithoutArgs) != 0; cmdNoArgs { flags &^= clCommandWithoutArgs } else { flags |= clIdentCanAutoCall } return } func callCmdNoArgs(ctx *blockCtx, src ast.Node, panicErr bool) (err error) { if gogen.IsFunc(ctx.cb.InternalStack().Get(-1).Type) { if err = ctx.cb.CallWithEx(0, 0, 0, src); err != nil { if panicErr { panic(err) } } } return } // compileExpr compiles expr. // lhs indicates how many values are expected on the left-hand side. func compileExpr(ctx *blockCtx, lhs int, expr ast.Expr, inFlags ...int) { switch v := expr.(type) { case *ast.Ident: flags, cmdNoArgs := identOrSelectorFlags(inFlags) if cmdNoArgs { flags |= clCommandIdent // for support XGo_Exec, see TestSpxXGoExec } _, kind := compileIdent(ctx, lhs, v, flags) if cmdNoArgs || kind == objXGoExecOrEnv { cb := ctx.cb if kind == objXGoExecOrEnv { cb.Val(v.Name, v) } else { err := callCmdNoArgs(ctx, expr, false) if err == nil { return } if !(ctx.isClass && tryXGoExec(cb, v)) { panic(err) } } cb.CallWith(1, 0, 0, v) } case *ast.BasicLit: compileBasicLit(ctx, v) case *ast.CallExpr: flags := 0 if inFlags != nil { flags = inFlags[0] } compileCallExpr(ctx, lhs, v, flags) case *ast.SelectorExpr: flags, cmdNoArgs := identOrSelectorFlags(inFlags) compileSelectorExpr(ctx, lhs, v, flags) if cmdNoArgs { callCmdNoArgs(ctx, expr, true) return } case *ast.BinaryExpr: compileBinaryExpr(ctx, v) case *ast.UnaryExpr: compileUnaryExpr(ctx, lhs, v) case *ast.FuncLit: compileFuncLit(ctx, v) case *ast.CompositeLit: compileCompositeLit(ctx, v, nil, false) case *ast.TupleLit: compileTupleLit(ctx, v, nil) case *ast.SliceLit: compileSliceLit(ctx, v, nil) case *ast.RangeExpr: compileRangeExpr(ctx, v) case *ast.IndexExpr: compileIndexExpr(ctx, lhs, v, inFlags...) case *ast.IndexListExpr: compileIndexListExpr(ctx, lhs, v, inFlags...) case *ast.SliceExpr: compileSliceExpr(ctx, v) case *ast.StarExpr: compileStarExpr(ctx, v) case *ast.ArrayType: ctx.cb.Typ(toArrayType(ctx, v), v) case *ast.MapType: ctx.cb.Typ(toMapType(ctx, v), v) case *ast.StructType: ctx.cb.Typ(toStructType(ctx, v), v) case *ast.ChanType: ctx.cb.Typ(toChanType(ctx, v), v) case *ast.InterfaceType: ctx.cb.Typ(toInterfaceType(ctx, v), v) case *ast.ComprehensionExpr: compileComprehensionExpr(ctx, lhs, v) case *ast.TypeAssertExpr: compileTypeAssertExpr(ctx, lhs, v) case *ast.ParenExpr: compileExpr(ctx, lhs, v.X, inFlags...) case *ast.ErrWrapExpr: compileErrWrapExpr(ctx, lhs, v, 0) case *ast.FuncType: ctx.cb.Typ(toFuncType(ctx, v, nil, nil), v) case *ast.EnvExpr: compileEnvExpr(ctx, lhs, v) /* case *ast.MatrixLit: compileMatrixLit(ctx, v) */ case *ast.DomainTextLit: compileDomainTextLit(ctx, v) case *ast.AnySelectorExpr: compileAnySelectorExpr(ctx, lhs, v) case *ast.CondExpr: compileCondExpr(ctx, v) default: panic(ctx.newCodeErrorf(v.Pos(), v.End(), "compileExpr failed: unknown - %T", v)) } if rec := ctx.recorder(); rec != nil { rec.recordExpr(ctx, expr, false) } } func compileExprOrNone(ctx *blockCtx, expr ast.Expr) { if expr != nil { compileExpr(ctx, 1, expr) } else { ctx.cb.None() } } func compileUnaryExpr(ctx *blockCtx, lhs int, v *ast.UnaryExpr) { compileExpr(ctx, 1, v.X) ctx.cb.UnaryOpEx(gotoken.Token(v.Op), lhs, v) } func compileBinaryExpr(ctx *blockCtx, v *ast.BinaryExpr) { compileExpr(ctx, 1, v.X) compileExpr(ctx, 1, v.Y) ctx.cb.BinaryOp(gotoken.Token(v.Op), v) } func compileIndexExprLHS(ctx *blockCtx, v *ast.IndexExpr) { compileExpr(ctx, 1, v.X) compileExpr(ctx, 1, v.Index) ctx.cb.IndexRef(1, v) } func compileStarExprLHS(ctx *blockCtx, v *ast.StarExpr) { // *x = ... compileExpr(ctx, 1, v.X) ctx.cb.ElemRef() } func compileStarExpr(ctx *blockCtx, v *ast.StarExpr) { // ... = *x compileExpr(ctx, 1, v.X) ctx.cb.Star(v) } func compileTypeAssertExpr(ctx *blockCtx, lhs int, v *ast.TypeAssertExpr) { compileExpr(ctx, 1, v.X) if v.Type == nil { panic("TODO: x.(type) is only used in type switch") } typ := toType(ctx, v.Type) ctx.cb.TypeAssert(typ, lhs, v) } func compileIndexExpr(ctx *blockCtx, lhs int, v *ast.IndexExpr, inFlags ...int) { // x[i] compileExpr(ctx, 1, v.X, inFlags...) compileExpr(ctx, 1, v.Index) ctx.cb.Index(1, lhs, v) } func compileIndexListExpr(ctx *blockCtx, lhs int, v *ast.IndexListExpr, inFlags ...int) { // fn[t1,t2] compileExpr(ctx, 1, v.X, inFlags...) n := len(v.Indices) for i := 0; i < n; i++ { compileExpr(ctx, 1, v.Indices[i]) } ctx.cb.Index(n, lhs, v) } func compileSliceExpr(ctx *blockCtx, v *ast.SliceExpr) { // x[i:j:k] compileExpr(ctx, 1, v.X) compileExprOrNone(ctx, v.Low) compileExprOrNone(ctx, v.High) if v.Slice3 { compileExprOrNone(ctx, v.Max) } ctx.cb.Slice(v.Slice3, v) } func compileSelectorExprLHS(ctx *blockCtx, v *ast.SelectorExpr) { switch x := v.X.(type) { case *ast.Ident: if at, kind := compileIdent(ctx, 1, x, clIdentLHS|clIdentSelectorExpr); kind != objNormal { ctx.cb.VarRef(at.Ref(v.Sel.Name)) return } default: compileExpr(ctx, 1, v.X) } ctx.cb.MemberRef(v.Sel.Name, v) } func compileCondExpr(ctx *blockCtx, v *ast.CondExpr) { const ( nameVal = "_xgo_val" nameErr = "_xgo_err" ) xExpr := v.X condExpr := v.Cond cb := ctx.cb compileExpr(ctx, 1, xExpr) if id, ok := condExpr.(*ast.Ident); ok { name := id.Name switch name[0] { case '"', '`': // @"elem-name" name = unquote(name) } cb.MemberVal("XGo_Select", 0, v).Val(name, id).CallWith(1, 1, 0, v) return } pkg := ctx.pkg x := cb.Get(-1) // x.Type is NodeSet nsType := x.Type pkgTypes := pkg.Types cb.MemberVal("XGo_Enum", 0, xExpr).CallWith(0, 1, 0, xExpr) varSelf := types.NewParam(0, pkgTypes, "self", nsType) yieldParams := types.NewTuple(varSelf) yieldRets := types.NewTuple(types.NewParam(0, nil, "", types.Typ[types.Bool])) sigYield := types.NewSignatureType(nil, nil, nil, yieldParams, yieldRets, false) cb.NewClosureWith(sigYield).BodyStart(pkg, condExpr). If(condExpr) compileExpr(ctx, 1, condExpr) cb.Then(condExpr). If().DefineVarStart(0, nameVal, nameErr). Val(varSelf).MemberVal("XGo_first", 0, v).CallWith(0, 2, 0, v) firstRet := cb.Get(-1) nodeType := firstRet.Type.(*types.Tuple).At(0).Type() varYield := newNodeSeqParam(pkgTypes, nodeType) cb.EndInit(1).VarVal(nameErr).Val(nil).BinaryOp(gotoken.EQL).Then(). If().Val(varYield).VarVal(nameVal).CallWith(1, 1, 0).UnaryOpEx(gotoken.NOT, 1).Then(). Val(false).Return(1). End().End().End(). Val(true).Return(1). End(). // end func CallWith(1, 0, 0, v) // ns.XGo_Enum()(func(self NodeSet) bool { ... }) stk := cb.InternalStack() seq := stk.Pop() sigSeq := types.NewSignatureType(nil, nil, nil, types.NewTuple(varYield), nil, false) cb.Typ(nsType). NewClosureWith(sigSeq).BodyStart(pkg) stk.Push(seq) cb.EndStmt(). End(). // end func CallWith(1, 1, 0, v) } func newNodeSeqParam(pkgTypes *types.Package, nodeType types.Type) *types.Var { yieldParams := types.NewTuple(types.NewParam(0, pkgTypes, "", nodeType)) yieldRets := types.NewTuple(types.NewParam(0, nil, "", types.Typ[types.Bool])) sigYield := types.NewSignatureType(nil, nil, nil, yieldParams, yieldRets, false) return types.NewParam(0, nil, "_xgo_yield", sigYield) } func compileAnySelectorExpr(ctx *blockCtx, lhs int, v *ast.AnySelectorExpr) { compileExpr(ctx, 0, v.X) // DQL (DOM Query Language) rules: // - selector.**.name -> XGo_Any("name") - descendants by name // - selector.**."elem-name" -> XGo_Any("elem-name") - descendants by name // - selector.**.* -> XGo_Any("") - all descendants cb, sel := ctx.cb, v.Sel name := sel.Name switch name[0] { case '"', '`': // ."elem-name" name = unquote(name) case '*': name = "" } convMapToNodeSet(cb) cb.MemberVal("XGo_Any", 0, v).Val(name).CallWith(1, lhs, 0, v) } func checkAnyOrMap(cb *gogen.CodeBuilder) *gogen.Element { e := cb.Get(-1) switch t := types.Unalias(e.Type).(type) { case *types.Interface: if !t.Empty() { return nil } case *types.Map: default: return nil } return e } func convMapToNodeSet(cb *gogen.CodeBuilder) { if e := checkAnyOrMap(cb); e != nil { stk := cb.InternalStack() stk.Pop() cb.Val(cb.Pkg().Import("github.com/goplus/xgo/dql/maps").Ref("New")) stk.Push(e) cb.CallWith(1, 1, 0) } } func compileSelectorExpr(ctx *blockCtx, lhs int, v *ast.SelectorExpr, flags int) { switch x := v.X.(type) { case *ast.Ident: if at, kind := compileIdent(ctx, 1, x, flags|clIdentCanAutoCall|clIdentSelectorExpr); kind != objNormal { if compilePkgRef(ctx, 1, at, v.Sel, flags, kind) { return } if token.IsExported(v.Sel.Name) { panic(ctx.newCodeErrorf(x.Pos(), x.End(), "undefined: %s.%s", x.Name, v.Sel.Name)) } panic(ctx.newCodeErrorf(x.Pos(), x.End(), "cannot refer to unexported name %s.%s", x.Name, v.Sel.Name)) } default: compileExpr(ctx, 1, x) } // DQL (DOM Query Language) rules: // - selector.name -> XGo_Elem("name") - children by name (fallback) // - selector."name" -> XGo_Elem("name") - children by name (fallback) // - selector.$attr -> XGo_Attr("attr") - attribute access // - selector.$"attr" -> XGo_Attr("attr") - attribute access // - selector.* -> XGo_Child() - direct children cb, sel := ctx.cb, v.Sel name := sel.Name switch name[0] { case '*': convMapToNodeSet(cb) cb.MemberVal("XGo_Child", 0, v).CallWith(0, lhs, 0, v) case '$': if err := compileAttr(cb, lhs, name[1:], v); err != nil { panic(err) // throw error } case '"', '`': name = unquote(name) fallthrough default: if err := compileMember(ctx, lhs, v, name, flags); err != nil { if kind, _ := cb.Member("XGo_Elem", 0, 0, v); kind == gogen.MemberInvalid { panic(err) // rethrow original error } cb.Val(name).CallWith(1, lhs, 0, v) } } } func compileAttr(cb *gogen.CodeBuilder, lhs int, name string, v ast.Node) (err error) { switch name[0] { case '"', '`': // @"attr-name" name = unquote(name) } if e := checkAnyOrMap(cb); e != nil { // v.$name => v["name"] as fallback if v is a map or empty interface cb.MemberVal(name, lhs, v) } else if _, err = cb.Member("XGo_Attr", 1, gogen.MemberFlagVal, v); err == nil { cb.Val(name).CallWith(1, lhs, 0, v) } return } func unquote(name string) string { // parser package already checks the syntax of the quoted string, // so we can ignore the error here s, _ := strconv.Unquote(name) return s } func compileFuncAlias(ctx *blockCtx, lhs int, scope *types.Scope, x *ast.Ident, flags int) bool { name := x.Name if c := name[0]; c >= 'a' && c <= 'z' { name = string(rune(c)+('A'-'a')) + name[1:] o := scope.Lookup(name) if o == nil && ctx.loadSymbol(name) { o = scope.Lookup(name) } if o != nil { return identVal(ctx, lhs, x, flags, o, true) } } return false } func pkgRef(at gogen.PkgRef, name string) (o types.Object, alias bool) { if c := name[0]; c >= 'a' && c <= 'z' { name = string(rune(c)+('A'-'a')) + name[1:] if v := at.TryRef(name); v != nil && gogen.IsFunc(v.Type()) { return v, true } return } return at.TryRef(name), false } // allow pkg.Types to be nil func lookupPkgRef(ctx *blockCtx, pkg gogen.PkgRef, x *ast.Ident, pkgKind int) (o types.Object, alias bool) { if pkg.Types != nil { return pkgRef(pkg, x.Name) } if pkgKind == objPkgRef { for _, at := range ctx.lookups { if o2, alias2 := pkgRef(at, x.Name); o2 != nil { if o != nil { panic(ctx.newCodeErrorf( x.Pos(), x.End(), "confliction: %s declared both in \"%s\" and \"%s\"", x.Name, at.Types.Path(), pkg.Types.Path())) } pkg, o, alias = at, o2, alias2 } } } return } // allow at.Types to be nil func compilePkgRef(ctx *blockCtx, lhs int, at gogen.PkgRef, x *ast.Ident, flags, pkgKind int) bool { if v, alias := lookupPkgRef(ctx, at, x, pkgKind); v != nil { if (flags & clIdentLHS) != 0 { if rec := ctx.recorder(); rec != nil { rec.Use(x, v) } ctx.cb.VarRef(v, x) return true } return identVal(ctx, lhs, x, flags, v, alias) } return false } func identVal(ctx *blockCtx, lhs int, x *ast.Ident, flags int, v types.Object, alias bool) bool { autocall := false if alias { if autocall = (flags & clIdentCanAutoCall) != 0; autocall { if !gogen.HasAutoProperty(v.Type()) { return false } } } if rec := ctx.recorder(); rec != nil { rec.Use(x, v) } cb := ctx.cb.Val(v, x) if autocall { cb.CallWith(0, lhs, 0, x) } return true } type fnType struct { next *fnType params *types.Tuple sig *types.Signature base int size int variadic bool typetype bool typeparam bool typeAsParams bool } func (p *fnType) arg(i int, ellipsis bool) types.Type { if i+p.base < p.size { return p.params.At(i + p.base).Type() } if p.variadic { t := p.params.At(p.size).Type() if ellipsis { return t } return t.(*types.Slice).Elem() } return nil } func (p *fnType) init(base int, t *types.Signature, typeAsParams bool) { p.base = base p.sig = t p.typeAsParams = typeAsParams p.params, p.variadic, p.typeparam = t.Params(), t.Variadic(), t.TypeParams() != nil p.size = p.params.Len() if p.variadic { p.size-- } } func (p *fnType) initTypeType(t *gogen.TypeType) { param := types.NewParam(0, nil, "", t.Type()) p.params, p.typetype = types.NewTuple(param), true p.size = 1 } func (p *fnType) unpackTupleLit(cb *gogen.CodeBuilder) bool { return p.size != 1 || !cb.IsTupleType(p.params.At(0).Type()) } func (p *fnType) load(fnt types.Type) { switch v := fnt.(type) { case *gogen.TypeType: p.initTypeType(v) case *types.Signature: typ, objs := gogen.CheckSigFuncExObjects(v) switch typ.(type) { case *gogen.TyOverloadFunc, *gogen.TyOverloadMethod: p.initFuncs(0, objs, false) return case *gogen.TyTemplateRecvMethod: p.initFuncs(1, objs, false) return case *gogen.TyTypeAsParams: p.initFuncs(1, objs, true) return } p.init(0, v, false) } } func (p *fnType) initFuncs(base int, funcs []types.Object, typeAsParams bool) { for i, obj := range funcs { if sig, ok := obj.Type().(*types.Signature); ok { if i == 0 { p.init(base, sig, typeAsParams) } else { fn := &fnType{} fn.init(base, sig, typeAsParams) p.next = fn p = p.next } } } } func compileCallExpr(ctx *blockCtx, lhs int, v *ast.CallExpr, inFlags int) { // If you need to confirm the callExpr format, you can turn on // if !v.NoParenEnd.IsValid() && !v.Rparen.IsValid() { // panic("unexpected invalid Rparen and NoParenEnd in CallExpr") // } var ifn *ast.Ident switch fn := v.Fun.(type) { case *ast.Ident: if v.IsCommand() { // for support XGo_Exec, see TestSpxXGoExec inFlags |= clCommandIdent } if _, kind := compileIdent(ctx, 1, fn, clIdentAllowBuiltin|inFlags); kind == objXGoExec { args := make([]ast.Expr, 1, len(v.Args)+1) args[0] = toBasicLit(fn) args = append(args, v.Args...) v = &ast.CallExpr{Fun: fn, Args: args, Ellipsis: v.Ellipsis, NoParenEnd: v.NoParenEnd} } else { ifn = fn } case *ast.SelectorExpr: compileSelectorExpr(ctx, 1, fn, 0) case *ast.ErrWrapExpr: if v.IsCommand() { callExpr := *v callExpr.Fun = fn.X ewExpr := *fn ewExpr.X = &callExpr compileErrWrapExpr(ctx, 0, &ewExpr, inFlags) return } compileErrWrapExpr(ctx, 1, fn, 0) default: compileExpr(ctx, 1, fn, clInCallExpr) } var err error var stk = ctx.cb.InternalStack() var base = stk.Len() var flags gogen.InstrFlags var ellipsis = v.Ellipsis != token.NoPos if ellipsis { flags = gogen.InstrFlagEllipsis } pfn := stk.Get(-1) fnt := pfn.Type fn := &fnType{} fn.load(fnt) if len(v.Kwargs) > 0 { // https://github.com/goplus/xgo/issues/2443 n := len(v.Args) args := make([]ast.Expr, n+1) if fn.variadic { // has variadic parameter idx := fn.size - 1 if idx < 0 { panic(ctx.newCodeError(v.Pos(), v.End(), msgNoKwargsOVF)) } if len(v.Args) < idx { panic(ctx.newCodeError(v.Pos(), v.End(), msgNoEnoughArgToKwargs)) } copy(args, v.Args[:idx]) args[idx] = mergeKwargs(ctx, v, fn.params.At(idx).Type()) copy(args[idx+1:], v.Args[idx:]) } else { copy(args, v.Args) args[n] = mergeKwargs(ctx, v, fn.arg(n, false)) } ne := *v ne.Args, ne.Kwargs = args, nil v = &ne } for fn != nil { if err = compileCallArgs(ctx, lhs, pfn, fn, v, ellipsis, flags); err == nil { if rec := ctx.recorder(); rec != nil { rec.recordCallExpr(ctx, v, fnt) } return } stk.SetLen(base) fn = fn.next } if ifn != nil && builtinOrXGoExec(ctx, lhs, ifn, v, flags) == nil { return } panic(err) } const ( msgNoKwargsOVF = "keyword arguments are not supported for a function with only variadic parameters" msgNoEnoughArgToKwargs = "not enough arguments for function call with keyword arguments" msgUnexpectedKwargs = "keyword arguments can only be used for struct or map[string]T types, but got %v" ) func inThisPkg(ctx *blockCtx, t types.Type) bool { if named, ok := t.(*types.Named); ok { if named.Obj().Pkg() == ctx.pkg.Types { return true } } return false } func mergeKwargs(ctx *blockCtx, v *ast.CallExpr, t types.Type) ast.Expr { if t != nil { switch u := t.Underlying().(type) { case *types.Pointer: t = u.Elem() if u, ok := t.Underlying().(*types.Struct); ok { return mergeStructKwargs(v.Kwargs, u, inThisPkg(ctx, t)) } panic(ctx.newCodeErrorf(v.Pos(), v.End(), msgUnexpectedKwargs, t)) case *types.Struct: return mergeStructKwargs(v.Kwargs, u, inThisPkg(ctx, t)) } } return mergeStringMapKwargs(v.Kwargs) // fallback to map[string]T } func mergeStringMapKwargs(kwargs []*ast.KwargExpr) ast.Expr { n := len(kwargs) elts := make([]ast.Expr, n) for i, arg := range kwargs { elts[i] = &ast.KeyValueExpr{ Key: toBasicLit(arg.Name), Value: arg.Value, } } return &ast.CompositeLit{ Lbrace: kwargs[0].Pos() - 1, Elts: elts, Rbrace: kwargs[n-1].End(), } } func mergeStructKwargs(kwargs []*ast.KwargExpr, u *types.Struct, inPkg bool) ast.Expr { n := len(kwargs) elts := make([]ast.Expr, n) for i, arg := range kwargs { elts[i] = &ast.KeyValueExpr{ Key: getFldName(arg.Name, u, inPkg), Value: arg.Value, } } return &ast.CompositeLit{ Lbrace: kwargs[0].Pos() - 1, Elts: elts, Rbrace: kwargs[n-1].End(), } } func getFldName(name *ast.Ident, u *types.Struct, inPkg bool) *ast.Ident { if name.IsExported() { return name } capName := stringutil.Capitalize(name.Name) if !inPkg { return &ast.Ident{NamePos: name.NamePos, Name: capName} } for i, n := 0, u.NumFields(); i < n; i++ { fld := u.Field(i) if fld.Name() == name.Name { return name } if fld.Exported() && fld.Name() == capName { return &ast.Ident{NamePos: name.NamePos, Name: capName} } } return name // fallback to origin name } func toBasicLit(fn *ast.Ident) *ast.BasicLit { return &ast.BasicLit{ValuePos: fn.NamePos, Kind: token.STRING, Value: strconv.Quote(fn.Name)} } // maybe builtin new/delete: see TestSpxNewObj, TestMayBuiltinDelete // maybe XGo_Exec: see TestSpxXGoExec func builtinOrXGoExec(ctx *blockCtx, lhs int, ifn *ast.Ident, v *ast.CallExpr, flags gogen.InstrFlags) error { cb := ctx.cb switch name := ifn.Name; name { case "new", "delete": cb.InternalStack().PopN(1) cb.Val(ctx.pkg.Builtin().Ref(name), ifn) return fnCall(ctx, lhs, v, flags, 0) default: // for support XGo_Exec, see TestSpxXGoExec if v.IsCommand() && ctx.isClass && tryXGoExec(cb, ifn) { return fnCall(ctx, lhs, v, flags, 1) } } return syscall.ENOENT } func tryXGoExec(cb *gogen.CodeBuilder, ifn *ast.Ident) bool { if recv := classRecv(cb); recv != nil { cb.InternalStack().PopN(1) if xgoOp(cb, recv, "XGo_Exec", "Gop_Exec", ifn) == nil { cb.Val(ifn.Name, ifn) return true } } return false } func fnCall(ctx *blockCtx, lhs int, v *ast.CallExpr, flags gogen.InstrFlags, extra int) error { for _, arg := range v.Args { compileExpr(ctx, 1, arg) } return ctx.cb.CallWithEx(len(v.Args)+extra, lhs, flags, v) } func compileCallArgs(ctx *blockCtx, lhs int, pfn *gogen.Element, fn *fnType, v *ast.CallExpr, ellipsis bool, flags gogen.InstrFlags) (err error) { defer func() { r := recover() if r != nil { err = ctx.recoverErr(r, v) } }() cb := ctx.cb vargs := v.Args if len(vargs) == 1 && !ellipsis { if tupleLit, ok := vargs[0].(*ast.TupleLit); ok { isEll := tupleLit.Ellipsis != token.NoPos if isEll || fn.unpackTupleLit(cb) { vargs, ellipsis = tupleLit.Elts, isEll } } } vargsOrg := vargs if fn.typeAsParams && fn.typeparam { n := fn.sig.TypeParams().Len() for i := 0; i < n; i++ { compileExpr(ctx, 1, vargs[i]) } args := cb.InternalStack().GetArgs(n) var targs []types.Type for i, arg := range args { typ := arg.Type t, ok := typ.(*gogen.TypeType) if !ok { return ctx.newCodeErrorf(vargs[i].Pos(), vargs[i].End(), "%v not type", ctx.LoadExpr(vargs[i])) } targs = append(targs, t.Type()) } ret, err := types.Instantiate(nil, fn.sig, targs, true) if err != nil { return ctx.newCodeError(v.Pos(), v.End(), err.Error()) } fn.init(1, ret.(*types.Signature), false) vargs = vargs[n:] } var needInferFunc bool for i, arg := range vargs { t := fn.arg(i, ellipsis) switch expr := arg.(type) { case *ast.LambdaExpr: if fn.typeparam { needInferFunc = true compileIdent(ctx, 0, ast.NewIdent("nil"), 0) // TODO(xsw): check lhs continue } sig, e := checkLambdaFuncType(ctx, expr, t, clLambaArgument, v.Fun) if e != nil { return e } if err = compileLambdaExpr(ctx, expr, sig); err != nil { return } case *ast.LambdaExpr2: if fn.typeparam { needInferFunc = true compileIdent(ctx, 0, ast.NewIdent("nil"), 0) // TODO(xsw): check lhs continue } sig, e := checkLambdaFuncType(ctx, expr, t, clLambaArgument, v.Fun) if e != nil { return e } if err = compileLambdaExpr2(ctx, expr, sig); err != nil { return } case *ast.CompositeLit: if err = compileCompositeLitEx(ctx, expr, t, true); err != nil { return } case *ast.TupleLit: if err = compileTupleLit(ctx, expr, t, true); err != nil { return } case *ast.SliceLit: switch t.(type) { case *types.Slice: case *types.Named: if _, ok := getUnderlying(ctx, t).(*types.Slice); !ok { t = nil } default: t = nil } typetype := fn.typetype && t != nil if typetype { cb.InternalStack().PopN(1) } if err = compileSliceLit(ctx, expr, t, true); err != nil { return } if typetype { return } case *ast.NumberUnitLit: compileNumberUnitLit(ctx, expr, t) default: compileExpr(ctx, 1, arg) if sigParamLen(t) == 0 { if nonClosure(cb.Get(-1).Type) { cb.ConvertToClosure() } } } } if needInferFunc { args := cb.InternalStack().GetArgs(len(vargsOrg)) typ, err := gogen.InferFunc(ctx.pkg, pfn, fn.sig, nil, args, flags) if err != nil { return err } next := &fnType{} next.init(fn.base, typ.(*types.Signature), false) next.next = fn.next fn.next = next return errCallNext } return cb.CallWithEx(len(vargsOrg), lhs, flags, v) } var ( errCallNext = errors.New("call next") ) type clLambaFlag string const ( clLambaAssign clLambaFlag = "assignment" clLambaField clLambaFlag = "field value" clLambaArgument clLambaFlag = "argument" ) // check lambda func type func checkLambdaFuncType(ctx *blockCtx, lambda ast.Expr, ftyp types.Type, flag clLambaFlag, toNode ast.Node) (*types.Signature, error) { typ := ftyp retry: switch t := typ.(type) { case *types.Signature: if l, ok := lambda.(*ast.LambdaExpr); ok { if len(l.Rhs) != t.Results().Len() { break } } return t, nil case *types.Named: typ = t.Underlying() goto retry } var to string if toNode != nil { to = " to " + ctx.LoadExpr(toNode) } return nil, ctx.newCodeErrorf(lambda.Pos(), lambda.End(), "cannot use lambda literal as type %v in %v%v", ftyp, flag, to) } func sigParamLen(typ types.Type) int { retry: switch t := typ.(type) { case *types.Signature: return t.Params().Len() case *types.Named: typ = t.Underlying() goto retry } return -1 } func nonClosure(typ types.Type) bool { retry: switch t := typ.(type) { case *types.Signature: return false case *types.Basic: if t.Kind() == types.UntypedNil { return false } case *types.Named: typ = t.Underlying() goto retry } return true } func compileLambda(ctx *blockCtx, lambda ast.Expr, sig *types.Signature) { switch expr := lambda.(type) { case *ast.LambdaExpr2: if err := compileLambdaExpr2(ctx, expr, sig); err != nil { panic(err) } case *ast.LambdaExpr: if err := compileLambdaExpr(ctx, expr, sig); err != nil { panic(err) } } } func makeLambdaParams(ctx *blockCtx, pos, end token.Pos, lhs []*ast.Ident, in *types.Tuple) (*types.Tuple, error) { pkg := ctx.pkg n := len(lhs) if nin := in.Len(); n != nin { fewOrMany := "few" if n > nin { fewOrMany = "many" } has := make([]string, n) for i, v := range lhs { has[i] = v.Name } return nil, ctx.newCodeErrorf( pos, end, "too %s arguments in lambda expression\n\thave (%s)\n\twant %v", fewOrMany, strings.Join(has, ", "), in) } if n == 0 { return nil, nil } params := make([]*types.Var, n) for i, name := range lhs { param := pkg.NewParam(name.Pos(), name.Name, in.At(i).Type()) params[i] = param if rec := ctx.recorder(); rec != nil { rec.Def(name, param) } } return types.NewTuple(params...), nil } func makeLambdaResults(pkg *gogen.Package, out *types.Tuple) *types.Tuple { nout := out.Len() if nout == 0 { return nil } results := make([]*types.Var, nout) for i := 0; i < nout; i++ { results[i] = pkg.NewParam(token.NoPos, "", out.At(i).Type()) } return types.NewTuple(results...) } func compileLambdaExpr(ctx *blockCtx, v *ast.LambdaExpr, sig *types.Signature) error { pkg := ctx.pkg params, err := makeLambdaParams(ctx, v.Pos(), v.End(), v.Lhs, sig.Params()) if err != nil { return err } results := makeLambdaResults(pkg, sig.Results()) ctx.cb.NewClosure(params, results, false).BodyStart(pkg) if len(v.Lhs) > 0 { defNames(ctx, v.Lhs, ctx.cb.Scope()) } for _, v := range v.Rhs { compileExpr(ctx, 1, v) } if rec := ctx.recorder(); rec != nil { rec.Scope(v, ctx.cb.Scope()) } ctx.cb.Return(len(v.Rhs)).End(v) return nil } func compileLambdaExpr2(ctx *blockCtx, v *ast.LambdaExpr2, sig *types.Signature) error { pkg := ctx.pkg params, err := makeLambdaParams(ctx, v.Pos(), v.End(), v.Lhs, sig.Params()) if err != nil { return err } results := makeLambdaResults(pkg, sig.Results()) comments, once := ctx.cb.BackupComments() fn := ctx.cb.NewClosure(params, results, false) cb := fn.BodyStart(ctx.pkg, v.Body) if len(v.Lhs) > 0 { defNames(ctx, v.Lhs, cb.Scope()) } compileStmts(ctx, v.Body.List) if rec := ctx.recorder(); rec != nil { rec.Scope(v, ctx.cb.Scope()) } cb.End(v) ctx.cb.SetComments(comments, once) return nil } func compileFuncLit(ctx *blockCtx, v *ast.FuncLit) { cb := ctx.cb comments, once := cb.BackupComments() sig := toFuncType(ctx, v.Type, nil, nil) if rec := ctx.recorder(); rec != nil { rec.recordFuncLit(v, sig) } fn := cb.NewClosureWith(sig) if body := v.Body; body != nil { loadFuncBody(ctx, fn, body, nil, v, false) cb.SetComments(comments, once) } } func compileNumberUnitLit(ctx *blockCtx, v *ast.NumberUnitLit, expected types.Type) { ctx.cb.ValWithUnit( &goast.BasicLit{ValuePos: v.ValuePos, Kind: gotoken.Token(v.Kind), Value: v.Value}, expected, v.Unit) } func compileBasicLit(ctx *blockCtx, v *ast.BasicLit) { cb := ctx.cb switch kind := v.Kind; kind { case token.RAT: val := v.Value bi, _ := new(big.Int).SetString(val[:len(val)-1], 10) // remove r suffix cb.UntypedBigInt(bi, v) case token.CSTRING, token.PYSTRING: s, err := strconv.Unquote(v.Value) if err != nil { log.Panicln("compileBasicLit:", err) } var xstr gogen.Ref switch kind { case token.CSTRING: xstr = ctx.cstr() default: xstr = ctx.pystr() } cb.Val(xstr).Val(s).Call(1) default: if v.Extra == nil { basicLit(cb, v) return } compileStringLitEx(ctx, cb, v) } } func invalidVal(cb *gogen.CodeBuilder) { cb.Val(&gogen.Element{Type: types.Typ[types.Invalid]}) } func basicLit(cb *gogen.CodeBuilder, v *ast.BasicLit) { cb.Val(&goast.BasicLit{Kind: gotoken.Token(v.Kind), Value: v.Value}, v) } const ( stringutilPkgPath = "github.com/qiniu/x/stringutil" ) func compileStringLitEx(ctx *blockCtx, cb *gogen.CodeBuilder, lit *ast.BasicLit) { pos := lit.ValuePos + 1 quote := lit.Value[:1] parts := lit.Extra.Parts n := len(parts) if n != 1 { cb.Val(ctx.pkg.Import(stringutilPkgPath).Ref("Concat")) } for _, part := range parts { switch v := part.(type) { case string: // normal string literal or end with "$$" next := pos + token.Pos(len(v)) if strings.HasSuffix(v, "$$") { v = v[:len(v)-1] } basicLit(cb, &ast.BasicLit{ValuePos: pos - 1, Value: quote + v + quote, Kind: token.STRING}) pos = next case ast.Expr: flags := 0 if _, ok := v.(*ast.Ident); ok { flags = clIdentInStringLitEx } compileExpr(ctx, 1, v, flags) t := cb.Get(-1).Type if t.Underlying() != types.Typ[types.String] { if _, err := cb.Member("string", 0, gogen.MemberFlagAutoProperty); err != nil { if kind, _ := cb.Member("error", 0, gogen.MemberFlagAutoProperty); kind == gogen.MemberInvalid { if e, ok := err.(*gogen.CodeError); ok { err = ctx.newCodeErrorf(v.Pos(), v.End(), "%s.string%s", ctx.LoadExpr(v), e.Msg) } ctx.handleErr(err) } } } pos = v.End() default: panic("compileStringLitEx TODO: unexpected part") } } if n != 1 { cb.CallWith(n, 0, 0, lit) } } const ( tplPkgPath = "github.com/goplus/xgo/tpl" encodingPkgPrefix = "github.com/goplus/xgo/encoding/" ) // A DomainTextLit node represents a domain-specific text literal. // https://github.com/goplus/xgo/issues/2143 // // domainTag`...` // domainTag`> arg1, arg2, ... // ... // ` func compileDomainTextLit(ctx *blockCtx, v *ast.DomainTextLit) { var cb = ctx.cb var imp gogen.PkgRef var name = v.Domain.Name var path string if pi, ok := ctx.findImport(name); ok { imp = pi.PkgRef path = pi.Path() } else { if name == "tpl" { path = tplPkgPath } else { path = encodingPkgPrefix + name } imp = ctx.pkg.Import(path) /* TODO(xsw): if imp = ctx.pkg.TryImport(path); imp.Types == nil { panic("compileDomainTextLit TODO: unknown domain: " + name) } */ } n := 1 if path == tplPkgPath { pos := ctx.fset.Position(v.ValuePos) filename := relFile(ctx.relBaseDir, pos.Filename) cb.Val(imp.Ref("NewEx")). Val(&goast.BasicLit{Kind: gotoken.STRING, Value: v.Value}, v). Val(filename).Val(pos.Line).Val(pos.Column) n += 3 if f, ok := v.Extra.(*tpl.File); ok { decls := f.Decls for _, decl := range decls { if r, ok := decl.(*tpl.Rule); ok { if expr, ok := r.RetProc.(*ast.LambdaExpr2); ok { cb.Val(r.Name.Name) sig := sigRetFunc(ctx.pkg, r.IsList()) compileLambdaExpr2(ctx, lambdaRetFunc(expr), sig) n += 2 } } } } } else { cb.Val(imp.Ref("New")) if lit, ok := v.Extra.(*ast.DomainTextLitEx); ok { cb.Val(lit.Raw) for _, arg := range lit.Args { compileExpr(ctx, 1, arg) } n += len(lit.Args) } else { cb.Val(&goast.BasicLit{Kind: gotoken.STRING, Value: v.Value}, v) } } cb.CallWith(n, 0, 0, v) } func lambdaRetFunc(expr *ast.LambdaExpr2) *ast.LambdaExpr2 { v := *expr v.Lhs = []*ast.Ident{ {NamePos: expr.Pos(), Name: "self"}, } return &v } func sigRetFunc(pkg *gogen.Package, isList bool) *types.Signature { rets := types.NewTuple(anyParam(pkg)) var args *types.Tuple if isList { args = types.NewTuple(anySliceParam(pkg)) } else { args = rets } return types.NewSignatureType(nil, nil, nil, args, rets, false) } func anyParam(pkg *gogen.Package) *types.Var { return pkg.NewParam(token.NoPos, "", gogen.TyEmptyInterface) } func anySliceParam(pkg *gogen.Package) *types.Var { return pkg.NewParam(token.NoPos, "", types.NewSlice(gogen.TyEmptyInterface)) } const ( compositeLitVal = 0 compositeLitKeyVal = 1 ) func checkCompositeLitElts(elts []ast.Expr) (kind int) { for _, elt := range elts { if _, ok := elt.(*ast.KeyValueExpr); ok { return compositeLitKeyVal } } return compositeLitVal } func compileCompositeLitElts(ctx *blockCtx, elts []ast.Expr, kind int, expected *kvType) error { for _, elt := range elts { if kv, ok := elt.(*ast.KeyValueExpr); ok { if key, ok := kv.Key.(*ast.CompositeLit); ok && key.Type == nil { compileCompositeLit(ctx, key, expected.Key(), false) } else { compileExpr(ctx, 1, kv.Key) } err := compileCompositeLitElt(ctx, kv.Value, expected.Elem(), clLambaAssign, kv.Key) if err != nil { return err } } else { if kind == compositeLitKeyVal { ctx.cb.None() } err := compileCompositeLitElt(ctx, elt, expected.Elem(), clLambaAssign, nil) if err != nil { return err } } } return nil } func compileCompositeLitElt(ctx *blockCtx, e ast.Expr, typ types.Type, flag clLambaFlag, toNode ast.Node) error { switch v := unparen(e).(type) { case *ast.LambdaExpr, *ast.LambdaExpr2: sig, err := checkLambdaFuncType(ctx, v, typ, flag, toNode) if err != nil { return err } compileLambda(ctx, v, sig) case *ast.TupleLit: compileTupleLit(ctx, v, typ) case *ast.SliceLit: compileSliceLit(ctx, v, typ) case *ast.CompositeLit: compileCompositeLit(ctx, v, typ, false) default: compileExpr(ctx, 1, v) } return nil } func unparen(x ast.Expr) ast.Expr { if e, ok := x.(*ast.ParenExpr); ok { return e.X } return x } func compileStructLit(ctx *blockCtx, elts []ast.Expr, t *types.Struct, typ types.Type, src *ast.CompositeLit) error { for idx, elt := range elts { if idx >= t.NumFields() { return ctx.newCodeErrorf(elt.Pos(), elt.End(), "too many values in %v{...}", typ) } err := compileCompositeLitElt(ctx, elt, t.Field(idx).Type(), clLambaField, nil) if err != nil { return err } } ctx.cb.StructLit(typ, len(elts), false, src) return nil } func compileStructLitInKeyVal(ctx *blockCtx, elts []ast.Expr, t *types.Struct, typ types.Type, src *ast.CompositeLit) error { cb := ctx.cb for _, elt := range elts { kv := elt.(*ast.KeyValueExpr) name := kv.Key.(*ast.Ident) idx := cb.LookupField(t, name.Name) if idx >= 0 { cb.Val(idx, src) } else { src := ctx.LoadExpr(name) return ctx.newCodeErrorf(name.Pos(), name.End(), "%s undefined (type %v has no field or method %s)", src, typ, name.Name) } if rec := ctx.recorder(); rec != nil { rec.Use(name, t.Field(idx)) } err := compileCompositeLitElt(ctx, kv.Value, t.Field(idx).Type(), clLambaField, kv.Key) if err != nil { return err } } cb.StructLit(typ, len(elts)<<1, true, src) return nil } type kvType struct { underlying types.Type key, val types.Type cached bool } func (p *kvType) required() *kvType { if !p.cached { p.cached = true switch t := p.underlying.(type) { case *types.Slice: p.key, p.val = types.Typ[types.Int], t.Elem() case *types.Array: p.key, p.val = types.Typ[types.Int], t.Elem() case *types.Map: p.key, p.val = t.Key(), t.Elem() } } return p } func (p *kvType) Key() types.Type { return p.required().key } func (p *kvType) Elem() types.Type { return p.required().val } func getUnderlying(ctx *blockCtx, typ types.Type) types.Type { u := typ.Underlying() if u == nil { if t, ok := typ.(*types.Named); ok { ctx.loadNamed(ctx.pkg, t) u = t.Underlying() } } return u } func compileCompositeLit(ctx *blockCtx, v *ast.CompositeLit, expected types.Type, mapOrStructOnly bool) { if err := compileCompositeLitEx(ctx, v, expected, mapOrStructOnly); err != nil { panic(err) } } // mapOrStructOnly means only map/struct can omit type func compileCompositeLitEx(ctx *blockCtx, v *ast.CompositeLit, expected types.Type, mapOrStructOnly bool) error { var hasPtr bool var typ, underlying types.Type var kind = checkCompositeLitElts(v.Elts) if v.Type != nil { typ = toType(ctx, v.Type) underlying = getUnderlying(ctx, typ) // Auto-reference typed composite literal when expected type is pointer if expected != nil { if t, ok := expected.(*types.Pointer); ok { telem := t.Elem() if types.Identical(typ, telem) { hasPtr = true } } } } else if expected != nil { if t, ok := expected.(*types.Pointer); ok { telem := t.Elem() tu := getUnderlying(ctx, telem) if _, ok := tu.(*types.Struct); ok { // struct pointer typ, underlying, hasPtr = telem, tu, true } } else if tu := getUnderlying(ctx, expected); !mapOrStructOnly || isMapOrStruct(tu) { typ, underlying = expected, tu } } if t, ok := underlying.(*types.Struct); ok { var err error if kind == compositeLitKeyVal { err = compileStructLitInKeyVal(ctx, v.Elts, t, typ, v) } else { err = compileStructLit(ctx, v.Elts, t, typ, v) } if err != nil { return err } } else { err := compileCompositeLitElts(ctx, v.Elts, kind, &kvType{underlying: underlying}) if err != nil { return err } n := len(v.Elts) switch underlying.(type) { case *types.Slice: ctx.cb.SliceLitEx(typ, n< 0 { return ctx.newCodeError(v.Pos(), v.End(), "missing key in map literal") } if err := compileMapLitEx(ctx, typ, n, v); err != nil { return err } default: if kind == compositeLitVal && n > 0 { return ctx.newCodeErrorf(v.Pos(), v.End(), "invalid composite literal type %v", typ) } if err := compileMapLitEx(ctx, nil, n, v); err != nil { return err } } } if hasPtr { ctx.cb.UnaryOp(gotoken.AND) typ = expected } if rec := ctx.recorder(); rec != nil { rec.recordCompositeLit(v, typ) } return nil } func compileMapLitEx(ctx *blockCtx, typ types.Type, n int, v *ast.CompositeLit) (err error) { defer func() { if e := recover(); e != nil { err = ctx.newCodeError(v.Pos(), v.End(), "invalid map literal") } }() err = ctx.cb.MapLitEx(typ, n<<1, v) return } func isMapOrStruct(tu types.Type) bool { switch tu.(type) { case *types.Struct: return true case *types.Map: return true } return false } func compileSliceLit(ctx *blockCtx, v *ast.SliceLit, typ types.Type, noPanic ...bool) (err error) { if noPanic != nil { defer func() { if e := recover(); e != nil { // TODO: don't use defer to capture error err = ctx.recoverErr(e, v) } }() } n := len(v.Elts) for _, elt := range v.Elts { compileExpr(ctx, 1, elt) } if isSpecificSliceType(ctx, typ) { ctx.cb.SliceLitEx(typ, n, false, v) } else { ctx.cb.SliceLitEx(nil, n, false, v) } return } func compileTupleLit(ctx *blockCtx, v *ast.TupleLit, typ types.Type, noPanic ...bool) (err error) { if noPanic != nil { defer func() { if e := recover(); e != nil { // TODO: don't use defer to capture error err = ctx.recoverErr(e, v) } }() } n := len(v.Elts) for _, elt := range v.Elts { compileExpr(ctx, 1, elt) } ctx.cb.TupleLit(typ, n, v) return } func compileRangeExpr(ctx *blockCtx, v *ast.RangeExpr) { pkg, cb := ctx.pkg, ctx.cb cb.Val(pkg.Builtin().Ref("newRange")) if v.First == nil { ctx.cb.Val(0, v) } else { compileExpr(ctx, 1, v.First) } compileExpr(ctx, 1, v.Last) if v.Expr3 == nil { ctx.cb.Val(1, v) } else { compileExpr(ctx, 1, v.Expr3) } cb.Call(3) } const ( comprehensionInvalid = iota comprehensionList comprehensionMap comprehensionSelect ) func comprehensionKind(v *ast.ComprehensionExpr) int { switch v.Tok { case token.LBRACK: // [ return comprehensionList case token.LBRACE: // { if _, ok := v.Elt.(*ast.KeyValueExpr); ok { return comprehensionMap } return comprehensionSelect } panic("TODO: invalid comprehensionExpr") } // [expr for k, v in container, cond] // {for k, v in container, cond} // {expr for k, in container, cond} // {kexpr: vexpr for k, v in container, cond} func compileComprehensionExpr(ctx *blockCtx, lhs int, v *ast.ComprehensionExpr) { const ( nameOk = "_xgo_ok" nameRet = "_xgo_ret" ) kind := comprehensionKind(v) pkg, cb := ctx.pkg, ctx.cb var results *types.Tuple var ret *gogen.Param if v.Elt == nil { boolean := pkg.NewParam(token.NoPos, nameOk, types.Typ[types.Bool]) results = types.NewTuple(boolean) } else { ret = pkg.NewAutoParam(nameRet) if kind == comprehensionSelect && lhs == 2 { boolean := pkg.NewParam(token.NoPos, nameOk, types.Typ[types.Bool]) results = types.NewTuple(ret, boolean) } else { results = types.NewTuple(ret) } } cb.NewClosure(nil, results, false).BodyStart(pkg) if kind == comprehensionMap { cb.VarRef(ret).ZeroLit(ret.Type()).Assign(1) } end := 0 for i := len(v.Fors) - 1; i >= 0; i-- { names := make([]string, 0, 2) defineNames := make([]*ast.Ident, 0, 2) forStmt := v.Fors[i] if forStmt.Key != nil { names = append(names, forStmt.Key.Name) defineNames = append(defineNames, forStmt.Key) } else { names = append(names, "_") } names = append(names, forStmt.Value.Name) defineNames = append(defineNames, forStmt.Value) cb.ForRange(names...) compileExpr(ctx, 1, forStmt.X) cb.RangeAssignThen(forStmt.TokPos) defNames(ctx, defineNames, cb.Scope()) if rec := ctx.recorder(); rec != nil { rec.Scope(forStmt, cb.Scope()) } if forStmt.Cond != nil { cb.If() if forStmt.Init != nil { compileStmt(ctx, forStmt.Init) } compileExpr(ctx, 1, forStmt.Cond) cb.Then() end++ } end++ } switch kind { case comprehensionList: // _xgo_ret = append(_xgo_ret, elt) cb.VarRef(ret) cb.Val(pkg.Builtin().Ref("append")) cb.Val(ret) compileExpr(ctx, 1, v.Elt) cb.Call(2).Assign(1) case comprehensionMap: // _xgo_ret[key] = val cb.Val(ret) kv := v.Elt.(*ast.KeyValueExpr) compileExpr(ctx, 1, kv.Key) cb.IndexRef(1) compileExpr(ctx, 1, kv.Value) cb.Assign(1) default: if v.Elt == nil { // return true cb.Val(true) cb.Return(1) } else { // return elt, true compileExpr(ctx, 1, v.Elt) n := 1 if lhs == 2 { cb.Val(true) n++ } cb.Return(n) } } for i := 0; i < end; i++ { cb.End() } cb.Return(0).End().Call(0) } const ( errorPkgPath = "github.com/qiniu/x/errors" ) var ( tyError = types.Universe.Lookup("error").Type() ) func compileErrWrapExpr(ctx *blockCtx, lhs int, v *ast.ErrWrapExpr, inFlags int) { const ( nameErr = "_xgo_err" nameRet = "_xgo_ret" ) pkg, cb := ctx.pkg, ctx.cb useClosure := v.Tok == token.NOT || v.Default != nil if !useClosure && (cb.Scope().Parent() == types.Universe) { panic("TODO: can't use expr? in global") } if lhs != 0 { // lhs == 0 means the result is discarded // +1 accounts for the error value that will be stripped from the result tuple lhs++ } compileExpr(ctx, lhs, v.X, inFlags) x := cb.InternalStack().Pop() n := 0 results, ok := x.Type.(*types.Tuple) if ok { n = results.Len() - 1 } var ret []*types.Var if n > 0 { i, retName := 0, nameRet ret = make([]*gogen.Param, n) for { ret[i] = pkg.NewAutoParam(retName) i++ if i >= n { break } retName = nameRet + strconv.Itoa(i+1) } } sig := types.NewSignatureType(nil, nil, nil, nil, types.NewTuple(ret...), false) if useClosure { cb.NewClosureWith(sig).BodyStart(pkg) } else { cb.CallInlineClosureStart(sig, 0, false) } cb.NewVar(tyError, nameErr) err := cb.Scope().Lookup(nameErr) for _, retVar := range ret { cb.VarRef(retVar) } cb.VarRef(err) cb.InternalStack().Push(x) cb.Assign(n+1, 1) cb.If().Val(err).CompareNil(gotoken.NEQ).Then() if v.Default == nil { pos := pkg.Fset.Position(v.Pos()) curFn := cb.Func().Ancestor() curFnName := curFn.Name() if curFnName == "" { curFnName = "main" } cb.VarRef(err). Val(pkg.Import(errorPkgPath).Ref("NewFrame")). Val(err). Val(sprintAst(pkg.Fset, v.X)). Val(relFile(ctx.relBaseDir, pos.Filename)). Val(pos.Line). Val(curFn.Pkg().Name() + "." + curFnName). Call(5). Assign(1) } if v.Tok == token.NOT { // expr! cb.Val(pkg.Builtin().Ref("panic")).Val(err).Call(1).EndStmt() } else if v.Default == nil { // expr? cb.Val(err).ReturnErr(true) } else { // expr?:val compileExpr(ctx, 1, v.Default) cb.Return(1) } cb.End().Return(0).End() if useClosure { cb.Call(0) } } func sprintAst(fset *token.FileSet, x ast.Node) string { var buf bytes.Buffer err := printer.Fprint(&buf, fset, x) if err != nil { panic("Unexpected error: " + err.Error()) } return buf.String() } // ----------------------------------------------------------------------------- ================================================ FILE: cl/func_type_and_var.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cl import ( "go/constant" "go/types" "log" "math/big" "strconv" "strings" "github.com/goplus/gogen" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/token" ) // ----------------------------------------------------------------------------- func toRecv(ctx *blockCtx, recv *ast.FieldList) *types.Var { v := recv.List[0] var name string if len(v.Names) > 0 { name = v.Names[0].Name } typ, star, _ := getRecvType(v.Type) id, ok := typ.(*ast.Ident) if !ok { panic("TODO: getRecvType") } t := toIdentType(ctx, id) if star { t = types.NewPointer(t) } ret := ctx.pkg.NewParam(v.Pos(), name, t) if rec := ctx.recorder(); rec != nil { dRecv := recv.List[0] if names := dRecv.Names; len(names) == 1 { rec.Def(names[0], ret) } } return ret } func getRecvTypeName(ctx *pkgCtx, recv *ast.FieldList, handleErr bool) (string, bool) { typ, _, _ := getRecvType(recv.List[0].Type) if t, ok := typ.(*ast.Ident); ok { return t.Name, true } if handleErr { src := ctx.LoadExpr(typ) ctx.handleErrorf(typ.Pos(), typ.End(), "invalid receiver type %v (%v is not a defined type)", src, src) } return "", false } func toResults(ctx *blockCtx, in *ast.FieldList) *types.Tuple { if in == nil { return nil } flds := in.List n := len(flds) args := make([]*types.Var, 0, n) for _, fld := range flds { args = toParam(ctx, fld, args) } return types.NewTuple(args...) } func toParams(ctx *blockCtx, flds []*ast.Field) (typ *types.Tuple, variadic bool) { n := len(flds) if n == 0 { return nil, false } args := make([]*types.Var, 0, n) for _, fld := range flds { args = toParam(ctx, fld, args) } _, ok := flds[n-1].Type.(*ast.Ellipsis) return types.NewTuple(args...), ok } func toParam(ctx *blockCtx, fld *ast.Field, args []*gogen.Param) []*gogen.Param { typ := toType(ctx, fld.Type) pkg := ctx.pkg isOptional := fld.Optional.IsValid() if len(fld.Names) == 0 { return append(args, pkg.NewParamEx(fld.Pos(), "", typ, isOptional)) } for _, name := range fld.Names { param := pkg.NewParamEx(name.Pos(), name.Name, typ, isOptional) args = append(args, param) if rec := ctx.recorder(); rec != nil { rec.Def(name, param) } } return args } // ----------------------------------------------------------------------------- func toType(ctx *blockCtx, typ ast.Expr) (t types.Type) { if rec := ctx.recorder(); rec != nil { defer func() { rec.recordType(typ, t) }() } switch v := typ.(type) { case *ast.Ident: ctx.idents = append(ctx.idents, v) defer func() { ctx.idents = ctx.idents[:len(ctx.idents)-1] }() typ := toIdentType(ctx, v) if ctx.inInst == 0 { if t, ok := typ.(*types.Named); ok { if namedIsTypeParams(ctx, t) { pos := ctx.idents[0].Pos() end := ctx.idents[0].End() for _, i := range ctx.idents { if i.Name == v.Name { pos = i.Pos() end = i.End() break } } ctx.handleErrorf(pos, end, "cannot use generic type %v without instantiation", t.Obj().Type()) return types.Typ[types.Invalid] } } } return typ case *ast.StarExpr: elem := toType(ctx, v.X) return types.NewPointer(elem) case *ast.ArrayType: return toArrayType(ctx, v) case *ast.InterfaceType: return toInterfaceType(ctx, v) case *ast.Ellipsis: elem := toType(ctx, v.Elt) return types.NewSlice(elem) case *ast.MapType: return toMapType(ctx, v) case *ast.TupleType: return toTupleType(ctx, v) case *ast.StructType: return toStructType(ctx, v) case *ast.ChanType: return toChanType(ctx, v) case *ast.FuncType: return toFuncType(ctx, v, nil, nil) case *ast.SelectorExpr: typ := toExternalType(ctx, v) if ctx.inInst == 0 { if t, ok := typ.(*types.Named); ok { if namedIsTypeParams(ctx, t) { panic(ctx.newCodeErrorf(v.Pos(), v.End(), "cannot use generic type %v without instantiation", t.Obj().Type())) } } } return typ case *ast.ParenExpr: return toType(ctx, v.X) case *ast.BinaryExpr: return toBinaryExprType(ctx, v) case *ast.UnaryExpr: return toUnaryExprType(ctx, v) case *ast.IndexExpr: return toIndexType(ctx, v) case *ast.IndexListExpr: return toIndexListType(ctx, v) default: ctx.handleErrorf(v.Pos(), v.End(), "toType unexpected: %T", v) return types.Typ[types.Invalid] } } var ( typesChanDirs = [...]types.ChanDir{ ast.RECV: types.RecvOnly, ast.SEND: types.SendOnly, ast.SEND | ast.RECV: types.SendRecv, } ) func toChanType(ctx *blockCtx, v *ast.ChanType) *types.Chan { return types.NewChan(typesChanDirs[v.Dir], toType(ctx, v.Value)) } func toExternalType(ctx *blockCtx, v *ast.SelectorExpr) types.Type { id := v.X.(*ast.Ident) name := id.Name if pi, ok := ctx.findImport(name); ok { rec := ctx.recorder() if rec != nil { rec.Use(id, pi.pkgName) } o := pi.TryRef(v.Sel.Name) if t, ok := o.(*types.TypeName); ok { if rec != nil { rec.Use(v.Sel, t) } return t.Type() } ctx.handleErrorf(v.Pos(), v.End(), "%s.%s is not a type", name, v.Sel.Name) } else { ctx.handleErrorf(v.Pos(), v.End(), "undefined: %s", name) } return types.Typ[types.Invalid] } /*----------------------------------------------------------------------------- Name context: - type - pkgRef.type - spx.type // ---------------------------------------------------------------------------*/ func toIdentType(ctx *blockCtx, ident *ast.Ident) (ret types.Type) { var obj types.Object if rec := ctx.recorder(); rec != nil { defer func() { if obj != nil { rec.recordIdent(ident, obj) } }() } if ctx.tlookup != nil { if typ := ctx.tlookup.Lookup(ident.Name); typ != nil { obj = typ.Obj() return typ } } v, builtin := lookupType(ctx, ident.Name) if isBuiltin(builtin) { ctx.handleErrorf(ident.Pos(), ident.End(), "use of builtin %s not in function call", ident.Name) return types.Typ[types.Invalid] } if t, ok := v.(*types.TypeName); ok { obj = t return t.Type() } if v, _ := lookupPkgRef(ctx, gogen.PkgRef{}, ident, objPkgRef); v != nil { if t, ok := v.(*types.TypeName); ok { obj = t return t.Type() } } ctx.handleErrorf(ident.Pos(), ident.End(), "%s is not a type", ident.Name) return types.Typ[types.Invalid] } // TODO: optimization func lookupType(ctx *blockCtx, name string) (types.Object, types.Object) { at, o := ctx.cb.Scope().LookupParent(name, token.NoPos) if o != nil && at != types.Universe { if debugLookup { log.Println("==> LookupParent", name, "=>", o) } return o, nil } if ctx.loadSymbol(name) { if v := ctx.pkg.Types.Scope().Lookup(name); v != nil { if debugLookup { log.Println("==> Lookup (LoadSymbol)", name, "=>", v) } return v, nil } } if obj := ctx.pkg.Builtin().TryRef(name); obj != nil { return obj, o } return o, o } type fieldKind int const ( fieldKindUser fieldKind = iota fieldKindClass ) type fieldElem struct { pos token.Pos end token.Pos kind fieldKind } type checkRedecl struct { names map[string]fieldElem } func newCheckRedecl() *checkRedecl { return &checkRedecl{names: make(map[string]fieldElem)} } func (p *checkRedecl) chkRedecl(ctx *blockCtx, name string, pos, end token.Pos, kind fieldKind) bool { if name == "_" { return false } if existing, ok := p.names[name]; ok { switch existing.kind { case fieldKindClass: ctx.handleErrorf( pos, end, "%s conflicts with class name.\n\trename the field to resolve the naming conflict.", name) case fieldKindUser: ctx.handleErrorf( pos, end, "%v redeclared\n\t%v other declaration of %v", name, ctx.Position(existing.pos), name) } return true } p.names[name] = fieldElem{ pos: pos, end: end, kind: kind, } return false } // toTupleType converts an AST TupleType node to a types.Struct. // Tuple types are syntactic sugar for structs with ordinal field names (_0, _1, ...). // Named fields in the tuple are compile-time aliases converted to ordinal fields. func toTupleType(ctx *blockCtx, v *ast.TupleType) types.Type { fieldList := v.Fields.List switch len(fieldList) { case 0: return types.NewStruct(nil, nil) case 1: // single-field tuple is equivalent to the field type itself if len(fieldList[0].Names) <= 1 { return toType(ctx, fieldList[0].Type) } } pkg := ctx.pkg pkgTypes := pkg.Types fields := make([]*types.Var, 0, len(fieldList)) chk := newCheckRedecl() rec := ctx.recorder() namedCount := 0 for _, field := range fieldList { fieldType := field.Type typ := toType(ctx, fieldType) if len(field.Names) == 0 { fld := types.NewField(fieldType.Pos(), pkgTypes, "", typ, true) fields = append(fields, fld) continue } for _, id := range field.Names { name := id.Name if name != "" { namedCount++ if chk.chkRedecl(ctx, name, id.Pos(), id.End(), fieldKindUser) { continue } if name == "_" { name = "" } } fld := types.NewField(id.NamePos, pkgTypes, name, typ, false) fields = append(fields, fld) if rec != nil { rec.Def(id, fld) } } } withName := namedCount == len(fields) return pkg.NewTuple(withName, fields...) } func toStructType(ctx *blockCtx, v *ast.StructType) *types.Struct { pkg := ctx.pkg.Types fieldList := v.Fields.List fields := make([]*types.Var, 0, len(fieldList)) tags := make([]string, 0, len(fieldList)) chk := newCheckRedecl() rec := ctx.recorder() for _, field := range fieldList { // Struct Tags (#2488): Check before calling toType to // avoid "_ is not a type" error if len(field.Names) == 0 && field.Tag != nil { if ident, ok := field.Type.(*ast.Ident); ok && ident.Name == "_" { emptyStruct := types.NewStruct(nil, nil) fld := types.NewField(ident.NamePos, pkg, "_", emptyStruct, false) fields = append(fields, fld) tags = append(tags, toFieldTag(field.Tag)) if rec != nil { rec.Def(ident, fld) } continue } } typ := toType(ctx, field.Type) if len(field.Names) == 0 { // embedded name := getTypeName(typ) if chk.chkRedecl(ctx, name, field.Type.Pos(), field.Type.End(), fieldKindUser) { continue } if t, ok := typ.(*types.Named); ok { // #1196: embedded type should ensure loaded ctx.loadNamed(ctx.pkg, t) } ident := parseTypeEmbedName(field.Type) fld := types.NewField(ident.NamePos, pkg, name, typ, true) fields = append(fields, fld) tags = append(tags, toFieldTag(field.Tag)) if rec != nil { rec.Def(ident, fld) } continue } for _, name := range field.Names { if chk.chkRedecl(ctx, name.Name, name.Pos(), name.End(), fieldKindUser) { continue } fld := types.NewField(name.NamePos, pkg, name.Name, typ, false) fields = append(fields, fld) tags = append(tags, toFieldTag(field.Tag)) if rec != nil { rec.Def(name, fld) } } } return types.NewStruct(fields, tags) } func toFieldTag(v *ast.BasicLit) string { if v != nil { data := v.Value if len(data) > 0 && data[0] == '"' && noTagKey(data) { return "_:" + data } tag, err := strconv.Unquote(data) if err != nil { log.Panicln("TODO: toFieldTag -", err) } return tag } return "" } func noTagKey(data string) bool { pos := strings.IndexByte(data, ':') if pos < 0 { return true } return strings.IndexByte(data[:pos], ' ') >= 0 } func getTypeName(typ types.Type) string { if t, ok := typ.(*types.Pointer); ok { typ = t.Elem() } switch t := typ.(type) { case *types.Named: return t.Obj().Name() case *types.Basic: return t.Name() default: panic("TODO: getTypeName") } } func toMapType(ctx *blockCtx, v *ast.MapType) *types.Map { key := toType(ctx, v.Key) val := toType(ctx, v.Value) return types.NewMap(key, val) } func toArrayType(ctx *blockCtx, v *ast.ArrayType) types.Type { elem := toType(ctx, v.Elt) if v.Len == nil { return types.NewSlice(elem) } if _, ok := v.Len.(*ast.Ellipsis); ok { return types.NewArray(elem, -1) // A negative length indicates an unknown length } return types.NewArray(elem, toInt64(ctx, v.Len, "non-constant array bound %s")) } func toInt64(ctx *blockCtx, e ast.Expr, emsg string) int64 { cb := ctx.pkg.ConstStart() compileExpr(ctx, 1, e) tv := cb.EndConst() if val := tv.CVal; val != nil { if val.Kind() == constant.Float { if v, ok := constant.Val(val).(*big.Rat); ok && v.IsInt() { return v.Num().Int64() } } else if v, ok := constant.Int64Val(val); ok { return v } } src := ctx.LoadExpr(e) panic(ctx.newCodeErrorf(e.Pos(), e.End(), emsg, src)) } func toInterfaceType(ctx *blockCtx, v *ast.InterfaceType) types.Type { methodsList := v.Methods.List if methodsList == nil { return types.NewInterfaceType(nil, nil) } var rec = ctx.recorder() var pkg = ctx.pkg.Types var methods []*types.Func var embeddeds []types.Type for _, m := range methodsList { if len(m.Names) == 0 { // embedded typ := toType(ctx, m.Type) if t, ok := typ.(*types.Named); ok { // #1198: embedded type should ensure loaded ctx.loadNamed(ctx.pkg, t) } embeddeds = append(embeddeds, typ) continue } name := m.Names[0] sig := toFuncType(ctx, m.Type.(*ast.FuncType), nil, nil) mthd := types.NewFunc(name.NamePos, pkg, name.Name, sig) methods = append(methods, mthd) if rec != nil { rec.Def(name, mthd) } } intf := types.NewInterfaceType(methods, embeddeds).Complete() return intf } func instantiate(ctx *blockCtx, exprX ast.Expr, indices ...ast.Expr) types.Type { ctx.inInst++ defer func() { ctx.inInst-- }() x := toType(ctx, exprX) idx := make([]types.Type, len(indices)) for i, index := range indices { idx[i] = toType(ctx, index) } typ := ctx.pkg.Instantiate(x, idx, exprX) if rec := ctx.recorder(); rec != nil { rec.instantiate(exprX, x, typ) } return typ } func toIndexType(ctx *blockCtx, v *ast.IndexExpr) types.Type { return instantiate(ctx, v.X, v.Index) } func toIndexListType(ctx *blockCtx, v *ast.IndexListExpr) types.Type { return instantiate(ctx, v.X, v.Indices...) } // ----------------------------------------------------------------------------- func toString(l *ast.BasicLit) string { if l.Kind == token.STRING { s, err := strconv.Unquote(l.Value) if err == nil { return s } } panic("TODO: toString - convert ast.BasicLit to string failed") } // ----------------------------------------------------------------------------- ================================================ FILE: cl/internal/.gitignore ================================================ .gop/ .xgo/ go.mod gop_autogen*.go xgo_autogen*.go ================================================ FILE: cl/internal/dql/dql.go ================================================ package dql import "iter" const ( XGoPackage = true ) type Node struct { } type NodeSet struct { } func New() NodeSet { return NodeSet{} } // NodeSet(seq func(func(*Node) bool)) func NodeSet_Cast(func(yield func(*Node) bool)) NodeSet { return NodeSet{} } // XGo_Enum returns an iterator over the nodes in the NodeSet. func (p NodeSet) XGo_Enum() iter.Seq[NodeSet] { return nil } // XGo_Any returns a NodeSet containing all descendant nodes with the specified name. func (p NodeSet) XGo_Any(name string) NodeSet { return NodeSet{} } func (p NodeSet) XGo_Select(name string) NodeSet { return NodeSet{} } // XGo_Child returns a NodeSet containing all child nodes of the nodes in the NodeSet. func (p NodeSet) XGo_Child() NodeSet { return NodeSet{} } // XGo_first returns the first node in the NodeSet, or an error if the NodeSet is empty. func (p NodeSet) XGo_first() (*Node, error) { return nil, nil } // XGo_Elem returns a NodeSet containing the child nodes with the specified name. func (p NodeSet) XGo_Elem(name string) NodeSet { return NodeSet{} } func (p NodeSet) XGo_Attr__0(name string) int { return 0 } func (p NodeSet) XGo_Attr__1(name string) (int, error) { return 0, nil } type NodeSet2 struct { } func New2() NodeSet2 { return NodeSet2{} } func NodeSet2_Cast(func(yield func(*Node) bool)) NodeSet2 { return NodeSet2{} } func (p NodeSet2) XGo_first() *Node { return nil } func (p NodeSet2) XGo_Enum() iter.Seq[NodeSet2] { return nil } func (p NodeSet2) XGo_Elem(name string) NodeSet2 { return NodeSet2{} } func (p NodeSet2) XGo_Attr(name string) int { return 0 } ================================================ FILE: cl/internal/gop-in-go/foo/foo.xgo ================================================ package foo func ReverseMap(m map[string]int) map[int]string { return {v: k for k, v <- m} } ================================================ FILE: cl/internal/gop-in-go/foo/foo_test.xgo ================================================ package foo import ( "testing" ) func TestReverseMap(t *testing.T) { out := ReverseMap({"a": 1}) if len(out) != 1 || out[1] != "a" { t.Fatal("ReverseMap failed:", out) } } ================================================ FILE: cl/internal/gop-in-go/foo/footest_test.xgo ================================================ package foo_test import ( "testing" "github.com/goplus/xgo/cl/internal/gop-in-go/foo" ) func TestReverseMap(t *testing.T) { out := foo.ReverseMap({"b": 2}) if len(out) != 1 || out[2] != "b" { t.Fatal("ReverseMap failed:", out) } } ================================================ FILE: cl/internal/huh/huh.go ================================================ package huh // ----------------------------------------------------------------------------- type Form int func (f Form) Run() { } func New(string, string, int) Form { return 0 } // ----------------------------------------------------------------------------- ================================================ FILE: cl/internal/llgo-hello/hello.go ================================================ package hello import "github.com/goplus/lib/c" func Main() { c.Printf(c.Str("Hello world\n")) } ================================================ FILE: cl/internal/mcp/classfile.go ================================================ package mcp const ( GopPackage = true ) type Game struct { } func New() *Game { return nil } func (p *Game) initGame() {} func (p *Game) Server(name string) {} type Tool struct { } func (p *Tool) Main(name string) int { return 0 } type Prompt struct { } func (p *Prompt) Main(*Tool) string { return "" } type Resource struct { } func (p *Resource) Main() { } type ToolProto interface { Main(name string) int } type PromptProto interface { Main(*Tool) string } type ResourceProto interface { Main() } func Gopt_Game_Main(game interface{ initGame() }, resources []ResourceProto, tools []ToolProto, prompts []PromptProto) { } ================================================ FILE: cl/internal/overload/bar/bar.go ================================================ package bar const GopPackage = true type M = map[string]any type basetype interface { string | int | bool | float64 } type Var__0[T basetype] struct { val T } func (p *Var__0[T]) Value() T { return p.val } type Var__1[T map[string]any] struct { val T } func (p *Var__1[T]) Value() T { return p.val } func XGox_Var_Cast__0[T basetype]() *Var__0[T] { return new(Var__0[T]) } func XGox_Var_Cast__1[T map[string]any]() *Var__1[T] { return new(Var__1[T]) } type Player struct { } func XGot_Player_XGox_OnCmd__0[T any](p *Player, handler func(cmd T) error) { var t T handler(t) } func XGot_Player_XGox_OnCmd__1[T1 ~int, T2 any](p *Player, n T1, handler func(n T1, cmd T2) error) { var t T2 handler(n, t) } ================================================ FILE: cl/internal/overload/foo/foo.go ================================================ package foo const GopPackage = true type Mesher interface { Name() string } type N struct { } func (m *N) OnKey__0(a string, fn func()) { } func (m *N) OnKey__1(a string, fn func(key string)) { } func (m *N) OnKey__2(a []string, fn func()) { } func (m *N) OnKey__3(a []string, fn func(key string)) { } func (m *N) OnKey__4(a []Mesher, fn func()) { } func (m *N) OnKey__5(a []Mesher, fn func(key Mesher)) { } func (m *N) OnKey__6(a []string, b []string, fn func(key string)) { } func (m *N) OnKey__7(a []string, b []Mesher, fn func(key string)) { } func (m *N) OnKey__8(x int, y int) { } func OnKey__0(a string, fn func()) { } func OnKey__1(a string, fn func(key string)) { } func OnKey__2(a []string, fn func()) { } func OnKey__3(a []string, fn func(key string)) { } func OnKey__4(a []Mesher, fn func()) { } func OnKey__5(a []Mesher, fn func(key Mesher)) { } func OnKey__6(a []string, b []string, fn func(key string)) { } func OnKey__7(a []string, b []Mesher, fn func(key string)) { } func OnKey__8(x int, y int) { } func Test__0() { } func Test__1[N any](n N) { } func Test__2[N1, N2 any](n1 N1, n2 N2) { } ================================================ FILE: cl/internal/spx/game.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package spx const ( GopPackage = "github.com/goplus/xgo/cl/internal/spx/pkg" Gop_sched = "Sched,SchedNow" ) type Sound string type MyGame struct { } func Gopt_MyGame_Main(game any) { } func (p *MyGame) Ls(n int) {} func (p *MyGame) Capout(doSth func()) (string, error) { return "", nil } func (p *MyGame) Gop_Env(name string) int { return 0 } func (p *MyGame) Gop_Exec(name string, args ...any) { } func (p *MyGame) InitGameApp(args ...string) { } func (p *MyGame) Broadcast__0(msg string) { } func (p *MyGame) Broadcast__1(msg string, wait bool) { } func (p *MyGame) Broadcast__2(msg string, data any, wait bool) { } func (p *MyGame) Play(media string, wait ...bool) { } func (p *MyGame) sendMessage(data any) { } func (p *MyGame) SendMessage(data any) { p.sendMessage(data) } func Gopt_MyGame_Run(game any, resource string) error { return nil } func Sched() { } func SchedNow() { } func Rand__0(int) int { return 0 } func Rand__1(float64) float64 { return 0 } var ( TestIntValue int ) ================================================ FILE: cl/internal/spx/pkg/pkg.go ================================================ package pkg const ( GopPackage = true ) type Vector struct { X int Y int } func NewVector(x, y int) *Vector { return &Vector{x, y} } func (v *Vector) Add__0(x int, y int) { v.X += x v.Y += y } func (v *Vector) Add__1(o *Vector) { v.Add__0(o.X, o.Y) } func (v *Vector) Self() *Vector { return v } ================================================ FILE: cl/internal/spx/sprite.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package spx import ( "github.com/goplus/xgo/cl/internal/spx/pkg" ) type Sprite struct { pos pkg.Vector Entry } type Entry struct { vec pkg.Vector } func (p *Entry) Vector() *pkg.Vector { return &p.vec } func (p *Sprite) SetCostume(costume any) { } func (p *Sprite) Say(msg string, secs ...float64) { } func (p *Sprite) Position() *pkg.Vector { return &p.pos } type Mesher interface { Name() string } func Gopt_Sprite_Clone__0(sprite any) { } func Gopt_Sprite_Clone__1(sprite any, data any) { } func Gopt_Sprite_OnKey__0(sprite any, a string, fn func()) { } func Gopt_Sprite_OnKey__1(sprite any, a string, fn func(key string)) { } func Gopt_Sprite_OnKey__2(sprite any, a []string, fn func()) { } func Gopt_Sprite_OnKey__3(sprite any, a []string, fn func(key string)) { } func Gopt_Sprite_OnKey__4(sprite any, a []Mesher, fn func()) { } func Gopt_Sprite_OnKey__5(sprite any, a []Mesher, fn func(key Mesher)) { } func Gopt_Sprite_OnKey__6(sprite any, a []string, b []string, fn func(key string)) { } func Gopt_Sprite_OnKey__7(sprite any, a []string, b []Mesher, fn func(key string)) { } func Gopt_Sprite_OnKey__8(sprite any, x int, y int) { } func Gopt_Sprite_OnKey2(sprite any, a string, fn func(key string)) { } ================================================ FILE: cl/internal/spx2/spx2.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package spx2 const ( Gop_sched = "Sched" Gop_work = "Sprite" ) type Game struct { } func (p *Game) Main() { } type Sprite struct { } func Sched() { } ================================================ FILE: cl/internal/spx3/jwt/jwt.go ================================================ /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jwt func Token(v string) string { return "token: " + v } ================================================ FILE: cl/internal/spx3/spx3.go ================================================ /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package spx3 const ( GopPackage = true ) type Game struct { } func New() *Game { return nil } func (p *Game) initGame() {} func (p *Game) Run() {} type Sprite struct { } func (p *Sprite) Name() string { return "sprite" } func (p *Sprite) Main(name string) {} type Handler interface { Main(name string) Classfname() string Classclone() Handler } func Gopt_Game_Main(game interface{ initGame() }, workers ...Handler) { } ================================================ FILE: cl/internal/spx4/game.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package spx4 const ( GopPackage = "github.com/goplus/xgo/cl/internal/spx/pkg" Gop_sched = "Sched,SchedNow" ) type Sound string type MyGame struct { } func Gopt_MyGame_Main(game any, sprites ...Sprite) { } func (p *MyGame) Ls(n int) {} func (p *MyGame) Capout(doSth func()) (string, error) { return "", nil } func (p *MyGame) Gop_Env(name string) int { return 0 } func (p *MyGame) Gop_Exec(name string, args ...any) { } func (p *MyGame) InitGameApp(args ...string) { } func (p *MyGame) Broadcast__0(msg string) { } func (p *MyGame) Broadcast__1(msg string, wait bool) { } func (p *MyGame) Broadcast__2(msg string, data any, wait bool) { } func (p *MyGame) BackdropName() string { return "" } func (p *MyGame) Play(media string, wait ...bool) { } func (p *MyGame) sendMessage(data any) { } func (p *MyGame) SendMessage(data any) { p.sendMessage(data) } func Gopt_MyGame_Run(game any, resource string) error { return nil } func Sched() { } func SchedNow() { } func Rand__0(int) int { return 0 } func Rand__1(float64) float64 { return 0 } var ( TestIntValue int ) ================================================ FILE: cl/internal/spx4/pkg/pkg.go ================================================ package pkg const ( GopPackage = true ) type Vector struct { X int Y int } func NewVector(x, y int) *Vector { return &Vector{x, y} } func (v *Vector) Add__0(x int, y int) { v.X += x v.Y += y } func (v *Vector) Add__1(o *Vector) { v.Add__0(o.X, o.Y) } func (v *Vector) Self() *Vector { return v } ================================================ FILE: cl/internal/spx4/sprite.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package spx4 import ( "github.com/goplus/xgo/cl/internal/spx/pkg" ) type Sprite struct { pos pkg.Vector Entry } type Entry struct { vec pkg.Vector } func (p *Entry) Vector() *pkg.Vector { return &p.vec } func (p *Sprite) SetCostume(costume any) { } func (p *Sprite) Say(msg string, secs ...float64) { } func (p *Sprite) Position() *pkg.Vector { return &p.pos } type Mesher interface { Name() string } func Gopt_Sprite_Clone__0(sprite any) { } func Gopt_Sprite_Clone__1(sprite any, data any) { } func Gopt_Sprite_OnKey__0(sprite any, a string, fn func()) { } func Gopt_Sprite_OnKey__1(sprite any, a string, fn func(key string)) { } func Gopt_Sprite_OnKey__2(sprite any, a []string, fn func()) { } func Gopt_Sprite_OnKey__3(sprite any, a []string, fn func(key string)) { } func Gopt_Sprite_OnKey__4(sprite any, a []Mesher, fn func()) { } func Gopt_Sprite_OnKey__5(sprite any, a []Mesher, fn func(key Mesher)) { } func Gopt_Sprite_OnKey__6(sprite any, a []string, b []string, fn func(key string)) { } func Gopt_Sprite_OnKey__7(sprite any, a []string, b []Mesher, fn func(key string)) { } func Gopt_Sprite_OnKey__8(sprite any, x int, y int) { } func Gopt_Sprite_OnKey2(sprite any, a string, fn func(key string)) { } ================================================ FILE: cl/internal/test/case.go ================================================ /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package test import ( "testing" "time" ) // ----------------------------------------------------------------------------- type CaseT interface { // Name returns the name of the running (sub-) test or benchmark. // // The name will include the name of the test along with the names of // any nested sub-tests. If two sibling sub-tests have the same name, // Name will append a suffix to guarantee the returned name is unique. Name() string // Fail marks the function as having failed but continues execution. Fail() // Failed reports whether the function has failed. Failed() bool // FailNow marks the function as having failed and stops its execution // by calling runtime.Goexit (which then runs all deferred calls in the // current goroutine). // Execution will continue at the next test or benchmark. // FailNow must be called from the goroutine running the // test or benchmark function, not from other goroutines // created during the test. Calling FailNow does not stop // those other goroutines. FailNow() // Log formats its arguments using default formatting, analogous to Println, // and records the text in the error log. For tests, the text will be printed only if // the test fails or the -test.v flag is set. For benchmarks, the text is always // printed to avoid having performance depend on the value of the -test.v flag. Log(args ...any) // Logf formats its arguments according to the format, analogous to Printf, and // records the text in the error log. A final newline is added if not provided. For // tests, the text will be printed only if the test fails or the -test.v flag is // set. For benchmarks, the text is always printed to avoid having performance // depend on the value of the -test.v flag. Logf(format string, args ...any) // Errorln is equivalent to Log followed by Fail. Errorln(args ...any) // Errorf is equivalent to Logf followed by Fail. Errorf(format string, args ...any) // Fatal is equivalent to Log followed by FailNow. Fatal(args ...any) // Fatalf is equivalent to Logf followed by FailNow. Fatalf(format string, args ...any) // Skip is equivalent to Log followed by SkipNow. Skip(args ...any) // Skipf is equivalent to Logf followed by SkipNow. Skipf(format string, args ...any) // SkipNow marks the test as having been skipped and stops its execution // by calling runtime.Goexit. // If a test fails (see Error, Errorf, Fail) and is then skipped, // it is still considered to have failed. // Execution will continue at the next test or benchmark. See also FailNow. // SkipNow must be called from the goroutine running the test, not from // other goroutines created during the test. Calling SkipNow does not stop // those other goroutines. SkipNow() // Skipped reports whether the test was skipped. Skipped() bool // Helper marks the calling function as a test helper function. // When printing file and line information, that function will be skipped. // Helper may be called simultaneously from multiple goroutines. Helper() // Cleanup registers a function to be called when the test (or subtest) and all its // subtests complete. Cleanup functions will be called in last added, // first called order. Cleanup(f func()) // TempDir returns a temporary directory for the test to use. // The directory is automatically removed by Cleanup when the test and // all its subtests complete. // Each subsequent call to t.TempDir returns a unique directory; // if the directory creation fails, TempDir terminates the test by calling Fatal. TempDir() string // Run runs f as a subtest of t called name. // // Run may be called simultaneously from multiple goroutines, but all such calls // must return before the outer test function for t returns. Run(name string, f func()) bool // Deadline reports the time at which the test binary will have // exceeded the timeout specified by the -timeout flag. // // The ok result is false if the -timeout flag indicates “no timeout” (0). Deadline() (deadline time.Time, ok bool) } // ----------------------------------------------------------------------------- type TestingT struct { *testing.T } // NewT creates a testing object. func NewT(t *testing.T) TestingT { return TestingT{t} } // Errorln is equivalent to Log followed by Fail. func (p TestingT) Errorln(args ...any) { t := p.T t.Helper() t.Error(args...) } // Run runs f as a subtest of t called name. // // Run may be called simultaneously from multiple goroutines, but all such calls // must return before the outer test function for t returns. func (p TestingT) Run(name string, doSth func()) bool { return p.T.Run(name, func(t *testing.T) { doSth() }) } // ----------------------------------------------------------------------------- ================================================ FILE: cl/internal/test/match.go ================================================ /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package test const ( GopPackage = true ) type basetype interface { string | int | bool | float64 } type Case struct { CaseT } const ( XGoo_Gopt_Case_Match = "Gopt_Case_MatchTBase,Gopt_Case_MatchAny" ) func Gopt_Case_MatchTBase[T basetype](t CaseT, got, expected T, name ...string) { } func Gopt_Case_MatchAny(t CaseT, got, expected any, name ...string) { } ================================================ FILE: cl/internal/testutil/testutil.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package testutil type Options struct { Loop bool Async bool } ================================================ FILE: cl/internal/typesutil/api.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package typesutil import ( "go/constant" "go/types" "unsafe" "github.com/goplus/gogen" ) // An OperandMode specifies the (addressing) mode of an operand. type OperandMode byte // TypeAndValue reports the type and value (for constants) // of the corresponding expression. type TypeAndValue struct { mode OperandMode Type types.Type Value constant.Value } func NewTypeAndValueForType(typ types.Type) (ret types.TypeAndValue) { switch t := typ.(type) { case *gogen.TypeType: typ = t.Type() } ret.Type = typ (*TypeAndValue)(unsafe.Pointer(&ret)).mode = TypExpr return } func NewTypeAndValueForValue(typ types.Type, val constant.Value, mode OperandMode) (ret types.TypeAndValue) { switch t := typ.(type) { case *gogen.TypeType: typ = t.Type() } if val != nil { mode = Constant } ret.Type = typ ret.Value = val (*TypeAndValue)(unsafe.Pointer(&ret)).mode = mode return } func NewTypeAndValueForCallResult(typ types.Type, val constant.Value) (ret types.TypeAndValue) { var mode OperandMode if typ == nil { ret.Type = &types.Tuple{} mode = NoValue } else { ret.Type = typ if val != nil { ret.Value = val mode = Constant } else { mode = Value } } (*TypeAndValue)(unsafe.Pointer(&ret)).mode = mode return } func NewTypeAndValueForObject(obj types.Object) (ret types.TypeAndValue) { var mode OperandMode var val constant.Value switch v := obj.(type) { case *types.Const: mode = Constant val = v.Val() case *types.TypeName: mode = TypExpr case *types.Var: mode = Variable case *types.Func: mode = Value case *types.Builtin: mode = Builtin case *types.Nil: mode = Value } ret.Type = obj.Type() ret.Value = val (*TypeAndValue)(unsafe.Pointer(&ret)).mode = mode return } ================================================ FILE: cl/internal/typesutil/api_test.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package typesutil import ( "go/constant" "go/types" "testing" "github.com/goplus/gogen" ) func TestTypeAndValue(t *testing.T) { tyInt := types.Typ[types.Int] ty := gogen.NewTypeType(tyInt) ret := NewTypeAndValueForType(ty) if !ret.IsType() { t.Fatal("NewTypeAndValueForType: not type?") } ret = NewTypeAndValueForValue(tyInt, constant.MakeInt64(1), Constant) if ret.Value == nil { t.Fatal("NewTypeAndValueForValue: not const?") } ret = NewTypeAndValueForValue(ty, constant.MakeInt64(1), Constant) if ret.Value == nil { t.Fatal("NewTypeAndValueForValue: not const?") } ret = NewTypeAndValueForCallResult(tyInt, nil) if !ret.IsValue() { t.Fatal("NewTypeAndValueForCall: not value?") } ret = NewTypeAndValueForCallResult(tyInt, constant.MakeInt64(1)) if !ret.IsValue() || ret.Value == nil { t.Fatal("NewTypeAndValueForCall: not const?") } ret = NewTypeAndValueForCallResult(nil, nil) if !ret.IsVoid() { t.Fatal("NewTypeAndValueForCall: not void?") } pkg := types.NewPackage("main", "main") ret = NewTypeAndValueForObject(types.NewConst(0, pkg, "v", tyInt, constant.MakeInt64(100))) if ret.Value == nil { t.Fatal("NewTypeAndValueForObject: not const?") } ret = NewTypeAndValueForObject(types.NewTypeName(0, pkg, "MyInt", tyInt)) if !ret.IsType() { t.Fatal("NewTypeAndValueForObject: not type?") } ret = NewTypeAndValueForObject(types.NewVar(0, pkg, "v", tyInt)) if !ret.Addressable() { t.Fatal("NewTypeAndValueForObject: not variable?") } ret = NewTypeAndValueForObject(types.NewFunc(0, pkg, "fn", types.NewSignature(nil, nil, nil, false))) if !ret.IsValue() { t.Fatal("NewTypeAndValueForObject: not value?") } ret = NewTypeAndValueForValue(types.Typ[types.UntypedNil], nil, Value) if !ret.IsNil() { t.Fatal("NewTypeAndValueForValue: not nil?") } ret = NewTypeAndValueForObject(types.Universe.Lookup("nil")) if !ret.IsNil() { t.Fatal("NewTypeAndValueForObject: not nil?") } ret = NewTypeAndValueForObject(types.Universe.Lookup("len")) if !ret.IsBuiltin() { t.Fatal("NewTypeAndValueForObject: not builtin?") } } ================================================ FILE: cl/internal/typesutil/mode.go ================================================ //go:build !go1.23 // +build !go1.23 /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package typesutil const ( Invalid OperandMode = iota // operand is invalid NoValue // operand represents no value (result of a function call w/o result) Builtin // operand is a built-in function TypExpr // operand is a type Constant // operand is a constant; the operand's typ is a Basic type Variable // operand is an addressable variable MapIndex // operand is a map index expression (acts like a variable on lhs, commaok on rhs of an assignment) Value // operand is a computed value CommaOK // like value, but operand may be used in a comma,ok expression CommaErr // like commaok, but second value is error, not boolean CgoFunc // operand is a cgo function ) ================================================ FILE: cl/internal/typesutil/mode_go123.go ================================================ //go:build go1.23 // +build go1.23 /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package typesutil const ( Invalid OperandMode = iota // operand is invalid NoValue // operand represents no value (result of a function call w/o result) Builtin // operand is a built-in function TypExpr // operand is a type Constant // operand is a constant; the operand's typ is a Basic type Variable // operand is an addressable variable MapIndex // operand is a map index expression (acts like a variable on lhs, commaok on rhs of an assignment) Value // operand is a computed value NilValue // operand is the nil value - only used by types2 CommaOK // like value, but operand may be used in a comma,ok expression CommaErr // like commaok, but second value is error, not boolean CgoFunc // operand is a cgo function ) ================================================ FILE: cl/internal/unit/unit.go ================================================ package unit // ----------------------------------------------------------------------------- type Distance int const XGou_Distance = "mm=1,cm=10,dm=100,m=1000" // ----------------------------------------------------------------------------- ================================================ FILE: cl/outline/outline.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package outline import ( "go/types" "strings" "github.com/goplus/gogen" "github.com/goplus/gogen/typeutil" "github.com/goplus/mod/modfile" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/token" ) // ----------------------------------------------------------------------------- type Project = modfile.Project type Config struct { // Fset provides source position information for syntax trees and types. // If Fset is nil, Load will use a new fileset, but preserve Fset's value. Fset *token.FileSet // LookupClass lookups a class by specified file extension. LookupClass func(ext string) (c *Project, ok bool) // An Importer resolves import paths to Packages. Importer types.Importer } type Package struct { pkg *types.Package docs gogen.ObjectDocs } // NewPackage creates a Go/XGo outline package. func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (_ Package, err error) { ret, err := cl.NewPackage(pkgPath, pkg, &cl.Config{ Fset: conf.Fset, LookupClass: conf.LookupClass, Importer: conf.Importer, NoFileLine: true, NoAutoGenMain: true, NoSkipConstant: true, Outline: true, }) if err != nil { return } return Package{ret.Types, ret.Docs}, nil } func (p Package) Pkg() *types.Package { return p.pkg } func (p Package) Valid() bool { return p.pkg != nil } // ----------------------------------------------------------------------------- type All struct { Consts []Const Vars []Var Funcs []Func Types []*TypeName Package named map[*types.TypeName]*TypeName } func setAlias(aliasr *typeutil.Map, t types.Type, named *TypeName) { real := indirect(t) if aliasr.Set(real, named) != nil { // conflict: has old value aliasr.Set(real, nil) } } // aliasr typeutil.Map // types.Type => *TypeName func (p *All) checkAlias(aliasr *typeutil.Map, t types.Type, withBasic bool) *TypeName { if _, ok := t.(*types.Basic); !ok || withBasic { if v := aliasr.At(t); v != nil { named := v.(*TypeName) p.markUsed(named) return named } } return nil } func (p *All) markUsed(named *TypeName) { if !named.isUsed { named.isUsed = true o := named.TypeName typ := o.Type() p.checkUsed(typ.Underlying()) if !o.IsAlias() { if t, ok := typ.(*types.Named); ok { for i, n := 0, t.NumMethods(); i < n; i++ { p.checkUsedMethod(t.Method(i)) } } } } } func (p *All) checkUsed(typ types.Type) { switch t := typ.(type) { case *types.Basic: case *types.Pointer: p.checkUsed(t.Elem()) case *types.Signature: p.checkUsedSig(t) case *types.Slice: p.checkUsed(t.Elem()) case *types.Map: p.checkUsed(t.Key()) p.checkUsed(t.Elem()) case *types.Struct: for i, n := 0, t.NumFields(); i < n; i++ { fld := t.Field(i) if fld.Exported() { p.checkUsed(fld.Type()) } } case *types.Named: o := t.Obj() if p.pkg == o.Pkg() { p.markUsed(p.getNamed(o)) } case *types.Interface: for i, n := 0, t.NumExplicitMethods(); i < n; i++ { p.checkUsedMethod(t.ExplicitMethod(i)) } for i, n := 0, t.NumEmbeddeds(); i < n; i++ { p.checkUsed(t.EmbeddedType(i)) } case *types.Chan: p.checkUsed(t.Elem()) case *types.Array: p.checkUsed(t.Elem()) default: panic("checkUsed: unknown type - " + typ.String()) } } func (p *All) checkUsedMethod(fn *types.Func) { if fn.Exported() { p.checkUsedSig(fn.Type().(*types.Signature)) } } func (p *All) checkUsedSig(sig *types.Signature) { p.checkUsedTuple(sig.Params()) p.checkUsedTuple(sig.Results()) } func (p *All) checkUsedTuple(t *types.Tuple) { for i, n := 0, t.Len(); i < n; i++ { p.checkUsed(t.At(i).Type()) } } func (p *All) getNamed(t *types.TypeName) *TypeName { if named, ok := p.named[t]; ok { return named } panic("getNamed: type not found - " + t.Name()) } func (p *All) initNamed(aliasr *typeutil.Map, objs []types.Object) { for _, o := range objs { if t, ok := o.(*types.TypeName); ok { named := &TypeName{TypeName: t} p.named[t] = named p.Types = append(p.Types, named) if t.IsAlias() { setAlias(aliasr, t.Type(), named) } } } } func (p *All) lookupNamed(pkg *types.Package, name string) (_ *TypeName, ok bool) { o := pkg.Scope().Lookup(name) if o == nil { return } t, ok := o.(*types.TypeName) if !ok { return } return p.getNamed(t), true } func (p Package) Outline(withUnexported ...bool) (ret *All) { pkg := p.Pkg() ret = &All{ Package: p, named: make(map[*types.TypeName]*TypeName), } all := (withUnexported != nil && withUnexported[0]) aliasr := &typeutil.Map{} scope := pkg.Scope() names := scope.Names() objs := make([]types.Object, len(names)) for i, name := range names { objs[i] = scope.Lookup(name) } ret.initNamed(aliasr, objs) for _, o := range objs { if !(all || o.Exported()) { continue } if obj, ok := o.(*types.TypeName); ok { if !all { ret.markUsed(ret.getNamed(obj)) } continue } switch v := o.(type) { case *types.Func: sig := v.Type().(*types.Signature) if !all { ret.checkUsedSig(sig) } if name, ok := checkGoptFunc(o.Name()); ok { if named, ok := ret.lookupNamed(pkg, name); ok { named.GoptFuncs = append(named.GoptFuncs, Func{v, p.docs}) continue } } kind, named := ret.sigKind(aliasr, sig) switch kind { case sigNormal: ret.Funcs = append(ret.Funcs, Func{v, p.docs}) case sigCreator: named.Creators = append(named.Creators, Func{v, p.docs}) case sigHelper: named.Helpers = append(named.Helpers, Func{v, p.docs}) } case *types.Const: if name := v.Name(); strings.HasPrefix(name, "Gop") || strings.HasPrefix(name, "XGo") { if name == "GopPackage" || name == "XGoPackage" || name == "Gop_sched" { continue } } typ := v.Type() if !all { ret.checkUsed(typ) } if named := ret.checkLocal(aliasr, typ, true); named != nil { named.Consts = append(named.Consts, Const{v}) } else { ret.Consts = append(ret.Consts, Const{v}) } case *types.Var: if !all { ret.checkUsed(v.Type()) } ret.Vars = append(ret.Vars, Var{v}) } } return } // ----------------------------------------------------------------------------- type sigKindType int const ( sigNormal sigKindType = iota sigCreator sigHelper ) func (p *All) sigKind(aliasr *typeutil.Map, sig *types.Signature) (sigKindType, *TypeName) { rets := sig.Results() if rets.Len() > 0 { if t := p.checkLocal(aliasr, rets.At(0).Type(), false); t != nil { return sigCreator, t } } params := sig.Params() if params.Len() > 0 { if t := p.checkLocal(aliasr, params.At(0).Type(), false); t != nil { return sigHelper, t } } return sigNormal, nil } func (p *All) checkLocal(aliasr *typeutil.Map, first types.Type, withBasic bool) *TypeName { first = indirect(first) if t, ok := first.(*types.Named); ok { o := t.Obj() if o.Pkg() == p.pkg { return p.getNamed(o) } } return p.checkAlias(aliasr, first, withBasic) } func indirect(typ types.Type) types.Type { if t, ok := typ.(*types.Pointer); ok { return t.Elem() } return typ } // ----------------------------------------------------------------------------- type Const struct { *types.Const } func (p Const) Obj() types.Object { return p.Const } func (p Const) Doc() string { return "" } type Var struct { *types.Var } func (p Var) Obj() types.Object { return p.Var } func (p Var) Doc() string { return "" } type Func struct { *types.Func docs gogen.ObjectDocs } func (p Func) Obj() types.Object { return p.Func } func (p Func) Doc() string { return p.docs[p.Func].Text() } func CheckOverload(obj types.Object) (name string, fn *types.Func, ok bool) { if fn, ok = obj.(*types.Func); ok { name, ok = checkOverloadFunc(fn.Name()) } return } const ( goptPrefix = "Gopt_" ) func isGoptFunc(name string) bool { return strings.HasPrefix(name, goptPrefix) } func isOverloadFunc(name string) bool { n := len(name) return n > 3 && name[n-3:n-1] == "__" } func checkGoptFunc(name string) (string, bool) { if isGoptFunc(name) { name = name[len(goptPrefix):] if pos := strings.IndexByte(name, '_'); pos > 0 { return name[:pos], true } } return "", false } func checkOverloadFunc(name string) (string, bool) { if isOverloadFunc(name) { return name[:len(name)-3], true } return "", false } // ----------------------------------------------------------------------------- type TypeName struct { *types.TypeName Consts []Const Creators []Func GoptFuncs []Func Helpers []Func isUsed bool } func (p *TypeName) IsUsed() bool { return p.isUsed } func (p *TypeName) ObjWith(all bool) *types.TypeName { o := p.TypeName if all { return o } return hideUnexported(o) } func (p *TypeName) Obj() types.Object { return p.TypeName } func (p *TypeName) Doc() string { return "" } func (p *TypeName) Type() Type { return Type{p.TypeName.Type()} } func hideUnexported(o *types.TypeName) *types.TypeName { if o.IsAlias() { if t, ok := typeHideUnexported(o.Type()); ok { return types.NewTypeName(o.Pos(), o.Pkg(), o.Name(), t) } } else if named, ok := o.Type().(*types.Named); ok { if t, ok := typeHideUnexported(named.Underlying()); ok { name := types.NewTypeName(o.Pos(), o.Pkg(), o.Name(), nil) n := named.NumMethods() var fns []*types.Func if n > 0 { fns = make([]*types.Func, n) for i := 0; i < n; i++ { fns[i] = named.Method(i) } } types.NewNamed(name, t, fns) return name } } return o } func typeHideUnexported(typ types.Type) (ret types.Type, ok bool) { switch t := typ.(type) { case *types.Struct: n := t.NumFields() for i := 0; i < n; i++ { fld := t.Field(i) if !fld.Exported() || t.Tag(i) != "" { ok = true break } } if ok { flds := make([]*types.Var, 0, n) for i := 0; i < n; i++ { fld := t.Field(i) if fld.Exported() { flds = append(flds, fld) } } if len(flds) < n { flds = append(flds, types.NewField(token.NoPos, nil, "", tyUnexp, true)) } ret = types.NewStruct(flds, nil) } } return } type tyUnexpImp struct{} func (p tyUnexpImp) String() string { return "..." } func (p tyUnexpImp) Underlying() types.Type { return p } var ( tyUnexp types.Type = tyUnexpImp{} ) // ----------------------------------------------------------------------------- type Type struct { types.Type } func (p Type) CheckNamed(pkg Package) (_ Named, ok bool) { ret, ok := p.Type.(*types.Named) if ok && ret.Obj().Pkg() == pkg.pkg { return Named{ret, pkg.docs}, true } return } // ----------------------------------------------------------------------------- type Named struct { *types.Named docs gogen.ObjectDocs } func (p Named) Methods() []Func { n := p.NumMethods() ret := make([]Func, n) for i := 0; i < n; i++ { fn := p.Method(i) ret[i] = Func{fn, p.docs} } return ret } // ----------------------------------------------------------------------------- ================================================ FILE: cl/recorder.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cl import ( "go/types" "strings" "github.com/goplus/gogen" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/ast/fromgo" "github.com/goplus/xgo/cl/internal/typesutil" "github.com/goplus/xgo/token" ) type goxRecorder struct { Recorder types map[ast.Expr]types.TypeAndValue referDefs map[*ast.Ident]ast.Node referUses map[string][]*ast.Ident } func newRecorder(rec Recorder) *goxRecorder { types := make(map[ast.Expr]types.TypeAndValue) referDefs := make(map[*ast.Ident]ast.Node) referUses := make(map[string][]*ast.Ident) return &goxRecorder{rec, types, referDefs, referUses} } // Refer uses maps identifiers to name for ast.OverloadFuncDecl. func (p *goxRecorder) ReferUse(ident *ast.Ident, name string) { p.referUses[name] = append(p.referUses[name], ident) } // Refer def maps for ast.FuncLit or ast.OverloadFuncDecl. func (p *goxRecorder) ReferDef(ident *ast.Ident, node ast.Node) { p.referDefs[ident] = node } // Complete computes the types record. func (p *goxRecorder) Complete(scope *types.Scope) { for id, node := range p.referDefs { switch fn := node.(type) { case *ast.FuncLit: if obj := scope.Lookup(id.Name); obj != nil { p.recordFuncLit(fn, obj.Type()) p.Implicit(node, obj) } case *ast.OverloadFuncDecl: if fn.Recv == nil { if obj := scope.Lookup(id.Name); obj != nil { p.Def(id, obj) } } else { if obj := scope.Lookup(fn.Recv.List[0].Type.(*ast.Ident).Name); obj != nil { if named, ok := obj.Type().(*types.Named); ok { n := named.NumMethods() for i := 0; i < n; i++ { if m := named.Method(i); m.Name() == id.Name { p.Def(id, m) break } } } } } } } for name, idents := range p.referUses { pos := strings.Index(name, ".") if pos == -1 { if obj := scope.Lookup(name); obj != nil { for _, id := range idents { p.Use(id, obj) } } continue } if obj := scope.Lookup(name[:pos]); obj != nil { if named, ok := obj.Type().(*types.Named); ok { n := named.NumMethods() for i := 0; i < n; i++ { if m := named.Method(i); m.Name() == name[pos+1:] { for _, id := range idents { p.Use(id, m) } break } } } } } p.types = nil p.referDefs = nil p.referUses = nil } // Member maps identifiers to the objects they denote. func (p *goxRecorder) Member(id ast.Node, obj types.Object) { switch v := id.(type) { case *ast.SelectorExpr: sel := v.Sel // TODO: record event for a Go ident if _, ok := fromgo.CheckIdent(sel); !ok { var tv types.TypeAndValue // check v.X call result by value if f, ok := obj.(*types.Var); ok && f.IsField() && p.checkExprByValue(v.X) { tv = typesutil.NewTypeAndValueForValue(obj.Type(), nil, typesutil.Value) } else { tv = typesutil.NewTypeAndValueForObject(obj) } p.Use(sel, obj) p.Type(v, tv) } case *ast.Ident: // it's in a classfile and impossible converted from Go p.Use(v, obj) p.Type(v, typesutil.NewTypeAndValueForObject(obj)) } } func (p *goxRecorder) Call(id ast.Node, obj types.Object) { switch v := id.(type) { case *ast.Ident: p.Use(v, obj) p.Type(v, typesutil.NewTypeAndValueForObject(obj)) case *ast.SelectorExpr: p.Use(v.Sel, obj) p.Type(v, typesutil.NewTypeAndValueForObject(obj)) case *ast.CallExpr: switch id := v.Fun.(type) { case *ast.Ident: p.Use(id, obj) case *ast.SelectorExpr: p.Use(id.Sel, obj) } p.Type(v.Fun, typesutil.NewTypeAndValueForObject(obj)) } } func (rec *goxRecorder) checkExprByValue(v ast.Expr) bool { if tv, ok := rec.types[v]; ok { switch v.(type) { case *ast.CallExpr: if _, ok := tv.Type.(*types.Pointer); !ok { return true } default: if tv, ok := rec.types[v]; ok { return !tv.Addressable() } } } return false } func (rec *goxRecorder) Type(expr ast.Expr, tv types.TypeAndValue) { rec.types[expr] = tv rec.Recorder.Type(expr, tv) } func (rec *goxRecorder) instantiate(expr ast.Expr, _, typ types.Type) { // check gox TyOverloadNamed if tv, ok := rec.types[expr]; ok { tv.Type = typ rec.Recorder.Type(expr, tv) } var ident *ast.Ident switch id := expr.(type) { case *ast.Ident: ident = id case *ast.SelectorExpr: ident = id.Sel } if ident != nil { if named, ok := typ.(*types.Named); ok { rec.Use(ident, named.Obj()) } } } func (rec *goxRecorder) recordTypeValue(ctx *blockCtx, expr ast.Expr, mode typesutil.OperandMode) { e := ctx.cb.Get(-1) t, _ := gogen.DerefType(e.Type) rec.Type(expr, typesutil.NewTypeAndValueForValue(t, e.CVal, mode)) } func (rec *goxRecorder) indexExpr(ctx *blockCtx, expr *ast.IndexExpr) { if tv, ok := rec.types[expr.X]; ok { switch tv.Type.(type) { case *types.Map: rec.recordTypeValue(ctx, expr, typesutil.MapIndex) return case *types.Slice: rec.recordTypeValue(ctx, expr, typesutil.Variable) return } } op := typesutil.Variable switch e := expr.X.(type) { case *ast.CompositeLit: op = typesutil.Value case *ast.SelectorExpr: if rec.checkExprByValue(e.X) { op = typesutil.Value } } rec.recordTypeValue(ctx, expr, op) } func (rec *goxRecorder) unaryExpr(ctx *blockCtx, expr *ast.UnaryExpr) { switch expr.Op { case token.ARROW: rec.recordTypeValue(ctx, expr, typesutil.CommaOK) default: rec.recordTypeValue(ctx, expr, typesutil.Value) } } func (rec *goxRecorder) recordCallExpr(ctx *blockCtx, v *ast.CallExpr, fnt types.Type) { e := ctx.cb.Get(-1) if _, ok := rec.types[v.Fun]; !ok { rec.Type(v.Fun, typesutil.NewTypeAndValueForValue(fnt, nil, typesutil.Value)) } rec.Type(v, typesutil.NewTypeAndValueForCallResult(e.Type, e.CVal)) } func (rec *goxRecorder) recordCompositeLit(v *ast.CompositeLit, typ types.Type) { rec.Type(v.Type, typesutil.NewTypeAndValueForType(typ)) rec.Type(v, typesutil.NewTypeAndValueForValue(typ, nil, typesutil.Value)) } func (rec *goxRecorder) recordFuncLit(v *ast.FuncLit, typ types.Type) { rec.Type(v.Type, typesutil.NewTypeAndValueForType(typ)) rec.Type(v, typesutil.NewTypeAndValueForValue(typ, nil, typesutil.Value)) } func (rec *goxRecorder) recordType(typ ast.Expr, t types.Type) { rec.Type(typ, typesutil.NewTypeAndValueForType(t)) } func (rec *goxRecorder) recordIdent(ident *ast.Ident, obj types.Object) { rec.Use(ident, obj) rec.Type(ident, typesutil.NewTypeAndValueForObject(obj)) } func (rec *goxRecorder) recordExpr(ctx *blockCtx, expr ast.Expr, _ bool) { switch v := expr.(type) { case *ast.Ident: case *ast.BasicLit: rec.recordTypeValue(ctx, v, typesutil.Value) case *ast.CallExpr: case *ast.SelectorExpr: if _, ok := rec.types[v]; !ok { rec.recordTypeValue(ctx, v, typesutil.Variable) } case *ast.BinaryExpr: rec.recordTypeValue(ctx, v, typesutil.Value) case *ast.UnaryExpr: rec.unaryExpr(ctx, v) case *ast.FuncLit: case *ast.CompositeLit: case *ast.SliceLit: case *ast.RangeExpr: case *ast.IndexExpr: rec.indexExpr(ctx, v) case *ast.IndexListExpr: case *ast.SliceExpr: rec.recordTypeValue(ctx, v, typesutil.Value) case *ast.StarExpr: rec.recordTypeValue(ctx, v, typesutil.Variable) case *ast.ArrayType: rec.recordTypeValue(ctx, v, typesutil.TypExpr) case *ast.MapType: rec.recordTypeValue(ctx, v, typesutil.TypExpr) case *ast.StructType: rec.recordTypeValue(ctx, v, typesutil.TypExpr) case *ast.ChanType: rec.recordTypeValue(ctx, v, typesutil.TypExpr) case *ast.InterfaceType: rec.recordTypeValue(ctx, v, typesutil.TypExpr) case *ast.ComprehensionExpr: case *ast.TypeAssertExpr: rec.recordTypeValue(ctx, v, typesutil.CommaOK) case *ast.ParenExpr: rec.recordTypeValue(ctx, v, typesutil.Value) case *ast.ErrWrapExpr: case *ast.FuncType: rec.recordTypeValue(ctx, v, typesutil.TypExpr) case *ast.Ellipsis: case *ast.KeyValueExpr: default: } } ================================================ FILE: cl/run_test.go ================================================ package cl_test import ( "bytes" "io" "log" "os" "os/exec" "strconv" "strings" "sync/atomic" "testing" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/cl/cltest" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/parser/fsx/memfs" "github.com/goplus/xgo/scanner" ) var ( tmpDir string tmpFileIdx int64 ) func init() { home, err := os.Getwd() check(err) tmpDir = home + "/.xgo/tmp/" err = os.MkdirAll(tmpDir, 0755) check(err) } func check(err error) { if err != nil { log.Panicln(err) } } func checkWith(err error, stdout, stderr io.Writer) { if err != nil { fatalWith(err, stdout, stderr) } } func fatalWith(err error, stdout, stderr io.Writer) { if o, ok := getBytes(stdout, stderr); ok { os.Stderr.Write(o.Bytes()) } log.Panicln(err) } type iBytes interface { Bytes() []byte } func getBytes(stdout, stderr io.Writer) (o iBytes, ok bool) { if o, ok = stderr.(iBytes); ok { return } o, ok = stdout.(iBytes) return } func goRun(_ *testing.T, code []byte) string { idx := atomic.AddInt64(&tmpFileIdx, 1) infile := tmpDir + strconv.FormatInt(idx, 10) + ".go" err := os.WriteFile(infile, []byte(code), 0666) check(err) var stdout, stderr bytes.Buffer cmd := exec.Command("go", "run", infile) cmd.Dir = tmpDir cmd.Stdout = &stdout cmd.Stderr = &stderr err = cmd.Run() os.Remove(infile) checkWith(err, &stdout, &stderr) return stdout.String() } func genGo(t *testing.T, conf *cl.Config, gopcode string) []byte { cl.SetDisableRecover(true) defer cl.SetDisableRecover(false) fs := memfs.SingleFile("/foo", "bar.xgo", gopcode) pkgs, err := parser.ParseFSDir(cltest.Conf.Fset, fs, "/foo", parser.Config{Mode: parser.ParseComments}) if err != nil { scanner.PrintError(os.Stderr, err) t.Fatal("ParseFSDir:", err) } pkg, err := cl.NewPackage("", pkgs["main"], conf) if err != nil { t.Fatal("NewPackage:", err) } var b bytes.Buffer err = pkg.WriteTo(&b) if err != nil { t.Fatal("gogen.WriteTo failed:", err) } return b.Bytes() } func testRun(t *testing.T, gopcode, expected string) { code := genGo(t, cltest.Conf, gopcode) result := goRun(t, code) if result != expected { t.Fatalf("=> Result:\n%s\n=> Expected:\n%s\n", result, expected) } } func testRunType(t *testing.T, typ, gopcodeT, expected string) { gopcode := strings.ReplaceAll(gopcodeT, "$(type)", typ) testRun(t, gopcode, expected) } // ----------------------------------------------------------------------------- const ( testType_inc_code, testType_inc_ret = ` { var x $(type) = 1 var y = +x x++ x+=10 println x, y } `, `12 1 ` testType_dec_code, testType_dec_ret = ` { var x $(type) = 0 x-- println x } `, `-1 ` testType_init_code, testType_init_ret = ` { var x $(type) = 1 << 65 var y = x >> 63 var z = x >> 65 println x println y, z } `, `36893488147419103232 4 1 ` testType_twoval_code, testType_twoval_ret = ` { var x $(type) = 1 << 65 var y = (x >> 2) - 1 v1, ok1 := int64(x) v2, ok2 := int64(y) println v1, ok1 println v2, ok2 } `, `0 false 9223372036854775807 true ` testType_cast_code, testType_cast_ret = ` { println $(type)(1 << 65), $(type)() } `, `36893488147419103232 0 ` testType_printf_code, testType_printf_ret = ` { var x $(type) = 1 printf "%4d\n", x } `, ` 1 ` testTypescanf_code, testType_scanf_ret = ` import "fmt" { var name string var age $(type) fmt.Sscanf("Kim is 22 years old", "%s is %d years old", &name, &age) println name, age } `, `Kim 22 ` ) const ( testType_com_code, testType_com_ret = testType_inc_code + testType_init_code + testType_cast_code + testType_printf_code + testType_twoval_code, testType_inc_ret + testType_init_ret + testType_cast_ret + testType_printf_ret + testType_twoval_ret testType_fixint_code, testType_fixint_ret = testTypescanf_code + testType_com_code, testType_scanf_ret + testType_com_ret ) func TestUint128_run(t *testing.T) { testRunType(t, "uint128", testType_fixint_code, testType_fixint_ret) } func TestInt128_run(t *testing.T) { testRunType(t, "int128", testType_fixint_code+testType_dec_code, testType_fixint_ret+testType_dec_ret) } func TestBigint_run(t *testing.T) { testRunType(t, "bigint", testType_com_code+testType_dec_code, testType_com_ret+testType_dec_ret) } // ----------------------------------------------------------------------------- ================================================ FILE: cl/stmt.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cl import ( "fmt" "go/constant" "log" "path/filepath" goast "go/ast" gotoken "go/token" "go/types" "github.com/goplus/gogen" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/token" ) func fileLineFile(relBaseDir, absFile string) string { if ret, err := filepath.Rel(relBaseDir, absFile); err == nil { return filepath.ToSlash(ret) } return absFile } func relFile(dir string, absFile string) string { if dir != "" { return fileLineFile(dir, absFile) } return absFile } func commentStmt(ctx *blockCtx, stmt ast.Stmt) { if ctx.fileLine { commentStmtEx(ctx.cb, ctx.pkgCtx, stmt) } } func commentStmtEx(cb *gogen.CodeBuilder, ctx *pkgCtx, stmt ast.Stmt) { start := stmt.Pos() if start == token.NoPos { cb.SetComments(nil, false) return } if doc := checkStmtDoc(stmt); doc != nil { start = doc.Pos() } pos := ctx.fset.Position(start) if ctx.relBaseDir != "" { pos.Filename = fileLineFile(ctx.relBaseDir, pos.Filename) } line := fmt.Sprintf("\n//line %s:%d:1", pos.Filename, pos.Line) comments := &goast.CommentGroup{ List: []*goast.Comment{{Text: line}}, } cb.SetComments(comments, false) } func checkStmtDoc(stmt ast.Stmt) *ast.CommentGroup { if decl, ok := stmt.(*ast.DeclStmt); ok { if d, ok := decl.Decl.(*ast.GenDecl); ok { return d.Doc } } return nil } func commentFunc(ctx *blockCtx, fn *gogen.Func, decl *ast.FuncDecl) { start := decl.Name.Pos() if ctx.fileLine && start != token.NoPos { if decl.Doc != nil { start = decl.Doc.Pos() } pos := ctx.fset.Position(start) if ctx.relBaseDir != "" { pos.Filename = fileLineFile(ctx.relBaseDir, pos.Filename) } var line string if decl.Shadow { line = fmt.Sprintf("//line %s:%d", pos.Filename, pos.Line) } else { line = fmt.Sprintf("//line %s:%d:1", pos.Filename, pos.Line) } doc := &goast.CommentGroup{} doc.List = append(doc.List, &goast.Comment{Text: line}) if decl.Doc != nil { doc.List = append(doc.List, decl.Doc.List...) } fn.SetComments(ctx.pkg, doc) } else if decl.Doc != nil { fn.SetComments(ctx.pkg, decl.Doc) } } func compileStmts(ctx *blockCtx, body []ast.Stmt) { for _, stmt := range body { if v, ok := stmt.(*ast.LabeledStmt); ok { expr := v.Label ctx.cb.NewLabel(expr.Pos(), expr.End(), expr.Name) } } for _, stmt := range body { compileStmt(ctx, stmt) } } func compileStmt(ctx *blockCtx, stmt ast.Stmt) { if enableRecover { defer func() { if e := recover(); e != nil { ctx.handleRecover(e, stmt) ctx.cb.ResetStmt() } }() } commentStmt(ctx, stmt) switch v := stmt.(type) { case *ast.ExprStmt: x := v.X inFlags := checkCommandWithoutArgs(x) compileExpr(ctx, 0, x, inFlags) case *ast.AssignStmt: compileAssignStmt(ctx, v) case *ast.ReturnStmt: compileReturnStmt(ctx, v) case *ast.IfStmt: compileIfStmt(ctx, v) case *ast.SwitchStmt: compileSwitchStmt(ctx, v) case *ast.RangeStmt: compileRangeStmt(ctx, v) case *ast.ForStmt: compileForStmt(ctx, v) case *ast.ForPhraseStmt: compileForPhraseStmt(ctx, v) case *ast.IncDecStmt: compileIncDecStmt(ctx, v) case *ast.DeferStmt: compileDeferStmt(ctx, v) case *ast.GoStmt: compileGoStmt(ctx, v) case *ast.DeclStmt: compileDeclStmt(ctx, v) case *ast.TypeSwitchStmt: compileTypeSwitchStmt(ctx, v) case *ast.SendStmt: compileSendStmt(ctx, v) case *ast.BranchStmt: compileBranchStmt(ctx, v) case *ast.LabeledStmt: compileLabeledStmt(ctx, v) case *ast.BlockStmt: ctx.cb.Block() compileStmts(ctx, v.List) if rec := ctx.recorder(); rec != nil { rec.Scope(v, ctx.cb.Scope()) } ctx.cb.End() return case *ast.SelectStmt: compileSelectStmt(ctx, v) case *ast.EmptyStmt: // do nothing default: log.Panicf("compileStmt failed: unknown - %T\n", v) } ctx.cb.EndStmt() } func checkCommandWithoutArgs(x ast.Expr) int { retry: if v, ok := x.(*ast.SelectorExpr); ok { x = v.X goto retry } if _, ok := x.(*ast.Ident); ok { return clCommandWithoutArgs } return 0 } func compileReturnStmt(ctx *blockCtx, expr *ast.ReturnStmt) { // Use defer to ensure Return is always called, even if argument compilation // fails. This guarantees the return statement is recorded in AST for control // flow analysis, preventing spurious "missing return" errors. defer ctx.cb.Return(len(expr.Results), expr) var n = -1 var results *types.Tuple for i, ret := range expr.Results { if c, ok := ret.(*ast.CompositeLit); ok && c.Type == nil { if n < 0 { results = ctx.cb.Func().Type().(*types.Signature).Results() n = results.Len() } var typ types.Type if i < n { typ = results.At(i).Type() } compileCompositeLit(ctx, c, typ, true) } else { lhs := 0 if len(expr.Results) == 1 { if _, ok := ret.(*ast.ComprehensionExpr); ok { results = ctx.cb.Func().Type().(*types.Signature).Results() if results.Len() == 2 { // TODO(xsw): check this lhs = 2 } } } switch v := ret.(type) { case *ast.LambdaExpr, *ast.LambdaExpr2: rtyp := ctx.cb.Func().Type().(*types.Signature).Results().At(i).Type() sig, ok := rtyp.(*types.Signature) if !ok { panic(ctx.newCodeErrorf( ret.Pos(), ret.End(), "cannot use lambda expression as type %v in return statement", rtyp)) } compileLambda(ctx, v, sig) case *ast.SliceLit: rtyp := ctx.cb.Func().Type().(*types.Signature).Results().At(i).Type() compileSliceLit(ctx, v, rtyp) default: compileExpr(ctx, lhs, ret) } } } } func compileIncDecStmt(ctx *blockCtx, expr *ast.IncDecStmt) { compileExprLHS(ctx, expr.X) ctx.cb.IncDec(gotoken.Token(expr.Tok)) } // issue #2107: // // a <- v // foo.a <- v func isAppendable(x ast.Expr) bool { switch x := x.(type) { case *ast.Ident: return true case *ast.SelectorExpr: _, ok := x.X.(*ast.Ident) return ok } return false } func compileSendStmt(ctx *blockCtx, expr *ast.SendStmt) { cb := ctx.cb ch, vals := expr.Chan, expr.Values stk := cb.InternalStack() if isAppendable(ch) { // a <- v1, v2, v3 (issue #2107) compileExpr(ctx, 1, ch) a := stk.Get(-1) t := a.Type.Underlying() if _, ok := t.(*types.Slice); ok { // a = append(a, v1, v2, v3) stk.Pop() compileExprLHS(ctx, ch) cb.Val(ctx.pkg.Builtin().Ref("append")) stk.Push(a) for _, v := range vals { compileExpr(ctx, 1, v) } flags := gogen.InstrFlags(0) if expr.Ellipsis != 0 { // a = append(a, b...) flags |= gogen.InstrFlagEllipsis } cb.CallWith(len(vals)+1, 0, flags, expr).AssignWith(1, 1, expr) return } goto normal } compileExpr(ctx, 1, ch) normal: if len(vals) != 1 || expr.Ellipsis != 0 { panic(ctx.newCodeError(vals[0].Pos(), vals[0].End(), "can't send multiple values to a channel")) } compileExpr(ctx, 1, vals[0]) ctx.cb.Send() } func compileAssignStmt(ctx *blockCtx, expr *ast.AssignStmt) { tok := expr.Tok lhs := 1 if len(expr.Lhs) > 1 && len(expr.Rhs) == 1 { lhs = len(expr.Lhs) } if tok == token.DEFINE { stk := ctx.cb.InternalStack() base := stk.Len() names := make([]string, len(expr.Lhs)) for i, lhs := range expr.Lhs { if v, ok := lhs.(*ast.Ident); ok { names[i] = v.Name } else { compileExprLHS(ctx, lhs) // only for typesutil.Check log.Panicln("TODO: non-name $v on left side of :=") } } if rec := ctx.recorder(); rec != nil { newNames := make([]*ast.Ident, 0, len(names)) scope := ctx.cb.Scope() for _, lhs := range expr.Lhs { v := lhs.(*ast.Ident) if scope.Lookup(v.Name) == nil { newNames = append(newNames, v) } } defer defNames(ctx, newNames, scope) } ctx.cb.DefineVarStart(expr.Pos(), names...) if enableRecover { defer func() { if e := recover(); e != nil { ctx.cb.ResetInit() panic(e) } }() } for _, rhs := range expr.Rhs { compileExpr(ctx, lhs, rhs) } ctx.cb.EndInit(stk.Len() - base) return } for _, lhs := range expr.Lhs { compileExprLHS(ctx, lhs) } for i, rhs := range expr.Rhs { switch e := unparen(rhs).(type) { case *ast.LambdaExpr, *ast.LambdaExpr2: if len(expr.Lhs) == 1 && len(expr.Rhs) == 1 { typ := ctx.cb.Get(-1).Type.(interface{ Elem() types.Type }).Elem() sig, err := checkLambdaFuncType(ctx, e, typ, clLambaAssign, expr.Lhs[0]) if err != nil { panic(err) } compileLambda(ctx, e, sig) } else { panic(ctx.newCodeErrorf(e.Pos(), e.End(), "lambda unsupport multiple assignment")) } case *ast.SliceLit: var typ types.Type if len(expr.Lhs) == len(expr.Rhs) { typ, _ = gogen.DerefType(ctx.cb.Get(-1 - i).Type) } compileSliceLit(ctx, e, typ) case *ast.CompositeLit: var typ types.Type if len(expr.Lhs) == len(expr.Rhs) { typ, _ = gogen.DerefType(ctx.cb.Get(-1 - i).Type) } compileCompositeLit(ctx, e, typ, false) default: compileExpr(ctx, lhs, rhs) } } if tok == token.ASSIGN { ctx.cb.AssignWith(len(expr.Lhs), len(expr.Rhs), expr) return } if len(expr.Lhs) != 1 || len(expr.Rhs) != 1 { panic("TODO: invalid syntax of assign by operator") } ctx.cb.AssignOp(gotoken.Token(tok), expr) } // forRange(names...) x rangeAssignThen // // body // // end // forRange k v x rangeAssignThen // // body // // end func compileRangeStmt(ctx *blockCtx, v *ast.RangeStmt) { if re, ok := v.X.(*ast.RangeExpr); ok { tok := token.DEFINE if v.Tok == token.ASSIGN { tok = v.Tok } compileForStmt(ctx, toForStmt(v.For, v.Key, v.Body, re, tok, nil)) return } cb := ctx.cb defer cb.End(v) defer func() { r := recover() if r != nil { ctx.handleRecover(r, v) cb.ResetStmt() } }() comments, once := cb.BackupComments() defineNames := make([]*ast.Ident, 0, 2) if v.Tok == token.DEFINE { names := make([]string, 1, 2) if v.Key == nil { names[0] = "_" } else { key := v.Key.(*ast.Ident) names[0] = key.Name defineNames = append(defineNames, key) } if v.Value != nil { value := v.Value.(*ast.Ident) names = append(names, value.Name) defineNames = append(defineNames, value) } cb.ForRangeEx(names, v) } else { cb.ForRangeEx(nil, v) n := 0 if v.Key == nil { if v.Value != nil { ctx.cb.VarRef(nil) // underscore n++ } } else { compileExprLHS(ctx, v.Key) n++ } if v.Value != nil { compileExprLHS(ctx, v.Value) n++ } } compileExpr(ctx, 1, v.X) pos := v.TokPos if pos == 0 { pos = v.For } cb.RangeAssignThen(pos) if len(defineNames) > 0 { defNames(ctx, defineNames, cb.Scope()) } if rec := ctx.recorder(); rec != nil { rec.Scope(v, cb.Scope()) } cb.VBlock() compileStmts(ctx, v.Body.List) if rec := ctx.recorder(); rec != nil { rec.Scope(v.Body, cb.Scope()) } cb.End(v.Body) cb.SetComments(comments, once) setBodyHandler(ctx) } func compileForPhraseStmt(ctx *blockCtx, v *ast.ForPhraseStmt) { if re, ok := v.X.(*ast.RangeExpr); ok { compileForStmt(ctx, toForStmt(v.For, v.Value, v.Body, re, token.DEFINE, v.ForPhrase)) return } cb := ctx.cb defer cb.End(v) defer func() { r := recover() if r != nil { ctx.handleRecover(r, v) cb.ResetStmt() } }() comments, once := cb.BackupComments() names := make([]string, 1, 2) defineNames := make([]*ast.Ident, 0, 2) if v.Key == nil { names[0] = "_" } else { names[0] = v.Key.Name defineNames = append(defineNames, v.Key) } if v.Value != nil { names = append(names, v.Value.Name) defineNames = append(defineNames, v.Value) } cb.ForRange(names...) compileExpr(ctx, 1, v.X) cb.RangeAssignThen(v.TokPos) if len(defineNames) > 0 { defNames(ctx, defineNames, cb.Scope()) } if rec := ctx.recorder(); rec != nil { rec.Scope(v, cb.Scope()) } if v.Cond != nil { cb.If() compileExpr(ctx, 1, v.Cond) cb.Then() compileStmts(ctx, v.Body.List) cb.SetComments(comments, once) if rec := ctx.recorder(); rec != nil { rec.Scope(v.Body, cb.Scope()) } cb.End() } else { cb.VBlock() compileStmts(ctx, v.Body.List) if rec := ctx.recorder(); rec != nil { rec.Scope(v.Body, cb.Scope()) } cb.End(v.Body) cb.SetComments(comments, once) } setBodyHandler(ctx) } func toForStmt(forPos token.Pos, value ast.Expr, body *ast.BlockStmt, re *ast.RangeExpr, tok token.Token, fp *ast.ForPhrase) *ast.ForStmt { const ( nameK = "_xgo_k" nameStep = "_xgo_step" nameEnd = "_xgo_end" ) nilIdent := value == nil if !nilIdent { if v, ok := value.(*ast.Ident); ok && v.Name == "_" { nilIdent = true } } if nilIdent { value = &ast.Ident{NamePos: forPos, Name: nameK} } first := re.First if first == nil { first = &ast.BasicLit{ValuePos: forPos, Kind: token.INT, Value: "0"} } initLhs := []ast.Expr{value} initRhs := []ast.Expr{first} replaceValue := false var cond ast.Expr var post ast.Expr switch re.Last.(type) { case *ast.Ident, *ast.BasicLit: cond = re.Last default: replaceValue = true cond = &ast.Ident{NamePos: forPos, Name: nameEnd} initLhs = append(initLhs, cond) initRhs = append(initRhs, re.Last) } if re.Expr3 == nil { post = &ast.BasicLit{ValuePos: forPos, Kind: token.INT, Value: "1"} } else { switch re.Expr3.(type) { case *ast.Ident, *ast.BasicLit: post = re.Expr3 default: replaceValue = true post = &ast.Ident{NamePos: forPos, Name: nameStep} initLhs = append(initLhs, post) initRhs = append(initRhs, re.Expr3) } } if tok == token.ASSIGN && replaceValue { oldValue := value value = &ast.Ident{NamePos: forPos, Name: nameK} initLhs[0] = value body.List = append([]ast.Stmt{&ast.AssignStmt{ Lhs: []ast.Expr{oldValue}, TokPos: forPos, Tok: token.ASSIGN, Rhs: []ast.Expr{value}, }}, body.List...) tok = token.DEFINE } if fp != nil && fp.Cond != nil { condStmt := &ast.IfStmt{ If: fp.IfPos, Init: fp.Init, Cond: fp.Cond, Body: body, } body = &ast.BlockStmt{ List: []ast.Stmt{condStmt}, } } return &ast.ForStmt{ For: forPos, Init: &ast.AssignStmt{ Lhs: initLhs, TokPos: re.To, Tok: tok, Rhs: initRhs, }, Cond: &ast.BinaryExpr{ X: value, OpPos: re.To, Op: token.LSS, Y: cond, }, Post: &ast.AssignStmt{ Lhs: []ast.Expr{value}, TokPos: re.Colon2, Tok: token.ADD_ASSIGN, Rhs: []ast.Expr{post}, }, Body: body, } } // for init; cond then // // body // post // // end func compileForStmt(ctx *blockCtx, v *ast.ForStmt) { cb := ctx.cb defer cb.End(v) defer func() { r := recover() if r != nil { ctx.handleRecover(r, v) cb.ResetStmt() } }() comments, once := cb.BackupComments() cb.For(v) if rec := ctx.recorder(); rec != nil { rec.Scope(v, cb.Scope()) } if v.Init != nil { compileStmt(ctx, v.Init) } if v.Cond != nil { compileExpr(ctx, 1, v.Cond) } else { cb.None() } cb.Then(v.Body) if rec := ctx.recorder(); rec != nil { rec.Scope(v.Body, cb.Scope()) } compileStmts(ctx, v.Body.List) if v.Post != nil { cb.Post() compileStmt(ctx, v.Post) } cb.SetComments(comments, once) setBodyHandler(ctx) } // if init; cond then // // body // // end func compileIfStmt(ctx *blockCtx, v *ast.IfStmt) { cb := ctx.cb defer cb.End(v) defer func() { r := recover() if r != nil { ctx.handleRecover(r, v) cb.ResetStmt() } }() comments, once := cb.BackupComments() cb.If(v) if v.Init != nil { compileStmt(ctx, v.Init) } compileExpr(ctx, 1, v.Cond) if rec := ctx.recorder(); rec != nil { rec.Scope(v, cb.Scope()) } cb.Then(v.Body) compileStmts(ctx, v.Body.List) if e := v.Else; e != nil { cb.Else(e) if stmts, ok := e.(*ast.BlockStmt); ok { compileStmts(ctx, stmts.List) } else { compileStmt(ctx, e) } } cb.SetComments(comments, once) if rec := ctx.recorder(); rec != nil { rec.Scope(v.Body, cb.Scope()) } } // typeSwitch(name) init; expr typeAssertThen() // type1 type2 ... typeN typeCase(N) // // ... // end // // type1 type2 ... typeM typeCase(M) // // ... // end // // end func compileTypeSwitchStmt(ctx *blockCtx, v *ast.TypeSwitchStmt) { var cb = ctx.cb defer cb.End(v) defer func() { r := recover() if r != nil { ctx.handleRecover(r, v) cb.ResetStmt() } }() comments, once := cb.BackupComments() var name string var ta *ast.TypeAssertExpr switch stmt := v.Assign.(type) { case *ast.AssignStmt: if stmt.Tok != token.DEFINE || len(stmt.Lhs) != 1 || len(stmt.Rhs) != 1 { panic("TODO: type switch syntax error") } name = stmt.Lhs[0].(*ast.Ident).Name ta = stmt.Rhs[0].(*ast.TypeAssertExpr) case *ast.ExprStmt: ta = stmt.X.(*ast.TypeAssertExpr) } if ta.Type != nil { panic("TODO: type switch syntax error, please use x.(type)") } cb.TypeSwitch(name, v) if rec := ctx.recorder(); rec != nil { rec.Scope(v, cb.Scope()) } if v.Init != nil { compileStmt(ctx, v.Init) } compileExpr(ctx, 1, ta.X) cb.TypeAssertThen() seen := make(map[types.Type]ast.Expr) var firstDefault ast.Stmt for _, stmt := range v.Body.List { c, ok := stmt.(*ast.CaseClause) if !ok { log.Panicln("TODO: compile TypeSwitchStmt failed - case clause expected.") } cb.TypeCase(c) for _, citem := range c.List { compileExpr(ctx, 1, citem) T := cb.Get(-1).Type if tt, ok := T.(*gogen.TypeType); ok { T = tt.Type() } var haserr bool for t, other := range seen { if T == nil && t == nil || T != nil && t != nil && types.Identical(T, t) { haserr = true pos := citem.Pos() end := citem.End() if T == types.Typ[types.UntypedNil] { ctx.handleErrorf( pos, end, "multiple nil cases in type switch (first at %v)", ctx.Position(other.Pos())) } else { ctx.handleErrorf( pos, end, "duplicate case %s in type switch\n\tprevious case at %v", T, ctx.Position(other.Pos())) } } } if !haserr { seen[T] = citem } } if c.List == nil { if firstDefault != nil { ctx.handleErrorf( c.Pos(), c.End(), "multiple defaults in type switch (first at %v)", ctx.Position(firstDefault.Pos())) } else { firstDefault = c } } cb.Then() compileStmts(ctx, c.Body) commentStmt(ctx, stmt) if rec := ctx.recorder(); rec != nil { rec.Scope(c, cb.Scope()) } cb.End(c) } cb.SetComments(comments, once) } // switch init; tag then // expr1 expr2 ... exprN case(N) // // ... // end // // expr1 expr2 ... exprM case(M) // // ... // end // // end func compileSwitchStmt(ctx *blockCtx, v *ast.SwitchStmt) { cb := ctx.cb defer cb.End(v) defer func() { r := recover() if r != nil { ctx.handleRecover(r, v) cb.ResetStmt() } }() comments, once := cb.BackupComments() cb.Switch(v) if v.Init != nil { compileStmt(ctx, v.Init) } if v.Tag != nil { // switch tag {....} compileExpr(ctx, 1, v.Tag) } else { cb.None() // switch {...} } if rec := ctx.recorder(); rec != nil { rec.Scope(v, cb.Scope()) } cb.Then(v.Body) seen := make(valueMap) var firstDefault ast.Stmt for _, stmt := range v.Body.List { c, ok := stmt.(*ast.CaseClause) if !ok { log.Panicln("TODO: compile SwitchStmt failed - case clause expected.") } cb.Case(c) for _, citem := range c.List { compileExpr(ctx, 1, citem) v := cb.Get(-1) if val := goVal(v.CVal); val != nil { // look for duplicate types for a given value // (quadratic algorithm, but these lists tend to be very short) typ := types.Default(v.Type) var haserr bool for _, vt := range seen[val] { if types.Identical(typ, vt.typ) { haserr = true src := ctx.LoadExpr(v.Src) if lit, ok := v.Src.(*ast.BasicLit); ok { ctx.handleErrorf(lit.Pos(), lit.End(), "duplicate case %s in switch\n\tprevious case at %v", src, ctx.Position(vt.pos)) } else { ctx.handleErrorf(v.Src.Pos(), v.Src.End(), "duplicate case %s (value %#v) in switch\n\tprevious case at %v", src, val, ctx.Position(vt.pos)) } } } if !haserr { seen[val] = append(seen[val], valueType{v.Src.Pos(), typ}) } } } if c.List == nil { if firstDefault != nil { ctx.handleErrorf(c.Pos(), c.End(), "multiple defaults in switch (first at %v)", ctx.Position(firstDefault.Pos())) } else { firstDefault = c } } cb.Then() body, has := hasFallthrough(c.Body) compileStmts(ctx, body) if has { cb.Fallthrough() } commentStmt(ctx, stmt) if rec := ctx.recorder(); rec != nil { rec.Scope(c, cb.Scope()) } cb.End(c) } cb.SetComments(comments, once) } func hasFallthrough(body []ast.Stmt) ([]ast.Stmt, bool) { if n := len(body); n > 0 { if bs, ok := body[n-1].(*ast.BranchStmt); ok && bs.Tok == token.FALLTHROUGH { return body[:n-1], true } } return body, false } // select // stmt1 commCase(1) // // ... // end // // stmt2 commCase(1) // // ... // end // // ... // commCase(0) // // ... // end // // end func compileSelectStmt(ctx *blockCtx, v *ast.SelectStmt) { cb := ctx.cb defer cb.End(v) defer func() { r := recover() if r != nil { ctx.handleRecover(r, v) cb.ResetStmt() } }() comments, once := cb.BackupComments() cb.Select(v) for _, stmt := range v.Body.List { c, ok := stmt.(*ast.CommClause) if !ok { log.Panicln("TODO: compile SelectStmt failed - comm clause expected.") } cb.CommCase(c) if c.Comm != nil { compileStmt(ctx, c.Comm) } cb.Then() compileStmts(ctx, c.Body) commentStmt(ctx, stmt) if rec := ctx.recorder(); rec != nil { rec.Scope(c, cb.Scope()) } cb.End(c) } cb.SetComments(comments, once) } func compileBranchStmt(ctx *blockCtx, v *ast.BranchStmt) { label := v.Label switch v.Tok { case token.GOTO: cb := ctx.cb if l, ok := cb.LookupLabel(label.Name); ok { cb.Goto(l) return } compileCallExpr(ctx, 0, &ast.CallExpr{ Fun: &ast.Ident{NamePos: v.TokPos, Name: "goto", Obj: &ast.Object{Data: label}}, Args: []ast.Expr{label}, NoParenEnd: label.End(), }, clIdentGoto) case token.BREAK: ctx.cb.Break(getLabel(ctx, label)) case token.CONTINUE: ctx.cb.Continue(getLabel(ctx, label)) case token.FALLTHROUGH: ctx.handleErrorf(v.Pos(), v.End(), "fallthrough statement out of place") default: panic("unknown branch statement") } } func getLabel(ctx *blockCtx, label *ast.Ident) *gogen.Label { if label != nil { if l, ok := ctx.cb.LookupLabel(label.Name); ok { return l } ctx.handleErrorf(label.Pos(), label.End(), "label %v is not defined", label.Name) } return nil } func compileLabeledStmt(ctx *blockCtx, v *ast.LabeledStmt) { l, _ := ctx.cb.LookupLabel(v.Label.Name) ctx.cb.Label(l) compileStmt(ctx, v.Stmt) } func compileGoStmt(ctx *blockCtx, v *ast.GoStmt) { compileCallExpr(ctx, 0, v.Call, 0) ctx.cb.Go() } func compileDeferStmt(ctx *blockCtx, v *ast.DeferStmt) { compileCallExpr(ctx, 0, v.Call, 0) ctx.cb.Defer() } func compileDeclStmt(ctx *blockCtx, expr *ast.DeclStmt) { switch d := expr.Decl.(type) { case *ast.GenDecl: switch d.Tok { case token.TYPE: for _, spec := range d.Specs { compileType(ctx, spec.(*ast.TypeSpec)) } case token.CONST: cdecl := ctx.pkg.NewConstDefs(ctx.cb.Scope()) loadConstSpecs(ctx, cdecl, d.Specs) case token.VAR: for _, spec := range d.Specs { v := spec.(*ast.ValueSpec) loadVars(ctx, v, d.Doc, false) } default: log.Panicln("TODO: compileDeclStmt - unknown") } } } func compileType(ctx *blockCtx, t *ast.TypeSpec) { name := t.Name.Name if name == "_" { return } if t.Assign != token.NoPos { // alias type ctx.cb.AliasType(name, toType(ctx, t.Type)) } else { ctx.cb.NewType(name).InitType(ctx.pkg, toType(ctx, t.Type)) } } type ( valueMap map[any][]valueType // underlying Go value -> valueType valueType struct { pos token.Pos typ types.Type } ) // goVal returns the Go value for val, or nil. func goVal(val constant.Value) any { // val should exist, but be conservative and check if val == nil { return nil } // Match implementation restriction of other compilers. // gc only checks duplicates for integer, floating-point // and string values, so only create Go values for these // types. switch val.Kind() { case constant.Int: if x, ok := constant.Int64Val(val); ok { return x } if x, ok := constant.Uint64Val(val); ok { return x } case constant.Float: if x, ok := constant.Float64Val(val); ok { return x } case constant.String: return constant.StringVal(val) } return nil } // ----------------------------------------------------------------------------- ================================================ FILE: cl/typeparams.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cl import ( "go/types" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/token" ) func toTermList(ctx *blockCtx, expr ast.Expr) []*types.Term { retry: switch v := expr.(type) { case *ast.UnaryExpr: if v.Op != token.TILDE { panic(ctx.newCodeErrorf(v.Pos(), v.End(), "invalid op %v must ~", v.Op)) } return []*types.Term{types.NewTerm(true, toType(ctx, v.X))} case *ast.BinaryExpr: if v.Op != token.OR { panic(ctx.newCodeErrorf(v.Pos(), v.End(), "invalid op %v must |", v.Op)) } return append(toTermList(ctx, v.X), toTermList(ctx, v.Y)...) case *ast.ParenExpr: expr = v.X goto retry } return []*types.Term{types.NewTerm(false, toType(ctx, expr))} } func toBinaryExprType(ctx *blockCtx, v *ast.BinaryExpr) types.Type { return types.NewInterfaceType(nil, []types.Type{types.NewUnion(toTermList(ctx, v))}) } func toUnaryExprType(ctx *blockCtx, v *ast.UnaryExpr) types.Type { return types.NewInterfaceType(nil, []types.Type{types.NewUnion(toTermList(ctx, v))}) } func toTypeParams(ctx *blockCtx, params *ast.FieldList) []*types.TypeParam { if params == nil { return nil } return collectTypeParams(ctx, params) } func recvTypeParams(ctx *blockCtx, typ ast.Expr, named *types.Named) (tparams []*types.TypeParam) { orgTypeParams := named.TypeParams() if orgTypeParams == nil { return nil } L: for { switch t := typ.(type) { case *ast.ParenExpr: typ = t.X case *ast.StarExpr: typ = t.X default: break L } } switch t := typ.(type) { case *ast.IndexExpr: if orgTypeParams.Len() != 1 { panic(ctx.newCodeErrorf(typ.Pos(), typ.End(), "got 1 type parameter, but receiver base type declares %v", orgTypeParams.Len())) } v := t.Index.(*ast.Ident) tp := orgTypeParams.At(0) obj := types.NewTypeName(v.Pos(), tp.Obj().Pkg(), v.Name, nil) tparams = []*types.TypeParam{types.NewTypeParam(obj, tp.Constraint())} case *ast.IndexListExpr: n := len(t.Indices) if n != orgTypeParams.Len() { panic(ctx.newCodeErrorf(typ.Pos(), typ.End(), "got %v arguments but %v type parameters", n, orgTypeParams.Len())) } tparams = make([]*types.TypeParam, n) for i := 0; i < n; i++ { v := t.Indices[i].(*ast.Ident) tp := orgTypeParams.At(i) obj := types.NewTypeName(v.Pos(), tp.Obj().Pkg(), v.Name, nil) tparams[i] = types.NewTypeParam(obj, tp.Constraint()) } default: panic(ctx.newCodeErrorf(typ.Pos(), typ.End(), "cannot use generic type %v without instantiation", named)) } return } func toFuncType(ctx *blockCtx, typ *ast.FuncType, recv *types.Var, d *ast.FuncDecl) *types.Signature { var typeParams []*types.TypeParam if recv != nil && d.Recv != nil { typ := recv.Type() if pt, ok := typ.(*types.Pointer); ok { typ = pt.Elem() } typeParams = recvTypeParams(ctx, d.Recv.List[0].Type, typ.(*types.Named)) } else { typeParams = toTypeParams(ctx, typ.TypeParams) } if len(typeParams) > 0 { ctx.tlookup = &typeParamLookup{typeParams} defer func() { ctx.tlookup = nil }() } params, variadic := toParams(ctx, typ.Params.List) results := toResults(ctx, typ.Results) if recv != nil { return types.NewSignatureType(recv, typeParams, nil, params, results, variadic) } return types.NewSignatureType(recv, nil, typeParams, params, results, variadic) } type typeParamLookup struct { typeParams []*types.TypeParam } func (p *typeParamLookup) Lookup(name string) *types.TypeParam { for _, t := range p.typeParams { tname := t.Obj().Name() if tname != "_" && name == tname { return t } } return nil } func initType(ctx *blockCtx, named *types.Named, spec *ast.TypeSpec) { typeParams := toTypeParams(ctx, spec.TypeParams) if len(typeParams) > 0 { named.SetTypeParams(typeParams) ctx.tlookup = &typeParamLookup{typeParams} defer func() { ctx.tlookup = nil }() } org := ctx.inInst ctx.inInst = 0 defer func() { ctx.inInst = org }() typ := toType(ctx, spec.Type) retry: switch t := typ.(type) { case *types.Named: typ = getUnderlying(ctx, t) case *types.Alias: typ = types.Unalias(t) goto retry } named.SetUnderlying(typ) } func getRecvType(expr ast.Expr) (typ ast.Expr, ptr bool, ok bool) { typ = expr L: for { switch t := typ.(type) { case *ast.ParenExpr: typ = t.X case *ast.StarExpr: if ptr { ok = false return } ptr = true typ = t.X default: break L } } switch t := typ.(type) { case *ast.IndexExpr: typ = t.X case *ast.IndexListExpr: typ = t.X } ok = true return } func collectTypeParams(ctx *blockCtx, list *ast.FieldList) []*types.TypeParam { var tparams []*types.TypeParam // Declare type parameters up-front, with empty interface as type bound. // The scope of type parameters starts at the beginning of the type parameter // list (so we can have mutually recursive parameterized interfaces). for _, f := range list.List { tparams = declareTypeParams(ctx, tparams, f.Names) } ctx.tlookup = &typeParamLookup{tparams} defer func() { ctx.tlookup = nil }() index := 0 for _, f := range list.List { var bound types.Type // NOTE: we may be able to assert that f.Type != nil here, but this is not // an invariant of the AST, so we are cautious. if f.Type != nil { bound = boundTypeParam(ctx, f.Type) if isTypeParam(bound) { // We may be able to allow this since it is now well-defined what // the underlying type and thus type set of a type parameter is. // But we may need some additional form of cycle detection within // type parameter lists. //check.error(f.Type, MisplacedTypeParam, "cannot use a type parameter as constraint") bound = types.Typ[types.Invalid] } else if t, ok := bound.(*types.Named); ok { if t.Underlying() == nil { // check named underlying is nil ctx.loadNamed(ctx.pkg, t) } } } else { bound = types.Typ[types.Invalid] } for i := range f.Names { tparams[index+i].SetConstraint(bound) } index += len(f.Names) } return tparams } func declareTypeParams(ctx *blockCtx, tparams []*types.TypeParam, names []*ast.Ident) []*types.TypeParam { // Use Typ[Invalid] for the type constraint to ensure that a type // is present even if the actual constraint has not been assigned // yet. // TODO(gri) Need to systematically review all uses of type parameter // constraints to make sure we don't rely on them if they // are not properly set yet. for _, name := range names { tname := types.NewTypeName(name.Pos(), ctx.pkg.Types, name.Name, nil) tpar := types.NewTypeParam(tname, types.Typ[types.Invalid]) // assigns type to tpar as a side-effect // check.declare(check.scope, name, tname, check.scope.pos) // TODO(gri) check scope position tparams = append(tparams, tpar) } return tparams } func isTypeParam(t types.Type) bool { _, ok := t.(*types.TypeParam) return ok } func isSpecificSliceType(ctx *blockCtx, typ types.Type) bool { if typ == nil { return false } var t *types.Slice switch tt := typ.(type) { case *types.Named: t = getUnderlying(ctx, tt).(*types.Slice) case *types.Slice: t = tt default: return false } _, ok := t.Elem().(*types.TypeParam) return !ok } func boundTypeParam(ctx *blockCtx, x ast.Expr) types.Type { // A type set literal of the form ~T and A|B may only appear as constraint; // embed it in an implicit interface so that only interface type-checking // needs to take care of such type expressions. wrap := false switch op := x.(type) { case *ast.UnaryExpr: wrap = op.Op == token.TILDE case *ast.BinaryExpr: wrap = op.Op == token.OR } if wrap { x = &ast.InterfaceType{Methods: &ast.FieldList{List: []*ast.Field{{Type: x}}}} t := toType(ctx, x) // mark t as implicit interface if all went well if t, _ := t.(*types.Interface); t != nil { t.MarkImplicit() } return t } return toType(ctx, x) } func namedIsTypeParams(ctx *blockCtx, t *types.Named) bool { o := t.Obj() if o.Pkg() == ctx.pkg.Types { if _, ok := ctx.generics[o.Name()]; !ok { return false } ctx.loadType(o.Name()) } return t.Obj() != nil && t.TypeArgs() == nil && t.TypeParams() != nil } ================================================ FILE: cl/typeparams_test.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cl_test import ( "go/scanner" "os" "runtime" "testing" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/cl/cltest" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/parser/fsx/memfs" ) func TestTypeParams(t *testing.T) { gopMixedClTest(t, "main", `package main type Data[X, Y any] struct { v X } func (p *Data[T, R]) foo() { fmt.Println(p.v) } `, ` v := Data[int, float64]{1} v.foo() `, `package main func main() { v := Data[int, float64]{1} v.foo() } `) } func TestTypeParamsFunc(t *testing.T) { gopMixedClTest(t, "main", `package main type Number interface { ~int | ~uint | (float64) } func Sum[T Number](vec []T) T { var sum T for _, elt := range vec { sum = sum + elt } return sum } func At[T interface{ ~[]E }, E any](x T, i int) E { return x[i] } func Loader[T1 any, T2 any](p1 T1, p2 T2) T1 { return p1 } func Add[T1 any, T2 ~int|~uint](v1 T1, v2 ...T2) (sum T2) { println(v1) for _, v := range v2 { sum += v } return sum } type Int []int var MyInts = Int{1,2,3,4} `, ` _ = At[[]int] _ = At[[]int,int] _ = Sum[int] _ = Loader[*int,int] _ = Add[string,int] var s1 int = Sum([1, 2, 3]) var s2 int = Sum[int]([1, 2, 3]) var v1 int = At([1, 2, 3], 1) var v2 int = At[[]int]([1, 2, 3], 1) var v3 int = At[[]int, int]([1, 2, 3], 1) var n1 int = Add("hello", 1, 2, 3) var n2 int = Add[string]("hello", 1, 2, 3) var n3 int = Add[string, int]("hello", 1, 2, 3) var n4 int = Add("hello", [1, 2, 3]...) var n5 int = Add("hello", [1, 2, 3]...) var n6 int = Add[string]("hello", MyInts...) var n7 int = Add[string, int]("hello", [1, 2, 3]...) var p1 *int = Loader[*int](nil, 1) var p2 *int = Loader[*int, int](nil, 1) var fn1 func(p1 *int, p2 int) *int fn1 = Loader[*int, int] fn1(nil, 1) `, `package main func main() { _ = At[[]int] _ = At[[]int, int] _ = Sum[int] _ = Loader[*int, int] _ = Add[string, int] var s1 int = Sum([]int{1, 2, 3}) var s2 int = Sum[int]([]int{1, 2, 3}) var v1 int = At([]int{1, 2, 3}, 1) var v2 int = At[[]int]([]int{1, 2, 3}, 1) var v3 int = At[[]int, int]([]int{1, 2, 3}, 1) var n1 int = Add("hello", 1, 2, 3) var n2 int = Add[string]("hello", 1, 2, 3) var n3 int = Add[string, int]("hello", 1, 2, 3) var n4 int = Add("hello", []int{1, 2, 3}...) var n5 int = Add("hello", []int{1, 2, 3}...) var n6 int = Add[string]("hello", MyInts...) var n7 int = Add[string, int]("hello", []int{1, 2, 3}...) var p1 *int = Loader[*int](nil, 1) var p2 *int = Loader[*int, int](nil, 1) var fn1 func(p1 *int, p2 int) *int fn1 = Loader[*int, int] fn1(nil, 1) } `) } func TestTypeParamsType(t *testing.T) { gopMixedClTest(t, "main", `package main type Data[T any] struct { v T } func(p *Data[T]) Set(v T) { p.v = v } func(p *(Data[T1])) Set2(v T1) { p.v = v } type sliceOf[E any] interface { ~[]E } type Slice[S sliceOf[T], T any] struct { Data S } func (p *Slice[S, T]) Append(t ...T) S { p.Data = append(p.Data, t...) return p.Data } func (p *Slice[S1, T1]) Append2(t ...T1) S1 { p.Data = append(p.Data, t...) return p.Data } type ( DataInt = Data[int] SliceInt = Slice[[]int,int] ) `, ` type DataString = Data[string] type SliceString = Slice[[]string,string] println(DataInt{1}.v) println(DataString{"hello"}.v) println(Data[int]{100}.v) println(Data[string]{"hello"}.v) println(Data[struct{X int;Y int}]{}.v.X) v1 := SliceInt{} v2 := SliceString{} v3 := Slice[[]int,int]{} v3.Append([1,2,3,4]...) v3.Append2([1,2,3,4]...) `, `package main import "fmt" type DataString = Data[string] type SliceString = Slice[[]string, string] func main() { fmt.Println(DataInt{1}.v) fmt.Println(DataString{"hello"}.v) fmt.Println(Data[int]{100}.v) fmt.Println(Data[string]{"hello"}.v) fmt.Println(Data[struct { X int Y int }]{}.v.X) v1 := SliceInt{} v2 := SliceString{} v3 := Slice[[]int, int]{} v3.Append([]int{1, 2, 3, 4}...) v3.Append2([]int{1, 2, 3, 4}...) } `) } func TestTypeParamsComparable(t *testing.T) { gopMixedClTest(t, "main", `package main // Index returns the index of x in s, or -1 if not found. func Index[T comparable](s []T, x T) int { for i, v := range s { // v and x are type T, which has the comparable // constraint, so we can use == here. if v == x { return i } } return -1 } var IndexInt = Index[int] `, ` v1 := IndexInt([1,2,3,4],1) v2 := Index([1,2,3,4],1) v3 := Index[int]([1,2,3,4],1) v4 := Index(["a","b","c","d"],"b") `, `package main func main() { v1 := IndexInt([]int{1, 2, 3, 4}, 1) v2 := Index([]int{1, 2, 3, 4}, 1) v3 := Index[int]([]int{1, 2, 3, 4}, 1) v4 := Index([]string{"a", "b", "c", "d"}, "b") } `) } func mixedErrorTest(t *testing.T, msg, gocode, gopcode string) { mixedErrorTestEx(t, "main", msg, gocode, gopcode) } func mixedErrorTestEx(t *testing.T, pkgname, msg, gocode, gopcode string) { fs := memfs.TwoFiles("/foo", "a.go", gocode, "b.xgo", gopcode) pkgs, err := parser.ParseFSDir(cltest.Conf.Fset, fs, "/foo", parser.Config{}) if err != nil { scanner.PrintError(os.Stderr, err) t.Fatal("parser.ParseFSDir failed") } conf := *cltest.Conf conf.NoFileLine = false conf.RelativeBase = "/foo" bar := pkgs[pkgname] _, err = cl.NewPackage("", bar, &conf) if err == nil { t.Fatal("no error?") } if ret := err.Error(); ret != msg { t.Fatalf("\nError: \"%s\"\nExpected: \"%s\"\n", ret, msg) } } func TestTypeParamsErrorInstantiate(t *testing.T) { var msg string switch runtime.Version()[:6] { case "go1.18": msg = `b.xgo:2:1: uint does not implement Number` case "go1.19": msg = `b.xgo:2:1: uint does not implement Number (uint missing in ~int | float64)` default: msg = `b.xgo:2:1: uint does not satisfy Number (uint missing in ~int | float64)` } mixedErrorTest(t, msg, ` package main type Number interface { ~int | float64 } func Sum[T Number](vec []T) T { var sum T for _, elt := range vec { sum = sum + elt } return sum } var SumInt = Sum[int] `, ` Sum[uint] `) } func TestTypeParamsErrorMatch(t *testing.T) { var msg string switch runtime.Version()[:6] { case "go1.18", "go1.19": msg = `b.xgo:2:5: T does not match ~[]E` case "go1.20": msg = `b.xgo:2:5: int does not match ~[]E` default: msg = `b.xgo:2:5: T (type int) does not satisfy interface{interface{~[]E}}` } mixedErrorTest(t, msg, ` package main func At[T interface{ ~[]E }, E any](x T, i int) E { return x[i] } var AtInt = At[[]int] `, ` _ = At[int] `) } func TestTypeParamsErrInferFunc(t *testing.T) { mixedErrorTest(t, `b.xgo:2:5: cannot infer T2 (declared at /foo/a.go:4:21)`, ` package main func Loader[T1 any, T2 any](p1 T1, p2 T2) T1 { return p1 } `, ` _ = Loader[int] `) } func TestTypeParamsErrArgumentsParameters1(t *testing.T) { mixedErrorTest(t, `b.xgo:2:7: got 1 type arguments but Data[T1, T2 interface{}] has 2 type parameters`, ` package main type Data[T1 any, T2 any] struct { v1 T1 v2 T2 } `, ` var v Data[int] `) } func TestTypeParamsErrArgumentsParameters2(t *testing.T) { mixedErrorTest(t, `b.xgo:2:7: got 3 type arguments but Data[T1, T2 interface{}] has 2 type parameters`, ` package main type Data[T1 any, T2 any] struct { v1 T1 v2 T2 } `, ` var v Data[int,int,int] `) } func TestTypeParamsErrArgumentsParameters3(t *testing.T) { mixedErrorTest(t, `b.xgo:2:1: got 3 type arguments but func[T1, T2 interface{}](t1 T1, t2 T2) has 2 type parameters`, ` package main func Test[T1 any, T2 any](t1 T1, t2 T2) { println(t1,t2) } `, ` Test[int,int,int](1,2) `) } func TestTypeParamsErrCallArguments1(t *testing.T) { mixedErrorTest(t, `b.xgo:2:1: not enough arguments in call to Test have (untyped int) want (T1, T2)`, ` package main func Test[T1 any, T2 any](t1 T1, t2 T2) { println(t1,t2) } `, ` Test(1) `) } func TestTypeParamsErrCallArguments2(t *testing.T) { mixedErrorTest(t, `b.xgo:2:1: too many arguments in call to Test have (untyped int, untyped int, untyped int) want (T1, T2)`, ` package main func Test[T1 any, T2 any](t1 T1, t2 T2) { println(t1,t2) } `, ` Test(1,2,3) `) } func TestTypeParamsErrCallArguments3(t *testing.T) { mixedErrorTest(t, `b.xgo:2:1: too many arguments in call to Test have (untyped int, untyped int) want ()`, ` package main func Test[T1 any, T2 any]() { var t1 T1 var t2 T2 println(t1,t2) } `, ` Test(1,2) `) } func TestTypeParamsErrCallVariadicArguments1(t *testing.T) { mixedErrorTest(t, `b.xgo:2:1: not enough arguments in call to Add have () want (T1, ...T2)`, ` package main func Add[T1 any, T2 ~int|~uint](v1 T1, v2 ...T2) (sum T2) { println(v1) for _, v := range v2 { sum += v } return sum } `, ` Add() `) } func TestTypeParamsErrCallVariadicArguments2(t *testing.T) { mixedErrorTest(t, `b.xgo:2:1: cannot infer T2 (a.go:4:18)`, ` package main func Add[T1 any, T2 ~int|~uint](v1 T1, v2 ...T2) (sum T2) { println(v1) for _, v := range v2 { sum += v } return sum } `, ` Add(1) `) } func TestTypeParamsRecvTypeError1(t *testing.T) { mixedErrorTest(t, `a.go:7:9: cannot use generic type Data[T interface{}] without instantiation`, ` package main type Data[T any] struct { v T } func(p *Data) Test() { } `, ` Data[int]{}.Test() `) } func TestTypeParamsRecvTypeError2(t *testing.T) { mixedErrorTest(t, `a.go:7:9: got 2 arguments but 1 type parameters`, ` package main type Data[T any] struct { v T } func(p *Data[T1,T2]) Test() { } `, ` Data[int]{}.Test() `) } func TestTypeParamsRecvTypeError3(t *testing.T) { mixedErrorTest(t, `a.go:8:9: got 1 type parameter, but receiver base type declares 2`, ` package main type Data[T1 any, T2 any] struct { v1 T1 v2 T2 } func(p *Data[T1]) Test() { } `, ` Data[int,int]{}.Test() `) } func TestGenericTypeWithoutInst1(t *testing.T) { mixedErrorTest(t, `a.go:8:9: cannot use generic type Data[T1, T2 interface{}] without instantiation`, ` package main type Data[T1 any, T2 any] struct { v1 T1 v2 T2 } func(p *Data) Test() { } `, ` var v Data[int,int] `) } func TestGenericTypeWithoutInst2(t *testing.T) { mixedErrorTest(t, `a.go:10:2: cannot use generic type Data[T1, T2 interface{}] without instantiation`, ` package main type Data[T1 any, T2 any] struct { v1 T1 v2 T2 } type My[T any] struct { Data } `, ` var v My[int] `) } func TestGenericTypeWithoutInst3(t *testing.T) { mixedErrorTest(t, `a.go:10:2: cannot use generic type Data[T1, T2 interface{}] without instantiation`, ` package main type Data[T1 any, T2 any] struct { v1 T1 v2 T2 } type My struct { Data } `, ` var v My `) } func TestGenericTypeWithoutInst4(t *testing.T) { mixedErrorTest(t, `a.go:10:15: cannot use generic type Data[T1, T2 interface{}] without instantiation`, ` package main type Data[T1 any, T2 any] struct { v1 T1 v2 T2 } type My struct { v map[string]Data } `, ` var v My `) } func TestGenericTypeWithoutInst5(t *testing.T) { mixedErrorTest(t, `b.xgo:2:7: cannot use generic type Data[T1, T2 interface{}] without instantiation`, ` package main type Data[T1 any, T2 any] struct { v1 T1 v2 T2 } `, ` var v Data `) } func TestGenericTypeWithoutInst6(t *testing.T) { mixedErrorTest(t, `b.xgo:2:8: cannot use generic type Data[T1, T2 interface{}] without instantiation`, ` package main type Data[T1 any, T2 any] struct { v1 T1 v2 T2 } `, ` type T Data `) } func TestGenericTypeWithoutInst7(t *testing.T) { mixedErrorTest(t, `b.xgo:3:2: cannot use generic type Data[T1, T2 interface{}] without instantiation`, ` package main type Data[T1 any, T2 any] struct { v1 T1 v2 T2 } `, ` type My struct { Data } `) } func TestGenericTypeWithoutInst8(t *testing.T) { mixedErrorTest(t, `b.xgo:2:23: cannot use generic type Data[T1, T2 interface{}] without instantiation`, ` package main type Data[T1 any, T2 any] struct { v1 T1 v2 T2 } `, ` func test(v1 int, v2 *Data) { } `) } func TestGenericTypeCompositeLit(t *testing.T) { gopMixedClTest(t, "main", `package main type A[T any] struct { m T } type B[T any] struct { n A[T] } `, ` var a [2]int if 0 == a[1] { println "world" } println B[int]{}.n if 0 < (B[int]{}).n.m { } `, `package main import "fmt" var a [2]int func main() { if 0 == a[1] { fmt.Println("world") } fmt.Println(B[int]{}.n) if 0 < (B[int]{}).n.m { } } `) } func TestInferFuncLambda(t *testing.T) { gopMixedClTest(t, "main", `package main func ListMap[T any](ar []T, fn func(v T) T)[]T { for i, v := range ar { ar[i] = fn(v) } return ar } `, ` println ListMap([1,2,3,4], x => x*x) ListMap [1,2,3,4], x => { println x return x } `, `package main import "fmt" func main() { fmt.Println(ListMap([]int{1, 2, 3, 4}, func(x int) int { return x * x })) ListMap([]int{1, 2, 3, 4}, func(x int) int { fmt.Println(x) return x }) } `) } func TestInferOverloadFuncLambda(t *testing.T) { gopMixedClTest(t, "main", `package main func ListMap__0[T any](ar []T, fn func(v T) T)[]T { for i, v := range ar { ar[i] = fn(v) } return ar } func ListMap__1(a string, fn func(s string)) { for _, c := range a { fn(string(c)) } } `, ` println ListMap([1,2,3,4], x => x*x) ListMap [1,2,3,4], x => { println x return x } ListMap "hello", x => { println x } `, `package main import "fmt" func main() { fmt.Println(ListMap__0([]int{1, 2, 3, 4}, func(x int) int { return x * x })) ListMap__0([]int{1, 2, 3, 4}, func(x int) int { fmt.Println(x) return x }) ListMap__1("hello", func(x string) { fmt.Println(x) }) } `) } func TestGenericFuncAlias(t *testing.T) { gopClTest(t, `import "github.com/goplus/xgo/cl/internal/overload/foo" foo.test(100) foo.test("hello",100) foo.test__1(100) foo.test__1[int](100) foo.test__2(1, true) foo.test__2[int, string](1, "hello") `, `package main import "github.com/goplus/xgo/cl/internal/overload/foo" func main() { foo.Test__1(100) foo.Test__2("hello", 100) foo.Test__1(100) foo.Test__1[int](100) foo.Test__2(1, true) foo.Test__2[int, string](1, "hello") } `) } func TestGoptLambdaFunc(t *testing.T) { gopClTest(t, ` import "github.com/goplus/xgo/cl/internal/overload/bar" type Message struct { info string } p := &bar.Player{} p.onCmd Message, msg => { echo msg.info return nil } p.onCmd int, Message, 100, (n,msg) => { echo n, msg.info return nil } `, `package main import ( "fmt" "github.com/goplus/xgo/cl/internal/overload/bar" ) type Message struct { info string } func main() { p := &bar.Player{} bar.XGot_Player_XGox_OnCmd__0[Message](p, func(msg Message) error { fmt.Println(msg.info) return nil }) bar.XGot_Player_XGox_OnCmd__1[int, Message](p, 100, func(n int, msg Message) error { fmt.Println(n, msg.info) return nil }) } `) } func TestGoptLambdaError(t *testing.T) { codeErrorTest(t, `bar.xgo:8:9: 100 not type`, ` import "github.com/goplus/xgo/cl/internal/overload/bar" type Message struct { info string } p := &bar.Player{} p.onCmd 100, Message, 100, (n, msg) => { echo msg.info return nil } `) var msg string switch runtime.Version()[:6] { case "go1.18": msg = "bar.xgo:8:1: string does not implement ~int" case "go1.19": msg = "bar.xgo:8:1: string does not implement ~int (string missing in ~int)" default: msg = "bar.xgo:8:1: string does not satisfy ~int (string missing in ~int)" } codeErrorTest(t, msg, ` import "github.com/goplus/xgo/cl/internal/overload/bar" type Message struct { info string } p := &bar.Player{} p.onCmd string, Message,"hello",(n, msg) => { echo msg.info return nil } `) } func TestAliasTypeparams(t *testing.T) { gopMixedClTest(t, "main", `package main type Set[T comparable] = map[T]struct{} `, ` set := Set[string]{ "go": {}, "gop": {}, "gox": {}, } echo set `, `package main import "fmt" func main() { set := Set[string]{"go": struct { }{}, "gop": struct { }{}, "gox": struct { }{}} fmt.Println(set) } `) gopMixedClTest(t, "main", `package main type Pair[T, U any] = struct { First T Second U } `, ` c := &Pair[string, bool]{"hello", true} echo c `, `package main import "fmt" func main() { c := &Pair[string, bool]{"hello", true} fmt.Println(c) } `) } ================================================ FILE: cmd/chore/goptestgo/goptestgo.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package main import ( "bytes" "crypto/sha1" "encoding/base64" "go/build" "log" "os" "os/exec" "path/filepath" "time" "github.com/goplus/xgo/tool" "github.com/goplus/xgo/x/gocmd" "github.com/goplus/xgo/x/xgoenv" ) func fileIsDirty(srcMod time.Time, destFile string) bool { fiDest, err := os.Stat(destFile) if err != nil { return true } return srcMod.After(fiDest.ModTime()) } func runGoFile(dir, file, fname string) { xgo := xgoenv.Get() conf := &tool.Config{XGo: xgo} confCmd := &gocmd.BuildConfig{XGo: xgo} fi, err := os.Stat(file) if err != nil { log.Panicln(err) } absFile, _ := filepath.Abs(file) hash := sha1.Sum([]byte(absFile)) outFile := dir + "g" + base64.RawURLEncoding.EncodeToString(hash[:]) + fname if fileIsDirty(fi.ModTime(), outFile) { err = tool.RunFiles(outFile, []string{file}, nil, conf, confCmd) if err != nil { os.Remove(outFile) switch e := err.(type) { case *exec.ExitError: os.Exit(e.ExitCode()) default: log.Fatalln("runGoFile:", err) } } } } var ( goRunPrefix = []byte("// run\n") ) var ( skipFileNames = map[string]struct{}{ "convert4.go": {}, "peano.go": {}, "bug295.go": {}, // import . "XXX" "issue15071.dir": {}, // dir "issue29612.dir": {}, "issue31959.dir": {}, "issue29504.go": {}, // line "issue18149.go": {}, "issue22662.go": {}, "issue27201.go": {}, "issue46903.go": {}, "issue50190.go": {}, // interesting, should be fixed "nilptr_aix.go": {}, "inline_literal.go": {}, "returntype.go": {}, // not a problem "unsafebuiltins.go": {}, } ) func gopTestRunGo(dir string) { home, _ := os.UserHomeDir() targetDir := home + "/.xgo/run/" os.MkdirAll(targetDir, 0777) filepath.Walk(dir, func(file string, fi os.FileInfo, err error) error { name := fi.Name() if err != nil || fi.IsDir() { if _, ok := skipFileNames[name]; ok { return filepath.SkipDir } return nil } if _, ok := skipFileNames[name]; ok { return nil } ext := filepath.Ext(name) if ext != ".go" { return nil } data, err := os.ReadFile(file) if err != nil { log.Panicln(err) } if !bytes.HasPrefix(data, goRunPrefix) { return nil } log.Println("==> gop run -v", file) runGoFile(targetDir, file, name) return nil }) } // goptestgo: run all $GOROOT/test/*.go func main() { dir := filepath.Join(build.Default.GOROOT, "test") gopTestRunGo(dir) } ================================================ FILE: cmd/chore/xgobuiltingen/builtin.gox ================================================ import ( "go/ast" ) var ( Name string Fn reference ) func genAST() *ast.FuncDecl { f := Fn ref := f.getAST() newName := Name.capitalize return { Doc: docForFunc(ref.Doc, f.Name, newName), Name: ast.newIdent(newName), Type: reference.toFuncType(ref.Type, f.Pkg), } } func genMethodAST(methods []builtin) *ast.FuncDecl { f := Fn at := f.Pkg ref := f.getAST() ex := f.Exargs mt, recvType := reference.toMethodType(ref.Type, ex, at) if at == "" { // builtin recvType = methods[methods.len-1].genAST().Type.Params.List[0].Type } recvName := recvNameOf(recvType) return { Doc: docForMethod(ref.Doc, at, f.Name, Name, recvName, ex), Name: ast.newIdent(Name), Type: mt, Recv: {List: {{ Names: []*ast.Ident{ast.newIdent(recvName)}, Type: recvType, }}}, } } ================================================ FILE: cmd/chore/xgobuiltingen/builtingen.gox ================================================ import ( "bytes" "go/ast" "go/format" "go/parser" "go/token" "os" xgoast "xgo/ast" "xgo/ast/gopq" "xgo/parser" ) type builtinTI struct { Methods []builtin } var ( Builtins []builtin Types []*builtinTI ) func gen() []byte { f := &ast.File{ Name: ast.newIdent("builtin"), Decls: make([]ast.Decl, 0, 128), } f.Decls <- &ast.GenDecl{ Tok: token.IMPORT, Specs: []ast.Spec{ importSpec("github.com/qiniu/x/osx"), importSpec("io"), importSpec("os"), importSpec("reflect"), }, } genDecls f b := new(bytes.Buffer) format.node! b, fset, f return format.source(b.bytes)! } func genDecls(f *ast.File) { for built in Builtins { f.Decls <- built.genAST() } for t in Types { mthds := t.Methods for m in mthds { f.Decls <- m.genMethodAST(mthds) } } } func initBuiltinTIs(fn gopq.NodeSet, f *xgoast.File) (tistr *builtinTI) { ti := fn.body.any.assignStmt.rhs(0).x.compositeLit("BuiltinTI") methods := ti.elt("methods").cache for method in methods { aTI := &builtinTI{} for item in method.elt { mthd := item.elt(0).unquotedString! fn := item.elt(1).one if ref := fn.callExpr.one; ref.ok { pkg := ref.fun.x.ident! name := ref.arg(0).unquotedString! var ex *exargs if e := item.elt(2).compositeLit("bmExargs").one; e.ok { pos := e.positions! code := gopq.codeOf(fset, f, pos[2]+1, pos[3]) ex = &exargs{e.eltLen!, code} } aTI.Methods <- builtin{mthd, {pkg, name, ex}} } else { name := fn.ident! aTI.Methods <- builtin{mthd, {"", name, nil}} } } Types <- aTI if len(aTI.Methods) > 10 { tistr = aTI } } return } func newBuiltinDefault(fn gopq.NodeSet, tistr *builtinTI) { methods := fn.body.any.exprStmt.x.callExpr("ti.AddMethods").cache for method in methods { aTI := &builtinTI{} for arg in method.varg(0).x { mthd := arg.elt("Name").unquotedString! ref := arg.elt("Fn").callExpr.one pkg := ref.fun.x.ident! name := ref.arg(0).unquotedString! if pkg == "strx" { tistr.Methods <- builtin{mthd, {pkg, name, nil}} } else { aTI.Methods <- builtin{mthd, {pkg, name, nil}} } } if len(aTI.Methods) > 0 { Types <- aTI } } } func initBuiltin(fn gopq.NodeSet) { stmt := fn.body.any.exprStmt.x.cache item := stmt.callExpr("scope.Insert").arg(0).cache for call in item.callExpr("gogen.NewOverloadFunc") { built := call.arg(2).unquotedString! if built != "newRange" { // hide builtin `newRange` ref := call.arg(3).callExpr.one pkg := ref.fun.x.ident! name := ref.arg(0).unquotedString! Builtins <- builtin{built, {pkg, name, nil}} } } for call in stmt.callExpr("initBuiltinFns") { pkg := call.arg(2).ident! builtins := call.arg(3).unquotedStringElts! for built in builtins { Builtins <- builtin{built, {pkg, built.capitalize, nil}} } } } f := parser.parseFile(fset, "${root}/../gogen/builtin.go", nil, parser.ParseComments)! fns := gopq.one(f).funcs tistr := initBuiltinTIs(fns.funcDecl("initBuiltinTIs").one, f) fns = gopq.fromFile(fset, "${root}/cl/builtin.go", nil, parser.ParseComments)!.funcs initBuiltin fns.funcDecl("initBuiltin").one newBuiltinDefault fns.funcDecl("newBuiltinDefault").one, tistr b := gen() os.Stdout.write b os.writeFile "${root}/builtin/doc.xgo", b, 0777 ================================================ FILE: cmd/chore/xgobuiltingen/helper.xgo ================================================ import ( "go/ast" ) func recvNameOf(recvType ast.Expr) string { var name string switch t := recvType.(type) { case *ast.Ident: name = t.Name[:1] case *ast.SelectorExpr: name = t.Sel.Name[:1] default: return "v" } return name.toLower } func importSpec(path string) *ast.ImportSpec { return { Path: { Value: path.quote, }, } } func rmExargs(list []*ast.Field, ex *exargs) []*ast.Field { if ex == nil { return list } n := len(list) ret := make([]*ast.Field, n) for i, f in list { ret[i] = f } exargs := ex.N for n > 0 && exargs > 0 { f := ret[n-1] if c := len(f.Names); c > exargs { f.Names = f.Names[:c-exargs] break } else { exargs -= c } n-- } return ret[:n] } func toParams(params *ast.FieldList, at string) *ast.FieldList { if params == nil { return nil } list := make([]*ast.Field, len(params.List)) for i, p in params.List { typ := p.Type switch t := typ.(type) { case *ast.Ident: if t.isExported { typ = &ast.SelectorExpr{ X: ast.newIdent(at), Sel: t, } } case *ast.StarExpr: if x, ok := t.X.(*ast.Ident); ok && x.isExported { typ = &ast.StarExpr{ X: &ast.SelectorExpr{ X: ast.newIdent(at), Sel: x, }, } } } list[i] = { Doc: p.Doc, Names: p.Names, Type: typ, Tag: p.Tag, Comment: p.Comment, } } return {List: list} } func docForFunc(doc *ast.CommentGroup, oldName, newName string) *ast.CommentGroup { if doc == nil { return nil } list := make([]*ast.Comment, len(doc.List)) for i, c := range doc.List { text := c.Text.replaceAll(oldName, newName) list[i] = {Text: text} } return {List: list} } func docForMethod(doc *ast.CommentGroup, at, oldName, newName, recvName string, ex *exargs) *ast.CommentGroup { if doc == nil { return nil } if ex == nil { return docForFunc(doc, oldName, newName) } list := make([]*ast.Comment, 0, len(doc.List)+2) list <- &ast.Comment{Text: "// ${newName} is equivalent to ${at}.${oldName}(${recvName}, ${ex.Code})"} list <- &ast.Comment{Text: "//"} for _, c := range doc.List { list <- &ast.Comment{Text: c.Text} } return {List: list} } ================================================ FILE: cmd/chore/xgobuiltingen/reference.gox ================================================ import ( "go/ast" "go/parser" "go/token" "runtime" "xgo/env" ) type exargs struct { N int Code string } var ( Pkg string Name string Exargs *exargs ) func .toFuncType(t *ast.FuncType, at string) *ast.FuncType { return { Params: t.Params, Results: toParams(t.Results, at), } } func .toMethodType(t *ast.FuncType, ex *exargs, at string) (mt *ast.FuncType, recvType ast.Expr) { list := t.Params.List first := list[0] recvType = first.Type if len(first.Names) == 1 { list = list[1:] } else { list[0] = { Doc: first.Doc, Names: first.Names[1:], Type: recvType, Comment: first.Comment, } } mt = { Params: {List: rmExargs(list, ex)}, Results: toParams(t.Results, at), } return } var ( fset = token.newFileSet pkgs = map[string]*ast.Package{} root = env.XGOROOT() goroot = runtime.GOROOT() pkgDirs = { "": "${goroot}/src/builtin", "fmt": "${goroot}/src/fmt", "os": "${goroot}/src/os", "reflect": "${goroot}/src/reflect", "strconv": "${goroot}/src/strconv", "strings": "${goroot}/src/strings", "buil": "${root}/../x/xgo", "osx": "${root}/../x/osx", "stringslice": "${root}/../x/stringslice", "strx": "${root}/../x/stringutil", } ) func getAST() *ast.FuncDecl { name := Name if Pkg == "" { name = name.trimPrefix("bto").toLower } pkg := reference.pkgOf(Pkg) return reference.funcDecl(pkg, name) } func .funcDecl(pkg *ast.Package, name string) *ast.FuncDecl { for file in pkg.Files { for decl in file.Decls { if fn, ok := decl.(*ast.FuncDecl); ok && fn.Name.Name == name { return fn } } } panic("function not found: ${pkg.Name}.${name}") } func .pkgOf(pkgPath string) *ast.Package { if pkg, ok := pkgs[pkgPath]; ok { return pkg } dir, ok := pkgDirs[pkgPath] if !ok { panic("unknown package: ${pkgPath}") } for name, pkg in parser.parseDir(fset, dir, nil, parser.ParseComments)! { if name.hasSuffix("_test") { continue } pkgs[pkgPath] = pkg return pkg } panic("package not found: ${pkgPath}") } ================================================ FILE: cmd/chore/xgofullspec/fullspec.xgo ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import ( "flag" "os" "path/filepath" "xgo/parser" "xgo/parser/parsertest" "xgo/token" "xgo/x/xgoprojs" ) func dump(f any) { parsertest.FprintNode os.Stdout, "", f, "", " " } func isGopFile(file string) bool { if len(file) > 4 { switch file[len(file)-4:] { case ".xgo", ".gop", ".gox", ".gsh", ".spx", ".yap": return true } } return false } func parseFiles(files []string, dumpAST bool) (nErr int) { fset := token.newFileSet for file in files { fprintln os.Stderr, "\n==> Parsing ${file}" f, err := parser.parseFile(fset, file, nil, parser.ParseComments) if err != nil { fprintln os.Stderr, err nErr++ } else if dumpAST { dump f } } return } func parseDir(dir string, recursively, dumpAST bool) (nErr int) { fset := token.newFileSet filepath.walkDir dir, (file, d, err) => { if err != nil { return err } else if fname := d.name; d.isDir { if recursively { if fname.hasPrefix("_") || fname == "fullspec" { return filepath.SkipDir } } else if file != dir { return filepath.SkipDir } } else if !fname.hasPrefix("_") && isGopFile(fname) { fprintln os.Stderr, "\n==> Parsing ${file}" f, err := parser.parseFile(fset, file, nil, parser.ParseComments) if err != nil { fprintln os.Stderr, err nErr++ } else if dumpAST { dump f } } return nil } return } var ( flagDumpAST = flag.Bool("ast", false, "dump AST") ) flag.parse pattern := flag.args if pattern.len == 0 { echo "Usage: gopfullspec [-ast] packages" return } dumpAST := *flagDumpAST nErr := 0 projs, err := xgoprojs.parseAll(pattern...) if err != nil { fprintln os.Stderr, err os.exit 1 } for proj in projs { switch v := proj.(type) { case *xgoprojs.DirProj: dir := v.Dir recursively := dir.hasSuffix("/...") if recursively { dir = dir[:len(dir)-4] } nErr += parseDir(dir, recursively, dumpAST) case *xgoprojs.FilesProj: nErr += parseFiles(v.Files, dumpAST) default: panic "only support local files and directories" } } fprintln os.Stderr if nErr > 0 { os.exit 1 } ================================================ FILE: cmd/chore/xgominispec/minispec.xgo ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import ( "flag" "os" "path/filepath" "xgo/doc/spec/mini" "xgo/tpl" "xgo/tpl/matcher" "xgo/x/xgoprojs" ) func isGopFile(file string) bool { if len(file) > 4 { switch file[len(file)-4:] { case ".xgo", ".gop", ".gox", ".gsh", ".spx", ".yap": return true } } return false } func parseFiles(files []string, dumpAST bool) (nErr int) { for file in files { fprintln os.Stderr, "\n==> Parsing ${file}" f, err := mini.parseFile(nil, file, nil) if err != nil { fprintln os.Stderr, err nErr++ } else if dumpAST { tpl.dump f } } return } func parseDir(dir string, recursively, dumpAST bool) (nErr int) { filepath.walkDir dir, (file, d, err) => { if err != nil { return err } else if fname := d.name; d.isDir { if recursively { if fname.hasPrefix("_") || fname == "fullspec" { return filepath.SkipDir } } else if file != dir { return filepath.SkipDir } } else if !fname.hasPrefix("_") && isGopFile(fname) { fprintln os.Stderr, "\n==> Parsing ${file}" f, err := mini.parseFile(nil, file, nil) if err != nil { fprintln os.Stderr, err nErr++ } else if dumpAST { tpl.dump f } } return nil } return } var ( flagVerbose = flag.Bool("v", false, "print verbose information") flagDumpAST = flag.Bool("ast", false, "dump AST") ) flag.parse pattern := flag.args if pattern.len == 0 { echo "Usage: gopminispec [-v -ast] packages" return } if *flagVerbose { matcher.setDebug matcher.DbgFlagAll } dumpAST := *flagDumpAST nErr := 0 projs, err := xgoprojs.parseAll(pattern...) if err != nil { fprintln os.Stderr, err os.exit 1 } for proj in projs { switch v := proj.(type) { case *xgoprojs.DirProj: dir := v.Dir recursively := dir.hasSuffix("/...") if recursively { dir = dir[:len(dir)-4] } nErr += parseDir(dir, recursively, dumpAST) case *xgoprojs.FilesProj: nErr += parseFiles(v.Files, dumpAST) default: panic "only support local files and directories" } } fprintln os.Stderr if nErr > 0 { os.exit 1 } ================================================ FILE: cmd/hdq/fetch_cmd.gox ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( "encoding/json" "io" "log" "os" "xgo/dql/fetcher" ) use "fetch [flags] fetchType [input ... | -]" short "Fetch objects from the html source with the specified fetchType and inputs" run args => { if args.len < 1 { help return } fetchType := args[0] inputs := args[1:] if len(inputs) == 1 && inputs[0] == "-" { inputs = string(io.readAll(os.Stdin)!).fields } docs := make([]any, 0, len(inputs)) for input in inputs { log.println "==> Fetch", input doc := fetcher.do(fetchType, input)! docs <- doc } enc := json.newEncoder(os.Stdout) enc.setIndent "", " " enc.encode! docs } ================================================ FILE: cmd/hdq/list_cmd.gox ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( "xgo/dql/fetcher" ) use "list" short "List all supported fetchTypes." run => { for ft in fetcher.list { echo ft } } ================================================ FILE: cmd/hdq/main_app.gox ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( _ "github.com/qiniu/x/stream/http/cached" _ "xgo/dql/fetcher/github.com/issueTask" _ "xgo/dql/fetcher/github.com/repoList" _ "xgo/dql/fetcher/hrefs" _ "xgo/dql/fetcher/pkg.go.dev/importedBy" _ "xgo/dql/fetcher/pytorch.org/fndoc" ) use "hdq" short "hdq - An HTML DOM Query Tool (powered by XGo DQL)" ================================================ FILE: cmd/hdq/xgo_autogen.go ================================================ // Code generated by xgo (XGo); DO NOT EDIT. package main import ( "encoding/json" "fmt" "github.com/goplus/cobra/xcmd" "github.com/goplus/xgo/dql/fetcher" _ "github.com/goplus/xgo/dql/fetcher/github.com/issueTask" _ "github.com/goplus/xgo/dql/fetcher/github.com/repoList" _ "github.com/goplus/xgo/dql/fetcher/hrefs" _ "github.com/goplus/xgo/dql/fetcher/pkg.go.dev/importedBy" _ "github.com/goplus/xgo/dql/fetcher/pytorch.org/fndoc" "github.com/qiniu/x/errors" _ "github.com/qiniu/x/stream/http/cached" "io" "log" "os" "strings" ) const _ = true type Cmd_fetch struct { xcmd.Command *App } type Cmd_list struct { xcmd.Command *App } type App struct { xcmd.App } //line cmd/hdq/main_app.gox:26 func (this *App) MainEntry() { //line cmd/hdq/main_app.gox:26:1 this.Use("hdq") //line cmd/hdq/main_app.gox:28:1 this.Short("hdq - An HTML DOM Query Tool (powered by XGo DQL)") } func (this *App) Main() { _xgo_obj0 := &Cmd_fetch{App: this} _xgo_obj1 := &Cmd_list{App: this} xcmd.XGot_App_Main(this, _xgo_obj0, _xgo_obj1) } //line cmd/hdq/fetch_cmd.gox:24 func (this *Cmd_fetch) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/hdq/fetch_cmd.gox:24:1 this.Use("fetch [flags] fetchType [input ... | -]") //line cmd/hdq/fetch_cmd.gox:26:1 this.Short("Fetch objects from the html source with the specified fetchType and inputs") //line cmd/hdq/fetch_cmd.gox:28:1 this.Run__1(func(args []string) { //line cmd/hdq/fetch_cmd.gox:29:1 if len(args) < 1 { //line cmd/hdq/fetch_cmd.gox:30:1 this.Help() //line cmd/hdq/fetch_cmd.gox:31:1 return } //line cmd/hdq/fetch_cmd.gox:33:1 fetchType := args[0] //line cmd/hdq/fetch_cmd.gox:34:1 inputs := args[1:] //line cmd/hdq/fetch_cmd.gox:35:1 if len(inputs) == 1 && inputs[0] == "-" { //line cmd/hdq/fetch_cmd.gox:36:1 inputs = strings.Fields(string(func() (_xgo_ret []byte) { //line cmd/hdq/fetch_cmd.gox:36:1 var _xgo_err error //line cmd/hdq/fetch_cmd.gox:36:1 _xgo_ret, _xgo_err = io.ReadAll(os.Stdin) //line cmd/hdq/fetch_cmd.gox:36:1 if _xgo_err != nil { //line cmd/hdq/fetch_cmd.gox:36:1 _xgo_err = errors.NewFrame(_xgo_err, "io.readAll(os.Stdin)", "cmd/hdq/fetch_cmd.gox", 36, "main.Main") //line cmd/hdq/fetch_cmd.gox:36:1 panic(_xgo_err) } //line cmd/hdq/fetch_cmd.gox:36:1 return }())) } //line cmd/hdq/fetch_cmd.gox:38:1 docs := make([]interface{}, 0, len(inputs)) for //line cmd/hdq/fetch_cmd.gox:39:1 _, input := range inputs { //line cmd/hdq/fetch_cmd.gox:40:1 log.Println("==> Fetch", input) //line cmd/hdq/fetch_cmd.gox:41:1 doc := func() (_xgo_ret any) { //line cmd/hdq/fetch_cmd.gox:41:1 var _xgo_err error //line cmd/hdq/fetch_cmd.gox:41:1 _xgo_ret, _xgo_err = fetcher.Do(fetchType, input) //line cmd/hdq/fetch_cmd.gox:41:1 if _xgo_err != nil { //line cmd/hdq/fetch_cmd.gox:41:1 _xgo_err = errors.NewFrame(_xgo_err, "fetcher.do(fetchType, input)", "cmd/hdq/fetch_cmd.gox", 41, "main.Main") //line cmd/hdq/fetch_cmd.gox:41:1 panic(_xgo_err) } //line cmd/hdq/fetch_cmd.gox:41:1 return }() //line cmd/hdq/fetch_cmd.gox:42:1 docs = append(docs, doc) } //line cmd/hdq/fetch_cmd.gox:44:1 enc := json.NewEncoder(os.Stdout) //line cmd/hdq/fetch_cmd.gox:45:1 enc.SetIndent("", " ") //line cmd/hdq/fetch_cmd.gox:46:1 func() { //line cmd/hdq/fetch_cmd.gox:46:1 var _xgo_err error //line cmd/hdq/fetch_cmd.gox:46:1 _xgo_err = enc.Encode(docs) //line cmd/hdq/fetch_cmd.gox:46:1 if _xgo_err != nil { //line cmd/hdq/fetch_cmd.gox:46:1 _xgo_err = errors.NewFrame(_xgo_err, "enc.encode docs", "cmd/hdq/fetch_cmd.gox", 46, "main.Main") //line cmd/hdq/fetch_cmd.gox:46:1 panic(_xgo_err) } //line cmd/hdq/fetch_cmd.gox:46:1 return }() }) } func (this *Cmd_fetch) Classfname() string { return "fetch" } //line cmd/hdq/list_cmd.gox:20 func (this *Cmd_list) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/hdq/list_cmd.gox:20:1 this.Use("list") //line cmd/hdq/list_cmd.gox:22:1 this.Short("List all supported fetchTypes.") //line cmd/hdq/list_cmd.gox:24:1 this.Run__0(func() { for //line cmd/hdq/list_cmd.gox:25:1 _, ft := range fetcher.List() { //line cmd/hdq/list_cmd.gox:26:1 fmt.Println(ft) } }) } func (this *Cmd_list) Classfname() string { return "list" } func main() { new(App).Main() } ================================================ FILE: cmd/internal/base/base.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package base defines shared basic pieces of the gop command, // in particular logging and the Command structure. package base import ( "flag" "fmt" "io" "os" "strings" ) // A Command is an implementation of a gop command // like gop export or gop install. type Command struct { // Run runs the command. // The args are the arguments after the command name. Run func(cmd *Command, args []string) // UsageLine is the one-line usage message. // The words between "gop" and the first flag or argument in the line are taken to be the command name. UsageLine string // Short is the short description shown in the 'gop help' output. Short string // Flag is a set of flags specific to this command. Flag flag.FlagSet // Commands lists the available commands and help topics. // The order here is the order in which they are printed by 'gop help'. // Note that subcommands are in general best avoided. Commands []*Command } // Gop command var Gop = &Command{ UsageLine: "gop", Short: `Gop is a tool for managing XGo source code.`, // Commands initialized in package main } // LongName returns the command's long name: all the words in the usage line between "gop" and a flag or argument, func (c *Command) LongName() string { name := c.UsageLine if i := strings.Index(name, " ["); i >= 0 { name = name[:i] } if name == "gop" { return "" } return strings.TrimPrefix(name, "gop ") } // Name returns the command's short name: the last word in the usage line before a flag or argument. func (c *Command) Name() string { name := c.LongName() if i := strings.LastIndex(name, " "); i >= 0 { name = name[i+1:] } return name } // Usage show the command usage. func (c *Command) Usage(w io.Writer) { fmt.Fprintf(w, "%s\n\nUsage: %s\n", c.Short, c.UsageLine) // restore output of flag defer c.Flag.SetOutput(c.Flag.Output()) c.Flag.SetOutput(w) c.Flag.PrintDefaults() fmt.Fprintln(w) os.Exit(2) } // Runnable reports whether the command can be run; otherwise // it is a documentation pseudo-command. func (c *Command) Runnable() bool { return c.Run != nil } // Usage is the usage-reporting function, filled in by package main // but here for reference by other packages. // // flag.Usage func() // CmdName - "build", "install", "list", "mod tidy", etc. var CmdName string // Main runs a command. func Main(c *Command, app string, args []string) { name := c.UsageLine if i := strings.Index(name, " ["); i >= 0 { c.UsageLine = app + name[i:] } c.Run(c, args) } ================================================ FILE: cmd/internal/base/pass.go ================================================ package base import ( "flag" "fmt" "strings" ) type stringValue struct { p *PassArgs name string } func (p *stringValue) String() string { return "" } func (p *stringValue) Set(v string) error { p.p.Args = append(p.p.Args, fmt.Sprintf("-%v=%v", p.name, v)) return nil } type boolValue struct { p *PassArgs name string } func (p *boolValue) String() string { return "" } func (p *boolValue) Set(v string) error { p.p.Args = append(p.p.Args, fmt.Sprintf("-%v=%v", p.name, v)) return nil } func (p *boolValue) IsBoolFlag() bool { return true } type PassArgs struct { Args []string Flag *flag.FlagSet } func (p *PassArgs) Tags() string { for _, v := range p.Args { if strings.HasPrefix(v, "-tags=") { return v[6:] } } return "" } func (p *PassArgs) Var(names ...string) { for _, name := range names { p.Flag.Var(&stringValue{p: p, name: name}, name, "") } } func (p *PassArgs) Bool(names ...string) { for _, name := range names { p.Flag.Var(&boolValue{p: p, name: name}, name, "") } } func NewPassArgs(flag *flag.FlagSet) *PassArgs { p := &PassArgs{Flag: flag} return p } func PassBuildFlags(cmd *Command) *PassArgs { p := NewPassArgs(&cmd.Flag) p.Bool("v") p.Bool("n", "x") p.Bool("a") p.Bool("linkshared", "race", "msan", "asan", "trimpath", "work") p.Var("p", "asmflags", "compiler", "buildmode", "gcflags", "gccgoflags", "installsuffix", "ldflags", "pkgdir", "tags", "toolexec", "buildvcs") return p } ================================================ FILE: cmd/internal/bug/bug.go ================================================ /* * Copyright (c) 2021-2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package bug import ( "bytes" "fmt" "io" "log" "os" "os/exec" "path/filepath" "regexp" "runtime" "time" urlpkg "net/url" "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/env" ) // ----------------------------------------------------------------------------- // Cmd - xgo bug var Cmd = &base.Command{ UsageLine: "xgo bug", Short: "Start a bug report", } func init() { Cmd.Run = runCmd } func runCmd(_ *base.Command, args []string) { if len(args) > 0 { log.Fatalf("xgo: bug takes no arguments") } var buf bytes.Buffer buf.WriteString(bugHeader) printXgoVersion(&buf) buf.WriteString("### Does this issue reproduce with the latest release?\n\n\n") printEnvDetails(&buf) buf.WriteString(bugFooter) body := buf.String() url := "https://github.com/goplus/xgo/issues/new?body=" + urlpkg.QueryEscape(body) if !open(url) { fmt.Print("Please file a new issue at golang.org/issue/new using this template:\n\n") fmt.Print(body) } } // ----------------------------------------------------------------------------- const bugHeader = ` ` const bugFooter = `### What did you do? ### What did you expect to see? ### What did you see instead? ` func printXgoVersion(w io.Writer) { fmt.Fprintf(w, "### What version of xgo are you using (`xgo version`)?\n\n") fmt.Fprintf(w, "
\n")
	fmt.Fprintf(w, "$ xgo version\n")
	fmt.Fprintf(w, "xgo version %s %s/%s\n", env.Version(), runtime.GOOS, runtime.GOARCH)
	fmt.Fprintf(w, "
\n") fmt.Fprintf(w, "\n") } func printEnvDetails(w io.Writer) { fmt.Fprintf(w, "### What operating system and processor architecture are you using (`xgo env`)?\n\n") fmt.Fprintf(w, "
xgo env Output
\n")
	fmt.Fprintf(w, "$ xgo env\n")
	printXgoEnv(w)
	printXgoDetails(w)
	printOSDetails(w)
	printCDetails(w)
	fmt.Fprintf(w, "
\n\n") } func printXgoEnv(w io.Writer) { cmd := exec.Command("xgo", "env") cmd.Env = os.Environ() cmd.Stdout = w err := cmd.Run() if err != nil { log.Fatalln("run xgo env failed:", err) } } func printXgoDetails(w io.Writer) { xgocmd := filepath.Join(runtime.GOROOT(), "bin/xgo") printCmdOut(w, "GOROOT/bin/xgo version: ", xgocmd, "version") printCmdOut(w, "GOROOT/bin/xgo tool compile -V: ", xgocmd, "tool", "compile", "-V") } func printOSDetails(w io.Writer) { switch runtime.GOOS { case "darwin", "ios": printCmdOut(w, "uname -v: ", "uname", "-v") printCmdOut(w, "", "sw_vers") case "linux": printCmdOut(w, "uname -sr: ", "uname", "-sr") printCmdOut(w, "", "lsb_release", "-a") printGlibcVersion(w) case "openbsd", "netbsd", "freebsd", "dragonfly": printCmdOut(w, "uname -v: ", "uname", "-v") case "illumos", "solaris": // Be sure to use the OS-supplied uname, in "/usr/bin": printCmdOut(w, "uname -srv: ", "/usr/bin/uname", "-srv") out, err := os.ReadFile("/etc/release") if err == nil { fmt.Fprintf(w, "/etc/release: %s\n", out) } } } func printCDetails(w io.Writer) { printCmdOut(w, "lldb --version: ", "lldb", "--version") cmd := exec.Command("gdb", "--version") out, err := cmd.Output() if err == nil { // There's apparently no combination of command line flags // to get gdb to spit out its version without the license and warranty. // Print up to the first newline. fmt.Fprintf(w, "gdb --version: %s\n", firstLine(out)) } } // printCmdOut prints the output of running the given command. // It ignores failures; 'xgo bug' is best effort. func printCmdOut(w io.Writer, prefix, path string, args ...string) { cmd := exec.Command(path, args...) out, err := cmd.Output() if err != nil { return } fmt.Fprintf(w, "%s%s\n", prefix, bytes.TrimSpace(out)) } // firstLine returns the first line of a given byte slice. func firstLine(buf []byte) []byte { idx := bytes.IndexByte(buf, '\n') if idx > 0 { buf = buf[:idx] } return bytes.TrimSpace(buf) } // printGlibcVersion prints information about the glibc version. // It ignores failures. func printGlibcVersion(w io.Writer) { tempdir := os.TempDir() if tempdir == "" { return } src := []byte(`int main() {}`) srcfile := filepath.Join(tempdir, "go-bug.c") outfile := filepath.Join(tempdir, "go-bug") err := os.WriteFile(srcfile, src, 0644) if err != nil { return } defer os.Remove(srcfile) cmd := exec.Command("gcc", "-o", outfile, srcfile) if _, err = cmd.CombinedOutput(); err != nil { return } defer os.Remove(outfile) cmd = exec.Command("ldd", outfile) out, err := cmd.CombinedOutput() if err != nil { return } re := regexp.MustCompile(`libc\.so[^ ]* => ([^ ]+)`) m := re.FindStringSubmatch(string(out)) if m == nil { return } cmd = exec.Command(m[1]) out, err = cmd.Output() if err != nil { return } fmt.Fprintf(w, "%s: %s\n", m[1], firstLine(out)) // print another line (the one containing version string) in case of musl libc if idx := bytes.IndexByte(out, '\n'); bytes.Contains(out, []byte("musl")) && idx > -1 { fmt.Fprintf(w, "%s\n", firstLine(out[idx+1:])) } } // Commands returns a list of possible commands to use to open a url. func commands() [][]string { var cmds [][]string if exe := os.Getenv("BROWSER"); exe != "" { cmds = append(cmds, []string{exe}) } switch runtime.GOOS { case "darwin": cmds = append(cmds, []string{"/usr/bin/open"}) case "windows": cmds = append(cmds, []string{"cmd", "/c", "start"}) default: if os.Getenv("DISPLAY") != "" { // xdg-open is only for use in a desktop environment. cmds = append(cmds, []string{"xdg-open"}) } } cmds = append(cmds, []string{"chrome"}, []string{"google-chrome"}, []string{"chromium"}, []string{"firefox"}, ) return cmds } // Open tries to open url in a browser and reports whether it succeeded. func open(url string) bool { for _, args := range commands() { cmd := exec.Command(args[0], append(args[1:], url)...) if cmd.Start() == nil && appearsSuccessful(cmd, 3*time.Second) { return true } } return false } // appearsSuccessful reports whether the command appears to have run successfully. // If the command runs longer than the timeout, it's deemed successful. // If the command runs within the timeout, it's deemed successful if it exited cleanly. func appearsSuccessful(cmd *exec.Cmd, timeout time.Duration) bool { errc := make(chan error, 1) go func() { errc <- cmd.Wait() }() select { case <-time.After(timeout): return true case err := <-errc: return err == nil } } ================================================ FILE: cmd/internal/build/build.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package build implements the “gop build” command. package build import ( "fmt" "log" "os" "path/filepath" "reflect" "github.com/goplus/gogen" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/tool" "github.com/goplus/xgo/x/gocmd" "github.com/goplus/xgo/x/xgoprojs" ) // gop build var Cmd = &base.Command{ UsageLine: "gop build [-debug -o output] [packages]", Short: "Build XGo files", } var ( flag = &Cmd.Flag flagDebug = flag.Bool("debug", false, "print debug information") flagOutput = flag.String("o", "", "gop build output file") ) func init() { Cmd.Run = runCmd } func runCmd(cmd *base.Command, args []string) { pass := base.PassBuildFlags(cmd) err := flag.Parse(args) if err != nil { log.Panicln("parse input arguments failed:", err) } if *flagDebug { gogen.SetDebug(gogen.DbgFlagAll &^ gogen.DbgFlagComments) cl.SetDebug(cl.DbgFlagAll) cl.SetDisableRecover(true) } args = flag.Args() if len(args) == 0 { args = []string{"."} } proj, args, err := xgoprojs.ParseOne(args...) if err != nil { log.Panicln(err) } if len(args) != 0 { log.Panicln("too many arguments:", args) } conf, err := tool.NewDefaultConf(".", tool.ConfFlagNoTestFiles, pass.Tags()) if err != nil { log.Panicln("tool.NewDefaultConf:", err) } defer conf.UpdateCache() confCmd := conf.NewGoCmdConf() if *flagOutput != "" { output, err := filepath.Abs(*flagOutput) if err != nil { log.Panicln(err) } confCmd.Flags = []string{"-o", output} } confCmd.Flags = append(confCmd.Flags, pass.Args...) build(proj, conf, confCmd) } func build(proj xgoprojs.Proj, conf *tool.Config, build *gocmd.BuildConfig) { const flags = tool.GenFlagPrompt var obj string var err error switch v := proj.(type) { case *xgoprojs.DirProj: obj = v.Dir err = tool.BuildDir(obj, conf, build, flags) case *xgoprojs.PkgPathProj: obj = v.Path err = tool.BuildPkgPath("", v.Path, conf, build, flags) case *xgoprojs.FilesProj: err = tool.BuildFiles(v.Files, conf, build) default: log.Panicln("`gop build` doesn't support", reflect.TypeOf(v)) } if tool.NotFound(err) { fmt.Fprintf(os.Stderr, "gop build %v: not found\n", obj) } else if err != nil { fmt.Fprintln(os.Stderr, err) } else { return } os.Exit(1) } // ----------------------------------------------------------------------------- ================================================ FILE: cmd/internal/clean/clean.go ================================================ /* * Copyright (c) 2021-2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package clean import ( "fmt" "log" "os" "path/filepath" "strings" "github.com/goplus/xgo/cmd/internal/base" ) const ( autoGenFileSuffix = "_autogen.go" autoGenGopTestFile = "gop_autogen_test.go" autoGen2GopTestFile = "gop_autogen2_test.go" autoGenXgoTestFile = "xgo_autogen_test.go" autoGen2XgoTestFile = "xgo_autogen2_test.go" ) // ----------------------------------------------------------------------------- func cleanAGFiles(dir string, execAct bool) { fis, err := os.ReadDir(dir) if err != nil { return } for _, fi := range fis { fname := fi.Name() if strings.HasPrefix(fname, "_") { continue } if fi.IsDir() { pkgDir := filepath.Join(dir, fname) if fname == ".xgo" || fname == ".gop" { removeGopDir(pkgDir, execAct) } else { cleanAGFiles(pkgDir, execAct) } continue } if strings.HasSuffix(fname, autoGenFileSuffix) { file := filepath.Join(dir, fname) fmt.Printf("Cleaning %s ...\n", file) if execAct { os.Remove(file) } } } autogens := []string{ autoGenGopTestFile, autoGen2GopTestFile, autoGenXgoTestFile, autoGen2XgoTestFile, } for _, autogen := range autogens { file := filepath.Join(dir, autogen) if _, err = os.Stat(file); err == nil { fmt.Printf("Cleaning %s ...\n", file) if execAct { os.Remove(file) } } } } func removeGopDir(dir string, execAct bool) { fis, err := os.ReadDir(dir) if err != nil { return } for _, fi := range fis { fname := fi.Name() if strings.HasSuffix(fname, ".xgo.go") || strings.HasSuffix(fname, ".gop.go") { genfile := filepath.Join(dir, fname) fmt.Printf("Cleaning %s ...\n", genfile) if execAct { os.Remove(genfile) } } } if execAct { os.Remove(dir) } } // ----------------------------------------------------------------------------- // Cmd - gop clean var Cmd = &base.Command{ UsageLine: "gop clean [flags] ", Short: "Clean all XGo auto generated files", } var ( flag = &Cmd.Flag _ = flag.Bool("v", false, "print verbose information.") testMode = flag.Bool("t", false, "test mode: display files to clean but don't clean them.") ) func init() { Cmd.Run = runCmd } func runCmd(_ *base.Command, args []string) { err := flag.Parse(args) if err != nil { log.Fatalln("parse input arguments failed:", err) } var dir string if flag.NArg() == 0 { dir = "." } else { dir = flag.Arg(0) } cleanAGFiles(dir, !*testMode) } // ----------------------------------------------------------------------------- ================================================ FILE: cmd/internal/deps/deps.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package deps /* import ( "fmt" "log" "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/x/gopmod" ) // ----------------------------------------------------------------------------- // gop deps var Cmd = &base.Command{ UsageLine: "gop deps [-v] [package]", Short: "Show dependencies of a package or module", } var ( flag = &Cmd.Flag _ = flag.Bool("v", false, "print verbose information.") ) func init() { Cmd.Run = runCmd } func runCmd(cmd *base.Command, args []string) { err := flag.Parse(args) if err != nil { log.Fatalln("parse input arguments failed:", err) } var dir string narg := flag.NArg() if narg < 1 { dir = "." } else { dir = flag.Arg(0) } imports, err := gopmod.Imports(dir) check(err) for _, imp := range imports { fmt.Println(imp) } } func check(err error) { if err != nil { log.Fatalln(err) } } */ // ----------------------------------------------------------------------------- ================================================ FILE: cmd/internal/doc/doc.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package doc import ( "fmt" "go/types" "log" "os" "reflect" "strconv" "strings" "github.com/goplus/gogen" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/cl/outline" "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/tool" "github.com/goplus/xgo/x/xgoenv" "github.com/goplus/xgo/x/xgoprojs" ) // ----------------------------------------------------------------------------- // gop doc var Cmd = &base.Command{ UsageLine: "gop doc [-u -all -debug] [pkgPath]", Short: "Show documentation for package or symbol", } var ( flag = &Cmd.Flag withDoc = flag.Bool("all", false, "Show all the documentation for the package.") debug = flag.Bool("debug", false, "Print debug information.") unexp = flag.Bool("u", false, "Show documentation for unexported as well as exported symbols, methods, and fields.") ) func init() { Cmd.Run = runCmd } func runCmd(cmd *base.Command, args []string) { err := flag.Parse(args) if err != nil { log.Fatalln("parse input arguments failed:", err) } pattern := flag.Args() if len(pattern) == 0 { pattern = []string{"."} } proj, _, err := xgoprojs.ParseOne(pattern...) if err != nil { log.Panicln("xgoprojs.ParseOne:", err) } if *debug { gogen.SetDebug(gogen.DbgFlagAll &^ gogen.DbgFlagComments) cl.SetDebug(cl.DbgFlagAll) cl.SetDisableRecover(true) } xgo := xgoenv.Get() conf := &tool.Config{XGo: xgo} outlinePkg(proj, conf) } func outlinePkg(proj xgoprojs.Proj, conf *tool.Config) { var obj string var out outline.Package var err error switch v := proj.(type) { case *xgoprojs.DirProj: obj = v.Dir out, err = tool.Outline(obj, conf) case *xgoprojs.PkgPathProj: obj = v.Path out, err = tool.OutlinePkgPath("", obj, conf, true) default: log.Panicln("`gop doc` doesn't support", reflect.TypeOf(v)) } if tool.NotFound(err) { fmt.Fprintf(os.Stderr, "gop doc %v: not Go/XGo files found\n", obj) } else if err != nil { fmt.Fprintln(os.Stderr, err) } else { outlineDoc(out.Outline(*unexp), *unexp, *withDoc) } } const ( indent = " " ln = "\n" ) func outlineDoc(out *outline.All, all, withDoc bool) { pkg := out.Pkg() fmt.Printf("package %s // import %s\n\n", pkg.Name(), strconv.Quote(pkg.Path())) if withDoc && len(out.Consts) > 0 { fmt.Print("CONSTANTS\n\n") } for _, o := range out.Consts { printObject(pkg, o, withDoc) } if withDoc && len(out.Vars) > 0 { fmt.Print("VARIABLES\n\n") } for _, o := range out.Vars { printObject(pkg, o, withDoc) } if withDoc && len(out.Funcs) > 0 { fmt.Print("FUNCTIONS\n\n") } for _, fn := range out.Funcs { printObject(pkg, fn, withDoc) } if withDoc && len(out.Types) > 0 { fmt.Print("TYPES\n\n") } for _, t := range out.Types { if !(all || t.IsUsed()) { continue } typName := t.ObjWith(all) fmt.Print(objectString(pkg, typName), ln) for _, o := range t.Consts { fmt.Print(indent, constShortString(o.Const), ln) } if withDoc { printDoc(t) } printFuncsForType(pkg, t.Creators, withDoc) printFuncsForType(pkg, t.GoptFuncs, withDoc) printFuncsForType(pkg, t.Helpers, withDoc) if !typName.IsAlias() { typ := t.Type() if named, ok := typ.CheckNamed(out.Package); ok { for _, fn := range named.Methods() { if o := fn.Obj(); all || o.Exported() { if withDoc { fmt.Print(objectString(pkg, o), ln) printDoc(fn) } else { fmt.Print(indent, objectString(pkg, o), ln) } } } } } } } type object interface { Obj() types.Object Doc() string } func printObject(pkg *types.Package, o object, withDoc bool) { fmt.Print(objectString(pkg, o.Obj()), ln) if withDoc { printDoc(o) } } func printDoc(o object) { if doc := o.Doc(); doc != "" { fmt.Print(indent, strings.ReplaceAll(doc, "\n", "\n"+indent), ln) } else { fmt.Println() } } func printFuncsForType(pkg *types.Package, fns []outline.Func, withDoc bool) { for _, fn := range fns { if withDoc { printObject(pkg, fn, true) } else { fmt.Print(indent, objectString(pkg, fn.Obj()), ln) } } } func objectString(pkg *types.Package, obj types.Object) string { if name, fn, ok := outline.CheckOverload(obj); ok { obj = types.NewFunc(fn.Pos(), fn.Pkg(), name, fn.Type().(*types.Signature)) } return types.ObjectString(obj, qualifier(pkg)) } func constShortString(obj *types.Const) string { return "const " + obj.Name() } func qualifier(pkg *types.Package) types.Qualifier { return func(other *types.Package) string { if pkg == other { return "" // same package; unqualified } return other.Name() } } // ----------------------------------------------------------------------------- ================================================ FILE: cmd/internal/env/env.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package env import ( "bytes" "encoding/json" "fmt" "log" "os" "os/exec" "sort" "github.com/goplus/mod" "github.com/goplus/mod/modcache" "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/env" "github.com/goplus/xgo/x/gocmd" ) // Cmd - gop env var Cmd = &base.Command{ UsageLine: "gop env [-json] [var ...]", Short: "Prints XGo environment information", } var ( flag = &Cmd.Flag envJson = flag.Bool("json", false, "prints Go environment information.") ) func init() { Cmd.Run = runCmd } func runCmd(_ *base.Command, args []string) { err := flag.Parse(args) if err != nil { log.Fatalln("parse input arguments failed:", err) } var stdout bytes.Buffer cmd := exec.Command("go", "env", "-json") cmd.Env = os.Environ() cmd.Stdout = &stdout err = cmd.Run() if err != nil { log.Fatalln("run go env failed:", err) } var xgoEnv map[string]any if err := json.Unmarshal(stdout.Bytes(), &xgoEnv); err != nil { log.Fatal("decode json of go env failed:", err) } xgoEnv["BUILDDATE"] = env.BuildDate() xgoEnv["XGOVERSION"] = env.Version() xgoEnv["XGOROOT"] = env.XGOROOT() xgoEnv["XGO_GOCMD"] = gocmd.Name() xgoEnv["GOMODCACHE"] = modcache.GOMODCACHE xgoEnv["GOXMOD"], _ = mod.GOXMOD("") xgoEnv["HOME"] = env.HOME() vars := flag.Args() outputEnvVars(xgoEnv, vars, *envJson) } func outputEnvVars(gopEnv map[string]any, vars []string, outputJson bool) { onlyValues := true if len(vars) == 0 { onlyValues = false vars = make([]string, 0, len(gopEnv)) for k := range gopEnv { vars = append(vars, k) } sort.Strings(vars) } else { newEnv := make(map[string]any) for _, v := range vars { if value, ok := gopEnv[v]; ok { newEnv[v] = value } else { newEnv[v] = "" } } gopEnv = newEnv } if outputJson { b, err := json.Marshal(gopEnv) if err != nil { log.Fatal("encode json of go env failed:", err) } var out bytes.Buffer json.Indent(&out, b, "", "\t") fmt.Println(out.String()) } else { for _, k := range vars { v := gopEnv[k] if onlyValues { fmt.Printf("%v\n", v) } else { fmt.Printf("%s=\"%v\"\n", k, v) } } } } ================================================ FILE: cmd/internal/gengo/go.go ================================================ /* * Copyright (c) 2021-2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package gengo implements the “gop go” command. package gengo import ( "fmt" "log" "os" "reflect" "github.com/goplus/gogen" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/tool" "github.com/goplus/xgo/x/xgoprojs" "github.com/qiniu/x/errors" ) // gop go var Cmd = &base.Command{ UsageLine: "gop go [-v] [packages|files]", Short: "Convert XGo code into Go code", } var ( flag = &Cmd.Flag flagVerbose = flag.Bool("v", false, "print verbose information") flagCheckMode = flag.Bool("t", false, "do check syntax only, no generate xgo_autogen.go") flagSingleMode = flag.Bool("s", false, "run in single file mode for package") flagIgnoreNotatedErr = flag.Bool( "ignore-notated-error", false, "ignore notated errors, only available together with -t (check mode)") flagTags = flag.String("tags", "", "a comma-separated list of additional build tags to consider satisfied") ) func init() { Cmd.Run = runCmd } func runCmd(cmd *base.Command, args []string) { err := flag.Parse(args) if err != nil { log.Panicln("parse input arguments failed:", err) } pattern := flag.Args() if len(pattern) == 0 { pattern = []string{"."} } projs, err := xgoprojs.ParseAll(pattern...) if err != nil { log.Panicln("xgoprojs.ParseAll:", err) } if *flagVerbose { gogen.SetDebug(gogen.DbgFlagAll &^ gogen.DbgFlagComments) cl.SetDebug(cl.DbgFlagAll) cl.SetDisableRecover(true) } conf, err := tool.NewDefaultConf(".", 0, *flagTags) if err != nil { log.Panicln("tool.NewDefaultConf:", err) } defer conf.UpdateCache() flags := tool.GenFlagPrintError | tool.GenFlagPrompt if *flagCheckMode { flags |= tool.GenFlagCheckOnly if *flagIgnoreNotatedErr { conf.IgnoreNotatedError = true } } if *flagSingleMode { flags |= tool.GenFlagSingleFile } for _, proj := range projs { switch v := proj.(type) { case *xgoprojs.DirProj: _, _, err = tool.GenGoEx(v.Dir, conf, true, flags) case *xgoprojs.PkgPathProj: _, _, err = tool.GenGoPkgPathEx("", v.Path, conf, true, flags) case *xgoprojs.FilesProj: _, err = tool.GenGoFiles("", v.Files, conf) default: log.Panicln("`gop go` doesn't support", reflect.TypeOf(v)) } if err != nil { fmt.Fprintf(os.Stderr, "GenGo failed: %d errors.\n", errorNum(err)) os.Exit(1) } } } func errorNum(err error) int { if e, ok := err.(errors.List); ok { return len(e) } return 1 } // ----------------------------------------------------------------------------- ================================================ FILE: cmd/internal/gopfmt/fmt.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package gopfmt implements the “gop fmt” command. package gopfmt import ( "bytes" "fmt" "io/fs" "log" "os" "path/filepath" "strings" "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/format" "github.com/goplus/xgo/tool" goformat "go/format" "go/parser" "go/token" xformat "github.com/goplus/xgo/x/format" ) // Cmd - gop fmt var Cmd = &base.Command{ UsageLine: "gop fmt [flags] path ...", Short: "Format XGo packages", } var ( flag = &Cmd.Flag flagTest = flag.Bool("t", false, "test if XGo files are formatted or not.") flagNotExec = flag.Bool("n", false, "prints commands that would be executed.") flagMoveGo = flag.Bool("mvgo", false, "move .go files to .xgo files (only available in `--smart` mode).") flagSmart = flag.Bool("smart", false, "convert Go code style into XGo style.") ) func init() { Cmd.Run = runCmd } var ( testErrCnt = 0 procCnt = 0 walkSubDir = false rootDir = "" ) func gopfmt(path string, class, smart, mvgo bool) (err error) { src, err := os.ReadFile(path) if err != nil { return } var target []byte if smart { target, err = xformat.GopstyleSource(src, path) } else { if !mvgo && filepath.Ext(path) == ".go" { fset := token.NewFileSet() f, err := parser.ParseFile(fset, path, src, parser.ParseComments) if err != nil { return err } var buf bytes.Buffer err = goformat.Node(&buf, fset, f) if err != nil { return err } target = buf.Bytes() } else { target, err = format.Source(src, class, path) } } if err != nil { return } if bytes.Equal(src, target) { return } fmt.Println(path) if *flagTest { testErrCnt++ return nil } if mvgo { newPath := strings.TrimSuffix(path, ".go") + ".xgo" if err = os.WriteFile(newPath, target, 0666); err != nil { return } return os.Remove(path) } return writeFileWithBackup(path, target) } func writeFileWithBackup(path string, target []byte) (err error) { dir, file := filepath.Split(path) f, err := os.CreateTemp(dir, file) if err != nil { return } tmpfile := f.Name() _, err = f.Write(target) f.Close() if err != nil { return } err = os.Remove(path) if err != nil { return } return os.Rename(tmpfile, path) } type walker struct { dirMap map[string]func(ext string) (ok, class bool) } func newWalker() *walker { return &walker{dirMap: make(map[string]func(ext string) (ok, class bool))} } func (w *walker) walk(path string, d fs.DirEntry, err error) error { if err != nil { fmt.Fprintln(os.Stderr, err) } else if d.IsDir() { if !walkSubDir && path != rootDir { return filepath.SkipDir } } else { dir, _ := filepath.Split(path) fn, ok := w.dirMap[dir] if !ok { if mod, err := tool.LoadMod(path); err == nil { fn = func(ext string) (ok bool, class bool) { switch ext { case ".go", ".xgo", ".gop": ok = true case ".gox", ".spx", ".gmx": ok, class = true, true default: class = mod.IsClass(ext) ok = class } return } } else { fn = func(ext string) (ok bool, class bool) { switch ext { case ".go", ".xgo", ".gop": ok = true case ".gox", ".spx", ".gmx": ok, class = true, true } return } } w.dirMap[dir] = fn } ext := filepath.Ext(path) smart := *flagSmart mvgo := smart && *flagMoveGo if ok, class := fn(ext); ok && (!mvgo || ext == ".go") { procCnt++ if *flagNotExec { fmt.Println("xgo fmt", path) } else { err = gopfmt(path, class, smart && (mvgo || ext != ".go"), mvgo) if err != nil { report(err) } } } } return err } func report(err error) { fmt.Println(err) os.Exit(2) } func runCmd(cmd *base.Command, args []string) { err := flag.Parse(args) if err != nil { log.Fatalln("parse input arguments failed:", err) } narg := flag.NArg() if narg < 1 { cmd.Usage(os.Stderr) } if *flagTest { defer func() { if testErrCnt > 0 { fmt.Printf("total %d files are not formatted.\n", testErrCnt) os.Exit(1) } }() } walker := newWalker() for i := 0; i < narg; i++ { path := flag.Arg(i) walkSubDir = strings.HasSuffix(path, "/...") if walkSubDir { path = path[:len(path)-4] } procCnt = 0 rootDir = path filepath.WalkDir(path, walker.walk) if procCnt == 0 { fmt.Println("no XGo files in", path) } } } // ----------------------------------------------------------------------------- ================================================ FILE: cmd/internal/gopget/get.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package gopget import ( "fmt" "log" "os" "github.com/goplus/mod/modcache" "github.com/goplus/mod/modfetch" "github.com/goplus/mod/modload" "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/tool" ) // ----------------------------------------------------------------------------- // Cmd - gop get var Cmd = &base.Command{ UsageLine: "gop get [-v] [packages]", Short: `Add dependencies to current module and install them`, } var ( flag = &Cmd.Flag _ = flag.Bool("v", false, "print verbose information.") ) func init() { Cmd.Run = runCmd } func runCmd(cmd *base.Command, args []string) { err := flag.Parse(args) if err != nil { log.Fatalln("parse input arguments failed:", err) } narg := flag.NArg() if narg < 1 { log.Fatalln("TODO: not impl") } for i := 0; i < narg; i++ { get(flag.Arg(i)) } } func get(pkgPath string) { modBase := "" mod, err := modload.Load(".") noMod := tool.NotFound(err) if !noMod { check(err) modBase = mod.Path() } pkgModVer, _, err := modfetch.GetPkg(pkgPath, modBase) check(err) if noMod { return } pkgModRoot, err := modcache.Path(pkgModVer) check(err) pkgMod, err := modload.Load(pkgModRoot) check(err) check(mod.AddRequire(pkgModVer.Path, pkgModVer.Version, pkgMod.HasProject())) fmt.Fprintf(os.Stderr, "gop get: added %s %s\n", pkgModVer.Path, pkgModVer.Version) check(mod.Save()) } func check(err error) { if err != nil { log.Fatalln(err) } } // ----------------------------------------------------------------------------- ================================================ FILE: cmd/internal/help/help.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package help implements the “gop help” command. package help import ( "bufio" "fmt" "io" "log" "os" "strings" "text/template" "unicode" "unicode/utf8" "github.com/goplus/xgo/cmd/internal/base" ) // Help implements the 'help' command. func Help(w io.Writer, args []string) { cmd := base.Gop Args: for i, arg := range args { for _, sub := range cmd.Commands { if sub.Name() == arg { cmd = sub continue Args } } // helpSuccess is the help command using as many args as possible that would succeed. helpSuccess := "gop help" if i > 0 { helpSuccess += " " + strings.Join(args[:i], " ") } fmt.Fprintf(os.Stderr, "gop help %s: unknown help topic. Run '%s'.\n", strings.Join(args, " "), helpSuccess) os.Exit(2) } if len(cmd.Commands) > 0 { PrintUsage(w, cmd) } else { cmd.Usage(w) } // not exit 2: succeeded at 'gop help cmd'. } var usageTemplate = `{{.Short | trim}} Usage: {{.UsageLine}} [arguments] The commands are: {{range .Commands}}{{if or (.Runnable) .Commands}} {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} Use "gop help{{with .LongName}} {{.}}{{end}} " for more information about a command. ` // An errWriter wraps a writer, recording whether a write error occurred. type errWriter struct { w io.Writer err error } func (w *errWriter) Write(b []byte) (int, error) { n, err := w.w.Write(b) if err != nil { w.err = err } return n, err } // tmpl executes the given template text on data, writing the result to w. func tmpl(w io.Writer, text string, data any) { t := template.New("top") t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize}) template.Must(t.Parse(text)) ew := &errWriter{w: w} err := t.Execute(ew, data) if ew.err != nil { // I/O error writing. Ignore write on closed pipe. if strings.Contains(ew.err.Error(), "pipe") { os.Exit(1) } log.Fatalf("writing output: %v", ew.err) } if err != nil { panic(err) } } func capitalize(s string) string { if s == "" { return s } r, n := utf8.DecodeRuneInString(s) return string(unicode.ToTitle(r)) + s[n:] } // PrintUsage prints usage information. func PrintUsage(w io.Writer, cmd *base.Command) { bw := bufio.NewWriter(w) tmpl(bw, usageTemplate, cmd) bw.Flush() } ================================================ FILE: cmd/internal/install/install.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package install implements the “gop install” command. package install import ( "fmt" "log" "os" "reflect" "github.com/goplus/gogen" "github.com/goplus/mod/modfetch" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/tool" "github.com/goplus/xgo/x/gocmd" "github.com/goplus/xgo/x/xgoprojs" ) // gop install var Cmd = &base.Command{ UsageLine: "gop install [-debug] [packages]", Short: "Build XGo files and install target to GOBIN", } var ( flag = &Cmd.Flag flagDebug = flag.Bool("debug", false, "print debug information") ) func init() { Cmd.Run = runCmd } func runCmd(cmd *base.Command, args []string) { pass := base.PassBuildFlags(cmd) err := flag.Parse(args) if err != nil { log.Fatalln("parse input arguments failed:", err) } pattern := flag.Args() if len(pattern) == 0 { pattern = []string{"."} } projs, err := xgoprojs.ParseAll(pattern...) if err != nil { log.Panicln("xgoprojs.ParseAll:", err) } if *flagDebug { modfetch.SetDebug(modfetch.DbgFlagAll) gogen.SetDebug(gogen.DbgFlagAll &^ gogen.DbgFlagComments) cl.SetDebug(cl.DbgFlagAll) cl.SetDisableRecover(true) } conf, err := tool.NewDefaultConf(".", tool.ConfFlagNoTestFiles, pass.Tags()) if err != nil { log.Panicln("tool.NewDefaultConf:", err) } defer conf.UpdateCache() confCmd := conf.NewGoCmdConf() confCmd.Flags = pass.Args for _, proj := range projs { install(proj, conf, confCmd) } } func install(proj xgoprojs.Proj, conf *tool.Config, install *gocmd.InstallConfig) { const flags = tool.GenFlagPrompt var obj string var err error switch v := proj.(type) { case *xgoprojs.DirProj: obj = v.Dir err = tool.InstallDir(obj, conf, install, flags) case *xgoprojs.PkgPathProj: obj = v.Path err = tool.InstallPkgPath("", v.Path, conf, install, flags) case *xgoprojs.FilesProj: err = tool.InstallFiles(v.Files, conf, install) default: log.Panicln("`gop install` doesn't support", reflect.TypeOf(v)) } if tool.NotFound(err) { fmt.Fprintf(os.Stderr, "gop install %v: not found\n", obj) } else if err != nil { fmt.Fprintln(os.Stderr, err) } else { return } os.Exit(1) } // ----------------------------------------------------------------------------- ================================================ FILE: cmd/internal/list/list.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package list /* import ( "fmt" "log" "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/x/gopmod" ) // ----------------------------------------------------------------------------- // gop list var Cmd = &base.Command{ UsageLine: "gop list [-json] [packages]", Short: "List packages or modules", } var ( flag = &Cmd.Flag _ = flag.Bool("json", false, "printing in JSON format.") ) func init() { Cmd.Run = runCmd } func runCmd(cmd *base.Command, args []string) { err := flag.Parse(args) if err != nil { log.Fatalln("parse input arguments failed:", err) } pattern := flag.Args() if len(pattern) == 0 { pattern = []string{"."} } pkgPaths, err := gopmod.List(pattern...) check(err) for _, pkgPath := range pkgPaths { fmt.Println(pkgPath) } } func check(err error) { if err != nil { log.Fatalln(err) } } */ // ----------------------------------------------------------------------------- ================================================ FILE: cmd/internal/mod/download.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package mod import ( "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/cmd/internal/gopget" ) // gop mod download var CmdDownload = &base.Command{ UsageLine: "gop mod download [-x -json] [modules]", Short: "download modules to local cache", } func init() { CmdDownload.Run = gopget.Cmd.Run } ================================================ FILE: cmd/internal/mod/init.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package mod import ( "log" "path/filepath" "runtime" "strings" "github.com/goplus/mod/modload" "github.com/goplus/mod/xgomod" "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/env" ) // gop mod init var CmdInit = &base.Command{ UsageLine: "gop mod init [-llgo -tinygo] module-path", Short: "initialize new module in current directory", } var ( flagInit = &CmdInit.Flag flagLLGo = flagInit.Bool("llgo", false, "use llgo as the compiler") flagTinyGo = flagInit.Bool("tinygo", false, "use tinygo as the compiler") ) func init() { CmdInit.Run = runInit } func runInit(cmd *base.Command, args []string) { err := flagInit.Parse(args) if err != nil { log.Fatalln("parse input arguments failed:", err) } args = flagInit.Args() switch len(args) { case 0: fatal(`Example usage: 'gop mod init example.com/m' to initialize a v0 or v1 module 'gop mod init example.com/m/v2' to initialize a v2 module Run 'gop help mod init' for more information.`) case 1: default: fatal("gop mod init: too many arguments") } modPath := args[0] mod, err := modload.Create(".", modPath, goMainVer(), env.MainVersion()) check(err) if *flagLLGo { mod.AddCompiler("llgo", "1.0") mod.AddRequire("github.com/goplus/lib", llgoLibVer(), false) } else if *flagTinyGo { mod.AddCompiler("tinygo", "0.32") } err = mod.Save() check(err) } func goMainVer() string { ver := strings.TrimPrefix(runtime.Version(), "go") if pos := strings.Index(ver, "."); pos > 0 { pos++ if pos2 := strings.Index(ver[pos:], "."); pos2 > 0 { ver = ver[:pos+pos2] } } return ver } func llgoLibVer() string { if modGop, e1 := xgomod.LoadFrom(filepath.Join(env.XGOROOT(), "go.mod"), ""); e1 == nil { if pkg, e2 := modGop.Lookup("github.com/goplus/lib"); e2 == nil { return pkg.Real.Version } } return "v0.2.0" } ================================================ FILE: cmd/internal/mod/mod.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package mod import ( "fmt" "log" "os" "github.com/goplus/xgo/cmd/internal/base" ) var Cmd = &base.Command{ UsageLine: "gop mod", Short: "Module maintenance", Commands: []*base.Command{ CmdInit, CmdDownload, CmdTidy, }, } func check(err error) { if err != nil { log.Panicln(err) } } func fatal(msg any) { fmt.Fprintln(os.Stderr, msg) os.Exit(1) } ================================================ FILE: cmd/internal/mod/tidy.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package mod import ( "fmt" "os" "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/tool" "github.com/goplus/xgo/x/xgoenv" ) // gop mod tidy var CmdTidy = &base.Command{ UsageLine: "gop mod tidy [-e -v]", Short: "add missing and remove unused modules", } func init() { CmdTidy.Run = runTidy } func runTidy(cmd *base.Command, args []string) { err := tool.Tidy(".", xgoenv.Get()) if err != nil { if tool.NotFound(err) { fmt.Fprintln(os.Stderr, "go.mod not found") } else { fmt.Fprintln(os.Stderr, err) } os.Exit(1) } } ================================================ FILE: cmd/internal/run/run.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package run implements the “gop run” command. package run import ( "fmt" "os" "reflect" "github.com/goplus/gogen" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/tool" "github.com/goplus/xgo/x/gocmd" "github.com/goplus/xgo/x/xgoprojs" "github.com/qiniu/x/log" ) // gop run var Cmd = &base.Command{ UsageLine: "gop run [-nc -asm -quiet -debug -prof] package [arguments...]", Short: "Run an XGo program", } var ( flag = &Cmd.Flag flagAsm = flag.Bool("asm", false, "generates `asm` code of XGo bytecode backend") flagDebug = flag.Bool("debug", false, "print debug information") flagQuiet = flag.Bool("quiet", false, "don't generate any compiling stage log") flagNoChdir = flag.Bool("nc", false, "don't change dir (only for `gop run pkgPath`)") flagProf = flag.Bool("prof", false, "do profile and generate profile report") ) func init() { Cmd.Run = runCmd } func runCmd(cmd *base.Command, args []string) { pass := base.PassBuildFlags(cmd) err := flag.Parse(args) if err != nil { log.Fatalln("parse input arguments failed:", err) } if flag.NArg() < 1 { cmd.Usage(os.Stderr) } proj, args, err := xgoprojs.ParseOne(flag.Args()...) if err != nil { log.Fatalln(err) } if *flagQuiet { log.SetOutputLevel(0x7000) } else if *flagDebug { gogen.SetDebug(gogen.DbgFlagAll &^ gogen.DbgFlagComments) cl.SetDebug(cl.DbgFlagAll) cl.SetDisableRecover(true) } else if *flagAsm { gogen.SetDebug(gogen.DbgFlagInstruction) } if *flagProf { panic("TODO: profile not impl") } noChdir := *flagNoChdir conf, err := tool.NewDefaultConf(".", tool.ConfFlagNoTestFiles, pass.Tags()) if err != nil { log.Panicln("tool.NewDefaultConf:", err) } defer conf.UpdateCache() if !conf.Mod.HasModfile() { // if no go.mod, check GopDeps conf.XGoDeps = new(int) } confCmd := conf.NewGoCmdConf() confCmd.Flags = pass.Args run(proj, args, !noChdir, conf, confCmd) } func run(proj xgoprojs.Proj, args []string, chDir bool, conf *tool.Config, run *gocmd.RunConfig) { const flags = 0 var obj string var err error switch v := proj.(type) { case *xgoprojs.DirProj: obj = v.Dir err = tool.RunDir(obj, args, conf, run, flags) case *xgoprojs.PkgPathProj: obj = v.Path err = tool.RunPkgPath(v.Path, args, chDir, conf, run, flags) case *xgoprojs.FilesProj: err = tool.RunFiles("", v.Files, args, conf, run) default: log.Panicln("`gop run` doesn't support", reflect.TypeOf(v)) } if tool.NotFound(err) { fmt.Fprintf(os.Stderr, "gop run %v: not found\n", obj) } else if err != nil { fmt.Fprintln(os.Stderr, err) } else { return } os.Exit(1) } // ----------------------------------------------------------------------------- ================================================ FILE: cmd/internal/serve/serve.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package serve implements the “gop serve command. package serve import ( "context" "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/x/jsonrpc2" "github.com/goplus/xgo/x/jsonrpc2/stdio" "github.com/goplus/xgo/x/langserver" "github.com/qiniu/x/log" ) // gop serve var Cmd = &base.Command{ UsageLine: "gop serve [flags]", Short: "Serve as an XGo LangServer", } var ( flag = &Cmd.Flag flagVerbose = flag.Bool("v", false, "print verbose information") ) func init() { Cmd.Run = runCmd } func runCmd(cmd *base.Command, args []string) { err := flag.Parse(args) if err != nil { log.Fatalln("parse input arguments failed:", err) } if *flagVerbose { jsonrpc2.SetDebug(jsonrpc2.DbgFlagCall) } listener := stdio.Listener(false) defer listener.Close() server := langserver.NewServer(context.Background(), listener, nil) server.Wait() } // ----------------------------------------------------------------------------- ================================================ FILE: cmd/internal/test/test.go ================================================ /* * Copyright (c) 2021-2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package test implements the “gop test” command. package test import ( "fmt" "log" "os" "reflect" "github.com/goplus/gogen" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/tool" "github.com/goplus/xgo/x/gocmd" "github.com/goplus/xgo/x/xgoprojs" ) // gop test var Cmd = &base.Command{ UsageLine: "gop test [-debug] [packages]", Short: "Test XGo packages", } var ( flag = &Cmd.Flag flagDebug = flag.Bool("debug", false, "print debug information") ) func init() { Cmd.Run = runCmd } func runCmd(cmd *base.Command, args []string) { pass := PassTestFlags(cmd) err := flag.Parse(args) if err != nil { log.Fatalln("parse input arguments failed:", err) } pattern := flag.Args() if len(pattern) == 0 { pattern = []string{"."} } projs, err := xgoprojs.ParseAll(pattern...) if err != nil { log.Panicln("xgoprojs.ParseAll:", err) } if *flagDebug { gogen.SetDebug(gogen.DbgFlagAll &^ gogen.DbgFlagComments) cl.SetDebug(cl.DbgFlagAll) cl.SetDisableRecover(true) } conf, err := tool.NewDefaultConf(".", 0, pass.Tags()) if err != nil { log.Panicln("tool.NewDefaultConf:", err) } defer conf.UpdateCache() confCmd := conf.NewGoCmdConf() confCmd.Flags = pass.Args for _, proj := range projs { test(proj, conf, confCmd) } } func test(proj xgoprojs.Proj, conf *tool.Config, test *gocmd.TestConfig) { const flags = tool.GenFlagPrompt var obj string var err error switch v := proj.(type) { case *xgoprojs.DirProj: obj = v.Dir err = tool.TestDir(obj, conf, test, flags) case *xgoprojs.PkgPathProj: obj = v.Path err = tool.TestPkgPath("", v.Path, conf, test, flags) case *xgoprojs.FilesProj: err = tool.TestFiles(v.Files, conf, test) default: log.Panicln("`gop test` doesn't support", reflect.TypeOf(v)) } if tool.NotFound(err) { fmt.Fprintf(os.Stderr, "gop test %v: not found\n", obj) } else if err != nil { fmt.Fprintln(os.Stderr, err) } else { return } os.Exit(1) } // ----------------------------------------------------------------------------- ================================================ FILE: cmd/internal/test/testflag.go ================================================ package test import ( "github.com/goplus/xgo/cmd/internal/base" ) type boolFlag interface { IsBoolFlag() bool } func PassTestFlags(cmd *base.Command) *base.PassArgs { p := base.PassBuildFlags(cmd) p.Bool("c", "i", "cover", "json", "benchmem", "failfast", "short") p.Var("o", "covermode", "coverpkg", "exec", "vet", "bench", "benchtime", "blockprofile", "blockprofilerate", "count", "coverprofile", "cpu", "cpuprofile", "fuzz", "list", "memprofile", "memprofilerate", "mutexprofile", "mutexprofilefraction", "outputdir", "parallel", "run", "timeout", "fuzztime", "fuzzminimizetime", "trace", "shuffle") for name := range passFlagToTest { if b, ok := cmd.Flag.Lookup(name).Value.(boolFlag); ok && b.IsBoolFlag() { p.Bool("test." + name) } else { p.Var("test." + name) } } return p } var passFlagToTest = map[string]bool{ "bench": true, "benchmem": true, "benchtime": true, "blockprofile": true, "blockprofilerate": true, "count": true, "coverprofile": true, "cpu": true, "cpuprofile": true, "failfast": true, "fuzz": true, "fuzzminimizetime": true, "fuzztime": true, "list": true, "memprofile": true, "memprofilerate": true, "mutexprofile": true, "mutexprofilefraction": true, "outputdir": true, "parallel": true, "run": true, "short": true, "shuffle": true, "timeout": true, "trace": true, "v": true, } ================================================ FILE: cmd/internal/version/ver.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package version import ( "fmt" "runtime" "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/env" ) // ----------------------------------------------------------------------------- // Cmd - gop version var Cmd = &base.Command{ UsageLine: "gop version [-v]", Short: "Print XGo version", } var ( flag = &Cmd.Flag _ = flag.Bool("v", false, "print verbose information.") ) func init() { Cmd.Run = runCmd } func runCmd(cmd *base.Command, args []string) { fmt.Printf("gop %s %s/%s\n", env.Version(), runtime.GOOS, runtime.GOARCH) } // ----------------------------------------------------------------------------- ================================================ FILE: cmd/internal/watch/watch.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package watch import ( "log" "path/filepath" "github.com/goplus/xgo/cmd/internal/base" "github.com/goplus/xgo/tool" "github.com/goplus/xgo/x/fsnotify" "github.com/goplus/xgo/x/watcher" ) // ----------------------------------------------------------------------------- // gop watch var Cmd = &base.Command{ UsageLine: "gop watch [-v -gentest] [dir]", Short: "Monitor code changes in an XGo workspace to generate Go files", } var ( flag = &Cmd.Flag verbose = flag.Bool("v", false, "print verbose information.") debug = flag.Bool("debug", false, "show all debug information.") genTestPkg = flag.Bool("gentest", false, "generate test package.") ) func init() { Cmd.Run = runCmd } func runCmd(cmd *base.Command, args []string) { err := flag.Parse(args) if err != nil { log.Fatalln("parse input arguments failed:", err) } if *debug { fsnotify.SetDebug(fsnotify.DbgFlagAll) } if *debug || *verbose { watcher.SetDebug(watcher.DbgFlagAll) } args = flag.Args() if len(args) == 0 { args = []string{"."} } root, _ := filepath.Abs(args[0]) log.Println("Watch", root) w := watcher.New(root) go w.Run() for { dir := w.Fetch(true) log.Println("GenGo", dir) _, _, err := tool.GenGo(dir, nil, *genTestPkg) if err != nil { log.Println(err) } } } // ----------------------------------------------------------------------------- ================================================ FILE: cmd/make.go ================================================ //go:build ignore // +build ignore /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package main import ( "bytes" "flag" "fmt" "log" "os" "os/exec" "path/filepath" "regexp" "runtime" "strings" "time" ) func checkPathExist(path string, isDir bool) bool { stat, err := os.Lstat(path) // Note: os.Lstat() will not follow the symbolic link. isExists := !os.IsNotExist(err) if isDir { return isExists && stat.IsDir() } return isExists && !stat.IsDir() } func trimRight(s string) string { return strings.TrimRight(s, " \t\r\n") } // Path returns single path to check type Path struct { path string isDir bool } func (p *Path) checkExists(rootDir string) bool { absPath := filepath.Join(rootDir, p.path) return checkPathExist(absPath, p.isDir) } func getXgoRoot() string { pwd, _ := os.Getwd() pathsToCheck := []Path{ {path: "cmd/xgo", isDir: true}, {path: "builtin", isDir: true}, {path: "go.mod", isDir: false}, {path: "go.sum", isDir: false}, } for _, path := range pathsToCheck { if !path.checkExists(pwd) { println("Error: This script should be run at the root directory of xgo repository.") os.Exit(1) } } return pwd } var xgoRoot = getXgoRoot() var initCommandExecuteEnv = os.Environ() var commandExecuteEnv = initCommandExecuteEnv // Always put `gop` command as the first item and `xgo` command as the second item, // as it will be referenced by below code. var xgoBinFiles = []string{"gop", "xgo"} const ( inWindows = (runtime.GOOS == "windows") ) func init() { if inWindows { for index := range xgoBinFiles { xgoBinFiles[index] += ".exe" } } } type ExecCmdError struct { Err error Stderr string } func (p *ExecCmdError) Error() string { if e := p.Stderr; e != "" { return e } return p.Err.Error() } type iGitRemote interface { CheckRemoteUrl() CreateBranchFrom(branch, remote string) error PushCommits(remote, branch string) error DeleteBranch(branch string) error } type ( gitRemoteImpl struct{} gitRemoteNone struct{} ) func (p *gitRemoteImpl) CheckRemoteUrl() { if getGitRemoteUrl("xgo") == "" { log.Fatalln("Error: git remote xgo not found, please use `git remote add xgo git@github.com:goplus/xgo.git`.") } } func (p *gitRemoteImpl) CreateBranchFrom(branch, remote string) (err error) { _, err = execCommand("git", "fetch", remote) if err != nil { return } execCommand("git", "branch", "-D", branch) _, err = execCommand("git", "checkout", "-b", branch, remote+"/"+branch) return } func (p *gitRemoteImpl) PushCommits(remote, branch string) error { _, err := execCommand("git", "push", remote, branch) return err } func (p *gitRemoteImpl) DeleteBranch(branch string) error { _, err := execCommand("git", "branch", "-D", branch) return err } func (p *gitRemoteNone) CheckRemoteUrl() {} func (p *gitRemoteNone) CreateBranchFrom(branch, remote string) (err error) { return nil } func (p *gitRemoteNone) PushCommits(remote, branch string) error { return nil } func (p *gitRemoteNone) DeleteBranch(branch string) error { return nil } var ( gitRemote iGitRemote = &gitRemoteImpl{} ) func execCommand(command string, arg ...string) (string, error) { var stdout, stderr bytes.Buffer cmd := exec.Command(command, arg...) cmd.Stdout = &stdout cmd.Stderr = &stderr cmd.Env = commandExecuteEnv err := cmd.Run() if err != nil { err = &ExecCmdError{Err: err, Stderr: stderr.String()} } return stdout.String(), err } func getTagRev(tag string) string { const commit = "commit " stdout, err := execCommand("git", "show", tag) if err != nil || !strings.HasPrefix(stdout, commit) { return "" } data := stdout[len(commit):] if pos := strings.IndexByte(data, '\n'); pos > 0 { return data[:pos] } return "" } func getGitRemoteUrl(name string) string { stdout, err := execCommand("git", "remote", "get-url", name) if err != nil { return "" } return stdout } func getGitBranch() string { stdout, err := execCommand("git", "rev-parse", "--abbrev-ref", "HEAD") if err != nil { return "" } return trimRight(stdout) } func gitTag(tag string) error { _, err := execCommand("git", "tag", tag) return err } func gitTagAndPushTo(tag string, remote, branch string) error { if err := gitRemote.PushCommits(remote, branch); err != nil { return err } if err := gitTag(tag); err != nil { return err } return gitRemote.PushCommits(remote, tag) } func gitAdd(file string) error { _, err := execCommand("git", "add", file) return err } func gitCommit(msg string) error { out, err := execCommand("git", "commit", "-a", "-m", msg) if err != nil { if e := err.(*ExecCmdError); e.Stderr == "" { e.Stderr = out } } return err } func gitCheckoutBranch(branch string) error { _, err := execCommand("git", "checkout", branch) return err } func isGitRepo() bool { gitDir, err := execCommand("git", "rev-parse", "--git-dir") if err != nil { return false } return checkPathExist(filepath.Join(xgoRoot, trimRight(gitDir)), true) } func getBuildDateTime() string { now := time.Now() return now.Format("2006-01-02_15-04-05") } func getBuildVer() string { latestTagCommit, err := execCommand("git", "rev-list", "--tags", "--max-count=1") if err != nil { return "" } stdout, err := execCommand("git", "describe", "--tags", trimRight(latestTagCommit)) if err != nil { return "" } return fmt.Sprintf("%s devel", trimRight(stdout)) } func getGopBuildFlags() string { defaultXGoRoot := xgoRoot if gopRootFinal := os.Getenv("GOPROOT_FINAL"); gopRootFinal != "" { defaultXGoRoot = gopRootFinal } buildFlags := fmt.Sprintf("-X \"github.com/goplus/xgo/env.defaultXGoRoot=%s\"", defaultXGoRoot) buildFlags += fmt.Sprintf(" -X \"github.com/goplus/xgo/env.buildDate=%s\"", getBuildDateTime()) version := findXgoVersion() buildFlags += fmt.Sprintf(" -X \"github.com/goplus/xgo/env.buildVersion=%s\"", version) return buildFlags } func detectGopBinPath() string { return filepath.Join(xgoRoot, "bin") } func detectGoBinPath() string { goBin, ok := os.LookupEnv("GOBIN") if ok { return goBin } goPath, ok := os.LookupEnv("GOPATH") if ok { list := filepath.SplitList(goPath) if len(list) > 0 { // Put in first directory of $GOPATH. return filepath.Join(list[0], "bin") } } homeDir, _ := os.UserHomeDir() return filepath.Join(homeDir, "go", "bin") } func linkGoplusToLocalBin() string { println("Start Linking.") gopBinPath := detectGopBinPath() goBinPath := detectGoBinPath() if !checkPathExist(gopBinPath, true) { log.Fatalf("Error: %s is not existed, you should build XGo before linking.\n", gopBinPath) } if !checkPathExist(goBinPath, true) { if err := os.MkdirAll(goBinPath, 0755); err != nil { fmt.Printf("Error: target directory %s is not existed and we can't create one.\n", goBinPath) log.Fatalln(err) } } for i, file := range xgoBinFiles { targetLink := filepath.Join(goBinPath, file) if i == 0 { // `gop` is the first file, we will link it to `xgo`. file = xgoBinFiles[1] } sourceFile := filepath.Join(gopBinPath, file) if !checkPathExist(sourceFile, false) { log.Fatalf("Error: %s is not existed, you should build XGo before linking.\n", sourceFile) } if checkPathExist(targetLink, false) { // Delete existed one if err := os.Remove(targetLink); err != nil { log.Fatalln(err) } } if err := os.Symlink(sourceFile, targetLink); err != nil { log.Fatalln(err) } fmt.Printf("Link %s to %s successfully.\n", sourceFile, targetLink) } println("End linking.") return goBinPath } func buildGoplusTools(useGoProxy bool) { commandsDir := filepath.Join(xgoRoot, "cmd") buildFlags := getGopBuildFlags() if useGoProxy { println("Info: we will use goproxy.cn as a Go proxy to accelerate installing process.") commandExecuteEnv = append(commandExecuteEnv, "GOPROXY=https://goproxy.cn,direct", ) } // Install XGo binary files under current ./bin directory. gopBinPath := detectGopBinPath() if err := os.Mkdir(gopBinPath, 0755); err != nil && !os.IsExist(err) { println("Error: XGo can't create ./bin directory to put build assets.") log.Fatalln(err) } println("Building XGo tools...\n") os.Chdir(commandsDir) buildOutput, err := execCommand("go", "build", "-o", gopBinPath, "-v", "-trimpath", "-ldflags", buildFlags, "./...") if err != nil { log.Fatalln(err) } print(buildOutput) // Clear xgo run cache cleanGopRunCache() println("\nXGo tools built successfully!") } func showHelpPostInstall(installPath string) { println("\nNEXT STEP:") println("\nWe just installed XGo into the directory: ", installPath) message := ` To setup a better XGo development environment, we recommend you add the above install directory into your PATH environment variable. ` println(message) } // Install XGo tools func install() { installPath := linkGoplusToLocalBin() println("\nXGo tools installed successfully!") if _, err := execCommand("xgo", "version"); err != nil { showHelpPostInstall(installPath) } } func runTestcases() { println("Start running testcases.") os.Chdir(xgoRoot) coverage := "-coverprofile=coverage.txt" gopCommand := filepath.Join(detectGopBinPath(), xgoBinFiles[1]) if !checkPathExist(gopCommand, false) { println("Error: XGo must be installed before running testcases.") os.Exit(1) } testOutput, err := execCommand(gopCommand, "test", coverage, "-covermode=atomic", "./...") println(testOutput) if err != nil { println(err.Error()) } println("End running testcases.") } func clean() { gopBinPath := detectGopBinPath() goBinPath := detectGoBinPath() // Clean links for _, file := range xgoBinFiles { targetLink := filepath.Join(goBinPath, file) if checkPathExist(targetLink, false) { if err := os.Remove(targetLink); err != nil { log.Fatalln(err) } } } // Clean build binary files if checkPathExist(gopBinPath, true) { if err := os.RemoveAll(gopBinPath); err != nil { log.Fatalln(err) } } cleanGopRunCache() } func cleanGopRunCache() { homeDir, _ := os.UserHomeDir() runCacheDir := filepath.Join(homeDir, ".xgo", "run") files := []string{"go.mod", "go.sum"} for _, file := range files { fullPath := filepath.Join(runCacheDir, file) if checkPathExist(fullPath, false) { if err := os.Remove(fullPath); err != nil { log.Fatalln(err) } } } } func uninstall() { println("Uninstalling XGo and related tools.") clean() println("XGo and related tools uninstalled successfully.") } func isInChinaWindows() bool { // Run `systeminfo` command on windows to check locale. out, err := execCommand("systeminfo") if err != nil { fmt.Println("Run [systeminfo] command failed with error: ", err) return false } // Check if output contains `zh-cn;` return strings.Contains(out, "zh-cn;") } func isInChina() bool { if inWindows { return isInChinaWindows() } const prefix = "LANG=\"" out, err := execCommand("locale") if err != nil { return false } if strings.HasPrefix(out, prefix) { out = out[len(prefix):] return strings.HasPrefix(out, "zh_CN") } return false } // findXgoVersion returns current version of xgo func findXgoVersion() string { versionFile := filepath.Join(xgoRoot, "VERSION") // Read version from VERSION file data, err := os.ReadFile(versionFile) if err == nil { version := trimRight(string(data)) return version } // Read version from git repo if !isGitRepo() { log.Fatal("Error: must be a git repo or a VERSION file existed.") } version := getBuildVer() // Closet tag on git log return version } // releaseNewVersion tags the repo with provided new tag, and writes new tag into VERSION file. func releaseNewVersion(tag string) { if !isGitRepo() { log.Fatalln("Error: Releasing a new version could only be operated under a git repo.") } gitRemote.CheckRemoteUrl() if getTagRev(tag) != "" { log.Fatalln("Error: tag already exists -", tag) } version := tag re := regexp.MustCompile(`^v\d+?\.\d+?`) releaseBranch := re.FindString(version) if releaseBranch == "" { log.Fatal("Error: A valid version should be has form: vX.Y.Z") } sourceBranch := getGitBranch() // Checkout to release breanch if sourceBranch != releaseBranch { if err := gitCheckoutBranch(releaseBranch); err != nil { log.Fatalf("Error: checkout to release branch: %s failed with error: %v.", releaseBranch, err) } defer func() { // Checkout back to source branch if err := gitCheckoutBranch(sourceBranch); err != nil { log.Fatalf("Error: checkout to source branch: %s failed with error: %v.", sourceBranch, err) } gitRemote.DeleteBranch(releaseBranch) }() } // Cache new version versionFile := filepath.Join(xgoRoot, "VERSION") if err := os.WriteFile(versionFile, []byte(version), 0644); err != nil { log.Fatalf("Error: cache new version with error: %v\n", err) } // Commit changes gitAdd(versionFile) if err := gitCommit("release version " + version); err != nil { log.Fatalf("Error: git commit with error: %v\n", err) } // Tag the source code if err := gitTagAndPushTo(tag, "xgo", releaseBranch); err != nil { log.Fatalf("Error: gitTagAndPushTo with error: %v\n", err) } println("Released new version:", version) } func runRegtests() { println("\nStart running regtests.") cmd := exec.Command(filepath.Join(xgoRoot, "bin/"+xgoBinFiles[1]), "go", "./...") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Dir = filepath.Join(xgoRoot, "demo") err := cmd.Run() if err != nil { code := cmd.ProcessState.ExitCode() if code == 0 { code = 1 } os.Exit(code) } } func main() { isInstall := flag.Bool("install", false, "Install XGo") isBuild := flag.Bool("build", false, "Build XGo tools") isTest := flag.Bool("test", false, "Run testcases") isRegtest := flag.Bool("regtest", false, "Run regtests") isUninstall := flag.Bool("uninstall", false, "Uninstall XGo") isGoProxy := flag.Bool("proxy", false, "Set GOPROXY for people in China") isAutoProxy := flag.Bool("autoproxy", false, "Check to set GOPROXY automatically") noPush := flag.Bool("nopush", false, "Don't push to remote repo") tag := flag.String("tag", "", "Release an new version with specified tag") flag.Parse() useGoProxy := *isGoProxy if !useGoProxy && *isAutoProxy { useGoProxy = isInChina() } flagActionMap := map[*bool]func(){ isBuild: func() { buildGoplusTools(useGoProxy) }, isInstall: func() { buildGoplusTools(useGoProxy) install() }, isUninstall: uninstall, isTest: runTestcases, isRegtest: runRegtests, } // Sort flags, for example: install flag should be checked earlier than test flag. flags := []*bool{isBuild, isInstall, isTest, isRegtest, isUninstall} hasActionDone := false if *tag != "" { if *noPush { gitRemote = &gitRemoteNone{} } releaseNewVersion(*tag) hasActionDone = true } for _, flag := range flags { if *flag { flagActionMap[flag]() hasActionDone = true } } if !hasActionDone { println("Usage:\n") flag.PrintDefaults() } } ================================================ FILE: cmd/make_test.go ================================================ //go:build ignore // +build ignore package make_test import ( "bytes" "errors" "os" "os/exec" "path/filepath" "runtime" "strings" "testing" ) const ( inWindows = (runtime.GOOS == "windows") ) var script = "all.bash" var gopRoot = "" var xgoBinFiles = []string{"gop", "xgo"} var installer = "cmd/make.go" var versionFile = "VERSION" var mainVersionFile = "env/version.go" func checkPathExist(path string, isDir bool) bool { stat, err := os.Stat(path) isExists := !os.IsNotExist(err) if isDir { return isExists && stat.IsDir() } return isExists && !stat.IsDir() } func trimRight(s string) string { return strings.TrimRight(s, " \t\r\n") } func execCommand(command string, arg ...string) (string, error) { var stdout, stderr bytes.Buffer cmd := exec.Command(command, arg...) cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() if err != nil { if stderr.Len() > 0 { err = errors.New(string(stderr.String())) } } return stdout.String(), err } func getBranch() string { stdout, err := execCommand("git", "rev-parse", "--abbrev-ref", "HEAD") if err != nil { return "" } return trimRight(stdout) } func detectGoBinPath() string { goBin, ok := os.LookupEnv("GOBIN") if ok { return goBin } goPath, ok := os.LookupEnv("GOPATH") if ok { list := filepath.SplitList(goPath) if len(list) > 0 { // Put in first directory of $GOPATH. return filepath.Join(list[0], "bin") } } homeDir, _ := os.UserHomeDir() return filepath.Join(homeDir, "go", "bin") } func init() { pwd, _ := os.Getwd() gopRoot = filepath.Join(pwd, "..") installer = filepath.Join(gopRoot, installer) versionFile = filepath.Join(gopRoot, versionFile) mainVersionFile = filepath.Join(gopRoot, mainVersionFile) if inWindows { script = "all.bat" for index := range xgoBinFiles { xgoBinFiles[index] += ".exe" } } } func cleanGopRunCacheFiles(t *testing.T) { homeDir, _ := os.UserHomeDir() runCacheDir := filepath.Join(homeDir, ".xgo", "run") files := []string{"go.mod", "go.sum"} for _, file := range files { fullPath := filepath.Join(runCacheDir, file) if checkPathExist(fullPath, false) { t.Fatalf("Failed: %s found in %s directory\n", file, runCacheDir) } } } func TestAllScript(t *testing.T) { os.Chdir(gopRoot) cmd := exec.Command(filepath.Join(gopRoot, script)) if output, err := cmd.CombinedOutput(); err != nil { t.Fatalf("Failed: %v:\nOut: %s\n", err, output) } goBinPath := detectGoBinPath() for i, file := range xgoBinFiles { if i == 0 { // skip gop continue } if !checkPathExist(filepath.Join(gopRoot, "bin", file), false) { t.Fatalf("Failed: %s not found in ./bin directory\n", file) } if !checkPathExist(filepath.Join(goBinPath, file), false) { t.Fatalf("Failed: %s not found in %s/bin directory\n", file, goBinPath) } } cleanGopRunCacheFiles(t) cmd = exec.Command(filepath.Join(gopRoot, "bin", xgoBinFiles[1]), "version") if output, err := cmd.CombinedOutput(); err != nil { t.Fatalf("Failed: %v:\nOut: %s\n", err, output) } } func TestTagFlagInGitRepo(t *testing.T) { os.Chdir(gopRoot) // Setup tag := "v1.0.90" tag2 := "v1.0.91" bigtag := "v1.999.12" releaseBranch := "v1.0" nonExistBranch := "non-exist-branch" sourceBranch := getBranch() // Teardown t.Cleanup(func() { gitCmd := exec.Command("git", "tag", "-d", tag) gitCmd.CombinedOutput() gitCmd = exec.Command("git", "tag", "-d", tag2) gitCmd.CombinedOutput() execCommand("git", "checkout", sourceBranch) execCommand("git", "branch", "-D", nonExistBranch) execCommand("git", "branch", "-D", releaseBranch) }) t.Run("release new version with bad tag", func(t *testing.T) { cmd := exec.Command("go", "run", installer, "--nopush", "--tag", "xyz") if _, err := cmd.CombinedOutput(); err == nil { t.Fatal("Failed: release a bad tag should be failed.") } }) t.Run("empty tag should failed", func(t *testing.T) { cmd := exec.Command("go", "run", installer, "--nopush", "--tag", "") if out, err := cmd.CombinedOutput(); err != nil || !strings.Contains(string(out), "Usage") { t.Fatalf("Failed: %v, out: %s\n", err, out) } }) t.Run("failed when release branch corresponding to tag does not exists", func(t *testing.T) { cmd := exec.Command("go", "run", installer, "--nopush", "--tag", bigtag) if _, err := cmd.CombinedOutput(); err == nil { t.Fatal("Failed: a corresponding release branch does not exists should be failed.") } }) t.Run("release new version on release branch", func(t *testing.T) { execCommand("git", "checkout", sourceBranch) execCommand("git", "branch", "-D", releaseBranch) _, err := execCommand("git", "checkout", "-b", releaseBranch) if err != nil { t.Fatal(err) } cmd := exec.Command("go", "run", installer, "--nopush", "--tag", tag) if out, err := cmd.CombinedOutput(); err != nil { t.Log(string(out)) t.Fatalf("Failed: release tag: %s on branch: %s should not be failed", tag, releaseBranch) } if !checkPathExist(versionFile, false) { t.Fatal("Failed: a VERSION file not found.") } if data, _ := os.ReadFile(versionFile); string(data) != tag { t.Fatalf("Failed: content of VERSION file: '%s' not match tag: %s", data, tag) } // Make sure tag exists. gitCmd := exec.Command("git", "tag") if allTags, _ := gitCmd.CombinedOutput(); !strings.Contains(string(allTags), tag) { t.Fatalf("Failed: %s tag not found in tag list of this git repo.\n", tag) } }) t.Run("release new version on non-release branch", func(t *testing.T) { _, err := execCommand("git", "checkout", "-b", nonExistBranch) if err != nil { t.Fatal(err) } execCommand("git", "branch", "-D", releaseBranch) _, err = execCommand("git", "checkout", "-b", releaseBranch) if err != nil { t.Log("current branch:", getBranch()) t.Fatal(err) } execCommand("git", "checkout", nonExistBranch) cmd := exec.Command("go", "run", installer, "--nopush", "--tag", tag2) if out, err := cmd.CombinedOutput(); err != nil { t.Log(string(out)) t.Fatalf("Failed: release tag: %s on branch: %s should not be failed", tag2, nonExistBranch) } if getBranch() != nonExistBranch { t.Fatal("Failed: getBranch() != nonExistBranch") } execCommand("git", "checkout", releaseBranch) if !checkPathExist(versionFile, false) { t.Fatal("Failed: a VERSION file not found.") } if data, _ := os.ReadFile(versionFile); string(data) != tag2 { t.Fatalf("Failed: content of VERSION file: '%s' not match tag: %s", data, tag2) } gitCmd := exec.Command("git", "tag") if allTags, _ := gitCmd.CombinedOutput(); !strings.Contains(string(allTags), tag2) { t.Fatalf("Failed: %s tag not found in tag list of this git repo.\n", tag) } }) } func TestTagFlagInNonGitRepo(t *testing.T) { os.Chdir(gopRoot) // Setup gitDir := filepath.Join(gopRoot, ".git") gitBackupDir := filepath.Join(gopRoot, ".gitBackup") // Rename .git dir if checkPathExist(gitDir, true) { os.Rename(gitDir, gitBackupDir) } // Teardown t.Cleanup(func() { if checkPathExist(gitBackupDir, true) { os.Rename(gitBackupDir, gitDir) } }) t.Run("specify new tag", func(t *testing.T) { cmd := exec.Command("go", "run", installer, "--tag", "v1.0.98") output, err := cmd.CombinedOutput() if err == nil || !strings.Contains(string(output), "Error") { t.Fatal("Failed: release on a non-git repo should be failed.") } }) } func TestInstallInNonGitRepo(t *testing.T) { os.Chdir(gopRoot) // Setup gitDir := filepath.Join(gopRoot, ".git") gitBackupDir := filepath.Join(gopRoot, ".gitBackup") // Rename .git dir if checkPathExist(gitDir, true) { os.Rename(gitDir, gitBackupDir) } // Teardown t.Cleanup(func() { if checkPathExist(gitBackupDir, true) { os.Rename(gitBackupDir, gitDir) } if checkPathExist(versionFile, false) { os.Remove(versionFile) } }) installCmd := func() *exec.Cmd { return exec.Command("go", "run", installer, "--install") } t.Run("failed build operation", func(t *testing.T) { os.Remove(versionFile) cmd := installCmd() output, err := cmd.CombinedOutput() if err == nil || !strings.Contains(string(output), "Error") { t.Log(string(output)) t.Fatal("Failed: build XGo in a non-git repo and no VERSION file should be failed.") } }) t.Run("install with VERSION file", func(t *testing.T) { version := "v1.5.65535" // Use a test version // Create VERSION file if err := os.WriteFile(versionFile, []byte(version), 0644); err != nil { t.Fatal(err) } cmd := installCmd() if output, err := cmd.CombinedOutput(); err != nil { t.Fatalf("Failed: %v, output: %s\n", err, output) } cmd = exec.Command(filepath.Join(gopRoot, "bin", xgoBinFiles[1]), "version") output, err := cmd.CombinedOutput() if err != nil || !strings.Contains(string(output), version) { t.Fatalf("Failed: %v, output: %s\n", err, output) } }) } func TestHandleMultiFlags(t *testing.T) { os.Chdir(gopRoot) cmd := exec.Command("go", "run", installer, "--install", "--test", "--uninstall") if output, err := cmd.CombinedOutput(); err != nil { t.Fatalf("Failed: %v:\nOut: %s\n", err, output) } goBinPath := detectGoBinPath() // Uninstall will be the last action to run, test if all build assets being cleared. for _, file := range xgoBinFiles { if checkPathExist(filepath.Join(gopRoot, "bin", file), false) { t.Fatalf("Failed: %s found in ./bin directory\n", file) } if checkPathExist(filepath.Join(goBinPath, file), false) { t.Fatalf("Failed: %s found in %s/bin directory\n", file, goBinPath) } } } ================================================ FILE: cmd/xgo/bug_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( self "github.com/goplus/xgo/cmd/internal/bug" ) use "bug" short "Start a bug report" flagOff run args => { self.Cmd.Run self.Cmd, args } ================================================ FILE: cmd/xgo/build_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( self "github.com/goplus/xgo/cmd/internal/build" ) use "build [flags] [packages]" short "Build XGo files" flagOff run args => { self.Cmd.Run self.Cmd, args } ================================================ FILE: cmd/xgo/clean_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( self "github.com/goplus/xgo/cmd/internal/clean" ) use "clean [flags] " short "Clean all XGo auto generated files" flagOff run args => { self.Cmd.Run self.Cmd, args } ================================================ FILE: cmd/xgo/doc_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( self "github.com/goplus/xgo/cmd/internal/doc" ) use "doc [flags] [pkgPath]" short "Show documentation for package or symbol" flagOff run args => { self.Cmd.Run self.Cmd, args } ================================================ FILE: cmd/xgo/env_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( self "github.com/goplus/xgo/cmd/internal/env" ) use "env [flags] [var ...]" short "Prints XGo environment information" flagOff run args => { self.Cmd.Run self.Cmd, args } ================================================ FILE: cmd/xgo/fmt_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( self "github.com/goplus/xgo/cmd/internal/gopfmt" ) use "fmt [flags] path ..." short "Format XGo packages" flagOff run args => { self.Cmd.Run self.Cmd, args } ================================================ FILE: cmd/xgo/get_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( self "github.com/goplus/xgo/cmd/internal/gopget" ) use "get [flags] [packages]" short "Add dependencies to current module and install them" flagOff run args => { self.Cmd.Run self.Cmd, args } ================================================ FILE: cmd/xgo/go_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( self "github.com/goplus/xgo/cmd/internal/gengo" ) use "go [flags] [packages|files]" short "Convert XGo code into Go code" flagOff run args => { self.Cmd.Run self.Cmd, args } ================================================ FILE: cmd/xgo/install_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( self "github.com/goplus/xgo/cmd/internal/install" ) use "install [flags] [packages]" short "Build XGo files and install target to GOBIN" flagOff run args => { self.Cmd.Run self.Cmd, args } ================================================ FILE: cmd/xgo/main_app.gox ================================================ import ( "github.com/goplus/gogen" "github.com/qiniu/x/log" ) short "xgo is a tool for managing XGo source code." log.setFlags log.Ldefault&^log.LstdFlags gogen.GeneratedHeader = "// Code generated by xgo (XGo); DO NOT EDIT.\n\n" ================================================ FILE: cmd/xgo/mod_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( self "github.com/goplus/xgo/cmd/internal/mod" ) use "mod" short "Module maintenance" run => { help } ================================================ FILE: cmd/xgo/mod_download_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( self "github.com/goplus/xgo/cmd/internal/mod" ) use "download [flags] [modules]" short "download modules to local cache" flagOff run args => { if args.len < 1 { help return } self.CmdDownload.Run self.CmdDownload, args } ================================================ FILE: cmd/xgo/mod_init_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( self "github.com/goplus/xgo/cmd/internal/mod" ) use "init [flags] module-path" short "initialize new module in current directory" flagOff run args => { if args.len < 1 { help return } self.CmdInit.Run self.CmdInit, args } ================================================ FILE: cmd/xgo/mod_tidy_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( self "github.com/goplus/xgo/cmd/internal/mod" ) use "tidy [flags]" short "add missing and remove unused modules" flagOff run args => { self.CmdTidy.Run self.CmdTidy, args } ================================================ FILE: cmd/xgo/run_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( self "github.com/goplus/xgo/cmd/internal/run" ) use "run [flags] package [arguments...]" short "Compile and run an XGo program" flagOff run args => { self.Cmd.Run self.Cmd, args } ================================================ FILE: cmd/xgo/serve_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( self "github.com/goplus/xgo/cmd/internal/serve" ) use "serve [flags]" short "Serve as an XGo LangServer" flagOff run args => { self.Cmd.Run self.Cmd, args } ================================================ FILE: cmd/xgo/test_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( self "github.com/goplus/xgo/cmd/internal/test" ) use "test [flags] [packages]" short "Test XGo packages" flagOff run args => { self.Cmd.Run self.Cmd, args } ================================================ FILE: cmd/xgo/version_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( "runtime" self "xgo/env" ) use "version [flags]" short "Print XGo version" run => { echo "xgo ${self.version} ${runtime.GOOS}/${runtime.GOARCH}" } ================================================ FILE: cmd/xgo/watch_cmd.gox ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the License. */ import ( self "github.com/goplus/xgo/cmd/internal/watch" ) use "watch [flags] [dir]" short "Monitor code changes in an XGo workspace to generate Go files" flagOff run args => { self.Cmd.Run self.Cmd, args } ================================================ FILE: cmd/xgo/xgo_autogen.go ================================================ // Code generated by xgo (XGo); DO NOT EDIT. package main import ( "fmt" "github.com/goplus/cobra/xcmd" "github.com/goplus/gogen" "github.com/goplus/xgo/cmd/internal/bug" "github.com/goplus/xgo/cmd/internal/build" "github.com/goplus/xgo/cmd/internal/clean" "github.com/goplus/xgo/cmd/internal/doc" "github.com/goplus/xgo/cmd/internal/env" "github.com/goplus/xgo/cmd/internal/gengo" "github.com/goplus/xgo/cmd/internal/gopfmt" "github.com/goplus/xgo/cmd/internal/gopget" "github.com/goplus/xgo/cmd/internal/install" "github.com/goplus/xgo/cmd/internal/mod" "github.com/goplus/xgo/cmd/internal/run" "github.com/goplus/xgo/cmd/internal/serve" "github.com/goplus/xgo/cmd/internal/test" "github.com/goplus/xgo/cmd/internal/watch" env1 "github.com/goplus/xgo/env" "github.com/qiniu/x/log" "github.com/qiniu/x/stringutil" "runtime" ) const _ = true type Cmd_bug struct { xcmd.Command *App } type Cmd_build struct { xcmd.Command *App } type Cmd_clean struct { xcmd.Command *App } type Cmd_doc struct { xcmd.Command *App } type Cmd_env struct { xcmd.Command *App } type Cmd_fmt struct { xcmd.Command *App } type Cmd_get struct { xcmd.Command *App } type Cmd_go struct { xcmd.Command *App } type Cmd_install struct { xcmd.Command *App } type App struct { xcmd.App } type Cmd_mod struct { xcmd.Command *App } type Cmd_mod_download struct { xcmd.Command *App } type Cmd_mod_init struct { xcmd.Command *App } type Cmd_mod_tidy struct { xcmd.Command *App } type Cmd_run struct { xcmd.Command *App } type Cmd_serve struct { xcmd.Command *App } type Cmd_test struct { xcmd.Command *App } type Cmd_version struct { xcmd.Command *App } type Cmd_watch struct { xcmd.Command *App } //line cmd/xgo/main_app.gox:6 func (this *App) MainEntry() { //line cmd/xgo/main_app.gox:6:1 this.Short("xgo is a tool for managing XGo source code.") //line cmd/xgo/main_app.gox:7:1 log.SetFlags(log.Ldefault &^ log.LstdFlags) //line cmd/xgo/main_app.gox:9:1 gogen.GeneratedHeader = "// Code generated by xgo (XGo); DO NOT EDIT.\n\n" } func (this *App) Main() { _xgo_obj0 := &Cmd_bug{App: this} _xgo_obj1 := &Cmd_build{App: this} _xgo_obj2 := &Cmd_clean{App: this} _xgo_obj3 := &Cmd_doc{App: this} _xgo_obj4 := &Cmd_env{App: this} _xgo_obj5 := &Cmd_fmt{App: this} _xgo_obj6 := &Cmd_get{App: this} _xgo_obj7 := &Cmd_go{App: this} _xgo_obj8 := &Cmd_install{App: this} _xgo_obj9 := &Cmd_mod{App: this} _xgo_obj10 := &Cmd_mod_download{App: this} _xgo_obj11 := &Cmd_mod_init{App: this} _xgo_obj12 := &Cmd_mod_tidy{App: this} _xgo_obj13 := &Cmd_run{App: this} _xgo_obj14 := &Cmd_serve{App: this} _xgo_obj15 := &Cmd_test{App: this} _xgo_obj16 := &Cmd_version{App: this} _xgo_obj17 := &Cmd_watch{App: this} xcmd.XGot_App_Main(this, _xgo_obj0, _xgo_obj1, _xgo_obj2, _xgo_obj3, _xgo_obj4, _xgo_obj5, _xgo_obj6, _xgo_obj7, _xgo_obj8, _xgo_obj9, _xgo_obj10, _xgo_obj11, _xgo_obj12, _xgo_obj13, _xgo_obj14, _xgo_obj15, _xgo_obj16, _xgo_obj17) } //line cmd/xgo/bug_cmd.gox:20 func (this *Cmd_bug) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/bug_cmd.gox:20:1 this.Use("bug") //line cmd/xgo/bug_cmd.gox:22:1 this.Short("Start a bug report") //line cmd/xgo/bug_cmd.gox:24:1 this.FlagOff() //line cmd/xgo/bug_cmd.gox:26:1 this.Run__1(func(args []string) { //line cmd/xgo/bug_cmd.gox:27:1 bug.Cmd.Run(bug.Cmd, args) }) } func (this *Cmd_bug) Classfname() string { return "bug" } //line cmd/xgo/build_cmd.gox:20 func (this *Cmd_build) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/build_cmd.gox:20:1 this.Use("build [flags] [packages]") //line cmd/xgo/build_cmd.gox:22:1 this.Short("Build XGo files") //line cmd/xgo/build_cmd.gox:24:1 this.FlagOff() //line cmd/xgo/build_cmd.gox:26:1 this.Run__1(func(args []string) { //line cmd/xgo/build_cmd.gox:27:1 build.Cmd.Run(build.Cmd, args) }) } func (this *Cmd_build) Classfname() string { return "build" } //line cmd/xgo/clean_cmd.gox:20 func (this *Cmd_clean) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/clean_cmd.gox:20:1 this.Use("clean [flags] ") //line cmd/xgo/clean_cmd.gox:22:1 this.Short("Clean all XGo auto generated files") //line cmd/xgo/clean_cmd.gox:24:1 this.FlagOff() //line cmd/xgo/clean_cmd.gox:26:1 this.Run__1(func(args []string) { //line cmd/xgo/clean_cmd.gox:27:1 clean.Cmd.Run(clean.Cmd, args) }) } func (this *Cmd_clean) Classfname() string { return "clean" } //line cmd/xgo/doc_cmd.gox:20 func (this *Cmd_doc) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/doc_cmd.gox:20:1 this.Use("doc [flags] [pkgPath]") //line cmd/xgo/doc_cmd.gox:22:1 this.Short("Show documentation for package or symbol") //line cmd/xgo/doc_cmd.gox:24:1 this.FlagOff() //line cmd/xgo/doc_cmd.gox:26:1 this.Run__1(func(args []string) { //line cmd/xgo/doc_cmd.gox:27:1 doc.Cmd.Run(doc.Cmd, args) }) } func (this *Cmd_doc) Classfname() string { return "doc" } //line cmd/xgo/env_cmd.gox:20 func (this *Cmd_env) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/env_cmd.gox:20:1 this.Use("env [flags] [var ...]") //line cmd/xgo/env_cmd.gox:22:1 this.Short("Prints XGo environment information") //line cmd/xgo/env_cmd.gox:24:1 this.FlagOff() //line cmd/xgo/env_cmd.gox:26:1 this.Run__1(func(args []string) { //line cmd/xgo/env_cmd.gox:27:1 env.Cmd.Run(env.Cmd, args) }) } func (this *Cmd_env) Classfname() string { return "env" } //line cmd/xgo/fmt_cmd.gox:20 func (this *Cmd_fmt) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/fmt_cmd.gox:20:1 this.Use("fmt [flags] path ...") //line cmd/xgo/fmt_cmd.gox:22:1 this.Short("Format XGo packages") //line cmd/xgo/fmt_cmd.gox:24:1 this.FlagOff() //line cmd/xgo/fmt_cmd.gox:26:1 this.Run__1(func(args []string) { //line cmd/xgo/fmt_cmd.gox:27:1 gopfmt.Cmd.Run(gopfmt.Cmd, args) }) } func (this *Cmd_fmt) Classfname() string { return "fmt" } //line cmd/xgo/get_cmd.gox:20 func (this *Cmd_get) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/get_cmd.gox:20:1 this.Use("get [flags] [packages]") //line cmd/xgo/get_cmd.gox:22:1 this.Short("Add dependencies to current module and install them") //line cmd/xgo/get_cmd.gox:24:1 this.FlagOff() //line cmd/xgo/get_cmd.gox:26:1 this.Run__1(func(args []string) { //line cmd/xgo/get_cmd.gox:27:1 gopget.Cmd.Run(gopget.Cmd, args) }) } func (this *Cmd_get) Classfname() string { return "get" } //line cmd/xgo/go_cmd.gox:20 func (this *Cmd_go) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/go_cmd.gox:20:1 this.Use("go [flags] [packages|files]") //line cmd/xgo/go_cmd.gox:22:1 this.Short("Convert XGo code into Go code") //line cmd/xgo/go_cmd.gox:24:1 this.FlagOff() //line cmd/xgo/go_cmd.gox:26:1 this.Run__1(func(args []string) { //line cmd/xgo/go_cmd.gox:27:1 gengo.Cmd.Run(gengo.Cmd, args) }) } func (this *Cmd_go) Classfname() string { return "go" } //line cmd/xgo/install_cmd.gox:20 func (this *Cmd_install) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/install_cmd.gox:20:1 this.Use("install [flags] [packages]") //line cmd/xgo/install_cmd.gox:22:1 this.Short("Build XGo files and install target to GOBIN") //line cmd/xgo/install_cmd.gox:24:1 this.FlagOff() //line cmd/xgo/install_cmd.gox:26:1 this.Run__1(func(args []string) { //line cmd/xgo/install_cmd.gox:27:1 install.Cmd.Run(install.Cmd, args) }) } func (this *Cmd_install) Classfname() string { return "install" } //line cmd/xgo/mod_cmd.gox:20 func (this *Cmd_mod) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/mod_cmd.gox:20:1 this.Use("mod") //line cmd/xgo/mod_cmd.gox:22:1 this.Short("Module maintenance") //line cmd/xgo/mod_cmd.gox:24:1 this.Run__0(func() { //line cmd/xgo/mod_cmd.gox:25:1 this.Help() }) } func (this *Cmd_mod) Classfname() string { return "mod" } //line cmd/xgo/mod_download_cmd.gox:20 func (this *Cmd_mod_download) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/mod_download_cmd.gox:20:1 this.Use("download [flags] [modules]") //line cmd/xgo/mod_download_cmd.gox:22:1 this.Short("download modules to local cache") //line cmd/xgo/mod_download_cmd.gox:24:1 this.FlagOff() //line cmd/xgo/mod_download_cmd.gox:26:1 this.Run__1(func(args []string) { //line cmd/xgo/mod_download_cmd.gox:27:1 if len(args) < 1 { //line cmd/xgo/mod_download_cmd.gox:28:1 this.Help() //line cmd/xgo/mod_download_cmd.gox:29:1 return } //line cmd/xgo/mod_download_cmd.gox:31:1 mod.CmdDownload.Run(mod.CmdDownload, args) }) } func (this *Cmd_mod_download) Classfname() string { return "mod_download" } //line cmd/xgo/mod_init_cmd.gox:20 func (this *Cmd_mod_init) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/mod_init_cmd.gox:20:1 this.Use("init [flags] module-path") //line cmd/xgo/mod_init_cmd.gox:22:1 this.Short("initialize new module in current directory") //line cmd/xgo/mod_init_cmd.gox:24:1 this.FlagOff() //line cmd/xgo/mod_init_cmd.gox:26:1 this.Run__1(func(args []string) { //line cmd/xgo/mod_init_cmd.gox:27:1 if len(args) < 1 { //line cmd/xgo/mod_init_cmd.gox:28:1 this.Help() //line cmd/xgo/mod_init_cmd.gox:29:1 return } //line cmd/xgo/mod_init_cmd.gox:31:1 mod.CmdInit.Run(mod.CmdInit, args) }) } func (this *Cmd_mod_init) Classfname() string { return "mod_init" } //line cmd/xgo/mod_tidy_cmd.gox:20 func (this *Cmd_mod_tidy) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/mod_tidy_cmd.gox:20:1 this.Use("tidy [flags]") //line cmd/xgo/mod_tidy_cmd.gox:22:1 this.Short("add missing and remove unused modules") //line cmd/xgo/mod_tidy_cmd.gox:24:1 this.FlagOff() //line cmd/xgo/mod_tidy_cmd.gox:26:1 this.Run__1(func(args []string) { //line cmd/xgo/mod_tidy_cmd.gox:27:1 mod.CmdTidy.Run(mod.CmdTidy, args) }) } func (this *Cmd_mod_tidy) Classfname() string { return "mod_tidy" } //line cmd/xgo/run_cmd.gox:20 func (this *Cmd_run) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/run_cmd.gox:20:1 this.Use("run [flags] package [arguments...]") //line cmd/xgo/run_cmd.gox:22:1 this.Short("Compile and run an XGo program") //line cmd/xgo/run_cmd.gox:24:1 this.FlagOff() //line cmd/xgo/run_cmd.gox:26:1 this.Run__1(func(args []string) { //line cmd/xgo/run_cmd.gox:27:1 run.Cmd.Run(run.Cmd, args) }) } func (this *Cmd_run) Classfname() string { return "run" } //line cmd/xgo/serve_cmd.gox:20 func (this *Cmd_serve) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/serve_cmd.gox:20:1 this.Use("serve [flags]") //line cmd/xgo/serve_cmd.gox:22:1 this.Short("Serve as an XGo LangServer") //line cmd/xgo/serve_cmd.gox:24:1 this.FlagOff() //line cmd/xgo/serve_cmd.gox:26:1 this.Run__1(func(args []string) { //line cmd/xgo/serve_cmd.gox:27:1 serve.Cmd.Run(serve.Cmd, args) }) } func (this *Cmd_serve) Classfname() string { return "serve" } //line cmd/xgo/test_cmd.gox:20 func (this *Cmd_test) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/test_cmd.gox:20:1 this.Use("test [flags] [packages]") //line cmd/xgo/test_cmd.gox:22:1 this.Short("Test XGo packages") //line cmd/xgo/test_cmd.gox:24:1 this.FlagOff() //line cmd/xgo/test_cmd.gox:26:1 this.Run__1(func(args []string) { //line cmd/xgo/test_cmd.gox:27:1 test.Cmd.Run(test.Cmd, args) }) } func (this *Cmd_test) Classfname() string { return "test" } //line cmd/xgo/version_cmd.gox:21 func (this *Cmd_version) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/version_cmd.gox:21:1 this.Use("version [flags]") //line cmd/xgo/version_cmd.gox:23:1 this.Short("Print XGo version") //line cmd/xgo/version_cmd.gox:25:1 this.Run__0(func() { //line cmd/xgo/version_cmd.gox:26:1 fmt.Println(stringutil.Concat("xgo ", env1.Version(), " ", runtime.GOOS, "/", runtime.GOARCH)) }) } func (this *Cmd_version) Classfname() string { return "version" } //line cmd/xgo/watch_cmd.gox:20 func (this *Cmd_watch) Main(_xgo_arg0 string) { this.Command.Main(_xgo_arg0) //line cmd/xgo/watch_cmd.gox:20:1 this.Use("watch [flags] [dir]") //line cmd/xgo/watch_cmd.gox:22:1 this.Short("Monitor code changes in an XGo workspace to generate Go files") //line cmd/xgo/watch_cmd.gox:24:1 this.FlagOff() //line cmd/xgo/watch_cmd.gox:26:1 this.Run__1(func(args []string) { //line cmd/xgo/watch_cmd.gox:27:1 watch.Cmd.Run(watch.Cmd, args) }) } func (this *Cmd_watch) Classfname() string { return "watch" } func main() { new(App).Main() } ================================================ FILE: demo/_llgo/callpy/callpy.xgo ================================================ import ( "c" "py" "py/math" ) x := math.sqrt(py.float(2)) c.printf c"sqrt(2) = %f\n", x.float64 ================================================ FILE: demo/_llgo/chello/hello.xgo ================================================ import "c" c.printf c"Hello world\n" ================================================ FILE: demo/_llgo/cpphello/cpphello.xgo ================================================ import "cpp/std" println std.str("Hello world").str ================================================ FILE: demo/_llgo/defer/defer.xgo ================================================ import "c" func f(s string) bool { return len(s) > 2 } defer func() { c.printf c"hi\n" }() if s := "hello"; f(s) { defer c.printf(c"%s\n", c.allocaCStr(s)) } else { defer c.printf(c"world\n") } defer c.printf(c"bye\n") ================================================ FILE: demo/_llgo/errors/errors.xgo ================================================ import ( "c" ) // New returns an error that formats as the given text. // Each call to New returns a distinct error value even if the text is identical. func New(text string) error { return &errorString{text} } // errorString is a trivial implementation of error. type errorString struct { s string } func (e *errorString) Error() string { return e.s } e := new("an error") c.printf c.allocaCStr(e.error+"\n") ================================================ FILE: demo/_llgo/go.mod ================================================ module llgoexample go 1.18 // llgo 1.0 require github.com/goplus/lib v0.2.0 ================================================ FILE: demo/_llgo/go.sum ================================================ github.com/goplus/lib v0.2.0 h1:AjqkN1XK5H23wZMMlpaUYAMCDAdSBQ2NMFrLtSh7W4g= github.com/goplus/lib v0.2.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0= ================================================ FILE: demo/_llgo/goroutine/goroutine.xgo ================================================ import "c" done := false go func() { c.printf c"Hello, goroutine\n" done = true }() for !done { c.printf c"." } ================================================ FILE: demo/_llgo/hello/hello.xgo ================================================ echo "Hello world" ================================================ FILE: demo/_llgo/hellollgo/README.md ================================================ This is an example to show how XGo interacts with C. ```go import "c" c.printf c"Hello, llgo!\n" c.fprintf c.Stderr, c"Hi, %6.1f\n", 3.14 ``` Here we use `import "c"` to import libc. It's an abbreviation for `import "github.com/goplus/lib/c"`. It is equivalent to the following code: ```go import "github.com/goplus/lib/c" c.printf c"Hello, llgo!\n" c.fprintf c.Stderr, c"Hi, %7.1f\n", 3.14 ``` In this example we call two C standard functions `printf` and `fprintf`, pass a C variable `stderr` and two C strings in the form of `c"xxx"`. To run this demo, you need to set the `XGO_GOCMD` environment variable first. ```sh export XGO_GOCMD=llgo # default is `go` ``` Then execute `xgo run .` to see the output of this example: ``` Hello, llgo! Hi, 3.1 ``` ### Give a Star! ⭐ If you like or are using XGo to learn or start your projects, please give it a star. Thanks! ================================================ FILE: demo/_llgo/hellollgo/hello.xgo ================================================ import "c" c.printf c"Hello, llgo!\n" c.fprintf c.Stderr, c"Hi, %6.1f\n", 3.14 ================================================ FILE: demo/_llgo/matrix/matrix.xgo ================================================ import ( "c" "py" "py/numpy" ) a := py.list( py.list(1.0, 2.0, 3.0), py.list(4.0, 5.0, 6.0), py.list(7.0, 8.0, 9.0), ) b := py.list( py.list(9.0, 8.0, 7.0), py.list(6.0, 5.0, 4.0), py.list(3.0, 2.0, 1.0), ) x := numpy.add(a, b) c.printf c"a+b = %s\n", x.str.cStr ================================================ FILE: demo/_llgo/pyhello/hello.xgo ================================================ import "py/std" std.print py"Hello world" ================================================ FILE: demo/_llgo/pymax/pymax.xgo ================================================ import ( "py" "py/std" ) x := std.max(py.float(3.0), py.float(9.0), py.float(23.0), py.float(100.0)) std.print(x) list := py.list(3.0, 9.0, 23.0, 100.0) y := std.max(std.iter(list)) std.print(y) ================================================ FILE: demo/_llgo/pyprint/print.xgo ================================================ import ( "py" "py/std" ) x := py.float(3.14) std.print(x) ================================================ FILE: demo/_llgo/pytensor/tensor.xgo ================================================ import ( "py" "py/std" "py/torch" ) data := py.list( py.list(1.0, 2.0), py.list(3.0, 4.0), ) x := torch.tensor(data) std.print(x) ================================================ FILE: demo/_llgo/qsort/qsort.xgo ================================================ import ( "c" "unsafe" ) a := [100, 8, 23, 2, 7] c.qsort unsafe.Pointer(&a[0]), 5, unsafe.Sizeof(0), (a, b) => { return c.Int(*(*int)(a) - *(*int)(b)) } for v in a { c.printf c"%d\n", v } ================================================ FILE: demo/_llgo/reflect/reflect.xgo ================================================ import ( "c" "reflect" ) tyIntSlice := reflect.sliceOf(reflect.typeOf(0)) v := reflect.zero(tyIntSlice) v = reflect.append(v, reflect.valueOf(1), reflect.valueOf(2), reflect.valueOf(3)) for i, n := 0, v.len; i < n; i++ { item := v.index(i) c.Printf c"%d\n", item.int } ================================================ FILE: demo/_llgo/sqlitedemo/sqlitedemo.xgo ================================================ import ( "c" "c/os" "c/sqlite" ) func check(err sqlite.Errno, db *sqlite.Sqlite3, at string) { if err != sqlite.OK { c.printf c"==> %s Error: (%d) %s\n", c.allocaCStr(at), err, db.errmsg c.exit 1 } } func checkDone(err sqlite.Errno, db *sqlite.Sqlite3, at string) { if err != sqlite.Done { check err, db, at } } os.remove c"test.db" db, err := sqlite.open(c"test.db") check err, db, "sqlite: Open" err = db.exec(c"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)", nil, nil, nil) check err, db, "sqlite: Exec CREATE TABLE" stmt, err := db.prepareV3("INSERT INTO users (id, name) VALUES (?, ?)", 0, nil) check err, db, "sqlite: PrepareV3 INSERT" stmt.bindInt 1, 100 stmt.bindText 2, c"Hello World", -1, nil err = stmt.step checkDone err, db, "sqlite: Step INSERT 1" stmt.reset stmt.bindInt 1, 200 stmt.bindText 2, c"This is llgo", -1, nil err = stmt.step checkDone err, db, "sqlite: Step INSERT 2" stmt.close stmt, err = db.prepareV3("SELECT * FROM users", 0, nil) check err, db, "sqlite: PrepareV3 SELECT" for { if err = stmt.step; err != sqlite.HasRow { break } c.printf c"==> id=%d, name=%s\n", stmt.columnInt(0), stmt.columnText(1) } checkDone err, db, "sqlite: Step done" stmt.close db.close ================================================ FILE: demo/_llgo/statistics/statistics.xgo ================================================ import ( "c" "py" "py/statistics" ) list := py.list(1.0, 2.0, 3.0, 4.0, 4.0) mean := statistics.mean(list) c.printf c"mean(1, 2, 3, 4, 4) = %f\n", mean.float64 ================================================ FILE: demo/_llgo/tetris/tetris.xgo ================================================ import ( "c" "c/raylib" ) const ( BOARD_WIDTH = 10 BOARD_HEIGHT = 20 BLOCK_SIZE = 30 SCREENWIDTH = 300 SCREENHEIGHT = 600 ) const MAX_BLOCKS = 4 type Shape struct { Blocks [MAX_BLOCKS]raylib.Vector2 Color raylib.Color } var SHAPES = []Shape{ {Blocks: [MAX_BLOCKS]raylib.Vector2{{X: 0, Y: 0}, {X: 1, Y: 0}, {X: 2, Y: 0}, {X: 3, Y: 0}}, Color: raylib.SKYBLUE}, {Blocks: [MAX_BLOCKS]raylib.Vector2{{X: 0, Y: 0}, {X: 1, Y: 0}, {X: 0, Y: 1}, {X: 1, Y: 1}}, Color: raylib.YELLOW}, {Blocks: [MAX_BLOCKS]raylib.Vector2{{X: 1, Y: 0}, {X: 0, Y: 1}, {X: 1, Y: 1}, {X: 2, Y: 1}}, Color: raylib.PURPLE}, {Blocks: [MAX_BLOCKS]raylib.Vector2{{X: 1, Y: 0}, {X: 2, Y: 0}, {X: 0, Y: 1}, {X: 1, Y: 1}}, Color: raylib.GREEN}, {Blocks: [MAX_BLOCKS]raylib.Vector2{{X: 0, Y: 0}, {X: 1, Y: 0}, {X: 1, Y: 1}, {X: 2, Y: 1}}, Color: raylib.RED}, {Blocks: [MAX_BLOCKS]raylib.Vector2{{X: 0, Y: 0}, {X: 0, Y: 1}, {X: 1, Y: 1}, {X: 2, Y: 1}}, Color: raylib.BLUE}, {Blocks: [MAX_BLOCKS]raylib.Vector2{{X: 2, Y: 0}, {X: 0, Y: 1}, {X: 1, Y: 1}, {X: 2, Y: 1}}, Color: raylib.ORANGE}, } var board [BOARD_HEIGHT][BOARD_WIDTH]raylib.Color var curShape Shape var curPos raylib.Vector2 var fallTime = c.Float(0) var fallSpeed = c.Float(0.2) var score = 0 var scoreText = (*c.Char)(c.malloc(20)) var gameOver = false func genShape() { curShape = SHAPES[raylib.getRandomValue(0, 6)] curPos = raylib.Vector2{BOARD_WIDTH/2 - 1, 0} } func checkCollision() bool { for i := 0; i < MAX_BLOCKS; i++ { x := int(curPos.X + curShape.Blocks[i].X) y := int(curPos.Y + curShape.Blocks[i].Y) if x < 0 || x >= BOARD_WIDTH || y >= BOARD_HEIGHT || (y >= 0 && board[y][x] != raylib.BLANK) { return true } } return false } func lockShape() { for i := 0; i < MAX_BLOCKS; i++ { x := int(curPos.X + curShape.Blocks[i].X) y := int(curPos.Y + curShape.Blocks[i].Y) if y >= 0 { board[y][x] = curShape.Color } } } func rotateShape() { rotated := curShape for i := 0; i < MAX_BLOCKS; i++ { x := rotated.Blocks[i].X rotated.Blocks[i].X = -rotated.Blocks[i].Y rotated.Blocks[i].Y = x } temp := curShape curShape = rotated if checkCollision() { curShape = temp } } func clearLines() int { linesCleared := 0 for y := BOARD_HEIGHT - 1; y >= 0; y-- { lineFull := true for x := 0; x < BOARD_WIDTH; x++ { if board[y][x] == raylib.BLANK { lineFull = false break } } if lineFull { for yy := y; yy > 0; yy-- { for x := 0; x < BOARD_WIDTH; x++ { board[yy][x] = board[yy-1][x] } } for x := 0; x < BOARD_WIDTH; x++ { board[0][x] = raylib.BLANK } y += 1 linesCleared += 1 } } return linesCleared } func keyPressed(key c.Int) bool { return raylib.isKeyPressed(key) || raylib.isKeyPressedRepeat(key) } raylib.initWindow SCREENWIDTH, SCREENHEIGHT, c"tetris (powered by raylib and XGo)" raylib.setTargetFPS 60 genShape for !raylib.windowShouldClose && !gameOver { fallTime += raylib.getFrameTime if fallTime >= fallSpeed { fallTime = 0 curPos.Y += 1 if checkCollision() { curPos.Y -= 1 lockShape linesCleared := clearLines() score += linesCleared * 100 genShape if checkCollision() { gameOver = true } } } if keyPressed(raylib.KEY_LEFT) { curPos.X-- if checkCollision() { curPos.X++ } } if keyPressed(raylib.KEY_RIGHT) { curPos.X++ if checkCollision() { curPos.X-- } } if keyPressed(raylib.KEY_SPACE) || keyPressed(raylib.KEY_UP) || keyPressed(raylib.KEY_DOWN) { rotateShape } raylib.beginDrawing raylib.clearBackground raylib.RAYWHITE for y := 0; y < BOARD_HEIGHT; y++ { for x := 0; x < BOARD_WIDTH; x++ { raylib.drawRectangle c.Int(x*BLOCK_SIZE), c.Int(y*BLOCK_SIZE), c.Int(BLOCK_SIZE-1), c.Int(BLOCK_SIZE-1), board[y][x] } } for i := 0; i < MAX_BLOCKS; i++ { raylib.drawRectangle c.Int((curPos.X+curShape.Blocks[i].X)*BLOCK_SIZE), c.Int((curPos.Y+curShape.Blocks[i].Y)*BLOCK_SIZE), BLOCK_SIZE-1, BLOCK_SIZE-1, curShape.Color } c.sprintf scoreText, c"Score:%d", score raylib.drawText scoreText, 10, 10, 20, raylib.BLACK raylib.endDrawing } for !raylib.windowShouldClose { raylib.beginDrawing raylib.clearBackground raylib.RAYWHITE raylib.drawText c"Game Over", SCREENWIDTH/2-50, SCREENHEIGHT/2-10, 20, raylib.RED raylib.drawText scoreText, SCREENWIDTH/2-50, SCREENHEIGHT/2+10, 20, raylib.BLACK raylib.endDrawing } ================================================ FILE: demo/_tinygo/blink/blink.xgo ================================================ import ( "machine" "time" ) led := machine.LED led.configure {Mode: machine.PinOutput} for { led.low time.sleep 1s led.high time.sleep 1s } ================================================ FILE: demo/_tinygo/go.mod ================================================ module tinygoexample go 1.18 // tinygo 0.32 ================================================ FILE: demo/_tinygo/sortdemo/sort.xgo ================================================ import "sort" vals := [32, 58, 25, 92, 45, 78] sort.ints vals for v in vals { println v } texts := ["apple", "banana", "cherry", "date", "elderberry", "fig"] sort.slice texts, (i, j) => { leni, lenj := len(texts[i]), len(texts[j]) if leni != lenj { return leni < lenj } return texts[i] < texts[j] } for v in texts { println v } ================================================ FILE: demo/clsinit/Rect.gox ================================================ var ( w, h = 10, 20 color int border float64 = 1.2 ) echo *this ================================================ FILE: demo/domaintext/domaintext.xgo ================================================ config := json`{ "server": "localhost", "port": 8080, "features": ["auth", "logging"] }`! echo config.port ================================================ FILE: demo/dql-fs/fsq.xgo ================================================ for e in fs`.`.**.file.match("*.xgo") { echo e.path } ================================================ FILE: demo/dql-json/jq.xgo ================================================ doc := json`{ "animals": [ {"class": "gopher", "at": "Line 1"}, {"class": "armadillo", "at": "Line 2"}, {"class": "zebra", "at": "Line 3"}, {"class": "unknown", "at": "Line 4"}, {"class": "gopher", "at": "Line 5"}, {"class": "bee", "at": "Line 6"}, {"class": "gopher", "at": "Line 7"}, {"class": "zebra", "at": "Line 8"} ] } `! for animal in doc.animals.*@($class == "zebra") { echo animal.$at } ================================================ FILE: demo/dql-links/links.xgo ================================================ import "os" import "github.com/goplus/xgo/dql/html" doc := html.source(os.Args[1]) for a in doc.**.a { if url := a.$href; url != "" { echo url } } ================================================ FILE: demo/dql-xgo/xgoq.xgo ================================================ doc := xgo` x, y := "Hi", 123 echo x print y `! stmts := doc.shadowEntry.body.list.*@(self.class == "ExprStmt") for fn in stmts.x@(self.class == "CallExpr").fun@(self.class == "Ident") { echo fn.$name } ================================================ FILE: demo/dql-yaml/yq.xgo ================================================ config := yaml`server: localhost port: 8080 features: - auth - logging `! echo config.port for feature in config.features.* { echo feature._value } ================================================ FILE: demo/fullspec/mixgo-complex/bar.go ================================================ package main import ( "io" "log" "testing" ) type ift interface { io.Closer f(int) string g() } type impl struct { a T } func Bar(t *testing.T) int { log.Println("Hello") t.Log("Hello", "world") return 0 } ================================================ FILE: demo/fullspec/mixgo-complex/foo.xgo ================================================ // Pkg doc import ( "io" "log" ) type ift2 interface { io.Closer f(int) string g() } // T doc type T struct { io.Closer } func (T) Close() (err error) { log.Println("Hi!") return } func (t T) g() {} func (t T) f(a int) (b string) { _ = t.Closer return } func Foo(i *impl) string { i.a.f(0) i.a.g() return "" } // foo golang/go#61561: interface instances aren't concurrency-safe // as they are not completed by the type checker. func foo(a, b int) string { return "" } println "Hello, world", T{}.f(0) ================================================ FILE: demo/fullspec/mixgo-complex/xgo_autogen.go ================================================ // Code generated by xgo (XGo); DO NOT EDIT. package main import ( "fmt" "io" "log" ) const _ = true type ift2 interface { io.Closer f(int) string g() } // T doc type T struct { io.Closer } //line demo/fullspec/mixgo-complex/foo.xgo:19:1 func (T) Close() (err error) { //line demo/fullspec/mixgo-complex/foo.xgo:20:1 log.Println("Hi!") //line demo/fullspec/mixgo-complex/foo.xgo:21:1 return } //line demo/fullspec/mixgo-complex/foo.xgo:24:1 func (t T) g() { } //line demo/fullspec/mixgo-complex/foo.xgo:26:1 func (t T) f(a int) (b string) { //line demo/fullspec/mixgo-complex/foo.xgo:27:1 _ = t.Closer //line demo/fullspec/mixgo-complex/foo.xgo:28:1 return } //line demo/fullspec/mixgo-complex/foo.xgo:31:1 func Foo(i *impl) string { //line demo/fullspec/mixgo-complex/foo.xgo:32:1 i.a.f(0) //line demo/fullspec/mixgo-complex/foo.xgo:33:1 i.a.g() //line demo/fullspec/mixgo-complex/foo.xgo:34:1 return "" } //line demo/fullspec/mixgo-complex/foo.xgo:37:1 // foo golang/go#61561: interface instances aren't concurrency-safe // as they are not completed by the type checker. func foo(a int, b int) string { //line demo/fullspec/mixgo-complex/foo.xgo:40:1 return "" } //line demo/fullspec/mixgo-complex/foo.xgo:43 func main() { //line demo/fullspec/mixgo-complex/foo.xgo:43:1 fmt.Println("Hello, world", T{}.f(0)) } ================================================ FILE: demo/fullspec/overloadfunc1/add.xgo ================================================ func add = ( func(a, b int) int { return a + b } func(a, b string) string { return a + b } ) println add(100, 7) println add("Hello", "World") ================================================ FILE: demo/fullspec/overloadfunc2/mul.xgo ================================================ func mulInt(a, b int) int { return a * b } func mulFloat(a, b float64) float64 { return a * b } func mul = ( mulInt mulFloat ) println mul(100, 7) println mul(1.2, 3.14) ================================================ FILE: demo/fullspec/overloadmethod/method.xgo ================================================ type foo struct { } func (a *foo) mulInt(b int) *foo { println "mulInt" return a } func (a *foo) mulFoo(b *foo) *foo { println "mulFoo" return a } func (foo).mul = ( (foo).mulInt (foo).mulFoo ) var a, b *foo var c = a.mul(100) var d = a.mul(c) ================================================ FILE: demo/fullspec/overloadop1/overloadop.xgo ================================================ type foo struct { } func (a foo) + (b foo) (ret foo) { println "a + b" return } func (a foo) - (b foo) (ret foo) { println "a - b" return } func (a foo) -> (b foo) { println "a -> b" } func (a foo) <> (b foo) { println "a <> b" } func -(a foo) (ret foo) { println "-a" return } func ++(a foo) { println "a++" } func (a foo) != (b foo) bool { println "a != b" return true } var a, b foo var c = a + b var d = a - b var e = -a var f = a != b println f a++ a -> b a <> b ================================================ FILE: demo/fullspec/overloadop2/overloadop.xgo ================================================ type foo struct { } func (a foo) mulInt(b int) (ret foo) { println "a * int" return } func (a foo) mulFoo(b foo) (ret foo) { println "a * b" return } func intMulFoo(a int, b foo) (ret foo) { println "int * b" return } func (foo).* = ( (foo).mulInt (foo).mulFoo intMulFoo ) var a, b foo var c = a * 10 var d = a * b var e = 10 * a ================================================ FILE: demo/fullspec/tpl-gen-ast/gen_calc_ast.xgo ================================================ import ( "encoding/json" "os" "xgo/tpl" "xgo/tpl/token" ) type Expr any type UnaryExpr struct { OpPos token.Pos Op token.Token X Expr } type BinaryExpr struct { X Expr OpPos token.Pos Op token.Token Y Expr } type BasicLit struct { ValuePos token.Pos Kind token.Token Value string } cl := tpl` expr = operand % ("*" | "/") % ("+" | "-") => { return tpl.binaryOp(true, self, (op, x, y) => { return &BinaryExpr{ X: x, OpPos: op.Pos, Op: op.Tok, Y: y, } }) } operand = basicLit | unaryExpr unaryExpr = "-" operand => { op := self[0].(*tpl.Token) return &UnaryExpr{ OpPos: op.Pos, Op: op.Tok, X: self[1], } } basicLit = INT | FLOAT => { op := self.(*tpl.Token) return &BasicLit{ ValuePos: op.Pos, Kind: op.Tok, Value: op.Lit, } } `! print "> " for line in os.Stdin { e, err := cl.parseExpr(line, nil) if err != nil { print err, "\n> " } else { print string(json.marshalIndent(e, "", " ")!), "\n> " } } ================================================ FILE: demo/gsh-exec/exec.gsh ================================================ xgo "run", "./foo" exec "xgo run ./foo" exec "FOO=100 xgo run ./foo" exec {"FOO": "101"}, "xgo", "run", "./foo" exec "xgo", "run", "./foo" exec "ls $HOME" ls ${HOME} ================================================ FILE: demo/gsh-exec/foo/foo.xgo ================================================ import "os" foo := os.getenv("FOO") if foo != "" { echo foo } else { echo "FOO not found" } ================================================ FILE: demo/kwargs/run.xgo ================================================ type Config (timeout, maxRetries int, debug bool) func run(task int, cfg Config?) { if cfg.timeout == 0 { cfg.timeout = 30 } if cfg.maxRetries == 0 { cfg.maxRetries = 3 } echo "timeout:", cfg.timeout, "maxRetries:", cfg.maxRetries, "debug:", cfg.debug echo "task:", task } run 100, timeout = 60, maxRetries = 5 run 200 ================================================ FILE: demo/lambda1/lambda.xgo ================================================ func f(x float64, t func(float64) float64) float64 { return t(x) } echo f(1.0, x => 2 * x) echo f(5.0, (x) => { return 2 * x }) ================================================ FILE: demo/mapliteral/mapliteral.xgo ================================================ func echoS2f32(vals map[string]float32) { echo vals } echo {"Monday": 1, "Sunday": 7} echoS2f32 {"Monday": 1, "Sunday": 7} var a map[string]any = {"Monday": 1, "Sunday": 7} echo a ================================================ FILE: demo/mixgo/README.md ================================================ This is an example to show how to mix Go/XGo code in the same package. In this example, we have a Go source file named `a.go`: ```go package main import "fmt" func p(a interface{}) { sayMix() fmt.Println("Hello,", a) } ``` And we have an XGo source file named `b.xgo`: ```go func sayMix() { println "Mix Go and XGo" } p "world" ``` You can see that Go calls an XGo function named `sayMix`, and XGo calls a Go function named `p`. As you are used to in Go programming, this kind of circular reference is allowed. Run `xgo run .` to see the output of this example: ``` Mix Go and XGo Hello, world ``` ### Give a Star! ⭐ If you like or are using XGo to learn or start your projects, please give it a star. Thanks! ================================================ FILE: demo/mixgo/a.go ================================================ package main import "fmt" func p(a any) { sayMix() fmt.Println("Hello,", a) } ================================================ FILE: demo/mixgo/b.xgo ================================================ func sayMix() { println "Mix Go and XGo" } p "world" ================================================ FILE: demo/mixgo/xgo_autogen.go ================================================ // Code generated by xgo (XGo); DO NOT EDIT. package main import "fmt" const _ = true //line demo/mixgo/b.xgo:1:1 func sayMix() { //line demo/mixgo/b.xgo:2:1 fmt.Println("Mix Go and XGo") } //line demo/mixgo/b.xgo:5 func main() { //line demo/mixgo/b.xgo:5:1 p("world") } ================================================ FILE: demo/sliceliteral/sliceliteral.xgo ================================================ func echoF32s(vals []float32) { echo vals } func anyslice() []any { return [10, 3.14, 200] } echo [10, 3.14, 200] echoF32s [10, 3.14, 200] var a []any = [10, 3.14, 200] a = [10, 3.14, 200] echo a echo anyslice() ================================================ FILE: demo/stringtrans/transform.xgo ================================================ t := "u_int32_t" echo t.split("_").capitalize.join("") echo t.split("_").repeat(3).join(" ") ================================================ FILE: demo/tpl-calc/calc.xgo ================================================ import ( "os" "xgo/tpl" ) cl := tpl` expr = operand % ("*" | "/") % ("+" | "-") => { return tpl.binaryOp(true, self, (op, x, y) => { switch op.Tok { case '+': return x.(float64) + y.(float64) case '-': return x.(float64) - y.(float64) case '*': return x.(float64) * y.(float64) case '/': return x.(float64) / y.(float64) } panic("unexpected") }) } operand = basicLit | unaryExpr unaryExpr = "-" operand => { return -(self[1].(float64)) } basicLit = INT | FLOAT => { return self.(*tpl.Token).Lit.float! } `! print "> " for line in os.Stdin { e, err := cl.parseExpr(line, nil) if err != nil { print err, "\n> " } else { print e, "\n> " } } ================================================ FILE: demo/tpl-calc-dump/calc_dump.xgo ================================================ import ( "os" "xgo/tpl" ) cl := tpl` expr = termExpr % ("+" | "-") termExpr = unaryExpr % ("*" | "/") unaryExpr = operand | "-" unaryExpr operand = INT | FLOAT | "(" expr ")" `! print "> " for line in os.Stdin { e, err := cl.parseExpr(line, nil) if err != nil { print "${err}\n> " } else { tpl.dump e print "> " } } ================================================ FILE: demo/tpl-intlist/ints.xgo ================================================ import "xgo/tpl" cl := tpl` expr = INT % "," => { return tpl.ListOp[int](self, v => { return v.(*tpl.Token).Lit.int! }) } `! echo cl.parseExpr("1, 2, 3", nil)! ================================================ FILE: demo/tpl-natural-lang/nlang.xgo ================================================ import "os" cl := tpl` expr = subject verb object subject = "I" | "You" | "He" | "She" | "It" | "Dog" | "Cat" object = "me" | "you" | "him" | "her" | "it" | "fish" | "apple" | "banana" | "dog" | "cat" verb = "attack" | "love" | "eat" | "hate" `! print "> " for line in os.Stdin { e, err := cl.parseExpr(line, nil) if err != nil { print "${err}\n> " } else { print e, "\n > " } } ================================================ FILE: demo/tpl-parser-demo/demo.xgo ================================================ cl := tpl`expr = *INT`! echo cl.parseExpr("1 2 3", nil)! cl = tpl`expr = INT % ","`! echo cl.parseExpr("1, 2, 3", nil)! cl = tpl`expr = INT % ("+" | "-")`! echo cl.parseExpr("1 + 2 - 3", nil)! ================================================ FILE: demo/tpl-pseudo/gauss.pseudo ================================================ DECLARE n, i, sum : INTEGER OUTPUT "please input n:" INPUT n i <- 1 sum <- 0 WHILE i <= n DO sum <- sum + i i <- i + 1 ENDWHILE OUTPUT sum ================================================ FILE: demo/tpl-pseudo/pseudo.gox ================================================ import ( "fmt" "os" "reflect" "xgo/tpl" "xgo/tpl/token" "xgo/tpl/variant/delay" _ "xgo/tpl/variant/builtin" _ "xgo/tpl/variant/math" _ "xgo/tpl/variant/time" ) var ( vars map[string]any consts map[string]any ) func exists(name string) bool { _, ok := vars[name] if !ok { _, ok = consts[name] } return ok } func value(name string) (v any, ok bool) { v, ok = vars[name] if !ok { v, ok = consts[name] } return } func setValue(name string, v any) { oldv, ok := vars[name] if !ok { panic "variable ${name} is undefined" } if reflect.typeOf(oldv) != reflect.typeOf(v) { panic "assignment of ${name}: type mismatch" } vars[name] = v } func chgValue(name string, chg func(oldv any) any) { oldv, ok := vars[name] if !ok { panic "variable ${name} is undefined" } vars[name] = chg(oldv) } if len(os.Args) < 2 { echo "Usage: tpl-pseudo " return } vars = {} consts = {} cl := tpl` stmts = *stmtEOS => { return delay.stmtList(self) } stmtEOS = stmt ";" => { return self[0] } stmt = varStmt | constStmt | outputStmt | inputStmt | ifStmt | whileStmt | untilStmt | assignStmt varStmt = "DECLARE" (IDENT % ",") ":" typeExpr => { namelist := self[1].([]any) typeVal := self[3] return delay.rangeOp(namelist, v => { t := v.(*tpl.Token) name := t.Lit if exists(name) { tpl.panic t.Pos, "${name} exists" } vars[name] = typeVal }) } constStmt = "CONSTANT" IDENT "<-" expr => { t := self[1].(*tpl.Token) return delay.evalOp(self[3], v => { name := t.Lit if exists(name) { tpl.panic t.Pos, "${name} exists" } consts[name] = v }) } assignStmt = IDENT "<-" expr => { t := self[0].(*tpl.Token) return delay.setValue(t.Lit, setValue, self[2]) } inputStmt = "INPUT" IDENT => { t := self[1].(*tpl.Token) return delay.chgValue(t.Lit, chgValue, oldv => { v := reflect.new(type(oldv)) fmt.scanln(v.Interface())! return v.elem.Interface() }) } outputStmt = "OUTPUT" (expr % ",") => { exprlist := self[1].([]any) return delay.list(exprlist, vals => { echo vals... }) } ifStmt = "IF" expr "THEN" ";" stmts ?("ELSE" ";" stmts) "ENDIF" => { return delay.ifElse(self[1], self[4], self[5], 2) } whileStmt = "WHILE" expr "DO" ";" stmts "ENDWHILE" => { return delay.while(self[1], self[4]) } untilStmt = "REPEAT" ";" stmts "UNTIL" expr => { return delay.repeatUntil(self[2], self[4]) } typeExpr = integer | real | string | boolean integer = "INTEGER" => { return 0 } real = "REAL" => { return 0.0 } string = "STRING" => { return "" } boolean = "BOOLEAN" => { return false } expr = cmpExpr % and % or => { return tpl.binaryOp(true, self, (op, x, y) => { return delay.logicOp(op.Tok, x, y) }) } and = "AND" => { return &tpl.Token{Tok: token.LAND} } or = "OR" => { return &tpl.Token{Tok: token.LOR} } cmpExpr = mathExpr % ("<" | "<=" | ">" | ">=" | "=" | "<>") => { return tpl.binaryOp(false, self, (op, x, y) => { return delay.compare(op.Tok, x, y) }) } mathExpr = operand % ("*" | "/" | "%") % ("+" | "-") => { return tpl.binaryOp(true, self, (op, x, y) => { return delay.mathOp(op.Tok, x, y) }) } operand = basicLit | parenExpr | unaryExpr | identOrCall identOrCall = IDENT ?("(" ?(expr % ",") ")") => { t := self[0].(*tpl.Token) if params := self[1]; params != nil { return delay.call(true, t.Lit, params.([]any)[1]) } return delay.valueOf(t.Lit, value) } parenExpr = "(" expr ")" => { return self[1] } unaryExpr = ("-" | "+" | "!") operand => { op := self[0].(*tpl.Token) return delay.unaryOp(op.Tok, self[1]) } basicLit = intVal | floatVal | stringVal | true | false true = "true" => { return true } false = "false" => { return false } stringVal = STRING => { return self.(*tpl.Token).Lit.unquote! } floatVal = FLOAT => { return self.(*tpl.Token).Lit.float! } intVal = INT => { return self.(*tpl.Token).Lit.int! } `! delay.initUniverse "builtin", "math", "time" e, err := cl.parse(os.Args[1], nil, nil) if err != nil { fprintln os.Stderr, err } else { delay.eval e } ================================================ FILE: demo/tpl-vcalc/variant_calc.xgo ================================================ import ( "os" "xgo/tpl" "xgo/tpl/token" "xgo/tpl/variant" _ "xgo/tpl/variant/builtin" _ "xgo/tpl/variant/math" _ "xgo/tpl/variant/time" ) cl := tpl` expr = cmpExpr % and % or => { return tpl.binaryOp(true, self, (op, x, y) => { return variant.logicOp(op.Tok, x, y) }) } and = "and" | "AND" | "&&" => { return &tpl.Token{Tok: token.LAND} } or = "or" | "OR" | "||" => { return &tpl.Token{Tok: token.LOR} } cmpExpr = mathExpr % ("==" | "=" | "!=" | "<>" | "<" | "<=" | ">" | ">=") => { return tpl.binaryOp(false, self, (op, x, y) => { return variant.compare(op.Tok, x, y) }) } mathExpr = operand % ("*" | "/" | "%") % ("+" | "-") => { return tpl.binaryOp(true, self, (op, x, y) => { return variant.mathOp(op.Tok, x, y) }) } operand = basicLit | parenExpr | unaryExpr | callExpr parenExpr = "(" expr ")" => { return self[1] } unaryExpr = ("-" | "+" | "!") operand => { op := self[0].(*tpl.Token) return variant.unaryOp(op.Tok, self[1]) } callExpr = IDENT "(" ?(expr % ",") ")" => { fn := self[0].(*tpl.Token).Lit return variant.call(true, fn, self[2]) } basicLit = intVal | floatVal | stringVal | true | false true = "true" => { return true } false = "false" => { return false } stringVal = STRING => { return self.(*tpl.Token).Lit.unquote! } floatVal = FLOAT => { return self.(*tpl.Token).Lit.float! } intVal = INT => { return self.(*tpl.Token).Lit.int! } `! variant.initUniverse "builtin", "math", "time" print "> " for line in os.Stdin { e, err := cl.parseExpr(line, nil) if err != nil { print err, "\n> " } else { print e, "\n> " } } ================================================ FILE: demo/tupletype/tuple.xgo ================================================ type Point (x, y int) pt := Point(2, 3) echo pt.x, pt.y pt = (100, 200) echo pt pt2 := Point(pt) echo pt2 pt3 := Point(y = 5, x = 3) echo pt3.x, pt3.y pts := make(map[Point]string) pts[(0, 0)] = "origin" echo pts ================================================ FILE: demo/typeasparamsfunc/col.go ================================================ package main import ( "fmt" "reflect" ) func XGox_Col[T any](name string) { fmt.Printf("%v: %s\n", reflect.TypeOf((*T)(nil)).Elem(), name) } ================================================ FILE: demo/typeasparamsfunc/typeAsParamsFunc.xgo ================================================ col string, "name" col int, "age" ================================================ FILE: demo/typeasparamsmethod/col.go ================================================ package main import ( "fmt" "reflect" ) type basetype interface { int | string } type Table struct { } func XGot_Table_XGox_Col__0[T basetype](p *Table, name string) { fmt.Printf("XGot_Table_XGox_Col__0 %v: %s\n", reflect.TypeOf((*T)(nil)).Elem(), name) } func XGot_Table_XGox_Col__1[Array any](p *Table, name string) { fmt.Printf("XGot_Table_XGox_Col__1 %v: %s\n", reflect.TypeOf((*Array)(nil)).Elem(), name) } ================================================ FILE: demo/typeasparamsmethod/typeAsParamsMethod.xgo ================================================ var tbl Table tbl.col int, "age" tbl.col bool, "male" ================================================ FILE: demo/typeparamscast/foo.go ================================================ package main type M = map[string]any type basetype interface { string | int | bool | float64 } type Var__0[T basetype] struct { Val T } type Var__1[T map[string]any] struct { Val T } func XGox_Var_Cast__0[T basetype]() *Var__0[T] { return new(Var__0[T]) } func XGox_Var_Cast__1[T map[string]any]() *Var__1[T] { return new(Var__1[T]) } ================================================ FILE: demo/typeparamscast/typecast.xgo ================================================ println Var(int) ================================================ FILE: demo/unit-test/foo.xgo ================================================ func foo(v int) int { return v * 2 } ================================================ FILE: demo/unit-test/foo_test.gox ================================================ if v := foo(50); v != 100 { t.error "foo(50) ret: ${v}" } t.run "foo -10", t => { if foo(-10) != -20 { t.fatal "foo(-10) != -20" } } t.run "foo 0", t => { if foo(0) != 0 { t.fatal "foo(0) != 0" } } ================================================ FILE: demo/unitliteral/unitlit.xgo ================================================ import "time" echo "Let's wait for a second" time.sleep 1s echo "Hello, world" ================================================ FILE: demo/xgo-calc/calc.xgo ================================================ import ( "math" "os" "xgo/ast" "xgo/parser" "xgo/token" ) func calc(e ast.Expr) float64 { switch e := e.(type) { case *ast.BasicLit: return e.Value.float! case *ast.BinaryExpr: switch e.Op { case token.ADD: return calc(e.X) + calc(e.Y) case token.SUB: return calc(e.X) - calc(e.Y) case token.MUL: return calc(e.X) * calc(e.Y) case token.QUO: return calc(e.X) / calc(e.Y) } panic("unknown binary operator") case *ast.CallExpr: switch e.Fun.(*ast.Ident).Name { case "sin": return math.Sin(calc(e.Args[0])) case "cos": return math.Cos(calc(e.Args[0])) case "pow": return math.Pow(calc(e.Args[0]), calc(e.Args[1])) } panic("unknown function") case *ast.ParenExpr: return calc(e.X) case *ast.UnaryExpr: switch e.Op { case token.SUB: return -calc(e.X) } panic("unknown unary operator") } panic("unknown expression") } print "> " for line in os.Stdin { e, err := parser.parseExpr(line) if err != nil { print "error: ${err}\n> " } else { print "${calc(e)}\n> " } } ================================================ FILE: demo/xgo-parser/parser.xgo ================================================ import ( "xgo/ast" "xgo/parser" ) e := parser.parseExpr("10 + 3.2")!.(*ast.BinaryExpr) echo e.X, e.Op, e.Y ================================================ FILE: demo/xgo-sample/a.xgo ================================================ package main func a() { println("a") } func Ab() { println("ab") } ================================================ FILE: demo/xgo-sample/b.xgo ================================================ package main import ( ab "github.com/goplus/xgo/demo/xgo-sample/cpkag/b" ) a() Ab() ab.Ab() ================================================ FILE: demo/xgo-sample/cpkag/b/ab.go ================================================ package ab func Ab() { println("ab") } ================================================ FILE: demo/xgo-scanner/rpncalc/rpncalc.xgo ================================================ import ( "os" "xgo/scanner" "xgo/token" ) func calc(expr string) string { var vals []float64 s := scanner.new(expr, nil, 0) for { pos, tok, lit := s.scan() switch tok { case token.INT, token.FLOAT: vals <- lit.float! case token.ADD, token.SUB, token.MUL, token.QUO: x := len(vals) - 1 switch tok { case token.ADD: vals[x-1] += vals[x] case token.SUB: vals[x-1] -= vals[x] case token.MUL: vals[x-1] *= vals[x] case token.QUO: vals[x-1] /= vals[x] } vals = vals[:x] case token.EOF, token.SEMICOLON: return vals[0].string default: return "${pos}: invalid token ${tok}" } } } print "> " for line in os.Stdin { print "${calc(line)}\n> " } ================================================ FILE: demo/xgo-scanner/scanner.xgo ================================================ import "xgo/scanner" s := scanner.new("10 + 3.2", nil, 0) _, _, lit1 := s.scan() _, tok2, _ := s.scan() _, _, lit3 := s.scan() echo lit1, tok2, lit3 ================================================ FILE: demo/xgo-scanner/simplecalc/calc.xgo ================================================ import ( "os" "xgo/scanner" "xgo/token" ) func calc(expr string) string { var ( ops []token.Token vals []float64 ) prec := { token.ADD: 1, token.SUB: 1, token.MUL: 2, token.QUO: 2, } s := scanner.new(expr, nil, 0) n := 0 // op count rv := true // require value neg := false // negative number for { pos, tok, lit := s.scan() switch tok { case token.INT, token.FLOAT: if !rv { return "${pos}: require operator" } v := lit.float! if neg { v = -v neg = false } vals <- v rv = false case token.ADD, token.SUB, token.MUL, token.QUO, token.SEMICOLON: if rv { if tok == token.SUB { neg = !neg // toggle negative flag continue } return "${pos}: require value" } for n > 0 && prec[ops[n-1]] >= prec[tok] { x := len(vals) - 1 switch ops[n-1] { case token.ADD: vals[x-1] += vals[x] case token.SUB: vals[x-1] -= vals[x] case token.MUL: vals[x-1] *= vals[x] case token.QUO: vals[x-1] /= vals[x] } vals = vals[:x] n-- } if tok == token.SEMICOLON { return vals[0].string } ops = append(ops[:n], tok) rv = true n++ default: return "${pos}: invalid token ${tok}" } } } print "> " for line in os.Stdin { print "${calc(line)}\n> " } ================================================ FILE: demo/xgo-typeof/typeof.xgo ================================================ echo type([1]), type("Hi") ================================================ FILE: doc/_testdata/gopoFn/in.go ================================================ package foo const GopPackage = true const Gopo_Add = "AddInt,,AddString" // Add doc func Add__1(a, b float64) float64 { return 0 } // AddInt doc func AddInt(a, b int) int { return 0 } // AddString doc func AddString(a, b string) string { return "" } ================================================ FILE: doc/_testdata/gopoFn/out.expect ================================================ == Func Add == Doc: AddInt doc func Add(a, b int) int == Func Add == Doc: Add doc func Add(a, b float64) float64 == Func Add == Doc: AddString doc func Add(a, b string) string == Func AddInt == Doc: AddInt doc func AddInt(a, b int) int == Func AddString == Doc: AddString doc func AddString(a, b string) string ================================================ FILE: doc/_testdata/gopoMethod/in.go ================================================ package foo const GopPackage = true const Gopo__T__Gop_Add = ".AddInt,AddString" type T int // AddInt doc func (p T) AddInt(b int) *T { return nil } // AddString doc func AddString(this *T, b string) *T { return nil } ================================================ FILE: doc/_testdata/gopoMethod/out.expect ================================================ == Type T == - Func AddString - Doc: AddString doc func AddString(this *T, b string) *T - Method AddInt - Recv: T Doc: AddInt doc func (p T) AddInt(b int) *T - Method Gop_Add - Recv: T Doc: AddInt doc func (p T) Gop_Add(b int) *T - Method Gop_Add - Recv: *T Doc: AddString doc func (this *T) Gop_Add(b string) *T ================================================ FILE: doc/_testdata/overloadFn/in.go ================================================ package foo const GopPackage = true // Bar doc func Bar__0() { } ================================================ FILE: doc/_testdata/overloadFn/out.expect ================================================ == Func Bar == Doc: Bar doc func Bar() ================================================ FILE: doc/_testdata/overloadMethod/in.go ================================================ package foo const GopPackage = true type T int // Bar doc 1 func (p *T) Bar__0() { } // Bar doc 2 func (t T) Bar__1() { } ================================================ FILE: doc/_testdata/overloadMethod/out.expect ================================================ == Type T == - Method Bar - Recv: *T Doc: Bar doc 1 func (p *T) Bar() - Method Bar - Recv: T Doc: Bar doc 2 func (t T) Bar() ================================================ FILE: doc/_testdata/xgoOverloadFn/in.go ================================================ package foo const XGoPackage = true // Bar doc func Bar__0() { } ================================================ FILE: doc/_testdata/xgoOverloadFn/out.expect ================================================ == Func Bar == Doc: Bar doc func Bar() ================================================ FILE: doc/_testdata/xgoOverloadMethod/in.go ================================================ package foo const XGoPackage = true type T int // Bar doc 1 func (p *T) Bar__0() { } // Bar doc 2 func (t T) Bar__1() { } ================================================ FILE: doc/_testdata/xgoOverloadMethod/out.expect ================================================ == Type T == - Method Bar - Recv: *T Doc: Bar doc 1 func (p *T) Bar() - Method Bar - Recv: T Doc: Bar doc 2 func (t T) Bar() ================================================ FILE: doc/builtin.md ================================================ # Built‐in Functions XGo extends Go's standard built-in functions with additional capabilities for common operations. This document provides a comprehensive reference for all built-in functions available in XGo. ## Standard Go Built-in Functions XGo supports all standard Go built-in functions: ### Memory and Data Structure Operations **`append(slice []Type, elems ...Type) []Type`** Appends elements to the end of a slice. Returns the updated slice. ```go numbers := []int{1, 2, 3} numbers = append(numbers, 4, 5) echo numbers // Output: [1 2 3 4 5] ``` **`len(v Type) int`** Returns the length of arrays, slices, maps, strings, or channels. ```go echo len("Hello") // Output: 5 echo len([]int{1,2,3}) // Output: 3 ``` **`cap(v Type) int`** Returns the capacity of arrays, slices, or channels. ```go s := make([]int, 3, 5) echo len(s) // Output: 3 echo cap(s) // Output: 5 ``` **`make(t Type, size ...IntegerType) Type`** Allocates and initializes slices, maps, or channels. ```go slice := make([]int, 5, 10) // length 5, capacity 10 m := make(map[string]int) // empty map ch := make(chan int, 10) // buffered channel ``` **`copy(dst, src []Type) int`** Copies elements from source to destination slice. Returns the number of elements copied. ```go src := []int{1, 2, 3} dst := make([]int, 3) n := copy(dst, src) echo n // Output: 3 echo dst // Output: [1 2 3] ``` **`delete(m map[Type]Type1, key Type)`** Deletes a key from a map. ```go m := map[string]int{"a": 1, "b": 2} delete(m, "a") echo m // Output: map[b:2] ``` **`clear[T ~[]Type | ~map[Type]Type1](t T)`** Clears all entries in maps or sets slice elements to zero values. ```go m := map[string]int{"a": 1, "b": 2} clear(m) echo len(m) // Output: 0 ``` **`new(Type) *Type`** Allocates memory and returns a pointer to zero value. ```go p := new(int) echo *p // Output: 0 ``` ### Numeric Operations **`max[T cmp.Ordered](x T, y ...T) T`** Returns the largest value among arguments. ```go echo max(1, 5, 3, 9, 2) // Output: 9 echo max(3.14, 2.71) // Output: 3.14 ``` **`min[T cmp.Ordered](x T, y ...T) T`** Returns the smallest value among arguments. ```go echo min(1, 5, 3, 9, 2) // Output: 1 echo min(3.14, 2.71) // Output: 2.71 ``` **`complex(r, i FloatType) ComplexType`** Constructs a complex number from real and imaginary parts. ```go c := complex(3.0, 4.0) echo c // Output: (3+4i) ``` **`real(c ComplexType) FloatType`** Returns the real part of a complex number. ```go c := 3 + 4i echo real(c) // Output: 3 ``` **`imag(c ComplexType) FloatType`** Returns the imaginary part of a complex number. ```go c := 3 + 4i echo imag(c) // Output: 4 ``` ### Control Flow **`panic(v any)`** Stops normal execution and begins panicking. ```go if value < 0 { panic("negative value not allowed") } ``` **`recover() any`** Recovers from a panic in deferred functions. ```go defer func() { if r := recover(); r != nil { println("Recovered from:", r) } }() ``` ### Channel Operations **`close(c chan<- Type)`** Closes a channel. ```go ch := make(chan int) close(ch) ``` ## I/O and Formatting Functions XGo provides enhanced I/O functions with a cleaner API: ### Standard Output **`Echo(a ...any) (n int, err error)`** Formats and writes to standard output with spaces between operands and a newline appended. ```go echo "Hello", "World" // Output: Hello World echo 42, true, 3.14 // Output: 42 true 3.14 ``` **`Print(a ...any) (n int, err error)`** Writes to standard output, adding spaces only when neither operand is a string. ```go print("Hello", "World") // Output: HelloWorld print(1, 2, 3) // Output: 1 2 3 ``` **`Println(a ...any) (n int, err error)`** Writes to standard output with spaces between operands and a newline appended. ```go println("Hello", "World") // Output: Hello World ``` **`Printf(format string, a ...any) (n int, err error)`** Formats according to format specifier and writes to standard output. ```go printf("Name: %s, Age: %d\n", "Alice", 30) // Output: Name: Alice, Age: 30 ``` ### String Formatting **`Sprint(a ...any) string`** Returns formatted string, adding spaces when neither operand is a string. ```go s := sprint("Hello", "World") echo s // Output: HelloWorld ``` **`Sprintln(a ...any) string`** Returns formatted string with spaces between operands and a newline. ```go s := sprintln("Hello", "World") echo s // Output: Hello World ``` **`Sprintf(format string, a ...any) string`** Returns string formatted according to format specifier. ```go s := sprintf("Age: %d", 25) echo s // Output: Age: 25 ``` ### Writer Operations **`Fprint(w io.Writer, a ...any) (n int, err error)`** Writes to specified writer. ```go file, _ := create("output.txt") fprint(file, "Hello", "World") ``` **`Fprintln(w io.Writer, a ...any) (n int, err error)`** Writes to specified writer with newline. ```go file, _ := create("output.txt") fprintln(file, "Hello", "World") ``` **`Fprintf(w io.Writer, format string, a ...any) (n int, err error)`** Writes formatted output to specified writer. ```go file, _ := create("output.txt") fprintf(file, "Count: %d\n", 42) ``` ### Error Operations **`Errorf(format string, a ...any) error`** Creates formatted error. Supports `%w` verb for error wrapping. ```go err := errorf("failed to process: %w", originalError) ``` **`Errorln(args ...any)`** Formats and prints to standard error. ```go errorln("Warning:", "Something went wrong") ``` **`Fatal(args ...any)`** Formats and prints to standard error (typically exits program). ```go fatal("Critical error occurred") ``` ## File Operations **`Open(name string) (*os.File, error)`** Opens file for reading with O_RDONLY mode. ```go file, err := open("data.txt") if err != nil { errorln(err) } defer file.close ``` **`Create(name string) (*os.File, error)`** Creates or truncates file with mode 0o666. ```go file, err := create("output.txt") if err != nil { errorln(err) } defer file.close ``` ## Type Reflection **`Type(i any) reflect.Type`** Returns the reflection Type representing the dynamic type of i. ```go t := type(42) echo t.name // Output: int s := "hello" echo type(s).name // Output: string ``` ## Line Reading XGo provides convenient line reading utilities: **`Lines(r io.Reader) osx.LineReader`** Returns a LineReader for reading lines. ```go file, _ := open("data.txt") for line in lines(file) { echo line } ``` **`Blines(r io.Reader) osx.BLineReader`** Returns a BLineReader for reading lines as byte slices. ```go file, _ := open("data.txt") for line in blines(file) { echo string(line) } ``` **`(r io.Reader).XGo_Enum() osx.LineIter`** Returns a LineIter for iterating over lines (supports `for in` syntax). ```go file, _ := open("data.txt") for line in file { echo line } ``` ## String Methods When working with strings, XGo provides convenient method syntax for common operations. ### Length and Counting **`(s string).Len() int`** Returns the number of bytes in the string. ```go echo "Hello".len // Output: 5 ``` **`(s string).Count(substr string) int`** Counts non-overlapping instances of substring. ```go echo "hello world".count("l") // Output: 3 echo "aaaa".count("aa") // Output: 2 ``` ### Case Conversion **`(s string).ToUpper() string`** Converts all letters to uppercase. ```go echo "Hello".toUpper // Output: HELLO ``` **`(s string).ToLower() string`** Converts all letters to lowercase. ```go echo "Hello".toLower // Output: hello ``` **`(s string).ToTitle() string`** Converts all letters to title case. ```go echo "hello world".toTitle // Output: HELLO WORLD ``` **`(s string).Capitalize() string`** Capitalizes the first letter only. ```go echo "hello world".capitalize // Output: Hello world ``` ### String Manipulation **`(s string).Repeat(count int) string`** Returns string repeated count times. ```go echo "Ha".repeat(3) // Output: HaHaHa ``` **`(s string).ReplaceAll(old, new string) string`** Replaces all non-overlapping instances of old with new. ```go echo "Hello".replaceAll("l", "L") // Output: HeLLo ``` **`(s string).Replace(old, new string, n int) string`** Returns a copy of the string with the first `n` non-overlapping instances of `old` replaced by `new`. If `n < 0`, there is no limit on the number of replacements. ```go s := "hello world" result := s.replace("world", "XGo", -1) echo result // Output: hello XGo ``` ### Trimming **`(s string).Trim(cutset string) string`** Removes leading and trailing characters from cutset. ```go echo " hello ".trim(" ") // Output: hello echo "!!hello!!".trim("!") // Output: hello ``` **`(s string).TrimSpace() string`** Removes leading and trailing whitespace. ```go echo " hello ".trimSpace // Output: hello ``` **`(s string).TrimLeft(cutset string) string`** Removes leading characters from cutset. ```go echo "###hello".trimLeft("#") // Output: hello ``` **`(s string).TrimRight(cutset string) string`** Removes trailing characters from cutset. ```go echo "hello###".trimRight("#") // Output: hello ``` **`(s string).TrimPrefix(prefix string) string`** Removes leading prefix if present. ```go echo "Hello World".trimPrefix("Hello ") // Output: World ``` **`(s string).TrimSuffix(suffix string) string`** Removes trailing suffix if present. ```go echo "file.txt".trimSuffix(".txt") // Output: file ``` ### Splitting **`(s string).Fields() []string`** Splits string around whitespace. ```go echo "hello world xgo".fields // Output: [hello world xgo] ``` **`(s string).Split(sep string) []string`** Splits string around separator. ```go echo "a,b,c".split(",") // Output: [a b c] ``` **`(s string).SplitN(sep string, n int) []string`** Splits string with count limit. ```go echo "a-b-c-d".splitN("-", 2) // Output: [a b-c-d] ``` **`(s string).SplitAfter(sep string) []string`** Splits after each separator. ```go echo "a,b,c".splitAfter(",") // Output: [a, b, c] ``` **`(s string).SplitAfterN(sep string, n int) []string`** Splits after separator with count limit. ```go echo "a,b,c,d".splitAfterN(",", 2) // Output: [a, b,c,d] ``` ### Searching **`(s string).Index(substr string) int`** Returns index of first instance of substring, or -1 if not found. ```go echo "hello".index("ll") // Output: 2 echo "hello".index("x") // Output: -1 ``` **`(s string).IndexByte(c byte) int`** Returns index of first instance of byte. ```go echo "hello".indexByte('l') // Output: 2 ``` **`(s string).IndexRune(r rune) int`** Returns index of first instance of rune. ```go echo "hello".indexRune('o') // Output: 4 ``` **`(s string).IndexAny(chars string) int`** Returns index of first instance of any character from chars. ```go echo "hello".indexAny("aeiou") // Output: 1 (finds 'e') ``` **`(s string).LastIndex(substr string) int`** Returns index of last instance of substring. ```go echo "hello".lastIndex("l") // Output: 3 ``` **`(s string).LastIndexByte(c byte) int`** Returns index of last instance of byte. ```go echo "hello".lastIndexByte('l') // Output: 3 ``` **`(s string).LastIndexAny(chars string) int`** Returns index of last instance of any character. ```go echo "hello".lastIndexAny("aeiou") // Output: 4 (finds 'o') ``` ### Testing **`(s string).Contains(substr string) bool`** Reports whether substring is present. ```go echo "hello".contains("ll") // Output: true echo "hello".contains("xyz") // Output: false ``` **`(s string).ContainsAny(chars string) bool`** Reports whether any character from chars is present. ```go echo "hello".containsAny("aeiou") // Output: true ``` **`(s string).ContainsRune(r rune) bool`** Reports whether rune is present. ```go echo "hello".containsRune('e') // Output: true ``` **`(s string).HasPrefix(prefix string) bool`** Reports whether string begins with prefix. ```go echo "hello".hasPrefix("hel") // Output: true echo "hello".hasPrefix("bye") // Output: false ``` **`(s string).HasSuffix(suffix string) bool`** Reports whether string ends with suffix. ```go echo "hello.txt".hasSuffix(".txt") // Output: true ``` **`(s string).EqualFold(t string) bool`** Reports case-insensitive equality. ```go echo "Hello".equalFold("hello") // Output: true ``` ### Comparison **`(s string).Compare(b string) int`** Returns 0 if equal, -1 if less, +1 if greater. ```go echo "abc".compare("abc") // Output: 0 echo "abc".compare("xyz") // Output: -1 echo "xyz".compare("abc") // Output: 1 ``` ### Quoting **`(s string).Quote() string`** Returns double-quoted Go string literal. ```go echo "hello\nworld".quote // Output: "hello\nworld" ``` **`(s string).Unquote() (string, error)`** Interprets string as a quoted Go string literal. ```go s, err := `"hello\nworld"`.unquote echo s // Output: hello // world ``` ### Type Conversion **`(s string).Int() (int, error)`** Parses string as base-10 integer. ```go n, err := "42".int if err == nil { echo n // Output: 42 } ``` **`(s string).Int64() (int64, error)`** Parses string as 64-bit signed integer. ```go n, err := "9223372036854775807".int64 if err == nil { echo n // Output: 9223372036854775807 } ``` **`(s string).Uint64() (uint64, error)`** Parses string as 64-bit unsigned integer. ```go n, err := "18446744073709551615".uint64 if err == nil { echo n // Output: 18446744073709551615 } ``` **`(s string).Float() (float64, error)`** Parses string as 64-bit floating-point number. ```go f, err := "3.14159".float if err == nil { echo f // Output: 3.14159 } ``` ## Numeric Type String Methods XGo provides String() methods for numeric types to easily convert numbers to strings. **`(i int).String() string`** Converts int to base-10 string. ```go echo (42).string // Output: 42 ``` **`(i int64).String() string`** Converts int64 to base-10 string. ```go n := int64(123456789) echo n.string // Output: 123456789 ``` **`(u uint64).String() string`** Converts uint64 to base-10 string. ```go n := uint64(18446744073709551615) echo n.string // Output: 18446744073709551615 ``` **`(f float64).String() string`** Converts float64 to string using format 'g' with precision -1. ```go echo (3.14159).string // Output: 3.14159 ``` ## String Slice Methods XGo provides method syntax for operations on string slices, making batch operations more convenient. ### Information **`(v []string).Len() int`** Returns the number of elements. ```go words := []string{"hello", "world"} echo words.len // Output: 2 ``` **`(v []string).Cap() int`** Returns the capacity. ```go words := make([]string, 2, 5) echo words.cap // Output: 5 ``` ### Joining **`(v []string).Join(sep string) string`** Concatenates elements with separator. ```go words := []string{"hello", "world", "xgo"} echo words.join(" ") // Output: hello world xgo echo words.join(", ") // Output: hello, world, xgo ``` ### Batch Case Conversions **`(v []string).Capitalize() []string`** Capitalizes first letter of each string. ```go words := []string{"hello", "world"} echo words.capitalize // Output: [Hello World] ``` **`(v []string).ToTitle() []string`** Title-cases all strings. ```go words := []string{"hello", "world"} echo words.toTitle // Output: [HELLO WORLD] ``` **`(v []string).ToUpper() []string`** Upper-cases all strings. ```go words := []string{"hello", "world"} echo words.toUpper // Output: [HELLO WORLD] ``` **`(v []string).ToLower() []string`** Lower-cases all strings. ```go words := []string{"HELLO", "WORLD"} echo words.toLower // Output: [hello world] ``` ### Batch Manipulation **`(v []string).Repeat(count int) []string`** Repeats each string count times. ```go words := []string{"ha", "ho"} echo words.repeat(3) // Output: [hahaha hohoho] ``` **`(v []string).Replace(old, new string, n int) []string`** Replaces occurrences in each string. ```go words := []string{"hello", "yellow"} echo words.replace("ll", "LL", -1) // Output: [heLLo yeLLow] ``` **`(v []string).ReplaceAll(old, new string) []string`** Replaces all occurrences in each string. ```go words := []string{"hello", "yellow"} echo words.replaceAll("l", "L") // Output: [heLLo yeLLow] ``` ### Batch Trimming **`(v []string).Trim(cutset string) []string`** Trims each string. ```go words := []string{" hello ", " world "} echo words.trim(" ") // Output: [hello world] ``` **`(v []string).TrimSpace() []string`** Removes whitespace from each string. ```go words := []string{" hello ", " world "} echo words.trimSpace // Output: [hello world] ``` **`(v []string).TrimLeft(cutset string) []string`** Removes leading characters from each string. ```go words := []string{"###hello", "###world"} echo words.trimLeft("#") // Output: [hello world] ``` **`(v []string).TrimRight(cutset string) []string`** Removes trailing characters from each string. ```go words := []string{"hello###", "world###"} echo words.trimRight("#") // Output: [hello world] ``` **`(v []string).TrimPrefix(prefix string) []string`** Removes prefix from each string. ```go words := []string{"Mr. John", "Mr. Smith"} echo words.trimPrefix("Mr. ") // Output: [John Smith] ``` **`(v []string).TrimSuffix(suffix string) []string`** Removes suffix from each string. ```go files := []string{"file1.txt", "file2.txt"} echo files.trimSuffix(".txt") // Output: [file1 file2] ``` ## Notes ### Method Syntax XGo allows calling many standard library functions as methods on their first argument. This provides a more fluent API while maintaining compatibility with Go's standard library. For example: ```go // Traditional Go style s := strings.ToUpper("hello") // XGo method style s := "hello".toUpper ``` Both styles work in XGo, giving you flexibility in how you write your code. ### Performance Considerations When performing batch operations on string slices, the method syntax creates a new slice with transformed values: ```go // Creates a new slice with all strings uppercased upper := words.toUpper // Original slice unchanged echo words // Original values echo upper // Uppercased values ``` ### For In Loops XGo uses `for in` syntax for iterating over collections, which is more intuitive than Go's traditional `for range`: ```go // Iterate over slice words := []string{"hello", "world", "xgo"} for word in words { echo word } // Iterate over file lines file, _ := open("data.txt") for line in file { echo line } ``` ================================================ FILE: doc/classfile.md ================================================ XGo Classfiles ===== ``` One language can change the world. XGo is a "DSL" for all domains. ``` Rob Pike once said that if he could only introduce one feature to Go, he would choose `interface` instead of `goroutine`. `classfile` (and `class framework`) is as important to XGo as `interface` is to Go. In the design philosophy of XGo, we do not recommend `DSL` (Domain Specific Language). But `SDF` (Specific Domain Friendliness) is very important. The XGo philosophy about `SDF` is: ``` Don't define a language for specific domain. Abstract domain knowledge for it. ``` XGo introduces `classfile` and `class framework` to abstract domain knowledge. * STEM Education: [spx: An XGo 2D Game Engine](https://github.com/goplus/spx) * AI Programming: [mcp: An XGo implementation of the Model Context Protocol (MCP)](https://github.com/goplus/mcp) * AI Programming: [mcptest: An XGo MCP Test Framework](https://github.com/goplus/mcp/tree/main/mtest) * Web Programming: [yap: Yet Another HTTP Web Framework](https://github.com/goplus/yap) * Web Programming: [yaptest: An XGo HTTP Test Framework](https://github.com/goplus/yap/tree/main/ytest) * Web Programming: [ydb: An XGo Database Framework](https://github.com/goplus/yap/tree/main/ydb) * CLI Programming: [cobra: A Commander for modern XGo CLI interactions](https://github.com/goplus/cobra) * CLI Programming: [gsh: An alternative to write shell scripts](https://github.com/qiniu/x/tree/main/gsh) * Unit Test: [class framework: Unit Test](#class-framework-unit-test) * Mechanism: [What's Classfile](#whats-classfile) Sound a bit abstract? Let's take web programming as an example. First let us create a file named [get.yap](https://github.com/goplus/yap/blob/main/demo/classfile2_hello/get.yap) with the following content: ```go html `Hello, YAP!` ``` Execute the following commands: ```sh gop mod init hello gop get github.com/goplus/yap@latest gop mod tidy gop run . ``` A simplest web program is running now. At this time, if you visit http://localhost:8080, you will get: ``` Hello, YAP! ``` YAP uses filenames to define routes. `get.yap`'s route is `get "/"` (GET homepage), and `get_p_#id.yap`'s route is `get "/p/:id"` (In fact, the filename can also be `get_p_:id.yap`, but it is not recommended because `:` is not allowed to exist in filenames under Windows). Let's create a file named [get_p_#id.yap](https://github.com/goplus/yap/blob/main/demo/classfile2_hello/get_p_%23id.yap) with the following content: ```coffee json { "id": ${id}, } ``` Execute `gop run .` and visit http://localhost:8080/p/123, you will get: ``` {"id": "123"} ``` Why is `yap` so easy to use? How does it do it? Let us analyze the principles one by one. ### What's classfile What's a classfile? And why it is called `classfile`? First let's create a file called `Rect.gox`: ```go var ( Width, Height int ) func Area() int { return Width * Height } ``` Then we create `hello.xgo` file in the same directory: ```go rect := &Rect{10, 20} echo rect.area ``` Then we execute `gop run .` to run it and get the result: ```sh 200 ``` This shows that the `Rect.gox` file actually defines a class named `Rect`. If we express it in Go syntax, it looks like this: ```go type Rect struct { Width, Height int } func (this *Rect) Area() int { return this.Width * this.Height } ``` So the name `classfile` comes from the fact that it actually defines a class. You may ask: What is the value of doing this? The value lies in its ease of use, especially for children and non-expert programmers. Let's look at this syntax: ```go var ( Width, Height int ) func Area() int { return Width * Height } ``` Defining variables and defining functions are all familiar to them while learning sequential programming. They can define new types using syntax they already know by heart. This will be valuable in getting a wider community to learn XGo. ### What's class framework Of course, this is not enough to make classfiles an exciting feature. What's more important is its ability to abstract domain knowledge. It is accomplished by defining `base class` for a class and defining `relationships between multiple classes`. What is a `class framework`? Usually it consists of a `project class` and multiple `work classes`. The class framework not only specifies the `base class` of all `project class` and `work classes`, but also organizes all these classes together by the base class of project class. There can be no work classes, that is, the entire classfile consists of only one project class. This is a bit abstract. Let's take the [2D Game Engine spx](https://github.com/goplus/spx) as an example. The base class of project class of `spx class framework` is called `Game`. The base class of work class is called `Sprite`. Obviously, there will only be one Game instance in a game, but there are many types of sprites, so many types of work classes are needed, but they all have the same base class called `Sprite`. XGo's class framework allows you to specify different base classes for different work classes. Although this is rare, it can be done. How does XGo identify various class files of a class framework? by its filename. By convention, if we define a class framework called `foo`, then its project class is usually called `main_foo.gox`, and the work class is usually called `xxx_foo.gox`. If this class framework does not have a work class, then the project class only needs to ensure that the suffix is `_foo.gox`, and the class name can be freely chosen. The earliest version of XGo allows classfiles to be identified through custom file extensions. For example, the project class of the `spx class framework` is called `main.spx`, and the work class is called `xxx.spx`. Although this ability to customize extensions is still retained for now, we do not recommend its use and there is no guarantee that it will continue to be available in the future. ### class framework: Unit Test XGo has a built-in class framework to simplify unit testing. This class framework has the file suffix `_test.gox`. Suppose you have a function named `foo`: ```go func foo(v int) int { return v * 2 } ``` Then you can create a `foo_test.gox` file to test it (see [unit-test/foo_test.gox](../demo/unit-test/foo_test.gox)): ```go if v := foo(50); v != 100 { t.error "foo(50) ret: ${v}" } t.run "foo -10", t => { if foo(-10) != -20 { t.fatal "foo(-10) != -20" } } t.run "foo 0", t => { if foo(0) != 0 { t.fatal "foo(0) != 0" } } ``` You don't need to define a series of `TestXXX` functions like Go, just write your test code directly. If you want to run a subtest case, use `t.run`. ### yap: Yet Another Go/XGo HTTP Web Framework This class framework has the file suffix `.yap`. See [yap: Yet Another HTTP Web Framework](https://github.com/goplus/yap) for more details. #### Router and Parameters YAP uses filenames to define routes. `get.yap`'s route is `get "/"` (GET homepage), and `get_p_#id.yap`'s route is `get "/p/:id"` (In fact, the filename can also be `get_p_:id.yap`, but it is not recommended because `:` is not allowed to exist in filenames under Windows). Let's create a file named [get_p_#id.yap](https://github.com/goplus/yap/blob/main/demo/classfile2_hello/get_p_%23id.yap) with the following content: ```coffee json { "id": ${id}, } ``` Execute `gop run .` and visit http://localhost:8080/p/123, you will get: ``` {"id": "123"} ``` #### YAP Template In most cases, we don't use the `html` directive to generate html pages, but use the `yap` template engine. See [get_p_#id.yap](https://github.com/goplus/yap/blob/main/demo/classfile2_blog/get_p_%23id.yap): ```coffee yap "article", { "id": ${id}, } ``` It means finding a template called `article` to render. See [yap/article_yap.html](https://github.com/goplus/yap/blob/main/demo/classfile2_blog/yap/article_yap.html): ```html Article {{.id}} ``` #### Run at specified address By default the YAP server runs on `localhost:8080`, but you can change it in [main.yap](https://github.com/goplus/yap/blob/main/demo/classfile2_blog/main.yap) file: ```coffee run ":8888" ``` #### Static files Static files server demo ([main.yap](https://github.com/goplus/yap/blob/main/demo/classfile2_static/main.yap)): ```coffee static "/foo", FS("public") static "/" run ":8080" ``` ### yaptest: HTTP Test Framework This class framework has the file suffix `_ytest.gox`. Suppose we have a web server ([foo/get_p_#id.yap](https://github.com/goplus/yap/blob/main/ytest/demo/foo/get_p_%23id.yap)): ```go json { "id": ${id}, } ``` Then we create a yaptest file ([foo/foo_ytest.gox](https://github.com/goplus/yap/blob/main/ytest/demo/foo/bar_ytest.gox)): ```go mock "foo.com", new(AppV2) // name of any YAP v2 web server is `AppV2` id := "123" get "http://foo.com/p/${id}" ret 200 json { "id": id, } ``` The directive `mock` creates the web server by [mockhttp](https://pkg.go.dev/github.com/qiniu/x/mockhttp). Then we write test code directly. You can change the directive `mock` to `testServer` (see [foo/bar_ytest.gox](https://github.com/goplus/yap/blob/main/ytest/demo/foo/bar_ytest.gox)), and keep everything else unchanged: ```go testServer "foo.com", new(AppV2) id := "123" get "http://foo.com/p/${id}" ret 200 json { "id": id, } ``` The directive `testServer` creates the web server by [net/http/httptest](https://pkg.go.dev/net/http/httptest#NewServer) and obtained a random port as the service address. Then it calls the directive [host](https://pkg.go.dev/github.com/goplus/yap/ytest#App.Host) to map the random service address to `foo.com`. This makes all other code no need to changed. For more details, see [yaptest - XGo HTTP Test Framework](https://github.com/goplus/yap/blob/main/ytest). ### spx: An XGo 2D Game Engine for STEM education This class framework has the file suffix `.spx`. It is the earliest class framework in the world. #### tutorial/01-Weather ![Screen Shot1](https://github.com/goplus/spx/blob/main/tutorial/01-Weather/1.jpg) ![Screen Shot2](https://github.com/goplus/spx/blob/main/tutorial/01-Weather/2.jpg) Through this example you can learn how to implement dialogues between multiple actors. Here are some codes in [Kai.spx](https://github.com/goplus/spx/blob/main/tutorial/01-Weather/Kai.spx): ```coffee onStart => { say "Where do you come from?", 2 broadcast "1" } onMsg "2", => { say "What's the climate like in your country?", 3 broadcast "3" } onMsg "4", => { say "Which seasons do you like best?", 3 broadcast "5" } ``` We call `onStart` and `onMsg` to listen events. `onStart` is called when the program is started. And `onMsg` is called when someone calls `broadcast` to broadcast a message. When the program starts, Kai says `Where do you come from?`, and then broadcasts the message `1`. Who will recieve this message? Let's see codes in [Jaime.spx](https://github.com/goplus/spx/blob/main/tutorial/01-Weather/Jaime.spx): ```coffee onMsg "1", => { say "I come from England.", 2 broadcast "2" } onMsg "3", => { say "It's mild, but it's not always pleasant.", 4 # ... broadcast "4" } ``` Yes, Jaime recieves the message `1` and says `I come from England.`. Then he broadcasts the message `2`. Kai recieves it and says `What's the climate like in your country?`. The following procedures are very similar. In this way you can implement dialogues between multiple actors. #### tutorial/02-Dragon ![Screen Shot1](https://github.com/goplus/spx/blob/main/tutorial/02-Dragon/1.jpg) Through this example you can learn how to define variables and show them on the stage. Here are all the codes of [Dragon](https://github.com/goplus/spx/blob/main/tutorial/02-Dragon/Dragon.spx): ```coffee var ( score int ) onStart => { score = 0 for { turn rand(-30, 30) step 5 if touching("Shark") { score++ play chomp, true step -100 } } } ``` We define a variable named `score` for `Dragon`. After the program starts, it moves randomly. And every time it touches `Shark`, it gains one score. How to show the `score` on the stage? You don't need write code, just add a `stageMonitor` object into [assets/index.json](https://github.com/goplus/spx/blob/main/tutorial/02-Dragon/assets/index.json): ```json { "zorder": [ { "type": "stageMonitor", "target": "Dragon", "val": "getVar:score", "color": 15629590, "label": "score", "mode": 1, "x": 5, "y": 5, "visible": true } ] } ``` #### tutorial/03-Clone ![Screen Shot1](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/1.png) Through this example you can learn: * Clone sprites and destory them. * Distinguish between sprite variables and shared variables that can access by all sprites. Here are some codes in [Calf.spx](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/Calf.spx): ```coffee var ( id int ) onClick => { clone } onCloned => { gid++ ... } ``` When we click the sprite `Calf`, it receives an `onClick` event. Then it calls `clone` to clone itself. And after cloning, the new `Calf` sprite will receive an `onCloned` event. In `onCloned` event, the new `Calf` sprite uses a variable named `gid`. It doesn't define in [Calf.spx](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/Calf.spx), but in [main.spx](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/main.spx). Here are all the codes of [main.spx](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/main.spx): ```coffee var ( Arrow Arrow Calf Calf gid int ) run "res", {Title: "Clone and Destory (by XGo)"} ``` All these three variables in [main.spx](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/main.spx) are shared by all sprites. `Arrow` and `Calf` are sprites that exist in this project. `gid` means `global id`. It is used to allocate id for all cloned `Calf` sprites. Let's back to [Calf.spx](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/Calf.spx) to see the full codes of `onCloned`: ```coffee onCloned => { gid++ id = gid step 50 say id, 0.5 } ``` It increases `gid` value and assigns it to sprite `id`. This makes all these `Calf` sprites have different `id`. Then the cloned `Calf` moves forward 50 steps and says `id` of itself. Why these `Calf` sprites need different `id`? Because we want destory one of them by its `id`. Here are all the codes in [Arrow.spx](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/Arrow.spx): ```coffee onClick => { broadcast "undo", true gid-- } ``` When we click `Arrow`, it broadcasts an "undo" message (NOTE: We pass the second parameter `true` to broadcast to indicate we wait all sprites to finish processing this message). All `Calf` sprites receive this message, but only the last cloned sprite finds its `id` is equal to `gid` then destroys itself. Here are the related codes in [Calf.spx](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/Calf.spx): ```coffee onMsg "undo", => { if id == gid { destroy } } ``` #### tutorial/04-Bullet ![Screen Shot1](https://github.com/goplus/spx/blob/main/tutorial/04-Bullet/1.jpg) Through this example you can learn: * How to keep a sprite following mouse position. * How to fire bullets. It's simple to keep a sprite following mouse position. Here are some related codes in [MyAircraft.spx](https://github.com/goplus/spx/blob/main/tutorial/04-Bullet/MyAircraft.spx): ```coffee onStart => { for { # ... setXYpos mouseX, mouseY } } ``` Yes, we just need to call `setXYpos mouseX, mouseY` to follow mouse position. But how to fire bullets? Let's see all codes of [MyAircraft.spx](https://github.com/goplus/spx/blob/main/tutorial/04-Bullet/MyAircraft.spx): ```coffee onStart => { for { wait 0.1 Bullet.clone setXYpos mouseX, mouseY } } ``` In this example, `MyAircraft` fires bullets every 0.1 seconds. It just calls `Bullet.clone` to create a new bullet. All the rest things are the responsibility of `Bullet`. Here are all the codes in [Bullet.spx](https://github.com/goplus/spx/blob/main/tutorial/04-Bullet/Bullet.spx): ```coffee onCloned => { setXYpos MyAircraft.xpos, MyAircraft.ypos+5 show for { wait 0.04 changeYpos 10 if touching(Edge) { destroy } } } ``` When a `Bullet` is cloned, it calls `setXYpos MyAircraft.xpos, MyAircraft.ypos+5` to follow `MyAircraft`'s position and shows itself (the default state of a `Bullet` is hidden). Then the `Bullet` moves forward every 0.04 seconds and this is why we see the `Bullet` is flying. At last, when the `Bullet` touches screen `Edge` or any enemy (in this example we don't have enemies), it destroys itself. These are all things about firing bullets. ================================================ FILE: doc/code-coverage.md ================================================ XGo Unit Test: Code Coverage ===== TODO ================================================ FILE: doc/contributing.md ================================================ # Contributing Guidelines Thank you for your interest in contributing to the XGo project! Your efforts help us build and improve this powerful language. Please be aware that participation in the XGo project requires adherence to our established [code of conduct](https://github.com/goplus/xgo/blob/main/CODE_OF_CONDUCT.md). Your involvement signifies your agreement to comply with the guidelines set forth within these terms. We appreciate your cooperation in fostering a respectful and inclusive environment for all contributors. To ensure a smooth contribution process, this guide outlines the steps and best practices for contributing to XGo. It's assumed that you have a foundational knowledge of XGo and are comfortable using Git and GitHub for version control and collaboration. ## Before contributing code The XGo project welcomes code contributions. However, to ensure coordination, please discuss any significant changes beforehand. It's recommended to signal your intent to contribute via the issue tracker, either by filing a new issue or claiming an existing one. The issue tracker is your go-to, whether you've got a contribution in mind or are looking for inspiration. Issues are organized to streamline the workflow. Except for very trivial changes, all contributions should relate to an existing issue. Feel free to open one and discuss your plans. This process allows validation of design, prevents duplicate efforts, ensures alignment with language and tool goals, and confirms design validity before coding. ## Making a code contribution Let's say you want to fix a typo in the `README.md` file of the `https://github.com/goplus/xgo.git` repository, changing "Github" to "GitHub", and you wish to merge this contribution into the `main` branch of the upstream repository. ### Step 1: Fork the upstream repository First, you need to fork the upstream repository to your own username (or an organization you own) by following [this guide](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo). Then you can start making your contributions. Let's say you've forked `https://github.com/goplus/xgo.git` to `https://github.com/YOUR_USERNAME/gop.git`. ### Step 2: Clone the forked repository Open your terminal, clone the repository you just forked, and change into the cloned directory: ``` git clone https://github.com/YOUR_USERNAME/gop.git cd gop ``` This directory will serve as the working directory in the following steps. ### Step 3: Create a new branch We recommend that every contribution should have its own branch, and this branch should be deleted after the contribution is merged into the target branch of the upstream repository. First, you need to sync your fork's `main` branch to keep it up-to-date with the upstream's `main` branch by following [this guide](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork). Then, you can create and switch to a branch for your contribution: ``` git checkout -b typo main ``` ### Step 4: Make the changes This step is where you'll spend most of your effort when contributing. It typically takes up the majority of the time needed to make a contribution. However, in our case, we just want to correct a typo in the `README.md` file, changing "Github" to "GitHub". So, go ahead and make that change. ### Step 5: Commit and push After making your changes, you need to commit them: ``` git add README.md git commit -s -m 'README.md: correct typo "Github" to "GitHub"' ``` The commit message here is very straightforward, but it's recommended to read the [Commit Messages](#commit-messages) section for crafting better commit messages. Once you've committed your changes, you also need to push your commit to your fork on GitHub: ``` git push --set-upstream origin typo ``` ### Step 6: Open a pull request Head to the upstream repository and open a pull request by following [this guide](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request). Your pull request should clearly describe the change you're proposing and why it's beneficial. Typically, the title and description of the pull request you open will come from the first commit message in the branch of your fork. You can modify them if necessary. ### Step 7: Engage with feedback Be open to feedback from upstream repository maintainers. They might request further changes or provide insights that could enhance your contribution. After making any further changes, simply repeat [Step 5](#step-5-commit-and-push). However, when committing, it's recommended to use the `--amend` option. This ensures that all your changes are contained within a single commit, maintaining a linear commit history. For example: ``` git add README.md git commit --amend ``` This isn't a strict requirement, though. You can create as many commits as you like. ### Step 8: Do post-merge cleanup As mentioned in [Step 3](#step-3-create-a-new-branch), we recommend dedicating a branch to each contribution. Once a contribution's pull request is [squash and merged](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-commits), the branch should be deleted: ``` git checkout main git branch -d typo git push origin -d typo ``` ## Commit messages Commit messages in XGo adhere to specific conventions outlined in this section. A well-crafted commit message looks like this: ``` docs: revise contribution guidelines for clarity and inclusivity As our project grows, we want to ensure that our community understands how to contribute effectively and feels welcomed to do so. This update to our contribution guidelines aims to clarify the process and encourage more developers to get involved. Key changes include: - Simplified the language to make the guidelines more accessible to non-native English speakers and newcomers to open source. - Included a step-by-step guide on setting up the development environment, submitting a pull request, and what to expect during the review process. - Added a new section on community standards and code of conduct to foster a respectful and inclusive community atmosphere. - Provided clear examples of desirable contributions, such as bug fixes, feature proposals, and documentation improvements. Fixes #123 Signed-off-by: John Doe ``` ### First line The first line of a commit message typically serves as a concise one-line summary, beginning with the primary component it impacts (e.g., a specific package). This summary should ideally be brief, with many Git interfaces favoring a limit of approximately 72 characters. While XGo is not strict on the length limit, it's a good guideline to follow. Think of the summary as finishing the sentence "This commit modifies XGo to _____." As such, it does not begin with a capital letter, form a complete sentence, or diverge from succinctly stating the commit's effect. After the summary, ensure to separate the main content (if any) with a blank line. ### Main content The rest of the commit message should elaborate, offering context and a clear explanation of the commit. Employ complete sentences and proper punctuation. Refrain from using HTML, Markdown, or any form of markup language. Include relevant information, like benchmark data, if the commit impacts performance. Adhere to a line width of approximately 72 characters to accommodate Git viewing tools, except when longer lines are necessary (such as for ASCII art, tables, or extended URLs). ### Referencing issues The special notation `Fixes #123` links the commit directly to issue 123 in the issue tracker. Upon merging this commit, the issue tracker will automatically mark the issue as resolved. For commits that partially address an issue, use `Updates #123`. This notation will not close the issue upon the commit's integration. To reference multiple issues within a single commit, list them by appending each issue number with a comma and a space, such as `Fixes #123, #456`. This indicates that the commit resolves both referenced issues. For cross-repository references, use the full `user/repo#number` syntax, such as `Fixes goplus/xgo#123`. ### Signed-off-by For contributions to be accepted, we mandate that a `Signed-off-by` line, in accordance with the [Developer Certificate of Origin](https://developercertificate.org/), be included at the conclusion of each commit message. ================================================ FILE: doc/docs.md ================================================ XGo Quick Start ====== XGo is the first AI-native programming language that integrates software engineering into a unified whole. ``` XGo := C * Go * Python * JavaScript + Scratch ``` Our vision is to **enable everyone to become a builder of the world**. #### Easy to learn * Simple and easy to understand * Smaller syntax set than Go and Python in best practices #### Ready for large projects * Integrate C/C++, Go, Python, and JavaScript into a unified ecosystem * Derived from Go and easy to build large projects from its good engineering foundation The XGo programming language is designed for engineering, STEM education, and data science. * **For engineering**: working in the simplest language that can be mastered by children. * **For STEM education**: studying an engineering language that can be used for work in the future. * **For data science**: communicating with engineers in the same language. ## How to install Note: Requires go1.19 or later ### on Windows ```sh winget install goplus.xgo ``` ### on Debian/Ubuntu ```sh sudo bash -c ' echo "deb [trusted=yes] https://pkgs.xgo.dev/apt/ /" > /etc/apt/sources.list.d/goplus.list' sudo apt update sudo apt install xgo ``` ### on RedHat/CentOS/Fedora ```sh sudo bash -c 'echo -e "[goplus]\nname=XGo Repo\nbaseurl=https://pkgs.xgo.dev/yum/\nenabled=1\ngpgcheck=0" > /etc/yum.repos.d/goplus.repo' sudo yum install xgo ``` ### on macOS/Linux (Homebrew) Install via [brew](https://brew.sh/) ```sh $ brew install xgo ``` ### from source code ```bash git clone https://github.com/goplus/xgo.git cd xgo # On mac/linux run: ./all.bash # On Windows run: all.bat ``` Actually, `all.bash` and `all.bat` will use `go run cmd/make.go` underneath. ## Running in XGo playground If you don't want install XGo, you can write your XGo programs in XGo playground. This is the fastest way to experience XGo. * XGo playground: https://play.xgo.dev/ And you can share your XGo code with your friends. Here is my `Hello world` program: * https://play.xgo.dev/?p=AAh_gQAKAZR. ## Table of Contents
* [Hello world](#hello-world) * [Running a project folder](#running-a-project-folder-with-several-files) * [Comments](#comments) * [Variables](#variables) * [XGo types](#xgo-types) * [Strings](#strings) * [Numbers](#numbers) * [Slices](#slices) * [Maps](#maps) * [Module imports](#module-imports) * [Statements & expressions](#statements--expressions) * [If..else](#ifelse) * [For loop](#for-loop) * [Error handling](#error-handling) * [Functions](#functions) * [Returning multiple values](#returning-multiple-values) * [Optional parameters](#optional-parameters) * [Variadic parameters](#variadic-parameters) * [Keyword arguments](#keyword-arguments) * [Higher order functions](#higher-order-functions) * [Lambda expressions](#lambda-expressions) * [Structs](#structs) * [Struct tags](#struct-tags) * [Custom iterators](#custom-iterators) * [Deduce struct type](#deduce-struct-type) * [Overload operators](#overload-operators) * [Auto property](#auto-property) * [Go/XGo hybrid programming](#goxgo-hybrid-programming) * [Run XGo in watch mode](#run-xgo-in-watch-mode) * [Calling C from XGo](#calling-c-from-xgo) * [Data processing](#data-processing) * [Rational numbers](#rational-numbers) * [List comprehension](#list-comprehension) * [Select data from a collection](#select-data-from-a-collection) * [Check if data exists in a collection](#check-if-data-exists-in-a-collection) * [Domain-specific text literals](#domain-specific-text-literals) * [Unix shebang](#unix-shebang) * [Compatibility with Go](#compatibility-with-go)
## Hello World Different from the function call style of most languages, XGo recommends command style code: ```go println "Hello world" ``` Save this snippet into a file named `hello.xgo`. Now do: `xgo run hello.xgo`. Congratulations - you just wrote and executed your first XGo program! You can compile a program without execution with `xgo build hello.xgo`. See `xgo help` for all supported commands. [`println`](#println) is one of the few [built-in functions](#builtin-functions). It prints the value passed to it to standard output. To emphasize our preference for command style, we introduce `echo` as an alias for `println`: ```coffee echo "Hello world" ``` See https://tutorial.xgo.dev/hello-world for more details.
⬆ back to toc
## Running a project folder with several files Suppose you have a folder with several .xgo files in it, and you want to compile them all into one program. Just do: `xgo run .`. Passing parameters also works, so you can do: `xgo run . --yourparams some_other_stuff`. Your program can then use the CLI parameters like this: ```go import "os" echo os.Args ```
⬆ back to toc
## Comments ```go # This is a single line comment. // This is a single line comment. /* This is a multiline comment. */ ```
⬆ back to toc
## Variables ```go name := "Bob" age := 20 largeNumber := int128(1 << 65) echo name, age echo largeNumber ``` Variables are declared and initialized with `:=`. The variable's type is inferred from the value on the right hand side. To choose a different type, use type conversion: the expression `T(v)` converts the value `v` to the type `T`.
⬆ back to toc
### Initialization vs. assignment Note the (important) difference between `:=` and `=`. `:=` is used for declaring and initializing, `=` is used for assigning. ```go failcompile age = 21 ``` This code will not compile, because the variable `age` is not declared. All variables need to be declared in XGo. ```go age := 21 ``` The values of multiple variables can be changed in one line. In this way, their values can be swapped without an intermediary variable. ```go a, b := 0, 1 a, b = b, a echo a, b // 1, 0 ```
⬆ back to toc
## XGo Types ### Primitive types ```go ignore bool int8 int16 int32 int int64 int128 uint8 uint16 uint32 uint uint64 uint128 uintptr // similar to C's size_t byte // alias for uint8 rune // alias for int32, represents a Unicode code point string float32 float64 complex64 complex128 bigint bigrat unsafe.Pointer // similar to C's void* any // alias for Go's interface{} ```
⬆ back to toc
### Strings ```go name := "Bob" echo name.len // 3 echo name[0] // 66 echo name[1:3] // ob echo name[:2] // Bo echo name[2:] // b // or using octal escape `\###` notation where `#` is an octal digit echo "\141a" // aa // Unicode can be specified directly as `\u####` where # is a hex digit // and will be converted internally to its UTF-8 representation echo "\u2605" // ★ ``` String values are immutable. You cannot mutate elements: ```go failcompile s := "hello 🌎" s[0] = `H` // not allowed ``` Note that indexing a string will produce a `byte`, not a `rune` nor another `string`. Strings can be easily converted to integers: ```go s := "12" a, err := s.int b := s.int! // will panic if s isn't a valid integer ```
⬆ back to toc
#### String operators ```go name := "Bob" bobby := name + "by" // + is used to concatenate strings echo bobby // Bobby s := "Hello " s += "world" echo s // Hello world ``` Most XGo operators must have values of the same type on both sides. You cannot concatenate an integer to a string: ```go failcompile age := 10 echo "age = " + age // not allowed ``` We have to either convert `age` to a `string`: ```go age := 10 echo "age = " + age.string ``` However, you can replace `age.string` to `"${age}"`: ```go age := 10 echo "age = ${age}" ``` Here is a more complex example of `${expr}`: ```go host := "example.com" page := 0 limit := 20 echo "https://${host}/items?page=${page+1}&limit=${limit}" // https://example.com/items?page=1&limit=20 echo "$$" // $ ```
⬆ back to toc
### Runes A `rune` represents a single Unicode character and is an alias for `int32`. ```go rocket := '🚀' echo rocket // 128640 echo string(rocket) // 🚀 ```
⬆ back to toc
### Numbers ```go a := 123 ``` This will assign the value of 123 to `a`. By default `a` will have the type `int`. You can also use hexadecimal, binary or octal notation for integer literals: ```go a := 0x7B b := 0b01111011 c := 0o173 ``` All of these will be assigned the same value, 123. They will all have type `int`, no matter what notation you used. XGo also supports writing numbers with `_` as separator: ```go num := 1_000_000 // same as 1000000 ``` If you want a different type of integer, you can use casting: ```go a := int64(123) b := uint8(12) c := int128(12345) ``` Assigning floating point numbers works the same way: ```go f1 := 1.0 f2 := float32(3.14) ``` If you do not specify the type explicitly, by default float literals will have the type of `float64`. Float literals can also be declared as a power of ten: ```go f0 := 42e1 // 420 f1 := 123e-2 // 1.23 f2 := 456e+2 // 45600 ``` XGo has built-in support for [rational numbers](#rational-numbers): ```go a := 1r << 200 // suffix `r` means `rational` b := bigint(1 << 200) ``` And you can cast bool to number types (this is NOT supported in Go): ```go echo int(true) // 1 echo float64(true) // 1 echo complex64(true) // (1+0i) ```
⬆ back to toc
### Slices A slice is a collection of data elements of the same type. A slice literal is a list of expressions surrounded by square brackets. An individual element can be accessed using an *index* expression. Indexes start from `0`: ```go nums := [1, 2, 3] echo nums // [1 2 3] echo nums.len // 3 echo nums[0] // 1 echo nums[1:3] // [2 3] echo nums[:2] // [1 2] echo nums[2:] // [3] nums[1] = 5 echo nums // [1 5 3] ``` Type of a slice literal is infered automatically. ```go a := [1, 2, 3] // []int b := [1, 2, 3.4] // []float64 c := ["Hi"] // []string d := ["Hi", 10] // []any d := [] // []any ``` And casting slice literals also works. ```go a := []float64([1, 2, 3]) // []float64 ``` #### Appending to slices XGo provides a convenient `<-` operator for appending elements to slices, which is more intuitive than Go's `append` function: ```go a := [1, 2, 3] a <- 4 // append single element a <- 5, 6, 7 // append multiple elements b := [8, 9] a <- b... // append another slice echo a // [1 2 3 4 5 6 7 8 9] ``` This is equivalent to Go's append operations: - `a <- v` is the same as `a = append(a, v)` - `a <- v1, v2, v3` is the same as `a = append(a, v1, v2, v3)` - `a <- b...` is the same as `a = append(a, b...)`
⬆ back to toc
### Maps A map literal is a list of expressions surrounded by curly braces. ```go a := {"Hello": 1, "xsw": 3} // map[string]int b := {"Hello": 1, "xsw": 3.4} // map[string]float64 c := {"Hello": 1, "xsw": "XGo"} // map[string]any e := {1: "one", 2: "two"} // map[int]string d := {} // map[string]any ``` Use `make` for empty maps or to **pre-allocate capacity** for better performance. ```go m := make(map[string]int) // Basic creation large := make(map[string]int, 100) // Pre-allocated for ~100 elements ``` Before manipulating maps, it is important to understand that XGo supports two notations for referencing keys: - **Bracket Notation** (`m["key"]`): The universal syntax. It works for all key types and allows using variables as keys. - **Field Access Notation** (`m.key`): A convenient shorthand for string-keyed maps when the key is a valid identifier (no spaces or special characters). **Field access is pure syntax sugar** - `m.field` and `m["field"]` behave identically in all contexts. Both notations are used for both **assigning** values and **retrieving** them. #### Adding and Updating Elements ```go a := {"a": 1, "b": 0} // Using bracket notation a["c"] = 100 // Using field notation a.d = 200 echo a // Output: map[a:1 b:0 c:100 d:200] // Works with maps created by make too m := make(map[string]int) m["x"] = 10 m.y = 20 echo m // Output: map[x:10 y:20] ``` #### Deleting Elements Use the `delete` function to remove elements from a map: ```go a := {"a": 1, "b": 0, "c": 100} delete(a, "b") echo a // Output: map[a:1 c:100] ``` #### Getting Map Length You can get the number of elements in a map using the `len` function: ```go a := {"a": 1, "b": 2, "c": 3} echo len(a) // Output: 3 ``` #### Accessing Elements ```go config := {"host": "localhost", "port": 8080} echo config.host // Output: localhost echo config.port // Output: 8080 // Equivalent to: echo config["host"] echo config["port"] ``` ##### Working with `any` Type Either notation also works with variables of type `any`, automatically treating them as `map[string]any`: ```go var response any = {"status": "ok", "code": 200} echo response.status // Output: ok echo response.code // Output: 200 ``` ##### Safe Access with Comma-ok When accessing uncertain data (such as from JSON or external APIs), use the comma-ok form to safely check if a path exists. The comma-ok form returns two values: - The value itself (or zero value if path doesn't exist) - A boolean indicating whether the access succeeded With comma-ok, accessing non-existent paths **never panics** - it simply returns `false`: ```go var data any = fetchFromAPI() // Without comma-ok - may panic if structure is wrong // name := data.user.profile.name.(string) // With comma-ok - safe, never panics name, ok := data.user.profile.name.(string) if ok { // ... } ```
⬆ back to toc
## Module imports For information about creating a module, see [Modules](#modules). Modules can be imported using the `import` keyword: ```go import "strings" x := strings.NewReplacer("?", "!").Replace("Hello, world???") echo x // Hello, world!!! ```
⬆ back to toc
### Module import aliasing Any imported module name can be aliased: ```go import strop "strings" x := strop.NewReplacer("?", "!").Replace("Hello, world???") echo x // Hello, world!!! ```
⬆ back to toc
## Statements & expressions ### If..else In XGo, `if` statements are pretty straightforward and similar to most other languages. Unlike other C-like languages, there are no parentheses surrounding the condition and the braces are always required. ```go a := 10 b := 20 if a < b { echo "a < b" } else if a > b { echo "a > b" } else { echo "a == b" } ```
⬆ back to toc
### For loop XGo has only one looping keyword: `for`, with several forms. #### `for..in` This is the most common form. You can use it with a slice, map, numeric range or custom iterators. For information about creating a custom iterators, see [Custom iterators](#custom-iterators). ##### Slice `for` The `for value in arr` form is used for going through elements of a slice. ```go numbers := [1, 3, 5, 7, 11, 13, 17] sum := 0 for x in numbers { sum += x } echo sum // 57 ``` If an index is required, an alternative form `for index, value in arr` can be used. ```go names := ["Sam", "Peter"] for i, name in names { echo i, name // 0 Sam // 1 Peter } ```
⬆ back to toc
##### Map `for` ```go m := {"one": 1, "two": 2} for key, val in m { echo key, val // one 1 // two 2 } for key, _ in m { echo key // one // two } for val in m { echo val // 1 // 2 } ```
⬆ back to toc
##### Range `for` You can use `range expression` (`start:end:step`) in for loop. ```go for i in :5 { echo i // 0 // 1 // 2 // 3 // 4 } for i in 1:5 { echo i // 1 // 2 // 3 // 4 } for i in 1:5:2 { echo i // 1 // 3 } ```
⬆ back to toc
##### `for`/`in`/`if` All loops of `for`/`in` form can have an optional `if` condition. ```go numbers := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] for num in numbers if num%3 == 0 { echo num // 0 // 3 // 6 // 9 } for num in :10 if num%3 == 0 { echo num // 0 // 3 // 6 // 9 } ```
⬆ back to toc
#### Condition `for` ```go sum := 0 i := 1 for i <= 100 { sum += i i++ } echo sum // 5050 ``` This form of the loop is similar to `while` loops in other languages. The loop will stop iterating once the boolean condition evaluates to false. Again, there are no parentheses surrounding the condition, and the braces are always required.
⬆ back to toc
#### C `for` ```go for i := 0; i < 10; i += 2 { // Don't print 6 if i == 6 { continue } echo i // 0 // 2 // 4 // 8 } ``` Finally, there's the traditional C style `for` loop. It's safer than the `while` form because with the latter it's easy to forget to update the counter and get stuck in an infinite loop.
⬆ back to toc
#### Bare `for` ```go for { // ... } ``` The condition can be omitted, resulting in an infinite loop. You can use `break` or `return` to end the loop.
⬆ back to toc
### Error handling We reinvent the error handling specification in XGo. We call them `ErrWrap expressions`: ```go expr! // panic if err expr? // return if err expr?:defval // use defval if err ``` How to use them? Here is an example: ```go import ( "strconv" ) func add(x, y string) (int, error) { return strconv.Atoi(x)? + strconv.Atoi(y)?, nil } func addSafe(x, y string) int { return strconv.Atoi(x)?:0 + strconv.Atoi(y)?:0 } echo `add("100", "23"):`, add("100", "23")! sum, err := add("10", "abc") echo `add("10", "abc"):`, sum, err echo `addSafe("10", "abc"):`, addSafe("10", "abc") ``` The output of this example is: ``` add("100", "23"): 123 add("10", "abc"): 0 strconv.Atoi: parsing "abc": invalid syntax ===> errors stack: main.add("10", "abc") /Users/xsw/tutorial/15-ErrWrap/err_wrap.xgo:6 strconv.Atoi(y)? addSafe("10", "abc"): 10 ``` Compared to corresponding Go code, It is clear and more readable. And the most interesting thing is, the return error contains the full error stack. When we got an error, it is very easy to position what the root cause is. How these `ErrWrap expressions` work? See [Error Handling](https://github.com/goplus/xgo/wiki/Error-Handling) for more information.
⬆ back to toc
## Functions ```go func add(x int, y int) int { return x + y } echo add(2, 3) // 5 ```
⬆ back to toc
### Returning multiple values ```go func foo() (int, int) { return 2, 3 } a, b := foo() echo a // 2 echo b // 3 c, _ := foo() // ignore values using `_` ```
⬆ back to toc
### Optional parameters XGo supports optional parameters using the `T?` syntax. Optional parameters must have zero values as their defaults. ```go func greet(name string, count int?) { if count == 0 { count = 1 } for i := 0; i < count; i++ { echo "Hello,", name } } greet "Alice", 3 // prints "Hello, Alice" three times greet "Bob" // prints "Hello, Bob" once (default behavior) ``` Optional parameters are denoted by adding `?` after the parameter type. The default value is always the zero value of that type (e.g., `0` for integers, `""` for strings, `false` for booleans). ```go func connect(host string, port int?, secure bool?) { if port == 0 { port = 80 } echo "Connecting to", host, "on port", port, "secure:", secure } connect "example.com", 443, true // Connecting to example.com on port 443 secure: true connect "example.com" // Connecting to example.com on port 80 secure: false ```
⬆ back to toc
### Variadic parameters ```go func sum(a ...int) int { total := 0 for x in a { total += x } return total } echo sum(2, 3, 5) // 10 ``` Output parameters can have names. ```go func sum(a ...int) (total int) { for x in a { total += x } return // don't need return values if they are assigned } echo sum(2, 3, 5) // 10 ```
⬆ back to toc
### Keyword arguments XGo supports Python-like keyword arguments (kwargs) syntax for improved code readability. When calling functions with many parameters, you can use `key=value` syntax to make your code more expressive and command-line-style. #### Using kwargs with maps ```go func process(opts map[string]any?, args ...any) { if name, ok := opts["name"]; ok { echo "Name:", name } if age, ok := opts["age"]; ok { echo "Age:", age } echo "Args:", args } process name = "Ken", age = 17 // keyword parameters only process "extra", 1, name = "Ken", age = 17 // variadic parameters first, then keyword parameters process // all parameters optional ``` #### Using kwargs with structs You can also use structs or struct pointers for keyword parameters, which provides type safety: ```go type Config struct { Timeout int MaxRetries int Debug bool } func run(cfg *Config?) { timeout := 30 maxRetries := 3 debug := false if cfg != nil { if cfg.Timeout > 0 { timeout = cfg.Timeout } if cfg.MaxRetries > 0 { maxRetries = cfg.MaxRetries } debug = cfg.Debug } echo "Timeout:", timeout, "MaxRetries:", maxRetries, "Debug:", debug } run timeout = 60, maxRetries = 5 // lowercase field names work run Timeout = 10, Debug = true // uppercase field names work too run // uses default values ``` **Key rules:** - The keyword parameter must be an optional parameter. - The keyword parameter must be the last parameter (without variadic) or second-to-last (with variadic). - When calling a function, keyword arguments must be placed after all normal parameters (including variadic parameters). This might seem inconsistent with the order of keyword and variadic parameters in a function declaration, but that's the rule.
⬆ back to toc
### Higher order functions Functions can also be parameters. ```go func square(x float64) float64 { return x*x } func abs(x float64) float64 { if x < 0 { return -x } return x } func transform(a []float64, f func(float64) float64) []float64 { return [f(x) for x in a] } y := transform([1, 2, 3], square) echo y // [1 4 9] z := transform([-3, 1, -5], abs) echo z // [3 1 5] ```
⬆ back to toc
### Lambda expressions You also can use `lambda expression` to define a anonymous function. ```go func transform(a []float64, f func(float64) float64) []float64 { return [f(x) for x in a] } y := transform([1, 2, 3], x => x*x) echo y // [1 4 9] z := transform([-3, 1, -5], x => { if x < 0 { return -x } return x }) echo z // [3 1 5] ```
⬆ back to toc
## Structs ### Struct tags Go does not provide a way to add reflection information to a struct type. XGo uses Go's built-in struct field tags to implement struct type tags. For example: ```go type Start struct { _ "Start recording meeting minutes" } ``` It is equivalent to ```go type Start struct { _ struct{} `_:"Start recording meeting minutes"` } ``` ### Custom iterators #### For range of UDT ```go type Foo struct { } // Gop_Enum(proc func(val ValType)) or: // Gop_Enum(proc func(key KeyType, val ValType)) func (p *Foo) Gop_Enum(proc func(key int, val string)) { // ... } foo := &Foo{} for k, v := range foo { echo k, v } for k, v in foo { echo k, v } echo {v: k for k, v in foo} ``` **Note: you can't use break/continue or return statements in for range of udt.Gop_Enum(callback).**
⬆ back to toc
#### For range of UDT2 ```go type FooIter struct { } // (Iterator) Next() (val ValType, ok bool) or: // (Iterator) Next() (key KeyType, val ValType, ok bool) func (p *FooIter) Next() (key int, val string, ok bool) { // ... } type Foo struct { } // Gop_Enum() Iterator func (p *Foo) Gop_Enum() *FooIter { // ... } foo := &Foo{} for k, v := range foo { echo k, v } for k, v in foo { echo k, v } echo {v: k for k, v in foo} ```
⬆ back to toc
### Deduce struct type ```go type Config struct { Dir string Level int } func foo(conf *Config) { // ... } foo {Dir: "/foo/bar", Level: 1} ``` Here `foo {Dir: "/foo/bar", Level: 1}` is equivalent to `foo(&Config{Dir: "/foo/bar", Level: 1})`. However, you can't replace `foo(&Config{"/foo/bar", 1})` with `foo {"/foo/bar", 1}`, because it is confusing to consider `{"/foo/bar", 1}` as a struct literal. You also can omit struct types in a return statement. For example: ```go type Result struct { Text string } func foo() *Result { return {Text: "Hi, XGo"} // return &Result{Text: "Hi, XGo"} } ```
⬆ back to toc
### Overload operators ```go import "math/big" type MyBigInt struct { *big.Int } func Int(v *big.Int) MyBigInt { return MyBigInt{v} } func (a MyBigInt) + (b MyBigInt) MyBigInt { // binary operator return MyBigInt{new(big.Int).Add(a.Int, b.Int)} } func (a MyBigInt) += (b MyBigInt) { a.Int.Add(a.Int, b.Int) } func -(a MyBigInt) MyBigInt { // unary operator return MyBigInt{new(big.Int).Neg(a.Int)} } a := Int(1r) a += Int(2r) echo a + Int(3r) echo -a ```
⬆ back to toc
### Auto property Let's see an example written in XGo: ```go import "xgo/ast/goptest" doc := goptest.New(`... XGo code ...`)! echo doc.Any().FuncDecl().Name() ``` In many languages, there is a concept named `property` who has `get` and `set` methods. Suppose we have `get property`, the above example will be: ```go import "xgo/ast/goptest" doc := goptest.New(`... XGo code ...`)! echo doc.any.funcDecl.name ``` In XGo, we introduce a concept named `auto property`. It is a `get property`, but is implemented automatically. If we have a method named `Bar()`, then we will have a `get property` named `bar` at the same time.
⬆ back to toc
## Go/XGo hybrid programming This is an example to show how to mix Go/XGo code in the same package. In this example, we have a Go source file named `a.go`: ```go package main import "fmt" func p(a interface{}) { sayMix() fmt.Println("Hello,", a) } ``` And we have an XGo source file named `b.xgo`: ```go func sayMix() { echo "Mix Go and XGo" } p "world" ``` You can see that Go calls an XGo function named `sayMix`, and XGo calls a Go function named `p`. As you are used to in Go programming, this kind of circular reference is allowed. Run `xgo run .` to see the output of this example: ``` Mix Go and XGo Hello, world ```
⬆ back to toc
### Run XGo in watch mode The `xgo` command can run in watch mode so that everytime an XGo file is changed it is transpiled to a Go file: ``` xgo watch [-gentest] [dir] ``` By default `xgo watch` does not convert test files (normally ending with `_test.xgo`). You can specify `-gentest` flag to force converting all XGo files.
⬆ back to toc
## Calling C from XGo Here is [an example to show how XGo interacts with C](https://github.com/goplus/xgo/tree/main/demo/_llgo/hellollgo). ```go import "c" c.printf c"Hello, llgo!\n" c.fprintf c.Stderr, c"Hi, %6.1f\n", 3.14 ``` Here `import "c"` is used to import libc. In this example we call two C standard functions `printf` and `fprintf`, passing a C variable `stderr` and two C strings in the form of `c"xxx"` (an XGo syntax to represent C-style strings). To run this demo, you need to set the `XGO_GOCMD` environment variable first. ```sh export XGO_GOCMD=llgo # default is `go` ``` Then execute `xgo run .` to see the output of this example: ``` Hello, llgo! Hi, 3.1 ```
⬆ back to toc
## Data processing ### Rational numbers We introduce rational numbers as primitive XGo types. We use suffix `r` to denote rational literals. For example, `1r << 200` means a big int whose value is equal to 2200. ```go a := 1r << 200 b := bigint(1 << 200) ``` By default, `1r` will have the type of `bigint`. And `4/5r` means the rational constant `4/5`. It will have the type of `bigrat`. ```go a := 4/5r b := a - 1/3r + 3 * 1/2r echo a, b // 4/5 59/30 ``` Casting rational numbers works like other [primitive types](#primitive-types): ```go a := 1r b := bigrat(1r) c := bigrat(1) echo a/3 // 0 echo b/3 // 1/3 echo c/3 // 1/3 ```
⬆ back to toc
### List comprehension ```go a := [x*x for x in [1, 3, 5, 7, 11]] b := [x*x for x in [1, 3, 5, 7, 11] if x > 3] c := [i+v for i, v in [1, 3, 5, 7, 11] if i%2 == 1] arr := [1, 2, 3, 4, 5, 6] d := [[a, b] for a in arr if a < b for b in arr if b > 2] x := {x: i for i, x in [1, 3, 5, 7, 11]} y := {x: i for i, x in [1, 3, 5, 7, 11] if i%2 == 1} z := {v: k for k, v in {1: "Hello", 3: "Hi", 5: "xsw", 7: "XGo"} if k > 3} ```
⬆ back to toc
### Select data from a collection ```go type student struct { name string score int } students := [student{"Ken", 90}, student{"Jason", 80}, student{"Lily", 85}] unknownScore, ok := {x.score for x in students if x.name == "Unknown"} jasonScore := {x.score for x in students if x.name == "Jason"} echo unknownScore, ok // 0 false echo jasonScore // 80 ```
⬆ back to toc
### Check if data exists in a collection ```go type student struct { name string score int } students := [student{"Ken", 90}, student{"Jason", 80}, student{"Lily", 85}] hasJason := {for x in students if x.name == "Jason"} // is any student named Jason? hasFailed := {for x in students if x.score < 60} // is any student failed? ```
⬆ back to toc
## Domain-Specific Text Literals Domain-specific text literals allow you to write inline code in specialized formats—such as JSON, XML, regular expressions, or custom DSLs—without sacrificing the benefits of compile-time checking and editor support. **Basic syntax:** ```go result := domainTag`content` ``` **With parameters:** ```go result := domainTag`> param1, param2 content ` ``` The `!` suffix forces error handling, causing a panic if parsing fails—useful for literals you expect to always be valid. ## Built-in Formats XGo currently supports several domain text literals natively: ### Text Processing Language (tpl) A grammar-based alternative to regular expressions that emphasizes clarity and composability. Ideal for defining parsers and text processors. ```go grammar := tpl` expr = term % ("+" | "-") term = INT % ("*" | "/") `! result := grammar.parseExpr("10+5*2", nil) echo result ``` Learn more in the [TPL documentation](../tpl/README.md). ### JSON Parse and validate JSON structures inline: ```go config := json`{ "server": "localhost", "port": 8080, "features": ["auth", "logging"] }`! echo config.port ``` ### XML Work with XML documents directly: ```go doc := xml` localhost 5432 `! ``` ### CSV Define tabular data inline: ```go data := csv` name,age,city Alice,30,NYC Bob,25,SF `! ``` ### HTML Embed HTML with proper parsing (requires `golang.org/x/net/html`): ```go import "golang.org/x/net/html" page := html`

Welcome

Domain-specific literals in action

`! ``` ### Regular Expressions Define regex patterns with improved readability. XGo supports both standard and POSIX regex: ```go pattern := regexp`^[a-z]+\[[0-9]+\]$`! if pattern.matchString("item[42]") { echo "Match found" } // POSIX variant posixPattern := regexposix`[[:alpha:]]+`! ``` ## Implementation Details Domain text literals compile to function calls to the corresponding package's `New()` function. For example: ```go json`{"key": "value"}` // Compiles to: json.New(`{"key": "value"}`) ``` This design keeps the feature simple while allowing seamless integration with existing Go packages. The `domainTag` represents a package that must have a global `func New(string)` function with any return type.
⬆ back to toc
## Unix shebang You can use XGo programs as shell scripts now. For example: ```go #!/usr/bin/env -S xgo run echo "Hello, XGo" echo 1r << 129 echo 1/3r + 2/7r*2 arr := [1, 3, 5, 7, 11, 13, 17, 19] echo arr echo [x*x for x in arr, x > 3] m := {"Hi": 1, "XGo": 2} echo m echo {v: k for k, v in m} echo [k for k, _ in m] echo [v for v in m] ```
⬆ back to toc
## Compatibility with Go All Go features will be supported (including partially support `cgo`, see [below](#bytecode-vs-go-code)). **All Go packages (even these packages use `cgo`) can be imported by XGo.** ```coffee import ( "fmt" "strings" ) x := strings.NewReplacer("?", "!").Replace("hello, world???") fmt.Println "x:", x ``` **And all XGo packages can also be imported in Go programs. What you need to do is just using `xgo` command instead of `go`.** First, let's make a directory named `14-Using-goplus-in-Go`. Then write an XGo package named [foo](https://github.com/goplus/tutorial/tree/main/14-Using-goplus-in-Go/foo) in it: ```go package foo func ReverseMap(m map[string]int) map[int]string { return {v: k for k, v in m} } ``` Then use it in a Go package [14-Using-goplus-in-Go/gomain](https://github.com/goplus/tutorial/tree/main/14-Using-goplus-in-Go/gomain): ```go package main import ( "fmt" "github.com/goplus/tutorial/14-Using-goplus-in-Go/foo" ) func main() { rmap := foo.ReverseMap(map[string]int{"Hi": 1, "Hello": 2}) fmt.Println(rmap) } ``` How to build this example? You can use: ```bash xgo install -v ./... ``` Go [github.com/goplus/tutorial/14-Using-goplus-in-Go](https://github.com/goplus/tutorial/tree/main/14-Using-goplus-in-Go) to get the source code.
⬆ back to toc
## Bytecode vs. Go code XGo supports bytecode backend and Go code generation. When we use `xgo` command, it generates Go code to covert XGo package into Go packages. ```bash xgo run # Run an XGo program xgo install # Build XGo files and install target to GOBIN xgo build # Build XGo files xgo test # Test XGo packages xgo fmt # Format XGo packages xgo clean # Clean all XGo auto generated files xgo go # Convert XGo packages into Go packages ``` When we use [`ixgo`](https://github.com/goplus/ixgo) command, it interprets and executes the program. ```bash ixgo # Run an XGo program ``` In bytecode mode, XGo doesn't support `cgo`. However, in Go-code-generation mode, XGo fully supports `cgo`. ================================================ FILE: doc/domian-text-lit.md ================================================ Domain Text Literals ===== XGo's domain-specific text literals provide a powerful way to embed specialized languages directly into your code with full syntax highlighting and type safety. This feature bridges the gap between general-purpose programming and domain-specific needs, making your code more expressive and maintainable. ## Overview Domain-specific text literals allow you to write inline code in specialized formats—such as JSON, XML, regular expressions, or custom DSLs—without sacrificing the benefits of compile-time checking and editor support. **Basic syntax:** ```go result := domainTag`content` ``` **With parameters:** ```go result := domainTag`> param1, param2 content ` ``` The `!` suffix forces error handling, causing a panic if parsing fails—useful for literals you expect to always be valid. ## Design Inspiration This syntax is inspired by **Markdown's code blocks**. Just as Markdown uses triple backticks with a language identifier (` ```json`) to denote code blocks in a specific language, XGo's domain-specific literals use a similar pattern—a tag followed by backticks—to embed domain-specific content directly in your code. This familiar syntax makes the feature intuitive for developers already comfortable with Markdown while bringing the same clarity and language-specific semantics to your programming workflow. ## Core Benefits - **Type Safety**: Catch errors at compile time rather than runtime - **Syntax Highlighting**: Full editor support for embedded languages - **Readability**: Keep domain-specific code inline where it's used - **Maintainability**: Easier to update and refactor than string concatenation - **Tooling Support**: Enables semantic understanding by XGo tools like formatters and IDEs ## Built-in Formats XGo currently supports several domain text literals natively: ### Text Processing Language (tpl) A grammar-based alternative to regular expressions that emphasizes clarity and composability. Ideal for defining parsers and text processors. ```go grammar := tpl` expr = term % ("+" | "-") term = INT % ("*" | "/") `! result := grammar.parseExpr("10+5*2", nil) echo result ``` Learn more in the [TPL documentation](../tpl/README.md). ### JSON Parse and validate JSON structures inline: ```go config := json`{ "server": "localhost", "port": 8080, "features": ["auth", "logging"] }`! echo config.port ``` ### XML Work with XML documents directly: ```go doc := xml` localhost 5432 `! ``` ### CSV Define tabular data inline: ```go data := csv` name,age,city Alice,30,NYC Bob,25,SF `! ``` ### HTML Embed HTML with proper parsing (requires `golang.org/x/net/html`): ```go import "golang.org/x/net/html" page := html`

Welcome

Domain-specific literals in action

`! ``` ### Regular Expressions Define regex patterns with improved readability. XGo supports both standard and POSIX regex: ```go pattern := regexp`^[a-z]+\[[0-9]+\]$`! if pattern.matchString("item[42]") { echo "Match found" } // POSIX variant posixPattern := regexposix`[[:alpha:]]+`! ``` ## Implementation Details Domain text literals compile to function calls to the corresponding package's `New()` function. For example: ```go json`{"key": "value"}` // Compiles to: json.New(`{"key": "value"}`) ``` This design keeps the feature simple while allowing seamless integration with existing Go packages. The `domainTag` represents a package that must have a global `func New(string)` function with any return type. ## Creating Custom Formats Extend XGo with your own domain-specific languages by implementing a package with a global `New(string)` function: ```go // Package sql provides SQL query literals package sql type Query struct { text string } func New(query string) (*Query, error) { // Validate and parse SQL if err := validateSQL(query); err != nil { return nil, err } return &Query{text: query}, nil } ``` **Usage:** ```go import "myproject/sql" query := sql` SELECT id, name, email FROM users WHERE active = true `! ``` ## Beyond Syntactic Sugar Domain text literals offer more than just convenient syntax. They enable XGo tooling to understand the semantics of these embedded texts rather than treating them as ordinary strings. This semantic understanding enables: - **Code formatters** like `xgo fmt` to format both XGo code and supported domain texts simultaneously - **IDE plugins** to provide syntax highlighting and advanced features for recognized domain texts - **Static analysis tools** to validate domain-specific content at build time - **Documentation generators** to extract and document embedded domain content ## Best Practices 1. **Use the `!` suffix for static literals** that should always be valid—this catches errors early 2. **Handle errors explicitly for dynamic content** that might fail validation 3. **Keep literals focused** on their domain—avoid mixing concerns 4. **Leverage syntax highlighting** by configuring your editor for the embedded languages 5. **Document custom formats** clearly to help other developers understand their usage ## Error Handling Without the `!` suffix, domain literals return an error that you can handle: ```go query, err := sql`SELECT * FROM ${table}` if err != nil { return fmt.Errorf("invalid query: %w", err) } ``` With the `!` suffix, invalid literals cause a panic: ```go // This panics if the JSON is malformed data := json`{"invalid": }`! ``` --- ## Historical Background The journey of domain text literals in XGo began with a [community proposal in early 2024](https://github.com/goplus/xgo/issues/1770) suggesting adding JSX syntax support to XGo. While JSX has gained widespread adoption in frontend development, particularly in React-based applications, the immediate benefits of building JSX syntax directly into XGo weren't immediately clear, causing the proposal to be temporarily shelved. The turning point came when XGo needed to support [TPL (Text Processing Language)](../tpl/README.md) syntax for the [XGo Mini Spec](spec-mini.md) project. This necessity prompted a reconsideration of how XGo should handle domain-specific notations more broadly. ### The Philosophy Behind Domain Text Literals A common understanding in programming language design suggests that **Domain-Specific Languages (DSLs)** often struggle to compete with general-purpose languages. However, this perspective overlooks the fact that numerous domain languages exist and thrive in specialized contexts: - **Interface description**: HTML, JSX - **Configuration and data representation**: JSON, YAML, CSV - **Text syntax representation**: EBNF-like grammar (including TPL syntax), regular expressions - **Document formats**: Markdown, DOCX, HTML What distinguishes these domain languages is that they aren't Turing-complete. They lack the full capabilities of general-purpose languages, such as I/O operations, function definitions, and comprehensive flow control structures. Rather than competing with general-purpose languages, these domain languages typically complement them. Most mainstream programming languages either officially support or have community-built libraries to interact with these domain languages. This complementary relationship led to the term "**Domain Text Literals**" rather than "**Domain-Specific Languages**", emphasizing their role as specialized text formats that can be embedded within general-purpose code. ### Syntax Evolution After considerable deliberation on how XGo should support domain text literals, inspiration came from Markdown's code block syntax. Initially, there was consideration to make XGo's domain text syntax identical to Markdown's. However, this would have prevented XGo code from being embedded as a domain text within Markdown documents, potentially reducing interoperability between XGo and Markdown. After careful consideration, the current syntax was chosen to ensure optimal compatibility while maintaining the familiar, intuitive pattern that developers already know from Markdown. --- Domain-specific text literals make XGo uniquely suited for projects that need to work with multiple specialized formats. By treating domain-specific languages as first-class citizens, XGo helps you write cleaner, safer, and more maintainable code. ================================================ FILE: doc/fncall.md ================================================ # Commands and Function Calls In XGo, there's a fundamental unity beneath seemingly different syntactic forms: **commands, function calls, and operators are all essentially function invocations**. This unified model makes the language both intuitive for beginners and consistent for experienced programmers. ## The Unified Function Model Consider these three ways of invoking functions: ```go echo "Hello" // Command style echo("Hello") // Function call style 3 + 4 // Operator style ``` While they look different, all three represent function invocations. The different syntaxes simply provide flexibility in how you express intent. ### Command Style: Natural and Intuitive Commands look like natural language instructions: ```go echo "Hello world" println "Temperature:", 25.5 time.sleep 2*time.Second ``` **Key characteristic:** Parentheses are optional. Arguments follow the command naturally, making code read like sentences. ### Function Call Style: Explicit and Familiar Function calls use traditional syntax with mandatory parentheses: ```go echo("Hello world") println("Temperature:", 25.5) time.sleep(2*time.Second) ``` **Key characteristic:** Explicit parentheses make nesting and composition clearer in complex expressions. ### Operators: Mathematical Notation for Functions Operators use familiar mathematical notation: ```go 3 + 4 // Addition x * y // Multiplication a == b // Equality comparison ``` While operators look like special symbols, they're actually function calls in disguise. The `+` operator calls an addition function, `*` calls a multiplication function, and so on. This syntax matches mathematical conventions, making numeric code natural to read and write. **Note:** XGo allows you to define your own operators (covered in advanced topics), reinforcing that operators are truly functions at their core. ## Categories of Functions Functions in XGo come from three sources, each accessed slightly differently: ### 1. Built-in Functions Built-in functions are always available without any imports. They're part of the language core. #### Input/Output Functions ```go echo "Hello", "World" // Output with spaces and newline print "Hello", "World" // Output without spaces, no newline ``` Both forms work identically: ```go echo "Result:", 42 // Command style echo("Result:", 42) // Function call style ``` **Difference between `echo` and `print`:** - `echo` adds spaces between arguments and ends with a newline - `print` concatenates arguments directly without spaces or newline ```go echo "A", "B", "C" // Output: A B C\n print "A", "B", "C" // Output: ABC ``` #### Error Handling ```go panic "Something went wrong!" panic("Fatal error: division by zero") ``` The `panic` function stops program execution immediately—use it for unrecoverable errors. #### Operators as Built-in Functions Arithmetic and comparison operators are also built-in functions: ```go sum := 3 + 4 // Addition operator product := 5 * 6 // Multiplication operator equal := (x == y) // Equality operator ``` The operator syntax is designed to match mathematical notation, but conceptually these are function invocations. This is why you can define custom operators in XGo—they're not special language primitives, just functions with infix notation. ### 2. Package Functions Functions from packages are accessed through import and qualified names. #### Importing Packages Place all imports at the beginning of your file: ```go import "math" import "time" ``` Or use the grouped form: ```go import ( "math" "time" ) ``` #### Using Package Functions Access package functions with dot notation: `packageName.functionName` ```go import "math" echo math.sqrt(16) // Square root: 4 echo math.pow(2, 3) // Power: 8 echo math.abs(-5) // Absolute value: 5 ``` **Lowercase calling convention:** XGo provides a convenient feature—exported functions (which start with uppercase letters in Go convention) can be called with lowercase names: ```go // In the math package, the actual function is Sqrt (uppercase) math.sqrt(16) // ✓ Lowercase call (recommended in XGo) math.Sqrt(16) // ✓ Original uppercase name (also works) // But you cannot call a lowercase function with uppercase // somePackage.DoSomething() // ✗ Won't work if function is actually doSomething ``` This feature is specifically designed to make code more readable while maintaining compatibility with Go's export rules. The convention is: - Exported functions start with uppercase (Go requirement) - You can call them with lowercase for convenience (XGo feature) - The reverse is not true—lowercase functions must be called with lowercase **Omitting parentheses:** For zero-parameter functions, parentheses are optional when using lowercase names: ```go import "time" echo time.now // Current time (no parentheses needed) echo time.now() // Same thing with explicit call echo time.Now() // Also works with uppercase ``` #### Common Package Examples **Math operations:** ```go import "math" math.sqrt(16) // 4 math.pow(2, 8) // 256 math.max(10, 20) // 20 math.min(10, 20) // 10 math.Pi // 3.141592653589793 (constant, not a function) ``` **Time operations:** ```go import "time" time.now // Current timestamp time.sleep 2*time.Second // Pause for 2 seconds ``` **Note on constants:** Packages also provide constants like `math.Pi` and `time.Second`. These are accessed the same way as functions but represent fixed values rather than executable code. ### 3. Methods: Functions Belonging to Objects Methods are functions that operate on specific objects. They're called using dot notation: `object.method()` Think of methods as actions an object can perform. A string can be converted to uppercase, a time can tell you what day of the week it is. #### String Methods Strings have built-in methods for common operations: ```go "Hello".len // 5 (length of string) "Hello".toUpper // "HELLO" "Hello".toLower // "hello" "Go".repeat(3) // "GoGoGo" "Hello".replaceAll("l", "L") // "HeLLo" ``` **Zero-parameter methods:** Like package functions, methods without parameters can omit parentheses: ```go "Hello".len // Parentheses optional "Hello".len() // Explicit call—same result ``` **Methods with parameters:** Require parentheses: ```go "Go".repeat(3) // Must use parentheses "Hello".replaceAll("l", "L") // Must use parentheses ``` #### Time Methods Time objects returned from `time.now` have methods to extract components: ```go import "time" now := time.now echo now.weekday // e.g., "Wednesday" echo now.year // e.g., 2025 echo now.month // e.g., "February" echo now.day // e.g., 15 echo now.hour // e.g., 14 (24-hour format, UTC) echo now.minute // e.g., 30 echo now.second // e.g., 45 ``` All these methods work without parentheses since they take no parameters. **Chaining method calls:** ```go import "time" echo time.now.weekday // Current day of week echo time.now.year // Current year ``` ## Understanding the Dot Notation The dot (`.`) connects an object to its method or a package to its function: ```go // Package.function math.sqrt(16) time.now // Object.method "Hello".toUpper time.now.weekday ``` Both follow the same pattern, making the language consistent and predictable. ## Practical Examples ### Combining Different Function Types ```go import "math" import "time" // Built-in + Package function echo "Square root of 25 is", math.sqrt(25) // Package function + Method echo "Today is", time.now.weekday // Operator + Built-in + Method result := 3 + 4 message := "Result: " + result.string echo message.toUpper ``` ### Command vs. Function Call Style Use command style for simple, top-level statements: ```go echo "Starting calculation..." result := math.sqrt(144) echo "Result:", result ``` Use function call style for nested expressions: ```go // Clear nesting with explicit parentheses echo math.sqrt(math.pow(3, 2) + math.pow(4, 2)) // Pythagorean theorem // String method in expression name := "alice" echo "Hello, " + name.toUpper() ``` ### Real-World Example ```go import "time" import "math" // Get current time details now := time.now echo "Current time:", now echo "Day:", now.weekday echo "Date:", now.year, "-", now.month, "-", now.day // Wait a bit time.sleep 2*time.Second // Do some calculations value := math.pow(2, 10) echo "2^10 =", value echo "Square root:", math.sqrt(value) // String manipulation message := "processing complete" echo message.toUpper() ``` ## Key Takeaways 1. **Everything is a function call** at the conceptual level—commands, function calls, and operators all invoke functions 2. **Operators use mathematical notation** but represent function calls underneath, allowing for custom operator definitions 3. **Built-in functions** like `echo`, `print`, and operators are always available 4. **Package functions** require imports and use `package.function` syntax 5. **Methods** are functions that belong to objects, using `object.method` syntax 6. **Parentheses are optional** for commands, and for zero-parameter functions and methods when using lowercase names 7. **Lowercase calling convention:** Uppercase-exported functions can be called with lowercase (e.g., `math.sqrt` for `math.Sqrt`), but not vice versa This unified model means once you understand one form, you understand them all. Whether you write `echo "Hello"` or `echo("Hello")`, whether you use `3 + 4` or call `time.now`, you're invoking functions—just with different syntactic styles suited to different situations. ================================================ FILE: doc/func-closure.md ================================================ # Functions and Closures ## Basic Function Definition Functions in XGo are defined with clear type specifications for parameters and return values: ```go func add(x int, y int) int { return x + y } echo add(2, 3) // 5 ``` ### Parameter List Rules When multiple consecutive parameters share the same type, you can combine them for more concise syntax: ```go // Verbose form: each parameter has its own type func add(x int, y int) int { return x + y } // Concise form: parameters of the same type can be grouped func add(x, y int) int { return x + y } // Mixed types require separate declarations func greet(firstName, lastName string, age int) { echo firstName, lastName, "is", age, "years old" } ``` The same grouping rule applies to return values: ```go // Multiple return values of the same type can be grouped func minMax(a, b int) (min, max int) { if a < b { return a, b } return b, a } // Mixed return types require separate declarations func divide(a, b float64) (result float64, err error) { if b == 0 { return 0, errors.New("division by zero") } return a / b, nil } ``` **Key points:** - Parameters or return values of the same type can be grouped: `x, y int` instead of `x int, y int` - Different types must be declared separately - This rule applies to both parameter lists and return value lists - Grouping improves readability without changing functionality ### Functions Without Return Values Functions don't always need to return values. When a function performs an action without producing a result, you can omit the return type: ```go func greet(name string) { echo "Hello,", name } greet("Alice") // Hello, Alice ``` You can also explicitly return early from such functions using a bare `return` statement: ```go func printPositive(x int) { if x <= 0 { return // exit early if condition not met } echo "Positive number:", x } printPositive(5) // Positive number: 5 printPositive(-3) // (prints nothing) ``` ### Multiple Return Values Functions can return multiple values simultaneously: ```go func foo() (int, int) { return 2, 3 } a, b := foo() echo a // 2 echo b // 3 c, _ := foo() // ignore values using `_` ``` ### Named Return Values Return values can be named to simplify return statements: ```go func sum(a ...int) (total int) { for x in a { total += x } return // no need to explicitly return named values that are already assigned } echo sum(2, 3, 5) // 10 ``` ## Optional Parameters XGo supports optional parameters using the `T?` syntax. Optional parameters default to their type's zero value: ```go func greet(name string, count int?) { if count == 0 { count = 1 } for i := 0; i < count; i++ { echo "Hello,", name } } greet "Alice", 3 // prints "Hello, Alice" three times greet "Bob" // prints "Hello, Bob" once (uses default value) ``` Optional parameters are denoted by adding `?` after the parameter type. The default value is always the zero value of that type (e.g., `0` for integers, `""` for strings, `false` for booleans). ```go func connect(host string, port int?, secure bool?) { if port == 0 { port = 80 } echo "Connecting to", host, "on port", port, "secure:", secure } connect "example.com", 443, true // Connecting to example.com on port 443 secure: true connect "example.com" // Connecting to example.com on port 80 secure: false ``` ## Variadic Parameters Variadic parameters allow functions to accept a variable number of arguments of the same type using the `...` syntax: ```go func sum(a ...int) int { total := 0 for x in a { total += x } return total } echo sum(2, 3, 5) // 10 echo sum(1, 2, 3, 4, 5) // 15 echo sum() // 0 ``` Inside the function, the variadic parameter behaves as a slice of the specified type. ### Parameter Positioning Rules **Important:** The variadic parameter must be the last parameter in the function signature, appearing after all regular parameters and optional parameters: ```go // ✓ Correct: variadic parameter is last func log(level string, verbose bool?, messages ...string) { // ... } // ✗ Wrong: variadic parameter must be last func log(messages ...string, level string) { // Error! // ... } // ✗ Wrong: variadic parameter must be after optional parameters func log(messages ...string, verbose bool?) { // Error! // ... } ``` ### Combining Regular and Variadic Parameters ```go func log(level string, messages ...string) { echo "[" + level + "]", strings.Join(messages, " ") } log("INFO", "Server", "started", "successfully") // Output: [INFO] Server started successfully ``` ### Passing Slices to Variadic Parameters Use the `...` suffix to pass an existing slice to a variadic parameter: ```go func max(nums ...int) int { if len(nums) == 0 { return 0 } maxVal := nums[0] for _, n in nums[1:] { if n > maxVal { maxVal = n } } return maxVal } numbers := []int{3, 7, 2, 9, 1} echo max(numbers...) // 9 echo max(5, 8, 3) // 8 ``` ### Practical Examples ```go // String formatting func format(template string, args ...any) string { result := template for i, arg in args { result = strings.Replace(result, "{${i}}", fmt.Sprint(arg), 1) } return result } echo format("Hello {0}, you have {1} messages", "Alice", 5) // Output: Hello Alice, you have 5 messages ``` ## Keyword Arguments XGo supports keyword arguments syntax for improved code readability and expressiveness. When calling functions with many parameters, you can use `key = value` syntax. ### Using Maps for Keyword Arguments ```go func process(opts map[string]any?, args ...any) { if name, ok := opts["name"]; ok { echo "Name:", name } if age, ok := opts["age"]; ok { echo "Age:", age } echo "Args:", args } process name = "Ken", age = 17 // keyword parameters only process "extra", 1, name = "Ken", age = 17 // variadic parameters first, then keyword parameters process // all parameters optional ``` **Best for:** Dynamic or extensible parameter sets, diverse parameter types, runtime parameter checking. ### Using Tuples for Keyword Arguments ```go type Config (timeout, maxRetries int, debug bool) func run(task int, cfg Config?) { if cfg.timeout == 0 { cfg.timeout = 30 } if cfg.maxRetries == 0 { cfg.maxRetries = 3 } echo "timeout:", cfg.timeout, "maxRetries:", cfg.maxRetries, "debug:", cfg.debug echo "task:", task } run 100, timeout = 60, maxRetries = 5 run 200 ``` **Best for:** Fixed parameter sets with known types, compile-time validation, optimal performance. **Recommended for most use cases.** **Note:** Tuple field names must match exactly as defined - no automatic case conversion is performed. ### Using Structs for Keyword Arguments Structs provide type safety and full runtime reflection support for keyword parameters: ```go type Config struct { Timeout int MaxRetries int Debug bool } func run(cfg *Config?) { timeout := 30 maxRetries := 3 debug := false if cfg != nil { if cfg.Timeout > 0 { timeout = cfg.Timeout } if cfg.MaxRetries > 0 { maxRetries = cfg.MaxRetries } debug = cfg.Debug } echo "Timeout:", timeout, "MaxRetries:", maxRetries, "Debug:", debug } run timeout = 60, maxRetries = 5 // lowercase field names work run Timeout = 10, Debug = true // uppercase field names work too run // uses default values ``` **Best for:** Go codebase compatibility, struct features (methods, embedding, tags), runtime reflection needs. ### Rules and Best Practices **Syntax Rules:** 1. **Parameter Position Requirements** - The keyword parameter must be an optional parameter (marked with `?`) - The keyword parameter must be the last parameter (if no variadic parameters), or second-to-last when variadic parameters are present 2. **Call Order Requirements** - When calling a function, keyword arguments must be placed after all normal parameters (including variadic parameters) ```go // ✓ Correct call order process "value", key1 = "a", key2 = "b" process "v1", "v2", key = "x" // ✗ Wrong call order process key = "x", "value" // keyword arguments must come last ``` **Type Selection:** - **Use Map** when you need dynamic parameters or runtime flexibility - **Use Tuple** for most cases - lightweight, compile-time validated, optimal performance (recommended) - **Use Struct** when you need Go compatibility or struct-specific features ## Higher-Order Functions Functions can be passed as parameters to other functions: ```go func square(x float64) float64 { return x*x } func abs(x float64) float64 { if x < 0 { return -x } return x } func transform(a []float64, f func(float64) float64) []float64 { return [f(x) for x in a] } y := transform([1, 2, 3], square) echo y // [1 4 9] z := transform([-3, 1, -5], abs) echo z // [3 1 5] ``` ## Lambda Expressions Lambda expressions provide a concise way to define anonymous functions inline using the `=>` operator. ### Basic Syntax ```go // Single parameter (no parentheses needed) x => x * x // Multiple parameters (parentheses required) (x, y) => x + y // No parameters (no parentheses needed, just start with =>) => someValue // Multi-line body x => { result := x * 2 return result } ``` ### Common Use Cases **Transformations:** ```go func transform(a []float64, f func(float64) float64) []float64 { return [f(x) for x in a] } // The lambda parameter type is inferred from transform's function parameter type // which expects func(float64) float64 y := transform([1, 2, 3], x => x*x) // [1 4 9] z := transform([-3, 1, -5], x => { if x < 0 { return -x } return x }) // [3 1 5] ``` **Combining values:** ```go func combine(a, b []int, f func(int, int) int) (result []int) { for i := 0; i < len(a) && i < len(b); i++ { result = append(result, f(a[i], b[i])) } return result } sums := combine([1, 2, 3], [4, 5, 6], (x, y) => x + y) // [5 7 9] ``` **Closures (capturing variables):** ```go func multiplier(factor int) func(int) int { return x => x * factor } func counter(start int) func() int { count := start return => { count++ return count } } double := multiplier(2) echo double(5) // 10 c := counter(0) echo c() // 1 echo c() // 2 ``` **Sorting and filtering:** ```go numbers := [1, 2, 3, 4, 5, 6] evens := filter(numbers, x => x % 2 == 0) // [2 4 6] sort.Slice(products, (i, j) => products[i].Price < products[j].Price) ``` **Event handling:** ```go // Event registration functions func OnStart(onStart func()) func OnMsg(msg string, onMsg func()) // Register event handlers with lambdas // With one argument (the lambda), no comma is needed. onStart => { echo "Game started!" initializeGame() } // With multiple arguments, use a comma to separate them. onMsg "game over", => { echo "Game over!" cleanup() } ``` **Note:** If a function with the lowercase name doesn't exist, XGo will automatically look for the capitalized version (e.g., if `onStart` is not defined, it tries `OnStart`). This allows for more flexible and natural function calling syntax. ### Type Inference Parameter and return types are automatically inferred from context. XGo lambdas do not support explicit type annotations: ```go // Types are inferred from the function signature transform([1, 2, 3], x => x * 2) // The lambda parameter type is inferred from transform's function parameter type // which expects func(float64) float64 ``` ### When to Use **Use lambdas for:** - One-off functions used inline - Simple transformations and filters - Callbacks and event handlers - Building processing pipelines **Use named functions for:** - Reusable logic - Complex operations needing documentation - Public APIs ## Summary XGo's function system combines powerful features: - Standard function definitions with multiple return values - Functions without return values for action-oriented operations - Optional parameters for flexible function calls - Comprehensive variadic parameters for variable-length argument lists - Keyword arguments with maps, tuples, or structs for improved readability - Higher-order functions for functional programming patterns - Elegant and expressive lambda expressions for inline function definitions - Closures for capturing and maintaining state These features work together to create a versatile and expressive function system that supports both traditional imperative programming and modern functional programming paradigms. ================================================ FILE: doc/goodbye-printf.md ================================================ Goodbye printf ===== For professional programmers, `printf` is a very familiar function, and it can be found in basically every language. However, `printf` is one of the most difficult functions for beginners to master. Unfortunately, formatting a piece of information and displaying it to the user is a very common operation, so one has to remember their usage. While finding its documentation over the Internet can somewhat ease the burden of using it every time, it's far from a pleasant experience. The most primitive way to format information is to use `string concat`: ```go age := 10 println "age = " + age.string ``` And the most classic way of formatting information is to use `printf`: ```go age := 10 printf "age = %d\n", age ``` Here `%d` means to format an integer value and `\n` means a newline. To simplify format information in most cases, XGo introduces `${expr}` expressions in string literals. For above example, you can replace `age.string` to `"${age}"`: ```go age := 10 println "age = ${age}" ``` Here is a more complex example of `${expr}`: ```go host := "foo.com" page := 0 limit := 20 println "https://${host}/items?page=${page+1}&limit=${limit}" // https://foo.com/items?page=1&limit=20 println "$$" // $ ``` This is a bit like how you feel at the `*nix` command line, right? To be more like it, we introduced a new builtin `echo` as an alias for `println`: ```go age := 10 echo "age = ${age}" ``` ================================================ FILE: doc/map.md ================================================ # Map Type XGo provides a concise syntax for working with maps. Maps are key-value data structures that allow you to store and retrieve values using keys. ## Creating Maps XGo provides two ways to create maps: using map literals for quick initialization with data, and using the `make` function for more control over map types and capacity. ### Map Literal Syntax In XGo, you can create maps using curly braces `{}`: ```go a := {"Hello": 1, "xsw": 3} // map[string]int b := {"Hello": 1, "xsw": 3.4} // map[string]float64 c := {"Hello": 1, "xsw": "XGo"} // map[string]any e := {1: "one", 2: "two"} // map[int]string d := {} // map[string]any ``` #### Automatic Type Inference When using the `:=` syntax without explicit type declaration, XGo automatically infers the complete map type `map[KeyType]ValueType` based on the literal syntax and values provided. **Type Inference Rules** Both `KeyType` and `ValueType` follow the same inference rules: 1. **Uniform Types**: If all elements have the same type, that type is used. 2. **Mixed Types**: If elements have different types, the type is inferred as `any`. 3. **Empty Map** `{}`: Inferred as `map[string]any` by default for maximum flexibility. You can also explicitly specify the map type to override automatic type inference: ```go var a map[string]float64 = {"Hello": 1, "xsw": 3} // Values converted to float64 var c map[string]any = {"x": 1, "y": "text"} // Explicit any type ``` When a type is explicitly declared, the literal values are converted to match the declared type. ### Creating Maps with `make` Use `make` when you need an empty map or want to optimize performance by pre-allocating capacity. #### Basic `make` Syntax ```go // Basic creation m := make(map[string]int) m["count"] = 42 echo m // Output: map[count:42] // Create a map with complex key types type Point (x, y int) positions := make(map[Point]string) positions[(0, 0)] = "origin" ``` #### Pre-allocating Capacity For performance optimization, you can specify an initial capacity hint: ```go // Create a map with initial capacity for ~100 elements // Pre-allocating capacity (helps performance for large maps) largeMap := make(map[string]int, 100) // This doesn't limit the map size, but helps reduce allocations for i := 0; i < 150; i++ { largeMap["key${i}"] = i } ``` The capacity hint doesn't limit the map's size but helps the runtime allocate memory more efficiently when you know approximately how many elements you'll add. #### When to Use `make` vs Literals **Use map literals** (`{}`) when: - You have initial data to populate - You want automatic type inference for convenience - You prefer concise, readable code **Use map literals with explicit type** (`var m map[K]V = {}`) when: - You have initial data with a specific type requirement - You need type safety while keeping syntax concise - You want to ensure value types are converted correctly **Use `make`** when: - You're creating an empty map and plan to add elements later - You want to pre-allocate capacity for performance - You prefer the traditional Go style - Working with codebases that consistently use `make` ## Map Operations Before manipulating maps, it is important to understand that XGo supports two notations for referencing keys: - **Bracket Notation** (`m["key"]`): The universal syntax. It works for all key types and allows using variables as keys. - **Field Access Notation** (`m.key`): A convenient shorthand for string-keyed maps when the key is a valid identifier (no spaces or special characters). **Field access is pure syntax sugar** - `m.field` and `m["field"]` behave identically in all contexts. Both notations are used for both **assigning** values and **retrieving** them. ### Adding and Updating Elements To add a new key-value pair or update an existing one, assign a value to a key using either notation. If the key exists, its value is updated; otherwise, a new entry is created. ```go a := {"a": 1, "b": 0} // Using bracket notation a["c"] = 100 // Using field notation a.d = 200 echo a // Output: map[a:1 b:0 c:100 d:200] // Works with maps created by make too m := make(map[string]int) m["x"] = 10 m.y = 20 echo m // Output: map[x:10 y:20] ``` ### Deleting Elements Use the `delete` function to remove elements from a map: ```go a := {"a": 1, "b": 0, "c": 100} delete(a, "b") echo a // Output: map[a:1 c:100] // Works with any key type m := make(map[int]string) m[1] = "one" m[2] = "two" delete(m, 1) echo m // Output: map[2:two] ``` ### Getting Map Length You can get the number of elements in a map using the `len` function: ```go a := {"a": 1, "b": 2, "c": 3} echo len(a) // Output: 3 b := make(map[string]int) b["x"] = 10 echo len(b) // Output: 1 ``` ### Accessing Elements XGo provides flexible ways to retrieve map values, including safety checks for missing keys. #### Bracket Notation The traditional way to access map elements is using the `[]` operator with a key: ```go a := {"name": "Alice", "age": 25} echo a["name"] // Output: Alice // Works with any key type m := make(map[int]string) m[42] = "answer" echo m[42] // Output: answer ``` #### Field Access Notation For string-keyed maps, XGo allows you to use dot notation when the key is a valid identifier: ```go config := {"host": "localhost", "port": 8080} echo config.host // Output: localhost echo config.port // Output: 8080 // Equivalent to: echo config["host"] echo config["port"] ``` ##### When to Use Each Style **Use field notation** (`m.field`) when: - Keys are known at development time - Keys are valid identifiers (letters, digits, underscores only) - You want more readable code **Use bracket notation** (`m["key"]`) when: - Keys are computed at runtime - Keys contain special characters, spaces, or start with digits - You need explicit compatibility with standard Go - Working with non-string key types ```go // Field notation - clean and readable user := {"name": "Alice", "age": 30} echo user.name echo user.age // Bracket notation - necessary for dynamic or special keys keyName := "name" echo user[keyName] // Dynamic key echo user["first-name"] // Key with hyphen echo user["2024-score"] // Key starting with digit // Bracket notation - required for non-string keys scores := make(map[int]float64) scores[1] = 95.5 echo scores[1] // Must use bracket notation ``` ##### Nested Access Field notation works seamlessly with nested maps: ```go data := { "user": { "profile": { "name": "Alice", "age": 30, }, }, } // Clean nested access echo data.user.profile.name // Output: Alice // Equivalent to: echo data["user"]["profile"]["name"] ``` ##### Working with `any` Type Either notation also works with variables of type `any`, automatically treating them as `map[string]any`: ```go var response any = {"status": "ok", "code": 200} echo response.status // Output: ok echo response.code // Output: 200 echo response["status"] // Output: ok echo response["code"] // Output: 200 ``` #### Safe Access with Comma-ok When accessing uncertain data (such as from JSON or external APIs), use the comma-ok form to safely check if a path exists. The comma-ok form returns two values: - The value itself (or zero value if path doesn't exist) - A boolean indicating whether the access succeeded ```go a := {"a": 1, "b": 0} // Check if key exists v, ok := a["c"] echo v, ok // Output: 0 false (key doesn't exist) v, ok = a["b"] echo v, ok // Output: 0 true (key exists with value 0) // Works with field notation too v, ok = a.c echo v, ok // Output: 0 false // Direct conditional check if v, ok := a["c"]; ok { echo "Found:", v } else { echo "Not found" // Output: Not found } ``` **With comma-ok, accessing non-existent paths never panics** - it simply returns `false`: ```go data := {"user": {"name": "Alice"}} // Safe single-level access name, ok := data.user if ok { echo "User found:", name } // Safe nested access profile, ok := data.user.profile if !ok { echo "Profile not found" // This will print } // Safe access with type assertion var response any = {"status": "ok", "code": 200} code, ok := response.code.(int) if ok { echo "Status code:", code } ``` This is especially useful when working with dynamic data: ```go var data any = {"user": "Alice"} // Without comma-ok - may panic if structure is wrong // name := data.user.profile.name // Would panic! // With comma-ok - safe, never panics name, ok := data.user.profile.name if !ok { echo "Path does not exist" // Output: Path does not exist name = "Unknown" } // Processing API response var apiResponse any = fetchFromAPI() // Safely extract nested values if userID, ok := apiResponse.data.user.id.(string); ok { processUser(userID) } else { echo "Invalid response structure" } // With fallback values city := "Unknown" if c, ok := apiResponse.user.address.city.(string); ok { city = c } echo "City:", city ``` ### Iterating Over Maps XGo provides two forms of `for in` loop for iterating over maps: #### Iterate Over Keys and Values ```go m := {"x": 10, "y": 20, "z": 30} for key, value in m { echo key, value } // Works with any map type ages := make(map[string]int) ages["Alice"] = 30 ages["Bob"] = 25 for name, age in ages { echo name, "is", age, "years old" } ``` #### Iterate Over Keys Only To iterate over just the keys, you can use the blank identifier `_` for the value part. ```go m := {"x": 10, "y": 20, "z": 30} for key, _ in m { echo key } ``` #### Iterate Over Values Only ```go m := {"x": 10, "y": 20, "z": 30} for value in m { echo value } ``` ## Map Comprehensions Map comprehensions provide a concise and expressive way to create new maps by transforming or filtering existing sequences. They follow a syntax similar to Python's dictionary comprehensions. ### Basic Syntax The general form of a map comprehension is: ```go {keyExpr: valueExpr for vars in iterable} ``` This creates a new map where each element from the `iterable` is transformed into a key-value pair. #### Creating Maps from Slices ```go // Map slice values to their indices numbers := [10, 20, 30, 40, 50] valueToIndex := {v: i for i, v in numbers} echo valueToIndex // Output: map[10:0 20:1 30:2 40:3 50:4] ``` #### Creating Maps from Strings ```go // Character positions in a string word := "hello" charPositions := {char: i for i, char in word} echo charPositions // Output: map[h:0 e:1 l:3 o:4] // Note: 'l' appears twice, so the last occurrence (index 3) is kept ``` ### Comprehensions with Conditions Add an `if` clause to filter elements: ```go {keyExpr: valueExpr for vars in iterable if condition} ``` #### Filtering Even/Odd Elements ```go numbers := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // Only even numbers evenSquares := {v: v * v for v in numbers if v%2 == 0} echo evenSquares // Output: map[2:4 4:16 6:36 8:64 10:100] // Only odd indices oddIndexValues := {i: v for i, v in numbers if i%2 == 1} echo oddIndexValues // Output: map[1:2 3:4 5:6 7:8 9:10] ``` ### Best Practices for Comprehensions 1. **Use comprehensions for simple transformations**: They're most readable when the logic is straightforward 2. **Consider traditional loops for complex logic**: If you need multiple statements or complex conditions, a regular loop may be clearer 3. **Watch for duplicate keys**: In comprehensions, later values overwrite earlier ones for the same key 4. **Keep conditions simple**: Complex filtering logic is often better in a traditional loop ## Common Patterns ### Configuration Maps ```go // Using literals for initial configuration config := { "host": "localhost", "port": 8080, "debug": true, } // Access with field notation echo "Connecting to", config.host, "on port", config.port // Using explicit type for type safety var settings map[string]int = { "maxConnections": 100, "timeout": 30, } // Using make for type-safe configuration options := make(map[string]int) options["maxConnections"] = 100 options["timeout"] = 30 ``` ### Processing JSON Responses ```go var response any = parseJSON(apiData) // Safe extraction with defaults userID, ok := response.user.id.(string) if !ok { userID = "guest" } userName, ok := response.user.name.(string) if !ok { userName = "Anonymous" } echo "User:", userName, "(", userID, ")" ``` ### Counting Occurrences ```go // Using make with pre-allocated capacity wordCounts := make(map[string]int, 1000) for word in words { wordCounts[word]++ } // Using comprehension to initialize words := ["apple", "banana", "apple", "orange", "banana", "apple"] uniqueWords := {w: 0 for w in words} // Initialize all to 0 for word in words { uniqueWords[word]++ } ``` ### Lookup Tables ```go // Simple lookup table with literals statusCodes := { "ok": 200, "not_found": 404, "error": 500, } echo statusCodes.ok // Output: 200 // Using comprehension to reverse the mapping codeToStatus := {code: status for status, code in statusCodes} echo codeToStatus[200] // Output: ok ``` ### Caching ```go // Cache with pre-allocated capacity for performance cache := make(map[string][]byte, 10000) func getCachedData(key string) []byte { if data, ok := cache[key]; ok { return data } data := fetchData(key) cache[key] = data return data } ``` ### Grouping Data ```go // Group items by category groups := make(map[string][]string) for item in items { category := getCategory(item) groups[category] = append(groups[category], item) } // Access grouped data for category, items in groups { echo "Category:", category for item in items { echo " -", item } } ``` ## Best Practices 1. **Use field notation for readability** when keys are known and are valid identifiers 2. **Use bracket notation** when keys are dynamic, contain special characters, or are non-string types 3. **Use comma-ok form** when working with uncertain data structures (APIs, JSON, dynamic data) 4. **Use map literals** for quick initialization with known data 5. **Use explicit type declaration** with literals when you need type safety or specific conversions 6. **Use `make`** when you need specific types, non-string keys, or want to pre-allocate capacity 7. **Use map comprehensions** for simple transformations and filtering of sequences 8. Check for key existence before accessing values when the key might not exist 9. Pre-allocate capacity with `make` for large maps when the approximate size is known 10. Use consistent value types when possible for type safety 11. Consider using `make` with explicit types for better code documentation and type safety in larger projects ## Performance Tips 1. **Pre-allocate capacity**: When you know the approximate size, use `make(map[K]V, size)` to reduce allocations 2. **Avoid frequent reallocations**: Maps grow dynamically, but pre-allocation prevents repeated internal resizing 3. **Use appropriate key types**: Simple types (int, string) as keys are more efficient than complex structs 4. **Consider zero values**: Accessing non-existent keys returns zero values, which can be useful for counters 5. **Comprehensions vs loops**: For large datasets or complex transformations, traditional loops with pre-allocation may be more efficient than comprehensions ================================================ FILE: doc/overload.md ================================================ Overload Func/Method/Operator/Types ===== ### Overload Funcs Define `overload func` in `inline func literal` style (see [overloadfunc1/add.xgo](demo/overloadfunc1/add.xgo)): ```go func add = ( func(a, b int) int { return a + b } func(a, b string) string { return a + b } ) println add(100, 7) println add("Hello", "World") ``` Define `overload func` in `ident` style (see [overloadfunc2/mul.xgo](demo/overloadfunc2/mul.xgo)): ```go func mulInt(a, b int) int { return a * b } func mulFloat(a, b float64) float64 { return a * b } func mul = ( mulInt mulFloat ) println mul(100, 7) println mul(1.2, 3.14) ``` ### Overload Methods Define `overload method` (see [overloadmethod/method.xgo](demo/overloadmethod/method.xgo)): ```go type foo struct { } func (a *foo) mulInt(b int) *foo { println "mulInt" return a } func (a *foo) mulFoo(b *foo) *foo { println "mulFoo" return a } func (foo).mul = ( (foo).mulInt (foo).mulFoo ) var a, b *foo var c = a.mul(100) var d = a.mul(c) ``` ### Overload Unary Operators Define `overload unary operator` (see [overloadop1/overloadop.xgo](demo/overloadop1/overloadop.xgo)): ```go type foo struct { } func -(a foo) (ret foo) { println "-a" return } func ++(a foo) { println "a++" } var a foo var b = -a a++ ``` ### Overload Binary Operators Define `overload binary operator` (see [overloadop1/overloadop.xgo](demo/overloadop1/overloadop.xgo)): ```go type foo struct { } func (a foo) * (b foo) (ret foo) { println "a * b" return } func (a foo) != (b foo) bool { println "a != b" return true } var a, b foo var c = a * b var d = a != b ``` However, `binary operator` usually need to support interoperability between multiple types. In this case it becomes more complex (see [overloadop2/overloadop.xgo](demo/overloadop2/overloadop.xgo)): ```go type foo struct { } func (a foo) mulInt(b int) (ret foo) { println "a * int" return } func (a foo) mulFoo(b foo) (ret foo) { println "a * b" return } func intMulFoo(a int, b foo) (ret foo) { println "int * b" return } func (foo).* = ( (foo).mulInt (foo).mulFoo intMulFoo ) var a, b foo var c = a * 10 var d = a * b var e = 10 * a ``` ### Overload Types TODO ### Overload Typecast TODO ================================================ FILE: doc/slice.md ================================================ # Slice Type A `slice` (also named `list`) is a dynamically-sized, flexible view into the elements of an array. Slices are one of the most commonly used data structures in XGo, providing efficient and convenient ways to work with sequences of elements. **Note**: In XGo, the terms `slice` and `list` are identical and refer to the same data structure. The term "slice" comes from Go's terminology, while "list" aligns with Python's naming convention. ## Understanding Slices A slice consists of three components: 1. **Pointer** - Points to the first element of the slice in the underlying array 2. **Length** - The number of elements in the slice 3. **Capacity** - The number of elements from the beginning of the slice to the end of the underlying array Unlike arrays which have a fixed size, slices can grow and shrink dynamically, making them ideal for most collection use cases. ## Creating Slices XGo provides multiple ways to create slices: using slice literals for quick initialization with data, using the `make` function for more control over slice types and capacity, and slicing existing arrays or slices. ### Slice Literal Syntax In XGo, you can create slices using square brackets `[]`: ```go a := [1, 2, 3] // []int b := [1, 2, 3.4] // []float64 c := ["Hi"] // []string d := ["Hi", 10] // []any - mixed types e := [] // []any - empty slice ``` #### Automatic Type Inference When using the `:=` syntax without explicit type declaration, XGo automatically infers the complete slice type `[]ElementType` based on the literal values provided. **Type Inference Rules** 1. **Uniform Types**: If all elements have the same type, that type is used. 2. **Mixed Types**: If elements have incompatible types, the type is inferred as `any`. 3. **Empty Slice** `[]`: Inferred as `[]any` by default for maximum flexibility. You can also explicitly specify the slice type to override automatic type inference: ```go // Explicit type declaration var a []float64 = [1, 2, 3] // Values converted to float64 var c []any = ["x", 1, true] // Explicit any type ``` When a type is explicitly declared, the literal values are converted to match the declared type. ### Creating Slices with `make` Use `make` when you need an empty slice or want to optimize performance by pre-allocating capacity. #### Basic `make` Syntax ```go // Create slice with specified length (initialized to zero values) s1 := make([]int, 5) // [0, 0, 0, 0, 0] s2 := make([]string, 3) // ["", "", ""] // Access and modify s1[0] = 100 s1[2] = 300 echo s1 // Output: [100 0 300 0 0] ``` #### Pre-allocating Capacity For performance optimization, you can specify both length and capacity: ```go // Create slice with length 0 and capacity 100 s := make([]int, 0, 100) // This doesn't limit the slice size, but helps reduce allocations for i := 0; i < 150; i++ { s <- i } echo len(s) // Output: 150 echo cap(s) // Output: likely > 150 ``` The capacity hint doesn't limit the slice's size but helps the runtime allocate memory more efficiently when you know approximately how many elements you'll add. #### When to Use `make` vs Literals **Use slice literals** (`[]`) when: - You have initial data to populate - You want automatic type inference for convenience - You prefer concise, readable code **Use slice literals with explicit type** (`var s []T = []`) when: - You have initial data with a specific type requirement - You need type safety while keeping syntax concise - You want to ensure value types are converted correctly **Use `make`** when: - You need a slice initialized with zero values - You want to pre-allocate capacity for performance - You're creating an empty slice and plan to add elements later - Working with codebases that consistently use `make` ### Creating Slices from Slices You can create new slices by slicing existing arrays or slices using the range syntax `[start:end]`: ```go arr := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // Basic slicing slice1 := arr[2:5] // [3, 4, 5] - from index 2 to 5 (exclusive) slice2 := arr[:3] // [1, 2, 3] - from start to index 3 slice3 := arr[5:] // [6, 7, 8, 9, 10] - from index 5 to end slice4 := arr[:] // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - full slice (shallow copy) ``` **Important**: Slices created this way share the same underlying array. Modifying one slice may affect others: ```go arr := [1, 2, 3, 4, 5] slice1 := arr[1:4] // [2, 3, 4] slice2 := arr[2:5] // [3, 4, 5] slice1[1] = 100 // Modifies the shared underlying array echo slice1 // Output: [2 100 4] echo slice2 // Output: [100 4 5] - also affected! echo arr // Output: [1 2 100 4 5] - original array modified ``` ## Slice Operations ### Modifying Elements You can directly modify elements at specific indexes: ```go nums := [1, 2, 3, 4, 5] nums[0] = 100 nums[2] = 300 echo nums // Output: [100 2 300 4 5] ``` ### Appending Elements XGo provides two ways to append elements to slices: the `<-` operator and the `append` built-in function. #### Using the `<-` Operator The `<-` operator provides an intuitive way to append elements: ```go nums := [1, 2, 3] nums <- 4 // Append single element nums <- 5, 6, 7 // Append multiple elements more := [8, 9, 10] nums <- more... // Append another slice echo nums // Output: [1 2 3 4 5 6 7 8 9 10] ``` #### Using the `append` Function The `append` function returns a new slice with elements added or removed: ```go // Adding elements nums := [1, 2, 3] nums = append(nums, 4) // Append single element nums = append(nums, 5, 6, 7) // Append multiple elements more := [8, 9, 10] nums = append(nums, more...) // Append another slice echo nums // Output: [1 2 3 4 5 6 7 8 9 10] ``` **Important**: The `append` function returns a new slice, so you must assign the result back to a variable. #### Removing Elements with `append` The `append` function can also remove consecutive elements by concatenating slices before and after the range to remove: ```go nums := [1, 2, 3, 4, 5] // Remove element at index 2 (value 3) nums = append(nums[:2], nums[3:]...) echo nums // Output: [1 2 4 5] // Remove multiple consecutive elements (indices 1-2) nums = [1, 2, 3, 4, 5] nums = append(nums[:1], nums[3:]...) echo nums // Output: [1 4 5] ``` This pattern uses slice notation to select everything before the removal range (`nums[:start]`) and everything after it (`nums[end:]`), then concatenates them together. This effectively removes the elements in the slice `nums[start:end]`. ### Accessing Elements Indexes start from `0`. Valid indexes range from `0` to `len(slice) - 1`: ```go nums := [10, 20, 30, 40, 50] echo nums[0] // 10 - first element echo nums[1] // 20 - second element echo nums[4] // 50 - last element ``` **Note**: Negative indexing is not supported. Using an index outside the valid range will cause a runtime error. ### Getting Slice Length and Capacity You can get the length and capacity of a slice using the `len` and `cap` functions: ```go nums := [1, 2, 3, 4, 5] echo len(nums) // 5 - number of elements echo cap(nums) // 5 - capacity (may be larger) ``` ### Extracting Sub-slices You can extract portions of a slice using the range syntax: ```go nums := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] echo nums[2:5] // [2 3 4] - from index 2 to 5 (exclusive) echo nums[:3] // [0 1 2] - from start to index 3 echo nums[5:] // [5 6 7 8 9] - from index 5 to end echo nums[:] // [0 1 2 3 4 5 6 7 8 9] - full slice (shallow copy) ``` **Remember**: Sub-slices share the underlying array with the original slice. See "Creating Slices from Arrays or Slices" for details on this behavior. ### Iterating Over Slices XGo provides multiple ways to iterate over slices using `for` loops: #### Iterate Over Index and Value ```go nums := [10, 20, 30, 40, 50] for i, v in nums { echo "Index:", i, "Value:", v } ``` #### Iterate Over Values Only ```go nums := [10, 20, 30, 40, 50] for v in nums { echo v } ``` #### Iterate Over Indexes Only ```go nums := [10, 20, 30, 40, 50] for i, _ in nums { echo "Index:", i } ``` ## List Comprehensions List comprehensions provide a concise and expressive way to create new lists by transforming or filtering existing sequences. They follow a syntax similar to Python's list comprehensions. ### Basic Syntax The general form of a list comprehension is: ```go [expression for vars in iterable] ``` This creates a new list where each element from the `iterable` is transformed by the `expression`. #### Transforming Elements ```go // Square all numbers numbers := [1, 2, 3, 4, 5] squares := [v * v for v in numbers] echo squares // Output: [1 4 9 16 25] // Convert to strings words := ["hello", "world"] upper := [v.toUpper for v in words] echo upper // Output: ["HELLO" "WORLD"] // Extract from index-value pairs doubled := [v * 2 for i, v in numbers] echo doubled // Output: [2 4 6 8 10] ``` #### Creating Lists from Ranges ```go // Generate sequence nums := [i for i in 1:11] echo nums // Output: [1 2 3 4 5 6 7 8 9 10] // With transformation evens := [i * 2 for i in :5] echo evens // Output: [0 2 4 6 8] // With step odds := [i for i in 1:10:2] echo odds // Output: [1 3 5 7 9] ``` ### Comprehensions with Conditions Add an `if` clause to filter elements: ```go [expression for vars in iterable if condition] ``` #### Filtering Elements ```go numbers := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // Only even numbers evens := [v for v in numbers if v % 2 == 0] echo evens // Output: [2 4 6 8 10] // Only numbers greater than 5 large := [v for v in numbers if v > 5] echo large // Output: [6 7 8 9 10] // Filter and transform evenSquares := [v * v for v in numbers if v % 2 == 0] echo evenSquares // Output: [4 16 36 64 100] ``` #### Filtering with Index ```go numbers := [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] // Elements at even indices evenIndexValues := [v for i, v in numbers if i % 2 == 0] echo evenIndexValues // Output: [10 30 50 70 90] ``` ### Nested Comprehensions List comprehensions can be nested to work with multi-dimensional data: ```go // Flatten a 2D list matrix := [[1, 2, 3], [4, 5, 6], [7, 8, 9]] flattened := [num for row in matrix for num in row] echo flattened // Output: [1 2 3 4 5 6 7 8 9] // Create multiplication table table := [[i * j for j in 1:6] for i in 1:6] echo table // Output: [[1 2 3 4 5] [2 4 6 8 10] [3 6 9 12 15] [4 8 12 16 20] [5 10 15 20 25]] // Extract diagonal elements diagonal := [matrix[i][i] for i in :len(matrix)] echo diagonal // Output: [1 5 9] ``` ### Best Practices for Comprehensions 1. **Use comprehensions for simple transformations**: They're most readable when the logic is straightforward 2. **Consider traditional loops for complex logic**: If you need multiple statements or complex conditions, a regular loop may be clearer 3. **Avoid excessive nesting**: More than two levels of nesting can be hard to read 4. **Keep expressions concise**: Long or complex expressions reduce readability 5. **Use meaningful variable names**: Even in short comprehensions, clarity matters ### When to Use Comprehensions vs Loops **Use list comprehensions** when: - You need a simple transformation of each element - You're filtering based on a clear condition - The logic fits naturally in a single expression - You want concise, functional-style code **Use traditional loops** when: - You need multiple statements per iteration - You have complex conditional logic - You need to break or continue based on conditions - You're modifying external state or have side effects - Readability would suffer from cramming logic into a comprehension ```go // Good use of comprehension squares := [x * x for x in :10] // Better as a traditional loop (side effects, complex logic) results := [] for x in :10 { result := complexCalculation(x) if result > threshold { results <- result updateGlobalState(result) } } ``` ## Common Patterns ### Filtering Slices ```go nums := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] evens := [] for v in nums { if v % 2 == 0 { evens <- v } } echo evens // Output: [2 4 6 8 10] ``` ### Transforming Slices (Map Operation) ```go nums := [1, 2, 3, 4, 5] squared := [] for v in nums { squared <- v * v } echo squared // Output: [1 4 9 16 25] ``` ### Finding Elements ```go nums := [10, 20, 30, 40, 50] target := 30 found := false index := -1 for i, v in nums { if v == target { found = true index = i break } } if found { echo "Found", target, "at index", index } else { echo target, "not found" } ``` ### Reversing a Slice ```go nums := [1, 2, 3, 4, 5] reversed := [] for i := len(nums) - 1; i >= 0; i-- { reversed <- nums[i] } echo reversed // Output: [5 4 3 2 1] ``` ### Removing Duplicates ```go nums := [1, 2, 2, 3, 3, 3, 4, 5, 5] unique := [] seen := {} for v in nums { if !seen[v] { unique <- v seen[v] = true } } echo unique // Output: [1 2 3 4 5] ``` ### Merging Multiple Slices ```go a := [1, 2, 3] b := [4, 5, 6] c := [7, 8, 9] merged := [] merged <- a... merged <- b... merged <- c... echo merged // Output: [1 2 3 4 5 6 7 8 9] ``` ### Summing Elements ```go nums := [1, 2, 3, 4, 5] sum := 0 for v in nums { sum += v } echo sum // Output: 15 ``` ### Finding Maximum and Minimum ```go nums := [34, 12, 67, 23, 89, 45] max := nums[0] min := nums[0] for v in nums { if v > max { max = v } if v < min { min = v } } echo "Max:", max // Output: 89 echo "Min:", min // Output: 12 ``` ### Checking if Slice Contains Element ```go nums := [10, 20, 30, 40, 50] target := 30 contains := false for v in nums { if v == target { contains = true break } } echo contains // Output: true ``` ### Partitioning a Slice ```go nums := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] evens := [] odds := [] for v in nums { if v % 2 == 0 { evens <- v } else { odds <- v } } echo evens // Output: [2 4 6 8 10] echo odds // Output: [1 3 5 7 9] ``` ### Flattening Nested Slices ```go nested := [[1, 2], [3, 4], [5, 6]] flat := [] for subslice in nested { flat <- subslice... } echo flat // Output: [1 2 3 4 5 6] ``` ### Using Slices as Stacks ```go stack := [] // Push elements stack <- 1 stack <- 2 stack <- 3 echo stack // Output: [1 2 3] // Pop element if len(stack) > 0 { top := stack[len(stack) - 1] stack = stack[:len(stack) - 1] echo "Popped:", top // Output: Popped: 3 echo stack // Output: [1 2] } ``` ### Using Slices as Queues ```go queue := [] // Enqueue elements queue <- 1 queue <- 2 queue <- 3 echo queue // Output: [1 2 3] // Dequeue element if len(queue) > 0 { front := queue[0] queue = queue[1:] echo "Dequeued:", front // Output: Dequeued: 1 echo queue // Output: [2 3] } ``` ### Sliding Window Pattern ```go nums := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] windowSize := 3 for i := 0; i <= len(nums) - windowSize; i++ { window := nums[i:i + windowSize] echo "Window:", window } // Output: // Window: [1 2 3] // Window: [2 3 4] // Window: [3 4 5] // ... ``` ### Grouping Data ```go // Group items by category items := ["apple", "banana", "carrot", "date", "eggplant"] groups := make(map[string][]string) for item in items { firstLetter := item[0:1] groups[firstLetter] <- item } // Access grouped data for category, itemList in groups { echo "Category:", category for item in itemList { echo " -", item } } ``` ## Best Practices 1. **Pre-allocate capacity when size is known**: Use `make([]T, 0, size)` to avoid multiple reallocations 2. **Use `len(slice)` and `cap(slice)`**: These are the recommended ways to get length and capacity 3. **Check bounds before accessing**: Ensure indexes are within valid range `[0, len(slice)-1]` 4. **Be aware of slice sharing**: Slices created by slicing share the same underlying array 5. **Use the `<-` operator for appending**: It's more concise and idiomatic in XGo 6. **Use meaningful variable names**: Make code self-documenting 7. **Avoid modifying slices during iteration**: Create a new slice instead 8. **Document slice modifications**: Make it clear whether functions modify input slices 9. **Use deep copies when independence is needed**: Use `copy` or manual copying 10. **Consider slice capacity for performance**: Pre-allocating can significantly improve performance for large slices ## Performance Considerations ### Slice Growth When a slice's capacity is exceeded during append operations, XGo allocates a new underlying array with increased capacity: ```go s := [] echo len(s), cap(s) // Output: 0 0 s <- 1 echo len(s), cap(s) // Output: 1 1 s <- 2 echo len(s), cap(s) // Output: 2 2 s <- 3 echo len(s), cap(s) // Output: 3 4 (capacity doubled) s <- 4, 5 echo len(s), cap(s) // Output: 5 8 (capacity doubled again) ``` The exact growth strategy may vary, but typically capacity doubles when exceeded. ### Memory Efficiency Pre-allocating capacity avoids multiple reallocations: ```go // Inefficient - multiple reallocations inefficient := [] for i := 0; i < 1000; i++ { inefficient <- i } // Efficient - single allocation efficient := make([]int, 0, 1000) for i := 0; i < 1000; i++ { efficient <- i } ``` ### Avoiding Memory Leaks When creating a small slice from a large slice, the underlying array is still retained: ```go // May cause memory leak func getFirstThree(data []int) []int { return data[:3] // Still references the entire underlying array } // Better approach - create independent slice func getFirstThree(data []int) []int { result := make([]int, 3) copy(result, data[:3]) return result } ``` ## Common Pitfalls ### 1. Index Out of Bounds ```go nums := [1, 2, 3] // This will cause a runtime error // echo nums[10] // Error: index out of range // Always check bounds index := 10 if index >= 0 && index < len(nums) { echo nums[index] } else { echo "Index out of bounds" } ``` ### 2. Negative Indexing Not Supported ```go nums := [1, 2, 3, 4, 5] // This is NOT valid in XGo // echo nums[-1] // Error: invalid slice index // To access last element, use: echo nums[len(nums) - 1] // Output: 5 ``` ### 3. Unintended Sharing ```go a := [1, 2, 3] b := a // b references same underlying array b[0] = 100 echo a // Output: [100 2 3] - a is also modified! // To avoid this, make a copy c := make([]int, len(a)) copy(c, a) c[0] = 200 echo a // Output: [100 2 3] - a is not affected ``` ### 4. Slice of Slices Sharing ```go // Careful with slice of slices matrix := [] row := [1, 2, 3] matrix <- row matrix <- row // Both rows reference the same underlying array! row[0] = 100 echo matrix // Output: [[100 2 3] [100 2 3]] - both rows are modified! // Better approach - create independent rows matrix := [] matrix <- [1, 2, 3] matrix <- [1, 2, 3] // Each row is independent ``` ### 5. Modifying During Iteration ```go // Avoid this - may cause unexpected behavior nums := [1, 2, 3, 4, 5] for i, v in nums { if v % 2 == 0 { nums <- v * 2 // Modifying during iteration - risky! } } // Better approach - create new slice result := [] for v in nums { if v % 2 == 0 { result <- v * 2 } else { result <- v } } ``` ## Summary XGo's slices provide a powerful and flexible way to work with sequences of elements. Key features include: 1. **Simple Literal Syntax**: Use `[]` for concise slice creation 2. **Automatic Type Inference**: No need for explicit type specification in most cases 3. **Intuitive Append Operations**: Use the `<-` operator or `append` function for adding elements 4. **Flexible Slicing**: Create sub-slices with simple range syntax 5. **Multiple Iteration Styles**: Choose the iteration pattern that fits your needs By understanding these features and following best practices, you can write efficient and maintainable code that leverages the full power of XGo's slice type. ================================================ FILE: doc/spec/mini/mini.xgo ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package mini import ( "xgo/token" "xgo/tpl" ) var _off = tpl.showConflict(false) var Spec = tpl` SourceFile = ?PackageClause *(ImportDecl ";") *(TopLevelDecl ";") ?MainStmts PackageClause = "package" IDENT ";" ImportDecl = "import" (ImportSpec | "(" *(ImportSpec ";") ")") ImportSpec = ?(IDENT | ".") STRING TopLevelDecl = Declaration | FuncDecl Declaration = ConstDecl | VarDecl | TypeDecl ConstDecl = "const" (ConstSpec | "(" *(ConstSpec ";") ")") ConstSpec = IdentifierList ?Type ?("=" ExpressionList) IdentifierList = IDENT % "," LambdaExprList = LambdaExpr % "," ExpressionList = Expression % "," VarDecl = "var" (VarSpec | "(" *(VarSpec ";") ")") VarSpec = IdentifierList ("=" ExpressionList | Type ?("=" LambdaExprList)) TypeDecl = "type" (TypeSpec | "(" *(TypeSpec ";") ")") TypeSpec = IDENT ?"=" Type FuncDecl = "func" IDENT Signature ?Block Signature = Parameters ?Result Parameters = "(" ?(ParameterList ?",") ")" ParameterList = IdentifierList ?(Type *("," IdentifierList Type) | *("," Type)) Result = Parameters | NoParenType MainStmts = StatementList // ----------------------------------------------------------------- Block = "{" StatementList "}" StatementList = *(Statement ";") Statement = Declaration | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt | FallthroughStmt | IfStmt | SwitchStmt | ForStmt | DeferStmt | Block | LabeledStmt | CommandStmt | SimpleStmt ReturnStmt = "return" ?LambdaExprList BreakStmt = "break" ?IDENT ContinueStmt = "continue" ?IDENT GotoStmt = "goto" IDENT FallthroughStmt = "fallthrough" IfStmt = "if" ?(SimpleStmt ";") Expression Block ?("else" (IfStmt | Block)) SwitchStmt = "switch" ?(SimpleStmt ";") SwitchGuard "{" *CaseClause "}" SwitchGuard = ?(IDENT ":=") PrimaryExpr | ?Expression CaseClause = ("case" ExpressionList | "default") ":" StatementList ForStmt = "for" (IDENT ?("," IDENT) "in" RangeExpr | ?RangeExpr) Block DeferStmt = "defer" Expression LabeledStmt = IDENT ":" Statement SimpleStmt = SendStmt | ShortVarDecl | IncDecStmt | Assignment | ExpressionStmt | EmptyStmt SendStmt = IDENT ?("." IDENT) "<-" LambdaExprList ?"..." ShortVarDecl = IdentifierList ":=" ExpressionList IncDecStmt = Expression ("++" | "--") Assignment = ExpressionList ( "=" | "+=" | "-=" | "|=" | "^=" | "*=" | "/=" | "%=" | "<<=" | ">>=" | "&=" | "&^=") LambdaExprList ExpressionStmt = Expression CommandStmt = IDENT ?("." IDENT) SPACE LambdaExprList ?"..." EmptyStmt = "" // ----------------------------------------------------------------- NoParenType = TypeLit | TypeName Type = TypeLit | TypeName | "(" Type ")" TypeName = IDENT ?("." IDENT) TypeLit = PointerType | ArrayType | MapType | FuncType | TupleType | InterfaceType PointerType = "*" Type ArrayType = "[" ?Expression "]" Type MapType = "map" "[" Type "]" Type FuncType = "func" Signature TupleType = Parameters InterfaceType = "interface" "{" *(InterfaceElem ";") "}" InterfaceElem = MethodElem | TypeName MethodElem = IDENT Signature // ----------------------------------------------------------------- LambdaExpr = ("(" ?(IDENT % ",") ")" | ?IDENT) "=>" LambdaBody | Expression LambdaBody = Block | "(" LambdaExpr % "," ")" | LambdaExpr RangeExpr = rangeExprEnd | Expression ?rangeExprEnd rangeExprEnd = ":" Expression ?(":" ?Expression) Expression = cmpExpr % "&&" % "||" cmpExpr = mathExpr % ("==" | "!=" | "<" | "<=" | ">" | ">=") mathExpr = UnaryExpr % ("*" | "/" | "%" | "<<" | ">>" | "&" | "&^") % ("+" | "-" | "|" | "^") UnaryExpr = PrimaryExpr | ("-" | "!" | "^" | "*" | "&" | "+") UnaryExpr PrimaryExpr = Operand *( CallOrConversion | SelectorOrTypeAssertion | IndexOrSlice | ErrWrap) Operand = INT ?UNIT | FLOAT ?UNIT | STRING | CHAR | RAT | IMAG | TupleLit | LiteralValue | FunctionLit | Env | "c" ++ QSTRING | "py" ++ QSTRING | ListLit | DomainTextLit | IDENT Env = "$" ("{" IDENT "}" | IDENT) DomainTextLit = IDENT ++ RAWSTRING TupleLit = "(" ExpressionList ?"," ")" ListLit = "[" ?(LambdaExpr % ",") ?"," "]" LiteralValue = "{" ElementList "}" ElementList = ?(KeyedElement % "," ?",") KeyedElement = ?(Key ":") Element Key = LiteralValue | Expression Element = LiteralValue | LambdaExpr FunctionLit = "func" Signature Block CallOrConversion = "(" ?(argExpr % ",") ?"..." ?"," ")" argExpr = KwargExpr | LambdaExpr KwargExpr = IDENT "=" LambdaExpr SelectorOrTypeAssertion = "." (IDENT | "(" Type ")") IndexOrSlice = "[" (":" ?Expression | Expression (":" ?Expression | ?",")) "]" ErrWrap = "!" | "?" ?(":" UnaryExpr) `! // ----------------------------------------------------------------- func ParseFile(fset *token.FileSet, filename string, src any) (any, error) { return Spec.Parse(filename, src, &tpl.Config{ Fset: fset, }) } // ----------------------------------------------------------------- ================================================ FILE: doc/spec-mini.md ================================================ The XGo Mini Specification ===== XGo has a recommended best practice syntax set, which we call the XGo Mini Specification. It is simple but Turing-complete and can elegantly implement any business requirements. ## Notation The syntax is specified using a [variant](https://en.wikipedia.org/wiki/Wirth_syntax_notation) of Extended Backus-Naur Form (EBNF): ```go Syntax = { Production } . Production = production_name "=" [ Expression ] "." . Expression = Term { "|" Term } . Term = Factor { Factor } . Factor = production_name | token [ "…" token ] | Group | Option | Repetition . Group = "(" Expression ")" . Option = "[" Expression "]" . Repetition = "{" Expression "}" . ``` Productions are expressions constructed from terms and the following operators, in increasing precedence: ```go | alternation () grouping [] option (0 or 1 times) {} repetition (0 to n times) ``` Lowercase production names are used to identify lexical (terminal) tokens. Non-terminals are in CamelCase. Lexical tokens are enclosed in double quotes `""` or back quotes ``. The form `a … b` represents the set of characters from a through b as alternatives. The horizontal ellipsis `…` is also used elsewhere in the spec to informally denote various enumerations or code snippets that are not further specified. The character … (as opposed to the three characters `...`) is not a token of the XGo language. ## Source code representation Source code is Unicode text encoded in [UTF-8](https://en.wikipedia.org/wiki/UTF-8). The text is not canonicalized, so a single accented code point is distinct from the same character constructed from combining an accent and a letter; those are treated as two code points. For simplicity, this document will use the unqualified term character to refer to a Unicode code point in the source text. Each code point is distinct; for instance, uppercase and lowercase letters are different characters. Implementation restriction: For compatibility with other tools, a compiler may disallow the NUL character (U+0000) in the source text. Implementation restriction: For compatibility with other tools, a compiler may ignore a UTF-8-encoded byte order mark (U+FEFF) if it is the first Unicode code point in the source text. A byte order mark may be disallowed anywhere else in the source. ### Characters The following terms are used to denote specific Unicode character categories: ```go newline = /* the Unicode code point U+000A */ . unicode_char = /* an arbitrary Unicode code point except newline */ . unicode_letter = /* a Unicode code point categorized as "Letter" */ . unicode_digit = /* a Unicode code point categorized as "Number, decimal digit" */ . ``` In [The Unicode Standard 8.0](https://www.unicode.org/versions/Unicode8.0.0/), Section 4.5 "General Category" defines a set of character categories. Go treats all characters in any of the Letter categories Lu, Ll, Lt, Lm, or Lo as Unicode letters, and those in the Number category Nd as Unicode digits. ### Letters and digits The underscore character _ (U+005F) is considered a lowercase letter. ```go letter = unicode_letter | "_" . decimal_digit = "0" … "9" . binary_digit = "0" | "1" . octal_digit = "0" … "7" . hex_digit = "0" … "9" | "A" … "F" | "a" … "f" . ``` ## Lexical elements ### Comments Comments serve as program documentation. There are three forms: * _Line comments_ start with the character sequence `//` and stop at the end of the line. * _Line comments_ start with the character sequence `#` and stop at the end of the line. * _General comments_ start with the character sequence `/*` and stop with the first subsequent character sequence `*/`. A _general comment_ containing no newlines acts like a space. Any other comment acts like a newline. ``` # this is a line comment // this is another line comment /* this is a general comment */ ``` ### Tokens Tokens form the vocabulary of the XGo language. There are four classes: _identifiers_, _keywords_, _operators_ and _punctuation_, and _literals_. White space, formed from spaces (U+0020), horizontal tabs (U+0009), carriage returns (U+000D), and newlines (U+000A), is ignored except as it separates tokens that would otherwise combine into a single token. Also, a newline or end of file may trigger the insertion of a [semicolon](). While breaking the input into tokens, the next token is the longest sequence of characters that form a valid token. ### Semicolons The formal syntax uses semicolons ";" as terminators in a number of productions. XGo programs may omit most of these semicolons using the following two rules: * When the input is broken into tokens, a semicolon is automatically inserted into the token stream immediately after a line's final token if that token is * an [identifier](#identifiers) * an [integer](), [floating-point](), [imaginary](), [rune](), or [string]() literal * one of the [keywords]() `break`, `continue`, `fallthrough`, or `return` * one of the [operators and punctuation]() `++`, `--`, `)`, `]`, or `}` * To allow complex statements to occupy a single line, a semicolon may be omitted before a closing `")"` or `"}"`. To reflect idiomatic use, code examples in this document elide semicolons using these rules. ### Identifiers Identifiers name program entities such as variables and types. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter. ```go identifier = letter { letter | unicode_digit } . ``` ```go a _x9 ThisVariableIsExported αβ ``` Some identifiers are [predeclared](#predeclared-identifiers). ### Keywords The following keywords are reserved and may not be used as identifiers (TODO: some keywords are allowed as identifiers). ```go break default func interface select case defer go map type chan else goto package switch const fallthrough if range var continue for import return ``` ### Operators and punctuation The following character sequences represent [operators](#operators) (including [assignment operators](#assignment-statements)) and punctuation: ``` + & += &= && == != ( ) - | -= |= || < <= [ ] * ^ *= ^= <- > >= { } / << /= <<= ++ = := , ; % >> %= >>= -- ! ... . : &^ &^= ~ ``` ### Integer literals An integer literal is a sequence of digits representing an [integer constant](#constants). An optional prefix sets a non-decimal base: 0b or 0B for binary, 0, 0o, or 0O for octal, and 0x or 0X for hexadecimal. A single 0 is considered a decimal zero. In hexadecimal literals, letters a through f and A through F represent values 10 through 15. For readability, an underscore character _ may appear after a base prefix or between successive digits; such underscores do not change the literal's value. ```go int_lit = decimal_lit | binary_lit | octal_lit | hex_lit . decimal_lit = "0" | ( "1" … "9" ) [ [ "_" ] decimal_digits ] . binary_lit = "0" ( "b" | "B" ) [ "_" ] binary_digits . octal_lit = "0" [ "o" | "O" ] [ "_" ] octal_digits . hex_lit = "0" ( "x" | "X" ) [ "_" ] hex_digits . decimal_digits = decimal_digit { [ "_" ] decimal_digit } . binary_digits = binary_digit { [ "_" ] binary_digit } . octal_digits = octal_digit { [ "_" ] octal_digit } . hex_digits = hex_digit { [ "_" ] hex_digit } . ``` ```go 42 4_2 0600 0_600 0o600 0O600 // second character is capital letter 'O' 0xBadFace 0xBad_Face 0x_67_7a_2f_cc_40_c6 170141183460469231731687303715884105727 170_141183_460469_231731_687303_715884_105727 _42 // an identifier, not an integer literal 42_ // invalid: _ must separate successive digits 4__2 // invalid: only one _ at a time 0_xBadFace // invalid: _ must separate successive digits ``` ### Floating-point literals A floating-point literal is a decimal or hexadecimal representation of a [floating-point constant](#constants). A decimal floating-point literal consists of an integer part (decimal digits), a decimal point, a fractional part (decimal digits), and an exponent part (e or E followed by an optional sign and decimal digits). One of the integer part or the fractional part may be elided; one of the decimal point or the exponent part may be elided. An exponent value exp scales the mantissa (integer and fractional part) by 10exp. A hexadecimal floating-point literal consists of a 0x or 0X prefix, an integer part (hexadecimal digits), a radix point, a fractional part (hexadecimal digits), and an exponent part (p or P followed by an optional sign and decimal digits). One of the integer part or the fractional part may be elided; the radix point may be elided as well, but the exponent part is required. (This syntax matches the one given in IEEE 754-2008 §5.12.3.) An exponent value exp scales the mantissa (integer and fractional part) by 2exp. For readability, an underscore character _ may appear after a base prefix or between successive digits; such underscores do not change the literal value. ```go float_lit = decimal_float_lit | hex_float_lit . decimal_float_lit = decimal_digits "." [ decimal_digits ] [ decimal_exponent ] | decimal_digits decimal_exponent | "." decimal_digits [ decimal_exponent ] . decimal_exponent = ( "e" | "E" ) [ "+" | "-" ] decimal_digits . hex_float_lit = "0" ( "x" | "X" ) hex_mantissa hex_exponent . hex_mantissa = [ "_" ] hex_digits "." [ hex_digits ] | [ "_" ] hex_digits | "." hex_digits . hex_exponent = ( "p" | "P" ) [ "+" | "-" ] decimal_digits . ``` ```go 0. 72.40 072.40 // == 72.40 2.71828 1.e+0 6.67428e-11 1E6 .25 .12345E+5 1_5. // == 15.0 0.15e+0_2 // == 15.0 0x1p-2 // == 0.25 0x2.p10 // == 2048.0 0x1.Fp+0 // == 1.9375 0X.8p-0 // == 0.5 0X_1FFFP-16 // == 0.1249847412109375 0x15e-2 // == 0x15e - 2 (integer subtraction) 0x.p1 // invalid: mantissa has no digits 1p-2 // invalid: p exponent requires hexadecimal mantissa 0x1.5e-2 // invalid: hexadecimal mantissa requires p exponent 1_.5 // invalid: _ must separate successive digits 1._5 // invalid: _ must separate successive digits 1.5_e1 // invalid: _ must separate successive digits 1.5e_1 // invalid: _ must separate successive digits 1.5e1_ // invalid: _ must separate successive digits ``` ### Rational literals Rational literals in XGo come in two forms: * **Rational integers**: An integer followed by the suffix `r`. * **Fractions**: A numerator, division operator `/`, denominator, followed by the suffix `r` ``` rational_lit = rational_int | rational_frac . rational_int = int_lit "r" . rational_frac = int_lit "/" int_lit "r" . ``` Examples: ```sh 1r # Rational integer 1 2/3r # Fraction 2/3 ``` ### Imaginary literals An imaginary literal represents the imaginary part of a [complex constant](#constants). It consists of an [integer](#integer-literals) or [floating-point](#floating-point-literals) literal followed by the lowercase letter _i_. The value of an imaginary literal is the value of the respective integer or floating-point literal multiplied by the imaginary unit _i_. ```go imaginary_lit = (decimal_digits | int_lit | float_lit) "i" . ``` For backward compatibility, an imaginary literal's integer part consisting entirely of decimal digits (and possibly underscores) is considered a decimal integer, even if it starts with a leading `0`. ```go 0i 0123i // == 123i for backward-compatibility 0o123i // == 0o123 * 1i == 83i 0xabci // == 0xabc * 1i == 2748i 0.i 2.71828i 1.e+0i 6.67428e-11i 1E6i .25i .12345E+5i 0x1p-2i // == 0x1p-2 * 1i == 0.25i ``` ### Boolean literals The boolean truth values are represented by the [predeclared constants](#constants) `true` and `false`. ```go true false ``` ### Rune literals A rune literal represents a [rune constant](#constants), an integer value identifying a Unicode code point. A rune literal is expressed as one or more characters enclosed in single quotes, as in `'x'` or `'\n'`. Within the quotes, any character may appear except newline and unescaped single quote. A single quoted character represents the Unicode value of the character itself, while multi-character sequences beginning with a backslash encode values in various formats. The simplest form represents the single character within the quotes; since XGo source text is Unicode characters encoded in UTF-8, multiple UTF-8-encoded bytes may represent a single integer value. For instance, the literal `'a'` holds a single byte representing a literal a, Unicode `U+0061`, value 0x61, while `'ä'` holds two bytes (0xc3 0xa4) representing a literal a-dieresis, `U+00E4`, value 0xe4. Several backslash escapes allow arbitrary values to be encoded as ASCII text. There are four ways to represent the integer value as a numeric constant: `\x` followed by exactly two hexadecimal digits; `\u` followed by exactly four hexadecimal digits; `\U` followed by exactly eight hexadecimal digits, and a plain backslash `\` followed by exactly three octal digits. In each case the value of the literal is the value represented by the digits in the corresponding base. Although these representations all result in an integer, they have different valid ranges. Octal escapes must represent a value between 0 and 255 inclusive. Hexadecimal escapes satisfy this condition by construction. The escapes `\u` and `\U` represent Unicode code points so within them some values are illegal, in particular those above 0x10FFFF and surrogate halves. After a backslash, certain single-character escapes represent special values: ``` \a U+0007 alert or bell \b U+0008 backspace \f U+000C form feed \n U+000A line feed or newline \r U+000D carriage return \t U+0009 horizontal tab \v U+000B vertical tab \\ U+005C backslash \' U+0027 single quote (valid escape only within rune literals) \" U+0022 double quote (valid escape only within string literals) ``` An unrecognized character following a backslash in a rune literal is illegal. ```go rune_lit = "'" ( unicode_value | byte_value ) "'" . unicode_value = unicode_char | little_u_value | big_u_value | escaped_char . byte_value = octal_byte_value | hex_byte_value . octal_byte_value = `\` octal_digit octal_digit octal_digit . hex_byte_value = `\` "x" hex_digit hex_digit . little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit . big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit . escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) . ``` ```go 'a' 'ä' '本' '\t' '\000' '\007' '\377' '\x07' '\xff' '\u12e4' '\U00101234' '\'' // rune literal containing single quote character 'aa' // illegal: too many characters '\k' // illegal: k is not recognized after a backslash '\xa' // illegal: too few hexadecimal digits '\0' // illegal: too few octal digits '\400' // illegal: octal value over 255 '\uDFFF' // illegal: surrogate half '\U00110000' // illegal: invalid Unicode code point ``` ### String literals A string literal represents a [string constant](#constants) obtained from concatenating a sequence of characters. There are two forms: raw string literals and interpreted string literals. Raw string literals are character sequences between back quotes, as in \`foo\`. Within the quotes, any character may appear except back quote. The value of a raw string literal is the string composed of the uninterpreted (implicitly UTF-8-encoded) characters between the quotes; in particular, backslashes have no special meaning and the string may contain newlines. Carriage return characters (`'\r'`) inside raw string literals are discarded from the raw string value. Interpreted string literals are character sequences between double quotes, as in `"bar"`. Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal, with backslash escapes interpreted as they are in [rune literals](#rune-literals) (except that `\'` is illegal and `\"` is legal), with the same restrictions. The three-digit octal (`\nnn`) and two-digit hexadecimal (`\xnn`) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multi-byte) UTF-8 encoding of individual characters. Thus inside a string literal `\377` and `\xFF` represent a single byte of value 0xFF=255, while `ÿ`, `\u00FF`, `\U000000FF` and `\xc3\xbf` represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character `U+00FF`. ```go string_lit = raw_string_lit | interpreted_string_lit . raw_string_lit = "`" { unicode_char | newline } "`" . interpreted_string_lit = `"` { unicode_value | byte_value } `"` . ``` ```go `abc` // same as "abc" `\n \n` // same as "\\n\n\\n" "\n" "\"" // same as `"` "Hello, world!\n" "日本語" "\u65e5本\U00008a9e" "\xff\u00FF" "\uD800" // illegal: surrogate half "\U00110000" // illegal: invalid Unicode code point ``` These examples all represent the same string: ```go "日本語" // UTF-8 input text `日本語` // UTF-8 input text as a raw literal "\u65e5\u672c\u8a9e" // the explicit Unicode code points "\U000065e5\U0000672c\U00008a9e" // the explicit Unicode code points "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" // the explicit UTF-8 bytes ``` If the source code represents a character as two code points, such as a combining form involving an accent and a letter, the result will be an error if placed in a rune literal (it is not a single code point), and will appear as two code points if placed in a string literal. ### Special literals TODO ```go nil iota ``` ## Constants There are _boolean constants_, _rune constants_, _integer constants_, _floating-point constants_, _complex constants_, and _string constants_. Rune, integer, floating-point, and complex constants are collectively called _numeric constants_. A constant value is represented by a [rune](#rune-literals), [integer](#integer-literals), [floating-point](#floating-point-literals), [imaginary](#imaginary-literals), [boolean](#boolean-literals) or [string](#string-literals) literal, an identifier denoting a constant, a [constant expression](#constant-expressions), a [conversion](#conversions) with a result that is a constant, or the result value of some built-in functions such as `min` or `max` applied to constant arguments, `unsafe.Sizeof` applied to [certain values](), `cap` or `len` applied to [some expressions](), `real` and `imag` applied to a complex constant and complex applied to numeric constants. The boolean truth values are represented by the predeclared constants `true` and `false`. The predeclared identifier `iota` denotes an integer constant. Not all literals are constants. For example: ```sh nil 1r 2/3r ``` In general, complex constants are a form of [constant expression](#constant-expressions) and are discussed in that section. Numeric constants represent exact values of arbitrary precision and do not overflow. Consequently, there are no constants denoting the IEEE-754 negative zero, infinity, and not-a-number values. Constants may be [typed](#types) or _untyped_. Literal constants (including `true`, `false`, `iota`), and certain [constant expressions]() containing only untyped constant operands are untyped. A constant may be given a type explicitly by a [constant declaration]() or [conversion](#conversions), or implicitly when used in a [variable declaration]() or an [assignment statement]() or as an operand in an [expression](#expressions). It is an error if the constant value cannot be [represented]() as a value of the respective type. An untyped constant has a _default type_ which is the type to which the constant is implicitly converted in contexts where a typed value is required, for instance, in a [short variable declaration]() such as `i := 0` where there is no explicit type. The default type of an untyped constant is `bool`, `rune`, `int`, `float64`, `complex128`, or `string` respectively, depending on whether it is a boolean, rune, integer, floating-point, complex, or string constant. ## Variables A variable is a storage location for holding a value. The set of permissible values is determined by the variable's [type](#types). A [variable declaration]() or, for function parameters and results, the signature of a [function declaration]() or [function literal]() reserves storage for a named variable. Calling the built-in function [new]() or taking the address of a [composite literal]() allocates storage for a variable at run time. Such an anonymous variable is referred to via a (possibly implicit) [pointer indirection](#address-operators). _Structured_ variables of [array](#array-types), [slice](#slice-types), and [class](#classes) types have elements and fields that may be [addressed](#address-operators) individually. Each such element acts like a variable. The _static type_ (or just _type_) of a variable is the type given in its declaration, the type provided in the new call or composite literal, or the type of an element of a class variable. Variables of interface type also have a distinct _dynamic type_, which is the (non-interface) type of the value assigned to the variable at run time (unless the value is the predeclared identifier `nil`, which has no type). The dynamic type may vary during execution but values stored in interface variables are always [assignable]() to the static type of the variable. ```go var x any // x is nil and has static type any var v *T // v has value nil, static type *T x = 42 // x has value 42 and dynamic type int x = v // x has value (*T)(nil) and dynamic type *T ``` A variable's value is retrieved by referring to the variable in an [expression](#expressions); it is the most recent value [assigned]() to the variable. If a variable has not yet been assigned a value, its value is the [zero value]() for its type. ## Types A type determines a set of values together with operations and methods specific to those values. A type may be denoted by a _type name_. A type may also be specified using a type literal, which composes a type from existing types. ```go Type = TypeName [ TypeArgs ] | TypeLit | "(" Type ")" . TypeName = identifier | QualifiedIdent . TypeArgs = "[" TypeList [ "," ] "]" . TypeList = Type { "," Type } . TypeLit = ArrayType | TupleType | PointerType | FunctionType | InterfaceType | SliceType | MapType . // TODO: check this ``` The language [predeclares]() certain type names. Others are introduced with [type declarations](#type-declarations). _Composite types_—array, tuple, pointer, function, interface, slice, map—may be constructed using type literals. Predeclared types and defined types are called _named types_. An alias denotes a named type if the type given in the alias declaration is a named type. ### Boolean types A _boolean type_ represents the set of Boolean truth values denoted by the predeclared constants true and false. The predeclared boolean type is `bool`; it is a defined type. ```go bool ``` ### Numeric types An _integer_, _floating-point_, _rational_ or _complex_ type represents the set of integer, floating-point, or complex values, respectively. They are collectively called _numeric types_. The predeclared architecture-independent numeric types are: ```go uint8 // the set of all unsigned 8-bit integers (0 to 255) uint16 // the set of all unsigned 16-bit integers (0 to 65535) uint32 // the set of all unsigned 32-bit integers (0 to 4294967295) uint64 // the set of all unsigned 64-bit integers (0 to 18446744073709551615) int8 // the set of all signed 8-bit integers (-128 to 127) int16 // the set of all signed 16-bit integers (-32768 to 32767) int32 // the set of all signed 32-bit integers (-2147483648 to 2147483647) int64 // the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807) float32 // the set of all IEEE-754 32-bit floating-point numbers float64 // the set of all IEEE-754 64-bit floating-point numbers complex64 // the set of all complex numbers with float32 real and imaginary parts complex128 // the set of all complex numbers with float64 real and imaginary parts byte // alias for uint8 rune // alias for int32 ``` The value of an _n_-bit integer is n bits wide and represented using [two's complement arithmetic](https://en.wikipedia.org/wiki/Two's_complement). There is also a set of predeclared integer types with implementation-specific sizes: ```go uint // either 32 or 64 bits int // same size as uint uintptr // an unsigned integer large enough to store the uninterpreted bits of a pointer value ``` To avoid portability issues all numeric types are defined types and thus distinct except _byte_, which is an [alias]() for _uint8_, and _rune_, which is an alias for _int32_. Explicit conversions are required when different numeric types are mixed in an expression or assignment. For instance, _int32_ and _int_ are not the same type even though they may have the same size on a particular architecture. TODO: ```go bigint // TODO bigrat // TODO ``` ### String types A _string type_ represents the set of string values. A string value is a (possibly empty) sequence of bytes. The number of bytes is called the length of the string and is never negative. Strings are immutable: once created, it is impossible to change the contents of a string. The predeclared string type is `string`; it is a defined type. ```go string ``` The length of a string `s` can be discovered using the built-in function [len](). The length is a compile-time constant if the string is a constant. A string's bytes can be accessed by integer [indices]() `0` through `len(s)-1`. It is illegal to take the address of such an element; if `s[i]` is the i'th byte of a string, `&s[i]` is invalid. ### Array types An array is a numbered sequence of elements of a single type, called the element type. The number of elements is called the length of the array and is never negative. ```go ArrayType = "[" ArrayLength "]" ElementType . ArrayLength = Expression . ElementType = Type . ``` The length is part of the array's type; it must evaluate to a non-negative [constant](#constants) [representable]() by a value of type int. The length of array `a` can be discovered using the built-in function [len](). The elements can be addressed by integer [indices]() `0` through `len(a)-1`. Array types are always one-dimensional but may be composed to form multi-dimensional types. ```go [32]byte [2*N](x int32, y int32) [1000]*float64 [3][5]int [2][2][2]float64 // same as [2]([2]([2]float64)) ``` An array type T may not have an element of type T, or of a type containing T as a component, directly or indirectly, if those containing types are only array or tuple types. ```go // invalid array types type ( T1 [10]T1 // element type of T1 is T1 T2 [10](f T2) // T2 contains T2 as component of a tuple T3 [10]T4 // T3 contains T3 as component of a tuple in T4 T4 (f [10]T3) // T4 contains T4 as component of array T3 in a tuple ) // valid array types type ( T5 [10]*T5 // T5 contains T5 as component of a pointer T6 [10]func() T6 // T6 contains T6 as component of a function type T7 [10](f []T7) // T7 contains T7 as component of a slice in a tuple ) ``` ### Pointer types A _pointer_ type denotes the set of all pointers to [variables](#variables) of a given type, called the base type of the pointer. The value of an uninitialized pointer is `nil`. ```go PointerType = "*" BaseType . BaseType = Type . ``` ```go *Point *[4]int ``` ### Slice types A _slice_ is a descriptor for a contiguous segment of an _underlying array_ and provides access to a numbered sequence of elements from that array. A slice type denotes the set of all slices of arrays of its element type. The number of elements is called the length of the slice and is never negative. The value of an uninitialized slice is `nil`. ```go SliceType = "[" "]" ElementType . ``` The length of a slice `s` can be discovered by the built-in function [len](); unlike with arrays it may change during execution. The elements can be addressed by integer [indices]() `0` through `len(s)-1`. The slice index of a given element may be less than the index of the same element in the underlying array. A slice, once initialized, is always associated with an underlying array that holds its elements. A slice therefore shares storage with its array and with other slices of the same array; by contrast, distinct arrays always represent distinct storage. The array underlying `a` slice may extend past the end of the slice. The capacity is a measure of that extent: it is the sum of the length of the slice and the length of the array beyond the slice; a slice of length up to that capacity can be created by [slicing]() a new one from the original slice. The capacity of a slice a can be discovered using the built-in function `cap(a)`. A new, initialized slice value for a given element type `T` may be made using the built-in function [make](), which takes a slice type and parameters specifying the length and optionally the capacity. A slice created with make always allocates a new, hidden array to which the returned slice value refers. That is, executing ```go make([]T, length, capacity) ``` produces the same slice as allocating an array and [slicing]() it, so these two expressions are equivalent: ``` make([]int, 50, 100) new([100]int)[0:50] ``` Like arrays, slices are always one-dimensional but may be composed to construct higher-dimensional objects. With arrays of arrays, the inner arrays are, by construction, always the same length; however with slices of slices (or arrays of slices), the inner lengths may vary dynamically. Moreover, the inner slices must be initialized individually. ### Tuple types A _tuple_ type is a lightweight data structure for grouping multiple values together. Tuple types are syntactic sugar for anonymous structs with ordinal field names (`X_0`, `X_1`, etc.). Named fields in tuples serve as compile-time aliases for better code readability but map to ordinal fields at runtime. ```go TupleType = Parameters . ``` A tuple type is denoted by a parenthesized, comma-separated list of types, optionally with names for the elements: ```go // Empty tuple (equivalent to struct{}) () // Anonymous tuples (elements accessed by ordinal: .0, .1, .2) (int, string) (int, string, bool) // Named tuples (compile-time names for readability, runtime uses ordinal fields) type Point (x int, y int) type Person (name string, age int) // Type shorthand syntax (same as: x int, y int, z int) type Point3D (x, y, z int) ``` **Single-Element Degeneracy**: A tuple with a single element `(T)` or `(value T)` degenerates to `T` itself, not a tuple. #### Tuple Construction Tuples are constructed using **function-style syntax**: ```go // Positional construction p := Point(10, 20) result := (42, "success", true) // Keyword argument construction (using = for clarity) p := Point(x = 10, y = 20) person := Person(name = "Alice", age = 30) ``` #### Field Access Tuple fields can be accessed by: - **Named fields**: `p.x`, `p.y` (using declared names) - **Ordinal notation**: `p.0`, `p.1` (numeric shorthand) ```go type Point (x int, y int) p := Point(10, 20) echo p.x // 10 (using compile-time name) echo p.0 // 10 (using ordinal index, equivalent to p.x) echo p.y // 20 echo p.1 // 20 ``` #### Type Identity Two tuple types are identical if they have the same number of elements and corresponding element types are identical. Named fields are not part of type identity: ```go type Point (x int, y int) type Coord (a int, b int) // Point and Coord have identical underlying types (both map to struct{ X_0 int; X_1 int }) // but are different named types ``` #### Tuples in Composite Types Tuples can be used as element types in arrays, slices, maps, and channels: ```go // Tuple as map value type var cache map[string](int, bool) // Tuple as slice element type var pairs [](string, int) // Tuple as channel element type var ch chan (int, error) ``` #### Recursive Type Restrictions A tuple type `T` may not contain an element of type `T`, or of a type containing `T` as a component, directly or indirectly, if those containing types are only array or tuple types. ```go // invalid tuple types type ( T1 (T1, int) // T1 contains an element of T1 T2 ([10]T2, string) // T2 contains T2 as component of an array ) // valid tuple types type ( T3 (*T3, int) // T3 contains T3 as component of a pointer T4 (func() T4, string) // T4 contains T4 as component of a function type T5 ([]T5, int) // T5 contains T5 as component of a slice ) ``` ### Map types A _map_ is an unordered group of elements of one type, called the element type, indexed by a set of unique _keys_ of another type, called the key type. The value of an uninitialized map is `nil`. ```go MapType = "map" "[" KeyType "]" ElementType . KeyType = Type . ``` The comparison operators `==` and `!=` must be fully defined for operands of the key type; thus the key type must not be a function, map, or slice. If the key type is an interface type, these comparison operators must be defined for the dynamic key values; failure will cause a run-time panic. ```go map[string]int map[*T](x float64, y float64) map[string]any ``` The number of map elements is called its length. For a map `m`, it can be discovered using the built-in function [len]() and may change during execution. Elements may be added during execution using [assignments]() and retrieved with [index expressions](); they may be removed with the [delete]() and [clear]() built-in function. A new, empty map value is made using the built-in function [make](), which takes the map type and an optional capacity hint as arguments: ```go make(map[string]int) make(map[string]int, 100) ``` The initial capacity does not bound its size: maps grow to accommodate the number of items stored in them, with the exception of nil maps. A nil map is equivalent to an empty map except that no elements may be added. ### Function types A _function_ type denotes the set of all functions with the same parameter and result types. The value of an uninitialized variable of function type is `nil`. ```go FunctionType = "func" Signature . Signature = Parameters [ Result ] . Result = Parameters | Type . Parameters = "(" [ ParameterList [ "," ] ] ")" . ParameterList = ParameterDecl { "," ParameterDecl } . ParameterDecl = [ IdentifierList ] [ "..." ] Type . ``` Within a list of parameters or results, the names (IdentifierList) must either all be present or all be absent. If present, each name stands for one item (parameter or result) of the specified type and all non-[blank]() names in the signature must be [unique](). If absent, each type stands for one item of that type. Parameter and result lists are always parenthesized except that if there is exactly one unnamed result it may be written as an unparenthesized type. The final incoming parameter in a function signature may have a type prefixed with `...`. A function with such a parameter is called _variadic_ and may be invoked with zero or more arguments for that parameter. ```go func() func(x int) int func(a, _ int, z float32) bool func(a, b int, z float32) (bool) func(prefix string, values ...int) func(a, b int, z float64, opt ...any) (success bool) func(int, int, float64) (float64, *[]int) func(n int) func(p *T) ``` ### Interface types An interface type specifies a [method set]() called its _interface_. A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to _implement the interface_. The value of an uninitialized variable of interface type is `nil`. ```go InterfaceType = "interface" "{" { ( MethodSpec | InterfaceTypeName ) ";" } "}" . MethodSpec = MethodName Signature . MethodName = identifier . InterfaceTypeName = TypeName . ``` An interface type may specify methods _explicitly_ through method specifications, or it may _embed_ methods of other interfaces through interface type names. ```go // A simple File interface. interface { Read([]byte) (int, error) Write([]byte) (int, error) Close() error } ``` The name of each explicitly specified method must be [unique]() and not [blank](). ```go interface { String() string String() string // illegal: String not unique _(x int) // illegal: method must have non-blank name } ``` More than one type may implement an interface. For instance, if two types `S1` and `S2` have the method set ```go func Read(p []byte) (n int, err error) func Write(p []byte) (n int, err error) func Close() error ``` (where `T` stands for either `S1` or `S2`) then the `File` interface is implemented by both `S1` and `S2`, regardless of what other methods `S1` and `S2` may have or share. A type implements any interface comprising any subset of its methods and may therefore implement several distinct interfaces. For instance, all types implement the _empty interface_: ```go interface{} ``` Similarly, consider this interface specification, which appears within a [type declaration](#type-declarations) to define an interface called `Locker`: ```go type Locker interface { Lock() Unlock() } ``` If `S1` and `S2` also implement ```go func Lock() { … } func Unlock() { … } ``` they implement the `Locker` interface as well as the `File` interface. An interface `T` may use a (possibly qualified) interface type name `E` in place of a method specification. This is called _embedding_ interface `E` in `T`. The method set of `T` is the union of the method sets of T’s explicitly declared methods and of T’s embedded interfaces. ```go type Reader interface { Read(p []byte) (n int, err error) Close() error } type Writer interface { Write(p []byte) (n int, err error) Close() error } // ReadWriter's methods are Read, Write, and Close. type ReadWriter interface { Reader // includes methods of Reader in ReadWriter's method set Writer // includes methods of Writer in ReadWriter's method set } ``` A union of method sets contains the (exported and non-exported) methods of each method set exactly once, and methods with the [same]() names must have [identical]() signatures. ```go type ReadCloser interface { Reader // includes methods of Reader in ReadCloser's method set Close() // illegal: signatures of Reader.Close and Close are different } ``` An interface type `T` may not embed itself or any interface type that embeds `T`, recursively. ```go // illegal: Bad cannot embed itself type Bad interface { Bad } // illegal: Bad1 cannot embed itself using Bad2 type Bad1 interface { Bad2 } type Bad2 interface { Bad1 } ``` #### Built-in interfaces The following interfaces are predeclared in XGo: ```go any // alias for interface{}, the empty interface error // interface for error handling ``` ##### The any interface The predeclared type `any` is an alias for the empty interface `interface{}`. It represents the set of all types and is useful when you need to work with values of any type: ```go var x any // x is nil and has static type any x = 42 // x has value 42 and dynamic type int x = "hello" // x has value "hello" and dynamic type string ``` ##### The error interface The predeclared type `error` is defined as: ```go type error interface { Error() string } ``` It is the conventional interface for representing an error condition, with the nil value representing no error. For instance, a function to read data from a file might be defined: ```go func Read(f *File, b []byte) (n int, err error) ``` Any type that implements an `Error()` method returning a string satisfies the `error` interface. ### Classes TODO (classfile) ## Properties of types and values ### Underlying types Each type T has an _underlying type_: If T is one of the predeclared boolean, numeric, or string types, or a type literal, the corresponding underlying type is T itself. Otherwise, T's underlying type is the underlying type of the type to which T refers in its declaration. ```go type ( A1 = string A2 = A1 ) type ( B1 string B2 B1 B3 []B1 B4 B3 ) ``` The underlying type of `string`, `A1`, `A2`, `B1`, and `B2` is `string`. The underlying type of `[]B1`, `B3`, and `B4` is `[]B1`. ### Type identity Two types are either _identical_ or _different_. A [named type](#types) is always different from any other type. Otherwise, two types are identical if their [underlying type](#underlying-types) literals are structurally equivalent; that is, they have the same literal structure and corresponding components have identical types. In detail: * Two array types are identical if they have identical element types and the same array length. * Two slice types are identical if they have identical element types. * Two struct types are identical if they have the same sequence of fields, and if corresponding fields have the same names, and identical types, and identical tags. [Non-exported](#exported-identifiers) field names from different packages are always different. * Two pointer types are identical if they have identical base types. * Two function types are identical if they have the same number of parameters and result values, corresponding parameter and result types are identical, and either both functions are variadic or neither is. Parameter and result names are not required to match. * Two interface types are identical if they define the same type set. * Two map types are identical if they have identical key and element types. Given the declarations ```go type ( A0 = []string A1 = A0 A2 = struct{ a, b int } A3 = int A4 = func(A3, float64) *A0 A5 = func(x int, _ float64) *[]string B0 A0 B1 []string B2 struct{ a, b int } B3 struct{ a, c int } B4 func(int, float64) *B0 B5 func(x int, y float64) *A1 C0 = B0 ) ``` these types are identical: ```go A0, A1, and []string A2 and struct{ a, b int } A3 and int A4, func(int, float64) *[]string, and A5 B0 and C0 []int and []int struct{ a, b *B5 } and struct{ a, b *B5 } func(x int, y float64) *[]string, func(int, float64) (result *[]string), and A5 ``` `B0` and `B1` are different because they are new types created by distinct [type definitions](#type-definitions); `func(int, float64) *B0` and `func(x int, y float64) *[]string` are different because `B0` is different from `[]string`. ### Assignability A value x of type V is _assignable_ to a [variable](#variables) of type T ("x is assignable to T") if one of the following conditions applies: * V and T are identical. * V and T have identical [underlying types](#underlying-types) but are not type parameters and at least one of V or T is not a [named type](#types). * T is an interface type, and x [implements]() T. * x is the predeclared identifier nil and T is a pointer, function, slice, map, or interface type. * x is an untyped [constant](#constants) [representable](#representability) by a value of type T. ### Representability A [constant](#constants) x is _representable_ by a value of type T if one of the following conditions applies: * x is in the set of values [determined](#types) by T. * T is a floating-point type and x can be rounded to T's precision without overflow. Rounding uses IEEE 754 round-to-even rules but with an IEEE negative zero further simplified to an unsigned zero. Note that constant values never result in an IEEE negative zero, NaN, or infinity. * T is a complex type, and x's [components](#manipulating-complex-numbers) `real(x)` and `imag(x)` are representable by values of T's component type (`float32` or `float64`). ```go x T x is representable by a value of T because 'a' byte 97 is in the set of byte values 97 rune rune is an alias for int32, and 97 is in the set of 32-bit integers "foo" string "foo" is in the set of string values 1024 int16 1024 is in the set of 16-bit integers 42.0 byte 42 is in the set of unsigned 8-bit integers 1e10 uint64 10000000000 is in the set of unsigned 64-bit integers 2.718281828459045 float32 2.718281828459045 rounds to 2.7182817 which is in the set of float32 values -1e-1000 float64 -1e-1000 rounds to IEEE -0.0 which is further simplified to 0.0 0i int 0 is an integer value (42 + 0i) float32 42.0 (with zero imaginary part) is in the set of float32 values ``` ```go x T x is not representable by a value of T because 0 bool 0 is not in the set of boolean values 'a' string 'a' is a rune, it is not in the set of string values 1024 byte 1024 is not in the set of unsigned 8-bit integers -1 uint16 -1 is not in the set of unsigned 16-bit integers 1.1 int 1.1 is not an integer value 42i float32 (0 + 42i) is not in the set of float32 values 1e1000 float64 1e1000 overflows to IEEE +Inf after rounding ``` ### Method sets The _method set_ of a type determines the methods that can be [called](#commands-and-calls) on an [operand]() of that type. Every type has a (possibly empty) method set associated with it: * The method set of a [defined type](#type-definitions) T consists of all [methods]() declared with receiver type T. * The method set of a pointer to a defined type T (where T is neither a pointer nor an interface) is the set of all methods declared with receiver *T or T. * The method set of an [interface type](#interface-types) is the intersection of the method sets of each type in the interface's [type set](#interface-types) (the resulting method set is usually just the set of declared methods in the interface). Further rules apply to structs (and pointer to structs) containing embedded fields, as described in the section on [struct types](#struct-types). Any other type has an empty method set. In a method set, each method must have a [unique](#uniqueness-of-identifiers) non-[blank](#blank-identifier) [method name](). ## Expressions An expression specifies the computation of a value by applying operators and functions to operands. ### Operands Operands denote the elementary values in an expression. An operand may be a literal, a (possibly [qualified]()) non-[blank](#blank-identifier) identifier denoting a [constant](#constant-declarations), [variable](#variable-declarations), or [function](#function-declarations), or a parenthesized expression. ```go Operand = Literal | OperandName [ TypeArgs ] . Literal = BasicLit | FunctionLit | TupleLit . BasicLit = int_lit | float_lit | imaginary_lit | rune_lit | string_lit . OperandName = identifier | QualifiedIdent . ``` The [blank identifier](#blank-identifier) may appear as an operand only on the left-hand side of an [assignment statement](#assignment-statements). ### Qualified identifiers A _qualified identifier_ is an identifier qualified with a package name prefix. Both the package name and the identifier must not be [blank](#blank-identifier). ```go QualifiedIdent = PackageName "." identifier . ``` A qualified identifier accesses an identifier in a different package, which must be [imported](#import-declarations). The identifier must be [exported](#exported-identifiers) and declared in the [package block](#blocks) of that package. ```go math.Sin // denotes the Sin function in package math ``` ### Tuple literals Tuple literals construct tuple values using function-style syntax. Both anonymous and named tuples are created using parenthesized, comma-separated expressions. ```go TupleLit = "(" [ ExpressionList [ "," ] ] ")" . ``` #### Anonymous Tuple Literals Anonymous tuple literals create values of anonymous tuple types: ```go result := (42, "success", true) // creates a (int, string, bool) pair := (10, 20) // creates a (int, int) empty := () // creates an empty tuple () ``` #### Named Tuple Construction Named tuple types are instantiated using function-style call syntax: **Positional Arguments**: ```go type Point (x int, y int) p := Point(10, 20) // positional construction type Person (name string, age int) alice := Person("Alice", 30) // positional construction ``` **Keyword Arguments**: Keyword arguments use `=` for assignment and provide clarity when initializing tuples with many fields: ```go p := Point(x = 10, y = 20) // keyword argument construction person := Person(name = "Bob", age = 25) // keyword argument construction ``` Keyword arguments can be mixed, but all positional arguments must come before keyword arguments: ```go // This is valid p := Point(10, y = 20) // This is invalid // p := Point(x = 10, 20) // error: positional arg after keyword arg ``` #### Zero Values Calling a tuple type with no arguments returns the zero value for that type: ```go type Point (x int, y int) zero := Point() // equivalent to Point(0, 0) ``` #### Type Inference The type of a tuple literal is inferred from context when possible: ```go func process(p (int, string)) { // ... } process((42, "hello")) // tuple literal type inferred as (int, string) ``` #### Tuple Literals in Composite Types Tuple literals can be used as elements in slices, arrays, and maps: ```go pairs := [](string, int){ ("a", 1), ("b", 2), ("c", 3), } // In a map cache := map[string](int, bool){ "key1": (100, true), "key2": (200, false), } ``` ### Function literals A function literal represents an anonymous [function](#function-declarations). ```go FunctionLit = [ AnonymousParameters ] "=>" AnonymousFunctionBody . AnonymousParameters = SingleAnonymousParameter | MultipleAnonymousParameters . SingleAnonymousParameter = identifier . MultipleAnonymousParameters = "(" AnonymousParameterList ")" . AnonymousParameterList = identifier { "," identifier } . AnonymousFunctionBody = Expression | FunctionBody . ``` A anonymous function body can be a single expression. ```go x => math.Sin(x) (x, y) => x*x + y*y ``` It can also be the same as a normal function body. The above examples are equivalent to ```go x => { return math.Sin(x) } (x, y) => { return x*x + y*y } ``` Anonymous functions are typically passed in as parameters for function calls. ```go import "sort" a := [32, 100, 50, 6, 92] sort.Slice a, (i, j) => { return a[i] < b[j] } ``` Note: Both the parameter types and return value types of anonymous functions are omitted. XGo determines the parameter types and return value types of anonymous functions through type inference. Function literals are _closures_: they may refer to variables defined in a surrounding function. Those variables are then shared between the surrounding function and the function literal, and they survive as long as they are accessible. ### Commands and calls XGo supports two styles for function and method calls: the traditional function-call style with parentheses, and a command-style syntax without parentheses. #### Function-call style The traditional function-call syntax uses parentheses: **Syntax:** ```go CallOrConversion = Operand "(" [ ArgList ] [ "..." ] [ "," ] ")" . ArgList = (KwargExpr | LambdaExpr) { "," (KwargExpr | LambdaExpr) } . KwargExpr = IDENT "=" LambdaExpr . ``` Examples: ```go echo("Hello world") fmt.Println("Hello, world") ``` #### Command-style syntax XGo recommends command-style code where the function name is followed by a space and then arguments, without parentheses: ```go CommandStmt = IDENT [ "." IDENT ] [ SPACE ArgList ] [ "..." ] . ``` Examples: ```go echo "Hello world" fmt.Println "Hello, world" ``` Command-style calls can be qualified (with package or receiver name) or unqualified: ```go echo "unqualified call" fmt.Println "qualified call" os.Exit 1 ``` Variadic arguments are supported with the `...` operator: ```go echo elements... ``` Both styles are equivalent and can be used interchangeably. XGo prefers command-style for its cleaner, more natural appearance, similar to shell commands. The built-in function `echo` is provided as an alias for `println` to emphasize this command-oriented approach. #### Keyword arguments XGo supports keyword arguments (kwargs) in commands and calls, allowing arguments to be specified by parameter name. When calling functions with many parameters, you can use `key=value` syntax to make your code more expressive and command-line-style. #### Using kwargs with tuples You can use tuples or tuple pointers as keyword parameters, which provides type safety: ```go type Config (timeout, maxRetries int, debug bool) func run(task int, cfg Config?) { if cfg.timeout == 0 { cfg.timeout = 30 } if cfg.maxRetries == 0 { cfg.maxRetries = 3 } echo "timeout:", cfg.timeout, "maxRetries:", cfg.maxRetries, "debug:", cfg.debug echo "task:", task } run 100, timeout = 60, maxRetries = 5 run 200 ``` #### Using kwargs with maps You also can use maps as keyword parameters: ```go func process(opts map[string]any?, args ...any) { if name, ok := opts["name"]; ok { echo "name:", name } if age, ok := opts["age"]; ok { echo "age:", age } echo "args:", args } process name = "Ken", age = 17 // keyword parameters only process "extra", 1, name = "Ken", age = 17 // variadic parameters first, then keyword parameters process // all parameters optional ``` **Key rules:** - The keyword parameter must be an optional parameter. - The keyword parameter must be the last parameter (without variadic) or second-to-last (with variadic). - When calling a function, keyword arguments must be placed after all normal parameters (including variadic parameters). This might seem inconsistent with the order of keyword and variadic parameters in a function declaration, but that's the rule. ### Built-in functions TODO ### Operators Operators combine operands into expressions. Binary operators: ```go || && == != < <= > >= + - * / % | & ^ &^ << >> ``` Unary operators: ```go + - ! ^ * & ``` #### Operator precedence _Unary operators_ have the highest precedence. As the ++ and -- operators form statements, not expressions, they fall outside the operator hierarchy. As a consequence, statement *p++ is the same as (*p)++. There are five precedence levels for _binary operators_. Multiplication operators bind strongest, followed by addition operators, comparison operators, && (logical AND), and finally || (logical OR): ``` Precedence Operator 5 * / % << >> & &^ 4 + - | ^ 3 == != < <= > >= 2 && 1 || ``` Binary operators of the same precedence associate from left to right. For instance, `x / y * z` is the same as `(x / y) * z`. ```go +x // x 42 + a - b // (42 + a) - b 23 + 3*x[i] // 23 + (3 * x[i]) x <= f() // x <= f() ^a >> b // (^a) >> b f() || g() // f() || g() x == y+1 && <-chanInt > 0 // (x == (y+1)) && ((<-chanInt) > 0) ``` #### Arithmetic operators _Arithmetic operators_ apply to numeric values and yield a result of the same type as the first operand. The four standard arithmetic operators (+, -, *, /) apply to [integer](), [floating-point](), [rational]() and [complex]() types; + also applies to [strings](). The bitwise logical and shift operators apply to integers only. ``` + sum integers (including bigint), floats, bigrat, complex values, strings - difference integers (including bigint), floats, bigrat, complex values * product integers (including bigint), floats, bigrat, complex values / quotient integers (including bigint), floats, bigrat, complex values % remainder integers (including bigint) & bitwise AND integers (including bigint) | bitwise OR integers (including bigint) ^ bitwise XOR integers (including bigint) &^ bit clear (AND NOT) integers (including bigint) << left shift integer << integer >= 0 >> right shift integer >> integer >= 0 ``` TODO #### Comparison operators _Comparison operators_ compare two operands and yield an untyped boolean value. ```go == equal != not equal < less <= less or equal > greater >= greater or equal ``` In any comparison, the first operand must be [assignable]() to the type of the second operand, or vice versa. The equality operators == and != apply to operands of comparable types. The ordering operators <, <=, >, and >= apply to operands of ordered types. These terms and the result of the comparisons are defined as follows: * Boolean types are comparable. Two boolean values are equal if they are either both true or both false. * Integer types are comparable and ordered. Two integer values are compared in the usual way. * Floating-point types are comparable and ordered. Two floating-point values are compared as defined by the IEEE-754 standard. * Complex types are comparable. Two complex values u and v are equal if both real(u) == real(v) and imag(u) == imag(v). * String types are comparable and ordered. Two string values are compared lexically byte-wise. * Pointer types are comparable. Two pointer values are equal if they point to the same variable or if both have value `nil`. Pointers to distinct [zero-size]() variables may or may not be equal. * Interface types are comparable. Two interface values are equal if they have [identical]() dynamic types and equal dynamic values or if both have value `nil`. * A value x of non-interface type X and a value t of interface type T can be compared if type X is comparable and X [implements]() T. They are equal if t's dynamic type is identical to X and t's dynamic value is equal to x. * Array types are comparable if their array element types are comparable. Two array values are equal if their corresponding element values are equal. The elements are compared in ascending index order, and comparison stops as soon as two element values differ (or all elements have been compared). A comparison of two interface values with identical dynamic types causes a [run-time panic]() if that type is not comparable. This behavior applies not only to direct interface value comparisons but also when comparing arrays of interface values or structs with interface-valued fields. Slice, map, and function types are not comparable. However, as a special case, a slice, map, or function value may be compared to the predeclared identifier `nil`. Comparison of pointer, and interface values to `nil` is also allowed and follows from the general rules above. #### Logical operators Logical operators apply to [boolean]() values and yield a result of the same type as the operands. The left operand is evaluated, and then the right if the condition requires it. ``` && conditional AND p && q is "if p then q else false" || conditional OR p || q is "if p then true else q" ! NOT !p is "not p" ``` #### Address operators For an operand x of type T, the address operation &x generates a pointer of type *T to x. The operand must be addressable, that is, either a variable, pointer indirection, or slice indexing operation; or a field selector of an addressable struct operand; or an array indexing operation of an addressable array. As an exception to the addressability requirement, x may also be a (possibly parenthesized) [composite literal](). If the evaluation of x would cause a [run-time panic](), then the evaluation of &x does too. For an operand x of pointer type *T, the pointer indirection *x denotes the [variable]() of type T pointed to by x. If x is nil, an attempt to evaluate *x will cause a [run-time panic](). ```go &x &a[f(2)] &Point{2, 3} *p *pf(x) var x *int = nil *x // causes a run-time panic &*x // causes a run-time panic ``` #### Conversions A _conversion_ changes the [type](#types) of an expression to the type specified by the conversion. A conversion may appear literally in the source, or it may be _implied_ by the context in which an expression appears. An _explicit conversion_ is an expression of the form `T(x)` where `T` is a type and `x` is an expression that can be converted to type `T`. ```go T(x) ``` If the type starts with the operator * or <-, or if the type starts with the keyword func and has no result list, it must be parenthesized when necessary to avoid ambiguity: ```go *Point(p) // same as *(Point(p)) (*Point)(p) // p is converted to *Point func()(x) // function signature func() x (func())(x) // x is converted to func() (func() int)(x) // x is converted to func() int func() int(x) // x is converted to func() int (unambiguous) ``` A [constant]() value `x` can be converted to type `T` if `x` is [representable]() by a value of `T`. As a special case, an integer constant `x` can be explicitly converted to a [string type]() using the [same rule]() as for non-constant `x`. Converting a constant to a type yields a typed constant. ```go uint(iota) // iota value of type uint float32(2.718281828) // 2.718281828 of type float32 complex128(1) // 1.0 + 0.0i of type complex128 float32(0.49999999) // 0.5 of type float32 float64(-1e-1000) // 0.0 of type float64 string('x') // "x" of type string string(0x266c) // "♬" of type string myString("foo" + "bar") // "foobar" of type myString string([]byte{'a'}) // not a constant: []byte{'a'} is not a constant (*int)(nil) // not a constant: nil is not a constant, *int is not a boolean, numeric, or string type int(1.2) // illegal: 1.2 cannot be represented as an int string(65.0) // illegal: 65.0 is not an integer constant ``` ##### Conversions between numeric types For the conversion of non-constant numeric values, the following rules apply: * When converting between [integer types](#numeric-types), if the value is a signed integer, it is sign extended to implicit infinite precision; otherwise it is zero extended. It is then truncated to fit in the result type's size. For example, if v := uint16(0x10F0), then uint32(int8(v)) == 0xFFFFFFF0. The conversion always yields a valid value; there is no indication of overflow. * When converting a [floating-point number](#numeric-types) to an integer, the fraction is discarded (truncation towards zero). * When converting an integer or floating-point number to a floating-point type, or a [complex number](#numeric-types) to another complex type, the result value is rounded to the precision specified by the destination type. For instance, the value of a variable x of type float32 may be stored using additional precision beyond that of an IEEE-754 32-bit number, but float32(x) represents the result of rounding x's value to 32-bit precision. Similarly, x + 0.1 may use more than 32 bits of precision, but float32(x + 0.1) does not. In all non-constant conversions involving floating-point or complex values, if the result type cannot represent the value the conversion succeeds but the result value is implementation-dependent. ##### Conversions to and from a string type TODO ##### Conversions from slice to array or array pointer TODO ### Constant expressions Constant expressions may contain only [constant](#constants) operands and are evaluated at compile time. Untyped boolean, numeric, and string constants may be used as operands wherever it is legal to use an operand of boolean, numeric, or string type, respectively. A constant [comparison](#comparison-operators) always yields an untyped boolean constant. If the left operand of a constant [shift expression](#operators) is an untyped constant, the result is an integer constant; otherwise it is a constant of the same type as the left operand, which must be of [integer type](#numeric-types). Any other operation on untyped constants results in an untyped constant of the same kind; that is, a boolean, integer, floating-point, complex, or string constant. If the untyped operands of a binary operation (other than a shift) are of different kinds, the result is of the operand's kind that appears later in this list: integer, rune, floating-point, complex. For example, an untyped integer constant divided by an untyped complex constant yields an untyped complex constant. ```go const a = 2 + 3.0 // a == 5.0 (untyped floating-point constant) const b = 15 / 4 // b == 3 (untyped integer constant) const c = 15 / 4.0 // c == 3.75 (untyped floating-point constant) const Θ float64 = 3/2 // Θ == 1.0 (type float64, 3/2 is integer division) const Π float64 = 3/2. // Π == 1.5 (type float64, 3/2. is float division) const d = 1 << 3.0 // d == 8 (untyped integer constant) const e = 1.0 << 3 // e == 8 (untyped integer constant) const f = int32(1) << 33 // illegal (constant 8589934592 overflows int32) const g = float64(2) >> 1 // illegal (float64(2) is a typed floating-point constant) const h = "foo" > "bar" // h == true (untyped boolean constant) const j = true // j == true (untyped boolean constant) const k = 'w' + 1 // k == 'x' (untyped rune constant) const l = "hi" // l == "hi" (untyped string constant) const m = string(k) // m == "x" (type string) const Σ = 1 - 0.707i // (untyped complex constant) const Δ = Σ + 2.0e-4 // (untyped complex constant) const Φ = iota*1i - 1/1i // (untyped complex constant) ``` Applying the built-in function `complex` to untyped integer, rune, or floating-point constants yields an untyped complex constant. ```go const ic = complex(0, c) // ic == 3.75i (untyped complex constant) const iΘ = complex(0, Θ) // iΘ == 1i (type complex128) ``` Constant expressions are always evaluated exactly; intermediate values and the constants themselves may require precision significantly larger than supported by any predeclared type in the language. The following are legal declarations: ```go const Huge = 1 << 100 // Huge == 1267650600228229401496703205376 (untyped integer constant) const Four int8 = Huge >> 98 // Four == 4 (type int8) ``` The divisor of a constant division or remainder operation must not be zero: ```go 3.14 / 0.0 // illegal: division by zero ``` The values of typed constants must always be accurately [representable]() by values of the constant type. The following constant expressions are illegal: ```go uint(-1) // -1 cannot be represented as a uint int(3.14) // 3.14 cannot be represented as an int int64(Huge) // 1267650600228229401496703205376 cannot be represented as an int64 Four * 300 // operand 300 cannot be represented as an int8 (type of Four) Four * 100 // product 400 cannot be represented as an int8 (type of Four) ``` The mask used by the unary bitwise complement operator ^ matches the rule for non-constants: the mask is all 1s for unsigned constants and -1 for signed and untyped constants. ```go ^1 // untyped integer constant, equal to -2 uint8(^1) // illegal: same as uint8(-2), -2 cannot be represented as a uint8 ^uint8(1) // typed uint8 constant, same as 0xFF ^ uint8(1) = uint8(0xFE) int8(^1) // same as int8(-2) ^int8(1) // same as -1 ^ int8(1) = -2 ``` ### Short variable declarations A short variable declaration uses the syntax: ```go ShortVarDecl = IdentifierList ":=" ExpressionList . ``` It is shorthand for a regular [variable declaration](#variable-declarations) with initializer expressions but no types: ```go "var" IdentifierList "=" ExpressionList . ``` ```go i, j := 0, 10 f := func() int { return 7 } ints := make([]int) r, w, _ := os.Pipe() // os.Pipe() returns a connected pair of Files and an error, if any _, y, _ := coord(p) // coord() returns three values; only interested in y coordinate ``` Unlike regular variable declarations, a short variable declaration may redeclare variables provided they were originally declared earlier in the same block (or the parameter lists if the block is the function body) with the same type, and at least one of the non-[blank]() variables is new. As a consequence, redeclaration can only appear in a multi-variable short declaration. Redeclaration does not introduce a new variable; it just assigns a new value to the original. The non-blank variable names on the left side of := must be [unique](). ```go field1, offset := nextField(str, 0) field2, offset := nextField(str, offset) // redeclares offset x, y, x := 1, 2, 3 // illegal: x repeated on left side of := ``` Short variable declarations may appear only inside functions. In some contexts such as the initializers for "[if]()", "[for]()", or "[switch]()" statements, they can be used to declare local temporary variables. ### Slice literals TODO ```go [expression1, ...] ``` For example: ```go [] // []any [1, 2, 3] // []int [10, 3.14, 200] // []float64 ["Hello", "world"] // []string ["Hello", 100, true] // []any ``` The type of slice literals can be inferred from the context: ```go func echoF32s(vals []float32) { echo vals } echo [10, 3.14, 200] // []float64 echoF32s [10, 3.14, 200] // []float32 var a []any = [10, 3.14, 200] // []any echo a ``` ### Map literals TODO ```go {key1: value1, ...} ``` For example: ```go {} // map[string]any {"Monday": 1, "Sunday": 7} // map[string]int {1: 100, 3: 3.14, 5: 10} // map[int]float64 ``` The type of map literals can be inferred from the context: ```go func echoS2f32(vals map[string]float32) { echo vals } echo {"Monday": 1, "Sunday": 7} echoS2f32 {"Monday": 1, "Sunday": 7} var a map[string]any = {"Monday": 1, "Sunday": 7} echo a ``` ### Order of evaluation At package level, [initialization dependencies]() determine the evaluation order of individual initialization expressions in [variable declarations](). Otherwise, when evaluating the [operands]() of an expression, assignment, or [return statement](#return-statements), all function calls, method calls, [receive operations](), and [binary logical operations](#logical-operators) are evaluated in lexical left-to-right order. For example, in the (function-local) assignment ```go y[f()], ok = g(z || h(), i()+x[j()], <-c), k() ``` the function calls and communication happen in the order `f()`, `h()` (if z evaluates to false), `i()`, `j()`, `<-c`, `g()`, and `k()`. However, the order of those events compared to the evaluation and indexing of `x` and the evaluation of `y` and `z` is not specified, except as required lexically. For instance, `g` cannot be called before its arguments are evaluated. ```go a := 1 f := func() int { a++; return a } x := [a, f()] // x may be [1, 2] or [2, 2]: evaluation order between a and f() is not specified m := {a: 1, a: 2} // m may be {2: 1} or {2: 2}: evaluation order between the two map assignments is not specified n := {a: f()} // n may be {2: 3} or {3: 3}: evaluation order between the key and the value is not specified ``` At package level, initialization dependencies override the left-to-right rule for individual initialization expressions, but not for operands within each expression: ```go var a, b, c = f() + v(), g(), sqr(u()) + v() func f() int { return c } func g() int { return a } func sqr(x int) int { return x*x } // functions u and v are independent of all other variables and functions ``` The function calls happen in the order `u()`, `sqr()`, `v()`, `f()`, `v()`, and `g()`. Floating-point operations within a single expression are evaluated according to the associativity of the operators. Explicit parentheses affect the evaluation by overriding the default associativity. In the expression `x + (y + z)` the addition `y + z` is performed before adding `x`. ## Statements Statements control execution. ```go Statement = Declaration | SimpleStmt | IfStmt | ForStmt | SwitchStmt | LabeledStmt | BreakStmt | ContinueStmt | FallthroughStmt | GotoStmt | ReturnStmt | DeferStmt | Block . SimpleStmt = EmptyStmt | ExpressionStmt | IncDecStmt | Assignment | ShortVarDecl . ``` ### Empty statements The empty statement does nothing. ```go EmptyStmt = . ``` ### Expression statements With the exception of specific built-in functions, function and method [calls](#commands-and-calls) and [receive operations]() can appear in statement context. Such statements may be parenthesized. ```go ExpressionStmt = Expression . ``` The following built-in functions are not permitted in statement context: ```go append cap complex imag len make new real unsafe.Add unsafe.Alignof unsafe.Offsetof unsafe.Sizeof unsafe.Slice unsafe.SliceData unsafe.String unsafe.StringData ``` ```go h(x+y) f.Close() <-ch (<-ch) len("foo") // illegal if len is the built-in function ``` ### IncDec statements The "++" and "--" statements increment or decrement their operands by the untyped constant 1. As with an assignment, the operand must be addressable or a map index expression. ```go IncDecStmt = Expression ( "++" | "--" ) . ``` The following [assignment statements]() are semantically equivalent: ```go IncDec statement Assignment x++ x += 1 x-- x -= 1 ``` ### Assignment statements An assignment replaces the current value stored in a [variable](#variables) with a new value specified by an [expression](#expressions). An assignment statement may assign a single value to a single variable, or multiple values to a matching number of variables. ```go Assignment = ExpressionList assign_op ExpressionList . ExpressionList = Expression { "," Expression } . ``` Here `assign_op` can be: ```go = += -= |= ^= *= /= %= <<= >>= &= &^= ``` Each left-hand side operand must be [addressable](#address-operators), a map index expression, or (for = assignments only) the [blank identifier](). Operands may be parenthesized. ```go x = 1 *p = f() a[i] = 23 (k) = <-ch // same as: k = <-ch ``` An _assignment operation_ x _op=_ y where op is a binary [arithmetic operator](#arithmetic-operators) is equivalent to x = x op (y) but evaluates x only once. The op= construct is a single token. In assignment operations, both the left- and right-hand expression lists must contain exactly one single-valued expression, and the left-hand expression must not be the blank identifier. ```go a[i] <<= 2 i &^= 1< 1 { x = 1 } ``` The expression may be preceded by a simple statement, which executes before the expression is evaluated. ```go if x := f(); x < y { return x } else if x > z { return z } else { return y } ``` ### For statements A "for" statement specifies repeated execution of a block. There are three forms: The iteration may be controlled by a single condition, a "for" clause, or a "range" clause. ```go ForStmt = "for" [ Condition | ForClause | RangeClause ] Block . Condition = Expression . ``` #### For statements with single condition In its simplest form, a "for" statement specifies the repeated execution of a block as long as a boolean condition evaluates to true. The condition is evaluated before each iteration. If the condition is absent, it is equivalent to the boolean value true. ```go for a < b { a *= 2 } ``` #### For statements with for clause A "for" statement with a ForClause is also controlled by its condition, but additionally it may specify an init and a post statement, such as an assignment, an increment or decrement statement. The init statement may be a [short variable declaration](#short-variable-declarations), but the post statement must not. ```go ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] . InitStmt = SimpleStmt . PostStmt = SimpleStmt . ``` ```go for i := 0; i < 10; i++ { f(i) } ``` If non-empty, the init statement is executed once before evaluating the condition for the first iteration; the post statement is executed after each execution of the block (and only if the block was executed). Any element of the ForClause may be empty but the [semicolons]() are required unless there is only a condition. If the condition is absent, it is equivalent to the boolean value true. ```go for cond { S() } is the same as for ; cond ; { S() } for { S() } is the same as for true { S() } ``` Each iteration has its own separate declared variable (or variables) [Go 1.22](). The variable used by the first iteration is declared by the init statement. The variable used by each subsequent iteration is declared implicitly before executing the post statement and initialized to the value of the previous iteration's variable at that moment. ```go var prints []func() for i := 0; i < 5; i++ { prints = append(prints, func() { println(i) }) i++ } for _, p := range prints { p() } ``` prints ``` 1 3 5 ``` Prior to [Go 1.22], iterations share one set of variables instead of having their own separate variables. In that case, the example above prints ``` 6 6 6 ``` #### For statements with range clause TODO ### Switch statements "Switch" statements provide multi-way execution. An expression or type is compared to the "cases" inside the "switch" to determine which branch to execute. ```go SwitchStmt = ExprSwitchStmt | TypeSwitchStmt . ``` There are two forms: expression switches and type switches. In an expression switch, the cases contain expressions that are compared against the value of the switch expression. In a type switch, the cases contain types that are compared against the type of a specially annotated switch expression. The switch expression is evaluated exactly once in a switch statement. #### Expression switches In an expression switch, the switch expression is evaluated and the case expressions, which need not be constants, are evaluated left-to-right and top-to-bottom; the first one that equals the switch expression triggers execution of the statements of the associated case; the other cases are skipped. If no case matches and there is a "default" case, its statements are executed. There can be at most one default case and it may appear anywhere in the "switch" statement. A missing switch expression is equivalent to the boolean value true. ```go ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [ Expression ] "{" { ExprCaseClause } "}" . ExprCaseClause = ExprSwitchCase ":" StatementList . ExprSwitchCase = "case" ExpressionList | "default" . ``` If the switch expression evaluates to an untyped constant, it is first implicitly [converted](#conversions) to its [default type](#constants). The predeclared untyped value nil cannot be used as a switch expression. The switch expression type must be [comparable](#comparison-operators). If a case expression is untyped, it is first implicitly [converted](#conversions) to the type of the switch expression. For each (possibly converted) case expression x and the value t of the switch expression, x == t must be a valid [comparison](#comparison-operators). In other words, the switch expression is treated as if it were used to declare and initialize a temporary variable t without explicit type; it is that value of t against which each case expression x is tested for equality. In a case or default clause, the last non-empty statement may be a (possibly [labeled](#labeled-statements)) ["fallthrough" statement]() to indicate that control should flow from the end of this clause to the first statement of the next clause. Otherwise control flows to the end of the "switch" statement. A "fallthrough" statement may appear as the last statement of all but the last clause of an expression switch. The switch expression may be preceded by a simple statement, which executes before the expression is evaluated. ```go switch tag { default: s3() case 0, 1, 2, 3: s1() case 4, 5, 6, 7: s2() } switch x := f(); { // missing switch expression means "true" case x < 0: return -x default: return x } switch { case x < y: f1() case x < z: f2() case x == 4: f3() } ``` Implementation restriction: A compiler may disallow multiple case expressions evaluating to the same constant. For instance, the current compilers disallow duplicate integer, floating point, or string constants in case expressions. #### Type switches A type switch compares types rather than values. It is otherwise similar to an expression switch. It is marked by a special switch expression that has the form of a [type assertion]() using the keyword type rather than an actual type: ```go switch x.(type) { // cases } ``` Cases then match actual types T against the dynamic type of the expression x. As with type assertions, x must be of interface type, but not a type parameter, and each non-interface type T listed in a case must implement the type of x. The types listed in the cases of a type switch must all be different. ```go TypeSwitchStmt = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" . TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . TypeCaseClause = TypeSwitchCase ":" StatementList . TypeSwitchCase = "case" TypeList | "default" . ``` The TypeSwitchGuard may include a [short variable declaration](#short-variable-declarations). When that form is used, the variable is declared at the end of the TypeSwitchCase in the [implicit block]() of each clause. In clauses with a case listing exactly one type, the variable has that type; otherwise, the variable has the type of the expression in the TypeSwitchGuard. Instead of a type, a case may use the predeclared identifier [nil](); that case is selected when the expression in the TypeSwitchGuard is a `nil` interface value. There may be at most one `nil` case. Given an expression x of type `any`, the following type switch: ``` switch i := x.(type) { case nil: printString("x is nil") // type of i is type of x (any) case int: printInt(i) // type of i is int case float64: printFloat64(i) // type of i is float64 case func(int) float64: printFunction(i) // type of i is func(int) float64 case bool, string: printString("type is bool or string") // type of i is type of x (any) default: printString("don't know the type") // type of i is type of x (any) } ``` could be rewritten: ```go v := x // x is evaluated exactly once if v == nil { i := v // type of i is type of x (any) printString("x is nil") } else if i, isInt := v.(int); isInt { printInt(i) // type of i is int } else if i, isFloat64 := v.(float64); isFloat64 { printFloat64(i) // type of i is float64 } else if i, isFunc := v.(func(int) float64); isFunc { printFunction(i) // type of i is func(int) float64 } else { _, isBool := v.(bool) _, isString := v.(string) if isBool || isString { i := v // type of i is type of x (any) printString("type is bool or string") } else { i := v // type of i is type of x (any) printString("don't know the type") } } ``` The type switch guard may be preceded by a simple statement, which executes before the guard is evaluated. The "fallthrough" statement is not permitted in a type switch. ### Labeled statements A labeled statement may be the target of a goto, break or continue statement. ```go LabeledStmt = Label ":" Statement . Label = identifier . ``` ```go Error: log.Panic("error encountered") ``` ### Break statements A "break" statement terminates execution of the innermost "[for](#for-statements)" or "[switch](#switch-statements)" statement within the same function. ```go BreakStmt = "break" [ Label ] . ``` If there is a label, it must be that of an enclosing "for" or "switch" statement, and that is the one whose execution terminates. ```go OuterLoop: for i = 0; i < n; i++ { for j = 0; j < m; j++ { switch a[i][j] { case nil: state = Error break OuterLoop case item: state = Found break OuterLoop } } } ``` ### Continue statements A "continue" statement begins the next iteration of the innermost enclosing "[for](#for-statements)" loop by advancing control to the end of the loop block. The "for" loop must be within the same function. ```go ContinueStmt = "continue" [ Label ] . ``` If there is a label, it must be that of an enclosing "for" statement, and that is the one whose execution advances. ```go RowLoop: for y, row := range rows { for x, data := range row { if data == endOfRow { continue RowLoop } row[x] = data + bias(x, y) } } ``` ### Fallthrough statements A "fallthrough" statement transfers control to the first statement of the next case clause in an expression "[switch](#switch-statements)" statement. It may be used only as the final non-empty statement in such a clause. ```go FallthroughStmt = "fallthrough" . ``` ### Goto statements A "goto" statement transfers control to the statement with the corresponding label within the same function. ```go GotoStmt = "goto" Label . ``` ```go goto Error ``` Executing the "goto" statement must not cause any variables to come into [scope]() that were not already in scope at the point of the goto. For instance, this example: ```go goto L // BAD v := 3 L: ``` is erroneous because the jump to label L skips the creation of v. A "goto" statement outside a [block]() cannot jump to a label inside that block. For instance, this example: ```go if n%2 == 1 { goto L1 // BAD } for n > 0 { f() n-- L1: f() n-- } ``` is erroneous because the label L1 is inside the "for" statement's block but the goto is not. ### Return statements A "return" statement in a function F terminates the execution of F, and optionally provides one or more result values. Any functions [deferred](#defer-statements) by F are executed before F returns to its caller. ```go ReturnStmt = "return" [ ExpressionList ] . ``` In a function without a result type, a "return" statement must not specify any result values. ```go func noResult() { return } ``` There are three ways to return values from a function with a result type: * The return value or values may be explicitly listed in the "return" statement. Each expression must be single-valued and [assignable]() to the corresponding element of the function's result type. ```go func simpleF() int { return 2 } func complexF1() (re float64, im float64) { return -7.0, -4.0 } ``` * The expression list in the "return" statement may be a single call to a multi-valued function. The effect is as if each value returned from that function were assigned to a temporary variable with the type of the respective value, followed by a "return" statement listing these variables, at which point the rules of the previous case apply. ```go func complexF2() (re float64, im float64) { return complexF1() } ``` * The expression list may be empty if the function's result type specifies names for its [result parameters](). The result parameters act as ordinary local variables and the function may assign values to them as necessary. The "return" statement returns the values of these variables. ```go func complexF3() (re float64, im float64) { re = 7.0 im = 4.0 return } ``` Regardless of how they are declared, all the result values are initialized to the [zero values]() for their type upon entry to the function. A "return" statement that specifies results sets the result parameters before any deferred functions are executed. Implementation restriction: A compiler may disallow an empty expression list in a "return" statement if a different entity (constant, type, or variable) with the same name as a result parameter is in [scope]() at the place of the return. ```go func f(n int) (res int, err error) { if _, err := f(n-1); err != nil { return // invalid return statement: err is shadowed } return } ``` ### Defer statements A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a [return statement](#return-statements), reached the end of its [function body](), or because the corresponding goroutine is [panicking](). ```go DeferStmt = "defer" Expression . ``` The expression must be a function or method call; it cannot be parenthesized. Calls of built-in functions are restricted as for [expression statements](#expression-statements). Each time a "defer" statement executes, the function value and parameters to the call are [evaluated as usual]() and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred. That is, if the surrounding function returns through an explicit [return statement](#return-statements), deferred functions are executed after any result parameters are set by that return statement but before the function returns to its caller. If a deferred function value evaluates to nil, execution [panics]() when the function is invoked, not when the "defer" statement is executed. For instance, if the deferred function is a [function literal]() and the surrounding function has [named result parameters]() that are in scope within the literal, the deferred function may access and modify the result parameters before they are returned. If the deferred function has any return values, they are discarded when the function completes. (See also the section on [handling panics]().) ```go lock(l) defer unlock(l) // unlocking happens before surrounding function returns // f returns 42 func f() (result int) { defer func() { // result is accessed after it was set to 6 by the return statement result *= 7 }() return 6 } ``` ### Terminating statements TODO ## Built-in functions Built-in functions are [predeclared](). They are called like any other function but some of them accept a type instead of an expression as the first argument. The built-in functions do not have standard Go types, so they can only appear in [call expressions](#commands-and-calls); they cannot be used as function values. ### Appending to and copying slices The built-in functions `append` and `copy` assist in common slice operations. For both functions, the result is independent of whether the memory referenced by the arguments overlaps. The [variadic](#function-types) function append appends zero or more values x to a slice s and returns the resulting slice of the same type as s. The [underlying type](#underlying-types) of s must be a slice of type `[]E`. The values x are passed to a parameter of type `...E` and the respective [parameter passing rules]() apply. As a special case, if the underlying type of s is []byte, append also accepts a second argument with underlying type [bytestring]() followed by `...`. This form appends the bytes of the byte slice or string. ```go append(s S, x ...E) S // underlying type of S is []E ``` If the capacity of s is not large enough to fit the additional values, append [allocates]() a new, sufficiently large underlying array that fits both the existing slice elements and the additional values. Otherwise, append re-uses the underlying array. ```go s0 := [0, 0] s1 := append(s0, 2) // append a single element s1 is [0, 0, 2] s2 := append(s1, 3, 5, 7) // append multiple elements s2 is [0, 0, 2, 3, 5, 7] s3 := append(s2, s0...) // append a slice s3 is [0, 0, 2, 3, 5, 7, 0, 0] s4 := append(s3[3:6], s3[2:]...) // append overlapping slice s4 is [3, 5, 7, 2, 3, 5, 7, 0, 0] var t []any t = append(t, 42, 3.1415, "foo") // t is [42, 3.1415, "foo"] var b []byte b = append(b, "bar"...) // append string contents b is []byte("bar") ``` The function copy copies slice elements from a source src to a destination dst and returns the number of elements copied. The [underlying types](#underlying-types) of both arguments must be slices with [identical]() element type. The number of elements copied is the minimum of `len(src)` and `len(dst)`. As a special case, if the destination's underlying type is `[]byte`, copy also accepts a source argument with underlying type [bytestring](). This form copies the bytes from the byte slice or string into the byte slice. ```go copy(dst, src []T) int copy(dst []byte, src string) int ``` Examples: ```go a := [0, 1, 2, 3, 4, 5, 6, 7] s := make([]int, 6) b := make([]byte, 5) n1 := copy(s, a) // n1 == 6, s is []int{0, 1, 2, 3, 4, 5} n2 := copy(s, s[2:]) // n2 == 4, s is []int{2, 3, 4, 5, 4, 5} n3 := copy(b, "Hello, World!") // n3 == 5, b is []byte("Hello") ``` ### Clear The built-in function `clear` takes an argument of `map` or `slice` and deletes or zeroes out all elements. ``` Call Argument type Result clear(m) map[K]T deletes all entries, resulting in an empty map (len(m) == 0) clear(s) []T sets all elements up to the length of s to the zero value of T ``` If the map or slice is `nil`, `clear` is a no-op. ### Manipulating complex numbers Three functions assemble and disassemble complex numbers. The built-in function complex constructs a complex value from a floating-point real and imaginary part, while real and imag extract the real and imaginary parts of a complex value. ```go complex(realPart, imaginaryPart floatT) complexT real(complexT) floatT imag(complexT) floatT ``` The type of the arguments and return value correspond. For `complex`, the two arguments must be of the same [floating-point type](#numeric-types) and the return type is the [complex type](#numeric-types) with the corresponding floating-point constituents: `complex64` for `float32` arguments, and `complex128` for `float64` arguments. If one of the arguments evaluates to an untyped constant, it is first implicitly [converted](#conversions) to the type of the other argument. If both arguments evaluate to untyped constants, they must be non-complex numbers or their imaginary parts must be zero, and the return value of the function is an untyped complex constant. For `real` and `imag`, the argument must be of complex type, and the return type is the corresponding floating-point type: `float32` for a `complex64` argument, and float64 for a complex128 argument. If the argument evaluates to an untyped constant, it must be a number, and the return value of the function is an untyped floating-point constant. The `real` and `imag` functions together form the inverse of `complex`, so for a value `z` of a complex type `Z`, `z == Z(complex(real(z), imag(z)))`. If the operands of these functions are all constants, the return value is a constant. ```go var a = complex(2, -2) // complex128 const b = complex(1.0, -1.4) // untyped complex constant 1 - 1.4i x := float32(math.Cos(math.Pi/2)) // float32 var c64 = complex(5, -x) // complex64 var s int = complex(1, 0) // untyped complex constant 1 + 0i can be converted to int _ = complex(1, 2< cap(s) m := make(map[string]int, 100) // map with initial space for approximately 100 elements ``` Calling make with a map type and size hint `n` will create a map with initial space to hold `n` map elements. The precise behavior is implementation-dependent. ### Allocation The built-in function `new` takes a type `T`, allocates storage for a [variable](#variables) of that type at run time, and returns a value of type `*T` [pointing](#pointer-types) to it. The variable is initialized as described in the section on [initial values](). ```go new(T) ``` For instance ```go new(int) ``` allocates storage for a variable of type `int`, initializes it `0`, and returns a value of type `*int` containing the address of the location. ### Min and max The built-in functions `min` and `max` compute the smallest—or largest, respectively—value of a fixed number of arguments of [ordered types](). There must be at least one argument. The same type rules as for [operators](#operators) apply: for [ordered]() arguments `x` and `y`, `min(x, y)` is valid if `x + y` is valid, and the type of `min(x, y)` is the type of `x + y` (and similarly for `max`). If all arguments are constant, the result is constant. ```go var x, y int m := min(x) // m == x m := min(x, y) // m is the smaller of x and y m := max(x, y, 10) // m is the larger of x and y but at least 10 c := max(1, 2.0, 10) // c == 10.0 (floating-point kind) f := max(0, float32(x)) // type of f is float32 var s []string _ = min(s...) // invalid: slice arguments are not permitted t := max("", "foo", "bar") // t == "foo" (string kind) ``` For numeric arguments, assuming all `NaN`s are equal, `min` and `max` are commutative and associative: ``` min(x, y) == min(y, x) min(x, y, z) == min(min(x, y), z) == min(x, min(y, z)) ``` For floating-point arguments negative zero, `NaN`, and infinity the following rules apply: ```go x y min(x, y) max(x, y) -0.0 0.0 -0.0 0.0 // negative zero is smaller than (non-negative) zero -Inf y -Inf y // negative infinity is smaller than any other number +Inf y y +Inf // positive infinity is larger than any other number NaN y NaN NaN // if any argument is a NaN, the result is a NaN ``` For string arguments the result for min is the first argument with the smallest (or for max, largest) value, compared lexically byte-wise: ```go min(x, y) == if x <= y then x else y min(x, y, z) == min(min(x, y), z) ``` ### Handling panics Two built-in functions, `panic` and `recover`, assist in reporting and handling [run-time panics]() and program-defined error conditions. ```go func panic(any) func recover() any ``` While executing a function `F`, an explicit call to `panic` or a [run-time panic]() terminates the execution of `F`. Any functions [deferred]() by `F` are then executed as usual. Next, any deferred functions run by `F`'s caller are run, and so on up to any deferred by the top-level function in the executing goroutine. At that point, the program is terminated and the error condition is reported, including the value of the argument to panic. This termination sequence is called _panicking_. ```go panic(42) panic("unreachable") panic(Error("cannot parse")) ``` The `recover` function allows a program to manage behavior of a panicking goroutine. Suppose a function `G` defers a function `D` that calls `recover` and a panic occurs in a function on the same goroutine in which `G` is executing. When the running of deferred functions reaches `D`, the return value of `D`'s call to recover will be the value passed to the call of panic. If `D` returns normally, without starting a new panic, the panicking sequence stops. In that case, the state of functions called between `G` and the call to panic is discarded, and normal execution resumes. Any functions deferred by `G` before `D` are then run and `G`'s execution terminates by returning to its caller. The return value of `recover` is `nil` when the goroutine is not panicking or `recover` was not called directly by a deferred function. Conversely, if a goroutine is panicking and recover was called directly by a deferred function, the return value of recover is guaranteed not to be `nil`. To ensure this, calling panic with a `nil` interface value (or an untyped nil) causes a [run-time panic](). The `protect` function in the example below invokes the function argument `g` and protects callers from run-time panics raised by `g`. ```go func protect(g func()) { defer func() { log.Println("done") // Println executes normally even if there is a panic if x := recover(); x != nil { log.Printf("run time panic: %v", x) } }() log.Println("start") g() } ``` ### TODO ```go print printf println ... ``` ## Blocks A _block_ is a possibly empty sequence of declarations and statements within matching brace brackets. ```go Block = "{" StatementList "}" . StatementList = { Statement ";" } . ``` In addition to explicit blocks in the source code, there are implicit blocks: * The _universe block_ encompasses all XGo source text. * Each [package](#packages) has a _package block_ containing all XGo source text for that package. * Each file has a _file block_ containing all XGo source text in that file. * Each "[if](#if-statements)", "[for](#for-statements)", and "[switch](#switch-statements)" statement is considered to be in its own implicit block. * Each clause in a "[switch](#switch-statements)" statement acts as an implicit block. Blocks nest and influence [scoping](). ## Declarations and scope A _declaration_ binds a non-[blank]() identifier to a [constant](), [type](), [variable](), [function](), [label](), or [package](). Every identifier in a program must be declared. No identifier may be declared twice in the same block, and no identifier may be declared in both the file and package block. The [blank identifier]() may be used like any other identifier in a declaration, but it does not introduce a binding and thus is not declared. In the package block, the identifier `init` may only be used for [init function]() declarations, and like the blank identifier it does not introduce a new binding. ```go Declaration = ConstDecl | TypeDecl | VarDecl . TopLevelDecl = Declaration | FunctionDecl . ``` The scope of a declared identifier is the extent of source text in which the identifier denotes the specified constant, type, variable, function, label, or package. XGo is lexically scoped using blocks: * The scope of a [predeclared identifier]() is the universe block. * The scope of an identifier denoting a constant, type, variable, or function declared at top level (outside any function) is the package block. * The scope of the package name of an imported package is the file block of the file containing the import declaration. * The scope of an identifier denoting function parameter, or result variable is the function body. * The scope of a constant or variable identifier declared inside a function begins at the end of the `ConstSpec` or `VarSpec` (`ShortVarDecl` for short variable declarations) and ends at the end of the innermost containing block. An identifier declared in a block may be redeclared in an inner block. While the identifier of the inner declaration is in scope, it denotes the entity declared by the inner declaration. The [package clause]() is not a declaration; the package name does not appear in any scope. Its purpose is to identify the files belonging to the same [package](#packages) and to specify the default package name for import declarations. ### Label scopes Labels are declared by [labeled statements](#labeled-statements) and are used in the "[break](#break-statements)", "[continue](#continue-statements)", and "[goto](#goto-statements)" statements. It is illegal to define a label that is never used. In contrast to other identifiers, labels are not block scoped and do not conflict with identifiers that are not labels. The scope of a label is the body of the function in which it is declared and excludes the body of any nested function. ### Blank identifier The _blank identifier_ is represented by the underscore character `_`. It serves as an anonymous placeholder instead of a regular (non-blank) identifier and has special meaning in [declarations](#declarations-and-scope), as an [operand](), and in [assignment statements](#assignment-statements). ### Predeclared identifiers The following identifiers are implicitly declared in the [universe block](): ```go Types: any bigint bigrat bool byte comparable complex64 complex128 error float32 float64 int int8 int16 int32 int64 rune string uint uint8 uint16 uint32 uint64 uintptr Constants: true false iota Zero value: nil Functions: append cap clear close complex copy delete echo imag len make max min new panic print printf println real recover // TODO(xsw): more ``` ### Exported identifiers An identifier may be _exported_ to permit access to it from another package. An identifier is exported if both: * the first character of the identifier's name is a Unicode uppercase letter (Unicode character category Lu); and * the identifier is declared in the [package block]() or it is a [field name]() or [method name](). All other identifiers are not exported. ### Uniqueness of identifiers Given a set of identifiers, an identifier is called _unique_ if it is _different_ from every other in the set. Two identifiers are different if they are spelled differently, or if they appear in different [packages](#packages) and are not [exported](#exported-identifiers). Otherwise, they are the same. ### Constant declarations A constant declaration binds a list of identifiers (the names of the constants) to the values of a list of [constant expressions](#constant-expressions). The number of identifiers must be equal to the number of expressions, and the nth identifier on the left is bound to the value of the nth expression on the right. ```go ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) . ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] . IdentifierList = identifier { "," identifier } . ExpressionList = Expression { "," Expression } . ``` If the type is present, all constants take the type specified, and the expressions must be [assignable]() to that type, which must not be a type parameter. If the type is omitted, the constants take the individual types of the corresponding expressions. If the expression values are untyped [constants](#constants), the declared constants remain untyped and the constant identifiers denote the constant values. For instance, if the expression is a floating-point literal, the constant identifier denotes a floating-point constant, even if the literal's fractional part is zero. ```go const Pi float64 = 3.14159265358979323846 const zero = 0.0 // untyped floating-point constant const ( size int64 = 1024 eof = -1 // untyped integer constant ) const a, b, c = 3, 4, "foo" // a = 3, b = 4, c = "foo", untyped integer and string constants const u, v float32 = 0, 3 // u = 0.0, v = 3.0 ``` Within a parenthesized const declaration list the expression list may be omitted from any but the first ConstSpec. Such an empty list is equivalent to the textual substitution of the first preceding non-empty expression list and its type if any. Omitting the list of expressions is therefore equivalent to repeating the previous list. The number of identifiers must be equal to the number of expressions in the previous list. Together with the [iota constant generator](#iota) this mechanism permits light-weight declaration of sequential values: ```go const ( Sunday = iota Monday Tuesday Wednesday Thursday Friday Partyday numberOfDays // this constant is not exported ) ``` ### Iota Within a [constant declaration](#constant-declarations), the predeclared identifier iota represents successive untyped integer [constants](#constants). Its value is the index of the respective [ConstSpec]() in that constant declaration, starting at zero. It can be used to construct a set of related constants: ```go const ( c0 = iota // c0 == 0 c1 = iota // c1 == 1 c2 = iota // c2 == 2 ) const ( a = 1 << iota // a == 1 (iota == 0) b = 1 << iota // b == 2 (iota == 1) c = 3 // c == 3 (iota == 2, unused) d = 1 << iota // d == 8 (iota == 3) ) const ( u = iota * 42 // u == 0 (untyped integer constant) v float64 = iota * 42 // v == 42.0 (float64 constant) w = iota * 42 // w == 84 (untyped integer constant) ) const x = iota // x == 0 const y = iota // y == 0 ``` By definition, multiple uses of iota in the same ConstSpec all have the same value: ```go const ( bit0, mask0 = 1 << iota, 1<?[\]^`{|} and the Unicode replacement character U+FFFD. Consider a compiled a package containing the package clause package math, which exports function `Sin`, and installed the compiled package in the file identified by "lib/math". This table illustrates how `Sin` is accessed in files that import the package after the various types of import declaration. ```go Import declaration Local name of Sin import "lib/math" math.Sin import m "lib/math" m.Sin import . "lib/math" Sin ``` An import declaration declares a dependency relation between the importing and imported package. It is illegal for a package to import itself, directly or indirectly, or to directly import a package without referring to any of its exported identifiers. To import a package solely for its side-effects (initialization), use the [blank]() identifier as explicit package name: ```go import _ "lib/math" ``` ### An example package Here is a complete XGo package that implements XXX. ```go TODO ``` ## Program initialization and execution ### The zero value When storage is allocated for a [variable](#variables), either through a declaration or a call of `new`, or when a new value is created, either through a composite literal or a call of `make`, and no explicit initialization is provided, the variable or value is given a default value. Each element of such a variable or value is set to the _zero_ value for its type: `false` for booleans, `0` for numeric types, `""` for strings, and `nil` for pointers, functions, interfaces, slices, and maps. This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified. These two simple declarations are equivalent: ```go var i int var i int = 0 ``` TODO ### Package initialization Within a package, package-level variable initialization proceeds stepwise, with each step selecting the variable earliest in `declaration order` which has no dependencies on uninitialized variables. More precisely, a package-level variable is considered ready for `initialization` if it is not yet initialized and either has no [initialization expression]() or its initialization expression has no dependencies on uninitialized variables. Initialization proceeds by repeatedly initializing the next package-level variable that is earliest in declaration order and ready for initialization, until there are no variables ready for initialization. If any variables are still uninitialized when this process ends, those variables are part of one or more initialization cycles, and the program is not valid. Multiple variables on the left-hand side of a variable declaration initialized by single (multi-valued) expression on the right-hand side are initialized together: If any of the variables on the left-hand side is initialized, all those variables are initialized in the same step. ```go var x = a var a, b = f() // a and b are initialized together, before x is initialized ``` For the purpose of package initialization, [blank]() variables are treated like any other variables in declarations. The declaration order of variables declared in multiple files is determined by the order in which the files are presented to the compiler: Variables declared in the first file are declared before any of the variables declared in the second file, and so on. To ensure reproducible initialization behavior, build systems are encouraged to present multiple files belonging to the same package in lexical file name order to a compiler. Dependency analysis does not rely on the actual values of the variables, only on lexical `references` to them in the source, analyzed transitively. For instance, if a variable x's initialization expression refers to a function whose body refers to variable y then x depends on y. Specifically: * A reference to a variable or function is an identifier denoting that variable or function. * A reference to a method m is a [method value]() or [method expression]() of the form `t.m`, where the (static) type of t is not an interface type, and the method `m` is in the method set of `t`. It is immaterial whether the resulting function value `t.m` is invoked. * A variable, function, or method x depends on a variable y if x's initialization expression or body (for functions and methods) contains a reference to y or to a function or method that depends on y. For example, given the declarations ```go var ( a = c + b // == 9 b = f() // == 4 c = f() // == 5 d = 3 // == 5 after initialization has finished ) func f() int { d++ return d } ``` the initialization order is `d`, `b`, `c`, `a`. Note that the order of subexpressions in initialization expressions is irrelevant: `a = c + b` and `a = b + c` result in the same initialization order in this example. Dependency analysis is performed per package; only references referring to variables, functions, and (non-interface) methods declared in the current package are considered. If other, hidden, data dependencies exists between variables, the initialization order between those variables is unspecified. For instance, given the declarations (TODO: use classfile instead of method) ```go var x = I(T{}).ab() // x has an undetected, hidden dependency on a and b var _ = sideEffect() // unrelated to x, a, or b var a = b var b = 42 type I interface { ab() []int } type T struct{} func (T) ab() []int { return []int{a, b} } ``` the variable `a` will be initialized after `b` but whether `x` is initialized before `b`, between `b` and `a`, or after `a`, and thus also the moment at which `sideEffect()` is called (before or after `x` is initialized) is not specified. Variables may also be initialized using functions named init declared in the package block, with no arguments and no result parameters. ```go func init() { … } ``` Multiple such functions may be defined per package, even within a single source file. In the package block, the `init` identifier can be used only to declare `init` functions, yet the identifier itself is not [declared](). Thus init functions cannot be referred to from anywhere in a program. The entire package is initialized by assigning initial values to all its package-level variables followed by calling all `init` functions in the order they appear in the source, possibly in multiple files, as presented to the compiler. ### Program initialization The packages of a complete program are initialized stepwise, one package at a time. If a package has imports, the imported packages are initialized before initializing the package itself. If multiple packages import a package, the imported package will be initialized only once. The importing of packages, by construction, guarantees that there can be no cyclic initialization dependencies. More precisely: Given the list of all packages, sorted by import path, in each step the first uninitialized package in the list for which all imported packages (if any) are already initialized is [initialized](#package-initialization). This step is repeated until all packages are initialized. Package initialization—variable initialization and the invocation of `init` functions—happens in a single goroutine, sequentially, one package at a time. An `init` function may launch other goroutines, which can run concurrently with the initialization code. However, initialization always sequences the `init` functions: it will not invoke the next one until the previous one has returned. ### Program execution A complete program is created by linking a single, unimported package called the main package with all the packages it imports, transitively. The main package must have package name main and declare a function main that takes no arguments and returns no value. ```go func main() { … } ``` Program execution begins by [initializing the program](#program-initialization) and then invoking the function `main` in package `main`. When that function invocation returns, the program exits. It does not wait for other (non-main) goroutines to complete. ### Run-time panics Execution errors such as attempting to index an array out of bounds trigger a _run-time panic_ equivalent to a call of the built-in function [panic]() with a value of the implementation-defined interface type `runtime.Error`. That type satisfies the predeclared interface type [error](#errors). The exact error values that represent distinct run-time error conditions are unspecified. ```go package runtime type Error interface { error // and perhaps other methods } ``` ## System considerations ### Package unsafe The built-in package `unsafe`, known to the compiler and accessible through the [import path]() `"unsafe"`, provides facilities for low-level programming including operations that violate the type system. A package using unsafe must be vetted manually for type safety and may not be portable. The package provides the following interface: ```go package unsafe type ArbitraryType int // shorthand for an arbitrary Go type; it is not a real type type Pointer *ArbitraryType func Alignof(variable ArbitraryType) uintptr func Offsetof(selector ArbitraryType) uintptr func Sizeof(variable ArbitraryType) uintptr type IntegerType int // shorthand for an integer type; it is not a real type func Add(ptr Pointer, len IntegerType) Pointer func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType func SliceData(slice []ArbitraryType) *ArbitraryType func String(ptr *byte, len IntegerType) string func StringData(str string) *byte ``` A Pointer is a [pointer type](#pointer-types) but a Pointer value may not be [dereferenced](#address-operators). Any pointer or value of [underlying type](#underlying-types) `uintptr` can be [converted](#conversions) to a type of underlying type Pointer and vice versa. The effect of converting between Pointer and `uintptr` is implementation-defined. ```go var f float64 bits = *(*uint64)(unsafe.Pointer(&f)) type ptr unsafe.Pointer bits = *(*uint64)(ptr(&f)) func f[P ~*B, B any](p P) uintptr { return uintptr(unsafe.Pointer(p)) } var p ptr = nil ``` The functions `Alignof` and `Sizeof` take an expression `x` of any type and return the alignment or size, respectively, of a hypothetical variable `v` as if `v` was declared via `var v = x`. The function `Offsetof` takes a (possibly parenthesized) [selector]() `s.f`, denoting a field `f` of the struct denoted by `s` or `*s`, and returns the field offset in bytes relative to the struct's address. If `f` is an [embedded field](), it must be reachable without pointer indirections through fields of the struct. For a struct `s` with field `f`: ```go uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(&s.f)) ``` Computer architectures may require memory addresses to be _aligned_; that is, for addresses of a variable to be a multiple of a factor, the variable's type's _alignment_. The function `Alignof` takes an expression denoting a variable of any type and returns the alignment of the (type of the) variable in bytes. For a variable `x`: ```go uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0 ``` A (variable of) type T has _variable size_ if T is a [type parameter](), or if it is an array or struct type containing elements or fields of variable size. Otherwise the size is constant. Calls to `Alignof`, `Offsetof`, and `Sizeof` are compile-time [constant expressions](#constant-expressions) of type `uintptr` if their arguments (or the struct `s` in the selector expression `s.f` for `Offsetof`) are types of constant size. The function `Add` adds `len` to `ptr` and returns the updated pointer `unsafe.Pointer(uintptr(ptr) + uintptr(len))`. The `len` argument must be of [integer type](#numeric-types) or an untyped [constant](#constants). A constant `len` argument must be [representable]() by a value of type `int`; if it is an untyped constant it is given type `int`. The rules for [valid uses]() of Pointer still apply. The function `Slice` returns a slice whose underlying array starts at `ptr` and whose length and capacity are `len`. `Slice(ptr, len)` is equivalent to ```go (*[len]ArbitraryType)(unsafe.Pointer(ptr))[:] ``` except that, as a special case, if `ptr` is `nil` and `len` is zero, `Slice` returns `nil`. The `len` argument must be of [integer type](#numeric-types) or an untyped [constant](#constants). A constant `len` argument must be non-negative and [representable]() by a value of type `int`; if it is an untyped constant it is given type `int`. At run time, if `len` is negative, or if `ptr` is nil and `len` is not zero, a [run-time panic](#run-time-panics) occurs. The function `SliceData` returns a pointer to the underlying array of the `slice` argument. If the slice's capacity `cap(slice)` is not zero, that pointer is `&slice[:1][0]`. If slice is `nil`, the result is `nil`. Otherwise it is a non-nil pointer to an unspecified memory address. The function `String` returns a `string` value whose underlying bytes start at `ptr` and whose length is `len`. The same requirements apply to the `ptr` and `len` argument as in the function `Slice`. If `len` is zero, the result is the empty string `""`. Since XGo strings are immutable, the bytes passed to `String` must not be modified afterwards. The function `StringData` returns a pointer to the underlying bytes of the `str` argument. For an empty string the return value is unspecified, and may be `nil`. Since XGo strings are immutable, the bytes returned by `StringData` must not be modified. ### Size and alignment guarantees For the [numeric types](#numeric-types), the following sizes are guaranteed: ```go type size in bytes byte, uint8, int8 1 uint16, int16 2 uint32, int32, float32 4 uint64, int64, float64, complex64 8 complex128 16 ``` The following minimal alignment properties are guaranteed: * For a variable `x` of any type: `unsafe.Alignof(x)` is at least 1. * For a variable `x` of struct type: `unsafe.Alignof(x)` is the largest of all the values `unsafe.Alignof(x.f)` for each field `f` of `x`, but at least 1. * For a variable `x` of array type: `unsafe.Alignof(x)` is the same as the alignment of a variable of the array's element type. A struct or array type has size zero if it contains no fields (or elements, respectively) that have a size greater than zero. Two distinct zero-size variables may have the same address in memory. ## Appendix ### Language versions TODO ### Type unification rules The type unification rules describe if and how two types unify. The precise details are relevant for XGo implementations, affect the specifics of error messages (such as whether a compiler reports a type inference or other error), and may explain why type inference fails in unusual code situations. But by and large these rules can be ignored when writing Go code: type inference is designed to mostly "work as expected", and the unification rules are fine-tuned accordingly. Type unification is controlled by a matching mode, which may be `exact` or `loose`. As unification recursively descends a composite type structure, the matching mode used for elements of the type, the element matching mode, remains the same as the matching mode except when two types are unified for [assignability]() (≡A): in this case, the matching mode is loose at the top level but then changes to exact for element types, reflecting the fact that types don't have to be identical to be assignable. Two types that are not bound type parameters unify exactly if any of following conditions is true: * Both types are [identical](). * Both types have identical structure and their element types unify exactly. * Exactly one type is an [unbound]() type parameter with a [underlying type](#underlying-types), and that underlying type unifies with the other type per the unification rules for ≡A (loose unification at the top level and exact unification for element types). If both types are bound type parameters, they unify per the given matching modes if: * Both type parameters are identical. * At most one of the type parameters has a known type argument. In this case, the type parameters are `joined`: they both stand for the same type argument. If neither type parameter has a known type argument yet, a future type argument inferred for one the type parameters is simultaneously inferred for both of them. * Both type parameters have a known type argument and the type arguments unify per the given matching modes. A single bound type parameter P and another type T unify per the given matching modes if: * P doesn't have a known type argument. In this case, T is inferred as the type argument for P. * P does have a known type argument A, A and T unify per the given matching modes, and one of the following conditions is true: * Both A and T are interface types: In this case, if both A and T are also [defined]() types, they must be [identical](). Otherwise, if neither of them is a defined type, they must have the same number of methods (unification of A and T already established that the methods match). * Neither A nor T are interface types: In this case, if T is a defined type, T replaces A as the inferred type argument for P. Finally, two types that are not bound type parameters unify loosely (and per the element matching mode) if: * Both types unify exactly. * One type is a [defined type](), the other type is a type literal, but not an interface, and their underlying types unify per the element matching mode. * Both types are interfaces (but not type parameters) with identical [type terms](), both or neither embed the predeclared type [comparable](), corresponding method types unify exactly, and the method set of one of the interfaces is a subset of the method set of the other interface. * Only one type is an interface (but not a type parameter), corresponding methods of the two types unify per the element matching mode, and the method set of the interface is a subset of the method set of the other type. * Both types have the same structure and their element types unify per the element matching mode. ================================================ FILE: doc/spec.md ================================================ The XGo Full Specification ===== TODO ## Comments See [Comments](spec-mini.md#comments). ## Literals See [Literals](spec-mini.md#literals). ### String literals ### Composite literals Composite literals construct new composite values each time they are evaluated. They consist of the type of the literal followed by a brace-bound list of elements. Each element may optionally be preceded by a corresponding key. ```go CompositeLit = LiteralType LiteralValue . LiteralType = TypeName [ TypeArgs ] . LiteralValue = "{" [ ElementList [ "," ] ] "}" . ElementList = KeyedElement { "," KeyedElement } . KeyedElement = [ Key ":" ] Element . Key = FieldName | Expression | LiteralValue . FieldName = identifier . Element = Expression | LiteralValue . ``` The LiteralType's [underlying type](#underlying-types) `T` must be a [struct](#struct-types) or a [classfile]() type. The types of the elements and keys must be [assignable](#assignability) to the respective field; there is no additional conversion. It is an error to specify multiple elements with the same field name. * A key must be a field name declared in the struct type. * An element list that does not contain any keys must list an element for each struct field in the order in which the fields are declared. * If any element has a key, every element must have a key. * An element list that contains keys does not need to have an element for each struct field. Omitted fields get the zero value for that field. * A literal may omit the element list; such a literal evaluates to the zero value for its type. * It is an error to specify an element for a non-exported field of a struct belonging to a different package. Given the declarations ```go type Point3D struct { x, y, z float64 } type Line struct { p, q Point3D } ``` one may write ```go origin := Point3D{} // zero value for Point3D line := Line{origin, Point3D{y: -4, z: 12.3}} // zero value for line.q.x ``` [Taking the address](#address-operators) of a composite literal generates a pointer to a unique [variable](#variables) initialized with the literal's value. ```go var pointer *Point3D = &Point3D{y: 1000} ``` A parsing ambiguity arises when a composite literal using the TypeName form of the LiteralType appears as an operand between the [keyword](#keywords) and the opening brace of the block of an "if", "for", or "switch" statement, and the composite literal is not enclosed in parentheses, square brackets, or curly braces. In this rare case, the opening brace of the literal is erroneously parsed as the one introducing the block of statements. To resolve the ambiguity, the composite literal must appear within parentheses. ```go if x == (T{a,b,c}[i]) { … } if (x == T{a,b,c}[i]) { … } ``` #### C style string literals TODO ```go c"Hello, world!\n" ``` #### Python string literals TODO ```go py"Hello, world!\n" ``` ## Types ### Boolean types See [Boolean types](spec-mini.md#boolean-types). ### Numeric types See [Numeric types](spec-mini.md#numeric-types). ### String types See [String types](spec-mini.md#string-types). #### C style string types ```go import "c" *c.Char // alias for *int8 ``` #### Python string types ```go import "py" *py.Object // TODO: *py.String? ``` ### Array types See [Array types](spec-mini.md#array-types). An array type T may not have an element of type T, or of a type containing T as a component, directly or indirectly, if those containing types are only array or struct types. ```go // invalid array types type ( T1 [10]T1 // element type of T1 is T1 T2 [10]struct{ f T2 } // T2 contains T2 as component of a struct T3 [10]T4 // T3 contains T3 as component of a struct in T4 T4 struct{ f T3 } // T4 contains T4 as component of array T3 in a struct ) // valid array types type ( T5 [10]*T5 // T5 contains T5 as component of a pointer T6 [10]func() T6 // T6 contains T6 as component of a function type T7 [10]struct{ f []T7 } // T7 contains T7 as component of a slice in a struct ) ``` ### Pointer types See [Pointer types](spec-mini.md#pointer-types). ### Slice types See [Slice types](spec-mini.md#slice-types). ### Map types See [Map types](spec-mini.md#map-types). ### Struct types A struct is a sequence of named elements, called fields, each of which has a name and a type. Field names may be specified explicitly (IdentifierList) or implicitly (EmbeddedField). Within a struct, non-[blank](#blank-identifier) field names must be [unique](). ```go StructType = "struct" "{" { FieldDecl ";" } "}" . FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] . EmbeddedField = [ "*" ] TypeName [ TypeArgs ] . Tag = string_lit . ``` ```go // An empty struct. struct {} // A struct with 6 fields. struct { x, y int u float32 _ float32 // padding A *[]int F func() } ``` A field declared with a type but no explicit field name is called an _embedded field_. An embedded field must be specified as a type name T or as a pointer to a non-interface type name *T, and T itself may not be a pointer type. The unqualified type name acts as the field name. ```go // A struct with four embedded fields of types T1, *T2, P.T3 and *P.T4 struct { T1 // field name is T1 *T2 // field name is T2 P.T3 // field name is T3 *P.T4 // field name is T4 x, y int // field names are x and y } ``` The following declaration is illegal because field names must be unique in a struct type: ```go struct { T // conflicts with embedded field *T and *P.T *T // conflicts with embedded field T and *P.T *P.T // conflicts with embedded field T and *T } ``` A field `f` or [method]() of an embedded field in a struct `x` is called promoted if `x.f` is a legal [selector]() that denotes that field or method `f`. Promoted fields act like ordinary fields of a struct except that they cannot be used as field names in [composite literals]() of the struct. Given a struct type `S` and a [named type](#types) `T`, promoted methods are included in the method set of the struct as follows: * If `S` contains an embedded field `T`, the [method sets]() of `S` and `*S` both include promoted methods with receiver `T`. The method set of `*S` also includes promoted methods with receiver `*T`. * If `S` contains an embedded field `*T`, the method sets of `S` and `*S` both include promoted methods with receiver `T` or `*T`. A field declaration may be followed by an optional string literal _tag_, which becomes an attribute for all the fields in the corresponding field declaration. An empty tag string is equivalent to an absent tag. The tags are made visible through a [reflection interface]() and take part in [type identity]() for structs but are otherwise ignored. ```go struct { x, y float64 "" // an empty tag string is like an absent tag name string "any string is permitted as a tag" _ [4]byte "ceci n'est pas un champ de structure" } // A struct corresponding to a TimeStamp protocol buffer. // The tag strings define the protocol buffer field numbers; // they follow the convention outlined by the reflect package. struct { microsec uint64 `protobuf:"1"` serverIP6 uint64 `protobuf:"2"` } ``` A struct type `T` may not contain a field of type T, or of a type containing T as a component, directly or indirectly, if those containing types are only array or struct types. ```go // invalid struct types type ( T1 struct{ T1 } // T1 contains a field of T1 T2 struct{ f [10]T2 } // T2 contains T2 as component of an array T3 struct{ T4 } // T3 contains T3 as component of an array in struct T4 T4 struct{ f [10]T3 } // T4 contains T4 as component of struct T3 in an array ) // valid struct types type ( T5 struct{ f *T5 } // T5 contains T5 as component of a pointer T6 struct{ f func() T6 } // T6 contains T6 as component of a function type T7 struct{ f [10][]T7 } // T7 contains T7 as component of a slice in an array ) ``` ### Tuple types See [Tuple types](spec-mini.md#tuple-types). #### Brace-Style Construction In addition to function-style construction, tuple supports brace-based initialization using `:` for field assignment: ```go type Point (x int, y int) p1 := Point{x: 10, y: 20} p2 := Point{10, 20} ``` #### Anonymous Tuple Literals with Braces It allows using tuple literals within brace-based composite literals: ```go // Using tuples in struct fields type Record struct { coords (int, int) data (string, bool) } r := Record{ coords: (10, 20), data: ("test", true), } ``` #### Type Compatibility and Reflection At runtime, tuples are implemented as structs with ordinal field names `X_0`, `X_1`, `X_2`, etc.: ```go type Point (x int, y int) // At runtime, Point is equivalent to: // struct { // X_0 int // accessible as .x or .0 at compile time, .X_0 at runtime // X_1 int // accessible as .y or .1 at compile time, .X_1 at runtime // } ``` Tuple types with the same element types (in the same order) have identical underlying structures but are distinct named types: ```go type Point (x int, y int) type Coord (a int, b int) // Point and Coord have identical underlying types but are different types // Conversion is required: c := Coord(p) ``` ### Function types See [Function types](spec-mini.md#function-types). ### Interface types TODO #### Builtin interfaces See [Builtin interfaces](spec-mini.md#builtin-interfaces). ##### The comparable interface The predeclared interface type `comparable` denotes the set of all non-interface types that are strictly comparable. A type is strictly comparable if values of that type can be compared using the `==` and `!=` operators. The `comparable` interface is primarily used as a type constraint in generic code and cannot be used as the type of a variable or struct field: ```go // Example: using comparable as a type constraint func Find[T comparable](slice []T, value T) int { for i, v := range slice { if v == value { return i } } return -1 } ``` Types that are strictly comparable include: - Boolean, numeric, and string types - Pointer types - Channel types - Array types (if their element type is strictly comparable) - Struct types (if all their field types are strictly comparable) Slice, map, and function types are not comparable and cannot be used with `comparable`. ### Channel types TODO ## Expressions ### Commands and calls See [Commands and calls](spec-mini.md#commands-and-calls). ### Operators See [Operators](spec-mini.md#operators). #### Operator precedence See [Operator precedence](spec-mini.md#operator-precedence). #### Arithmetic operators See [Arithmetic operators](spec-mini.md#arithmetic-operators). #### Comparison operators See [Comparison operators](spec-mini.md#comparison-operators). The equality operators == and != apply to operands of comparable types. The ordering operators <, <=, >, and >= apply to operands of ordered types. These terms and the result of the comparisons are defined as follows: * Channel types are comparable. Two channel values are equal if they were created by the same call to [make]() or if both have value `nil`. * Struct types are comparable if all their field types are comparable. Two struct values are equal if their corresponding non-[blank]() field values are equal. The fields are compared in source order, and comparison stops as soon as two field values differ (or all fields have been compared). * Type parameters are comparable if they are strictly comparable (see below). ```go const c = 3 < 4 // c is the untyped boolean constant true type MyBool bool var x, y int var ( // The result of a comparison is an untyped boolean. // The usual assignment rules apply. b3 = x == y // b3 has type bool b4 bool = x == y // b4 has type bool b5 MyBool = x == y // b5 has type MyBool ) ``` A type is _strictly comparable_ if it is comparable and not an interface type nor composed of interface types. Specifically: * Boolean, numeric, string, pointer, and channel types are strictly comparable. * Struct types are strictly comparable if all their field types are strictly comparable. * Array types are strictly comparable if their array element types are strictly comparable. * Type parameters are strictly comparable if all types in their type set are strictly comparable. #### Logical operators See [Logical operators](spec-mini.md#logical-operators). ### Address operators See [Address operators](spec-mini.md#address-operators). ### Send/Receive operator TODO ### Conversions See [Conversions](spec-mini.md#conversions). TODO ## Statements TODO ## Built-in functions ### Appending to and copying slices See [Appending to and copying slices](spec-mini.md#appending-to-and-copying-slices). ### Clear TODO ### Close TODO ### Manipulating complex numbers ================================================ FILE: doc/string.md ================================================ # String Type XGo provides powerful and flexible string handling capabilities. Strings are sequences of characters used to represent text, and they are one of the most commonly used data types in programming. ## String Literals XGo provides multiple ways to represent strings, from simple literals to complex Unicode characters. ### Basic String Literals In XGo, you can create strings using double quotes: ```go name := "Bob" message := "Hello, World!" empty := "" ``` ### Raw String Literals XGo also supports raw string literals using backticks (`` ` ``). Raw strings treat backslashes and other special characters literally, making them ideal for regular expressions, file paths, and multi-line text: ```go // Raw strings ignore escape sequences path := `C:\Users\Bob\Documents` // Backslashes are literal regex := `\d+\.\d+` // No need to escape backslashes // Multi-line raw strings multiline := `Line 1 Line 2 Line 3` // JSON or code snippets json := `{ "name": "Alice", "age": 30 }` // SQL queries query := `SELECT * FROM users WHERE age > 18 ORDER BY name` ``` **Key differences between double-quoted and raw strings:** | Feature | Double-quoted `"..."` | Raw (backtick) `` `...` `` | |---------|----------------------|---------------------------| | Escape sequences | Processed (`\n`, `\t`, etc.) | Literal (ignored) | | Multi-line | Requires `\n` | Natural line breaks | | Backslashes | Must escape `\\` | Literal `\` | | Interpolation | Supported `${...}` | Not supported | | Use case | General strings, interpolation | Paths, regex, multi-line text | ```go // Comparison example escaped := "Line 1\nLine 2" // Two lines when printed raw := `Line 1\nLine 2` // Literal \n characters echo escaped // Output: // Line 1 // Line 2 echo raw // Output: // Line 1\nLine 2 ``` ### Escape Sequences XGo supports various escape sequences for special characters: ```go // Common escape sequences newline := "Line 1\nLine 2" // Newline tab := "Column1\tColumn2" // Tab quote := "She said \"Hello\"" // Double quote backslash := "Path: C:\\files" // Backslash // Octal escape notation \### where # is an octal digit octalChar := "\141a" // aa // Unicode can be specified as \u#### where # is a hex digit // It will be converted internally to its UTF-8 representation star := "\u2605" // ★ heart := "\u2665" // ♥ ``` #### Common Escape Sequences | Sequence | Description | Example | |----------|-------------|---------| | `\n` | Newline | `"Line 1\nLine 2"` | | `\t` | Tab | `"Name:\tAlice"` | | `\\` | Backslash | `"C:\\path"` | | `\"` | Double quote | `"He said \"Hi\""` | | `\###` | Octal character | `"\141"` (a) | | `\u####` | Unicode character | `"\u2605"` (★) | ## String Immutability String values are immutable in XGo. Once created, you cannot modify individual characters: ```go failcompile s := "hello 🌎" s[0] = `H` // Error: not allowed ``` To modify a string, you must create a new one: ```go s := "hello" s = "Hello" // OK: assigning a new string s = s + " world" // OK: creating a new concatenated string ``` ## String Operations ### Indexing and Slicing #### Indexing Indexing a string returns a `byte` value (not a `rune` or another `string`): ```go name := "Bob" echo name[0] // 66 (ASCII value of 'B') echo name[1] // 111 (ASCII value of 'o') echo name[2] // 98 (ASCII value of 'b') ``` **Warning**: Indexing into multi-byte characters (like Chinese characters or emojis) will return individual bytes, which may not represent a complete character. #### Slicing You can extract substrings using slice notation: ```go name := "Bob" echo name[1:3] // ob (from index 1 to 3, exclusive) echo name[:2] // Bo (from start to index 2) echo name[2:] // b (from index 2 to end) s := "Hello, World!" echo s[0:5] // Hello echo s[:5] // Hello (start defaults to 0) echo s[7:] // World! (end defaults to string length) ``` Slicing syntax: - `s[start:end]` - from index `start` to `end` (exclusive) - `s[:end]` - from beginning to `end` - `s[start:]` - from `start` to end of string ### String Conversion #### Converting Strings to Integers Strings can be easily converted to integers: ```go s := "12" a, err := s.int // Returns value and error (safe conversion) b := s.int! // Panics if s isn't a valid integer (unsafe conversion) // Example with error handling if num, err := s.int; err == nil { echo "Valid number:", num } else { echo "Invalid number" } ``` #### Converting Other Types to Strings To convert other types to strings, use the `.string` property: ```go age := 10 ageStr := age.string echo "age = " + ageStr // age = 10 pi := 3.14159 piStr := pi.string echo "π = " + piStr // π = 3.14159 ``` ## String Operators ### Concatenation Use the `+` operator to concatenate strings: ```go name := "Bob" bobby := name + "by" echo bobby // Bobby s := "Hello " s += "world" echo s // Hello world // Multiple concatenations greeting := "Hello" + " " + "World" + "!" echo greeting // Hello World! ``` ### Type Consistency XGo operators require values of the same type on both sides. You cannot concatenate an integer directly to a string: ```go failcompile age := 10 echo "age = " + age // Error: not allowed ``` You must convert `age` to a string first: ```go age := 10 echo "age = " + age.string // age = 10 ``` ## String Interpolation XGo supports string interpolation using `${expression}` syntax, which provides a cleaner alternative to concatenation: ```go age := 10 echo "age = ${age}" // age = 10 name := "Alice" greeting := "Hello, ${name}!" echo greeting // Hello, Alice! ``` ### Complex Interpolation You can use any expression inside `${...}`: ```go // Arithmetic expressions x := 5 y := 3 echo "${x} + ${y} = ${x + y}" // 5 + 3 = 8 // Function calls and method calls name := "bob" echo "Hello, ${name.toUpper}!" // Hello, BOB! // Complex example host := "example.com" page := 0 limit := 20 url := "https://${host}/items?page=${page+1}&limit=${limit}" echo url // https://example.com/items?page=1&limit=20 ``` ### Escaping the Dollar Sign To include a literal `$` in a string, use `$$`: ```go echo "Price: $$50" // Price: $50 echo "Total: $$${100 + 50}" // Total: $150 ``` ## String Methods XGo provides built-in methods for common string operations. These methods do not modify the original string (strings are immutable) but return new strings. ### String Length You can get the length of a string using the `len` method: ```go name := "Bob" echo name.len // 3 chinese := "你好" echo chinese.len // 6 (bytes, not characters - each Chinese character is 3 bytes in UTF-8) ``` **Important**: `len` returns the number of bytes, not the number of characters. For strings containing non-ASCII characters (like Chinese, emojis), the byte length will be larger than the character count. ### Case Conversion ```go // Convert to uppercase echo "Hello".toUpper // HELLO echo "hello world".toUpper // HELLO WORLD // Convert to lowercase echo "Hello".toLower // hello echo "HELLO WORLD".toLower // hello world // Capitalize first letter of each word echo "hello world".capitalize // Hello World echo "the quick brown fox".capitalize // The Quick Brown Fox ``` ### String Repetition ```go // Repeat a string n times echo "XGo".repeat(3) // XGoXGoXGo echo "Ha".repeat(5) // HaHaHaHaHa echo "-".repeat(10) // ---------- // Useful for formatting separator := "=".repeat(40) echo separator echo "Title" echo separator ``` ### String Replacement ```go // Replace all occurrences echo "Hello".replaceAll("l", "L") // HeLLo echo "banana".replaceAll("a", "o") // bonono // Practical example text := "The quick brown fox" censored := text.replaceAll("fox", "***") echo censored // The quick brown *** ``` ### Joining Strings Join a list of strings into a single string with a separator: ```go // Join with comma fruits := ["apple", "banana", "cherry"] echo fruits.join(",") // apple,banana,cherry // Join with space words := ["Hello", "World"] echo words.join(" ") // Hello World // Join without separator letters := ["H", "e", "l", "l", "o"] echo letters.join("") // Hello // Practical example with newlines lines := ["Line 1", "Line 2", "Line 3"] text := lines.join("\n") echo text // Output: // Line 1 // Line 2 // Line 3 ``` ### Splitting Strings Split a string into a list of substrings using a separator: ```go // Split by delimiter subjects := "Math-English-Science-History" subjectList := subjects.split("-") echo subjectList // [Math English Science History] // Split by space sentence := "The quick brown fox" words := sentence.split(" ") echo words // [The quick brown fox] // Split CSV data csv := "Alice,30,Engineer" fields := csv.split(",") echo fields // [Alice 30 Engineer] // Process split results for field in fields { echo "Field:", field } ``` ## Characters and Bytes In XGo, strings can be traversed by character (`rune`) or by byte. Understanding the difference is crucial when working with non-ASCII characters. ### Character Encoding Basics - **ASCII characters** (like English letters, digits): 1 byte per character - **Non-ASCII characters** (like Chinese, emojis): 2-4 bytes per character (UTF-8 encoding) - **`len`** returns byte count, not character count - **Indexing** returns bytes, not complete characters ### Iterating by Character (Rune) Use `for in` loop to iterate over characters (runes): ```go // English text (1 byte per character) s := "Hello" for c in s { echo c } // Output: // H // e // l // l // o // Mixed text with Chinese characters s := "你好XGo" for c in s { echo c } // Output: // 你 // 好 // X // G // o ``` ### Iterating by Byte Use traditional index-based loop to iterate over bytes: ```go s := "Hello" for i := 0; i < len(s); i++ { echo s[i] // Prints byte values: 72, 101, 108, 108, 111 } // With non-ASCII characters s := "你好XGo" for i := 0; i < len(s); i++ { echo s[i] } // Outputs byte values (each Chinese character is 3 bytes) // For '你': 228, 189, 160 // For '好': 229, 165, 189 // For 'X': 88 // For 'G': 71 // For 'o': 111 ``` ### Working with Non-ASCII Characters **⚠️ Important Warnings**: 1. **Length discrepancy**: `len()` returns bytes, not character count 2. **Indexing multi-byte characters**: Accessing individual bytes of multi-byte characters yields incomplete data 3. **Use character iteration**: When processing text with non-ASCII characters, use `for c in s` instead of index-based loops ```go // Example: Chinese characters s := "你好" // WRONG: This returns byte count, not character count echo s.len // 6 (bytes) // WRONG: This returns part of a character echo s[0] // 228 (first byte of '你') // CORRECT: Count characters count := 0 for _ in s { count++ } echo count // 2 (characters) // CORRECT: Process characters for char in s { echo char // Prints: 你, then 好 } ``` ### Practical Example: Character vs Byte Processing ```go // Process mixed text (need character iteration) mixed := "Hello你好" echo "Byte length:", mixed.len // 11 (5 ASCII + 6 for Chinese) charCount := 0 for _ in mixed { charCount++ } echo "Character count:", charCount // 7 // Extract characters correctly for i, char in mixed { echo "Character ${i}: ${char}" } // Output: // Character 0: H // Character 1: e // Character 2: l // Character 3: l // Character 4: o // Character 5: 你 // Character 8: 好 ``` ## Common Patterns ### String Validation ```go // Check if string is a valid integer input := "12345" if num, err := input.int; err == nil { echo "Valid number:", num } else { echo "Invalid number" } // Check string length username := "alice" if username.len < 3 { echo "Username too short" } else if username.len > 20 { echo "Username too long" } else { echo "Username OK" } ``` ### String Formatting ```go // Build formatted strings name := "Alice" age := 30 city := "New York" // Using interpolation profile := "Name: ${name}, Age: ${age}, City: ${city}" echo profile // Building multi-line strings header := "=".repeat(40) title := "User Profile" content := "${header}\n${title}\n${header}\nName: ${name}\nAge: ${age}\nCity: ${city}" echo content ``` ### String Parsing ```go // Parse CSV data csv := "Alice,30,Engineer,New York" fields := csv.split(",") name := fields[0] age := fields[1].int! job := fields[2] city := fields[3] echo "Name: ${name}, Age: ${age}, Job: ${job}, City: ${city}" // Parse key-value pairs config := "host=localhost;port=8080;debug=true" pairs := config.split(";") settings := {} for pair in pairs { parts := pair.split("=") key := parts[0] value := parts[1] settings[key] = value } echo settings // map[host:localhost port:8080 debug:true] ``` ### String Templates ```go // Email template func generateEmail(name, action, link string) string { return "Hello ${name},\n\nPlease click the link below to ${action}:\n${link}\n\nBest regards,\nThe Team" } email := generateEmail("Alice", "verify your email", "https://example.com/verify") echo email ``` ### Text Processing ```go // Word count text := "The quick brown fox jumps over the lazy dog" words := text.split(" ") echo "Word count:", words.len // Capitalize each word capitalized := [] for word in words { capitalized = append(capitalized, word.capitalize) } result := capitalized.join(" ") echo result // The Quick Brown Fox Jumps Over The Lazy Dog // Remove extra spaces messyText := " Too many spaces " cleaned := [s for s in messyText.split(" ") if s != ""].join(" ") echo cleaned // Too many spaces ``` ### Building Complex Strings ```go // Building a URL with query parameters func buildURL(base string, params map[string]any) string { if params.len == 0 { return base } queryParts := [] for key, value in params { queryParts = append(queryParts, "${key}=${value}") } return "${base}?${queryParts.join("&")}" } url := buildURL("https://api.example.com/search", { "q": "xgo", "page": 1, "limit": 20, }) echo url // https://api.example.com/search?q=xgo&page=1&limit=20 // Building a report func buildReport(title string, items []string) string { separator := "=".repeat(50) header := "${separator}\n${title}\n${separator}" itemList := [] for i, item in items { itemList = append(itemList, "${i+1}. ${item}") } return "${header}\n${itemList.join("\n")}" } report := buildReport("Task List", ["Review code", "Write tests", "Update docs"]) echo report ``` ## Best Practices 1. **Use string interpolation** (`"${expr}"`) instead of concatenation for better readability 2. **Use `.string` method** to convert other types to strings 3. **Check string length** before accessing indices to avoid runtime errors 4. **Use character iteration** (`for c in s`) when processing text with non-ASCII characters 5. **Prefer string methods** over manual manipulation for common operations 6. **Handle conversion errors** when converting strings to numbers using the comma-ok form 7. **Remember strings are immutable** - methods return new strings rather than modifying originals 8. **Use escape sequences** for special characters rather than trying to insert them literally 9. **Be aware of byte vs. character distinction** when working with internationalized text 10. **Use appropriate string methods** (`.toUpper`, `.toLower`, etc.) for case-insensitive operations 11. **Use raw strings** (backticks) for paths, regular expressions, and multi-line text to avoid escape sequence hassles 12. **Choose the right string literal type**: double quotes for interpolation and escape sequences, backticks for literal text ## Performance Tips 1. **Avoid excessive concatenation in loops**: Build string slices and join them instead ```go // Less efficient result := "" for i := 0; i < 1000; i++ { result += "item${i}," } // More efficient parts := [] for i := 0; i < 1000; i++ { parts = append(parts, "item${i}") } result := parts.join(",") ``` 2. **Use string interpolation**: It's more efficient than multiple concatenations ```go // Less efficient message := "Hello, " + name + "! You are " + age.string + " years old." // More efficient message := "Hello, ${name}! You are ${age} years old." ``` 3. **Reuse string slices**: When splitting strings multiple times, consider reusing slices 4. **Consider byte operations**: For performance-critical ASCII-only operations, byte-level processing can be faster 5. **Preallocate when building large strings**: If you know the approximate size, preallocate capacity ================================================ FILE: doc/struct-vs-tuple.md ================================================ # Tuples vs. Structs In the XGo programming language, tuple and struct are two distinct ways of organizing data. While both can combine multiple values together, they differ significantly in their type system, visibility rules, runtime characteristics, and other aspects. Understanding these differences is crucial for choosing the right data structure. ## Type Identity and Instantiation Tuple and struct behave very differently in both the type system and how instances are created. **Type identity:** Tuple types with the same element structure have identical underlying representations, meaning `(a int, b string)` and `(c int, d string)` are **identical** types. However, named tuple types are distinct. For example, `type Point (x int, y int)` and `type Coord (a int, b int)` define two different types, even though they have the same underlying structure. In this context, tuple field names are compile-time aliases, but named tuple types provide nominal typing. In contrast, struct type checking is much stricter. Even if two structs have exactly the same field types and order, they are considered different types if their definitions differ or their field names are different. This characteristic makes struct more suitable for expressing data structures with clear semantics. **Initialization syntax:** A key advantage of tuple is its **unified syntax philosophy**—both tuple type definitions and tuple initialization use **function-like syntax**. Defining a tuple type resembles a function signature, and creating a tuple instance resembles a function call. This consistency makes tuples intuitive and reduces cognitive overhead. Furthermore, tuple initialization syntax is **identical to type conversion syntax**. Whether you're creating a new tuple or converting between compatible tuple types, you use the same `TypeName(values)` pattern. This means developers only need to learn one syntax pattern that works for function calls, tuple creation, and type conversions—a remarkable level of consistency. Struct, however, requires learning multiple distinct syntaxes: curly braces with field-value pairs for initialization (`Point{3, 4}`), and a different pattern for type conversion. This adds conceptual complexity that doesn't align with the rest of the language's function-centric design. ### Tuple Example ```go // Anonymous tuples: structural equivalence var t1 (int, string) = (42, "hello") var t2 (int, string) = t1 // ✓ OK: same structure // Named tuples: nominal typing but structurally convertible type Point (x, y int) // Definition: function-like syntax type Coord (a, b int) // Tuple initialization has two equivalent forms: // Form 1: positional arguments (like function calls) var pt1 = Point(3, 4) // Form 2: named arguments (explicit field names) var pt2 = Point(x = 3, y = 4) // Both forms use function-call syntax // Type conversion: same function-like syntax! var cd = Coord(pt1) // Type conversion (structurally compatible) // Notice: Point(3, 4) creates a tuple, Coord(pt1) converts types // Both use identical syntax pattern - no new syntax to learn ``` ### Struct Example ```go // Structs: always require exact type match type Point struct { // Definition: requires 'struct' keyword X, Y int } type Coord struct { X, Y int } // Struct initialization also has two equivalent forms: // Form 1: positional arguments var pt1 = Point{3, 4} // Form 2: named fields (explicit field names) var pt2 = Point{X: 3, Y: 4} // Type conversion still requires explicit field access var cd = Coord(pt1) // ✗ Error: different types var cd = Coord{pt1.X, pt1.Y} // Positional conversion // Struct requires learning the {field: value} syntax separately // No unified pattern across initialization and conversion ``` ## Differences in Visibility Rules Regarding visibility control, tuple adopts a simpler strategy: all fields in a tuple are **always public**, with no concept of lowercase letters indicating private access. This design reflects tuple's positioning as a lightweight data container—it's primarily used for temporarily combining data rather than encapsulating complex object state. Struct, on the other hand, fully supports Go-style visibility control, using uppercase and lowercase initial letters to distinguish between public and private fields, providing necessary support for modular design and encapsulation. ## Runtime Reflection Differences When it comes to runtime reflection, the differences become even more pronounced. After performing reflect operations on a tuple, its field names become `X_0`, `X_1`, and so on. This means that the friendly field names used at compile time **only exist during compilation** and are erased at runtime. In contrast, struct field names are fully preserved at runtime, which enables struct to support various reflection-based functionalities such as serialization, ORM mapping, configuration parsing, and more. This is a significant advantage of struct over tuple. ## Methods and Object-Orientation In XGo's design philosophy, **tuple does not encourage objectification**, meaning it's not recommended to add methods to tuples. This aligns with tuple's positioning as a simple data container—it should remain lightweight and simple, avoiding the burden of excessive behavioral logic. If methods are genuinely needed for a data structure, XGo recommends using [classfile](classfile.md) to achieve more complete object-oriented features. ## Practical Application Limitations In practical applications, these differences lead to obvious usage limitations. For example, in scenarios like **reading configuration files**, tuple cannot replace struct. Configuration parsing typically relies on reflection mechanisms to map configuration items to data structure fields, and since tuple loses field name information at runtime, it cannot support this kind of mapping. More broadly, almost all **functionalities that depend on reflect must use struct**. Common scenarios including JSON/XML serialization, database ORM, dependency injection, and struct tag parsing all require the complete runtime type information that struct provides. ## XGo MiniSpec Recommendation An important consideration when choosing between tuple and struct is their position in **XGo's recommended syntax set (XGo MiniSpec)**. Tuple is included in the XGo MiniSpec, while struct is not. This design choice reflects XGo's philosophy that **tuple combined with [classfile](classfile.md) can completely replace all scenarios where struct is used in Go**. The combination provides: - Tuple for lightweight data containers and function return values - Classfile for complex types requiring encapsulation, methods, and object-oriented features By promoting this tuple + classfile approach, XGo MiniSpec aims to provide a more streamlined and consistent way of organizing data and behavior, reducing the conceptual overhead of having multiple overlapping constructs. ## Summary Tuple and struct each have their appropriate use cases in XGo. Tuple is suitable as a lightweight, temporary data container for returning multiple values from functions or simple data combinations. Struct, however, is more appropriate for defining data types with clear semantics, especially in scenarios requiring encapsulation, reflection support, or method binding. For developers following XGo MiniSpec, the recommended approach is to use tuple for simple data combinations and classfile when object-oriented features are needed, avoiding struct altogether. This provides a cleaner conceptual model while maintaining full functionality. Understanding these differences helps developers choose the appropriate data structure for the right scenarios, leading to clearer and more efficient code. ================================================ FILE: doc/xgo-vs-go.md ================================================ XGo vs. Go ====== What's the difference between XGo and Go? The goal of Go is to provide a way to easily build simple, reliable, and efficient software. It wants to be a better C. The goal of XGo is to achieve a natural fusion of engineering and low-code. It wants to be a better Python. The essence of low-code is non-professionals oriented. Today's mainstream programming language designers still take non-professionals as a niche group. But XGo take this group as vital, and they are a vital breakthrough in today's programming language revolution. The Python language appeared very early (1991), but its design idea is very far-sighted, and it is a rare non-professionals oriented programming language so far. There are quite a few languages that look relatively concise, such as Ruby and CoffeeScript, but they have many language magics so that people feel they are flexible and powerful, but not easy to master. They cannot be regarded as non-professionals oriented. Python is very popular today, but it's hard to call it an engineering language. Most people just take it as a tool for rapid prototyping. Is it possible to minimize the complexity of engineering and present it in a low-code form? XGo hopes to explore a new path at this point. ## Fundamental Differences in Design Philosophy ### XGo: A Programming Language for Everyone XGo's core design philosophy is to "enable everyone to become a builder of the world". This is reflected in three key design goals: - **For engineering**: working in the simplest language that can be mastered by children - **For STEM education**: studying an engineering language that can be used for work in the future - **For data science**: communicating with engineers in the same language ### Go: An Engineering Language for Systems Programming Go's design focus is on engineering practices for systems programming, emphasizing performance, concurrency, and maintainability for large projects. ## Differences in Abstraction Levels ### XGo: Specific Domain Friendliness (SDF) XGo adopts a unique design philosophy: instead of creating Domain Specific Languages (DSL), it provides domain-friendly support. The core of this design philosophy is: - Don't define a language for specific domain - Abstract domain knowledge for it ### Go: General-Purpose Systems Programming Go focuses on general-purpose systems programming, providing abstraction capabilities through interfaces and composition, but doesn't specifically optimize for particular application domains. ## Differences in Learning Curve Design ### XGo: Progressive Complexity XGo's design allows starting from the simplest scripts and gradually scaling to large projects: - Simple and easy to understand - Smaller syntax set than Python in best practices - Easy to build large projects from Go's good engineering foundation ### Go: Consistent Complexity Go was designed from the beginning for systems programming, with a relatively flat learning curve but a higher starting point. ## Differences in Ecosystem Integration Philosophy ### XGo: Multi-Language Ecosystem Fusion XGo's design formula `XGo := C * Go * Python * JavaScript + Scratch` reflects its philosophy of integrating multiple programming paradigms, aiming to break down language barriers. ### Go: Focus on Go Ecosystem Go focuses on building and maintaining its own ecosystem. While it can integrate with C, this is not a core design goal. ## Notes XGo's design philosophy is essentially about "lowering the programming barrier while maintaining engineering capabilities," which fundamentally differs from Go's approach of "providing simple and efficient tools for systems programming." XGo is more like an attempt at "programming democratization," while Go is a product of "engineering efficiency optimization." ### Give a Star! ⭐ If you like or are using XGo to learn or start your projects, please give it a star. Thanks! ================================================ FILE: doc/z_gop.go ================================================ /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package doc import ( "go/doc" "strings" ) const ( goptPrefix = "Gopt_" // template method gopoPrefix = "Gopo_" // overload function/method gopxPrefix = "Gopx_" // type as parameters function/method gopPackage = "GopPackage" xgoPackage = "XGoPackage" ) func isXGoPackage(in *doc.Package) bool { for _, v := range in.Consts { for _, name := range v.Names { if name == gopPackage || name == xgoPackage { return true } } } return false } func isGopoConst(name string) bool { return strings.HasPrefix(name, gopoPrefix) } func hasGopoConst(in *doc.Value) bool { for _, name := range in.Names { if isGopoConst(name) { return true } } return false } func isOverload(name string) bool { n := len(name) return n > 3 && name[n-3:n-1] == "__" } // Func (no _ func name) // _Func (with _ func name) // TypeName_Method (no _ method name) // _TypeName__Method (with _ method name) func checkTypeMethod(name string) mthd { if pos := strings.IndexByte(name, '_'); pos >= 0 { if pos == 0 { t := name[1:] if pos = strings.Index(t, "__"); pos <= 0 { return mthd{"", t} // _Func } return mthd{t[:pos], t[pos+2:]} // _TypeName__Method } return mthd{name[:pos], name[pos+1:]} // TypeName_Method } return mthd{"", name} // Func } ================================================ FILE: doc/z_test.go ================================================ /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package doc import ( "bytes" "fmt" "go/ast" "go/doc" "go/format" "go/parser" "go/token" "os" "path" "strings" "testing" ) func TestToIndex(t *testing.T) { if ret := toIndex('a'); ret != 10 { t.Fatal("toIndex:", ret) } defer func() { if e := recover(); e != "invalid character out of [0-9,a-z]" { t.Fatal("panic:", e) } }() toIndex('A') } func TestCheckTypeMethod(t *testing.T) { if ret := checkTypeMethod("_Foo_a"); ret.typ != "" || ret.name != "Foo_a" { t.Fatal("checkTypeMethod:", ret) } if ret := checkTypeMethod("Foo_a"); ret.typ != "Foo" || ret.name != "a" { t.Fatal("checkTypeMethod:", ret) } } func TestIsXGoPackage(t *testing.T) { if isXGoPackage(&doc.Package{}) { t.Fatal("isXGoPackage: true?") } } func TestDocRecv(t *testing.T) { if _, ok := docRecv(&ast.Field{}); ok { t.Fatal("docRecv: ok?") } } func printVal(parts []string, format string, val any) []string { return append(parts, fmt.Sprintf(format, val)) } func printFuncDecl(parts []string, fset *token.FileSet, decl *ast.FuncDecl) []string { var b bytes.Buffer if e := format.Node(&b, fset, decl); e != nil { panic(e) } return append(parts, b.String()) } func printFunc(parts []string, fset *token.FileSet, format string, fn *doc.Func) []string { parts = printVal(parts, format, fn.Name) if fn.Recv != "" { parts = printVal(parts, "Recv: %s", fn.Recv) } parts = printVal(parts, "Doc: %s", fn.Doc) parts = printFuncDecl(parts, fset, fn.Decl) return parts } func printFuncs(parts []string, fset *token.FileSet, fns []*doc.Func) []string { for _, fn := range fns { parts = printFunc(parts, fset, "== Func %s ==", fn) } return parts } func printType(parts []string, fset *token.FileSet, typ *doc.Type) []string { parts = append(parts, fmt.Sprintf("== Type %s ==", typ.Name)) for _, fn := range typ.Funcs { parts = printFunc(parts, fset, "- Func %s -", fn) } for _, fn := range typ.Methods { parts = printFunc(parts, fset, "- Method %s -", fn) } return parts } func printTypes(parts []string, fset *token.FileSet, types []*doc.Type) []string { for _, typ := range types { parts = printType(parts, fset, typ) } return parts } func printPkg(fset *token.FileSet, in *doc.Package) string { var parts []string parts = printFuncs(parts, fset, in.Funcs) parts = printTypes(parts, fset, in.Types) return strings.Join(append(parts, ""), "\n") } func testPkg(t *testing.T, filename string, src any, expected string) { t.Helper() fset := token.NewFileSet() f, err := parser.ParseFile(fset, filename, src, parser.ParseComments) if err != nil { t.Fatal(err) } pkg, err := doc.NewFromFiles(fset, []*ast.File{f}, "foo") if err != nil { t.Fatal(err) } pkg = Transform(pkg) if ret := printPkg(fset, pkg); ret != expected { t.Fatalf("got:\n%s\nexpected:\n%s\n", ret, expected) } } func testFromDir(t *testing.T, sel, relDir string) { dir, err := os.Getwd() if err != nil { t.Fatal("Getwd failed:", err) } dir = path.Join(dir, relDir) fis, err := os.ReadDir(dir) if err != nil { t.Fatal("ReadDir failed:", err) } for _, fi := range fis { name := fi.Name() if strings.HasPrefix(name, "_") || !fi.IsDir() || (sel != "" && !strings.Contains(name, sel)) { continue } t.Run(name, func(t *testing.T) { testDir := dir + "/" + name out, e := os.ReadFile(testDir + "/out.expect") if e != nil { t.Fatal(e) } testPkg(t, testDir+"/in.go", nil, string(out)) }) } } func TestFromTestdata(t *testing.T) { testFromDir(t, "", "./_testdata") } ================================================ FILE: doc/z_transform.go ================================================ /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package doc import ( "go/ast" "go/doc" "go/token" "sort" "strconv" "strings" ) type mthd struct { typ string name string } type omthd struct { mthd idx int } type typExtra struct { t *doc.Type funcs []*doc.Func methods []*doc.Func } type transformCtx struct { overloadFuncs map[mthd][]omthd // realName => []overloadName typs map[string]*typExtra orders map[*doc.Func]int } func (p *transformCtx) finish(in *doc.Package) { for _, ex := range p.typs { if t := ex.t; t != nil { t.Funcs = p.mergeFuncs(t.Funcs, ex.funcs) t.Methods = p.mergeFuncs(t.Methods, ex.methods) } else { in.Funcs = p.mergeFuncs(in.Funcs, ex.funcs) } } } func (p *transformCtx) mergeFuncs(a, b []*doc.Func) []*doc.Func { if len(b) == 0 { return a } a = append(a, b...) sort.Slice(a, func(i, j int) bool { fa, fb := a[i], a[j] aName, bName := fa.Name, fb.Name if aName == bName { return p.orders[fa] < p.orders[fb] } return aName < bName }) return a } func newCtx(in *doc.Package) *transformCtx { typs := make(map[string]*typExtra, len(in.Types)+1) typs[""] = &typExtra{} // global functions for _, t := range in.Types { typs[t.Name] = &typExtra{t: t} } return &transformCtx{ overloadFuncs: make(map[mthd][]omthd), typs: typs, orders: make(map[*doc.Func]int), } } func newIdent(name string, in *ast.Ident) *ast.Ident { ret := *in ret.Name = name return &ret } func newFuncDecl(name string, in *ast.FuncDecl) *ast.FuncDecl { ret := *in ret.Name = newIdent(name, ret.Name) return &ret } func newMethodDecl(name string, in *ast.FuncDecl) *ast.FuncDecl { ret := *in if ret.Recv == nil { ft := *ret.Type params := *ft.Params ret.Recv = &ast.FieldList{List: params.List[:1]} params.List = params.List[1:] ft.Params = ¶ms ret.Type = &ft } ret.Name = newIdent(name, ret.Name) return &ret } func docRecv(recv *ast.Field) (_ string, ok bool) { switch v := recv.Type.(type) { case *ast.Ident: return v.Name, true case *ast.StarExpr: if t, ok := v.X.(*ast.Ident); ok { return "*" + t.Name, true } } return } func newMethod(name string, in *doc.Func) *doc.Func { ret := *in ret.Name = name ret.Decl = newMethodDecl(name, in.Decl) if recv, ok := docRecv(ret.Decl.Recv.List[0]); ok { ret.Recv = recv } // TODO(xsw): alias doc - ret.Doc return &ret } func newFunc(name string, in *doc.Func) *doc.Func { ret := *in ret.Name = name ret.Decl = newFuncDecl(name, in.Decl) // TODO(xsw): alias doc - ret.Doc return &ret } func setOrder(ctx *transformCtx, in *doc.Func, order int) *doc.Func { ctx.orders[in] = order return in } func buildFunc(ctx *transformCtx, overload omthd, in *doc.Func) { if ex, ok := ctx.typs[overload.typ]; ok { if ex.t != nil { // method ex.methods = append(ex.methods, setOrder(ctx, newMethod(overload.name, in), overload.idx)) } else { ex.funcs = append(ex.funcs, setOrder(ctx, newFunc(overload.name, in), overload.idx)) } } } func toIndex(c byte) int { if c >= '0' && c <= '9' { return int(c - '0') } if c >= 'a' && c <= 'z' { return int(c - ('a' - 10)) } panic("invalid character out of [0-9,a-z]") } func transformFunc(ctx *transformCtx, t *doc.Type, in *doc.Func, method bool) { var m mthd if method { m.typ = t.Name } m.name = in.Name if overloads, ok := ctx.overloadFuncs[m]; ok { for _, overload := range overloads { buildFunc(ctx, overload, in) } } if isOverload(in.Name) { order := toIndex(in.Name[len(in.Name)-1]) in.Name = in.Name[:len(in.Name)-3] in.Decl.Name.Name = in.Name ctx.orders[in] = order } } func transformFuncs(ctx *transformCtx, t *doc.Type, in []*doc.Func, method bool) { for _, f := range in { transformFunc(ctx, t, f, method) } } func transformTypes(ctx *transformCtx, in []*doc.Type) { for _, t := range in { transformFuncs(ctx, t, t.Funcs, false) transformFuncs(ctx, t, t.Methods, true) } } func transformGopo(ctx *transformCtx, name, val string) { overload := checkTypeMethod(name[len(gopoPrefix):]) parts := strings.Split(val, ",") for idx, part := range parts { if part == "" { continue } var real mthd if part[0] == '.' { real = mthd{overload.typ, part[1:]} } else { real = mthd{"", part} } ctx.overloadFuncs[real] = append(ctx.overloadFuncs[real], omthd{overload, idx}) } } func transformConstSpec(ctx *transformCtx, vspec *ast.ValueSpec) { name := vspec.Names[0].Name if isGopoConst(name) { if lit, ok := vspec.Values[0].(*ast.BasicLit); ok { if lit.Kind == token.STRING { if val, e := strconv.Unquote(lit.Value); e == nil { transformGopo(ctx, name, val) } } } } } func transformConst(ctx *transformCtx, in *doc.Value) { if hasGopoConst(in) { for _, spec := range in.Decl.Specs { vspec := spec.(*ast.ValueSpec) transformConstSpec(ctx, vspec) } } } func transformConsts(ctx *transformCtx, in []*doc.Value) { for _, v := range in { transformConst(ctx, v) } } // Transform converts a Go doc package to an XGo doc package. func Transform(in *doc.Package) *doc.Package { if isXGoPackage(in) { ctx := newCtx(in) transformConsts(ctx, in.Consts) transformFuncs(ctx, nil, in.Funcs, false) transformTypes(ctx, in.Types) ctx.finish(in) } return in } ================================================ FILE: dql/README.md ================================================ DQL - DOM Query Language ===== DQL is a universal, expressive query language for structured and tree-shaped data, built for [XGo](https://github.com/goplus/xgo). It provides a unified query interface across JSON, XML, HTML, ASTs, file systems, and any other domain that can be modeled as a tree. --- ## Table of Contents - [Overview](#overview) - [Supported Domains](#supported-domains) - [Core Concepts](#core-concepts) - [Syntax Reference](#syntax-reference) - [Query Examples](#query-examples) - [JSON](#json) - [HTML](#html) - [XGo AST](#xgo-ast) - [File System](#file-system) - [Error Handling](#error-handling) - [Performance and Caching](#performance-and-caching) - [Implementing a NodeSet](#implementing-a-nodeset) - [Standard Errors](#standard-errors) --- ## Overview DQL brings a concise, chainable query syntax to XGo. Think of it as XPath or CSS selectors — but designed for XGo's type system, with first-class support for lazy evaluation, error propagation, and domain-specific adaptations. ```go // Find all hyperlinks in an HTML document for a in doc.**.a { if url := a.$href; url != "" { echo url } } // Filter JSON records for animal in doc.animals.*@($class == "zebra") { echo animal.$at } // Walk the file system for e in fs`.`.**.file.match("*.xgo") { echo e.path } ``` --- ## Supported Domains DQL is not tied to any single format. Any data that can be represented as a tree can be queried with DQL: - **JSON** — query fields, arrays, and nested objects - **YAML** — same model as JSON - **HTML / XML** — traverse elements, attributes, and text - **Go / XGo AST** — inspect and filter syntax trees - **File System** — walk directories, match files by name or extension - **Any custom tree structure** — implement the NodeSet interface --- ## Core Concepts ### NodeSet Everything in DQL is a `NodeSet`. Queries consume a NodeSet and produce a new NodeSet. The entry point is typically the root document node, called `doc`. ```go doc NodeSet // root of the query ``` NodeSets are **lazily evaluated** — they describe a query plan that is executed only when you actually iterate or read data from them. This keeps memory usage low and allows complex query composition without overhead. ### Child Access Navigate the tree with dot notation: ``` ns.name // direct child named "name" ns."elem-name" // child with a hyphenated name ns.* // all direct children (wildcard) ns.**.name // all descendants named "name" (deep query) ns.**.* // all descendants ns[0] // first element of an array/list node (0-based) ``` ### Filtering (Select) Filter nodes using `@`: ``` ns@name // keep only nodes named "name" ns@(condExpr) // keep nodes matching a boolean expression ns@fn(args) // keep nodes where fn(...) returns true ``` Examples: ```go doc.users.*@($role == "admin") // users whose role attribute is "admin" doc.users.*@($age.int?:100 < 18) // users under 18 doc.**.div@hasClass("widget") // all divs with a given CSS class ``` ### Attributes Read node-level data with `$`: ```go val := ns.$name // single-value: zero value on error val, err := ns.$name // dual-value: explicit error handling val := ns.$name! // panic on error val := ns.$name?:"default" // use a fallback value on error val := ns.$"attr-name" // hyphenated attribute names ``` Attribute access always operates on the **first node** in the NodeSet. To collect an attribute from every node, use a list comprehension: ```go names := [user.$name for user in doc.users.*] ``` ### Methods Methods provide access to computed or typed data: ```go text := div.text // call Text() method (single-value) text, err := div.text // dual-value count := div.count?:0 // explicit default for numeric methods age, err := node.$age.int // numeric methods require dual-value ``` Methods prefixed with `_` map to `XGo_`-prefixed implementations: ```go node._text // → node.XGo_text() node._count // → node.XGo_count() node._first // → node.XGo_first() ``` > **Note on numeric methods**: Methods that return numeric types (like `int`, `float`, `count`) do **not** have a single-value form, to prevent silent bugs where a zero default masks a real error. Always use the dual-value form or an explicit `?:` default. --- ## Syntax Reference | Syntax | Meaning | |---|---| | `ns.name` | Direct child named `name` | | `ns."elem-name"` | Direct child with special characters | | `ns.*` | All direct children | | `ns[n]` | Child at index `n` (0-based) | | `ns.**.name` | Deep search for `name` | | `ns.**.*` | All descendants | | `ns@name` | Filter: keep nodes named `name` | | `ns@(expr)` | Filter: keep nodes matching expression | | `ns.$name` | Attribute (single-value, zero on error) | | `val, err := ns.$name` | Attribute (dual-value) | | `ns.$name!` | Attribute, panic on error | | `ns.$name?:def` | Attribute, custom default on error | | `ns.method(args)` | Method call | | `ns._method` | Method call via `XGo_method()` | | `ns.all` / `ns._all` | Materialize and cache all results | | `ns.one` / `ns._one` | First match, early termination | | `ns.single` / `ns._single` | Exactly one match (validates uniqueness) | --- ## Query Examples ### JSON ```go doc := json`{ "animals": [ {"class": "gopher", "at": "Line 1"}, {"class": "armadillo", "at": "Line 2"}, {"class": "zebra", "at": "Line 3"}, {"class": "unknown", "at": "Line 4"}, {"class": "gopher", "at": "Line 5"}, {"class": "bee", "at": "Line 6"}, {"class": "gopher", "at": "Line 7"}, {"class": "zebra", "at": "Line 8"} ] }`! // Iterate filtered records for animal in doc.animals.*@($class == "zebra") { echo animal.$at } // Collect all names into a list names := [a.$class for a in doc.animals.*] // Access by index first := doc.animals[0].$class // Find unique admin (validate exactly one exists) user := doc.users.*@($role == "admin")._single name, err := user.$name ``` ### HTML ```go import "os" import "github.com/goplus/xgo/dql/html" doc := html.source(os.Args[1]) // Print all hyperlink URLs for a in doc.**.a { if url := a.$href; url != "" { echo url } } // Collect text from all paragraphs texts := [p.text for p in doc.**.p] // Find the first element with a specific class (early termination) widget := doc.**.*@($class == "widget").one id := widget.$id ``` ### XGo AST ```go doc := xgo` x, y := "Hi", 123 echo x print y `! // Find all expression statements stmts := doc.shadowEntry.body.list.*@(self.class == "ExprStmt") // Extract function names from call expressions for fn in stmts.x@(self.class == "CallExpr").fun@(self.class == "Ident") { echo fn.$name } ``` ### File System ```go // Walk current directory and print all .xgo files for e in fs`.`.**.file.match("*.xgo") { echo e.path } // Collect all Go source file names names := [f.$name for f in root.**.file@match("*.go", $name)] ``` --- ## Error Handling DQL integrates with XGo's error handling operators and follows a consistent two-version design for attribute and method access. ### Two-Version Design Most accessors come in two forms: ```go // Single-value: convenient, returns zero on error name := node.$name // Dual-value: explicit, lets you inspect the error name, err := node.$name ``` Additionally, XGo's error operators work directly on DQL expressions: ```go name := node.$name! // panic if error name := node.$name?:"N/A" // custom fallback ``` ### NodeSet Error State Some operations return a NodeSet that internally carries an error (e.g., `_one` when no match is found, `_single` when zero or multiple matches are found). This error propagates automatically to any subsequent attribute or method access: ```go admin := doc.users.*@($role == "admin")._one // All of these reflect the internal ErrNotFound: name := admin.$name // returns zero value name, err := admin.$name // err == dql.ErrNotFound name := admin.$name! // panics ``` An empty NodeSet is still a valid NodeSet — loops simply don't execute: ```go for user in admin { // Not reached if admin has ErrNotFound } ``` --- ## Performance and Caching DQL uses **lazy evaluation** by default. Queries are not executed until you iterate or read from the NodeSet. This is memory-efficient and enables query composition, but means a NodeSet re-executes its query each time it is accessed. Use cache control methods to avoid repeated execution: ### `_all` / `all` — Materialize Everything Executes the query and caches all results. Use when you need to access the same NodeSet multiple times. ```go users := doc.users.*@($active == true)._all names := [u.$name for u in users] // uses cache emails := [u.$email for u in users] // uses cache, no re-query ``` ### `_one` / `one` — First Match, Early Exit Stops after finding the first matching node. Use when you know (or assume) there is at most one result. ```go admin := doc.users.*@($role == "admin")._one name := admin.$name // ErrNotFound if no match ``` ### `_single` / `single` — Uniqueness Validation Validates that exactly one node matches. Returns `ErrMultipleEntities` if more than one is found. ```go user := doc.users.*@($id == 12345)._single name, err := user.$name // err may be ErrNotFound or ErrMultipleEntities ``` ### Choosing the Right Method | Need | Use | |---|---| | Multiple accesses to same results | `_all` | | Expect one result, want fast exit | `_one` | | Require exactly one result | `_single` | | Single-pass iteration | no cache (default lazy) | --- ## Implementing a NodeSet To make a custom data source queryable with DQL, implement the following interface on your NodeSet type. ### Required Methods ```go // Iteration func (ns NodeSet) XGo_Enum() iter.Seq[NodeSet] // Extract first node func (ns NodeSet) XGo_first() (NodeType, error) // Type conversion from raw sequence func NodeSet_Cast(seq func(func(NodeType) bool)) NodeSet ``` ### Child Navigation ```go func (ns NodeSet) XGo_Elem(name string) NodeSet // ns.name func (ns NodeSet) XGo_Child() NodeSet // ns.* func (ns NodeSet) XGo_Index(index int) NodeSet // ns[n] func (ns NodeSet) XGo_Any(name string) NodeSet // ns.**.name ("" = all) ``` ### Filtering ```go func (ns NodeSet) XGo_Select(name string) NodeSet // ns@name // Conditional filters are compiler-generated using NodeSet_Cast ``` ### Attribute Access ```go func (ns NodeSet) XGo_Attr__0(name string) ValueType // single-value func (ns NodeSet) XGo_Attr__1(name string) (ValueType, error) // dual-value ``` ### Cache Control Provide either general-form (`XGo_all`, `XGo_one`, `XGo_single`) or domain-specific form (`All`, `One`, `Single`) methods: ```go func (ns NodeSet) XGo_all() NodeSet // or All() func (ns NodeSet) XGo_one() NodeSet // or One() func (ns NodeSet) XGo_single() NodeSet // or Single() ``` Domain types (`NodeType`, `ValueType`, `IndexType`) are customized per implementation. --- ## Standard Errors The `dql` package defines two standard sentinel errors: ```go package dql var ( ErrNotFound = errors.New("node not found") ErrMultipleEntities = errors.New("multiple entities found, expected single") ) ``` These are returned by `_one` (when no match exists) and `_single` (when zero or more than one match exists), and propagate through the NodeSet to any subsequent attribute or method call. ================================================ FILE: dql/dql.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dql import ( "errors" "strconv" "strings" ) const ( XGoPackage = true ) var ( ErrNotFound = errors.New("entity not found") ErrMultiEntities = errors.New("too many entities found") ) // ----------------------------------------------------------------------------- // NopIter is a no-operation iterator that yields no values. func NopIter[T any](yield func(T) bool) {} // ----------------------------------------------------------------------------- // First retrieves the first item from the provided sequence. If the sequence is // empty, it returns ErrNotFound. func First[T any, Seq ~func(func(T) bool)](seq Seq) (ret T, err error) { err = ErrNotFound seq(func(item T) bool { ret, err = item, nil return false }) return } // Single retrieves a single item from the provided sequence. If the sequence is // empty, it returns ErrNotFound. If the sequence contains more than one item, it // returns ErrMultiEntities. func Single[T any, Seq ~func(func(T) bool)](seq Seq) (ret T, err error) { err = ErrNotFound first := true seq(func(item T) bool { if first { ret, err = item, nil first = false return true } err = ErrMultiEntities return false }) return } // Collect retrieves all items from the provided sequence. func Collect[T any, Seq ~func(func(T) bool)](seq Seq) []T { ret := make([]T, 0, 8) seq(func(item T) bool { ret = append(ret, item) return true }) return ret } // ----------------------------------------------------------------------------- // Int parses the given string as an integer, removing any commas and trimming // whitespace. func Int(text string) (int, error) { return strconv.Atoi(strings.ReplaceAll(strings.TrimSpace(text), ",", "")) } // ----------------------------------------------------------------------------- ================================================ FILE: dql/fetcher/fetch.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package fetcher import ( "errors" "reflect" "sort" "github.com/goplus/xgo/dql/html" ) // ----------------------------------------------------------------------------- // Conv defines a converter function type. // func(input any, doc html.NodeSet) // A converter function converts a html source to an object. type Conv = any // convert converts a html source to an object. func convert(conv reflect.Value, input, source any) any { doc := reflect.ValueOf(html.Source(source)) out := conv.Call([]reflect.Value{reflect.ValueOf(input), doc}) return out[0].Interface() } // ----------------------------------------------------------------------------- var ( ErrUnknownFetchType = errors.New("unknown fetch type") ) // URL generates a URL from an input by registered converter. func URL(fetchType string, input any) (string, error) { fi, ok := convs[fetchType] if !ok { return "", ErrUnknownFetchType } return fi.URL(input), nil } // Do fetches HTML content from an input and converts it to an object by // registered converter. func Do(fetchType string, input any) (any, error) { fi, ok := convs[fetchType] if !ok { return nil, ErrUnknownFetchType } url := fi.URL(input) return convert(fi.Conv, input, url), nil } // From reads HTML content from a source and converts it to an object by // registered converter. It is used when HTML content is already available. func From(fetchType string, input, source any) (any, error) { fi, ok := convs[fetchType] if !ok { return nil, ErrUnknownFetchType } return convert(fi.Conv, input, source), nil } // fetchInfo represents a fetch information, including convert function // and URL function that generates URL from input. type fetchInfo struct { Conv reflect.Value URL func(input any) string } var ( convs = map[string]fetchInfo{} ) // Register registers a fetchType with a convert function. // The urlOf function generates URL from input. // func conv(input any, doc html.NodeSet) func Register(fetchType string, conv Conv, urlOf func(input any) string) { vConv := reflect.ValueOf(conv) convs[fetchType] = fetchInfo{vConv, urlOf} } // List returns a list of registered fetch types. func List() []string { keys := make([]string, 0, len(convs)) for k := range convs { keys = append(keys, k) } sort.Strings(keys) return keys } // ----------------------------------------------------------------------------- ================================================ FILE: dql/fetcher/github.com/issueTask/issueTask.xgo ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package issueTask import ( "github.com/goplus/xgo/dql/html" "github.com/goplus/xgo/dql/fetcher" ) // Task represents a GitHub issue task item. type Task struct { Desc string `json:"desc"` Done bool `json:"done"` } // Result represents the result of issue task fetcher. type Result struct { Issue string `json:"issue"` // goplus/llgo#642 Tasks []Task `json:"tasks"` } // New extracts issue tasks from the given document and returns the Result. func New(input any, doc html.NodeSet) Result { issue := input.(string) taskList := doc.**.ul@($class == "contains-task-list").one tasks := [Task{li.text, li.firstElementChild.hasAttr("checked")} for li in taskList.li] return {issue, tasks} } // URL returns the URL from the input. // Input can be either a full github issue URL or a shorthand format // like "goplus/llgo#642". func URL(input any) string { issue := input.(string) if issue.hasPrefix("https://github.com/") { return issue } return "https://github.com/" + issue.replace("#", "/issues/", 1) } func init() { fetcher.register("github.com/issueTask", New, URL) } ================================================ FILE: dql/fetcher/github.com/issueTask/xgo_autogen.go ================================================ // Code generated by xgo (XGo); DO NOT EDIT. package issueTask import ( "github.com/goplus/xgo/dql/fetcher" "github.com/goplus/xgo/dql/html" "strings" ) const XGoPackage = "github.com/goplus/xgo/dql/html" const _ = true // Task represents a GitHub issue task item. type Task struct { Desc string `json:"desc"` Done bool `json:"done"` } // Result represents the result of issue task fetcher. type Result struct { Issue string `json:"issue"` Tasks []Task `json:"tasks"` } //line dql/fetcher/github.com/issueTask/issueTask.xgo:36:1 // New extracts issue tasks from the given document and returns the Result. func New(input interface{}, doc html.NodeSet) Result { //line dql/fetcher/github.com/issueTask/issueTask.xgo:38:1 issue := input.(string) //line dql/fetcher/github.com/issueTask/issueTask.xgo:39:1 taskList := html.NodeSet_Cast(func(_xgo_yield func(*html.Node) bool) { //line dql/fetcher/github.com/issueTask/issueTask.xgo:39:1 doc.XGo_Any("ul").XGo_Enum()(func(self html.NodeSet) bool { //line dql/fetcher/github.com/issueTask/issueTask.xgo:39:1 if self.XGo_Attr__0("class") == "contains-task-list" { //line dql/fetcher/github.com/issueTask/issueTask.xgo:39:1 if //line dql/fetcher/github.com/issueTask/issueTask.xgo:39:1 _xgo_val, _xgo_err := self.XGo_first(); _xgo_err == nil { //line dql/fetcher/github.com/issueTask/issueTask.xgo:39:1 if !_xgo_yield(_xgo_val) { //line dql/fetcher/github.com/issueTask/issueTask.xgo:39:1 return false } } } //line dql/fetcher/github.com/issueTask/issueTask.xgo:39:1 return true }) }).One() //line dql/fetcher/github.com/issueTask/issueTask.xgo:40:1 tasks := func() (_xgo_ret []Task) { for //line dql/fetcher/github.com/issueTask/issueTask.xgo:40:1 li := range taskList.XGo_Elem("li").XGo_Enum() { //line dql/fetcher/github.com/issueTask/issueTask.xgo:40:1 _xgo_ret = append(_xgo_ret, Task{li.Text__0(), li.FirstElementChild().HasAttr("checked")}) } //line dql/fetcher/github.com/issueTask/issueTask.xgo:40:1 return }() //line dql/fetcher/github.com/issueTask/issueTask.xgo:41:1 return Result{issue, tasks} } //line dql/fetcher/github.com/issueTask/issueTask.xgo:44:1 // URL returns the URL from the input. // Input can be either a full github issue URL or a shorthand format // like "goplus/llgo#642". func URL(input interface{}) string { //line dql/fetcher/github.com/issueTask/issueTask.xgo:48:1 issue := input.(string) //line dql/fetcher/github.com/issueTask/issueTask.xgo:49:1 if strings.HasPrefix(issue, "https://github.com/") { //line dql/fetcher/github.com/issueTask/issueTask.xgo:50:1 return issue } //line dql/fetcher/github.com/issueTask/issueTask.xgo:52:1 return "https://github.com/" + strings.Replace(issue, "#", "/issues/", 1) } //line dql/fetcher/github.com/issueTask/issueTask.xgo:55:1 func init() { //line dql/fetcher/github.com/issueTask/issueTask.xgo:56:1 fetcher.Register("github.com/issueTask", New, URL) } ================================================ FILE: dql/fetcher/github.com/repoList/repoList.xgo ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package repoList import ( "github.com/goplus/xgo/dql/html" "github.com/goplus/xgo/dql/fetcher" ) // Repo is the information of a repository. type Repo struct { Repo string `json:"repo"` ForkedFrom string `json:"forkedFrom"` Title string `json:"title"` Language string `json:"language"` UpdateTime string `json:"updateTime"` Forks int `json:"forks"` } // Result is the result of fetching a repository list page. type Result struct { User string `json:"user"` // github username Repos []Repo `json:"repos"` Next string `json:"next"` } func newRepo(node html.NodeSet) Repo { aRepo := node.**.a@($itemprop == "name codeRepository").one repo := aRepo.$href root := aRepo.parentN(3).one forkedFrom := root.**.span.**.textNode@(self.value.trimSpace == "Forked from").nextSibling@a.$href title := root.**.p@($itemprop == "description").text language := root.**.span@($itemprop == "programmingLanguage").text updateTime := root.**."relative-time".$datetime forks := root.**.a@($href == repo+"/network/members").int?:0 return { Repo: repo, ForkedFrom: forkedFrom, Title: title, Language: language, UpdateTime: updateTime, Forks: forks, } } // New extracts the repository information from the given HTML document and // returns the Result. func New(input any, doc html.NodeSet) Result { user := input.(string) divRepos := doc.**.div@($id == "user-repositories-list").one repos := [newRepo(x) for x in divRepos.ul.li] divPaginate := doc.**.div@($class == "paginate-container").one next := divPaginate.**.a@(self.text == "Next").$href return {user, repos, next} } // URL returns the URL from the input. // Input is expected to be a GitHub username, and the URL will be the user's // repository list page. func URL(input any) string { return "https://github.com/" + input.(string) } func init() { fetcher.register("github.com/repoList", New, URL) } ================================================ FILE: dql/fetcher/github.com/repoList/xgo_autogen.go ================================================ // Code generated by xgo (XGo); DO NOT EDIT. package repoList import ( "github.com/goplus/xgo/dql/fetcher" "github.com/goplus/xgo/dql/html" "strings" ) const XGoPackage = "github.com/goplus/xgo/dql/html" const _ = true // Repo is the information of a repository. type Repo struct { Repo string `json:"repo"` ForkedFrom string `json:"forkedFrom"` Title string `json:"title"` Language string `json:"language"` UpdateTime string `json:"updateTime"` Forks int `json:"forks"` } // Result is the result of fetching a repository list page. type Result struct { User string `json:"user"` Repos []Repo `json:"repos"` Next string `json:"next"` } //line dql/fetcher/github.com/repoList/repoList.xgo:41:1 func newRepo(node html.NodeSet) Repo { //line dql/fetcher/github.com/repoList/repoList.xgo:42:1 aRepo := html.NodeSet_Cast(func(_xgo_yield func(*html.Node) bool) { //line dql/fetcher/github.com/repoList/repoList.xgo:42:1 node.XGo_Any("a").XGo_Enum()(func(self html.NodeSet) bool { //line dql/fetcher/github.com/repoList/repoList.xgo:42:1 if self.XGo_Attr__0("itemprop") == "name codeRepository" { //line dql/fetcher/github.com/repoList/repoList.xgo:42:1 if //line dql/fetcher/github.com/repoList/repoList.xgo:42:1 _xgo_val, _xgo_err := self.XGo_first(); _xgo_err == nil { //line dql/fetcher/github.com/repoList/repoList.xgo:42:1 if !_xgo_yield(_xgo_val) { //line dql/fetcher/github.com/repoList/repoList.xgo:42:1 return false } } } //line dql/fetcher/github.com/repoList/repoList.xgo:42:1 return true }) }).One() //line dql/fetcher/github.com/repoList/repoList.xgo:43:1 repo := aRepo.XGo_Attr__0("href") //line dql/fetcher/github.com/repoList/repoList.xgo:44:1 root := aRepo.ParentN(3).One() //line dql/fetcher/github.com/repoList/repoList.xgo:45:1 forkedFrom := html.NodeSet_Cast(func(_xgo_yield func(*html.Node) bool) { //line dql/fetcher/github.com/repoList/repoList.xgo:45:1 root.XGo_Any("span").XGo_Any("textNode").XGo_Enum()(func(self html.NodeSet) bool { //line dql/fetcher/github.com/repoList/repoList.xgo:45:1 if strings.TrimSpace(self.Value__0()) == "Forked from" { //line dql/fetcher/github.com/repoList/repoList.xgo:45:1 if //line dql/fetcher/github.com/repoList/repoList.xgo:45:1 _xgo_val, _xgo_err := self.XGo_first(); _xgo_err == nil { //line dql/fetcher/github.com/repoList/repoList.xgo:45:1 if !_xgo_yield(_xgo_val) { //line dql/fetcher/github.com/repoList/repoList.xgo:45:1 return false } } } //line dql/fetcher/github.com/repoList/repoList.xgo:45:1 return true }) }).NextSibling().XGo_Select("a").XGo_Attr__0("href") //line dql/fetcher/github.com/repoList/repoList.xgo:46:1 title := html.NodeSet_Cast(func(_xgo_yield func(*html.Node) bool) { //line dql/fetcher/github.com/repoList/repoList.xgo:46:1 root.XGo_Any("p").XGo_Enum()(func(self html.NodeSet) bool { //line dql/fetcher/github.com/repoList/repoList.xgo:46:1 if self.XGo_Attr__0("itemprop") == "description" { //line dql/fetcher/github.com/repoList/repoList.xgo:46:1 if //line dql/fetcher/github.com/repoList/repoList.xgo:46:1 _xgo_val, _xgo_err := self.XGo_first(); _xgo_err == nil { //line dql/fetcher/github.com/repoList/repoList.xgo:46:1 if !_xgo_yield(_xgo_val) { //line dql/fetcher/github.com/repoList/repoList.xgo:46:1 return false } } } //line dql/fetcher/github.com/repoList/repoList.xgo:46:1 return true }) }).Text__0() //line dql/fetcher/github.com/repoList/repoList.xgo:47:1 language := html.NodeSet_Cast(func(_xgo_yield func(*html.Node) bool) { //line dql/fetcher/github.com/repoList/repoList.xgo:47:1 root.XGo_Any("span").XGo_Enum()(func(self html.NodeSet) bool { //line dql/fetcher/github.com/repoList/repoList.xgo:47:1 if self.XGo_Attr__0("itemprop") == "programmingLanguage" { //line dql/fetcher/github.com/repoList/repoList.xgo:47:1 if //line dql/fetcher/github.com/repoList/repoList.xgo:47:1 _xgo_val, _xgo_err := self.XGo_first(); _xgo_err == nil { //line dql/fetcher/github.com/repoList/repoList.xgo:47:1 if !_xgo_yield(_xgo_val) { //line dql/fetcher/github.com/repoList/repoList.xgo:47:1 return false } } } //line dql/fetcher/github.com/repoList/repoList.xgo:47:1 return true }) }).Text__0() //line dql/fetcher/github.com/repoList/repoList.xgo:48:1 updateTime := root.XGo_Any("relative-time").XGo_Attr__0("datetime") //line dql/fetcher/github.com/repoList/repoList.xgo:49:1 forks := func() (_xgo_ret int) { //line dql/fetcher/github.com/repoList/repoList.xgo:49:1 var _xgo_err error //line dql/fetcher/github.com/repoList/repoList.xgo:49:1 _xgo_ret, _xgo_err = html.NodeSet_Cast(func(_xgo_yield func(*html.Node) bool) { //line dql/fetcher/github.com/repoList/repoList.xgo:49:1 root.XGo_Any("a").XGo_Enum()(func(self html.NodeSet) bool { //line dql/fetcher/github.com/repoList/repoList.xgo:49:1 if self.XGo_Attr__0("href") == repo+"/network/members" { //line dql/fetcher/github.com/repoList/repoList.xgo:49:1 if //line dql/fetcher/github.com/repoList/repoList.xgo:49:1 _xgo_val, _xgo_err := self.XGo_first(); _xgo_err == nil { //line dql/fetcher/github.com/repoList/repoList.xgo:49:1 if !_xgo_yield(_xgo_val) { //line dql/fetcher/github.com/repoList/repoList.xgo:49:1 return false } } } //line dql/fetcher/github.com/repoList/repoList.xgo:49:1 return true }) }).Int() //line dql/fetcher/github.com/repoList/repoList.xgo:49:1 if _xgo_err != nil { //line dql/fetcher/github.com/repoList/repoList.xgo:49:1 return 0 } //line dql/fetcher/github.com/repoList/repoList.xgo:49:1 return }() //line dql/fetcher/github.com/repoList/repoList.xgo:50:1 return Repo{Repo: repo, ForkedFrom: forkedFrom, Title: title, Language: language, UpdateTime: updateTime, Forks: forks} } //line dql/fetcher/github.com/repoList/repoList.xgo:60:1 // New extracts the repository information from the given HTML document and // returns the Result. func New(input interface{}, doc html.NodeSet) Result { //line dql/fetcher/github.com/repoList/repoList.xgo:63:1 user := input.(string) //line dql/fetcher/github.com/repoList/repoList.xgo:64:1 divRepos := html.NodeSet_Cast(func(_xgo_yield func(*html.Node) bool) { //line dql/fetcher/github.com/repoList/repoList.xgo:64:1 doc.XGo_Any("div").XGo_Enum()(func(self html.NodeSet) bool { //line dql/fetcher/github.com/repoList/repoList.xgo:64:1 if self.XGo_Attr__0("id") == "user-repositories-list" { //line dql/fetcher/github.com/repoList/repoList.xgo:64:1 if //line dql/fetcher/github.com/repoList/repoList.xgo:64:1 _xgo_val, _xgo_err := self.XGo_first(); _xgo_err == nil { //line dql/fetcher/github.com/repoList/repoList.xgo:64:1 if !_xgo_yield(_xgo_val) { //line dql/fetcher/github.com/repoList/repoList.xgo:64:1 return false } } } //line dql/fetcher/github.com/repoList/repoList.xgo:64:1 return true }) }).One() //line dql/fetcher/github.com/repoList/repoList.xgo:65:1 repos := func() (_xgo_ret []Repo) { for //line dql/fetcher/github.com/repoList/repoList.xgo:65:1 x := range divRepos.XGo_Elem("ul").XGo_Elem("li").XGo_Enum() { //line dql/fetcher/github.com/repoList/repoList.xgo:65:1 _xgo_ret = append(_xgo_ret, newRepo(x)) } //line dql/fetcher/github.com/repoList/repoList.xgo:65:1 return }() //line dql/fetcher/github.com/repoList/repoList.xgo:66:1 divPaginate := html.NodeSet_Cast(func(_xgo_yield func(*html.Node) bool) { //line dql/fetcher/github.com/repoList/repoList.xgo:66:1 doc.XGo_Any("div").XGo_Enum()(func(self html.NodeSet) bool { //line dql/fetcher/github.com/repoList/repoList.xgo:66:1 if self.XGo_Attr__0("class") == "paginate-container" { //line dql/fetcher/github.com/repoList/repoList.xgo:66:1 if //line dql/fetcher/github.com/repoList/repoList.xgo:66:1 _xgo_val, _xgo_err := self.XGo_first(); _xgo_err == nil { //line dql/fetcher/github.com/repoList/repoList.xgo:66:1 if !_xgo_yield(_xgo_val) { //line dql/fetcher/github.com/repoList/repoList.xgo:66:1 return false } } } //line dql/fetcher/github.com/repoList/repoList.xgo:66:1 return true }) }).One() //line dql/fetcher/github.com/repoList/repoList.xgo:67:1 next := html.NodeSet_Cast(func(_xgo_yield func(*html.Node) bool) { //line dql/fetcher/github.com/repoList/repoList.xgo:67:1 divPaginate.XGo_Any("a").XGo_Enum()(func(self html.NodeSet) bool { //line dql/fetcher/github.com/repoList/repoList.xgo:67:1 if self.Text__0() == "Next" { //line dql/fetcher/github.com/repoList/repoList.xgo:67:1 if //line dql/fetcher/github.com/repoList/repoList.xgo:67:1 _xgo_val, _xgo_err := self.XGo_first(); _xgo_err == nil { //line dql/fetcher/github.com/repoList/repoList.xgo:67:1 if !_xgo_yield(_xgo_val) { //line dql/fetcher/github.com/repoList/repoList.xgo:67:1 return false } } } //line dql/fetcher/github.com/repoList/repoList.xgo:67:1 return true }) }).XGo_Attr__0("href") //line dql/fetcher/github.com/repoList/repoList.xgo:68:1 return Result{user, repos, next} } //line dql/fetcher/github.com/repoList/repoList.xgo:71:1 // URL returns the URL from the input. // Input is expected to be a GitHub username, and the URL will be the user's // repository list page. func URL(input interface{}) string { //line dql/fetcher/github.com/repoList/repoList.xgo:75:1 return "https://github.com/" + input.(string) } //line dql/fetcher/github.com/repoList/repoList.xgo:78:1 func init() { //line dql/fetcher/github.com/repoList/repoList.xgo:79:1 fetcher.Register("github.com/repoList", New, URL) } ================================================ FILE: dql/fetcher/hrefs/hrefs.xgo ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package hrefs import ( "github.com/goplus/xgo/dql/html" "github.com/goplus/xgo/dql/fetcher" ) // Result represents the result of hrefs fetcher. type Result struct { URL string `json:"url,omitempty"` Hrefs []string `json:"hrefs,omitempty"` } // New extracts hrefs from the given document and returns the Result. func New(input any, doc html.NodeSet) Result { hrefs := [url for a in doc.**.a if url := a.$href; url != ""] return {input.(string), hrefs} } // URL returns the URL from the input. // Input is expected to be a URL, and the fetcher will extract all hrefs // from the page at that URL. func URL(input any) string { return input.(string) } func init() { fetcher.register("hrefs", New, URL) } ================================================ FILE: dql/fetcher/hrefs/xgo_autogen.go ================================================ // Code generated by xgo (XGo); DO NOT EDIT. package hrefs import ( "github.com/goplus/xgo/dql/fetcher" "github.com/goplus/xgo/dql/html" ) const XGoPackage = "github.com/goplus/xgo/dql/html" const _ = true // Result represents the result of hrefs fetcher. type Result struct { URL string `json:"url,omitempty"` Hrefs []string `json:"hrefs,omitempty"` } //line dql/fetcher/hrefs/hrefs.xgo:30:1 // New extracts hrefs from the given document and returns the Result. func New(input interface{}, doc html.NodeSet) Result { //line dql/fetcher/hrefs/hrefs.xgo:32:1 hrefs := func() (_xgo_ret []string) { for //line dql/fetcher/hrefs/hrefs.xgo:32:1 a := range doc.XGo_Any("a").XGo_Enum() { //line dql/fetcher/hrefs/hrefs.xgo:32:1 if //line dql/fetcher/hrefs/hrefs.xgo:32:1 url := a.XGo_Attr__0("href"); url != "" { //line dql/fetcher/hrefs/hrefs.xgo:32:1 _xgo_ret = append(_xgo_ret, url) } } //line dql/fetcher/hrefs/hrefs.xgo:32:1 return }() //line dql/fetcher/hrefs/hrefs.xgo:33:1 return Result{input.(string), hrefs} } //line dql/fetcher/hrefs/hrefs.xgo:36:1 // URL returns the URL from the input. // Input is expected to be a URL, and the fetcher will extract all hrefs // from the page at that URL. func URL(input interface{}) string { //line dql/fetcher/hrefs/hrefs.xgo:40:1 return input.(string) } //line dql/fetcher/hrefs/hrefs.xgo:43:1 func init() { //line dql/fetcher/hrefs/hrefs.xgo:44:1 fetcher.Register("hrefs", New, URL) } ================================================ FILE: dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package importedBy import ( "github.com/goplus/xgo/dql" "github.com/goplus/xgo/dql/html" "github.com/goplus/xgo/dql/fetcher" ) type Result struct { Path string `json:"path"` // package path ImportedBy int `json:"importedBy"` } // New extracts the number of packages that import the given package from // the given HTML document and returns the Result. func New(input any, doc html.NodeSet) Result { const importedBy = "Imported By:" path := input.(string) a := doc.**.a@(($"aria-label"?:"").hasPrefix(importedBy)).one if !a.ok { return {path, 0} } label := a.$"aria-label"! nImported := dql.int(label[len(importedBy):])! // dql.int support comma in number string, e.g. "1,234" return {path, nImported} } // URL returns the input URL for the given input. // Input is expected to be a Go package path. func URL(input any) string { return "https://pkg.go.dev/" + input.(string) } func init() { fetcher.register("pkg.go.dev/importedBy", New, URL) } ================================================ FILE: dql/fetcher/pkg.go.dev/importedBy/xgo_autogen.go ================================================ // Code generated by xgo (XGo); DO NOT EDIT. package importedBy import ( "github.com/goplus/xgo/dql" "github.com/goplus/xgo/dql/fetcher" "github.com/goplus/xgo/dql/html" "github.com/qiniu/x/errors" "strings" ) const XGoPackage = "github.com/goplus/xgo/dql/html" const _ = true type Result struct { Path string `json:"path"` ImportedBy int `json:"importedBy"` } //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:30:1 // New extracts the number of packages that import the given package from // the given HTML document and returns the Result. func New(input interface{}, doc html.NodeSet) Result { //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:33:1 const importedBy = "Imported By:" //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:34:1 path := input.(string) //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:35:1 a := html.NodeSet_Cast(func(_xgo_yield func(*html.Node) bool) { //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:35:1 doc.XGo_Any("a").XGo_Enum()(func(self html.NodeSet) bool { //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:35:1 if strings.HasPrefix(func() (_xgo_ret string) { //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:35:1 var _xgo_err error //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:35:1 _xgo_ret, _xgo_err = self.XGo_Attr__1("aria-label") //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:35:1 if _xgo_err != nil { //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:35:1 return "" } //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:35:1 return }(), importedBy) { //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:35:1 if //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:35:1 _xgo_val, _xgo_err := self.XGo_first(); _xgo_err == nil { //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:35:1 if !_xgo_yield(_xgo_val) { //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:35:1 return false } } } //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:35:1 return true }) }).One() //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:36:1 if !a.Ok() { //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:37:1 return Result{path, 0} } //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:39:1 label := func() (_xgo_ret string) { //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:39:1 var _xgo_err error //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:39:1 _xgo_ret, _xgo_err = a.XGo_Attr__1("aria-label") //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:39:1 if _xgo_err != nil { //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:39:1 _xgo_err = errors.NewFrame(_xgo_err, "a.$\"aria-label\"", "dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo", 39, "importedBy.New") //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:39:1 panic(_xgo_err) } //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:39:1 return }() //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:40:1 nImported := func() (_xgo_ret int) { //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:40:1 var _xgo_err error //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:40:1 _xgo_ret, _xgo_err = dql.Int(label[len(importedBy):]) //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:40:1 if _xgo_err != nil { //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:40:1 _xgo_err = errors.NewFrame(_xgo_err, "dql.int(label[len(importedBy):])", "dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo", 40, "importedBy.New") //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:40:1 panic(_xgo_err) } //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:40:1 return }() //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:42:1 return Result{path, nImported} } //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:45:1 // URL returns the input URL for the given input. // Input is expected to be a Go package path. func URL(input interface{}) string { //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:48:1 return "https://pkg.go.dev/" + input.(string) } //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:51:1 func init() { //line dql/fetcher/pkg.go.dev/importedBy/importedBy.xgo:52:1 fetcher.Register("pkg.go.dev/importedBy", New, URL) } ================================================ FILE: dql/fetcher/pytorch.org/fndoc/fndoc.xgo ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package fndoc import ( "github.com/goplus/xgo/dql/html" "github.com/goplus/xgo/dql/fetcher" ) // ----------------------------------------------------------------------------- const ( spaces = " \t\r\n¶" ) type Result struct { Name string `json:"name"` // function name, type name, var name, etc. Type string `json:"type,omitempty"` // "function", "type", "var", etc. Doc string `json:"doc,omitempty"` Sig string `json:"sig"` URL string `json:"url,omitempty"` } // New extracts the function declaration from the given HTML document // and returns the Result. func New(input any, doc html.NodeSet) Result { name := input.(string) url := name if name != "" { url = URL(input) } if doc.ok { fn := doc.**.dl@($class == "py function").one decl := fn.firstElementChild@dt.text pos := decl.indexByte('(') if pos > 0 { sig := decl[pos:] return {name, "function", "", sig.trimRight(spaces), url} } } return {name, "", "", "", url} } // URL returns the input URL for the given input. // Input is expected to be a function name, and the URL will be the function's // documentation page on pytorch.org. func URL(input any) string { return "https://pytorch.org/docs/stable/generated/torch." + input.(string) + ".html" } func init() { fetcher.Register("pytorch.org/fndoc", New, URL) } // ----------------------------------------------------------------------------- ================================================ FILE: dql/fetcher/pytorch.org/fndoc/xgo_autogen.go ================================================ // Code generated by xgo (XGo); DO NOT EDIT. package fndoc import ( "github.com/goplus/xgo/dql/fetcher" "github.com/goplus/xgo/dql/html" "strings" ) const XGoPackage = "github.com/goplus/xgo/dql/html" const _ = true const spaces = " \t\r\n¶" type Result struct { Name string `json:"name"` Type string `json:"type,omitempty"` Doc string `json:"doc,omitempty"` Sig string `json:"sig"` URL string `json:"url,omitempty"` } //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:38:1 // New extracts the function declaration from the given HTML document // and returns the Result. func New(input interface{}, doc html.NodeSet) Result { //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:41:1 name := input.(string) //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:42:1 url := name //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:43:1 if name != "" { //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:62:1 url = URL(input) } //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:46:1 if doc.Ok() { //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:47:1 fn := html.NodeSet_Cast(func(_xgo_yield func(*html.Node) bool) { //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:47:1 doc.XGo_Any("dl").XGo_Enum()(func(self html.NodeSet) bool { //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:47:1 if self.XGo_Attr__0("class") == "py function" { //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:47:1 if //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:47:1 _xgo_val, _xgo_err := self.XGo_first(); _xgo_err == nil { //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:47:1 if !_xgo_yield(_xgo_val) { //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:47:1 return false } } } //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:47:1 return true }) }).One() //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:48:1 decl := fn.FirstElementChild().XGo_Select("dt").Text__0() //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:49:1 pos := strings.IndexByte(decl, '(') //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:50:1 if pos > 0 { //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:51:1 sig := decl[pos:] //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:52:1 return Result{name, "function", "", strings.TrimRight(sig, spaces), url} } } //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:55:1 return Result{name, "", "", "", url} } //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:58:1 // URL returns the input URL for the given input. // Input is expected to be a function name, and the URL will be the function's // documentation page on pytorch.org. func URL(input interface{}) string { //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:62:1 return "https://pytorch.org/docs/stable/generated/torch." + input.(string) + ".html" } //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:65:1 func init() { //line dql/fetcher/pytorch.org/fndoc/fndoc.xgo:66:1 fetcher.Register("pytorch.org/fndoc", New, URL) } ================================================ FILE: dql/fs/fs.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package fs import ( "errors" "io/fs" "iter" "os" "path" "time" "github.com/goplus/xgo/dql" ) // ----------------------------------------------------------------------------- // Node represents a file or directory in the file system. type Node struct { // Path is the absolute path to the file or directory (relative to the root // of the file system). Path string // directory entry for the file or directory. de fs.DirEntry fi fs.FileInfo err error } // Name returns the name of the file (or subdirectory) described by the entry. // This name is only the final element of the path (the base name), not the entire path. // For example, Name would return "hello.go" not "home/gopher/hello.go". func (p *Node) Name() (string, error) { if p.err != nil { return "", p.err } return p.de.Name(), nil } // IsDir reports whether the entry describes a directory. func (p *Node) IsDir() (bool, error) { if p.err != nil { return false, p.err } return p.de.IsDir(), nil } func (p *Node) info() (fs.FileInfo, error) { if p.fi == nil { if p.err == nil { p.fi, p.err = p.de.Info() } } return p.fi, p.err } // Size returns the size of the file in bytes. // If the file is a directory, the size is system-dependent and should not be used. func (p *Node) Size() (int64, error) { fi, err := p.info() if err != nil { return 0, err } return fi.Size(), nil } // Type returns the type bits for the entry. // The type bits are a subset of the usual FileMode bits, those returned by the FileMode.Type method. func (p *Node) Type() (fs.FileMode, error) { if p.err != nil { return 0, p.err } return p.de.Type(), nil } // Mode returns file mode bits. func (p *Node) Mode() (fs.FileMode, error) { fi, err := p.info() if err != nil { return 0, err } return fi.Mode(), nil } // ModTime returns the modification time of the file. func (p *Node) ModTime() (time.Time, error) { fi, err := p.info() if err != nil { return time.Time{}, err } return fi.ModTime(), nil } // Sys returns the underlying data source (can return nil). func (p *Node) Sys() (any, error) { fi, err := p.info() if err != nil { return nil, err } return fi.Sys(), nil } // ----------------------------------------------------------------------------- // NodeSet represents a set of file system nodes, along with any error that // occurred while retrieving them. type NodeSet struct { Data iter.Seq[*Node] Base fs.FS Err error } // Root creates a NodeSet containing the provided root node. func Root(root fs.FS, doc *Node) NodeSet { return NodeSet{ Base: root, Data: func(yield func(*Node) bool) { yield(doc) }, } } // Nodes creates a NodeSet containing the provided nodes. func Nodes(root fs.FS, nodes ...*Node) NodeSet { return NodeSet{ Base: root, Data: func(yield func(*Node) bool) { for _, node := range nodes { if !yield(node) { break } } }, } } // Dir returns a NodeSet for the specified directory. func Dir(dir string) NodeSet { return New(os.DirFS(dir)) } // New creates a NodeSet for the provided file system, starting with // the root node. func New(root fs.FS) NodeSet { return NodeSet{ Base: root, Data: func(yield func(*Node) bool) { yield(rootEntry) }, } } var ( rootEntry = &Node{ Path: "", de: rootDirEntry{}, fi: rootDirEntry{}, } ) type rootDirEntry struct { } func (p rootDirEntry) Name() string { return "" } func (p rootDirEntry) Size() int64 { return 0 } func (p rootDirEntry) Type() fs.FileMode { return fs.ModeDir } func (p rootDirEntry) Mode() fs.FileMode { return fs.ModeDir } func (p rootDirEntry) Info() (fs.FileInfo, error) { return p, nil } func (p rootDirEntry) ModTime() time.Time { return time.Time{} } func (p rootDirEntry) IsDir() bool { return true } func (p rootDirEntry) Sys() any { return nil } // ----------------------------------------------------------------------------- // XGo_Enum returns an iterator over the nodes in the NodeSet. func (p NodeSet) XGo_Enum() iter.Seq[NodeSet] { if p.Err != nil { return dql.NopIter[NodeSet] } return func(yield func(NodeSet) bool) { p.Data(func(node *Node) bool { return yield(Root(p.Base, node)) }) } } // Dir returns a NodeSet containing all child nodes of the nodes in the NodeSet // that are directories. func (p NodeSet) Dir() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } return NodeSet{ Base: p.Base, Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { return yieldChildNodes(p.Base, node, filterDir, yield) }) }, } } // File returns a NodeSet containing all child nodes of the nodes in the NodeSet // that are files (not directories). func (p NodeSet) File() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } return NodeSet{ Base: p.Base, Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { return yieldChildNodes(p.Base, node, filterFile, yield) }) }, } } // XGo_Child returns a NodeSet containing all child nodes of the nodes in the NodeSet. func (p NodeSet) XGo_Child() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } return NodeSet{ Base: p.Base, Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { return yieldChildNodes(p.Base, node, nil, yield) }) }, } } type filterType = func(fs.DirEntry) bool func filterDir(de fs.DirEntry) bool { return de.IsDir() } func filterFile(de fs.DirEntry) bool { return !de.IsDir() } // yieldChildNodes yields all child nodes of the given node. func yieldChildNodes(base fs.FS, node *Node, filter filterType, yield func(*Node) bool) bool { var items []fs.DirEntry var path = node.Path isDir, err := node.IsDir() if err == nil { if !isDir { return true } dir := path if dir == "" { // fs.ReadDir does not accept an empty string as the directory, use "." // instead to read the root directory. dir = "." } items, err = fs.ReadDir(base, dir) } if err != nil { return yield(&Node{Path: path, err: err}) // yield the error as a node } if path != "" { path += "/" } for _, item := range items { if filter == nil || filter(item) { childPath := path + item.Name() if !yield(&Node{Path: childPath, de: item}) { return false } } } return true } const ( kindAny = iota kindFile kindDir ) // XGo_Any returns a NodeSet containing all descendant nodes (including the // nodes themselves) with the specified name. // If name is "file", it returns all file nodes. // If name is "dir", it returns all directory nodes. // If name is "", it returns all nodes. // - .**.file // - .**.dir // - .**.* func (p NodeSet) XGo_Any(name string) NodeSet { if p.Err != nil { return p } kind := kindAny switch name { case "file": kind = kindFile case "dir": kind = kindDir case "": default: return NodeSet{Err: errors.New("XGo_Any: invalid name - " + name)} } return NodeSet{ Base: p.Base, Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { return yieldAnyNodes(kind, p.Base, node, yield) }) }, } } // yieldAnyNodes yields all descendant nodes of the given node that match the // specified kind. If kind is kindAny, it yields all nodes. func yieldAnyNodes(kind int, base fs.FS, node *Node, yield func(*Node) bool) bool { isDir, err := node.IsDir() if err != nil { return yield(&Node{Path: node.Path, err: err}) // yield the error as a node } switch kind { case kindFile: if isDir { goto checkChildren } case kindDir: if !isDir { return true } } if !yield(node) { return false } checkChildren: if isDir { return yieldChildNodes(base, node, nil, func(n *Node) bool { return yieldAnyNodes(kind, base, n, yield) }) } return true } // ----------------------------------------------------------------------------- // Match returns a NodeSet containing all child nodes of the nodes in the NodeSet // that match the specified pattern. The pattern syntax is the same as in path.Match. // For example, "file*.txt" matches "file1.txt" and "file2.txt", but not "myfile.txt". func (p NodeSet) Match(pattern string) NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } if _, err := path.Match(pattern, ""); err != nil { return NodeSet{Err: err} } return NodeSet{ Base: p.Base, Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { if name, err := node.Name(); err == nil { // The pattern has been validated, so we can ignore the error here. matched, _ := path.Match(pattern, name) if matched { return yield(node) } } return true }) }, } } // OnError calls onErr for any error in the NodeSet and returns a new NodeSet without // the nodes that have errors. If onErr returns false, it stops processing and returns // a NodeSet without the remaining nodes. func (p NodeSet) OnError(onErr func(error) bool) NodeSet { if p.Err != nil { onErr(p.Err) return NodeSet{Err: p.Err} } return NodeSet{ Base: p.Base, Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { if node.err != nil { return onErr(node.err) } return yield(node) }) }, } } // ----------------------------------------------------------------------------- // All returns a NodeSet containing all nodes. // It's a cache operation for performance optimization when you need to traverse // the nodes multiple times. func (p NodeSet) All() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } nodes := dql.Collect(p.Data) return Nodes(p.Base, nodes...) } // One returns a NodeSet containing the first node. // It's a performance optimization when you only need the first node (stop early). func (p NodeSet) One() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } n, err := dql.First(p.Data) if err != nil { return NodeSet{Err: err} } return Root(p.Base, n) } // Single returns a NodeSet containing the single node. // If there are zero or more than one nodes, it returns an error. // ErrNotFound or ErrMultiEntities is returned accordingly. func (p NodeSet) Single() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } n, err := dql.Single(p.Data) if err != nil { return NodeSet{Err: err} } return Root(p.Base, n) } // ----------------------------------------------------------------------------- // Ok returns true if there is no error in the NodeSet. func (p NodeSet) Ok() bool { return p.Err == nil } // _first returns the first node in the NodeSet. // It's required by XGo compiler. func (p NodeSet) XGo_first() (ret *Node, err error) { if p.Err != nil { err = p.Err return } return dql.First(p.Data) } // First returns the first node in the NodeSet. func (p NodeSet) First() (*Node, error) { if p.Err != nil { return nil, p.Err } return dql.First(p.Data) } // Collect retrieves all nodes from the NodeSet. func (p NodeSet) Collect() ([]*Node, error) { if p.Err != nil { return nil, p.Err } return dql.Collect(p.Data), nil } // ----------------------------------------------------------------------------- // Path returns the path of the first node in the NodeSet. // The path is the absolute path to the file or directory (relative to the root // of the file system). // Note the path is not started with a slash. // For example, if the root of the file system is "/home/gopher" and the node // represents the file "/home/gopher/a/b.go", Path would return "a/b.go". // For the root node, Path would return "" (not "/"). func (p NodeSet) Path() (name string, err error) { node, err := p.First() if err != nil { return } return node.Path, nil } // Name returns the name of the first node in the NodeSet. func (p NodeSet) Name() (name string, err error) { node, err := p.First() if err != nil { return } return node.Name() } // IsDir reports whether the first node in the NodeSet is a directory. func (p NodeSet) IsDir() (is bool, err error) { node, err := p.First() if err != nil { return } return node.IsDir() } // Size returns the size of the first node in the NodeSet. func (p NodeSet) Size() (size int64, err error) { node, err := p.First() if err != nil { return } return node.Size() } // Mode returns the file mode of the first node in the NodeSet. func (p NodeSet) Mode() (mode fs.FileMode, err error) { node, err := p.First() if err != nil { return } return node.Mode() } // ModTime returns the modification time of the first node in the NodeSet. func (p NodeSet) ModTime() (modTime time.Time, err error) { node, err := p.First() if err != nil { return } return node.ModTime() } // ----------------------------------------------------------------------------- ================================================ FILE: dql/golang/golang.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package golang import ( "bytes" "go/ast" "go/parser" "go/token" "io" "iter" "reflect" "github.com/goplus/xgo/dql" "github.com/goplus/xgo/dql/reflects" "github.com/qiniu/x/stream" ) const ( XGoPackage = "github.com/goplus/xgo/dql/reflects" ) // ----------------------------------------------------------------------------- // Node represents a Go AST node. type Node = reflects.Node // NodeSet represents a set of Go AST nodes. type NodeSet struct { reflects.NodeSet } // NodeSet(seq) casts a NodeSet from a sequence of nodes. func NodeSet_Cast(seq iter.Seq[Node]) NodeSet { return NodeSet{ NodeSet: reflects.NodeSet{Data: seq}, } } // Root creates a NodeSet containing the provided root node. func Root(doc Node) NodeSet { return NodeSet{ NodeSet: reflects.Root(doc), } } // Nodes creates a NodeSet containing the provided nodes. func Nodes(nodes ...Node) NodeSet { return NodeSet{ NodeSet: reflects.Nodes(nodes...), } } // New creates a NodeSet from the given *ast.File. func New(f *ast.File) NodeSet { return NodeSet{ NodeSet: reflects.New(reflect.ValueOf(f)), } } // Config represents the configuration for parsing Go source code. type Config struct { Mode parser.Mode Fset *token.FileSet } const ( defaultMode = parser.ParseComments ) // parse parses Go source code from the given URI or source. func parse(uri string, src any, conf ...Config) (f *ast.File, err error) { in, err := stream.ReadSourceFromURI(uri, src) if err != nil { return } var c Config if len(conf) > 0 { c = conf[0] } else { c.Mode = defaultMode } if c.Fset == nil { c.Fset = token.NewFileSet() } return parser.ParseFile(c.Fset, uri, in, c.Mode) } // From parses Go source code from the given URI or source, returning a NodeSet. // An optional Config can be provided to customize the parsing behavior. func From(uri string, src any, conf ...Config) NodeSet { f, err := parse(uri, src, conf...) if err != nil { return NodeSet{NodeSet: reflects.NodeSet{Err: err}} } return New(f) } // Source creates a NodeSet from various types of Go sources. // It supports the following source types: // - string: treats the string as a URI, opens the resource, and reads Go source code from it. // - []byte: treated as Go source code. // - *bytes.Buffer: treated as Go source code. // - io.Reader: treated as Go source code. // - *ast.File: creates a NodeSet from the provided *ast.File. // - reflect.Value: creates a NodeSet from the provided reflect.Value (expected to be *ast.File). // - Node: creates a NodeSet containing the single provided node. // - iter.Seq[Node]: returns the provided sequence as a NodeSet. // - NodeSet: returns the provided NodeSet as is. // If the source type is unsupported, it panics. func Source(r any, conf ...Config) (ret NodeSet) { switch v := r.(type) { case string: return From(v, nil, conf...) case []byte: return From("", v, conf...) case *bytes.Buffer: return From("", v, conf...) case io.Reader: return From("", v, conf...) case *ast.File: return New(v) case reflect.Value: return NodeSet{NodeSet: reflects.New(v)} case Node: return NodeSet{NodeSet: reflects.Root(v)} case iter.Seq[Node]: return NodeSet{NodeSet: reflects.NodeSet{Data: v}} case NodeSet: return v default: panic("dql/golang.Source: unsupported source type") } } // ----------------------------------------------------------------------------- // XGo_Enum returns an iterator over the nodes in the NodeSet. func (p NodeSet) XGo_Enum() iter.Seq[NodeSet] { if p.Err != nil { return dql.NopIter[NodeSet] } return func(yield func(NodeSet) bool) { p.Data(func(node Node) bool { return yield(Root(node)) }) } } // XGo_Select returns a NodeSet containing the nodes with the specified name. // - @name // - @"element-name" func (p NodeSet) XGo_Select(name string) NodeSet { return NodeSet{ NodeSet: p.NodeSet.XGo_Select(name), } } // XGo_Elem returns a NodeSet containing the child nodes with the specified name. // - .name // - .“element-name” func (p NodeSet) XGo_Elem(name string) NodeSet { return NodeSet{ NodeSet: p.NodeSet.XGo_Elem(name), } } // XGo_Child returns a NodeSet containing all child nodes of the nodes in the NodeSet. func (p NodeSet) XGo_Child() NodeSet { return NodeSet{ NodeSet: p.NodeSet.XGo_Child(), } } // XGo_Any returns a NodeSet containing all descendant nodes (including the // nodes themselves) with the specified name. // If name is "", it returns all nodes. // - .**.name // - .**.“element-name” // - .**.* func (p NodeSet) XGo_Any(name string) NodeSet { return NodeSet{ NodeSet: p.NodeSet.XGo_Any(name), } } // ----------------------------------------------------------------------------- // All returns a NodeSet containing all nodes. // It's a cache operation for performance optimization when you need to traverse // the nodes multiple times. func (p NodeSet) All() NodeSet { return NodeSet{ NodeSet: p.NodeSet.XGo_all(), } } // One returns a NodeSet containing the first node. // It's a performance optimization when you only need the first node (stop early). func (p NodeSet) One() NodeSet { return NodeSet{ NodeSet: p.NodeSet.XGo_one(), } } // Single returns a NodeSet containing the single node. // If there are zero or more than one nodes, it returns an error. // ErrNotFound or ErrMultipleResults is returned accordingly. func (p NodeSet) Single() NodeSet { return NodeSet{ NodeSet: p.NodeSet.XGo_single(), } } // ----------------------------------------------------------------------------- // Ok returns true if there is no error in the NodeSet. func (p NodeSet) Ok() bool { return p.Err == nil } // XGo_Attr returns the value of the specified attribute from the first node in the // NodeSet. It only retrieves the attribute from the first node. // - $name // - $“attr-name” func (p NodeSet) XGo_Attr__0(name string) any { val, _ := p.XGo_Attr__1(name) return val } // XGo_Attr returns the value of the specified attribute from the first node in the // NodeSet. It only retrieves the attribute from the first node. // - $name // - $“attr-name” func (p NodeSet) XGo_Attr__1(name string) (val any, err error) { val, err = p.NodeSet.XGo_Attr__1(name) if err == nil { switch v := val.(type) { case *ast.Ident: if v != nil { return v.Name, nil } return "", nil case *ast.BasicLit: if v != nil { return v.Value, nil } return "", nil } } return } // Class returns the class name of the first node in the NodeSet. func (p NodeSet) Class() string { return p.XGo_class() } // ----------------------------------------------------------------------------- ================================================ FILE: dql/golang/parse.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package golang import ( "go/ast" "unsafe" ) // ----------------------------------------------------------------------------- // File represents a Go file. type File struct { ast.File // File must contain only the embedded ast.File field. } // ParseFile parses Go source code from the given filename or source, returning a File // object. An optional Config can be provided to customize the parsing behavior. func ParseFile(filename string, src any, conf ...Config) (f *File, err error) { doc, err := parse(filename, src, conf...) if err == nil { f = (*File)(unsafe.Pointer(doc)) } return } // ----------------------------------------------------------------------------- // XGo_Elem returns a NodeSet containing the child nodes with the specified name. // - .name // - .“element-name” func (f *File) XGo_Elem(name string) NodeSet { return New(&f.File).XGo_Elem(name) } // XGo_Child returns a NodeSet containing all child nodes of the node. // - .* func (f *File) XGo_Child() NodeSet { return New(&f.File).XGo_Child() } // XGo_Any returns a NodeSet containing all descendant nodes (including the // node itself) with the specified name. // If name is "", it returns all nodes. // - .**.name // - .**.“element-name” // - .**.* func (f *File) XGo_Any(name string) NodeSet { return New(&f.File).XGo_Any(name) } // ----------------------------------------------------------------------------- ================================================ FILE: dql/html/html.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package html import ( "bytes" "fmt" "io" "iter" "os" "github.com/goplus/xgo/dql" "github.com/qiniu/x/stream" "golang.org/x/net/html" ) const ( XGoPackage = true ) // ----------------------------------------------------------------------------- // NodeSet represents a set of HTML nodes. type NodeSet struct { Data iter.Seq[*Node] Err error } // NodeSet(seq) casts a NodeSet from a sequence of nodes. func NodeSet_Cast(seq iter.Seq[*Node]) NodeSet { return NodeSet{Data: seq} } // Root creates a NodeSet containing the provided root node. func Root(doc *Node) NodeSet { if doc.Type == html.DocumentNode { if n := doc.FirstChild; n.NextSibling == nil { doc = toNode(n) // skip document node } } return NodeSet{ Data: func(yield func(*Node) bool) { yield(doc) }, } } // Nodes creates a NodeSet containing the provided nodes. func Nodes(nodes ...*Node) NodeSet { return NodeSet{ Data: func(yield func(*Node) bool) { for _, node := range nodes { if !yield(node) { break } } }, } } // New parses the HTML document from the provided reader and returns a NodeSet // containing the root node. If there is an error during parsing, the NodeSet's // Err field is set. func New(r io.Reader) NodeSet { doc, err := html.Parse(r) if err != nil { return NodeSet{Err: err} } return Root(toNode(doc)) } // Source creates a NodeSet from various types of sources: // - string: treated as an URL to read HTML content from. // - []byte: treated as raw HTML content. // - io.Reader: reads HTML content from the reader. // - *Node: creates a NodeSet containing the single provided node. // - iter.Seq[*Node]: directly uses the provided sequence of nodes. // - NodeSet: returns the provided NodeSet as is. // If the source type is unsupported, it panics. func Source(r any) (ret NodeSet) { switch v := r.(type) { case string: f, err := stream.Open(v) if err != nil { return NodeSet{Err: err} } defer f.Close() return New(f) case []byte: r := bytes.NewReader(v) return New(r) case io.Reader: return New(v) case *Node: return Root(v) case iter.Seq[*Node]: return NodeSet{Data: v} case NodeSet: return v default: panic("dql/html.Source: unsupported source type") } } // ----------------------------------------------------------------------------- // XGo_Enum returns an iterator over the nodes in the NodeSet. func (p NodeSet) XGo_Enum() iter.Seq[NodeSet] { if p.Err != nil { return dql.NopIter[NodeSet] } return func(yield func(NodeSet) bool) { p.Data(func(node *Node) bool { return yield(Root(node)) }) } } // XGo_Select returns a NodeSet containing the nodes with the specified name. // - @name // - @"element-name" func (p NodeSet) XGo_Select(name string) NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { return selectNode(node, name, yield) }) }, } } // selectNode yields the node if it matches the specified name. func selectNode(node *Node, name string, yield func(*Node) bool) bool { if node.Type == html.ElementNode && node.Data == name { return yield(node) } return true } // XGo_Elem returns a NodeSet containing the child nodes with the specified name. // - .name // - .“element-name” func (p NodeSet) XGo_Elem(name string) NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { return yieldNode(node, name, yield) }) }, } } // yieldNode yields the child node with the specified name if it exists. func yieldNode(n *Node, name string, yield func(*Node) bool) bool { for c := n.FirstChild; c != nil; c = c.NextSibling { if c.Type == html.ElementNode && c.Data == name { if !yield(toNode(c)) { return false } } } return true } // XGo_Child returns a NodeSet containing all child nodes of the nodes in the NodeSet. func (p NodeSet) XGo_Child() NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(*Node) bool) { p.Data(func(n *Node) bool { return yieldChildNodes(n, yield) }) }, } } // yieldChildNodes yields all child nodes of the given node. func yieldChildNodes(n *Node, yield func(*Node) bool) bool { for c := n.FirstChild; c != nil; c = c.NextSibling { if !yield(toNode(c)) { return false } } return true } // XGo_Any returns a NodeSet containing all descendant nodes (including the // nodes themselves) with the specified name. // If name is "textNode", it returns all text nodes. // If name is "", it returns all nodes. // - .**.name // - .**.“element-name” // - .**.* func (p NodeSet) XGo_Any(name string) NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { return yieldAnyNodes(node, name, yield) }) }, } } // yieldAnyNodes yields all descendant nodes of the given node that match the // specified name. If name is "textNode", it yields text nodes. If name is "", // it yields all nodes. func yieldAnyNodes(n *Node, name string, yield func(*Node) bool) bool { switch name { case "textNode": if n.Type == html.TextNode { if !yield(n) { return false } } case "": // .**.* if !yield(n) { return false } default: if n.Type == html.ElementNode && n.Data == name { if !yield(n) { return false } } } for c := n.FirstChild; c != nil; c = c.NextSibling { if !yieldAnyNodes(toNode(c), name, yield) { return false } } return true } // ----------------------------------------------------------------------------- // All returns a NodeSet containing all nodes. // It's a cache operation for performance optimization when you need to traverse // the nodes multiple times. func (p NodeSet) All() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } nodes := dql.Collect(p.Data) return Nodes(nodes...) } // One returns a NodeSet containing the first node. // It's a performance optimization when you only need the first node (stop early). func (p NodeSet) One() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } n, err := dql.First(p.Data) if err != nil { return NodeSet{Err: err} } return Root(n) } // Single returns a NodeSet containing the single node. // If there are zero or more than one nodes, it returns an error. // ErrNotFound or ErrMultiEntities is returned accordingly. func (p NodeSet) Single() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } n, err := dql.Single(p.Data) if err != nil { return NodeSet{Err: err} } return Root(n) } // ParentN returns a NodeSet containing the N-th parent nodes. func (p NodeSet) ParentN(n int) NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { return yieldParentN(node, n, yield) }) }, } } func yieldParentN(node *Node, n int, yield func(*Node) bool) bool { if n > 0 { for { node = toNode(node.Parent) if node == nil { break } n-- if n == 0 { return yield(node) } } } return true } // Parent returns a NodeSet containing the parent nodes. func (p NodeSet) Parent() NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { if next := node.Parent; next != nil { return yield(toNode(next)) } return true }) }, } } // PrevSibling returns a NodeSet containing the previous sibling nodes. func (p NodeSet) PrevSibling() NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { if next := node.PrevSibling; next != nil { return yield(toNode(next)) } return true }) }, } } // NextSibling returns a NodeSet containing the next sibling nodes. func (p NodeSet) NextSibling() NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { if next := node.NextSibling; next != nil { return yield(toNode(next)) } return true }) }, } } // FirstElementChild returns a NodeSet containing the first element // child of each node. func (p NodeSet) FirstElementChild() NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { for c := node.FirstChild; c != nil; c = c.NextSibling { if c.Type == html.ElementNode { return yield(toNode(c)) } } return true }) }, } } // TextNode returns a NodeSet containing all text nodes. func (p NodeSet) TextNode() NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { return yieldNodeType(node, html.TextNode, yield) }) }, } } func yieldNodeType(node *Node, typ html.NodeType, yield func(*Node) bool) bool { for c := node.FirstChild; c != nil; c = c.NextSibling { if c.Type == typ { if !yield(toNode(c)) { return false } } } return true } // ----------------------------------------------------------------------------- // Dump prints the nodes in the NodeSet for debugging purposes. func (p NodeSet) Dump() NodeSet { if p.Err == nil { p.Data(func(node *Node) bool { switch node.Type { case html.ElementNode: fmt.Fprintln(os.Stderr, "==> element:", node.Data, node.Attr) case html.TextNode: fmt.Fprintln(os.Stderr, "==> text:", node.Data) case html.DocumentNode: fmt.Fprintln(os.Stderr, "==> document") } return true }) } return p } // ----------------------------------------------------------------------------- // Ok returns true if there is no error in the NodeSet. func (p NodeSet) Ok() bool { return p.Err == nil } // _first returns the first node in the NodeSet. // It's required by XGo compiler. func (p NodeSet) XGo_first() (ret *Node, err error) { if p.Err != nil { err = p.Err return } return dql.First(p.Data) } // First returns the first node in the NodeSet. func (p NodeSet) First() (*Node, error) { if p.Err != nil { return nil, p.Err } return dql.First(p.Data) } // Collect retrieves all nodes from the NodeSet. func (p NodeSet) Collect() ([]*Node, error) { if p.Err != nil { return nil, p.Err } return dql.Collect(p.Data), nil } // Name returns the name of the first node in the NodeSet. // empty string is returned if the NodeSet is empty or the first node is not // an element node. func (p NodeSet) Name() string { node, err := p.First() if err == nil { if node.Type == html.ElementNode { return node.Data } } return "" } // Value returns the data content of the first node in the NodeSet. func (p NodeSet) Value__0() string { val, _ := p.Value__1() return val } // Value returns the data content of the first node in the NodeSet. func (p NodeSet) Value__1() (val string, err error) { node, err := p.First() if err == nil { return node.Data, nil } return } // HasAttr returns true if the first node in the NodeSet has the specified attribute. // It returns false otherwise. func (p NodeSet) HasAttr(name string) bool { node, err := p.First() if err == nil { return node.HasAttr(name) } return false } // XGo_Attr returns the value of the specified attribute from the first node in the // NodeSet. It only retrieves the attribute from the first node. // - $name // - $“attr-name” func (p NodeSet) XGo_Attr__0(name string) string { val, _ := p.XGo_Attr__1(name) return val } // XGo_Attr returns the value of the specified attribute from the first node in the // NodeSet. It only retrieves the attribute from the first node. // - $name // - $“attr-name” func (p NodeSet) XGo_Attr__1(name string) (val string, err error) { node, err := p.First() if err == nil { return node.XGo_Attr__1(name) } return } // ----------------------------------------------------------------------------- ================================================ FILE: dql/html/html_test.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package html import ( "strings" "testing" "golang.org/x/net/html" "golang.org/x/net/html/atom" ) func TestTextOf(t *testing.T) { cases := []struct { name string html string want string }{ {"p", `

Aspect ratio of the generated images (width:height)

`, "Aspect ratio of the generated images (width:height)\n"}, {"codeBody", codeBody, `{ "code": 0, // Error codes; specific definitions see Error codes "message": "string", // Error information "request_id": "string", // Request ID, generated by the system, for tracking and troubleshooting "data": { "task_id": "string", // Task ID, generated by the system "task_status": "string", // Task status: submitted, processing, succeed, failed "task_info": { // Task creation parameters "external_task_id": "string" // Customer-defined task ID }, "created_at": 1722769557708, // Task creation time, Unix timestamp, ms "updated_at": 1722769557708 // Task update time, Unix timestamp, ms } } `}, } for _, c := range cases { doc, e := html.Parse(strings.NewReader(c.html)) if e != nil { t.Fatalf("%s error: %v", c.name, e) } if got := textOf(doc, false, noFilter{}); got != c.want { t.Errorf("%s: `%s`, want `%s`", c.name, got, c.want) } } } const codeBody = `
{
"code": 0, // Error codes; specific definitions see Error codes
"message": "string", // Error information
"request_id": "string", // Request ID, generated by the system, for tracking and troubleshooting
"data": {
"task_id": "string", // Task ID, generated by the system
"task_status": "string", // Task status: submitted, processing, succeed, failed
"task_info": { // Task creation parameters
  "external_task_id": "string" // Customer-defined task ID
},
"created_at": 1722769557708, // Task creation time, Unix timestamp, ms
"updated_at": 1722769557708 // Task update time, Unix timestamp, ms
}
}
` type commentRemover struct { noFilter } func (f commentRemover) Filter(node *html.Node) bool { if node.DataAtom != atom.Span { return true } for _, a := range node.Attr { if a.Key == "class" && a.Val == "hljs-comment" { return false } } return true } func TestTextFilter(t *testing.T) { cases := []struct { name string html string want string }{ {"codeBody", codeBody, `{ "code": 0, "message": "string", "request_id": "string", "data": { "task_id": "string", "task_status": "string", "task_info": { "external_task_id": "string" }, "created_at": 1722769557708, "updated_at": 1722769557708 } } `}, } for _, c := range cases { doc, e := html.Parse(strings.NewReader(c.html)) if e != nil { t.Fatalf("%s error: %v", c.name, e) } if got := textOf(doc, false, commentRemover{}); got != c.want { t.Errorf("%s: `%s`, want `%s`", c.name, got, c.want) } } } ================================================ FILE: dql/html/node.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package html import ( "io" "unsafe" "github.com/goplus/xgo/dql" "golang.org/x/net/html" ) // ----------------------------------------------------------------------------- // Node represents an HTML node. type Node struct { html.Node // Node must contain only the embedded html.Node field. } // Parse returns the parse tree for the HTML from the given Reader. func Parse(r io.Reader) (n *Node, err error) { doc, err := html.Parse(r) if err == nil { n = toNode(doc) } return } func toNode(n *html.Node) *Node { return (*Node)(unsafe.Pointer(n)) } // ----------------------------------------------------------------------------- // XGo_Elem returns a NodeSet containing the child nodes with the specified name. // - .name // - .“element-name” func (n *Node) XGo_Elem(name string) NodeSet { return Root(n).XGo_Elem(name) } // XGo_Child returns a NodeSet containing all child nodes of the node. // - .* func (n *Node) XGo_Child() NodeSet { return Root(n).XGo_Child() } // XGo_Any returns a NodeSet containing all descendant nodes (including the // node itself) with the specified name. // If name is "", it returns all nodes. // - .**.name // - .**.“element-name” // - .**.* func (n *Node) XGo_Any(name string) NodeSet { return Root(n).XGo_Any(name) } // Dump prints the node for debugging purposes. func (n *Node) Dump() NodeSet { return Root(n).Dump() } // ----------------------------------------------------------------------------- // Name returns the name of the node if it's an element node, or an empty string // otherwise. func (n *Node) Name() string { if n.Type == html.ElementNode { return n.Data } return "" } // Value returns the data of the node. func (n *Node) Value() string { return n.Data } // HasAttr returns true if the node has the specified attribute. func (n *Node) HasAttr(name string) bool { for _, attr := range n.Attr { if attr.Key == name { return true } } return false } // XGo_Attr returns the value of the specified attribute from the node. // If the attribute is not found, it returns an empty string. // - $name // - $“attr-name” func (n *Node) XGo_Attr__0(name string) string { val, _ := n.XGo_Attr__1(name) return val } // XGo_Attr returns the value of the specified attribute from the node. // If the attribute is not found, it returns ErrNotFound. // - $name // - $“attr-name” func (n *Node) XGo_Attr__1(name string) (string, error) { for _, attr := range n.Attr { if attr.Key == name { return attr.Val, nil } } return "", dql.ErrNotFound } // ----------------------------------------------------------------------------- ================================================ FILE: dql/html/text.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package html import ( "github.com/goplus/xgo/dql" "golang.org/x/net/html" "golang.org/x/net/html/atom" ) // ----------------------------------------------------------------------------- // NodeFilter is the interface for filtering nodes when retrieving text content. type NodeFilter interface { // Filter returns true if the node should be included in the text content. Filter(*html.Node) bool // TextNodeData returns the text data of a text node. It can be used to customize // the text data of a text node, for example, to trim spaces or to replace certain // characters. TextNodeData(*html.Node) string } type noFilter struct{} func (f noFilter) Filter(*html.Node) bool { return true } func (f noFilter) TextNodeData(node *html.Node) string { return node.Data } // textOf returns text data of all node's children. func textOf[F NodeFilter](node *html.Node, outer bool, f F) string { p := textPrinter[F]{ nodef: f, } p.printNode(node, outer, false) return string(p.data) } type textPrinter[F NodeFilter] struct { data []byte nodef F notLineStart bool hasSpace bool } func isSpace(c byte) bool { return c == ' ' || c == '\t' || c == '\r' || c == '\n' } func (p *textPrinter[F]) printCollapsed(v string) { for len(v) > 0 { n := len(v) i := 0 for i < n && isSpace(v[i]) { i++ // skip leading spaces } if i > 0 { p.hasSpace = true } if i >= n { break } if p.notLineStart && p.hasSpace { p.data = append(p.data, ' ') } else { p.notLineStart = true } p.hasSpace = false start := i i++ for i < n && !isSpace(v[i]) { i++ } p.data = append(p.data, v[start:i]...) v = v[i:] } } func (p *textPrinter[F]) printVerbatim(v string) { p.data = append(p.data, v...) if len(v) > 0 { last := v[len(v)-1] p.notLineStart = last != '\n' p.hasSpace = p.notLineStart && isSpace(last) } } func (p *textPrinter[F]) printNode(node *html.Node, outer, verbatim bool) { if node == nil { return } f := p.nodef if node.Type == html.TextNode { data := f.TextNodeData(node) if verbatim { p.printVerbatim(data) } else { p.printCollapsed(data) } return } verbatim = verbatim || node.DataAtom == atom.Pre for child := node.FirstChild; child != nil; child = child.NextSibling { if f.Filter(child) { p.printNode(child, true, verbatim) } } if outer { switch node.DataAtom { case atom.P, atom.Div, atom.Br, atom.H1, atom.H2, atom.H3, atom.H4, atom.H5, atom.H6, atom.Li, atom.Blockquote, atom.Pre: p.data = append(p.data, '\n') p.notLineStart = false } } } // ----------------------------------------------------------------------------- // Text retrieves the text content of the NodeSet. It only retrieves from the // first node in the NodeSet. It ignores any error and returns an empty string // if there is an error. func (p NodeSet) Text__0() string { val, _ := p.Text__1() return val } // Text retrieves the text content of the NodeSet. It only retrieves from the // first node in the NodeSet. func (p NodeSet) Text__1() (val string, err error) { node, err := p.First() if err == nil { val = textOf(&node.Node, false, noFilter{}) } return } // Text retrieves the text content of the NodeSet with a node filter. It only // retrieves from the first node in the NodeSet. // // The node filter is used to filter the nodes when retrieving text content. If // the node filter returns false for a node, the node and its children will be // ignored when retrieving text content. // // The outer parameter specifies whether to include the text content of the // outer node itself. If outer is true, the text content of the outer node will // be included; otherwise, only the text content of the inner nodes will be // included. func Text[F NodeFilter](ns NodeSet, outer bool, f F) (val string, err error) { node, err := ns.First() if err == nil { val = textOf(&node.Node, outer, f) } return } // Int retrieves the integer value from the text content of the first node in // the NodeSet. func (p NodeSet) Int() (int, error) { text, err := p.Text__1() if err != nil { return 0, err } return dql.Int(text) } // ----------------------------------------------------------------------------- ================================================ FILE: dql/json/json.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package json import ( "bytes" "encoding/json" "io" "iter" "github.com/goplus/xgo/dql/maps" "github.com/qiniu/x/stream" ) const ( XGoPackage = "github.com/goplus/xgo/dql/maps" ) // ----------------------------------------------------------------------------- // Node represents a map[string]any or []any node. type Node = maps.Node // NodeSet represents a set of JSON nodes. type NodeSet = maps.NodeSet // New creates a JSON NodeSet from JSON data read from r. func New(r io.Reader) NodeSet { var data any err := json.NewDecoder(r).Decode(&data) if err != nil { return NodeSet{Err: err} } return maps.New(data) } // Source creates a JSON NodeSet from various source types: // - string: treats the string as a file path, opens the file, and reads JSON data from it. // - []byte: reads JSON data from the byte slice. // - io.Reader: reads JSON data from the provided reader. // - map[string]any: creates a NodeSet from the provided map. // - []any: creates a NodeSet from the provided slice. // - Node: creates a NodeSet containing the single provided node. // - iter.Seq[Node]: directly uses the provided sequence of nodes. // - NodeSet: returns the provided NodeSet as is. // If the source type is unsupported, it panics. func Source(r any) (ret NodeSet) { switch v := r.(type) { case string: f, err := stream.Open(v) if err != nil { return NodeSet{Err: err} } defer f.Close() return New(f) case []byte: r := bytes.NewReader(v) return New(r) case io.Reader: return New(v) case map[string]any, []any: return maps.New(v) case Node: return maps.Root(v) case iter.Seq[Node]: return NodeSet{Data: v} case NodeSet: return v default: panic("dql/json.Source: unsupported source type") } } // ----------------------------------------------------------------------------- ================================================ FILE: dql/maps/maps.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package maps import ( "iter" "github.com/goplus/xgo/dql" ) const ( XGoPackage = true ) // ----------------------------------------------------------------------------- // NodeSet represents a set of nodes. type NodeSet struct { Data iter.Seq[Node] Err error } // NodeSet(seq) casts a NodeSet from a sequence of nodes. func NodeSet_Cast(seq iter.Seq[Node]) NodeSet { return NodeSet{Data: seq} } // Root creates a NodeSet containing the provided root node. func Root(doc Node) NodeSet { return NodeSet{ Data: func(yield func(Node) bool) { yield(doc) }, } } // Nodes creates a NodeSet containing the provided nodes. func Nodes(nodes ...Node) NodeSet { return NodeSet{ Data: func(yield func(Node) bool) { for _, node := range nodes { if !yield(node) { break } } }, } } // New creates a NodeSet containing a single node from the provided document. // The document should be of type map[string]any or []any. // If the document type is invalid, it panics. func New(doc any) NodeSet { switch doc.(type) { case map[string]any, []any: default: panic("dql/maps.New: invalid document type, should be map[string]any or []any") } return NodeSet{ Data: func(yield func(Node) bool) { yield(Node{Name: "", Value: doc}) }, } } // Source creates a NodeSet from various types of sources: // - map[string]any: creates a NodeSet containing the single provided node. // - []any: creates a NodeSet containing the single provided node. // - Node: creates a NodeSet containing the single provided node. // - iter.Seq[Node]: directly uses the provided sequence of nodes. // - NodeSet: returns the provided NodeSet as is. // If the source type is unsupported, it panics. func Source(r any) (ret NodeSet) { switch v := r.(type) { case map[string]any, []any: return New(v) case Node: return Root(v) case iter.Seq[Node]: return NodeSet{Data: v} case NodeSet: return v default: panic("dql/maps.Source: unsupported source type") } } // ----------------------------------------------------------------------------- // XGo_Enum returns an iterator over the nodes in the NodeSet. func (p NodeSet) XGo_Enum() iter.Seq[NodeSet] { if p.Err != nil { return dql.NopIter[NodeSet] } return func(yield func(NodeSet) bool) { p.Data(func(node Node) bool { return yield(Root(node)) }) } } // XGo_Select returns a NodeSet containing the nodes with the specified name. // - @name // - @"element-name" func (p NodeSet) XGo_Select(name string) NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(Node) bool) { p.Data(func(node Node) bool { if node.Name == name { return yield(node) } return true }) }, } } // XGo_Elem returns a NodeSet containing the child nodes with the specified name. // - .name // - .“element-name” func (p NodeSet) XGo_Elem(name string) NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(Node) bool) { p.Data(func(node Node) bool { return yieldElem(node, name, yield) }) }, } } // yieldElem yields the child node with the specified name if it exists. func yieldElem(node Node, name string, yield func(Node) bool) bool { if children, ok := node.Value.(map[string]any); ok { if v, ok := children[name]; ok { return yield(Node{Name: name, Value: v}) } } return true } // XGo_Child returns a NodeSet containing all child nodes of the nodes in the NodeSet. func (p NodeSet) XGo_Child() NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(Node) bool) { p.Data(func(node Node) bool { return yieldChildNodes(node, yield) }) }, } } // yieldChildNodes yields all child nodes of the given node. func yieldChildNodes(node Node, yield func(Node) bool) bool { switch children := node.Value.(type) { case map[string]any: for k, v := range children { if !yield(Node{Name: k, Value: v}) { return false } } case []any: for _, v := range children { if !yield(Node{Name: "", Value: v}) { return false } } } return true } // XGo_Any returns a NodeSet containing all descendant nodes (including the // nodes themselves) with the specified name. // If name is "", it returns all nodes. // - .**.name // - .**.“element-name” // - .**.* func (p NodeSet) XGo_Any(name string) NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(Node) bool) { p.Data(func(node Node) bool { return yieldAnyNodes(name, node, yield) }) }, } } // yieldAnyNodes yields all descendant nodes of the given node that match the // specified name. If name is "", it yields all nodes. func yieldAnyNodes(name string, node Node, yield func(Node) bool) bool { if name == "" || node.Name == name { if !yield(node) { return false } } switch children := node.Value.(type) { case map[string]any: for k, v := range children { if !yieldAnyNode(name, k, v, yield) { return false } } case []any: for _, v := range children { if !yieldAnyNode(name, "", v, yield) { return false } } } return true } // yieldAnyNode recursively traverses into v if it is a map[string]any or []any, // looking for descendant nodes matching name. func yieldAnyNode(name, k string, v any, yield func(Node) bool) bool { switch v.(type) { case map[string]any, []any: return yieldAnyNodes(name, Node{Name: k, Value: v}, yield) } return true } // ----------------------------------------------------------------------------- // _all returns a NodeSet containing all nodes. // It's a cache operation for performance optimization when you need to traverse // the nodes multiple times. func (p NodeSet) XGo_all() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } nodes := dql.Collect(p.Data) return Nodes(nodes...) } // _one returns a NodeSet containing the first node. // It's a performance optimization when you only need the first node (stop early). func (p NodeSet) XGo_one() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } n, err := dql.First(p.Data) if err != nil { return NodeSet{Err: err} } return Root(n) } // _single returns a NodeSet containing the single node. // If there are zero or more than one nodes, it returns an error. // ErrNotFound or ErrMultipleResults is returned accordingly. func (p NodeSet) XGo_single() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } n, err := dql.Single(p.Data) if err != nil { return NodeSet{Err: err} } return Root(n) } // ----------------------------------------------------------------------------- // _ok returns true if there is no error in the NodeSet. func (p NodeSet) XGo_ok() bool { return p.Err == nil } // _first returns the first node in the NodeSet. func (p NodeSet) XGo_first() (Node, error) { if p.Err != nil { return Node{}, p.Err } return dql.First(p.Data) } // _name returns the name of the first node in the NodeSet. // empty string is returned if the NodeSet is empty or error occurs. func (p NodeSet) XGo_name__0() string { val, _ := p.XGo_name__1() return val } // _name returns the name of the first node in the NodeSet. // If the NodeSet is empty, it returns ErrNotFound. func (p NodeSet) XGo_name__1() (ret string, err error) { node, err := p.XGo_first() if err == nil { ret = node.Name } return } // _value returns the value of the first node in the NodeSet. // nil is returned if the NodeSet is empty or error occurs. func (p NodeSet) XGo_value__0() any { val, _ := p.XGo_value__1() return val } // _value returns the value of the first node in the NodeSet. // If the NodeSet is empty, it returns ErrNotFound. func (p NodeSet) XGo_value__1() (ret any, err error) { node, err := p.XGo_first() if err == nil { ret = node.Value } return } // _hasAttr returns true if the first node in the NodeSet has the specified attribute. // It returns false otherwise. func (p NodeSet) XGo_hasAttr(name string) bool { node, err := p.XGo_first() if err == nil { return node.XGo_hasAttr(name) } return false } // XGo_Attr returns the value of the specified attribute from the first node in the // NodeSet. It only retrieves the attribute from the first node. // - $name // - $“attr-name” func (p NodeSet) XGo_Attr__0(name string) any { val, _ := p.XGo_Attr__1(name) return val } // XGo_Attr returns the value of the specified attribute from the first node in the // NodeSet. It only retrieves the attribute from the first node. // - $name // - $“attr-name” func (p NodeSet) XGo_Attr__1(name string) (val any, err error) { node, err := p.XGo_first() if err == nil { return node.XGo_Attr__1(name) } return } // ----------------------------------------------------------------------------- ================================================ FILE: dql/maps/node.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package maps import ( "github.com/goplus/xgo/dql" ) // ----------------------------------------------------------------------------- // Node represents a named value in a DQL query tree. type Node struct { Name string Value any } // XGo_Elem returns the child node with the specified name. // - .name // - .“element-name” func (n Node) XGo_Elem(name string) (ret Node) { if children, ok := n.Value.(map[string]any); ok { if v, ok := children[name]; ok { ret = Node{Name: name, Value: v} } } return } // XGo_Child returns a NodeSet containing all child nodes of the node. // - .* func (n Node) XGo_Child() NodeSet { return Root(n).XGo_Child() } // XGo_Any returns a NodeSet containing all descendant nodes (including the // node itself) with the specified name. // If name is "", it returns all nodes. // - .**.name // - .**.“element-name” // - .**.* func (n Node) XGo_Any(name string) NodeSet { return Root(n).XGo_Any(name) } // ----------------------------------------------------------------------------- // _hasAttr returns true if the node has the specified attribute. func (n Node) XGo_hasAttr(name string) bool { switch children := n.Value.(type) { case map[string]any: _, ok := children[name] return ok } return false } // XGo_Attr returns the value of the specified attribute from the node. // If the attribute does not exist, it returns nil. // - $name // - $“attr-name” func (n Node) XGo_Attr__0(name string) any { val, _ := n.XGo_Attr__1(name) return val } // XGo_Attr returns the value of the specified attribute from the node. // If the attribute does not exist, it returns ErrNotFound. // - $name // - $“attr-name” func (n Node) XGo_Attr__1(name string) (any, error) { switch children := n.Value.(type) { case map[string]any: if v, ok := children[name]; ok { return v, nil } } return nil, dql.ErrNotFound } // ----------------------------------------------------------------------------- ================================================ FILE: dql/reflects/node.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package reflects import ( "reflect" "github.com/goplus/xgo/dql" ) // capitalize capitalizes the first letter of the given name. func capitalize(name string) string { if name != "" { if c := name[0]; c >= 'a' && c <= 'z' { return string(c-'a'+'A') + name[1:] } } return name } // uncapitalize uncapitalizes the first letter of the given name. func uncapitalize(name string) string { if name != "" { if c := name[0]; c >= 'A' && c <= 'Z' { return string(c-'A'+'a') + name[1:] } } return name } func allowAnyMethod(obj reflect.Value, name string) bool { return true } var ( tyError = reflect.TypeFor[error]() ) func lookup(obj reflect.Value, name string, allowMthd func(reflect.Value, string) bool) (ret reflect.Value) { kind, node := deref(obj) switch kind { case reflect.Struct: name = capitalize(name) ret = node.FieldByName(name) if !ret.IsValid() { if mth := obj.MethodByName(name); mth.IsValid() { if t := mth.Type(); t.NumIn() == 0 && t.NumOut() == 1 && t.Out(0) != tyError { if allowMthd == nil { return mth // only find the method, do not call it } else if allowMthd(obj, name) { // method call as attribute value ret = mth.Call(nil)[0] } } } } case reflect.Map: ret = node.MapIndex(reflect.ValueOf(name)) } return } func deref(v reflect.Value) (reflect.Kind, reflect.Value) { kind := v.Kind() if kind == reflect.Interface { v = v.Elem() kind = v.Kind() } if kind == reflect.Pointer { v = v.Elem() kind = v.Kind() } return kind, v } // ----------------------------------------------------------------------------- // Node represents a named value in a DQL query tree. type Node struct { Name string Value reflect.Value } // XGo_Elem returns the child node with the specified name. // - .name // - .“element-name” func (n Node) XGo_Elem(name string) (ret Node) { return n.XGo_ElemEx(name, allowAnyMethod) } // XGo_ElemEx returns the child node with the specified name. // It allows you to specify a custom function to determine whether to call a method. // - .name // - .“element-name” func (n Node) XGo_ElemEx(name string, allowMthd func(reflect.Value, string) bool) (ret Node) { if v := lookup(n.Value, name, allowMthd); v.IsValid() { ret = Node{Name: name, Value: v} } return } // XGo_Child returns a NodeSet containing all child nodes of the node. // - .* func (n Node) XGo_Child() NodeSet { return Root(n).XGo_Child() } // XGo_Any returns a NodeSet containing all descendant nodes (including the // node itself) with the specified name. // If name is "", it returns all nodes. // - .**.name // - .**.“element-name” // - .**.* func (n Node) XGo_Any(name string) NodeSet { return Root(n).XGo_Any(name) } // ----------------------------------------------------------------------------- // _hasAttr checks if the node has an attribute with the specified name. func (n Node) XGo_hasAttr(name string) bool { return lookup(n.Value, name, nil).IsValid() } // XGo_Attr returns the value of the attribute with the specified name. // If the attribute does not exist, it returns nil. // - $name // - $“attr-name” func (n Node) XGo_Attr__0(name string) any { val, _ := n.XGo_AttrEx(name, allowAnyMethod) return val } // XGo_Attr returns the value of the attribute with the specified name. // If the attribute does not exist, it returns ErrNotFound. // - $name // - $“attr-name” func (n Node) XGo_Attr__1(name string) (any, error) { return n.XGo_AttrEx(name, allowAnyMethod) } // XGo_AttrEx returns the value of the attribute with the specified name. // If the attribute does not exist, it returns ErrNotFound. // It allows you to specify a custom function to determine whether to call a method. // - $name // - $“attr-name” func (n Node) XGo_AttrEx(name string, allowMthd func(reflect.Value, string) bool) (any, error) { if v := lookup(n.Value, name, allowMthd); v.IsValid() { return v.Interface(), nil } return nil, dql.ErrNotFound } // ----------------------------------------------------------------------------- ================================================ FILE: dql/reflects/reflects.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package reflects import ( "iter" "reflect" "github.com/goplus/xgo/dql" ) const ( XGoPackage = true ) // ----------------------------------------------------------------------------- // NodeSet represents a set of nodes. type NodeSet struct { Data iter.Seq[Node] Err error } // NodeSet(seq) casts a NodeSet from a sequence of nodes. func NodeSet_Cast(seq iter.Seq[Node]) NodeSet { return NodeSet{Data: seq} } // Root creates a NodeSet containing the provided root node. func Root(doc Node) NodeSet { return NodeSet{ Data: func(yield func(Node) bool) { yield(doc) }, } } // Nodes creates a NodeSet containing the provided nodes. func Nodes(nodes ...Node) NodeSet { return NodeSet{ Data: func(yield func(Node) bool) { for _, node := range nodes { if !yield(node) { break } } }, } } // New creates a NodeSet containing a single provided node. func New(doc reflect.Value) NodeSet { return NodeSet{ Data: func(yield func(Node) bool) { yield(Node{Name: "", Value: doc}) }, } } // Source creates a NodeSet from various types of sources: // - reflect.Value: creates a NodeSet containing the single provided node. // - Node: creates a NodeSet containing the single provided node. // - iter.Seq[Node]: directly uses the provided sequence of nodes. // - NodeSet: returns the provided NodeSet as is. // - any other type: uses reflect.ValueOf to create a NodeSet. func Source(r any) (ret NodeSet) { switch v := r.(type) { case reflect.Value: return New(v) case Node: return Root(v) case iter.Seq[Node]: return NodeSet{Data: v} case NodeSet: return v default: return New(reflect.ValueOf(r)) } } // XGo_Enum returns an iterator over the nodes in the NodeSet. func (p NodeSet) XGo_Enum() iter.Seq[NodeSet] { if p.Err != nil { return dql.NopIter[NodeSet] } return func(yield func(NodeSet) bool) { p.Data(func(node Node) bool { return yield(Root(node)) }) } } // XGo_Select returns a NodeSet containing the nodes with the specified name. // - @name // - @"element-name" func (p NodeSet) XGo_Select(name string) NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(Node) bool) { p.Data(func(node Node) bool { if node.Name == name { return yield(node) } return true }) }, } } // XGo_Elem returns a NodeSet containing the child nodes with the specified name. // - .name // - .“element-name” func (p NodeSet) XGo_Elem(name string) NodeSet { return p.XGo_ElemEx(name, allowAnyMethod) } // XGo_ElemEx returns a NodeSet containing the child nodes with the specified name. // It allows you to specify a custom function to determine whether to call a method. // - .name // - .“element-name” func (p NodeSet) XGo_ElemEx(name string, allowMthd func(reflect.Value, string) bool) NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(Node) bool) { p.Data(func(node Node) bool { return yieldElem(node, name, allowMthd, yield) }) }, } } // yieldElem yields the child node with the specified name if it exists. func yieldElem(node Node, name string, allowMthd func(reflect.Value, string) bool, yield func(Node) bool) bool { if v := lookup(node.Value, name, allowMthd); v.IsValid() { return yield(Node{Name: name, Value: v}) } return true } func yieldChildNodes(node reflect.Value, yield func(Node) bool) bool { kind, node := deref(node) switch kind { case reflect.Struct: typ := node.Type() for i, n := 0, typ.NumField(); i < n; i++ { if v := node.Field(i); v.CanInterface() { // only yield exported fields if !yield(Node{Name: uncapitalize(typ.Field(i).Name), Value: v}) { return false } } } case reflect.Map: typ := node.Type() if typ.Key().Kind() != reflect.String { // Only support map[string]T break } it := node.MapRange() for it.Next() { if !yield(Node{Name: it.Key().String(), Value: it.Value()}) { return false } } case reflect.Slice: for i := 0; i < node.Len(); i++ { if !yield(Node{Name: "", Value: node.Index(i)}) { return false } } } return true } // yieldAnyNodes yields all descendant nodes of the given node that match the // specified name. If name is "", it yields all nodes. func yieldAnyNodes(name string, node Node, yield func(Node) bool) bool { if name == "" || node.Name == name { if !yield(node) { return false } } return yieldChildNodes(node.Value, func(n Node) bool { return yieldAnyNodes(name, n, yield) }) } // XGo_Child returns a NodeSet containing all child nodes of the nodes in the NodeSet. func (p NodeSet) XGo_Child() NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(Node) bool) { p.Data(func(node Node) bool { return yieldChildNodes(node.Value, yield) }) }, } } // XGo_Any returns a NodeSet containing all descendant nodes (including the // nodes themselves) with the specified name. // If name is "", it returns all nodes. // - .**.name // - .**.“element-name” // - .**.* func (p NodeSet) XGo_Any(name string) NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(Node) bool) { p.Data(func(node Node) bool { return yieldAnyNodes(name, node, yield) }) }, } } // ----------------------------------------------------------------------------- // _all returns a NodeSet containing all nodes. // It's a cache operation for performance optimization when you need to traverse // the nodes multiple times. func (p NodeSet) XGo_all() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } nodes := dql.Collect(p.Data) return Nodes(nodes...) } // _one returns a NodeSet containing the first node. // It's a performance optimization when you only need the first node (stop early). func (p NodeSet) XGo_one() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } n, err := dql.First(p.Data) if err != nil { return NodeSet{Err: err} } return Root(n) } // _single returns a NodeSet containing the single node. // If there are zero or more than one nodes, it returns an error. // ErrNotFound or ErrMultipleResults is returned accordingly. func (p NodeSet) XGo_single() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } n, err := dql.Single(p.Data) if err != nil { return NodeSet{Err: err} } return Root(n) } // ----------------------------------------------------------------------------- // _ok returns true if there is no error in the NodeSet. func (p NodeSet) XGo_ok() bool { return p.Err == nil } // _first returns the first node in the NodeSet. func (p NodeSet) XGo_first() (Node, error) { if p.Err != nil { return Node{}, p.Err } return dql.First(p.Data) } // _name returns the name of the first node in the NodeSet. // empty string is returned if the NodeSet is empty or error occurs. func (p NodeSet) XGo_name__0() string { val, _ := p.XGo_name__1() return val } // _name returns the name of the first node in the NodeSet. // If the NodeSet is empty, it returns ErrNotFound. func (p NodeSet) XGo_name__1() (ret string, err error) { node, err := p.XGo_first() if err == nil { ret = node.Name } return } // _value returns the value of the first node in the NodeSet. // nil is returned if the NodeSet is empty or error occurs. func (p NodeSet) XGo_value__0() any { val, _ := p.XGo_value__1() return val } // _value returns the value of the first node in the NodeSet. // If the NodeSet is empty, it returns ErrNotFound. func (p NodeSet) XGo_value__1() (ret any, err error) { node, err := p.XGo_first() if err == nil { ret = node.Value.Interface() } return } // XGo_class returns the class name of the first node in the NodeSet. func (p NodeSet) XGo_class() (class string) { node, err := p.XGo_first() if err != nil { return } _, v := deref(node.Value) if v.IsValid() { return v.Type().Name() } return "" } // _hasAttr returns true if the first node in the NodeSet has the specified attribute. // It returns false otherwise. func (p NodeSet) XGo_hasAttr(name string) bool { node, err := p.XGo_first() if err == nil { return node.XGo_hasAttr(name) } return false } // XGo_Attr returns the value of the specified attribute from the first node in the // NodeSet. It only retrieves the attribute from the first node. // - $name // - $“attr-name” func (p NodeSet) XGo_Attr__0(name string) any { val, _ := p.XGo_AttrEx(name, allowAnyMethod) return val } // XGo_Attr returns the value of the specified attribute from the first node in the // NodeSet. It only retrieves the attribute from the first node. // - $name // - $“attr-name” func (p NodeSet) XGo_Attr__1(name string) (any, error) { return p.XGo_AttrEx(name, allowAnyMethod) } // XGo_AttrEx returns the value of the specified attribute from the first node in the // NodeSet. It allows you to specify a custom function to determine whether to call a // method. // - $name // - $“attr-name” func (p NodeSet) XGo_AttrEx(name string, allowMthd func(reflect.Value, string) bool) (any, error) { node, err := p.XGo_first() if err == nil { return node.XGo_AttrEx(name, allowMthd) } return nil, err } // ----------------------------------------------------------------------------- ================================================ FILE: dql/xgo/parse.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package xgo import ( "unsafe" "github.com/goplus/xgo/ast" ) // ----------------------------------------------------------------------------- // File represents a XGo file. type File struct { ast.File // File must contain only the embedded ast.File field. } // ParseFile parses XGo source code from the given filename or source, returning a File // object. An optional Config can be provided to customize the parsing behavior. func ParseFile(filename string, src any, conf ...Config) (f *File, err error) { doc, err := parse(filename, src, conf...) if err == nil { f = (*File)(unsafe.Pointer(doc)) } return } // ----------------------------------------------------------------------------- // XGo_Elem returns a NodeSet containing the child nodes with the specified name. // - .name // - .“element-name” func (f *File) XGo_Elem(name string) NodeSet { return New(&f.File).XGo_Elem(name) } // XGo_Child returns a NodeSet containing all child nodes of the node. // - .* func (f *File) XGo_Child() NodeSet { return New(&f.File).XGo_Child() } // XGo_Any returns a NodeSet containing all descendant nodes (including the // node itself) with the specified name. // If name is "", it returns all nodes. // - .**.name // - .**.“element-name” // - .**.* func (f *File) XGo_Any(name string) NodeSet { return New(&f.File).XGo_Any(name) } // ----------------------------------------------------------------------------- ================================================ FILE: dql/xgo/xgo.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package xgo import ( "bytes" "io" "iter" "reflect" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/dql" "github.com/goplus/xgo/dql/reflects" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/token" "github.com/qiniu/x/stream" ) const ( XGoPackage = "github.com/goplus/xgo/dql/reflects" ) // ----------------------------------------------------------------------------- // Node represents a XGo AST node. type Node = reflects.Node // NodeSet represents a set of XGo AST nodes. type NodeSet struct { reflects.NodeSet } // NodeSet(seq) casts a NodeSet from a sequence of nodes. func NodeSet_Cast(seq iter.Seq[Node]) NodeSet { return NodeSet{ NodeSet: reflects.NodeSet{Data: seq}, } } // Root creates a NodeSet containing the provided root node. func Root(doc Node) NodeSet { return NodeSet{ NodeSet: reflects.Root(doc), } } // Nodes creates a NodeSet containing the provided nodes. func Nodes(nodes ...Node) NodeSet { return NodeSet{ NodeSet: reflects.Nodes(nodes...), } } // New creates a NodeSet from the given *ast.File. func New(f *ast.File) NodeSet { return NodeSet{ NodeSet: reflects.New(reflect.ValueOf(f)), } } // Config represents the configuration for parsing XGo source code. type Config struct { Mode parser.Mode Fset *token.FileSet } const ( defaultMode = parser.ParseComments ) // parse parses XGo source code from the given URI or source. func parse(uri string, src any, conf ...Config) (f *ast.File, err error) { in, err := stream.ReadSourceFromURI(uri, src) if err != nil { return } var c Config if len(conf) > 0 { c = conf[0] } else { c.Mode = defaultMode } if c.Fset == nil { c.Fset = token.NewFileSet() } return parser.ParseFile(c.Fset, uri, in, c.Mode) } // From parses XGo source code from the given URI or source, returning a NodeSet. // An optional Config can be provided to customize the parsing behavior. func From(uri string, src any, conf ...Config) NodeSet { f, err := parse(uri, src, conf...) if err != nil { return NodeSet{NodeSet: reflects.NodeSet{Err: err}} } return New(f) } // Source creates a NodeSet from various types of XGo sources. // It supports the following source types: // - string: treats the string as a URI, opens the resource, and reads XGo source code from it. // - []byte: treated as XGo source code. // - *bytes.Buffer: treated as XGo source code. // - io.Reader: treated as XGo source code. // - *ast.File: creates a NodeSet from the provided *ast.File. // - reflect.Value: creates a NodeSet from the provided reflect.Value (expected to be *ast.File). // - Node: creates a NodeSet containing the single provided node. // - iter.Seq[Node]: returns the provided sequence as a NodeSet. // - NodeSet: returns the provided NodeSet as is. // If the source type is unsupported, it panics. func Source(r any, conf ...Config) (ret NodeSet) { switch v := r.(type) { case string: return From(v, nil, conf...) case []byte: return From("", v, conf...) case *bytes.Buffer: return From("", v, conf...) case io.Reader: return From("", v, conf...) case *ast.File: return New(v) case reflect.Value: return NodeSet{NodeSet: reflects.New(v)} case Node: return NodeSet{NodeSet: reflects.Root(v)} case iter.Seq[Node]: return NodeSet{NodeSet: reflects.NodeSet{Data: v}} case NodeSet: return v default: panic("dql/xgo.Source: unsupported source type") } } // ----------------------------------------------------------------------------- // XGo_Enum returns an iterator over the nodes in the NodeSet. func (p NodeSet) XGo_Enum() iter.Seq[NodeSet] { if p.Err != nil { return dql.NopIter[NodeSet] } return func(yield func(NodeSet) bool) { p.Data(func(node Node) bool { return yield(Root(node)) }) } } // XGo_Select returns a NodeSet containing the nodes with the specified name. // - @name // - @"element-name" func (p NodeSet) XGo_Select(name string) NodeSet { return NodeSet{ NodeSet: p.NodeSet.XGo_Select(name), } } // XGo_Elem returns a NodeSet containing the child nodes with the specified name. // - .name // - .“element-name” func (p NodeSet) XGo_Elem(name string) NodeSet { return NodeSet{ NodeSet: p.NodeSet.XGo_Elem(name), } } // XGo_Child returns a NodeSet containing all child nodes of the nodes in the NodeSet. func (p NodeSet) XGo_Child() NodeSet { return NodeSet{ NodeSet: p.NodeSet.XGo_Child(), } } // XGo_Any returns a NodeSet containing all descendant nodes (including the // nodes themselves) with the specified name. // If name is "", it returns all nodes. // - .**.name // - .**.“element-name” // - .**.* func (p NodeSet) XGo_Any(name string) NodeSet { return NodeSet{ NodeSet: p.NodeSet.XGo_Any(name), } } // ----------------------------------------------------------------------------- // All returns a NodeSet containing all nodes. // It's a cache operation for performance optimization when you need to traverse // the nodes multiple times. func (p NodeSet) All() NodeSet { return NodeSet{ NodeSet: p.NodeSet.XGo_all(), } } // One returns a NodeSet containing the first node. // It's a performance optimization when you only need the first node (stop early). func (p NodeSet) One() NodeSet { return NodeSet{ NodeSet: p.NodeSet.XGo_one(), } } // Single returns a NodeSet containing the single node. // If there are zero or more than one nodes, it returns an error. // ErrNotFound or ErrMultipleResults is returned accordingly. func (p NodeSet) Single() NodeSet { return NodeSet{ NodeSet: p.NodeSet.XGo_single(), } } // ----------------------------------------------------------------------------- // Ok returns true if there is no error in the NodeSet. func (p NodeSet) Ok() bool { return p.Err == nil } // XGo_Attr returns the value of the specified attribute from the first node in the // NodeSet. It only retrieves the attribute from the first node. // - $name // - $“attr-name” func (p NodeSet) XGo_Attr__0(name string) any { val, _ := p.XGo_Attr__1(name) return val } // XGo_Attr returns the value of the specified attribute from the first node in the // NodeSet. It only retrieves the attribute from the first node. // - $name // - $“attr-name” func (p NodeSet) XGo_Attr__1(name string) (val any, err error) { val, err = p.NodeSet.XGo_Attr__1(name) if err == nil { switch v := val.(type) { case *ast.Ident: if v != nil { return v.Name, nil } return "", nil case *ast.BasicLit: if v != nil { return v.Value, nil } return "", nil } } return } // Class returns the class name of the first node in the NodeSet. func (p NodeSet) Class() string { return p.XGo_class() } // ----------------------------------------------------------------------------- ================================================ FILE: dql/xml/node.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package xml import ( "encoding/xml" "io" "github.com/goplus/xgo/dql" ) // ----------------------------------------------------------------------------- // A CharData represents XML character data (raw text), // in which XML escape sequences have been replaced by // the characters they represent. type CharData = xml.CharData // A Name represents an XML name (Local) annotated // with a name space identifier (Space). // In tokens returned by [Decoder.Token], the Space identifier // is given as a canonical URL, not the short prefix used // in the document being parsed. type Name = xml.Name // An Attr represents an attribute in an XML element (Name=Value). type Attr = xml.Attr // Node represents a generic XML node with its name, attributes, and children. type Node struct { Name xml.Name Attr []xml.Attr Children []any // can be *Node or xml.CharData } // Parse returns the parse tree for the XML from the given Reader. func Parse(r io.Reader) (doc *Node, err error) { doc = new(Node) err = xml.NewDecoder(r).Decode(doc) return } // UnmarshalXML implements the xml.Unmarshaler interface for the Node struct. func (n *Node) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { n.Name = start.Name // The start.Attr slice is owned by the xml.Decoder and is only valid // until the next call to d.Token(). // It must be copied to be stored in the Node struct, otherwise it can // lead to data corruption. n.Attr = append([]xml.Attr(nil), start.Attr...) for { token, err := d.Token() if err != nil { return err } switch t := token.(type) { case xml.StartElement: child := &Node{} if err := d.DecodeElement(child, &t); err != nil { return err } n.Children = append(n.Children, child) case xml.CharData: // CharData tokens must be copied before storage text := append(xml.CharData(nil), t...) n.Children = append(n.Children, text) case xml.EndElement: return nil } } } // ----------------------------------------------------------------------------- // XGo_Elem returns a NodeSet containing the child nodes with the specified name. // - .name // - .“element-name” func (n *Node) XGo_Elem(name string) NodeSet { return Root(n).XGo_Elem(name) } // XGo_Child returns a NodeSet containing all child nodes of the node. // - .* func (n *Node) XGo_Child() NodeSet { return Root(n).XGo_Child() } // XGo_Any returns a NodeSet containing all descendant nodes (including the // node itself) with the specified name. // If name is "", it returns all nodes. // - .**.name // - .**.“element-name” // - .**.* func (n *Node) XGo_Any(name string) NodeSet { return Root(n).XGo_Any(name) } // _dump prints the node for debugging purposes. func (n *Node) XGo_dump() NodeSet { return Root(n).XGo_dump() } // ----------------------------------------------------------------------------- // _hasAttr returns true if the node has the specified attribute. func (n *Node) XGo_hasAttr(name string) bool { for _, attr := range n.Attr { if attr.Name.Local == name { return true } } return false } // XGo_Attr returns the value of the specified attribute from the node. // If the attribute is not found, it returns an empty string. // - $name // - $“attr-name” func (n *Node) XGo_Attr__0(name string) string { val, _ := n.XGo_Attr__1(name) return val } // XGo_Attr returns the value of the specified attribute from the node. // If the attribute is not found, it returns ErrNotFound. // - $name // - $“attr-name” func (n *Node) XGo_Attr__1(name string) (string, error) { for _, attr := range n.Attr { if attr.Name.Local == name { return attr.Value, nil } } return "", dql.ErrNotFound } // ----------------------------------------------------------------------------- ================================================ FILE: dql/xml/text.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package xml import ( "encoding/xml" "github.com/goplus/xgo/dql" ) // ----------------------------------------------------------------------------- // _text retrieves the text content of the first child xml.CharData. // It only retrieves from the first node in the NodeSet. func (p NodeSet) XGo_text__0() string { val, _ := p.XGo_text__1() return val } // _text retrieves the text content of the first child xml.CharData. // It only retrieves from the first node in the NodeSet. func (p NodeSet) XGo_text__1() (val string, err error) { node, err := p.XGo_first() if err == nil { for _, c := range node.Children { if data, ok := c.(xml.CharData); ok { return string(data), nil } } err = dql.ErrNotFound // text not found on first node } return } // _int retrieves the integer value from the text content of the first child // text node. It only retrieves from the first node in the NodeSet. func (p NodeSet) XGo_int() (int, error) { text, err := p.XGo_text__1() if err != nil { return 0, err } return dql.Int(text) } // ----------------------------------------------------------------------------- ================================================ FILE: dql/xml/xml.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package xml import ( "bytes" "fmt" "io" "iter" "os" "github.com/goplus/xgo/dql" "github.com/qiniu/x/stream" ) const ( XGoPackage = true ) // ----------------------------------------------------------------------------- // NodeSet represents a set of XML nodes. type NodeSet struct { Data iter.Seq[*Node] Err error } // NodeSet(seq) casts a NodeSet from a sequence of nodes. func NodeSet_Cast(seq iter.Seq[*Node]) NodeSet { return NodeSet{Data: seq} } // Root creates a NodeSet containing the provided root node. func Root(doc *Node) NodeSet { return NodeSet{ Data: func(yield func(*Node) bool) { yield(doc) }, } } // Nodes creates a NodeSet containing the provided nodes. func Nodes(nodes ...*Node) NodeSet { return NodeSet{ Data: func(yield func(*Node) bool) { for _, node := range nodes { if !yield(node) { break } } }, } } // New parses the XML document from the provided reader and returns a NodeSet // containing the root node. If there is an error during parsing, the NodeSet's // Err field is set. func New(r io.Reader) NodeSet { doc, err := Parse(r) if err != nil { return NodeSet{Err: err} } return NodeSet{ Data: func(yield func(*Node) bool) { yield(doc) }, } } // Source creates a NodeSet from various types of sources: // - string: treated as an URL to read XML content from. // - []byte: treated as raw XML content. // - io.Reader: reads XML content from the reader. // - *Node: creates a NodeSet containing the single provided node. // - iter.Seq[*Node]: directly uses the provided sequence of nodes. // - NodeSet: returns the provided NodeSet as is. // If the source type is unsupported, it panics. func Source(r any) (ret NodeSet) { switch v := r.(type) { case string: f, err := stream.Open(v) if err != nil { return NodeSet{Err: err} } defer f.Close() return New(f) case []byte: r := bytes.NewReader(v) return New(r) case io.Reader: return New(v) case *Node: return Root(v) case iter.Seq[*Node]: return NodeSet{Data: v} case NodeSet: return v default: panic("dql/xml.Source: unsupported source type") } } // XGo_Enum returns an iterator over the nodes in the NodeSet. func (p NodeSet) XGo_Enum() iter.Seq[NodeSet] { if p.Err != nil { return dql.NopIter[NodeSet] } return func(yield func(NodeSet) bool) { p.Data(func(node *Node) bool { return yield(Root(node)) }) } } // XGo_Select returns a NodeSet containing the nodes with the specified name. // - @name // - @"element-name" func (p NodeSet) XGo_Select(name string) NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { return selectNode(node, name, yield) }) }, } } // selectNode yields the node if it matches the specified name. func selectNode(node *Node, name string, yield func(*Node) bool) bool { if node.Name.Local == name { return yield(node) } return true } // XGo_Elem returns a NodeSet containing the child nodes with the specified name. // - .name // - .“element-name” func (p NodeSet) XGo_Elem(name string) NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { return yieldNode(node, name, yield) }) }, } } // yieldNode yields the child node with the specified name if it exists. func yieldNode(n *Node, name string, yield func(*Node) bool) bool { for _, c := range n.Children { if child, ok := c.(*Node); ok { if child.Name.Local == name { if !yield(child) { return false } } } } return true } // XGo_Child returns a NodeSet containing all child nodes of the nodes in the NodeSet. func (p NodeSet) XGo_Child() NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(*Node) bool) { p.Data(func(n *Node) bool { return yieldChildNodes(n, yield) }) }, } } // yieldChildNodes yields all child nodes of the given node. func yieldChildNodes(n *Node, yield func(*Node) bool) bool { for _, c := range n.Children { if child, ok := c.(*Node); ok { if !yield(child) { return false } } } return true } // XGo_Any returns a NodeSet containing all descendant nodes (including the // nodes themselves) with the specified name. // If name is "", it returns all nodes. // - .**.name // - .**.“element-name” // - .**.* func (p NodeSet) XGo_Any(name string) NodeSet { if p.Err != nil { return p } return NodeSet{ Data: func(yield func(*Node) bool) { p.Data(func(node *Node) bool { return yieldAnyNodes(node, name, yield) }) }, } } // yieldAnyNodes yields all descendant nodes of the given node that match the // specified name. If name is "", it yields all nodes. func yieldAnyNodes(n *Node, name string, yield func(*Node) bool) bool { if name == "" || n.Name.Local == name { if !yield(n) { return false } } for _, c := range n.Children { if child, ok := c.(*Node); ok { if !yieldAnyNodes(child, name, yield) { return false } } } return true } // ----------------------------------------------------------------------------- // _all returns a NodeSet containing all nodes. // It's a cache operation for performance optimization when you need to traverse // the nodes multiple times. func (p NodeSet) XGo_all() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } nodes := dql.Collect(p.Data) return Nodes(nodes...) } // _one returns a NodeSet containing the first node. // It's a performance optimization when you only need the first node (stop early). func (p NodeSet) XGo_one() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } n, err := dql.First(p.Data) if err != nil { return NodeSet{Err: err} } return Root(n) } // _single returns a NodeSet containing the single node. // If there are zero or more than one nodes, it returns an error. // ErrNotFound or ErrMultiEntities is returned accordingly. func (p NodeSet) XGo_single() NodeSet { if p.Err != nil { return NodeSet{Err: p.Err} } n, err := dql.Single(p.Data) if err != nil { return NodeSet{Err: err} } return Root(n) } // ----------------------------------------------------------------------------- // _dump prints the nodes in the NodeSet for debugging purposes. func (p NodeSet) XGo_dump() NodeSet { if p.Err == nil { p.Data(func(node *Node) bool { fmt.Fprintln(os.Stderr, "node:", node.Name.Local, node.Attr) return true }) } return p } // ----------------------------------------------------------------------------- // _ok returns true if there is no error in the NodeSet. func (p NodeSet) XGo_ok() bool { return p.Err == nil } // _first returns the first node in the NodeSet. func (p NodeSet) XGo_first() (*Node, error) { if p.Err != nil { return nil, p.Err } return dql.First(p.Data) } // _hasAttr returns true if the first node in the NodeSet has the specified attribute. // It returns false otherwise. func (p NodeSet) XGo_hasAttr(name string) bool { node, err := p.XGo_first() if err == nil { return node.XGo_hasAttr(name) } return false } // XGo_Attr returns the value of the specified attribute from the first node in the // NodeSet. It only retrieves the attribute from the first node. // - $name // - $“attr-name” func (p NodeSet) XGo_Attr__0(name string) string { val, _ := p.XGo_Attr__1(name) return val } // XGo_Attr returns the value of the specified attribute from the first node in the // NodeSet. It only retrieves the attribute from the first node. // - $name // - $“attr-name” func (p NodeSet) XGo_Attr__1(name string) (val string, err error) { node, err := p.XGo_first() if err == nil { return node.XGo_Attr__1(name) } return } // ----------------------------------------------------------------------------- ================================================ FILE: dql/yaml/yaml.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package yaml import ( "bytes" "io" "iter" "github.com/goccy/go-yaml" "github.com/goplus/xgo/dql/maps" "github.com/qiniu/x/stream" ) const ( XGoPackage = "github.com/goplus/xgo/dql/maps" ) // ----------------------------------------------------------------------------- // Node represents a map[string]any or []any node. type Node = maps.Node // NodeSet represents a set of YAML nodes. type NodeSet = maps.NodeSet // New creates a YAML NodeSet from YAML data read from r. func New(r io.Reader, opts ...yaml.DecodeOption) NodeSet { var data any err := yaml.NewDecoder(r, opts...).Decode(&data) if err != nil { return NodeSet{Err: err} } return maps.New(data) } // Source creates a YAML NodeSet from various source types: // - string: treats the string as a file path, opens the file, and reads YAML data from it. // - []byte: reads YAML data from the byte slice. // - io.Reader: reads YAML data from the provided reader. // - map[string]any: creates a NodeSet from the provided map. // - []any: creates a NodeSet from the provided slice. // - Node: creates a NodeSet containing the single provided node. // - iter.Seq[Node]: directly uses the provided sequence of nodes. // - NodeSet: returns the provided NodeSet as is. // If the source type is unsupported, it panics. func Source(r any, opts ...yaml.DecodeOption) (ret NodeSet) { switch v := r.(type) { case string: f, err := stream.Open(v) if err != nil { return NodeSet{Err: err} } defer f.Close() return New(f, opts...) case []byte: r := bytes.NewReader(v) return New(r, opts...) case io.Reader: return New(v, opts...) case map[string]any, []any: return maps.New(v) case Node: return maps.Root(v) case iter.Seq[Node]: return NodeSet{Data: v} case NodeSet: return v default: panic("dql/yaml.Source: unsupported source type") } } // ----------------------------------------------------------------------------- ================================================ FILE: encoding/csv/csv.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package csv import ( "encoding/csv" "strings" ) // Object is a type alias for a 2D slice of strings, representing the rows and // columns of a CSV file. type Object = [][]string // New creates a new CSV object from a string. func New(text string) (records Object, err error) { return csv.NewReader(strings.NewReader(text)).ReadAll() } ================================================ FILE: encoding/fs/fs.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package fs import ( "github.com/goplus/xgo/dql/fs" ) // New returns a new fs.NodeSet for the specified directory. func New(dir string) fs.NodeSet { return fs.Dir(dir) } ================================================ FILE: encoding/golang/golang.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package golang import ( "go/parser" "strings" "github.com/goplus/xgo/dql/golang" ) const ( XGoPackage = "github.com/goplus/xgo/dql/golang" ) // Object represents a Go File. type Object = *golang.File // New parses Go source code from the given text, returning a Go File object. // An optional parser Mode can be provided to customize the parsing behavior. func New(text string, mode ...parser.Mode) (f Object, err error) { var conf []golang.Config if len(mode) > 0 { conf = []golang.Config{{Mode: mode[0]}} } return golang.ParseFile("", strings.NewReader(text), conf...) } ================================================ FILE: encoding/html/html.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package html import ( "strings" "github.com/goplus/xgo/dql/html" ) const ( XGoPackage = "github.com/goplus/xgo/dql/html" ) // Object represents an HTML object. type Object = *html.Node // New creates a new HTML object from a string. func New(text string) (ret Object, err error) { return html.Parse(strings.NewReader(text)) } ================================================ FILE: encoding/json/json.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package json import ( "encoding/json" "strings" ) // Object is a type alias for any JSON value, which can be a map, slice, string, // number, boolean, or null. type Object = any // New creates a new JSON object from a string. func New(text string) (ret Object, err error) { err = json.NewDecoder(strings.NewReader(text)).Decode(&ret) return } ================================================ FILE: encoding/regexp/regexp.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package regexp import ( "regexp" ) // Object is a type alias for a compiled regular expression. type Object = *regexp.Regexp // New creates a new regexp object from a string. func New(text string) (Object, error) { return regexp.Compile(text) } ================================================ FILE: encoding/regexposix/regexp.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package regexposix import ( "regexp" ) // Object is a type alias for a compiled POSIX regular expression. type Object = *regexp.Regexp // New creates a new POSIX regexp object from a string. func New(text string) (Object, error) { return regexp.CompilePOSIX(text) } ================================================ FILE: encoding/xgo/xgo.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package xgo import ( "strings" "github.com/goplus/xgo/dql/xgo" "github.com/goplus/xgo/parser" ) const ( XGoPackage = "github.com/goplus/xgo/dql/xgo" ) // Object represents a XGo File. type Object = *xgo.File // New parses XGo source code from the given text, returning a XGo File object. // An optional parser Mode can be provided to customize the parsing behavior. func New(text string, mode ...parser.Mode) (f Object, err error) { var conf []xgo.Config if len(mode) > 0 { conf = []xgo.Config{{Mode: mode[0]}} } return xgo.ParseFile("", strings.NewReader(text), conf...) } ================================================ FILE: encoding/xml/xml.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package xml import ( "strings" "github.com/goplus/xgo/dql/xml" ) const ( XGoPackage = "github.com/goplus/xgo/dql/xml" ) // Object represents an XML object. type Object = *xml.Node // New creates a new XML object from a string. func New(text string) (ret Object, err error) { return xml.Parse(strings.NewReader(text)) } ================================================ FILE: encoding/yaml/yaml.go ================================================ /* * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package yaml import ( "strings" "github.com/goccy/go-yaml" ) // Object is a type alias for any YAML value, which can be a map, slice, string, // number, boolean, or null. type Object = any // New creates a new YAML object from a string. func New(text string, opts ...yaml.DecodeOption) (ret Object, err error) { err = yaml.NewDecoder(strings.NewReader(text), opts...).Decode(&ret) return } ================================================ FILE: env/build.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package env // The value of variables come form // `go build -ldflags '-X "buildDate=xxxxx"` var ( buildDate string ) // BuildDate returns build date of the `xgo` command. func BuildDate() string { return buildDate } ================================================ FILE: env/goenv.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package env import ( "os" ) // ----------------------------------------------------------------------------- func HOME() string { return os.Getenv(envHOME) } // ----------------------------------------------------------------------------- ================================================ FILE: env/gop_nonwindows.go ================================================ //go:build !windows // +build !windows /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package env func isXgoCmd(fname string) bool { return fname == "xgo" || fname == "gop" } ================================================ FILE: env/path.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package env import ( "log" "os" "path/filepath" "syscall" ) var ( // This is set by the linker. defaultXGoRoot string ) // XGOROOT returns the root of the XGo tree. It uses the XGOROOT environment variable, // if set at process start, or else the root used during the XGo build. func XGOROOT() string { xgoRoot, err := findXgoRoot() if err != nil { log.Panicln("XGOROOT not found:", err) } return xgoRoot } const ( envXGOROOT = "XGOROOT" ) func findXgoRoot() (string, error) { envXgoRoot := os.Getenv(envXGOROOT) if envXgoRoot != "" { // XGOROOT must valid if isValidXgoRoot(envXgoRoot) { return envXgoRoot, nil } log.Panicf("\n%s (%s) is not valid\n", envXGOROOT, envXgoRoot) } // if parent directory is a valid xgo root, use it exePath, err := executableRealPath() if err == nil { dir := filepath.Dir(exePath) parentDir := filepath.Dir(dir) if parentDir != dir && isValidXgoRoot(parentDir) { return parentDir, nil } } // check defaultXGoRoot, if it is valid, use it if defaultXGoRoot != "" && isValidXgoRoot(defaultXGoRoot) { return defaultXGoRoot, nil } // Compatible with old XGOROOT if home := HOME(); home != "" { xgoRoot := filepath.Join(home, "xgo") if isValidXgoRoot(xgoRoot) { return xgoRoot, nil } } return "", syscall.ENOENT } // Mockable for testing. var executable = func() (string, error) { return os.Executable() } func executableRealPath() (path string, err error) { path, err = executable() if err == nil { path, err = filepath.EvalSymlinks(path) if err == nil { path, err = filepath.Abs(path) } } return } func isFileExists(path string) bool { _, err := os.Stat(path) return err == nil } func isDirExists(path string) bool { st, err := os.Stat(path) return err == nil && st.IsDir() } func isValidXgoRoot(path string) bool { return isDirExists(filepath.Join(path, "cmd/xgo")) && isFileExists(filepath.Join(path, "go.mod")) } ================================================ FILE: env/path_test.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package env import ( "os" "path/filepath" "testing" "github.com/goplus/mod" ) func findGoModFile(dir string) (modfile string, noCacheFile bool, err error) { modfile, err = mod.GOMOD(dir) if err != nil { xgoRoot, err := findXgoRoot() if err == nil { modfile = filepath.Join(xgoRoot, "go.mod") return modfile, true, nil } } return } // Common testing directory structure: // testing_root/ // // src/ // subdir/ // valid_xgoroot/ // go.mod // go.sum // cmd/xgo/ func makeTestDir(t *testing.T) (root string, src string, xgoRoot string) { root, _ = filepath.EvalSymlinks(t.TempDir()) src = filepath.Join(root, "src") xgoRoot = filepath.Join(root, "valid_xgoroot") makeValidXgoRoot(xgoRoot) os.Mkdir(src, 0755) return } func makeValidXgoRoot(root string) { os.Mkdir(root, 0755) os.MkdirAll(filepath.Join(root, "cmd/xgo"), 0755) os.WriteFile(filepath.Join(root, "go.mod"), nil, 0644) os.WriteFile(filepath.Join(root, "go.sum"), nil, 0644) } func writeDummyFile(path string) { os.WriteFile(path, nil, 0644) } func cleanup() { os.Setenv("XGOROOT", "") os.Setenv(envHOME, "") defaultXGoRoot = "" } func TestBasic(t *testing.T) { defaultXGoRoot = ".." if XGOROOT() == "" { t.Fatal("TestBasic failed") } defaultXGoRoot = "" } func TestFindGoModFileInGoModDir(t *testing.T) { cleanup() t.Run("the src/ is a valid mod dir", func(tt *testing.T) { tt.Cleanup(cleanup) _, src, _ := makeTestDir(tt) subdir := filepath.Join(src, "subdir") writeDummyFile(filepath.Join(src, "go.mod")) os.Mkdir(subdir, 0755) { modfile, noCacheFile, err := findGoModFile(src) if err != nil || modfile != filepath.Join(src, "go.mod") || noCacheFile { tt.Fatal("got:", modfile, noCacheFile, err) } } { // Should found go.mod in parent dir modfile, noCacheFile, err := findGoModFile(subdir) if err != nil || modfile != filepath.Join(src, "go.mod") || noCacheFile { tt.Fatal("got:", modfile, noCacheFile, err) } } }) t.Run("the src/ is not a valid mod dir", func(tt *testing.T) { tt.Cleanup(cleanup) _, src, _ := makeTestDir(tt) modfile, noCacheFile, err := findGoModFile(src) if err == nil { tt.Fatal("should not found the mod file, but got:", modfile, noCacheFile) } }) } func TestFindGoModFileInGopRoot(t *testing.T) { originDir, _ := os.Getwd() origiExecutable := executable home := filepath.Join(os.TempDir(), "test_home") os.Mkdir(home, 0755) t.Cleanup(func() { os.Chdir(originDir) os.RemoveAll(home) executable = origiExecutable }) bin := filepath.Join(home, "bin") // Don't find go.mod in gop source dir when testing os.Chdir(home) cleanupAll := func() { cleanup() executable = func() (string, error) { return filepath.Join(bin, "run"), nil } } cleanupAll() t.Run("without xgo root", func(tt *testing.T) { tt.Cleanup(cleanupAll) root, _, _ := makeTestDir(tt) modfile, noCacheFile, err := findGoModFile(root) if err == nil || noCacheFile || modfile != "" { tt.Fatal("should not found go.mod without xgo root, got:", modfile, noCacheFile, err) } }) t.Run("set XGOROOT to a valid xgoroot path", func(tt *testing.T) { tt.Cleanup(cleanupAll) _, src, xgoRoot := makeTestDir(tt) os.Setenv("XGOROOT", xgoRoot) modfile, noCacheFile, err := findGoModFile(src) if err != nil || modfile != filepath.Join(xgoRoot, "go.mod") || !noCacheFile { tt.Fatal("should found mod file in XGOROOT, got:", modfile, noCacheFile, err) } }) t.Run("set XGOROOT to an invalid xgoroot path", func(tt *testing.T) { tt.Cleanup(cleanupAll) root, src, _ := makeTestDir(tt) invalidXgoRoot := filepath.Join(root, "invalid_xgoroot") defer func() { r := recover() if r == nil { tt.Fatal("should panic, but not") } }() os.Setenv("XGOROOT", invalidXgoRoot) findGoModFile(src) }) t.Run("set defaultXGoRoot to a valid xgoroot path", func(tt *testing.T) { tt.Cleanup(cleanupAll) _, src, xgoRoot := makeTestDir(tt) defaultXGoRoot = xgoRoot modfile, noCacheFile, err := findGoModFile(src) if err != nil || modfile != filepath.Join(xgoRoot, "go.mod") || !noCacheFile { tt.Fatal("should found go.mod in the dir of defaultXGoRoot, got:", modfile, noCacheFile, err) } }) t.Run("set defaultXGoRoot to an invalid path", func(tt *testing.T) { tt.Cleanup(cleanupAll) root, src, _ := makeTestDir(tt) invalidXgoRoot := filepath.Join(root, "invalid_xgoroot") defaultXGoRoot = invalidXgoRoot { modfile, noCacheFile, err := findGoModFile(src) if err == nil || noCacheFile || modfile != "" { tt.Fatal("should not found go.mod when defaultXGoRoot isn't exists, got:", modfile, noCacheFile, err) } } { os.Mkdir(invalidXgoRoot, 0755) modfile, noCacheFile, err := findGoModFile(src) if err == nil || noCacheFile || modfile != "" { tt.Fatal("should not found go.mod when defaultXGoRoot isn't an valid gop root dir, got:", modfile, noCacheFile, err) } } }) t.Run("use $HOME/xgo", func(tt *testing.T) { tt.Cleanup(cleanupAll) root, src, _ := makeTestDir(tt) home := filepath.Join(root, "home") os.Mkdir(home, 0755) os.Setenv(envHOME, home) { xgoRoot := filepath.Join(home, "xgo") makeValidXgoRoot(xgoRoot) modfile, noCacheFile, err := findGoModFile(src) if err != nil || !noCacheFile || modfile != filepath.Join(xgoRoot, "go.mod") { tt.Fatal("should found go.mod in $HOME/gop, but got:", modfile, noCacheFile, err) } } }) t.Run("check if parent dir of the executable is valid gop root", func(tt *testing.T) { tt.Cleanup(cleanupAll) _, src, xgoRoot := makeTestDir(tt) bin := filepath.Join(xgoRoot, "bin") exePath := filepath.Join(bin, "run") os.Mkdir(bin, 0755) writeDummyFile(exePath) // Mock executable location executable = func() (string, error) { return exePath, nil } modfile, noCacheFile, err := findGoModFile(src) if err != nil || !noCacheFile || modfile != filepath.Join(xgoRoot, "go.mod") { tt.Fatal("should found go.mod in xgoRoot, but got:", modfile, noCacheFile, err) } }) t.Run("test xgo root priority", func(tt *testing.T) { tt.Cleanup(cleanupAll) root, src, _ := makeTestDir(tt) tt.Run("without xgo root", func(tt *testing.T) { modfile, noCacheFile, err := findGoModFile(src) if err == nil || noCacheFile || modfile != "" { tt.Fatal("should not found go.mod without xgo root, got:", modfile, noCacheFile, err) } }) tt.Run("set defaultXGoRoot to a valid xgo root dir", func(tt *testing.T) { newXgoRoot := filepath.Join(root, "new_xgo_root") makeValidXgoRoot(newXgoRoot) defaultXGoRoot = newXgoRoot modfile, noCacheFile, err := findGoModFile(src) if err != nil || !noCacheFile || modfile != filepath.Join(newXgoRoot, "go.mod") { tt.Fatal("should found go.mod in new_xgo_root/, but got:", modfile, noCacheFile, err) } }) tt.Run("the executable's parent dir is a valid xgo root dir", func(tt *testing.T) { newGopRoot2 := filepath.Join(root, "new_xgo_root2") makeValidXgoRoot(newGopRoot2) bin := filepath.Join(newGopRoot2, "bin") exePath := filepath.Join(bin, "run") os.Mkdir(bin, 0755) writeDummyFile(exePath) // Mock executable location executable = func() (string, error) { return exePath, nil } modfile, noCacheFile, err := findGoModFile(src) if err != nil || !noCacheFile || modfile != filepath.Join(newGopRoot2, "go.mod") { tt.Fatal("should found go.mod in new_xgo_root2/, but got:", modfile, noCacheFile, err) } }) tt.Run("set XGOROOT to an invalid xgo root dir", func(tt *testing.T) { newGopRoot3 := filepath.Join(root, "new_xgo_root3") defer func() { r := recover() if r == nil { tt.Fatal("should panic, but not") } }() os.Setenv("XGOROOT", newGopRoot3) findGoModFile(src) }) tt.Run("set XGOROOT to a valid xgo root dir", func(tt *testing.T) { newGopRoot4 := filepath.Join(root, "new_xgo_root4") makeValidXgoRoot(newGopRoot4) os.Setenv("XGOROOT", newGopRoot4) modfile, noCacheFile, err := findGoModFile(src) if err != nil || !noCacheFile || modfile != filepath.Join(newGopRoot4, "go.mod") { tt.Fatal("should found go.mod in new_xgo_root3/, but got:", modfile, noCacheFile, err) } }) }) } ================================================ FILE: env/sys_others.go ================================================ //go:build aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd js,wasm linux netbsd openbsd solaris /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package env const ( envHOME = "HOME" ) ================================================ FILE: env/sys_plan9.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package env const ( envHOME = "home" ) ================================================ FILE: env/sys_windows.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package env import "strings" const ( envHOME = "USERPROFILE" ) func isXgoCmd(fname string) bool { fname = strings.TrimSuffix(fname, ".exe") return fname == "xgo" || fname == "gop" } ================================================ FILE: env/version.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package env import ( "bytes" "os" "os/exec" "path/filepath" "runtime/debug" "strings" ) // buildVersion is the XGo tree's version string at build time. // This is set by the linker via ldflags for official releases. var ( buildVersion string ) func init() { initEnv() } func initEnv() { if buildVersion == "" { initEnvByXgo() } } func initEnvByXgo() { if fname := filepath.Base(os.Args[0]); !isXgoCmd(fname) { if ret, err := xgoEnv(); err == nil { parts := strings.SplitN(strings.TrimRight(ret, "\n"), "\n", 3) if len(parts) == 3 { buildVersion, buildDate, defaultXGoRoot = parts[0], parts[1], parts[2] } } } } var xgoEnv = func() (string, error) { var b bytes.Buffer cmd := exec.Command("xgo", "env", "XGOVERSION", "BUILDDATE", "XGOROOT") cmd.Stdout = &b err := cmd.Run() return b.String(), err } // Installed checks is `xgo` installed or not. // If returns false, it means `xgo` is not installed or not in PATH. func Installed() bool { return buildVersion != "" } // Version returns the XGo tree's version string. // It is either the commit hash and date at the time of the build or, // when possible, a release tag like "v1.0.0-rc1". // // Version detection priority: // 1. buildVersion from ldflags (for official releases via goreleaser) // 2. debug.ReadBuildInfo() - reads embedded Go module version from VCS // 3. "(devel)" for non-VCS builds func Version() string { // Prefer ldflags-injected version (for official releases) if buildVersion != "" { return buildVersion } // Fallback to debug.ReadBuildInfo (embedded module version from VCS) if bi, ok := debug.ReadBuildInfo(); ok { if bi.Main.Version != "" { return bi.Main.Version } } // Return devel for non-VCS builds return "(devel)" } // MainVersion extracts the major.minor version from the current version. // For example, "v1.5.3" returns "1.5", "v1.5.0-rc1" returns "1.5". // For development versions like "(devel)" or pseudo-versions, returns "0.0". func MainVersion() string { ver := Version() // Handle (devel) and other non-version strings if !strings.HasPrefix(ver, "v") { return "0.0" } // Remove 'v' prefix ver = strings.TrimPrefix(ver, "v") // Handle pseudo-versions (e.g., "v0.0.0-20240101-abcdef") if strings.HasPrefix(ver, "0.0.0-") { return "0.0" } // Extract major.minor from semantic version parts := strings.Split(ver, ".") if len(parts) >= 2 { return parts[0] + "." + parts[1] } return "0.0" } ================================================ FILE: env/version_test.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package env import ( "os" "path/filepath" "strings" "testing" ) func TestPanic(t *testing.T) { t.Run("XGOROOT panic", func(t *testing.T) { defer func() { if e := recover(); e == nil { t.Fatal("XGOROOT: no panic?") } }() defaultXGoRoot = "" os.Setenv(envXGOROOT, "") XGOROOT() }) } func TestEnv(t *testing.T) { xgoEnv = func() (string, error) { wd, _ := os.Getwd() root := filepath.Dir(wd) return "v1.0.0-beta1\n2023-10-18_17-45-50\n" + root + "\n", nil } buildVersion = "" initEnv() if !Installed() { t.Fatal("not Installed") } if Version() != "v1.0.0-beta1" { t.Fatal("TestVersion failed:", Version()) } buildVersion = "" // When no buildVersion is set, Version() should return the module version // from debug.ReadBuildInfo() or "(devel)" ver := Version() if ver != "(devel)" && !strings.HasPrefix(ver, "v") { t.Fatal("TestVersion failed - expected (devel) or version starting with v, got:", ver) } if BuildDate() != "2023-10-18_17-45-50" { t.Fatal("BuildInfo failed:", BuildDate()) } } func TestMainVersion(t *testing.T) { tests := []struct { version string expected string }{ {"v1.5.3", "1.5"}, {"v1.5.0-rc1", "1.5"}, {"v2.0.10", "2.0"}, {"(devel)", "0.0"}, {"v0.0.0-20240101-abcdef", "0.0"}, {"invalid", "0.0"}, } for _, tt := range tests { t.Run(tt.version, func(t *testing.T) { // Temporarily set buildVersion to control Version() output oldBuildVersion := buildVersion buildVersion = tt.version defer func() { buildVersion = oldBuildVersion }() result := MainVersion() if result != tt.expected { t.Fatalf("MainVersion() for %s: expected %s, got %s", tt.version, tt.expected, result) } }) } } ================================================ FILE: format/format.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package format implements standard formatting of XGo source. // // Note that formatting of Go source code changes over time, so tools relying on // consistent formatting should execute a specific version of the gofmt binary // instead of using this package. That way, the formatting will be stable, and // the tools won't need to be recompiled each time gofmt changes. // // For example, pre-submit checks that use this package directly would behave // differently depending on what Go version each developer uses, causing the // check to be inherently fragile. package format import ( "bytes" "io" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/printer" "github.com/goplus/xgo/token" ) var config = printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8} const parserMode = parser.ParseComments // Node formats node in canonical gofmt style and writes the result to dst. // // The node type must be *ast.File, *printer.CommentedNode, []ast.Decl, // []ast.Stmt, or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, // or ast.Stmt. Node does not modify node. Imports are not sorted for // nodes representing partial source files (for instance, if the node is // not an *ast.File or a *printer.CommentedNode not wrapping an *ast.File). // // The function may return early (before the entire result is written) // and return a formatting error, for instance due to an incorrect AST. func Node(dst io.Writer, fset *token.FileSet, node any) error { // Determine if we have a complete source file (file != nil). var file *ast.File var cnode *printer.CommentedNode switch n := node.(type) { case *ast.File: file = n case *printer.CommentedNode: if f, ok := n.Node.(*ast.File); ok { file = f cnode = n } } // Sort imports if necessary. if file != nil && hasUnsortedImports(file) { // Make a copy of the AST because ast.SortImports is destructive. // TODO(gri) Do this more efficiently. var buf bytes.Buffer err := config.Fprint(&buf, fset, file) if err != nil { return err } file, err = parser.ParseFile(fset, "", buf.Bytes(), parserMode) if err != nil { _, err = dst.Write(buf.Bytes()) return err // We should never get here. If we do, provide good diagnostic. // return fmt.Errorf("format.Node internal error (%s)", err) } ast.SortImports(fset, file) // Use new file with sorted imports. node = file if cnode != nil { node = &printer.CommentedNode{Node: file, Comments: cnode.Comments} } } return config.Fprint(dst, fset, node) } // Source formats src in canonical gofmt style and returns the result // or an (I/O or syntax) error. src is expected to be a syntactically // correct Go source file, or a list of Go declarations or statements. // // If src is a partial source file, the leading and trailing space of src // is applied to the result (such that it has the same leading and trailing // space as src), and the result is indented by the same amount as the first // line of src containing code. Imports are not sorted for partial source files. func Source(src []byte, class bool, filename ...string) ([]byte, error) { var fname string if filename != nil { fname = filename[0] } fset := token.NewFileSet() file, sourceAdj, indentAdj, err := parse(fset, fname, src, class, true) if err != nil { return nil, err } if sourceAdj == nil { // Complete source file. // TODO(gri) consider doing this always. ast.SortImports(fset, file) } return format(fset, file, sourceAdj, indentAdj, src, config) } func hasUnsortedImports(file *ast.File) bool { for _, d := range file.Decls { d, ok := d.(*ast.GenDecl) if !ok || d.Tok != token.IMPORT { // Not an import declaration, so we're done. // Imports are always first. return false } if d.Lparen.IsValid() { // For now assume all grouped imports are unsorted. // TODO(gri) Should check if they are sorted already. return true } // Ungrouped imports are sorted by default. } return false } ================================================ FILE: format/formatutil/_testdata/format/basic/in.data ================================================ goto "a" // this is a comment // x comment 1 // x comment 2 // x comment 3 func x() { } // call func(a func()) {}(nil) /* y comment */ func y() { } echo "Hi" ================================================ FILE: format/formatutil/_testdata/format/basic/out.expect ================================================ // this is a comment // x comment 1 // x comment 2 // x comment 3 func x() { } /* y comment */ func y() { } goto "a" // call func(a func()) {}(nil) echo "Hi" ================================================ FILE: format/formatutil/_testdata/format/nondecl/in.data ================================================ const a = 0 ================================================ FILE: format/formatutil/_testdata/format/nondecl/out.expect ================================================ const a = 0 ================================================ FILE: format/formatutil/_testdata/rearrange/noeol/in.data ================================================ var a int ================================================ FILE: format/formatutil/_testdata/rearrange/noeol/out.expect ================================================ var a int ================================================ FILE: format/formatutil/_testdata/rearrange/nondecl/in.data ================================================ // this is a comment // x comment 1 // x comment 2 // x comment 3 func x() { } /* y comment */ var y int ================================================ FILE: format/formatutil/_testdata/rearrange/nondecl/out.expect ================================================ // this is a comment // x comment 1 // x comment 2 // x comment 3 func x() { } /* y comment */ var y int ================================================ FILE: format/formatutil/_testdata/splitstmts/basic/in.data ================================================ goto "a" // this is a comment // x comment 1 // x comment 2 // x comment 3 func x() { } // call func(a func()) {}(nil) /* y comment */ func y() { } echo "Hi" ================================================ FILE: format/formatutil/_testdata/splitstmts/basic/out.expect ================================================ goto FUNC FNCALL FUNC IDENT ================================================ FILE: format/formatutil/format_gop.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package formatutil import ( "github.com/goplus/xgo/format" "github.com/goplus/xgo/scanner" "github.com/goplus/xgo/token" ) // RearrangeFuncs rearranges functions in src. func RearrangeFuncs(src []byte, filename ...string) ([]byte, error) { var fname string if filename != nil { fname = filename[0] } fset := token.NewFileSet() base := fset.Base() f := fset.AddFile(fname, base, len(src)) var s scanner.Scanner s.Init(f, src, nil, scanner.ScanComments) stmts := splitStmts(&s) first := firstNonDecl(stmts) if first < 0 { // no non-decl stmt return src, nil } ret := make([]byte, 0, len(src)) off := int(stmts[first].words[0].pos) - base ret = append(ret, src[:off]...) rest := stmts[first:] for i, s := range rest { if s.isFuncDecl() { ret = append(ret, codeOf(src, base, i, rest)...) } } for i, s := range rest { if !s.isFuncDecl() { ret = append(ret, codeOf(src, base, i, rest)...) } } return ret, nil } func codeOf(src []byte, base, i int, rest []aStmt) []byte { from := int(rest[i].words[0].pos) - base to := 0 if i == len(rest)-1 { to = len(src) } else { to = int(rest[i+1].words[0].pos) - base } return src[from:to] } func firstNonDecl(stmts []aStmt) int { for i, stmt := range stmts { if !stmt.isDecl() { return i } } return -1 } func splitStmts(s *scanner.Scanner) (stmts []aStmt) { var level int var stmt aStmt for { pos, tok, _ := s.Scan() if tok == token.EOF { return } stmt.words = append(stmt.words, aWord{pos, tok}) switch tok { case token.LBRACE: level++ case token.RBRACE: level-- } if tok == token.SEMICOLON && level == 0 { stmt.tok, stmt.at = tokOf(stmt.words) stmts = append(stmts, stmt) stmt = aStmt{} continue } } } type aWord struct { pos token.Pos tok token.Token } type aStmt struct { words []aWord tok token.Token at int } func (s aStmt) isFuncDecl() bool { return s.tok == token.FUNC && isFuncDecl(s.words[s.at+1:]) } func (s aStmt) isDecl() bool { switch s.tok { case token.CONST, token.TYPE, token.VAR: return true case token.FUNC: return isFuncDecl(s.words[s.at+1:]) } return false } func tokOf(words []aWord) (tok token.Token, at int) { for i, w := range words { if w.tok != token.COMMENT { return w.tok, i } } return words[0].tok, 0 } func isFuncDecl(words []aWord) bool { if startWith(words, token.LPAREN) { // func ( words = seekAfter(words[1:], token.RPAREN, token.LPAREN) // func (...) if startWith(words, token.LBRACE) { // func (...) { return false } } return true } func seekAfter(words []aWord, tokR, tokL token.Token) []aWord { level := 0 for i, w := range words { switch w.tok { case tokR: if level == 0 { return words[i+1:] } level-- case tokL: level++ } } return nil } func startWith(words []aWord, tok token.Token) bool { for _, w := range words { switch w.tok { case token.COMMENT: continue case tok: return true } break } return false } // SourceEx formats src in canonical xgofmt style and returns the result // or an (I/O or syntax) error. src is expected to be a syntactically // correct XGo source file, or a list of XGo declarations or statements. // // If src is a partial source file, the leading and trailing space of src // is applied to the result (such that it has the same leading and trailing // space as src), and the result is indented by the same amount as the first // line of src containing code. Imports are not sorted for partial source files. func SourceEx(src []byte, class bool, filename ...string) (formatted []byte, err error) { formatted, err = format.Source(src, class, filename...) if err == nil { return } src, err = RearrangeFuncs(src, filename...) if err == nil { formatted, err = format.Source(src, class, filename...) } return } ================================================ FILE: format/formatutil/format_test.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package formatutil import ( "bytes" "log" "os" "path" "runtime" "strings" "testing" "github.com/goplus/xgo/scanner" "github.com/goplus/xgo/token" ) func TestSeekAfter(t *testing.T) { if seekAfter(nil, 0, 0) != nil { t.Fatal("seekAfter failed") } } func TestTokOf(t *testing.T) { words := []aWord{{tok: token.COMMENT}} if tok, _ := tokOf(words); token.COMMENT != tok { t.Fatal("tokOf failed:", tok) } if startWith(words, token.VAR) { t.Fatal("startWith failed") } } func doSplitStmts(src []byte) (ret []string) { fset := token.NewFileSet() base := fset.Base() f := fset.AddFile("", base, len(src)) var s scanner.Scanner s.Init(f, src, nil, scanner.ScanComments) stmts := splitStmts(&s) ret = make([]string, len(stmts)) for i, stmt := range stmts { ret[i] = stmtKind(stmt) } return } func stmtKind(s aStmt) string { tok := s.tok if tok == token.FUNC { if isFuncDecl(s.words[s.at+1:]) { return "FUNC" } return "FNCALL" } return tok.String() } func testFrom(t *testing.T, pkgDir, sel string, doIt func(in []byte) ([]byte, error)) { if sel != "" && !strings.Contains(pkgDir, sel) { return } t.Helper() log.Println("Parsing", pkgDir) in, err := os.ReadFile(pkgDir + "/in.data") if err != nil { t.Fatal("Parsing", pkgDir, "-", err) } out, err := os.ReadFile(pkgDir + "/out.expect") if err != nil { t.Fatal("Parsing", pkgDir, "-", err) } ret, err := doIt(in) if err != nil { t.Fatal(err) } if !bytes.Equal(ret, out) { t.Fatal("Parsing", pkgDir, "- failed:\n"+string(ret)) } } func testFromDir(t *testing.T, sel, relDir string, doIt func(in []byte) ([]byte, error)) { dir, err := os.Getwd() if err != nil { t.Fatal("Getwd failed:", err) } dir = path.Join(dir, relDir) fis, err := os.ReadDir(dir) if err != nil { t.Fatal("ReadDir failed:", err) } for _, fi := range fis { name := fi.Name() if strings.HasPrefix(name, "_") { continue } t.Run(name, func(t *testing.T) { testFrom(t, dir+"/"+name, sel, doIt) }) } } func TestSplitStmts(t *testing.T) { testFromDir(t, "", "./_testdata/splitstmts", func(in []byte) ([]byte, error) { ret := strings.Join(doSplitStmts(in), "\n") + "\n" return []byte(ret), nil }) } func TestRearrangeFuncs(t *testing.T) { if runtime.GOOS == "windows" { // skip temporarily return } testFromDir(t, "", "./_testdata/rearrange", func(in []byte) ([]byte, error) { return RearrangeFuncs(in) }) } func TestFormat(t *testing.T) { testFromDir(t, "", "./_testdata/format", func(in []byte) ([]byte, error) { return SourceEx(in, false, "") }) } ================================================ FILE: format/internal.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // TODO(gri): This file and the file src/cmd/gofmt/internal.go are // the same (but for this comment and the package name). Do not modify // one without the other. Determine if we can factor out functionality // in a public API. See also #11844 for context. package format import ( "bytes" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/printer" "github.com/goplus/xgo/token" ) // parse parses src, which was read from the named file, // as a Go source file, declaration, or statement list. func parse(fset *token.FileSet, filename string, src []byte, class, _ /* fragmentOk */ bool) ( file *ast.File, sourceAdj func(src []byte, indent int) []byte, indentAdj int, err error, ) { mode := parserMode if class { mode |= parser.ParseXGoClass } file, err = parser.ParseFile(fset, filename, src, mode) return } // format formats the given package file originally obtained from src // and adjusts the result based on the original source via sourceAdj // and indentAdj. func format( fset *token.FileSet, file *ast.File, sourceAdj func(src []byte, indent int) []byte, indentAdj int, src []byte, cfg printer.Config, ) ([]byte, error) { if sourceAdj == nil { // Complete source file. var buf bytes.Buffer err := cfg.Fprint(&buf, fset, file) if err != nil { return nil, err } return buf.Bytes(), nil } // Partial source file. // Determine and prepend leading space. i, j := 0, 0 for j < len(src) && isSpace(src[j]) { if src[j] == '\n' { i = j + 1 // byte offset of last line in leading space } j++ } var res []byte res = append(res, src[:i]...) // Determine and prepend indentation of first code line. // Spaces are ignored unless there are no tabs, // in which case spaces count as one tab. indent := 0 hasSpace := false for _, b := range src[i:j] { switch b { case ' ': hasSpace = true case '\t': indent++ } } if indent == 0 && hasSpace { indent = 1 } for i := 0; i < indent; i++ { res = append(res, '\t') } // Format the source. // Write it without any leading and trailing space. cfg.Indent = indent + indentAdj var buf bytes.Buffer err := cfg.Fprint(&buf, fset, file) if err != nil { return nil, err } out := sourceAdj(buf.Bytes(), cfg.Indent) // If the adjusted output is empty, the source // was empty but (possibly) for white space. // The result is the incoming source. if len(out) == 0 { return src, nil } // Otherwise, append output to leading space. res = append(res, out...) return append(res, '\n'), nil } // isSpace reports whether the byte is a space character. // isSpace defines a space as being among the following bytes: ' ', '\t', '\n' and '\r'. func isSpace(b byte) bool { return b == ' ' || b == '\t' || b == '\n' || b == '\r' } ================================================ FILE: go.mod ================================================ module github.com/goplus/xgo go 1.24.0 toolchain go1.24.2 require ( github.com/fsnotify/fsnotify v1.9.0 github.com/goccy/go-yaml v1.19.2 github.com/goplus/cobra v1.9.13 //xgo:class github.com/goplus/gogen v1.21.2 github.com/goplus/lib v0.3.1 github.com/goplus/mod v0.19.6 github.com/qiniu/x v1.16.5 golang.org/x/net v0.50.0 ) require ( golang.org/x/mod v0.20.0 // indirect golang.org/x/sys v0.41.0 // indirect ) retract v1.1.12 ================================================ FILE: go.sum ================================================ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/goplus/cobra v1.9.13 h1:onu/65rXILVMc7RBvdY1mGdOIZ/F4C/6o7DWgglwFaw= github.com/goplus/cobra v1.9.13/go.mod h1:p4LhfNJDKEpiGjGiNn0crUXL5dUPA5DX2ztYpEJR34E= github.com/goplus/gogen v1.21.2 h1:xbXPgZOZiQx/WBM0nZxVSxFbtdCMZCRB+lguDnh8pfs= github.com/goplus/gogen v1.21.2/go.mod h1:Y7ulYW3wonQ3d9er00b0uGFEV/IUZa6okWJZh892ACQ= github.com/goplus/lib v0.3.1 h1:Xws4DBVvgOMu58awqB972wtvTacDbk3nqcbHjdx9KSg= github.com/goplus/lib v0.3.1/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0= github.com/goplus/mod v0.19.6 h1:o00M6mN/rQ06cGDdH8xSHdL3xiZJXom6AUmwpOTBZ84= github.com/goplus/mod v0.19.6/go.mod h1:UnoI3xX5LbUu5TFwOhVRpwOBHSH//s7yqWNIbXpzpRA= github.com/qiniu/x v1.16.5 h1:+/cSm9m8F8sx6zJ4ylmsuhux8xVpxMHP/pzL8xv1Y9w= github.com/qiniu/x v1.16.5/go.mod h1:AiovSOCaRijaf3fj+0CBOpR1457pn24b0Vdb1JpwhII= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= ================================================ FILE: make.bash ================================================ #! /usr/bin/env bash # # Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # set -ex go run cmd/make.go --install --autoproxy ================================================ FILE: make.bat ================================================ go run cmd/make.go --install --autoproxy ================================================ FILE: parser/_instance/instance1/cmd.xgo ================================================ type T1 P1[int] type T2 P2[int, string] type T3 *P1[P2[int, P1[int]]] type S1 struct { v1 P1[int] v2 P2[int, string] } type S2 struct { P1[int] } type A1 []P1[int] type A2 [2]P2[int, string] type M1 map[P1[int]]P2[int, string] type C1 chan P1[int] ================================================ FILE: parser/_instance/instance1/parser.expect ================================================ package main file cmd.xgo ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: T1 Type: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: T2 Type: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: T3 Type: ast.StarExpr: X: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: S1 Type: ast.StructType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: v1 Type: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: v2 Type: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: S2 Type: ast.StructType: Fields: ast.FieldList: List: ast.Field: Type: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: A1 Type: ast.ArrayType: Elt: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: A2 Type: ast.ArrayType: Len: ast.BasicLit: Kind: INT Value: 2 Elt: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: M1 Type: ast.MapType: Key: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int Value: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: C1 Type: ast.ChanType: Value: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ================================================ FILE: parser/_instance/instance2/cmd.xgo ================================================ func fn1(P1[int], P2[int, string]) func fn2(p1 P1[int], p2 P2[int, string]) func fn3(p1 P1[int], p2 ...P2[int, string]) func fn4(p1 []P1[int], p3 []P2[int, string]) *P1[P2[int, P1[int]]] func fn5(p1 []P1[int], p2 [2]P2[int, string]) func fn6(p1 chan P1[int], p2 map[P1[int]]P2[int, string]) func fn7(p1 struct { v1 P1[int] v2 P2[int, string] }) ================================================ FILE: parser/_instance/instance2/parser.expect ================================================ package main file cmd.xgo ast.FuncDecl: Name: ast.Ident: Name: fn1 Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Type: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ast.Field: Type: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string ast.FuncDecl: Name: ast.Ident: Name: fn2 Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: p1 Type: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: p2 Type: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string ast.FuncDecl: Name: ast.Ident: Name: fn3 Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: p1 Type: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: p2 Type: ast.Ellipsis: Elt: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string ast.FuncDecl: Name: ast.Ident: Name: fn4 Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: p1 Type: ast.ArrayType: Elt: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: p3 Type: ast.ArrayType: Elt: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.StarExpr: X: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ast.FuncDecl: Name: ast.Ident: Name: fn5 Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: p1 Type: ast.ArrayType: Elt: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: p2 Type: ast.ArrayType: Len: ast.BasicLit: Kind: INT Value: 2 Elt: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string ast.FuncDecl: Name: ast.Ident: Name: fn6 Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: p1 Type: ast.ChanType: Value: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: p2 Type: ast.MapType: Key: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int Value: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string ast.FuncDecl: Name: ast.Ident: Name: fn7 Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: p1 Type: ast.StructType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: v1 Type: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: v2 Type: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string ================================================ FILE: parser/_instance/instance3/cmd.xgo ================================================ println(P1[int]{}, P2[int, string]{}) println((*P1[int])(nil), (*P2[int, string])(nil)) println P1[int]{}, P2[int, string]{} println (*P1[int])(nil), (*P2[int, string])(nil) func(x P1[int], y *P2[int, string]) *int { return nil }(P1[int]{1}, &P2[int, string]{}) foo(=> P1[int]{1}) foo(=> { println(&P2[int, string]{}) }) fn1[int]([]P1[int]{P1[int]{0}, P1[int]{1}}, &P2[int, string]{}) fn2[int, string](&P1[int]{}, &P2[int, string]{}) foo1[int](=> P1[int]{1}) foo2[int, string](=> { println(&P2[int, string]{}) }) ================================================ FILE: parser/_instance/instance3/parser.expect ================================================ package main file cmd.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.CompositeLit: Type: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ast.CompositeLit: Type: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.CallExpr: Fun: ast.ParenExpr: X: ast.StarExpr: X: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int Args: ast.Ident: Name: nil ast.CallExpr: Fun: ast.ParenExpr: X: ast.StarExpr: X: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string Args: ast.Ident: Name: nil ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.CompositeLit: Type: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ast.CompositeLit: Type: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.CallExpr: Fun: ast.ParenExpr: X: ast.StarExpr: X: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int Args: ast.Ident: Name: nil ast.CallExpr: Fun: ast.ParenExpr: X: ast.StarExpr: X: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string Args: ast.Ident: Name: nil ast.ExprStmt: X: ast.CallExpr: Fun: ast.FuncLit: Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: x Type: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: y Type: ast.StarExpr: X: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.StarExpr: X: ast.Ident: Name: int Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.Ident: Name: nil Args: ast.CompositeLit: Type: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int Elts: ast.BasicLit: Kind: INT Value: 1 ast.UnaryExpr: Op: & X: ast.CompositeLit: Type: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: foo Args: ast.LambdaExpr: Rhs: ast.CompositeLit: Type: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int Elts: ast.BasicLit: Kind: INT Value: 1 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: foo Args: ast.LambdaExpr2: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.UnaryExpr: Op: & X: ast.CompositeLit: Type: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string ast.ExprStmt: X: ast.CallExpr: Fun: ast.IndexExpr: X: ast.Ident: Name: fn1 Index: ast.Ident: Name: int Args: ast.CompositeLit: Type: ast.ArrayType: Elt: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int Elts: ast.CompositeLit: Type: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int Elts: ast.BasicLit: Kind: INT Value: 0 ast.CompositeLit: Type: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int Elts: ast.BasicLit: Kind: INT Value: 1 ast.UnaryExpr: Op: & X: ast.CompositeLit: Type: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string ast.ExprStmt: X: ast.CallExpr: Fun: ast.IndexListExpr: X: ast.Ident: Name: fn2 Indices: ast.Ident: Name: int ast.Ident: Name: string Args: ast.UnaryExpr: Op: & X: ast.CompositeLit: Type: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int ast.UnaryExpr: Op: & X: ast.CompositeLit: Type: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string ast.ExprStmt: X: ast.CallExpr: Fun: ast.IndexExpr: X: ast.Ident: Name: foo1 Index: ast.Ident: Name: int Args: ast.LambdaExpr: Rhs: ast.CompositeLit: Type: ast.IndexExpr: X: ast.Ident: Name: P1 Index: ast.Ident: Name: int Elts: ast.BasicLit: Kind: INT Value: 1 ast.ExprStmt: X: ast.CallExpr: Fun: ast.IndexListExpr: X: ast.Ident: Name: foo2 Indices: ast.Ident: Name: int ast.Ident: Name: string Args: ast.LambdaExpr2: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.UnaryExpr: Op: & X: ast.CompositeLit: Type: ast.IndexListExpr: X: ast.Ident: Name: P2 Indices: ast.Ident: Name: int ast.Ident: Name: string ================================================ FILE: parser/_instance/instance4/cmd.xgo ================================================ fn[int]() fn[[]int]() fn[struct { X int Y int }]() fn[map[int]string]() fn[chan int]() fn[chan struct{}]() fn[interface{}]() fn[interface{ Method() }]() fn[[]struct { X int Y int }, map[int]string]() ================================================ FILE: parser/_instance/instance4/parser.expect ================================================ package main file cmd.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.IndexExpr: X: ast.Ident: Name: fn Index: ast.Ident: Name: int ast.ExprStmt: X: ast.CallExpr: Fun: ast.IndexExpr: X: ast.Ident: Name: fn Index: ast.ArrayType: Elt: ast.Ident: Name: int ast.ExprStmt: X: ast.CallExpr: Fun: ast.IndexExpr: X: ast.Ident: Name: fn Index: ast.StructType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: X Type: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: Y Type: ast.Ident: Name: int ast.ExprStmt: X: ast.CallExpr: Fun: ast.IndexExpr: X: ast.Ident: Name: fn Index: ast.MapType: Key: ast.Ident: Name: int Value: ast.Ident: Name: string ast.ExprStmt: X: ast.CallExpr: Fun: ast.IndexExpr: X: ast.Ident: Name: fn Index: ast.ChanType: Value: ast.Ident: Name: int ast.ExprStmt: X: ast.CallExpr: Fun: ast.IndexExpr: X: ast.Ident: Name: fn Index: ast.ChanType: Value: ast.StructType: Fields: ast.FieldList: ast.ExprStmt: X: ast.CallExpr: Fun: ast.IndexExpr: X: ast.Ident: Name: fn Index: ast.InterfaceType: Methods: ast.FieldList: ast.ExprStmt: X: ast.CallExpr: Fun: ast.IndexExpr: X: ast.Ident: Name: fn Index: ast.InterfaceType: Methods: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: Method Type: ast.FuncType: Params: ast.FieldList: ast.ExprStmt: X: ast.CallExpr: Fun: ast.IndexListExpr: X: ast.Ident: Name: fn Indices: ast.ArrayType: Elt: ast.StructType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: X Type: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: Y Type: ast.Ident: Name: int ast.MapType: Key: ast.Ident: Name: int Value: ast.Ident: Name: string ================================================ FILE: parser/_instance/instance5/cmd.xgo ================================================ var a [2]int if 0 < a[0] { println a println M[int]{1} println T[int]{a: 1, b: 2} } for _, i := range []int{7, 42} { println i } ================================================ FILE: parser/_instance/instance5/parser.expect ================================================ package main file cmd.xgo noEntrypoint ast.GenDecl: Tok: var Specs: ast.ValueSpec: Names: ast.Ident: Name: a Type: ast.ArrayType: Len: ast.BasicLit: Kind: INT Value: 2 Elt: ast.Ident: Name: int ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.IfStmt: Cond: ast.BinaryExpr: X: ast.BasicLit: Kind: INT Value: 0 Op: < Y: ast.IndexExpr: X: ast.Ident: Name: a Index: ast.BasicLit: Kind: INT Value: 0 Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: a ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.CompositeLit: Type: ast.IndexExpr: X: ast.Ident: Name: M Index: ast.Ident: Name: int Elts: ast.BasicLit: Kind: INT Value: 1 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.CompositeLit: Type: ast.IndexExpr: X: ast.Ident: Name: T Index: ast.Ident: Name: int Elts: ast.KeyValueExpr: Key: ast.Ident: Name: a Value: ast.BasicLit: Kind: INT Value: 1 ast.KeyValueExpr: Key: ast.Ident: Name: b Value: ast.BasicLit: Kind: INT Value: 2 ast.RangeStmt: Key: ast.Ident: Name: _ Value: ast.Ident: Name: i Tok: := X: ast.CompositeLit: Type: ast.ArrayType: Elt: ast.Ident: Name: int Elts: ast.BasicLit: Kind: INT Value: 7 ast.BasicLit: Kind: INT Value: 42 Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: i ================================================ FILE: parser/_nofmt/cmdlinestyle1/cmd.xgo ================================================ package main func main() { changeYpos -0.7, 8 changeYpos -0.7 changeYpos - 0.7 changeYpos-0.7 x[1] x []... } ================================================ FILE: parser/_nofmt/cmdlinestyle1/parser.expect ================================================ package main file cmd.xgo ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: changeYpos Args: ast.UnaryExpr: Op: - X: ast.BasicLit: Kind: FLOAT Value: 0.7 ast.BasicLit: Kind: INT Value: 8 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: changeYpos Args: ast.UnaryExpr: Op: - X: ast.BasicLit: Kind: FLOAT Value: 0.7 ast.ExprStmt: X: ast.BinaryExpr: X: ast.Ident: Name: changeYpos Op: - Y: ast.BasicLit: Kind: FLOAT Value: 0.7 ast.ExprStmt: X: ast.BinaryExpr: X: ast.Ident: Name: changeYpos Op: - Y: ast.BasicLit: Kind: FLOAT Value: 0.7 ast.ExprStmt: X: ast.IndexExpr: X: ast.Ident: Name: x Index: ast.BasicLit: Kind: INT Value: 1 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: x Args: ast.SliceLit: ================================================ FILE: parser/_nofmt/cmdlinestyle2/cmd2.xgo ================================================ add(100, 200) (0).Test() ================================================ FILE: parser/_nofmt/cmdlinestyle2/parser.expect ================================================ package main file cmd2.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.CallExpr: Fun: ast.CallExpr: Fun: ast.Ident: Name: add Args: ast.BasicLit: Kind: INT Value: 100 ast.BasicLit: Kind: INT Value: 200 Args: ast.BasicLit: Kind: INT Value: 0 Sel: ast.Ident: Name: Test ================================================ FILE: parser/_nofmt/cmdlinestyle3/cmd.xgo ================================================ println (1+2i, 2) println (1, a...) println (a...) ================================================ FILE: parser/_nofmt/cmdlinestyle3/parser.expect ================================================ package main file cmd.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.TupleLit: Elts: ast.BinaryExpr: X: ast.BasicLit: Kind: INT Value: 1 Op: + Y: ast.BasicLit: Kind: IMAG Value: 2i ast.BasicLit: Kind: INT Value: 2 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.TupleLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.Ident: Name: a ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.TupleLit: Elts: ast.Ident: Name: a ================================================ FILE: parser/_nofmt/exists/exists.xgo ================================================ a := [1, 3, 5, 7, 8, 19] hasEven := {for x <- a if x%2 == 0} ================================================ FILE: parser/_nofmt/exists/parser.expect ================================================ package main file exists.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: a Tok: := Rhs: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 3 ast.BasicLit: Kind: INT Value: 5 ast.BasicLit: Kind: INT Value: 7 ast.BasicLit: Kind: INT Value: 8 ast.BasicLit: Kind: INT Value: 19 ast.AssignStmt: Lhs: ast.Ident: Name: hasEven Tok: := Rhs: ast.ComprehensionExpr: Tok: { Fors: ast.ForPhrase: Value: ast.Ident: Name: x X: ast.Ident: Name: a Cond: ast.BinaryExpr: X: ast.BinaryExpr: X: ast.Ident: Name: x Op: % Y: ast.BasicLit: Kind: INT Value: 2 Op: == Y: ast.BasicLit: Kind: INT Value: 0 ================================================ FILE: parser/_nofmt/forloop/forloop.xgo ================================================ n := 0 for range [1, 3, 5, 7, 11] { n++ } println("n:", n) for x := range [1] { } sum := 0 for _, x := range [1, 3, 5, 7, 11] { if x > 3 { sum += x } } println("sum(1,3,5,7,11):", sum) sum = 0 for i := 1; i < 100; i++ { sum += i } println("sum(1-100):", sum) for x <- [1] { println(x) } for i, x <- [1] if i%2 == 0 { println(i, x) } ================================================ FILE: parser/_nofmt/forloop/parser.expect ================================================ package main file forloop.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: n Tok: := Rhs: ast.BasicLit: Kind: INT Value: 0 ast.RangeStmt: Tok: ILLEGAL X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 3 ast.BasicLit: Kind: INT Value: 5 ast.BasicLit: Kind: INT Value: 7 ast.BasicLit: Kind: INT Value: 11 Body: ast.BlockStmt: List: ast.IncDecStmt: X: ast.Ident: Name: n Tok: ++ ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "n:" ast.Ident: Name: n ast.RangeStmt: Key: ast.Ident: Name: x Tok: := X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 Body: ast.BlockStmt: ast.AssignStmt: Lhs: ast.Ident: Name: sum Tok: := Rhs: ast.BasicLit: Kind: INT Value: 0 ast.RangeStmt: Key: ast.Ident: Name: _ Value: ast.Ident: Name: x Tok: := X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 3 ast.BasicLit: Kind: INT Value: 5 ast.BasicLit: Kind: INT Value: 7 ast.BasicLit: Kind: INT Value: 11 Body: ast.BlockStmt: List: ast.IfStmt: Cond: ast.BinaryExpr: X: ast.Ident: Name: x Op: > Y: ast.BasicLit: Kind: INT Value: 3 Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: sum Tok: += Rhs: ast.Ident: Name: x ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "sum(1,3,5,7,11):" ast.Ident: Name: sum ast.AssignStmt: Lhs: ast.Ident: Name: sum Tok: = Rhs: ast.BasicLit: Kind: INT Value: 0 ast.ForStmt: Init: ast.AssignStmt: Lhs: ast.Ident: Name: i Tok: := Rhs: ast.BasicLit: Kind: INT Value: 1 Cond: ast.BinaryExpr: X: ast.Ident: Name: i Op: < Y: ast.BasicLit: Kind: INT Value: 100 Post: ast.IncDecStmt: X: ast.Ident: Name: i Tok: ++ Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: sum Tok: += Rhs: ast.Ident: Name: i ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "sum(1-100):" ast.Ident: Name: sum ast.ForPhraseStmt: ForPhrase: ast.ForPhrase: Value: ast.Ident: Name: x X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: x ast.ForPhraseStmt: ForPhrase: ast.ForPhrase: Key: ast.Ident: Name: i Value: ast.Ident: Name: x X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 Cond: ast.BinaryExpr: X: ast.BinaryExpr: X: ast.Ident: Name: i Op: % Y: ast.BasicLit: Kind: INT Value: 2 Op: == Y: ast.BasicLit: Kind: INT Value: 0 Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: i ast.Ident: Name: x ================================================ FILE: parser/_nofmt/listcompr/listcompr.xgo ================================================ y := [x*x for x <- [1]] println(y) y = [x*x for x <- [1, 3, 5, 7, 11] if x > 3] println(y) z := [i+v for i, v <- [1, 3, 5, 7, 11] if t := i % 2; t == 1] println(z) println([k+","+s for k, s <- {"Hello": "xsw", "Hi": "XGo"}]) arr := [1, 2, 3, 4, 5, 6] x := [[a, b] for a <- arr if a < b for b <- arr if b > 2] println("x:", x) ================================================ FILE: parser/_nofmt/listcompr/parser.expect ================================================ package main file listcompr.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: y Tok: := Rhs: ast.ComprehensionExpr: Tok: [ Elt: ast.BinaryExpr: X: ast.Ident: Name: x Op: * Y: ast.Ident: Name: x Fors: ast.ForPhrase: Value: ast.Ident: Name: x X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: y ast.AssignStmt: Lhs: ast.Ident: Name: y Tok: = Rhs: ast.ComprehensionExpr: Tok: [ Elt: ast.BinaryExpr: X: ast.Ident: Name: x Op: * Y: ast.Ident: Name: x Fors: ast.ForPhrase: Value: ast.Ident: Name: x X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 3 ast.BasicLit: Kind: INT Value: 5 ast.BasicLit: Kind: INT Value: 7 ast.BasicLit: Kind: INT Value: 11 Cond: ast.BinaryExpr: X: ast.Ident: Name: x Op: > Y: ast.BasicLit: Kind: INT Value: 3 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: y ast.AssignStmt: Lhs: ast.Ident: Name: z Tok: := Rhs: ast.ComprehensionExpr: Tok: [ Elt: ast.BinaryExpr: X: ast.Ident: Name: i Op: + Y: ast.Ident: Name: v Fors: ast.ForPhrase: Key: ast.Ident: Name: i Value: ast.Ident: Name: v X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 3 ast.BasicLit: Kind: INT Value: 5 ast.BasicLit: Kind: INT Value: 7 ast.BasicLit: Kind: INT Value: 11 Init: ast.AssignStmt: Lhs: ast.Ident: Name: t Tok: := Rhs: ast.BinaryExpr: X: ast.Ident: Name: i Op: % Y: ast.BasicLit: Kind: INT Value: 2 Cond: ast.BinaryExpr: X: ast.Ident: Name: t Op: == Y: ast.BasicLit: Kind: INT Value: 1 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: z ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.ComprehensionExpr: Tok: [ Elt: ast.BinaryExpr: X: ast.BinaryExpr: X: ast.Ident: Name: k Op: + Y: ast.BasicLit: Kind: STRING Value: "," Op: + Y: ast.Ident: Name: s Fors: ast.ForPhrase: Key: ast.Ident: Name: k Value: ast.Ident: Name: s X: ast.CompositeLit: Elts: ast.KeyValueExpr: Key: ast.BasicLit: Kind: STRING Value: "Hello" Value: ast.BasicLit: Kind: STRING Value: "xsw" ast.KeyValueExpr: Key: ast.BasicLit: Kind: STRING Value: "Hi" Value: ast.BasicLit: Kind: STRING Value: "XGo" ast.AssignStmt: Lhs: ast.Ident: Name: arr Tok: := Rhs: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 2 ast.BasicLit: Kind: INT Value: 3 ast.BasicLit: Kind: INT Value: 4 ast.BasicLit: Kind: INT Value: 5 ast.BasicLit: Kind: INT Value: 6 ast.AssignStmt: Lhs: ast.Ident: Name: x Tok: := Rhs: ast.ComprehensionExpr: Tok: [ Elt: ast.SliceLit: Elts: ast.Ident: Name: a ast.Ident: Name: b Fors: ast.ForPhrase: Value: ast.Ident: Name: a X: ast.Ident: Name: arr Cond: ast.BinaryExpr: X: ast.Ident: Name: a Op: < Y: ast.Ident: Name: b ast.ForPhrase: Value: ast.Ident: Name: b X: ast.Ident: Name: arr Cond: ast.BinaryExpr: X: ast.Ident: Name: b Op: > Y: ast.BasicLit: Kind: INT Value: 2 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "x:" ast.Ident: Name: x ================================================ FILE: parser/_nofmt/matrix1/matrix.xgo ================================================ echo [ 1, 2, 3 4, 5, 6 ] ================================================ FILE: parser/_nofmt/matrix1/parser.expect ================================================ package main file matrix.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: echo Args: ast.MatrixLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 2 ast.BasicLit: Kind: INT Value: 3 ast.BasicLit: Kind: INT Value: 4 ast.BasicLit: Kind: INT Value: 5 ast.BasicLit: Kind: INT Value: 6 NElt: 2 ================================================ FILE: parser/_nofmt/printvariadic/parser.expect ================================================ package main file printv.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: x ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: y ================================================ FILE: parser/_nofmt/printvariadic/printv.xgo ================================================ println( x... ) println( y..., ) ================================================ FILE: parser/_nofmt/rangeexpr1/parser.expect ================================================ package main file rangeexpr.xgo ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.RangeStmt: Key: ast.Ident: Name: i Tok: := X: ast.RangeExpr: Last: ast.BasicLit: Kind: INT Value: 10 Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: i ast.ForPhraseStmt: ForPhrase: ast.ForPhrase: Value: ast.Ident: Name: i X: ast.RangeExpr: First: ast.BasicLit: Kind: INT Value: 1 Last: ast.BasicLit: Kind: INT Value: 10 Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: i ast.RangeStmt: Key: ast.Ident: Name: i Tok: := X: ast.RangeExpr: Last: ast.BasicLit: Kind: INT Value: 10 Expr3: ast.BasicLit: Kind: INT Value: 2 Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: i ast.RangeStmt: Tok: ILLEGAL X: ast.RangeExpr: Last: ast.BasicLit: Kind: INT Value: 10 Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "Range expression" ================================================ FILE: parser/_nofmt/rangeexpr1/rangeexpr.xgo ================================================ package main func main() { for i := range :10 { println(i) } for i <- 1:10 { println(i) } for i := range :10:2 { println(i) } for range :10 { println("Range expression") } } ================================================ FILE: parser/_nofmt/selectdata/parser.expect ================================================ package main file select.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: a Tok: := Rhs: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 3 ast.BasicLit: Kind: INT Value: 5 ast.BasicLit: Kind: INT Value: 7 ast.BasicLit: Kind: INT Value: 8 ast.BasicLit: Kind: INT Value: 19 ast.AssignStmt: Lhs: ast.Ident: Name: y Tok: := Rhs: ast.ComprehensionExpr: Tok: { Elt: ast.Ident: Name: x Fors: ast.ForPhrase: Value: ast.Ident: Name: x X: ast.Ident: Name: a Cond: ast.BinaryExpr: X: ast.BinaryExpr: X: ast.Ident: Name: x Op: % Y: ast.BasicLit: Kind: INT Value: 2 Op: == Y: ast.BasicLit: Kind: INT Value: 0 ================================================ FILE: parser/_nofmt/selectdata/select.xgo ================================================ a := [1, 3, 5, 7, 8, 19] y := {x for x <- a if x%2 == 0} ================================================ FILE: parser/_nofmt/structtag/parser.expect ================================================ package main file tag.xgo ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: Start Type: ast.StructType: Fields: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: _ Tag: ast.BasicLit: Kind: STRING Value: "Start program" ================================================ FILE: parser/_nofmt/structtag/tag.xgo ================================================ type Start struct { _ "Start program" } ================================================ FILE: parser/_nofmt/tupletype/parser.expect ================================================ package main file tuple.xgo ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Tuple with parenthesized types (covers token.LPAREN case) Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: WithParen Type: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Type: ast.ParenExpr: X: ast.Ident: Name: int ast.Field: Type: ast.ParenExpr: X: ast.Ident: Name: string ================================================ FILE: parser/_nofmt/tupletype/tuple.xgo ================================================ // Tuple with parenthesized types (covers token.LPAREN case) type WithParen ((int), (string)) ================================================ FILE: parser/_testdata/append1/append.xgo ================================================ a <- 1, 2, 3 ================================================ FILE: parser/_testdata/append1/parser.expect ================================================ package main file append.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.SendStmt: Chan: ast.Ident: Name: a Values: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 2 ast.BasicLit: Kind: INT Value: 3 ================================================ FILE: parser/_testdata/append2/append.xgo ================================================ a <- b... ================================================ FILE: parser/_testdata/append2/parser.expect ================================================ package main file append.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.SendStmt: Chan: ast.Ident: Name: a Values: ast.Ident: Name: b ================================================ FILE: parser/_testdata/arrowop/arrowop.xgo ================================================ echo 1+a -> b echo a <> b+1 echo a -> b echo a <> b, "Hi" ================================================ FILE: parser/_testdata/arrowop/parser.expect ================================================ package main file arrowop.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: echo Args: ast.BinaryExpr: X: ast.BinaryExpr: X: ast.BasicLit: Kind: INT Value: 1 Op: + Y: ast.Ident: Name: a Op: -> Y: ast.Ident: Name: b ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: echo Args: ast.BinaryExpr: X: ast.Ident: Name: a Op: <> Y: ast.BinaryExpr: X: ast.Ident: Name: b Op: + Y: ast.BasicLit: Kind: INT Value: 1 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: echo Args: ast.BinaryExpr: X: ast.Ident: Name: a Op: -> Y: ast.Ident: Name: b ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: echo Args: ast.BinaryExpr: X: ast.Ident: Name: a Op: <> Y: ast.Ident: Name: b ast.BasicLit: Kind: STRING Value: "Hi" ================================================ FILE: parser/_testdata/autoprop/goto.xgo ================================================ L: goto(1, 2) + break(3, 4) + a.goto(6) goto L ================================================ FILE: parser/_testdata/autoprop/parser.expect ================================================ package main file goto.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.LabeledStmt: Label: ast.Ident: Name: L Stmt: ast.ExprStmt: X: ast.BinaryExpr: X: ast.BinaryExpr: X: ast.CallExpr: Fun: ast.Ident: Name: goto Args: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 2 Op: + Y: ast.CallExpr: Fun: ast.Ident: Name: break Args: ast.BasicLit: Kind: INT Value: 3 ast.BasicLit: Kind: INT Value: 4 Op: + Y: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.Ident: Name: a Sel: ast.Ident: Name: goto Args: ast.BasicLit: Kind: INT Value: 6 ast.BranchStmt: Tok: goto Label: ast.Ident: Name: L ================================================ FILE: parser/_testdata/build/build.xgo ================================================ type cstring string title := "Hello,world!2020-05-27" s := (*cstring)(&title) println(title[0 : len(title)-len("2006-01-02")]) ================================================ FILE: parser/_testdata/build/parser.expect ================================================ package main file build.xgo noEntrypoint ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: cstring Type: ast.Ident: Name: string ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: title Tok: := Rhs: ast.BasicLit: Kind: STRING Value: "Hello,world!2020-05-27" ast.AssignStmt: Lhs: ast.Ident: Name: s Tok: := Rhs: ast.CallExpr: Fun: ast.ParenExpr: X: ast.StarExpr: X: ast.Ident: Name: cstring Args: ast.UnaryExpr: Op: & X: ast.Ident: Name: title ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.SliceExpr: X: ast.Ident: Name: title Low: ast.BasicLit: Kind: INT Value: 0 High: ast.BinaryExpr: X: ast.CallExpr: Fun: ast.Ident: Name: len Args: ast.Ident: Name: title Op: - Y: ast.CallExpr: Fun: ast.Ident: Name: len Args: ast.BasicLit: Kind: STRING Value: "2006-01-02" ================================================ FILE: parser/_testdata/c2gohello/hello.xgo ================================================ import "C" C.printf c"Hello, world!\n" ================================================ FILE: parser/_testdata/c2gohello/parser.expect ================================================ package main file hello.xgo noEntrypoint ast.GenDecl: Tok: import Specs: ast.ImportSpec: Path: ast.BasicLit: Kind: STRING Value: "C" ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.Ident: Name: C Sel: ast.Ident: Name: printf Args: ast.BasicLit: Kind: CSTRING Value: "Hello, world!\n" ================================================ FILE: parser/_testdata/classfile_init1/Rect.gox ================================================ var ( Width float64 = 100.0 Height float64 = 200.0 Name string = "Rectangle" Count int = 0 ) func Area() float64 { return Width * Height } ================================================ FILE: parser/_testdata/classfile_init1/parser.expect ================================================ package main file Rect.gox ast.GenDecl: Tok: var Specs: ast.ValueSpec: Names: ast.Ident: Name: Width Type: ast.Ident: Name: float64 Values: ast.BasicLit: Kind: FLOAT Value: 100.0 ast.ValueSpec: Names: ast.Ident: Name: Height Type: ast.Ident: Name: float64 Values: ast.BasicLit: Kind: FLOAT Value: 200.0 ast.ValueSpec: Names: ast.Ident: Name: Name Type: ast.Ident: Name: string Values: ast.BasicLit: Kind: STRING Value: "Rectangle" ast.ValueSpec: Names: ast.Ident: Name: Count Type: ast.Ident: Name: int Values: ast.BasicLit: Kind: INT Value: 0 ast.FuncDecl: Name: ast.Ident: Name: Area Type: ast.FuncType: Params: ast.FieldList: Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: float64 Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.BinaryExpr: X: ast.Ident: Name: Width Op: * Y: ast.Ident: Name: Height ================================================ FILE: parser/_testdata/classfile_init2/Rect.gox ================================================ var ( x = 1 ) ================================================ FILE: parser/_testdata/classfile_init2/parser.expect ================================================ package main file Rect.gox ast.GenDecl: Tok: var Specs: ast.ValueSpec: Names: ast.Ident: Name: x Values: ast.BasicLit: Kind: INT Value: 1 ================================================ FILE: parser/_testdata/cmdlinestyle1/cmd.xgo ================================================ println (1+2i)*2 ================================================ FILE: parser/_testdata/cmdlinestyle1/parser.expect ================================================ package main file cmd.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BinaryExpr: X: ast.ParenExpr: X: ast.BinaryExpr: X: ast.BasicLit: Kind: INT Value: 1 Op: + Y: ast.BasicLit: Kind: IMAG Value: 2i Op: * Y: ast.BasicLit: Kind: INT Value: 2 ================================================ FILE: parser/_testdata/cmdlinestyle2/cmd2.xgo ================================================ x {} x{} ================================================ FILE: parser/_testdata/cmdlinestyle2/parser.expect ================================================ package main file cmd2.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: x Args: ast.CompositeLit: ast.ExprStmt: X: ast.CompositeLit: Type: ast.Ident: Name: x ================================================ FILE: parser/_testdata/cmdlinestyle3/cmd3.xgo ================================================ println &x println !x ================================================ FILE: parser/_testdata/cmdlinestyle3/parser.expect ================================================ package main file cmd3.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.UnaryExpr: Op: & X: ast.Ident: Name: x ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.UnaryExpr: Op: ! X: ast.Ident: Name: x ================================================ FILE: parser/_testdata/cmdlinestyle4/cmd4.xgo ================================================ func call(fn func(x int)) { fn(100) } call(func(x int) { println }) call(func(x int) { println x }) call(func(x int) { println {"x": 100, "y": 200}, x }) ================================================ FILE: parser/_testdata/cmdlinestyle4/parser.expect ================================================ package main file cmd4.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: call Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: fn Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: x Type: ast.Ident: Name: int Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: fn Args: ast.BasicLit: Kind: INT Value: 100 ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: call Args: ast.FuncLit: Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: x Type: ast.Ident: Name: int Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.Ident: Name: println ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: call Args: ast.FuncLit: Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: x Type: ast.Ident: Name: int Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: x ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: call Args: ast.FuncLit: Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: x Type: ast.Ident: Name: int Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.CompositeLit: Elts: ast.KeyValueExpr: Key: ast.BasicLit: Kind: STRING Value: "x" Value: ast.BasicLit: Kind: INT Value: 100 ast.KeyValueExpr: Key: ast.BasicLit: Kind: STRING Value: "y" Value: ast.BasicLit: Kind: INT Value: 200 ast.Ident: Name: x ================================================ FILE: parser/_testdata/collection/collection.xgo ================================================ // We often need our programs to perform operations on // collections of data, like selecting all items that // satisfy a given predicate or mapping all items to a new // collection with a custom function. // In some languages it's idiomatic to use [generic](http://en.wikipedia.org/wiki/Generic_programming) // data structures and algorithms. Go does not support // generics; in Go it's common to provide collection // functions if and when they are specifically needed for // your program and data types. // Here are some example collection functions for slices // of `strings`. You can use these examples to build your // own functions. Note that in some cases it may be // clearest to just inline the collection-manipulating // code directly, instead of creating and calling a // helper function. package main import ( "fmt" "strings" ) // Index returns the first index of the target string `t`, or // -1 if no match is found. func Index(vs []string, t string) int { for i, v := range vs { if v == t { return i } } return -1 } // Include returns `true` if the target string t is in the // slice. func Include(vs []string, t string) bool { return Index(vs, t) >= 0 } // Any returns `true` if one of the strings in the slice // satisfies the predicate `f`. func Any(vs []string, f func(string) bool) bool { for _, v := range vs { if f(v) { return true } } return false } // All returns `true` if all of the strings in the slice // satisfy the predicate `f`. func All(vs []string, f func(string) bool) bool { for _, v := range vs { if !f(v) { return false } } return true } // Filter returns a new slice containing all strings in the // slice that satisfy the predicate `f`. func Filter(vs []string, f func(string) bool) []string { vsf := make([]string, 0) for _, v := range vs { if f(v) { vsf = append(vsf, v) } } return vsf } // Map returns a new slice containing the results of applying // the function `f` to each string in the original slice. func Map(vs []string, f func(string) string) []string { vsm := make([]string, len(vs)) for i, v := range vs { vsm[i] = f(v) } return vsm } func main() { // Here we try out our various collection functions. var strs = []string{"peach", "apple", "pear", "plum"} fmt.Println(Index(strs, "pear")) fmt.Println(Include(strs, "grape")) fmt.Println(Any(strs, func(v string) bool { return strings.HasPrefix(v, "p") })) fmt.Println(All(strs, func(v string) bool { return strings.HasPrefix(v, "p") })) fmt.Println(Filter(strs, func(v string) bool { return strings.Contains(v, "e") })) // The above examples all used anonymous functions, // but you can also use named functions of the correct // type. fmt.Println(Map(strs, strings.ToUpper)) } ================================================ FILE: parser/_testdata/collection/parser.expect ================================================ package main file collection.xgo ast.GenDecl: Tok: import Specs: ast.ImportSpec: Path: ast.BasicLit: Kind: STRING Value: "fmt" ast.ImportSpec: Path: ast.BasicLit: Kind: STRING Value: "strings" ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Index returns the first index of the target string `t`, or ast.Comment: Text: // -1 if no match is found. Name: ast.Ident: Name: Index Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: vs Type: ast.ArrayType: Elt: ast.Ident: Name: string ast.Field: Names: ast.Ident: Name: t Type: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: int Body: ast.BlockStmt: List: ast.RangeStmt: Key: ast.Ident: Name: i Value: ast.Ident: Name: v Tok: := X: ast.Ident: Name: vs Body: ast.BlockStmt: List: ast.IfStmt: Cond: ast.BinaryExpr: X: ast.Ident: Name: v Op: == Y: ast.Ident: Name: t Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.Ident: Name: i ast.ReturnStmt: Results: ast.UnaryExpr: Op: - X: ast.BasicLit: Kind: INT Value: 1 ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Include returns `true` if the target string t is in the ast.Comment: Text: // slice. Name: ast.Ident: Name: Include Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: vs Type: ast.ArrayType: Elt: ast.Ident: Name: string ast.Field: Names: ast.Ident: Name: t Type: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: bool Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.BinaryExpr: X: ast.CallExpr: Fun: ast.Ident: Name: Index Args: ast.Ident: Name: vs ast.Ident: Name: t Op: >= Y: ast.BasicLit: Kind: INT Value: 0 ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Any returns `true` if one of the strings in the slice ast.Comment: Text: // satisfies the predicate `f`. Name: ast.Ident: Name: Any Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: vs Type: ast.ArrayType: Elt: ast.Ident: Name: string ast.Field: Names: ast.Ident: Name: f Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: bool Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: bool Body: ast.BlockStmt: List: ast.RangeStmt: Key: ast.Ident: Name: _ Value: ast.Ident: Name: v Tok: := X: ast.Ident: Name: vs Body: ast.BlockStmt: List: ast.IfStmt: Cond: ast.CallExpr: Fun: ast.Ident: Name: f Args: ast.Ident: Name: v Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.Ident: Name: true ast.ReturnStmt: Results: ast.Ident: Name: false ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // All returns `true` if all of the strings in the slice ast.Comment: Text: // satisfy the predicate `f`. Name: ast.Ident: Name: All Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: vs Type: ast.ArrayType: Elt: ast.Ident: Name: string ast.Field: Names: ast.Ident: Name: f Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: bool Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: bool Body: ast.BlockStmt: List: ast.RangeStmt: Key: ast.Ident: Name: _ Value: ast.Ident: Name: v Tok: := X: ast.Ident: Name: vs Body: ast.BlockStmt: List: ast.IfStmt: Cond: ast.UnaryExpr: Op: ! X: ast.CallExpr: Fun: ast.Ident: Name: f Args: ast.Ident: Name: v Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.Ident: Name: false ast.ReturnStmt: Results: ast.Ident: Name: true ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Filter returns a new slice containing all strings in the ast.Comment: Text: // slice that satisfy the predicate `f`. Name: ast.Ident: Name: Filter Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: vs Type: ast.ArrayType: Elt: ast.Ident: Name: string ast.Field: Names: ast.Ident: Name: f Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: bool Results: ast.FieldList: List: ast.Field: Type: ast.ArrayType: Elt: ast.Ident: Name: string Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: vsf Tok: := Rhs: ast.CallExpr: Fun: ast.Ident: Name: make Args: ast.ArrayType: Elt: ast.Ident: Name: string ast.BasicLit: Kind: INT Value: 0 ast.RangeStmt: Key: ast.Ident: Name: _ Value: ast.Ident: Name: v Tok: := X: ast.Ident: Name: vs Body: ast.BlockStmt: List: ast.IfStmt: Cond: ast.CallExpr: Fun: ast.Ident: Name: f Args: ast.Ident: Name: v Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: vsf Tok: = Rhs: ast.CallExpr: Fun: ast.Ident: Name: append Args: ast.Ident: Name: vsf ast.Ident: Name: v ast.ReturnStmt: Results: ast.Ident: Name: vsf ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Map returns a new slice containing the results of applying ast.Comment: Text: // the function `f` to each string in the original slice. Name: ast.Ident: Name: Map Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: vs Type: ast.ArrayType: Elt: ast.Ident: Name: string ast.Field: Names: ast.Ident: Name: f Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.ArrayType: Elt: ast.Ident: Name: string Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: vsm Tok: := Rhs: ast.CallExpr: Fun: ast.Ident: Name: make Args: ast.ArrayType: Elt: ast.Ident: Name: string ast.CallExpr: Fun: ast.Ident: Name: len Args: ast.Ident: Name: vs ast.RangeStmt: Key: ast.Ident: Name: i Value: ast.Ident: Name: v Tok: := X: ast.Ident: Name: vs Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.IndexExpr: X: ast.Ident: Name: vsm Index: ast.Ident: Name: i Tok: = Rhs: ast.CallExpr: Fun: ast.Ident: Name: f Args: ast.Ident: Name: v ast.ReturnStmt: Results: ast.Ident: Name: vsm ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.DeclStmt: Decl: ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Here we try out our various collection functions. Tok: var Specs: ast.ValueSpec: Names: ast.Ident: Name: strs Values: ast.CompositeLit: Type: ast.ArrayType: Elt: ast.Ident: Name: string Elts: ast.BasicLit: Kind: STRING Value: "peach" ast.BasicLit: Kind: STRING Value: "apple" ast.BasicLit: Kind: STRING Value: "pear" ast.BasicLit: Kind: STRING Value: "plum" ast.ExprStmt: X: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.Ident: Name: fmt Sel: ast.Ident: Name: Println Args: ast.CallExpr: Fun: ast.Ident: Name: Index Args: ast.Ident: Name: strs ast.BasicLit: Kind: STRING Value: "pear" ast.ExprStmt: X: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.Ident: Name: fmt Sel: ast.Ident: Name: Println Args: ast.CallExpr: Fun: ast.Ident: Name: Include Args: ast.Ident: Name: strs ast.BasicLit: Kind: STRING Value: "grape" ast.ExprStmt: X: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.Ident: Name: fmt Sel: ast.Ident: Name: Println Args: ast.CallExpr: Fun: ast.Ident: Name: Any Args: ast.Ident: Name: strs ast.FuncLit: Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: v Type: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: bool Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.Ident: Name: strings Sel: ast.Ident: Name: HasPrefix Args: ast.Ident: Name: v ast.BasicLit: Kind: STRING Value: "p" ast.ExprStmt: X: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.Ident: Name: fmt Sel: ast.Ident: Name: Println Args: ast.CallExpr: Fun: ast.Ident: Name: All Args: ast.Ident: Name: strs ast.FuncLit: Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: v Type: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: bool Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.Ident: Name: strings Sel: ast.Ident: Name: HasPrefix Args: ast.Ident: Name: v ast.BasicLit: Kind: STRING Value: "p" ast.ExprStmt: X: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.Ident: Name: fmt Sel: ast.Ident: Name: Println Args: ast.CallExpr: Fun: ast.Ident: Name: Filter Args: ast.Ident: Name: strs ast.FuncLit: Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: v Type: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: bool Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.Ident: Name: strings Sel: ast.Ident: Name: Contains Args: ast.Ident: Name: v ast.BasicLit: Kind: STRING Value: "e" ast.ExprStmt: X: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.Ident: Name: fmt Sel: ast.Ident: Name: Println Args: ast.CallExpr: Fun: ast.Ident: Name: Map Args: ast.Ident: Name: strs ast.SelectorExpr: X: ast.Ident: Name: strings Sel: ast.Ident: Name: ToUpper ================================================ FILE: parser/_testdata/complit/complit.xgo ================================================ a := [][]int{} println(a) ================================================ FILE: parser/_testdata/complit/parser.expect ================================================ package main file complit.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: a Tok: := Rhs: ast.CompositeLit: Type: ast.ArrayType: Elt: ast.ArrayType: Elt: ast.Ident: Name: int ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: a ================================================ FILE: parser/_testdata/domainhuh/huh.xgo ================================================ form := huh`> &ret, 10
` ================================================ FILE: parser/_testdata/domainhuh/parser.expect ================================================ package main file huh.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: form Tok: := Rhs: ast.DomainTextLit: Domain: ast.Ident: Name: huh Value: `> &ret, 10
` Extra: args=2 ast.UnaryExpr: Op: & X: ast.Ident: Name: ret ast.BasicLit: Kind: INT Value: 10
================================================ FILE: parser/_testdata/domaintext/parser.expect ================================================ package main file tpl.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: cl Tok: := Rhs: ast.ErrWrapExpr: X: ast.DomainTextLit: Domain: ast.Ident: Name: tpl Value: ` expr = termExpr % ("+" | "-") termExpr = unaryExpr % ("*" | "/") unaryExpr = operand | "-" unaryExpr operand = INT | FLOAT | "(" expr ")" ` Extra: ast.File: Decls: ast.Rule: Name: ast.Ident: Name: expr Expr: ast.BinaryExpr: X: ast.Ident: Name: termExpr Op: % Y: ast.Choice: Options: ast.BasicLit: Kind: STRING Value: "+" ast.BasicLit: Kind: STRING Value: "-" ast.Rule: Name: ast.Ident: Name: termExpr Expr: ast.BinaryExpr: X: ast.Ident: Name: unaryExpr Op: % Y: ast.Choice: Options: ast.BasicLit: Kind: STRING Value: "*" ast.BasicLit: Kind: STRING Value: "/" ast.Rule: Name: ast.Ident: Name: unaryExpr Expr: ast.Choice: Options: ast.Ident: Name: operand ast.Sequence: Items: ast.BasicLit: Kind: STRING Value: "-" ast.Ident: Name: unaryExpr ast.Rule: Name: ast.Ident: Name: operand Expr: ast.Choice: Options: ast.Ident: Name: INT ast.Ident: Name: FLOAT ast.Sequence: Items: ast.BasicLit: Kind: STRING Value: "(" ast.Ident: Name: expr ast.BasicLit: Kind: STRING Value: ")" Tok: ! ================================================ FILE: parser/_testdata/domaintext/tpl.xgo ================================================ cl := tpl` expr = termExpr % ("+" | "-") termExpr = unaryExpr % ("*" | "/") unaryExpr = operand | "-" unaryExpr operand = INT | FLOAT | "(" expr ")" `! ================================================ FILE: parser/_testdata/domaintpl/parser.expect ================================================ package main file tpl.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: cl Tok: := Rhs: ast.ErrWrapExpr: X: ast.DomainTextLit: Domain: ast.Ident: Name: tpl Value: ` file = stmts => { return &ast.File{ Stmts: this.([]ast.Stmt), } } stmts = *(stmt ";") => { return [n.([]any)[0].(ast.Stmt) for n in this] } ` Extra: ast.File: Decls: ast.Rule: Name: ast.Ident: Name: file Expr: ast.Ident: Name: stmts RetProc: ast.LambdaExpr2: Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.UnaryExpr: Op: & X: ast.CompositeLit: Type: ast.SelectorExpr: X: ast.Ident: Name: ast Sel: ast.Ident: Name: File Elts: ast.KeyValueExpr: Key: ast.Ident: Name: Stmts Value: ast.TypeAssertExpr: X: ast.Ident: Name: this Type: ast.ArrayType: Elt: ast.SelectorExpr: X: ast.Ident: Name: ast Sel: ast.Ident: Name: Stmt ast.Rule: Name: ast.Ident: Name: stmts Expr: ast.UnaryExpr: Op: * X: ast.Sequence: Items: ast.Ident: Name: stmt ast.BasicLit: Kind: STRING Value: ";" RetProc: ast.LambdaExpr2: Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.ComprehensionExpr: Tok: [ Elt: ast.TypeAssertExpr: X: ast.IndexExpr: X: ast.TypeAssertExpr: X: ast.Ident: Name: n Type: ast.ArrayType: Elt: ast.Ident: Name: any Index: ast.BasicLit: Kind: INT Value: 0 Type: ast.SelectorExpr: X: ast.Ident: Name: ast Sel: ast.Ident: Name: Stmt Fors: ast.ForPhrase: Value: ast.Ident: Name: n X: ast.Ident: Name: this Tok: ! ================================================ FILE: parser/_testdata/domaintpl/tpl.xgo ================================================ cl := tpl` file = stmts => { return &ast.File{ Stmts: this.([]ast.Stmt), } } stmts = *(stmt ";") => { return [n.([]any)[0].(ast.Stmt) for n in this] } `! ================================================ FILE: parser/_testdata/dql1/dql.xgo ================================================ echo doc.**.*@($class == "red") echo doc.**.users@($name == "ken").$age echo doc.*."elem-name"@isTotal(self).**. "a".$"attr-name" ================================================ FILE: parser/_testdata/dql1/parser.expect ================================================ package main file dql.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: echo Args: ast.CondExpr: X: ast.AnySelectorExpr: X: ast.Ident: Name: doc Sel: ast.Ident: Name: * Cond: ast.ParenExpr: X: ast.BinaryExpr: X: ast.EnvExpr: Name: ast.Ident: Name: class Op: == Y: ast.BasicLit: Kind: STRING Value: "red" ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: echo Args: ast.SelectorExpr: X: ast.CondExpr: X: ast.AnySelectorExpr: X: ast.Ident: Name: doc Sel: ast.Ident: Name: users Cond: ast.ParenExpr: X: ast.BinaryExpr: X: ast.EnvExpr: Name: ast.Ident: Name: name Op: == Y: ast.BasicLit: Kind: STRING Value: "ken" Sel: ast.Ident: Name: $age ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: echo Args: ast.SelectorExpr: X: ast.AnySelectorExpr: X: ast.CondExpr: X: ast.SelectorExpr: X: ast.SelectorExpr: X: ast.Ident: Name: doc Sel: ast.Ident: Name: * Sel: ast.Ident: Name: "elem-name" Cond: ast.CallExpr: Fun: ast.Ident: Name: isTotal Args: ast.Ident: Name: self Sel: ast.Ident: Name: "a" Sel: ast.Ident: Name: $"attr-name" ================================================ FILE: parser/_testdata/dql2/dql.xgo ================================================ echo doc.*@users@"users".$name ================================================ FILE: parser/_testdata/dql2/parser.expect ================================================ package main file dql.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: echo Args: ast.SelectorExpr: X: ast.CondExpr: X: ast.CondExpr: X: ast.SelectorExpr: X: ast.Ident: Name: doc Sel: ast.Ident: Name: * Cond: ast.Ident: Name: users Cond: ast.Ident: Name: "users" Sel: ast.Ident: Name: $name ================================================ FILE: parser/_testdata/dql3/dql.xgo ================================================ a := doc.**.a@(($"aria-label"?:"").hasPrefix(importedBy)) ================================================ FILE: parser/_testdata/dql3/parser.expect ================================================ package main file dql.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: a Tok: := Rhs: ast.CondExpr: X: ast.AnySelectorExpr: X: ast.Ident: Name: doc Sel: ast.Ident: Name: a Cond: ast.ParenExpr: X: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.ParenExpr: X: ast.ErrWrapExpr: X: ast.EnvExpr: Name: ast.Ident: Name: "aria-label" Tok: ? Default: ast.BasicLit: Kind: STRING Value: "" Sel: ast.Ident: Name: hasPrefix Args: ast.Ident: Name: importedBy ================================================ FILE: parser/_testdata/embedded1/embtype.xgo ================================================ type T struct { abc.E } ================================================ FILE: parser/_testdata/embedded1/parser.expect ================================================ package main file embtype.xgo ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: T Type: ast.StructType: Fields: ast.FieldList: List: ast.Field: Type: ast.SelectorExpr: X: ast.Ident: Name: abc Sel: ast.Ident: Name: E ================================================ FILE: parser/_testdata/envop1/envop.xgo ================================================ echo ${name}, $id ================================================ FILE: parser/_testdata/envop1/parser.expect ================================================ package main file envop.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: echo Args: ast.EnvExpr: Name: ast.Ident: Name: name ast.EnvExpr: Name: ast.Ident: Name: id ================================================ FILE: parser/_testdata/envop2/envop.xgo ================================================ ${name} echo {"id": $id} ================================================ FILE: parser/_testdata/envop2/parser.expect ================================================ package main file envop.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.EnvExpr: Name: ast.Ident: Name: name ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: echo Args: ast.CompositeLit: Elts: ast.KeyValueExpr: Key: ast.BasicLit: Kind: STRING Value: "id" Value: ast.EnvExpr: Name: ast.Ident: Name: id ================================================ FILE: parser/_testdata/errwrap1/errwrap.xgo ================================================ import ( "strconv" ) func add(x, y string) (int, error) { return strconv.Atoi(x)? + strconv.Atoi(y)?, nil } func addSafe(x, y string) int { return strconv.Atoi(x)?:0 + strconv.Atoi(y)?:0 } println(`add("100", "23"):`, add("100", "23")!) sum, err := add("10", "abc") println(`add("10", "abc"):`, sum, err) println(`addSafe("10", "abc"):`, addSafe("10", "abc")) ================================================ FILE: parser/_testdata/errwrap1/parser.expect ================================================ package main file errwrap.xgo noEntrypoint ast.GenDecl: Tok: import Specs: ast.ImportSpec: Path: ast.BasicLit: Kind: STRING Value: "strconv" ast.FuncDecl: Name: ast.Ident: Name: add Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: x ast.Ident: Name: y Type: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: int ast.Field: Type: ast.Ident: Name: error Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.BinaryExpr: X: ast.ErrWrapExpr: X: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.Ident: Name: strconv Sel: ast.Ident: Name: Atoi Args: ast.Ident: Name: x Tok: ? Op: + Y: ast.ErrWrapExpr: X: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.Ident: Name: strconv Sel: ast.Ident: Name: Atoi Args: ast.Ident: Name: y Tok: ? ast.Ident: Name: nil ast.FuncDecl: Name: ast.Ident: Name: addSafe Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: x ast.Ident: Name: y Type: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: int Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.BinaryExpr: X: ast.ErrWrapExpr: X: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.Ident: Name: strconv Sel: ast.Ident: Name: Atoi Args: ast.Ident: Name: x Tok: ? Default: ast.BasicLit: Kind: INT Value: 0 Op: + Y: ast.ErrWrapExpr: X: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.Ident: Name: strconv Sel: ast.Ident: Name: Atoi Args: ast.Ident: Name: y Tok: ? Default: ast.BasicLit: Kind: INT Value: 0 ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: `add("100", "23"):` ast.ErrWrapExpr: X: ast.CallExpr: Fun: ast.Ident: Name: add Args: ast.BasicLit: Kind: STRING Value: "100" ast.BasicLit: Kind: STRING Value: "23" Tok: ! ast.AssignStmt: Lhs: ast.Ident: Name: sum ast.Ident: Name: err Tok: := Rhs: ast.CallExpr: Fun: ast.Ident: Name: add Args: ast.BasicLit: Kind: STRING Value: "10" ast.BasicLit: Kind: STRING Value: "abc" ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: `add("10", "abc"):` ast.Ident: Name: sum ast.Ident: Name: err ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: `addSafe("10", "abc"):` ast.CallExpr: Fun: ast.Ident: Name: addSafe Args: ast.BasicLit: Kind: STRING Value: "10" ast.BasicLit: Kind: STRING Value: "abc" ================================================ FILE: parser/_testdata/errwrap2/errwrap2.xgo ================================================ func neg(x string) (int, error) { return -atoi(x)?, nil } ================================================ FILE: parser/_testdata/errwrap2/parser.expect ================================================ package main file errwrap2.xgo ast.FuncDecl: Name: ast.Ident: Name: neg Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: x Type: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: int ast.Field: Type: ast.Ident: Name: error Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.UnaryExpr: Op: - X: ast.ErrWrapExpr: X: ast.CallExpr: Fun: ast.Ident: Name: atoi Args: ast.Ident: Name: x Tok: ? ast.Ident: Name: nil ================================================ FILE: parser/_testdata/errwrap3/errwrap3.xgo ================================================ mkdir! "foo" println foo()!.fields ================================================ FILE: parser/_testdata/errwrap3/parser.expect ================================================ package main file errwrap3.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.ErrWrapExpr: X: ast.Ident: Name: mkdir Tok: ! Args: ast.BasicLit: Kind: STRING Value: "foo" ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.SelectorExpr: X: ast.ErrWrapExpr: X: ast.CallExpr: Fun: ast.Ident: Name: foo Tok: ! Sel: ast.Ident: Name: fields ================================================ FILE: parser/_testdata/exists/exists.xgo ================================================ a := [1, 3, 5, 7, 8, 19] hasEven := {for x in a if x%2 == 0} ================================================ FILE: parser/_testdata/exists/parser.expect ================================================ package main file exists.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: a Tok: := Rhs: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 3 ast.BasicLit: Kind: INT Value: 5 ast.BasicLit: Kind: INT Value: 7 ast.BasicLit: Kind: INT Value: 8 ast.BasicLit: Kind: INT Value: 19 ast.AssignStmt: Lhs: ast.Ident: Name: hasEven Tok: := Rhs: ast.ComprehensionExpr: Tok: { Fors: ast.ForPhrase: Value: ast.Ident: Name: x X: ast.Ident: Name: a Cond: ast.BinaryExpr: X: ast.BinaryExpr: X: ast.Ident: Name: x Op: % Y: ast.BasicLit: Kind: INT Value: 2 Op: == Y: ast.BasicLit: Kind: INT Value: 0 ================================================ FILE: parser/_testdata/fnbody/fnbody.xgo ================================================ a := 1 { type T = int var b = 2 var c T = 3 } ================================================ FILE: parser/_testdata/fnbody/parser.expect ================================================ package main file fnbody.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: a Tok: := Rhs: ast.BasicLit: Kind: INT Value: 1 ast.BlockStmt: List: ast.DeclStmt: Decl: ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: T Type: ast.Ident: Name: int ast.DeclStmt: Decl: ast.GenDecl: Tok: var Specs: ast.ValueSpec: Names: ast.Ident: Name: b Values: ast.BasicLit: Kind: INT Value: 2 ast.DeclStmt: Decl: ast.GenDecl: Tok: var Specs: ast.ValueSpec: Names: ast.Ident: Name: c Type: ast.Ident: Name: T Values: ast.BasicLit: Kind: INT Value: 3 ================================================ FILE: parser/_testdata/fncall/fncall.xgo ================================================ fn(1)(x) ================================================ FILE: parser/_testdata/fncall/parser.expect ================================================ package main file fncall.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.CallExpr: Fun: ast.Ident: Name: fn Args: ast.BasicLit: Kind: INT Value: 1 Args: ast.Ident: Name: x ================================================ FILE: parser/_testdata/forloop/forloop.xgo ================================================ n := 0 for range [1, 3, 5, 7, 11] { n++ } println("n:", n) for x := range [1] { } sum := 0 for _, x := range [1, 3, 5, 7, 11] { if x > 3 { sum += x } } println("sum(1,3,5,7,11):", sum) sum = 0 for i := 1; i < 100; i++ { sum += i } println("sum(1-100):", sum) for x in [1] { println(x) } for i, x in [1] if i%2 == 0 { println(i, x) } ================================================ FILE: parser/_testdata/forloop/parser.expect ================================================ package main file forloop.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: n Tok: := Rhs: ast.BasicLit: Kind: INT Value: 0 ast.RangeStmt: Tok: ILLEGAL X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 3 ast.BasicLit: Kind: INT Value: 5 ast.BasicLit: Kind: INT Value: 7 ast.BasicLit: Kind: INT Value: 11 Body: ast.BlockStmt: List: ast.IncDecStmt: X: ast.Ident: Name: n Tok: ++ ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "n:" ast.Ident: Name: n ast.RangeStmt: Key: ast.Ident: Name: x Tok: := X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 Body: ast.BlockStmt: ast.AssignStmt: Lhs: ast.Ident: Name: sum Tok: := Rhs: ast.BasicLit: Kind: INT Value: 0 ast.RangeStmt: Key: ast.Ident: Name: _ Value: ast.Ident: Name: x Tok: := X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 3 ast.BasicLit: Kind: INT Value: 5 ast.BasicLit: Kind: INT Value: 7 ast.BasicLit: Kind: INT Value: 11 Body: ast.BlockStmt: List: ast.IfStmt: Cond: ast.BinaryExpr: X: ast.Ident: Name: x Op: > Y: ast.BasicLit: Kind: INT Value: 3 Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: sum Tok: += Rhs: ast.Ident: Name: x ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "sum(1,3,5,7,11):" ast.Ident: Name: sum ast.AssignStmt: Lhs: ast.Ident: Name: sum Tok: = Rhs: ast.BasicLit: Kind: INT Value: 0 ast.ForStmt: Init: ast.AssignStmt: Lhs: ast.Ident: Name: i Tok: := Rhs: ast.BasicLit: Kind: INT Value: 1 Cond: ast.BinaryExpr: X: ast.Ident: Name: i Op: < Y: ast.BasicLit: Kind: INT Value: 100 Post: ast.IncDecStmt: X: ast.Ident: Name: i Tok: ++ Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: sum Tok: += Rhs: ast.Ident: Name: i ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "sum(1-100):" ast.Ident: Name: sum ast.ForPhraseStmt: ForPhrase: ast.ForPhrase: Value: ast.Ident: Name: x X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: x ast.ForPhraseStmt: ForPhrase: ast.ForPhrase: Key: ast.Ident: Name: i Value: ast.Ident: Name: x X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 Cond: ast.BinaryExpr: X: ast.BinaryExpr: X: ast.Ident: Name: i Op: % Y: ast.BasicLit: Kind: INT Value: 2 Op: == Y: ast.BasicLit: Kind: INT Value: 0 Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: i ast.Ident: Name: x ================================================ FILE: parser/_testdata/funcdecl1/fndecl.xgo ================================================ func() (int, int) { return 1, 1 }() ================================================ FILE: parser/_testdata/funcdecl1/parser.expect ================================================ package main file fndecl.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.FuncLit: Type: ast.FuncType: Params: ast.FieldList: Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: int ast.Field: Type: ast.Ident: Name: int Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 1 ================================================ FILE: parser/_testdata/funcdecl2/fndecl.xgo ================================================ func() { }() ================================================ FILE: parser/_testdata/funcdecl2/parser.expect ================================================ package main file fndecl.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.FuncLit: Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: ================================================ FILE: parser/_testdata/funcdecl3/fndecl.xgo ================================================ func() int { return 1 }() ================================================ FILE: parser/_testdata/funcdecl3/parser.expect ================================================ package main file fndecl.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.FuncLit: Type: ast.FuncType: Params: ast.FieldList: Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: int Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.BasicLit: Kind: INT Value: 1 ================================================ FILE: parser/_testdata/funcdoc/funcdoc.xgo ================================================ package foo //go:noinline //go:uintptrescapes func test(s string, p, q uintptr, rest ...uintptr) int { } ================================================ FILE: parser/_testdata/funcdoc/parser.expect ================================================ package foo file funcdoc.xgo ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: //go:noinline ast.Comment: Text: //go:uintptrescapes Name: ast.Ident: Name: test Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: s Type: ast.Ident: Name: string ast.Field: Names: ast.Ident: Name: p ast.Ident: Name: q Type: ast.Ident: Name: uintptr ast.Field: Names: ast.Ident: Name: rest Type: ast.Ellipsis: Elt: ast.Ident: Name: uintptr Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: int Body: ast.BlockStmt: ================================================ FILE: parser/_testdata/funclit/funclit.xgo ================================================ func(x, y int) *int { return nil }(100, 200) println "hello" ================================================ FILE: parser/_testdata/funclit/parser.expect ================================================ package main file funclit.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.FuncLit: Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: x ast.Ident: Name: y Type: ast.Ident: Name: int Results: ast.FieldList: List: ast.Field: Type: ast.StarExpr: X: ast.Ident: Name: int Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.Ident: Name: nil Args: ast.BasicLit: Kind: INT Value: 100 ast.BasicLit: Kind: INT Value: 200 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "hello" ================================================ FILE: parser/_testdata/functype/dummy/dummy.md ================================================ dummy file ================================================ FILE: parser/_testdata/functype/functype.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ type T struct { *T A int `json:"a"` } func bar(v chan bool) (int, <-chan error) { v <- true <-v return 0, (<-chan error)(nil) } func foo(f func([]byte, *string, ...T) chan<- int) (v int, err error) { return } ================================================ FILE: parser/_testdata/functype/parser.expect ================================================ package main file functype.go ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: T Type: ast.StructType: Fields: ast.FieldList: List: ast.Field: Type: ast.StarExpr: X: ast.Ident: Name: T ast.Field: Names: ast.Ident: Name: A Type: ast.Ident: Name: int Tag: ast.BasicLit: Kind: STRING Value: `json:"a"` ast.FuncDecl: Name: ast.Ident: Name: bar Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: v Type: ast.ChanType: Value: ast.Ident: Name: bool Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: int ast.Field: Type: ast.ChanType: Value: ast.Ident: Name: error Body: ast.BlockStmt: List: ast.SendStmt: Chan: ast.Ident: Name: v Values: ast.Ident: Name: true ast.ExprStmt: X: ast.UnaryExpr: Op: <- X: ast.Ident: Name: v ast.ReturnStmt: Results: ast.BasicLit: Kind: INT Value: 0 ast.CallExpr: Fun: ast.ParenExpr: X: ast.ChanType: Value: ast.Ident: Name: error Args: ast.Ident: Name: nil ast.FuncDecl: Name: ast.Ident: Name: foo Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: f Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Type: ast.ArrayType: Elt: ast.Ident: Name: byte ast.Field: Type: ast.StarExpr: X: ast.Ident: Name: string ast.Field: Type: ast.Ellipsis: Elt: ast.Ident: Name: T Results: ast.FieldList: List: ast.Field: Type: ast.ChanType: Value: ast.Ident: Name: int Results: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: v Type: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: err Type: ast.Ident: Name: error Body: ast.BlockStmt: List: ast.ReturnStmt: ================================================ FILE: parser/_testdata/gmxtest/foo.gmx ================================================ a := 1 ================================================ FILE: parser/_testdata/gmxtest/parser.expect ================================================ package main file foo.gmx noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: a Tok: := Rhs: ast.BasicLit: Kind: INT Value: 1 ================================================ FILE: parser/_testdata/goto1/goto.xgo ================================================ goto "a" goto 1, 2 goto (1+2)*3 ================================================ FILE: parser/_testdata/goto1/parser.expect ================================================ package main file goto.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: goto Args: ast.BasicLit: Kind: STRING Value: "a" ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: goto Args: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 2 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: goto Args: ast.BinaryExpr: X: ast.ParenExpr: X: ast.BinaryExpr: X: ast.BasicLit: Kind: INT Value: 1 Op: + Y: ast.BasicLit: Kind: INT Value: 2 Op: * Y: ast.BasicLit: Kind: INT Value: 3 ================================================ FILE: parser/_testdata/goto2/goto.xgo ================================================ goto x+y goto x, y ================================================ FILE: parser/_testdata/goto2/parser.expect ================================================ package main file goto.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: goto Args: ast.BinaryExpr: X: ast.Ident: Name: x Op: + Y: ast.Ident: Name: y ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: goto Args: ast.Ident: Name: x ast.Ident: Name: y ================================================ FILE: parser/_testdata/goxtest1/bar.gox ================================================ var ( A `json:"a"` *B x, y string C.A `json:"ca"` *C.B `json:"b"` v int `json:"v"` ) type A struct{} type B struct{} ================================================ FILE: parser/_testdata/goxtest1/parser.expect ================================================ package main file bar.gox ast.GenDecl: Tok: var Specs: ast.ValueSpec: Type: ast.Ident: Name: A Tag: ast.BasicLit: Kind: STRING Value: `json:"a"` ast.ValueSpec: Type: ast.StarExpr: X: ast.Ident: Name: B ast.ValueSpec: Names: ast.Ident: Name: x ast.Ident: Name: y Type: ast.Ident: Name: string ast.ValueSpec: Type: ast.SelectorExpr: X: ast.Ident: Name: C Sel: ast.Ident: Name: A Tag: ast.BasicLit: Kind: STRING Value: `json:"ca"` ast.ValueSpec: Type: ast.StarExpr: X: ast.SelectorExpr: X: ast.Ident: Name: C Sel: ast.Ident: Name: B Tag: ast.BasicLit: Kind: STRING Value: `json:"b"` ast.ValueSpec: Names: ast.Ident: Name: v Type: ast.Ident: Name: int Tag: ast.BasicLit: Kind: STRING Value: `json:"v"` ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: A Type: ast.StructType: Fields: ast.FieldList: ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: B Type: ast.StructType: Fields: ast.FieldList: ================================================ FILE: parser/_testdata/goxtest2/bar.gox ================================================ var ( x.App ) ================================================ FILE: parser/_testdata/goxtest2/parser.expect ================================================ package main file bar.gox ast.GenDecl: Tok: var Specs: ast.ValueSpec: Type: ast.SelectorExpr: X: ast.Ident: Name: x Sel: ast.Ident: Name: App ================================================ FILE: parser/_testdata/kwargs1/kwargs.xgo ================================================ playSound getUrl("1.mp3", cache = false), loop = true listDir withHidden = true, recursive = false ================================================ FILE: parser/_testdata/kwargs1/parser.expect ================================================ package main file kwargs.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: playSound Args: ast.CallExpr: Fun: ast.Ident: Name: getUrl Args: ast.BasicLit: Kind: STRING Value: "1.mp3" Kwargs: ast.KwargExpr: Name: ast.Ident: Name: cache Value: ast.Ident: Name: false Kwargs: ast.KwargExpr: Name: ast.Ident: Name: loop Value: ast.Ident: Name: true ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: listDir Kwargs: ast.KwargExpr: Name: ast.Ident: Name: withHidden Value: ast.Ident: Name: true ast.KwargExpr: Name: ast.Ident: Name: recursive Value: ast.Ident: Name: false ================================================ FILE: parser/_testdata/lambda1/lambda.xgo ================================================ package main func main() { foo(=> "Hi") foo(x => x * x) foo((x, y) => x + y) foo((x) => (x, x * 2)) foo(() => "Hi") } ================================================ FILE: parser/_testdata/lambda1/parser.expect ================================================ package main file lambda.xgo ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: foo Args: ast.LambdaExpr: Rhs: ast.BasicLit: Kind: STRING Value: "Hi" ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: foo Args: ast.LambdaExpr: Lhs: ast.Ident: Name: x Rhs: ast.BinaryExpr: X: ast.Ident: Name: x Op: * Y: ast.Ident: Name: x ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: foo Args: ast.LambdaExpr: Lhs: ast.Ident: Name: x ast.Ident: Name: y Rhs: ast.BinaryExpr: X: ast.Ident: Name: x Op: + Y: ast.Ident: Name: y ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: foo Args: ast.LambdaExpr: Lhs: ast.Ident: Name: x Rhs: ast.Ident: Name: x ast.BinaryExpr: X: ast.Ident: Name: x Op: * Y: ast.BasicLit: Kind: INT Value: 2 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: foo Args: ast.LambdaExpr: Rhs: ast.BasicLit: Kind: STRING Value: "Hi" ================================================ FILE: parser/_testdata/lambda2/lambda2.xgo ================================================ package main func main() { foo(=> { println("Hi") }) foo(x => { println(x) }) foo((x, y) => { println(x, y) }) } ================================================ FILE: parser/_testdata/lambda2/parser.expect ================================================ package main file lambda2.xgo ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: foo Args: ast.LambdaExpr2: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "Hi" ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: foo Args: ast.LambdaExpr2: Lhs: ast.Ident: Name: x Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: x ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: foo Args: ast.LambdaExpr2: Lhs: ast.Ident: Name: x ast.Ident: Name: y Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: x ast.Ident: Name: y ================================================ FILE: parser/_testdata/lambda3/lambda3.xgo ================================================ package main func main() { foo => { println "Hi" } foo x => { println x } } ================================================ FILE: parser/_testdata/lambda3/parser.expect ================================================ package main file lambda3.xgo ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: foo Args: ast.LambdaExpr2: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "Hi" ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: foo Args: ast.LambdaExpr2: Lhs: ast.Ident: Name: x Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: x ================================================ FILE: parser/_testdata/lambda4/lambda4.xgo ================================================ type Foo struct { Plot func(x float64) (float64, float64) } &Foo{ Plot: x => (x * 2, x * x), } &Foo{ Plot: x => { return x * 2, x * x }, } ================================================ FILE: parser/_testdata/lambda4/parser.expect ================================================ package main file lambda4.xgo noEntrypoint ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: Foo Type: ast.StructType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: Plot Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: x Type: ast.Ident: Name: float64 Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: float64 ast.Field: Type: ast.Ident: Name: float64 ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.UnaryExpr: Op: & X: ast.CompositeLit: Type: ast.Ident: Name: Foo Elts: ast.KeyValueExpr: Key: ast.Ident: Name: Plot Value: ast.LambdaExpr: Lhs: ast.Ident: Name: x Rhs: ast.BinaryExpr: X: ast.Ident: Name: x Op: * Y: ast.BasicLit: Kind: INT Value: 2 ast.BinaryExpr: X: ast.Ident: Name: x Op: * Y: ast.Ident: Name: x ast.ExprStmt: X: ast.UnaryExpr: Op: & X: ast.CompositeLit: Type: ast.Ident: Name: Foo Elts: ast.KeyValueExpr: Key: ast.Ident: Name: Plot Value: ast.LambdaExpr2: Lhs: ast.Ident: Name: x Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.BinaryExpr: X: ast.Ident: Name: x Op: * Y: ast.BasicLit: Kind: INT Value: 2 ast.BinaryExpr: X: ast.Ident: Name: x Op: * Y: ast.Ident: Name: x ================================================ FILE: parser/_testdata/listcompr/listcompr.xgo ================================================ y := [x*x for x in [1]] println(y) y = [x*x for x in [1, 3, 5, 7, 11] if x > 3] println(y) z := [i+v for i, v in [1, 3, 5, 7, 11] if t := i % 2; t == 1] println(z) println([k+","+s for k, s in {"Hello": "xsw", "Hi": "XGo"}]) arr := [1, 2, 3, 4, 5, 6] x := [[a, b] for a in arr if a < b for b in arr if b > 2] println("x:", x) ================================================ FILE: parser/_testdata/listcompr/parser.expect ================================================ package main file listcompr.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: y Tok: := Rhs: ast.ComprehensionExpr: Tok: [ Elt: ast.BinaryExpr: X: ast.Ident: Name: x Op: * Y: ast.Ident: Name: x Fors: ast.ForPhrase: Value: ast.Ident: Name: x X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: y ast.AssignStmt: Lhs: ast.Ident: Name: y Tok: = Rhs: ast.ComprehensionExpr: Tok: [ Elt: ast.BinaryExpr: X: ast.Ident: Name: x Op: * Y: ast.Ident: Name: x Fors: ast.ForPhrase: Value: ast.Ident: Name: x X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 3 ast.BasicLit: Kind: INT Value: 5 ast.BasicLit: Kind: INT Value: 7 ast.BasicLit: Kind: INT Value: 11 Cond: ast.BinaryExpr: X: ast.Ident: Name: x Op: > Y: ast.BasicLit: Kind: INT Value: 3 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: y ast.AssignStmt: Lhs: ast.Ident: Name: z Tok: := Rhs: ast.ComprehensionExpr: Tok: [ Elt: ast.BinaryExpr: X: ast.Ident: Name: i Op: + Y: ast.Ident: Name: v Fors: ast.ForPhrase: Key: ast.Ident: Name: i Value: ast.Ident: Name: v X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 3 ast.BasicLit: Kind: INT Value: 5 ast.BasicLit: Kind: INT Value: 7 ast.BasicLit: Kind: INT Value: 11 Init: ast.AssignStmt: Lhs: ast.Ident: Name: t Tok: := Rhs: ast.BinaryExpr: X: ast.Ident: Name: i Op: % Y: ast.BasicLit: Kind: INT Value: 2 Cond: ast.BinaryExpr: X: ast.Ident: Name: t Op: == Y: ast.BasicLit: Kind: INT Value: 1 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: z ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.ComprehensionExpr: Tok: [ Elt: ast.BinaryExpr: X: ast.BinaryExpr: X: ast.Ident: Name: k Op: + Y: ast.BasicLit: Kind: STRING Value: "," Op: + Y: ast.Ident: Name: s Fors: ast.ForPhrase: Key: ast.Ident: Name: k Value: ast.Ident: Name: s X: ast.CompositeLit: Elts: ast.KeyValueExpr: Key: ast.BasicLit: Kind: STRING Value: "Hello" Value: ast.BasicLit: Kind: STRING Value: "xsw" ast.KeyValueExpr: Key: ast.BasicLit: Kind: STRING Value: "Hi" Value: ast.BasicLit: Kind: STRING Value: "XGo" ast.AssignStmt: Lhs: ast.Ident: Name: arr Tok: := Rhs: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 2 ast.BasicLit: Kind: INT Value: 3 ast.BasicLit: Kind: INT Value: 4 ast.BasicLit: Kind: INT Value: 5 ast.BasicLit: Kind: INT Value: 6 ast.AssignStmt: Lhs: ast.Ident: Name: x Tok: := Rhs: ast.ComprehensionExpr: Tok: [ Elt: ast.SliceLit: Elts: ast.Ident: Name: a ast.Ident: Name: b Fors: ast.ForPhrase: Value: ast.Ident: Name: a X: ast.Ident: Name: arr Cond: ast.BinaryExpr: X: ast.Ident: Name: a Op: < Y: ast.Ident: Name: b ast.ForPhrase: Value: ast.Ident: Name: b X: ast.Ident: Name: arr Cond: ast.BinaryExpr: X: ast.Ident: Name: b Op: > Y: ast.BasicLit: Kind: INT Value: 2 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "x:" ast.Ident: Name: x ================================================ FILE: parser/_testdata/mapfunc/map.xgo ================================================ map strs, toUpper map ["hello", "world"], toUpper map[string]int{"Hi": 1} println map(strs, strings.ToUpper) ================================================ FILE: parser/_testdata/mapfunc/parser.expect ================================================ package main file map.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: map Args: ast.Ident: Name: strs ast.Ident: Name: toUpper ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: map Args: ast.SliceLit: Elts: ast.BasicLit: Kind: STRING Value: "hello" ast.BasicLit: Kind: STRING Value: "world" ast.Ident: Name: toUpper ast.ExprStmt: X: ast.CompositeLit: Type: ast.MapType: Key: ast.Ident: Name: string Value: ast.Ident: Name: int Elts: ast.KeyValueExpr: Key: ast.BasicLit: Kind: STRING Value: "Hi" Value: ast.BasicLit: Kind: INT Value: 1 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.CallExpr: Fun: ast.Ident: Name: map Args: ast.Ident: Name: strs ast.SelectorExpr: X: ast.Ident: Name: strings Sel: ast.Ident: Name: ToUpper ================================================ FILE: parser/_testdata/matrix1/matrix.xgo ================================================ echo [ 1, 2, 3 4, 5, 6 ] ================================================ FILE: parser/_testdata/matrix1/parser.expect ================================================ package main file matrix.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: echo Args: ast.MatrixLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 2 ast.BasicLit: Kind: INT Value: 3 ast.BasicLit: Kind: INT Value: 4 ast.BasicLit: Kind: INT Value: 5 ast.BasicLit: Kind: INT Value: 6 NElt: 2 ================================================ FILE: parser/_testdata/matrix2/matrix.xgo ================================================ echo [ 1, 2, 3 row... 7, 8, 9 ] ================================================ FILE: parser/_testdata/matrix2/parser.expect ================================================ package main file matrix.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: echo Args: ast.MatrixLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 2 ast.BasicLit: Kind: INT Value: 3 ast.ElemEllipsis: Elt: ast.Ident: Name: row ast.BasicLit: Kind: INT Value: 7 ast.BasicLit: Kind: INT Value: 8 ast.BasicLit: Kind: INT Value: 9 NElt: 3 ================================================ FILE: parser/_testdata/mytest/mytest.xgo ================================================ package demo import ( "go/token" "os" ) var stmtStart = map[token.Token]bool{ token.BREAK: true, token.CONST: true, token.CONTINUE: true, token.DEFER: true, token.FALLTHROUGH: true, token.FOR: true, token.GO: true, token.GOTO: true, token.IF: true, token.RETURN: true, token.SELECT: true, token.SWITCH: true, token.TYPE: true, token.VAR: true, } type Mode uint const ( // PackageClauseOnly - stop parsing after package clause PackageClauseOnly Mode = 1 << iota // ImportsOnly - stop parsing after import declarations ImportsOnly // ParseComments - parse comments and add them to AST ParseComments // Trace - print a trace of parsed productions Trace // DeclarationErrors - report declaration errors DeclarationErrors // AllErrors - report all errors (not just the first 10 on different lines) AllErrors ) // FileSystem represents a file system. type FileSystem interface { ReadDir(dirname string) ([]os. FileInfo, error) ReadFile(filename string) ([]byte, error) Join(elem ...string) string } type IF = FileSystem type Foo struct { a, b map[string]struct{} } func (p *Foo) bar() { } func init() { f, err := os. Open("a") if err != nil { return } defer f.Close() ch := make(chan bool, 100) select { case <-ch: println("1") case ch <- true: println("2") } go func(fs FileSystem) { if foo, ok := fs.(*Foo); ok { println(foo) } }(nil) } ================================================ FILE: parser/_testdata/mytest/parser.expect ================================================ package demo file mytest.xgo ast.GenDecl: Tok: import Specs: ast.ImportSpec: Path: ast.BasicLit: Kind: STRING Value: "go/token" ast.ImportSpec: Path: ast.BasicLit: Kind: STRING Value: "os" ast.GenDecl: Tok: var Specs: ast.ValueSpec: Names: ast.Ident: Name: stmtStart Values: ast.CompositeLit: Type: ast.MapType: Key: ast.SelectorExpr: X: ast.Ident: Name: token Sel: ast.Ident: Name: Token Value: ast.Ident: Name: bool Elts: ast.KeyValueExpr: Key: ast.SelectorExpr: X: ast.Ident: Name: token Sel: ast.Ident: Name: BREAK Value: ast.Ident: Name: true ast.KeyValueExpr: Key: ast.SelectorExpr: X: ast.Ident: Name: token Sel: ast.Ident: Name: CONST Value: ast.Ident: Name: true ast.KeyValueExpr: Key: ast.SelectorExpr: X: ast.Ident: Name: token Sel: ast.Ident: Name: CONTINUE Value: ast.Ident: Name: true ast.KeyValueExpr: Key: ast.SelectorExpr: X: ast.Ident: Name: token Sel: ast.Ident: Name: DEFER Value: ast.Ident: Name: true ast.KeyValueExpr: Key: ast.SelectorExpr: X: ast.Ident: Name: token Sel: ast.Ident: Name: FALLTHROUGH Value: ast.Ident: Name: true ast.KeyValueExpr: Key: ast.SelectorExpr: X: ast.Ident: Name: token Sel: ast.Ident: Name: FOR Value: ast.Ident: Name: true ast.KeyValueExpr: Key: ast.SelectorExpr: X: ast.Ident: Name: token Sel: ast.Ident: Name: GO Value: ast.Ident: Name: true ast.KeyValueExpr: Key: ast.SelectorExpr: X: ast.Ident: Name: token Sel: ast.Ident: Name: GOTO Value: ast.Ident: Name: true ast.KeyValueExpr: Key: ast.SelectorExpr: X: ast.Ident: Name: token Sel: ast.Ident: Name: IF Value: ast.Ident: Name: true ast.KeyValueExpr: Key: ast.SelectorExpr: X: ast.Ident: Name: token Sel: ast.Ident: Name: RETURN Value: ast.Ident: Name: true ast.KeyValueExpr: Key: ast.SelectorExpr: X: ast.Ident: Name: token Sel: ast.Ident: Name: SELECT Value: ast.Ident: Name: true ast.KeyValueExpr: Key: ast.SelectorExpr: X: ast.Ident: Name: token Sel: ast.Ident: Name: SWITCH Value: ast.Ident: Name: true ast.KeyValueExpr: Key: ast.SelectorExpr: X: ast.Ident: Name: token Sel: ast.Ident: Name: TYPE Value: ast.Ident: Name: true ast.KeyValueExpr: Key: ast.SelectorExpr: X: ast.Ident: Name: token Sel: ast.Ident: Name: VAR Value: ast.Ident: Name: true ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: Mode Type: ast.Ident: Name: uint ast.GenDecl: Tok: const Specs: ast.ValueSpec: Doc: ast.CommentGroup: List: ast.Comment: Text: // PackageClauseOnly - stop parsing after package clause Names: ast.Ident: Name: PackageClauseOnly Type: ast.Ident: Name: Mode Values: ast.BinaryExpr: X: ast.BasicLit: Kind: INT Value: 1 Op: << Y: ast.Ident: Name: iota ast.ValueSpec: Doc: ast.CommentGroup: List: ast.Comment: Text: // ImportsOnly - stop parsing after import declarations Names: ast.Ident: Name: ImportsOnly ast.ValueSpec: Doc: ast.CommentGroup: List: ast.Comment: Text: // ParseComments - parse comments and add them to AST Names: ast.Ident: Name: ParseComments ast.ValueSpec: Doc: ast.CommentGroup: List: ast.Comment: Text: // Trace - print a trace of parsed productions Names: ast.Ident: Name: Trace ast.ValueSpec: Doc: ast.CommentGroup: List: ast.Comment: Text: // DeclarationErrors - report declaration errors Names: ast.Ident: Name: DeclarationErrors ast.ValueSpec: Doc: ast.CommentGroup: List: ast.Comment: Text: // AllErrors - report all errors (not just the first 10 on different lines) Names: ast.Ident: Name: AllErrors ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // FileSystem represents a file system. Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: FileSystem Type: ast.InterfaceType: Methods: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: ReadDir Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: dirname Type: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.ArrayType: Elt: ast.SelectorExpr: X: ast.Ident: Name: os Sel: ast.Ident: Name: FileInfo ast.Field: Type: ast.Ident: Name: error ast.Field: Names: ast.Ident: Name: ReadFile Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: filename Type: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.ArrayType: Elt: ast.Ident: Name: byte ast.Field: Type: ast.Ident: Name: error ast.Field: Names: ast.Ident: Name: Join Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: elem Type: ast.Ellipsis: Elt: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: string ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: IF Type: ast.Ident: Name: FileSystem ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: Foo Type: ast.StructType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: a ast.Ident: Name: b Type: ast.MapType: Key: ast.Ident: Name: string Value: ast.StructType: Fields: ast.FieldList: ast.FuncDecl: Recv: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: p Type: ast.StarExpr: X: ast.Ident: Name: Foo Name: ast.Ident: Name: bar Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: ast.FuncDecl: Name: ast.Ident: Name: init Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: f ast.Ident: Name: err Tok: := Rhs: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.Ident: Name: os Sel: ast.Ident: Name: Open Args: ast.BasicLit: Kind: STRING Value: "a" ast.IfStmt: Cond: ast.BinaryExpr: X: ast.Ident: Name: err Op: != Y: ast.Ident: Name: nil Body: ast.BlockStmt: List: ast.ReturnStmt: ast.DeferStmt: Call: ast.CallExpr: Fun: ast.SelectorExpr: X: ast.Ident: Name: f Sel: ast.Ident: Name: Close ast.AssignStmt: Lhs: ast.Ident: Name: ch Tok: := Rhs: ast.CallExpr: Fun: ast.Ident: Name: make Args: ast.ChanType: Value: ast.Ident: Name: bool ast.BasicLit: Kind: INT Value: 100 ast.SelectStmt: Body: ast.BlockStmt: List: ast.CommClause: Comm: ast.ExprStmt: X: ast.UnaryExpr: Op: <- X: ast.Ident: Name: ch Body: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "1" ast.CommClause: Comm: ast.SendStmt: Chan: ast.Ident: Name: ch Values: ast.Ident: Name: true Body: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "2" ast.GoStmt: Call: ast.CallExpr: Fun: ast.FuncLit: Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: fs Type: ast.Ident: Name: FileSystem Body: ast.BlockStmt: List: ast.IfStmt: Init: ast.AssignStmt: Lhs: ast.Ident: Name: foo ast.Ident: Name: ok Tok: := Rhs: ast.TypeAssertExpr: X: ast.Ident: Name: fs Type: ast.StarExpr: X: ast.Ident: Name: Foo Cond: ast.Ident: Name: ok Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: foo Args: ast.Ident: Name: nil ================================================ FILE: parser/_testdata/optparam/optparam.xgo ================================================ // Basic optional parameters func single(a int?) func multiple(a int, b string?, c bool?) func mixed(name string, age int?, active bool?, data []byte) // Pointer types with optional func pointer(a *int?, b **string?) // Complex types with optional func complex(m map[string]int?, s []int?, ch chan int?) // Array types with optional func arrays(a [10]int?, b [5]string?) // Interface types with optional func interfaces(io.Reader?, io.Writer?) // Struct types with optional func structs(p struct{ X int }?, q struct{ Y string }?) // Function types with optional func funcs(f func(int) string?, g func(string) error?) // Qualified identifiers with optional func qualified(t time.Time?, d time.Duration?) // Unnamed (anonymous) parameters with optional func unnamed(int?, string?, bool) // All optional parameters func allOptional(a int?, b string?, c bool?) // Optional with variadic (variadic cannot be optional) func withVariadic(a int?, b ...string) // Type declarations with optional type Handler func(req *Request?, resp *Response?) error type Callback func(int?, string?) bool // Method with optional parameters type Server struct{} func (s *Server) Handle(req *Request?, opts *Options?) error { return nil } ================================================ FILE: parser/_testdata/optparam/parser.expect ================================================ package main file optparam.xgo ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Basic optional parameters Name: ast.Ident: Name: single Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: a Type: ast.Ident: Name: int Optional: true ast.FuncDecl: Name: ast.Ident: Name: multiple Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: a Type: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: b Type: ast.Ident: Name: string Optional: true ast.Field: Names: ast.Ident: Name: c Type: ast.Ident: Name: bool Optional: true ast.FuncDecl: Name: ast.Ident: Name: mixed Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: name Type: ast.Ident: Name: string ast.Field: Names: ast.Ident: Name: age Type: ast.Ident: Name: int Optional: true ast.Field: Names: ast.Ident: Name: active Type: ast.Ident: Name: bool Optional: true ast.Field: Names: ast.Ident: Name: data Type: ast.ArrayType: Elt: ast.Ident: Name: byte ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Pointer types with optional Name: ast.Ident: Name: pointer Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: a Type: ast.StarExpr: X: ast.Ident: Name: int Optional: true ast.Field: Names: ast.Ident: Name: b Type: ast.StarExpr: X: ast.StarExpr: X: ast.Ident: Name: string Optional: true ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Complex types with optional Name: ast.Ident: Name: complex Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: m Type: ast.MapType: Key: ast.Ident: Name: string Value: ast.Ident: Name: int Optional: true ast.Field: Names: ast.Ident: Name: s Type: ast.ArrayType: Elt: ast.Ident: Name: int Optional: true ast.Field: Names: ast.Ident: Name: ch Type: ast.ChanType: Value: ast.Ident: Name: int Optional: true ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Array types with optional Name: ast.Ident: Name: arrays Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: a Type: ast.ArrayType: Len: ast.BasicLit: Kind: INT Value: 10 Elt: ast.Ident: Name: int Optional: true ast.Field: Names: ast.Ident: Name: b Type: ast.ArrayType: Len: ast.BasicLit: Kind: INT Value: 5 Elt: ast.Ident: Name: string Optional: true ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Interface types with optional Name: ast.Ident: Name: interfaces Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Type: ast.SelectorExpr: X: ast.Ident: Name: io Sel: ast.Ident: Name: Reader Optional: true ast.Field: Type: ast.SelectorExpr: X: ast.Ident: Name: io Sel: ast.Ident: Name: Writer Optional: true ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Struct types with optional Name: ast.Ident: Name: structs Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: p Type: ast.StructType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: X Type: ast.Ident: Name: int Optional: true ast.Field: Names: ast.Ident: Name: q Type: ast.StructType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: Y Type: ast.Ident: Name: string Optional: true ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Function types with optional Name: ast.Ident: Name: funcs Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: f Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: int Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: string Optional: true ast.Field: Names: ast.Ident: Name: g Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: error Optional: true ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Qualified identifiers with optional Name: ast.Ident: Name: qualified Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: t Type: ast.SelectorExpr: X: ast.Ident: Name: time Sel: ast.Ident: Name: Time Optional: true ast.Field: Names: ast.Ident: Name: d Type: ast.SelectorExpr: X: ast.Ident: Name: time Sel: ast.Ident: Name: Duration Optional: true ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Unnamed (anonymous) parameters with optional Name: ast.Ident: Name: unnamed Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: int Optional: true ast.Field: Type: ast.Ident: Name: string Optional: true ast.Field: Type: ast.Ident: Name: bool ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // All optional parameters Name: ast.Ident: Name: allOptional Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: a Type: ast.Ident: Name: int Optional: true ast.Field: Names: ast.Ident: Name: b Type: ast.Ident: Name: string Optional: true ast.Field: Names: ast.Ident: Name: c Type: ast.Ident: Name: bool Optional: true ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Optional with variadic (variadic cannot be optional) Name: ast.Ident: Name: withVariadic Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: a Type: ast.Ident: Name: int Optional: true ast.Field: Names: ast.Ident: Name: b Type: ast.Ellipsis: Elt: ast.Ident: Name: string ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Type declarations with optional Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: Handler Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: req Type: ast.StarExpr: X: ast.Ident: Name: Request Optional: true ast.Field: Names: ast.Ident: Name: resp Type: ast.StarExpr: X: ast.Ident: Name: Response Optional: true Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: error ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: Callback Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: int Optional: true ast.Field: Type: ast.Ident: Name: string Optional: true Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: bool ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Method with optional parameters Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: Server Type: ast.StructType: Fields: ast.FieldList: ast.FuncDecl: Recv: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: s Type: ast.StarExpr: X: ast.Ident: Name: Server Name: ast.Ident: Name: Handle Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: req Type: ast.StarExpr: X: ast.Ident: Name: Request Optional: true ast.Field: Names: ast.Ident: Name: opts Type: ast.StarExpr: X: ast.Ident: Name: Options Optional: true Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: error Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.Ident: Name: nil ================================================ FILE: parser/_testdata/overload1/overload.xgo ================================================ func foo = ( func(a, b float64) float64 { return a + b } func(a, b string) string { return a + b } ) func bar = ( addComplex (T).add ) ================================================ FILE: parser/_testdata/overload1/parser.expect ================================================ package main file overload.xgo ast.OverloadFuncDecl: Name: ast.Ident: Name: foo Funcs: ast.FuncLit: Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: a ast.Ident: Name: b Type: ast.Ident: Name: float64 Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: float64 Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.BinaryExpr: X: ast.Ident: Name: a Op: + Y: ast.Ident: Name: b ast.FuncLit: Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: a ast.Ident: Name: b Type: ast.Ident: Name: string Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: string Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.BinaryExpr: X: ast.Ident: Name: a Op: + Y: ast.Ident: Name: b ast.OverloadFuncDecl: Name: ast.Ident: Name: bar Funcs: ast.Ident: Name: addComplex ast.SelectorExpr: X: ast.ParenExpr: X: ast.Ident: Name: T Sel: ast.Ident: Name: add ================================================ FILE: parser/_testdata/overload2/overload2.xgo ================================================ func (T).* = ( mul1 mul2 ) func (T).add = ( add1 func(a, b T) T { return a + b } ) ================================================ FILE: parser/_testdata/overload2/parser.expect ================================================ package main file overload2.xgo ast.OverloadFuncDecl: Recv: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: T Name: ast.Ident: Name: * Funcs: ast.Ident: Name: mul1 ast.Ident: Name: mul2 ast.OverloadFuncDecl: Recv: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: T Name: ast.Ident: Name: add Funcs: ast.Ident: Name: add1 ast.FuncLit: Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: a ast.Ident: Name: b Type: ast.Ident: Name: T Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: T Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.BinaryExpr: X: ast.Ident: Name: a Op: + Y: ast.Ident: Name: b ================================================ FILE: parser/_testdata/overloadop/op_overload.xgo ================================================ type foo struct { } func (a *foo) * (b *foo) *foo func (a *foo) + (b *foo) *foo { println("a + b") return &foo{} } func (a foo) / (b foo) foo { println("a / b") return foo{} } func -(a foo) { println("-a") } func ++(a foo) { println("a++") } ================================================ FILE: parser/_testdata/overloadop/parser.expect ================================================ package main file op_overload.xgo ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: foo Type: ast.StructType: Fields: ast.FieldList: ast.FuncDecl: Recv: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: a Type: ast.StarExpr: X: ast.Ident: Name: foo Name: ast.Ident: Name: * Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: b Type: ast.StarExpr: X: ast.Ident: Name: foo Results: ast.FieldList: List: ast.Field: Type: ast.StarExpr: X: ast.Ident: Name: foo ast.FuncDecl: Recv: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: a Type: ast.StarExpr: X: ast.Ident: Name: foo Name: ast.Ident: Name: + Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: b Type: ast.StarExpr: X: ast.Ident: Name: foo Results: ast.FieldList: List: ast.Field: Type: ast.StarExpr: X: ast.Ident: Name: foo Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "a + b" ast.ReturnStmt: Results: ast.UnaryExpr: Op: & X: ast.CompositeLit: Type: ast.Ident: Name: foo ast.FuncDecl: Recv: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: a Type: ast.Ident: Name: foo Name: ast.Ident: Name: / Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: b Type: ast.Ident: Name: foo Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: foo Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "a / b" ast.ReturnStmt: Results: ast.CompositeLit: Type: ast.Ident: Name: foo ast.FuncDecl: Name: ast.Ident: Name: - Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: a Type: ast.Ident: Name: foo Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "-a" ast.FuncDecl: Name: ast.Ident: Name: ++ Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: a Type: ast.Ident: Name: foo Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "a++" ================================================ FILE: parser/_testdata/printvariadic/parser.expect ================================================ package main file printv.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: x ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: y ================================================ FILE: parser/_testdata/printvariadic/printv.xgo ================================================ println x... println y... ================================================ FILE: parser/_testdata/pystr/parser.expect ================================================ package main file pystr.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: print Args: ast.BasicLit: Kind: PYSTRING Value: "Hello" ================================================ FILE: parser/_testdata/pystr/pystr.xgo ================================================ print py"Hello" ================================================ FILE: parser/_testdata/rangeexpr1/parser.expect ================================================ package main file rangeexpr.xgo ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.RangeStmt: Key: ast.Ident: Name: i Tok: := X: ast.RangeExpr: Last: ast.BasicLit: Kind: INT Value: 10 Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: i ast.ForPhraseStmt: ForPhrase: ast.ForPhrase: Value: ast.Ident: Name: i X: ast.RangeExpr: First: ast.BasicLit: Kind: INT Value: 1 Last: ast.BasicLit: Kind: INT Value: 10 Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: i ast.RangeStmt: Key: ast.Ident: Name: i Tok: := X: ast.RangeExpr: Last: ast.BasicLit: Kind: INT Value: 10 Expr3: ast.BasicLit: Kind: INT Value: 2 Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: i ast.RangeStmt: Tok: ILLEGAL X: ast.RangeExpr: Last: ast.BasicLit: Kind: INT Value: 10 Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "Range expression" ================================================ FILE: parser/_testdata/rangeexpr1/rangeexpr.xgo ================================================ package main func main() { for i := range :10 { println(i) } for i in 1:10 { println(i) } for i := range :10:2 { println(i) } for range :10 { println("Range expression") } } ================================================ FILE: parser/_testdata/rangeexpr2/parser.expect ================================================ package main file rangeexpr.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.RangeStmt: Tok: ILLEGAL X: ast.RangeExpr: Last: ast.BasicLit: Kind: INT Value: 10 Body: ast.BlockStmt: ================================================ FILE: parser/_testdata/rangeexpr2/rangeexpr.xgo ================================================ for :10 { } ================================================ FILE: parser/_testdata/rangeexpr3/parser.expect ================================================ package main file rangeexpr.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.RangeStmt: Tok: ILLEGAL X: ast.RangeExpr: First: ast.BasicLit: Kind: INT Value: 1 Last: ast.BasicLit: Kind: INT Value: 10 Body: ast.BlockStmt: ================================================ FILE: parser/_testdata/rangeexpr3/rangeexpr.xgo ================================================ for 1:10 { } ================================================ FILE: parser/_testdata/rational/parser.expect ================================================ package main file rational.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: a Tok: := Rhs: ast.BinaryExpr: X: ast.BasicLit: Kind: RAT Value: 1r Op: << Y: ast.BasicLit: Kind: INT Value: 65 ast.AssignStmt: Lhs: ast.Ident: Name: b Tok: := Rhs: ast.BinaryExpr: X: ast.BasicLit: Kind: INT Value: 4 Op: / Y: ast.BasicLit: Kind: RAT Value: 5r ast.AssignStmt: Lhs: ast.Ident: Name: c Tok: := Rhs: ast.BinaryExpr: X: ast.BinaryExpr: X: ast.Ident: Name: b Op: - Y: ast.BinaryExpr: X: ast.BasicLit: Kind: INT Value: 1 Op: / Y: ast.BasicLit: Kind: RAT Value: 3r Op: + Y: ast.BinaryExpr: X: ast.BinaryExpr: X: ast.BasicLit: Kind: INT Value: 3 Op: * Y: ast.BasicLit: Kind: INT Value: 1 Op: / Y: ast.BasicLit: Kind: RAT Value: 2r ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: a ast.Ident: Name: b ast.Ident: Name: c ================================================ FILE: parser/_testdata/rational/rational.xgo ================================================ a := 1r << 65 // bigint, large than int64 b := 4/5r // bigrat c := b - 1/3r + 3*1/2r println(a, b, c) ================================================ FILE: parser/_testdata/selectdata/parser.expect ================================================ package main file select.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: a Tok: := Rhs: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 3 ast.BasicLit: Kind: INT Value: 5 ast.BasicLit: Kind: INT Value: 7 ast.BasicLit: Kind: INT Value: 8 ast.BasicLit: Kind: INT Value: 19 ast.AssignStmt: Lhs: ast.Ident: Name: y Tok: := Rhs: ast.ComprehensionExpr: Tok: { Elt: ast.Ident: Name: x Fors: ast.ForPhrase: Value: ast.Ident: Name: x X: ast.Ident: Name: a Cond: ast.BinaryExpr: X: ast.BinaryExpr: X: ast.Ident: Name: x Op: % Y: ast.BasicLit: Kind: INT Value: 2 Op: == Y: ast.BasicLit: Kind: INT Value: 0 ================================================ FILE: parser/_testdata/selectdata/select.xgo ================================================ a := [1, 3, 5, 7, 8, 19] y := {x for x in a if x%2 == 0} ================================================ FILE: parser/_testdata/slice1/parser.expect ================================================ package main file slice.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.IndexExpr: X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 Index: ast.BasicLit: Kind: INT Value: 0 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.SliceExpr: X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 Low: ast.BasicLit: Kind: INT Value: 0 High: ast.BasicLit: Kind: INT Value: 1 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.SliceExpr: X: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 2 Low: ast.BasicLit: Kind: INT Value: 0 High: ast.BasicLit: Kind: INT Value: 1 Max: ast.BasicLit: Kind: INT Value: 5 ast.AssignStmt: Lhs: ast.Ident: Name: a Tok: := Rhs: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.IndexExpr: X: ast.Ident: Name: a Index: ast.BasicLit: Kind: INT Value: 0 ================================================ FILE: parser/_testdata/slice1/slice.xgo ================================================ println([1][0]) println([1][0:1]) println([1, 2][0:1:5]) a := [1] println(a[0]) ================================================ FILE: parser/_testdata/slice2/parser.expect ================================================ package main file slice2.xgo ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: a Tok: := Rhs: ast.SliceLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: INT Value: 2 ast.BasicLit: Kind: INT Value: 3 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.Ident: Name: a ================================================ FILE: parser/_testdata/slice2/slice2.xgo ================================================ package main func main() { a := [ 1, 2, 3, ] println(a) } ================================================ FILE: parser/_testdata/spxtest/foo.spx ================================================ a := 1 ================================================ FILE: parser/_testdata/spxtest/parser.expect ================================================ package main file foo.spx noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: a Tok: := Rhs: ast.BasicLit: Kind: INT Value: 1 ================================================ FILE: parser/_testdata/staticmthd1/parser.expect ================================================ package main file static_method.xgo ast.FuncDecl: Recv: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: T Name: ast.Ident: Name: foo Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: a ast.Ident: Name: b Type: ast.Ident: Name: int Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: string Body: ast.BlockStmt: ================================================ FILE: parser/_testdata/staticmthd1/static_method.xgo ================================================ func T.foo(a, b int) string { } ================================================ FILE: parser/_testdata/staticmthd2/a.gox ================================================ func .New() *T { } ================================================ FILE: parser/_testdata/staticmthd2/parser.expect ================================================ package main file a.gox ast.FuncDecl: Recv: ast.FieldList: Name: ast.Ident: Name: New Type: ast.FuncType: Params: ast.FieldList: Results: ast.FieldList: List: ast.Field: Type: ast.StarExpr: X: ast.Ident: Name: T Body: ast.BlockStmt: ================================================ FILE: parser/_testdata/stdtype/parser.expect ================================================ package bar file stdtype.xgo noEntrypoint ast.GenDecl: Tok: import Specs: ast.ImportSpec: Path: ast.BasicLit: Kind: STRING Value: "io" ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: x Tok: := Rhs: ast.CompositeLit: Type: ast.ArrayType: Elt: ast.Ident: Name: float64 Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: FLOAT Value: 3.4 ast.BasicLit: Kind: INT Value: 5 ast.AssignStmt: Lhs: ast.Ident: Name: y Tok: := Rhs: ast.CompositeLit: Type: ast.MapType: Key: ast.Ident: Name: string Value: ast.Ident: Name: float64 Elts: ast.KeyValueExpr: Key: ast.BasicLit: Kind: STRING Value: "Hello" Value: ast.BasicLit: Kind: INT Value: 1 ast.KeyValueExpr: Key: ast.BasicLit: Kind: STRING Value: "xsw" Value: ast.BasicLit: Kind: FLOAT Value: 3.4 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "x:" ast.Ident: Name: x ast.BasicLit: Kind: STRING Value: "y:" ast.Ident: Name: y ast.AssignStmt: Lhs: ast.Ident: Name: a Tok: := Rhs: ast.CompositeLit: Type: ast.ArrayType: Len: ast.Ellipsis: Elt: ast.Ident: Name: float64 Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: FLOAT Value: 3.4 ast.BasicLit: Kind: INT Value: 5 ast.AssignStmt: Lhs: ast.Ident: Name: b Tok: := Rhs: ast.CompositeLit: Type: ast.ArrayType: Len: ast.Ellipsis: Elt: ast.Ident: Name: float64 Elts: ast.BasicLit: Kind: INT Value: 1 ast.KeyValueExpr: Key: ast.BasicLit: Kind: INT Value: 3 Value: ast.BasicLit: Kind: FLOAT Value: 3.4 ast.BasicLit: Kind: INT Value: 5 ast.AssignStmt: Lhs: ast.Ident: Name: c Tok: := Rhs: ast.CompositeLit: Type: ast.ArrayType: Elt: ast.Ident: Name: float64 Elts: ast.KeyValueExpr: Key: ast.BasicLit: Kind: INT Value: 2 Value: ast.BasicLit: Kind: FLOAT Value: 1.2 ast.BasicLit: Kind: INT Value: 3 ast.KeyValueExpr: Key: ast.BasicLit: Kind: INT Value: 6 Value: ast.BasicLit: Kind: FLOAT Value: 4.5 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "a:" ast.Ident: Name: a ast.BasicLit: Kind: STRING Value: "b:" ast.Ident: Name: b ast.BasicLit: Kind: STRING Value: "c:" ast.Ident: Name: c ================================================ FILE: parser/_testdata/stdtype/stdtype.xgo ================================================ package bar import "io" x := []float64{1, 3.4, 5} y := map[string]float64{"Hello": 1, "xsw": 3.4} println("x:", x, "y:", y) a := [...]float64{1, 3.4, 5} b := [...]float64{1, 3: 3.4, 5} c := []float64{2: 1.2, 3, 6: 4.5} println("a:", a, "b:", b, "c:", c) ================================================ FILE: parser/_testdata/stringex1/parser.expect ================================================ package main file string_lit.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "$" ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "$$" Extra: $$ ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "a$$b$" Extra: a$$ b$ ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "a$$b$$" Extra: a$$ b$$ ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "a$b$%" ================================================ FILE: parser/_testdata/stringex1/string_lit.xgo ================================================ println "$" println "$$" println "a$$b$" println "a$$b$$" println "a$b$%" ================================================ FILE: parser/_testdata/stringex2/parser.expect ================================================ package main file string_lit.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "${" Extra: ${ ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "${ b }" Extra: ast.Ident: Name: b ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "a${" Extra: a${ ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "a${b}c" Extra: a ast.Ident: Name: b c ================================================ FILE: parser/_testdata/stringex2/string_lit.xgo ================================================ println "${" println "${ b }" println "a${" println "a${b}c" ================================================ FILE: parser/_testdata/stringex3/parser.expect ================================================ package main file string_lit.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "file:${args[0]}?${query}" Extra: file: ast.IndexExpr: X: ast.Ident: Name: args Index: ast.BasicLit: Kind: INT Value: 0 ? ast.Ident: Name: query ================================================ FILE: parser/_testdata/stringex3/string_lit.xgo ================================================ println "file:${args[0]}?${query}" ================================================ FILE: parser/_testdata/tuplelit/parser.expect ================================================ package main file tuplelit.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: ken Tok: := Rhs: ast.TupleLit: Elts: ast.BasicLit: Kind: STRING Value: "Ken" ast.BasicLit: Kind: STRING Value: "ken@abc.com" ast.BasicLit: Kind: INT Value: 7 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: echo Args: ast.SelectorExpr: X: ast.Ident: Name: ken Sel: ast.Ident: Name: 0 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: dump Args: ast.TupleLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: FLOAT Value: 3.14 ast.Ident: Name: true ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: dump Args: ast.TupleLit: Elts: ast.BasicLit: Kind: INT Value: 1 ast.BasicLit: Kind: FLOAT Value: 3.14 ================================================ FILE: parser/_testdata/tuplelit/tuplelit.xgo ================================================ ken := ("Ken", "ken@abc.com", 7) echo ken.0 dump (1, 3.14), true dump (1, 3.14) ================================================ FILE: parser/_testdata/tupletype/parser.expect ================================================ package main file tupletype.xgo ast.GenDecl: Tok: import Specs: ast.ImportSpec: Path: ast.BasicLit: Kind: STRING Value: "io" ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Empty tuple Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: Empty Type: ast.TupleType: Fields: ast.FieldList: ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Anonymous tuple types Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: Pair Type: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: int ast.Field: Type: ast.Ident: Name: string ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: Triple Type: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: int ast.Field: Type: ast.Ident: Name: string ast.Field: Type: ast.Ident: Name: bool ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Named tuple types Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: Point Type: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: x Type: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: y Type: ast.Ident: Name: int ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: Person Type: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: name Type: ast.Ident: Name: string ast.Field: Names: ast.Ident: Name: age Type: ast.Ident: Name: int ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Type shorthand syntax Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: Point3D Type: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: x ast.Ident: Name: y ast.Ident: Name: z Type: ast.Ident: Name: int ast.GenDecl: Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: Mixed Type: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: a ast.Ident: Name: b Type: ast.Ident: Name: string ast.Field: Names: ast.Ident: Name: c Type: ast.Ident: Name: int ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Tuple as channel element type Tok: var Specs: ast.ValueSpec: Names: ast.Ident: Name: ch Type: ast.ChanType: Value: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: int ast.Field: Type: ast.Ident: Name: error ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Tuple as map value type Tok: var Specs: ast.ValueSpec: Names: ast.Ident: Name: cache Type: ast.MapType: Key: ast.Ident: Name: string Value: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: int ast.Field: Type: ast.Ident: Name: bool ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Tuple as slice element type Tok: var Specs: ast.ValueSpec: Names: ast.Ident: Name: pairs Type: ast.ArrayType: Elt: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: string ast.Field: Type: ast.Ident: Name: int ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Tuple with array types (covers token.LBRACK case) Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: WithArray Type: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: arr Type: ast.ArrayType: Elt: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: data Type: ast.ArrayType: Len: ast.BasicLit: Kind: INT Value: 5 Elt: ast.Ident: Name: string ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Tuple with pointer types (covers token.MUL case) Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: WithPointers Type: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Type: ast.StarExpr: X: ast.Ident: Name: int ast.Field: Type: ast.StarExpr: X: ast.Ident: Name: string ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Tuple with function types (covers token.FUNC case) Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: WithFunc Type: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: fn Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: int Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: string ast.Field: Names: ast.Ident: Name: callback Type: ast.FuncType: Params: ast.FieldList: ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Tuple with channel types (covers token.CHAN case) Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: WithChan Type: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: ch Type: ast.ChanType: Value: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: recv Type: ast.ChanType: Value: ast.Ident: Name: string ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Tuple with map types (covers token.MAP case) Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: WithMap Type: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: m Type: ast.MapType: Key: ast.Ident: Name: string Value: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: lookup Type: ast.MapType: Key: ast.Ident: Name: int Value: ast.Ident: Name: bool ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Tuple with struct types (covers token.STRUCT case) Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: WithStruct Type: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: s Type: ast.StructType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: x Type: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: data Type: ast.StructType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: name Type: ast.Ident: Name: string ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Tuple with interface types (covers token.INTERFACE case) Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: WithInterface Type: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: i Type: ast.InterfaceType: Methods: ast.FieldList: ast.Field: Names: ast.Ident: Name: reader Type: ast.InterfaceType: Methods: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: Read Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Type: ast.ArrayType: Elt: ast.Ident: Name: byte Results: ast.FieldList: List: ast.Field: Type: ast.Ident: Name: int ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Tuple with qualified type names (covers token.PERIOD case) Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: WithQualified Type: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Type: ast.SelectorExpr: X: ast.Ident: Name: io Sel: ast.Ident: Name: Reader ast.Field: Type: ast.SelectorExpr: X: ast.Ident: Name: io Sel: ast.Ident: Name: Writer ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Tuple with mixed named and array types Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: MixedArray Type: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: items Type: ast.ArrayType: Elt: ast.Ident: Name: int ast.Field: Names: ast.Ident: Name: count Type: ast.Ident: Name: int ast.GenDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // Single named field tuple Tok: type Specs: ast.TypeSpec: Name: ast.Ident: Name: SingleNamed Type: ast.TupleType: Fields: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: value Type: ast.Ident: Name: int ================================================ FILE: parser/_testdata/tupletype/tupletype.xgo ================================================ // Test cases for tuple types import "io" // Empty tuple type Empty () // Anonymous tuple types type Pair (int, string) type Triple (int, string, bool) // Named tuple types type Point (x int, y int) type Person (name string, age int) // Type shorthand syntax type Point3D (x, y, z int) type Mixed (a, b string, c int) // Tuple as channel element type var ch chan (int, error) // Tuple as map value type var cache map[string](int, bool) // Tuple as slice element type var pairs [](string, int) // Tuple with array types (covers token.LBRACK case) type WithArray (arr []int, data [5]string) // Tuple with pointer types (covers token.MUL case) type WithPointers (*int, *string) // Tuple with function types (covers token.FUNC case) type WithFunc (fn func(int) string, callback func()) // Tuple with channel types (covers token.CHAN case) type WithChan (ch chan int, recv <-chan string) // Tuple with map types (covers token.MAP case) type WithMap (m map[string]int, lookup map[int]bool) // Tuple with struct types (covers token.STRUCT case) type WithStruct (s struct{ x int }, data struct{ name string }) // Tuple with interface types (covers token.INTERFACE case) type WithInterface (i interface{}, reader interface{ Read([]byte) int }) // Tuple with qualified type names (covers token.PERIOD case) type WithQualified (io.Reader, io.Writer) // Tuple with mixed named and array types type MixedArray (items []int, count int) // Single named field tuple type SingleNamed (value int) ================================================ FILE: parser/_testdata/typeof/parser.expect ================================================ package main file typeof.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: echo Args: ast.CallExpr: Fun: ast.Ident: Name: type Args: ast.BasicLit: Kind: INT Value: 1 ast.CallExpr: Fun: ast.Ident: Name: type Args: ast.BasicLit: Kind: STRING Value: "Hi" ================================================ FILE: parser/_testdata/typeof/typeof.xgo ================================================ echo type(1), type("Hi") ================================================ FILE: parser/_testdata/typeswitch/parser.expect ================================================ package main file typeswitch.xgo ast.FuncDecl: Name: ast.Ident: Name: add Type: ast.FuncType: Params: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: v Type: ast.InterfaceType: Methods: ast.FieldList: Body: ast.BlockStmt: List: ast.TypeSwitchStmt: Assign: ast.AssignStmt: Lhs: ast.Ident: Name: a Tok: := Rhs: ast.TypeAssertExpr: X: ast.Ident: Name: v Body: ast.BlockStmt: List: ast.CaseClause: List: ast.Ident: Name: int ast.InterfaceType: Methods: ast.FieldList: List: ast.Field: Names: ast.Ident: Name: Foo Type: ast.FuncType: Params: ast.FieldList: ast.CaseClause: List: ast.StarExpr: X: ast.Ident: Name: string ast.CaseClause: ================================================ FILE: parser/_testdata/typeswitch/typeswitch.xgo ================================================ func add(v interface{}) { switch a := v.(type) { case int, interface{ Foo() }: case *string: default: } } ================================================ FILE: parser/_testdata/unit/parser.expect ================================================ package main file step.xgo noEntrypoint ast.FuncDecl: Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: wait Args: ast.NumberUnitLit: Kind: INT Value: 1 Unit: µs ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: wait Args: ast.NumberUnitLit: Kind: FLOAT Value: 0.5 Unit: µs ================================================ FILE: parser/_testdata/unit/step.xgo ================================================ wait 1µs wait 0.5µs ================================================ FILE: parser/_testexpr/lambda/in.xgo ================================================ => { return this } ================================================ FILE: parser/_testexpr/lambda/out.expect ================================================ ast.LambdaExpr2: Body: ast.BlockStmt: List: ast.ReturnStmt: Results: ast.Ident: Name: this ================================================ FILE: parser/fsx/fsys.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package fsx import ( "io/fs" "os" "path/filepath" ) // ----------------------------------------------------------------------------- // FileSystem represents a file system. type FileSystem interface { ReadDir(dirname string) ([]fs.DirEntry, error) ReadFile(filename string) ([]byte, error) Join(elem ...string) string // Base returns the last element of path. // Trailing path separators are removed before extracting the last element. // If the path is empty, Base returns ".". // If the path consists entirely of separators, Base returns a single separator. Base(filename string) string // Abs returns an absolute representation of path. Abs(path string) (string, error) } // ----------------------------------------------------------------------------- type localFS struct{} func (p localFS) ReadDir(dirname string) ([]fs.DirEntry, error) { return os.ReadDir(dirname) } func (p localFS) ReadFile(filename string) ([]byte, error) { return os.ReadFile(filename) } func (p localFS) Join(elem ...string) string { return filepath.Join(elem...) } // Base returns the last element of path. // Trailing path separators are removed before extracting the last element. // If the path is empty, Base returns ".". // If the path consists entirely of separators, Base returns a single separator. func (p localFS) Base(filename string) string { return filepath.Base(filename) } // Abs returns an absolute representation of path. func (p localFS) Abs(path string) (string, error) { return filepath.Abs(path) } var Local FileSystem = localFS{} // ----------------------------------------------------------------------------- ================================================ FILE: parser/fsx/memfs/fs.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package memfs import ( "bytes" "io" "io/fs" "os" "path/filepath" ) type FileFS struct { data []byte info fs.DirEntry } type dirEntry struct { fs.FileInfo } func (p *dirEntry) Type() fs.FileMode { return p.FileInfo.Mode().Type() } func (p *dirEntry) Info() (fs.FileInfo, error) { return p.FileInfo, nil } func File(filename string, src any) (f *FileFS, err error) { var data []byte var info fs.DirEntry if src != nil { data, err = readSource(src) if err != nil { return } info = &memFileInfo{name: filename, size: len(data)} } else { fi, e := os.Stat(filename) if e != nil { return nil, e } data, err = os.ReadFile(filename) if err != nil { return } info = &dirEntry{fi} } return &FileFS{data: data, info: info}, nil } func (p *FileFS) ReadDir(dirname string) ([]fs.DirEntry, error) { return []fs.DirEntry{p.info}, nil } func (p *FileFS) ReadFile(filename string) ([]byte, error) { return p.data, nil } func (p *FileFS) Join(elem ...string) string { return filepath.Join(elem...) } func (p *FileFS) Base(filename string) string { return filepath.Base(filename) } func (p *FileFS) Abs(path string) (string, error) { return path, nil } func readSource(src any) ([]byte, error) { switch s := src.(type) { case string: return []byte(s), nil case []byte: return s, nil case *bytes.Buffer: // is io.Reader, but src is already available in []byte form if s != nil { return s.Bytes(), nil } case io.Reader: return io.ReadAll(s) } return nil, os.ErrInvalid } ================================================ FILE: parser/fsx/memfs/memfs.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package memfs import ( "io/fs" "path" "path/filepath" "syscall" "time" ) // ----------------------------------------------------------------------------- type memFileInfo struct { name string size int } func (p *memFileInfo) Name() string { return filepath.Base(p.name) } func (p *memFileInfo) Size() int64 { return int64(p.size) } func (p *memFileInfo) Mode() fs.FileMode { return 0 } func (p *memFileInfo) Type() fs.FileMode { return 0 } func (p *memFileInfo) ModTime() (t time.Time) { return time.Now() } func (p *memFileInfo) IsDir() bool { return false } func (p *memFileInfo) Sys() any { return nil } func (p *memFileInfo) Info() (fs.FileInfo, error) { return p, nil } // ----------------------------------------------------------------------------- // FS represents a file system in memory. type FS struct { dirs map[string][]string files map[string]string } // New creates a file system instance. func New(dirs map[string][]string, files map[string]string) *FS { return &FS{dirs: dirs, files: files} } // ReadDir reads the directory named by dirname and returns // a list of directory entries sorted by filename. func (p *FS) ReadDir(dirname string) ([]fs.DirEntry, error) { if items, ok := p.dirs[dirname]; ok { fis := make([]fs.DirEntry, len(items)) for i, item := range items { fis[i] = &memFileInfo{name: item} } return fis, nil } return nil, syscall.ENOENT } // ReadFile reads the file named by filename and returns the contents. // A successful call returns err == nil, not err == EOF. Because ReadFile // reads the whole file, it does not treat an EOF from Read as an error // to be reported. func (p *FS) ReadFile(filename string) ([]byte, error) { if data, ok := p.files[filename]; ok { return []byte(data), nil } return nil, syscall.ENOENT } // Join joins any number of path elements into a single path, // separating them with slashes. Empty elements are ignored. // The result is Cleaned. However, if the argument list is // empty or all its elements are empty, Join returns // an empty string. func (p *FS) Join(elem ...string) string { return path.Join(elem...) } // Base returns the last element of path. // Trailing slashes are removed before extracting the last element. // If the path is empty, Base returns ".". // If the path consists entirely of slashes, Base returns "/". func (p *FS) Base(filename string) string { return path.Base(filename) } func (p *FS) Abs(path string) (string, error) { return path, nil } // ----------------------------------------------------------------------------- // SingleFile creates a file system that only contains a single file. func SingleFile(dir string, fname string, data string) *FS { return New(map[string][]string{ dir: {fname}, }, map[string]string{ path.Join(dir, fname): data, }) } // TwoFiles creates a file system that contains two files. func TwoFiles(dir string, fname1, data1, fname2, data2 string) *FS { return New(map[string][]string{ dir: {fname1, fname2}, }, map[string]string{ path.Join(dir, fname1): data1, path.Join(dir, fname2): data2, }) } // ----------------------------------------------------------------------------- ================================================ FILE: parser/interface.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // This file contains the exported entry points for invoking the parser. package parser import ( goparser "go/parser" "go/scanner" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/token" "github.com/qiniu/x/stream" ) // A Mode value is a set of flags (or 0). // They control the amount of source code parsed and other optional // parser functionality. type Mode uint const ( // PackageClauseOnly - stop parsing after package clause PackageClauseOnly = Mode(goparser.PackageClauseOnly) // ImportsOnly - stop parsing after import declarations ImportsOnly = Mode(goparser.ImportsOnly) // ParseComments - parse comments and add them to AST ParseComments = Mode(goparser.ParseComments) // Trace - print a trace of parsed productions Trace = Mode(goparser.Trace) // DeclarationErrors - report declaration errors DeclarationErrors = Mode(goparser.DeclarationErrors) // AllErrors - report all errors (not just the first 10 on different lines) AllErrors = Mode(goparser.AllErrors) // ParseGoAsXGo - parse Go files by xgo/parser ParseGoAsXGo Mode = 1 << 16 // ParseXGoClass - parse XGo classfile by xgo/parser ParseXGoClass Mode = 1 << 17 // SaveAbsFile - parse and save absolute path to pkg.Files SaveAbsFile Mode = 1 << 18 // Deprecated: use ParseGoAsXGo instead. ParseGoAsGoPlus = ParseGoAsXGo // Deprecated: use ParseXGoClass instead. ParseGoPlusClass = ParseXGoClass goReservedFlags Mode = ((1 << 16) - 1) ) // ----------------------------------------------------------------------------- // ParseFile parses the source code of a single Go source file and returns // the corresponding ast.File node. The source code may be provided via // the filename of the source file, or via the src parameter. // // If src != nil, ParseFile parses the source from src and the filename is // only used when recording position information. The type of the argument // for the src parameter must be string, []byte, or io.Reader. // If src == nil, ParseFile parses the file specified by filename. // // The mode parameter controls the amount of source text parsed and other // optional parser functionality. Position information is recorded in the // file set fset, which must not be nil. // // If the source couldn't be read, the returned AST is nil and the error // indicates the specific failure. If the source was read but syntax // errors were found, the result is a partial AST (with ast.Bad* nodes // representing the fragments of erroneous source code). Multiple errors // are returned via a scanner.ErrorList which is sorted by source position. func parseFile(fset *token.FileSet, filename string, src any, mode Mode) (f *ast.File, err error) { if fset == nil { panic("parser.ParseFile: no token.FileSet provided (fset == nil)") } // get source text, err := stream.ReadSourceLocal(filename, src) if err != nil { return } var p parser defer func() { if e := recover(); e != nil { // resume same panic if it's not a bailout if _, ok := e.(bailout); !ok { panic(e) } } // set result values if f == nil { // source is not a valid Go source file - satisfy // ParseFile API and return a valid (but) empty *ast.File f = &ast.File{ Name: new(ast.Ident), } } f.Code = text p.errors.Sort() err = p.errors.Err() }() // parse source p.init(fset, filename, text, mode) f = p.parseFile() return } // ----------------------------------------------------------------------------- // ParseExprFrom is a convenience function for parsing an expression. // The arguments have the same meaning as for ParseFile, but the source must // be a valid Go/XGo (type or value) expression. Specifically, fset must not // be nil. // // If the source couldn't be read, the returned AST is nil and the error // indicates the specific failure. If the source was read but syntax // errors were found, the result is a partial AST (with ast.Bad* nodes // representing the fragments of erroneous source code). Multiple errors // are returned via a scanner.ErrorList which is sorted by source position. func ParseExprFrom(fset *token.FileSet, filename string, src any, mode Mode) (expr ast.Expr, err error) { // get source text, err := stream.ReadSourceLocal(filename, src) if err != nil { return } var p parser defer func() { if e := recover(); e != nil { // resume same panic if it's not a bailout if _, ok := e.(bailout); !ok { panic(e) } } p.errors.Sort() err = p.errors.Err() }() // parse expr p.init(fset, filename, text, mode) expr = p.parseRHS() // If a semicolon was inserted, consume it; // report an error if there's more tokens. if p.tok == token.SEMICOLON && p.lit == "\n" { p.next() } p.expect(token.EOF) return } // ParseExprEx is a convenience function for parsing an expression. // The arguments have the same meaning as for ParseFile, but the source must // be a valid Go/XGo (type or value) expression. Specifically, fset must not // be nil. // // If the source couldn't be read, the returned AST is nil and the error // indicates the specific failure. If the source was read but syntax // errors were found, the result is a partial AST (with ast.Bad* nodes // representing the fragments of erroneous source code). Multiple errors // are returned via a scanner.ErrorList which is sorted by source position. func ParseExprEx(file *token.File, src []byte, offset int, mode Mode) (expr ast.Expr, err scanner.ErrorList) { var p parser defer func() { if e := recover(); e != nil { // resume same panic if it's not a bailout if _, ok := e.(bailout); !ok { panic(e) } } err = p.errors }() // parse expr p.initSub(file, src, offset, mode) expr = p.parseRHS() // If a semicolon was inserted, consume it; // report an error if there's more tokens. if p.tok == token.SEMICOLON && p.lit == "\n" { p.next() } p.expect(token.EOF) return } // ParseExpr is a convenience function for obtaining the AST of an expression x. // The position information recorded in the AST is undefined. The filename used // in error messages is the empty string. // // If syntax errors were found, the result is a partial AST (with ast.Bad* nodes // representing the fragments of erroneous source code). Multiple errors are // returned via a scanner.ErrorList which is sorted by source position. func ParseExpr(x string) (ast.Expr, error) { return ParseExprFrom(token.NewFileSet(), "", []byte(x), 0) } // ----------------------------------------------------------------------------- ================================================ FILE: parser/parser.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package parser implements a parser for XGo source files. Input may be // provided in a variety of forms (see the various Parse* functions); the // output is an abstract syntax tree (AST) representing the Go source. The // parser is invoked through one of the Parse* functions. // // The parser accepts a larger language than is syntactically permitted by // the XGo spec, for simplicity, and for improved robustness in the presence // of syntax errors. For instance, in method declarations, the receiver is // treated like an ordinary parameter list and thus may contain multiple // entries where the spec permits exactly one. Consequently, the corresponding // field in the AST (ast.FuncDecl.Recv) field is not restricted to one entry. package parser import ( "fmt" "strconv" "strings" "unicode" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/scanner" "github.com/goplus/xgo/token" tplast "github.com/goplus/xgo/tpl/ast" tpl "github.com/goplus/xgo/tpl/parser" "github.com/qiniu/x/log" ) // Parser flags control parsing behavior using bitwise operations: // // flagInLHS - parsing left-hand side expression (identifiers not resolved) // flagAllowCmd - allow command-style function calls without parentheses // flagAllowRangeExpr - allow range expressions (first:last or first:last:step) const ( flagInLHS = 1 << iota flagAllowCmd flagAllowRangeExpr flagAllowKwargExpr ) const ( exprNormal = iota exprTuple // (expr1, expr2, ...) exprKwarg // name=expr ) // The parser structure holds the parser's internal state. type parser struct { file *token.File errors scanner.ErrorList scanner scanner.Scanner // Tracing/debugging mode Mode // parsing mode trace bool // == (mode & Trace != 0) indent int // indentation used for tracing output // Comments comments []*ast.CommentGroup leadComment *ast.CommentGroup // last lead comment lineComment *ast.CommentGroup // last line comment // Next token pos token.Pos // token position tok token.Token // one token look-ahead lit string // token literal old struct { pos token.Pos tok token.Token lit string } // Error recovery // (used to limit the number of calls to parser.advance // w/o making scanning progress - avoids potential endless // loops across multiple parser functions during error recovery) syncPos token.Pos // last synchronization position syncCnt int // number of parser.advance calls without progress varDeclCnt int // number of var decl // Non-syntactic parser control exprLev int // < 0: in control clause, >= 0: in expression inRHS bool // if set, the parser is parsing a rhs expression // Ordinary identifier scopes pkgScope *ast.Scope // pkgScope.Outer == nil topScope *ast.Scope // top-most scope; may be pkgScope unresolved []*ast.Ident // unresolved identifiers imports []*ast.ImportSpec // list of imports // Label scopes // (maintained by open/close LabelScope) labelScope *ast.Scope // label scope for current function targetStack [][]*ast.Ident // stack of unresolved labels } func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mode) { p.file = fset.AddFile(filename, -1, len(src)) var m scanner.Mode if mode&ParseComments != 0 { m = scanner.ScanComments } eh := func(pos token.Position, msg string) { p.errors.Add(pos, msg) } p.scanner.Init(p.file, src, eh, m) p.mode = mode p.trace = mode&Trace != 0 // for convenience (p.trace is used frequently) p.next() } func (p *parser) initSub(file *token.File, src []byte, offset int, mode Mode) { p.file = file eh := func(pos token.Position, msg string) { p.errors.Add(pos, msg) } p.scanner.InitEx(p.file, src, offset, eh, 0) p.mode = mode p.trace = mode&Trace != 0 // for convenience (p.trace is used frequently) p.next() } // ---------------------------------------------------------------------------- // Scoping support func (p *parser) openScope() { p.topScope = ast.NewScope(p.topScope) } func (p *parser) closeScope() { p.topScope = p.topScope.Outer } func (p *parser) openLabelScope() { p.labelScope = ast.NewScope(p.labelScope) p.targetStack = append(p.targetStack, nil) } func (p *parser) closeLabelScope() { // resolve labels n := len(p.targetStack) - 1 scope := p.labelScope for _, ident := range p.targetStack[n] { ident.Obj = scope.Lookup(ident.Name) if ident.Obj == nil && p.mode&DeclarationErrors != 0 { p.error(ident.Pos(), fmt.Sprintf("label %s undefined", ident.Name)) } } // pop label scope p.targetStack = p.targetStack[0:n] p.labelScope = p.labelScope.Outer } func (p *parser) declare(decl, data any, scope *ast.Scope, kind ast.ObjKind, idents ...*ast.Ident) { for _, ident := range idents { assert(ident.Obj == nil, "identifier already declared or resolved") obj := ast.NewObj(kind, ident.Name) // remember the corresponding declaration for redeclaration // errors and global variable resolution/typechecking phase obj.Decl = decl obj.Data = data ident.Obj = obj if ident.Name != "_" { if alt := scope.Insert(obj); alt != nil && p.mode&DeclarationErrors != 0 { prevDecl := "" if pos := alt.Pos(); pos.IsValid() { prevDecl = fmt.Sprintf("\n\tprevious declaration at %s", p.file.Position(pos)) } p.error(ident.Pos(), fmt.Sprintf("%s redeclared in this block%s", ident.Name, prevDecl)) } } } } func (p *parser) shortVarDecl(decl *ast.AssignStmt, list []ast.Expr) { // Go spec: A short variable declaration may redeclare variables // provided they were originally declared in the same block with // the same type, and at least one of the non-blank variables is new. n := 0 // number of new variables for _, x := range list { if ident, isIdent := x.(*ast.Ident); isIdent { assert(ident.Obj == nil, "identifier already declared or resolved") obj := ast.NewObj(ast.Var, ident.Name) // remember corresponding assignment for other tools obj.Decl = decl ident.Obj = obj if ident.Name != "_" { if alt := p.topScope.Insert(obj); alt != nil { ident.Obj = alt // redeclaration } else { n++ // new declaration } } } else { p.errorExpected(x.Pos(), "identifier on left side of :=", 2) } } if n == 0 && p.mode&DeclarationErrors != 0 { p.error(list[0].Pos(), "no new variables on left side of :=") } } // The unresolved object is a sentinel to mark identifiers that have been added // to the list of unresolved identifiers. The sentinel is only used for verifying // internal consistency. var unresolved = new(ast.Object) // If x is an identifier, tryResolve attempts to resolve x by looking up // the object it denotes. If no object is found and collectUnresolved is // set, x is marked as unresolved and collected in the list of unresolved // identifiers. func (p *parser) tryResolve(x ast.Expr, collectUnresolved bool) { // nothing to do if x is not an identifier or the blank identifier ident, _ := x.(*ast.Ident) if ident == nil { return } assert(ident.Obj == nil, "identifier already declared or resolved") if ident.Name == "_" { return } // try to resolve the identifier for s := p.topScope; s != nil; s = s.Outer { if obj := s.Lookup(ident.Name); obj != nil { ident.Obj = obj return } } // all local scopes are known, so any unresolved identifier // must be found either in the file scope, package scope // (perhaps in another file), or universe scope --- collect // them so that they can be resolved later if collectUnresolved { ident.Obj = unresolved p.unresolved = append(p.unresolved, ident) } } func (p *parser) resolve(x ast.Expr) { p.tryResolve(x, true) } // ---------------------------------------------------------------------------- // Parsing support func (p *parser) printTrace(a ...any) { const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " const n = len(dots) pos := p.file.Position(p.pos) fmt.Printf("%5d:%3d: ", pos.Line, pos.Column) i := 2 * p.indent for i > n { fmt.Print(dots) i -= n } // i <= n fmt.Print(dots[0:i]) fmt.Println(a...) } func trace(p *parser, msg string) *parser { p.printTrace(msg, "(") p.indent++ return p } // Usage pattern: defer un(trace(p, "...")) func un(p *parser) { p.indent-- p.printTrace(")") } func (p *parser) unget(pos token.Pos, tok token.Token, lit string) { p.old.pos, p.old.tok, p.old.lit = p.pos, p.tok, p.lit p.pos, p.tok, p.lit = pos, tok, lit } // Advance to the next token. func (p *parser) next0() { if p.old.pos != 0 { // XGo: support unget p.pos, p.tok, p.lit = p.old.pos, p.old.tok, p.old.lit p.old.pos = 0 return } // Because of one-token look-ahead, print the previous token // when tracing as it provides a more readable output. The // very first token (!p.pos.IsValid()) is not initialized // (it is token.ILLEGAL), so don't print it . if p.trace && p.pos.IsValid() { s := p.tok.String() switch { case p.tok.IsLiteral(): p.printTrace(s, p.lit) case p.tok.IsOperator(), p.tok.IsKeyword(): p.printTrace("\"" + s + "\"") default: p.printTrace(s) } } p.pos, p.tok, p.lit = p.scanner.Scan() } // Consume a comment and return it and the line on which it ends. func (p *parser) consumeComment() (comment *ast.Comment, endline int) { // /*-style comments may end on a different line than where they start. // Scan the comment for '\n' chars and adjust endline accordingly. endline = p.file.Line(p.pos) if p.lit[1] == '*' { // don't use range here - no need to decode Unicode code points for i := 0; i < len(p.lit); i++ { if p.lit[i] == '\n' { endline++ } } } comment = &ast.Comment{Slash: p.pos, Text: p.lit} p.next0() return } // Consume a group of adjacent comments, add it to the parser's // comments list, and return it together with the line at which // the last comment in the group ends. A non-comment token or n // empty lines terminate a comment group. func (p *parser) consumeCommentGroup(n int) (comments *ast.CommentGroup, endline int) { var list []*ast.Comment endline = p.file.Line(p.pos) for p.tok == token.COMMENT && p.file.Line(p.pos) <= endline+n { var comment *ast.Comment comment, endline = p.consumeComment() list = append(list, comment) } // add comment group to the comments list comments = &ast.CommentGroup{List: list} p.comments = append(p.comments, comments) return } // Advance to the next non-comment token. In the process, collect // any comment groups encountered, and remember the last lead and // line comments. // // A lead comment is a comment group that starts and ends in a // line without any other tokens and that is followed by a non-comment // token on the line immediately after the comment group. // // A line comment is a comment group that follows a non-comment // token on the same line, and that has no tokens after it on the line // where it ends. // // Lead and line comments may be considered documentation that is // stored in the AST. func (p *parser) next() { p.leadComment = nil p.lineComment = nil prev := p.pos p.next0() if p.tok == token.COMMENT { var comment *ast.CommentGroup var endline int if p.file.Line(p.pos) == p.file.Line(prev) || p.lit[0] == '#' { // The comment is on same line as the previous token; it // cannot be a lead comment but may be a line comment. comment, endline = p.consumeCommentGroup(0) if p.file.Line(p.pos) != endline || p.tok == token.EOF { // The next token is on a different line, thus // the last comment group is a line comment. p.lineComment = comment } } // consume successor comments, if any endline = -1 for p.tok == token.COMMENT { comment, endline = p.consumeCommentGroup(1) } if endline+1 == p.file.Line(p.pos) { // The next token is following on the line immediately after the // comment group, thus the last comment group is a lead comment. p.leadComment = comment } } } // A bailout panic is raised to indicate early termination. type bailout struct { } func (p *parser) error(pos token.Pos, msg string) { epos := p.file.Position(pos) // If AllErrors is not set, discard errors reported on the same line // as the last recorded error and stop parsing if there are more than // 10 errors. if p.mode&AllErrors == 0 { n := len(p.errors) if n > 0 && p.errors[n-1].Pos.Line == epos.Line { return // discard - likely a spurious error } if n > 10 { panic(bailout{}) } } p.errors.Add(epos, msg) } func (p *parser) errorExpected(pos token.Pos, msg string, calldepth int) { msg = "expected " + msg if pos == p.pos { // the error happened at the current position; // make the error message more specific switch { case p.tok == token.SEMICOLON && p.lit == "\n": msg += ", found newline" case p.tok.IsLiteral(): // print 123 rather than 'INT', etc. msg += ", found " + p.lit default: msg += ", found '" + p.tok.String() + "'" } } if debugParseError { log.Std.Output("", log.Linfo, calldepth, msg) } p.error(pos, msg) } func (p *parser) expect(tok token.Token) token.Pos { pos := p.pos if p.tok != tok { p.errorExpected(pos, "'"+tok.String()+"'", 3) } p.next() // make progress return pos } // expect2 is like expect, but it returns an invalid position // if the expected token is not found. func (p *parser) expect2(tok token.Token) (pos token.Pos) { if p.tok == tok { pos = p.pos } else { p.errorExpected(p.pos, "'"+tok.String()+"'", 3) } p.next() // make progress return } func (p *parser) expectIn() token.Pos { pos := p.pos switch p.tok { case token.ARROW: // <- (backward compatibility) case token.IDENT: // in if p.lit == "in" { break } fallthrough default: p.errorExpected(pos, "'in'", 3) } p.next() // make progress return pos } // expectClosing is like expect but provides a better error message // for the common case of a missing comma before a newline. func (p *parser) expectClosing(tok token.Token, context string) token.Pos { if p.tok != tok && p.tok == token.SEMICOLON && p.lit == "\n" { p.error(p.pos, "missing ',' before newline in "+context) p.next() } return p.expect(tok) } func (p *parser) expectSemi() { // semicolon is optional before a closing ')' or '}' if p.tok != token.RPAREN && p.tok != token.RBRACE { switch p.tok { case token.COMMA: // permit a ',' instead of a ';' but complain p.errorExpected(p.pos, "';'", 3) fallthrough case token.SEMICOLON: p.next() default: p.errorExpected(p.pos, "';'", 3) p.advance(stmtStart) } } } func (p *parser) atComma(context string, follow token.Token) bool { if p.tok == token.COMMA { return true } if p.tok != follow { msg := "missing ','" if p.tok == token.SEMICOLON && p.lit == "\n" { msg += " before newline" } msgctx := msg + " in " + context p.error(p.pos, msgctx) if debugParseError { log.Std.Output("", log.Linfo, 2, msgctx) } return true // "insert" comma and continue } return false } func assert(cond bool, msg string) { if !cond { panic("go/parser internal error: " + msg) } } // advance consumes tokens until the current token p.tok // is in the 'to' set, or token.EOF. For error recovery. func (p *parser) advance(to map[token.Token]bool) { for ; p.tok != token.EOF; p.next() { if to[p.tok] { // Return only if parser made some progress since last // sync or if it has not reached 10 advance calls without // progress. Otherwise consume at least one token to // avoid an endless parser loop (it is possible that // both parseOperand and parseStmt call advance and // correctly do not advance, thus the need for the // invocation limit p.syncCnt). if p.pos == p.syncPos && p.syncCnt < 10 { p.syncCnt++ return } if p.pos > p.syncPos { p.syncPos = p.pos p.syncCnt = 0 return } // Reaching here indicates a parser bug, likely an // incorrect token list in this function, but it only // leads to skipping of possibly correct code if a // previous error is present, and thus is preferred // over a non-terminating parse. } } } var stmtStart = map[token.Token]bool{ token.BREAK: true, token.CONST: true, token.CONTINUE: true, token.DEFER: true, token.FALLTHROUGH: true, token.FOR: true, token.GO: true, token.GOTO: true, token.IF: true, token.RETURN: true, token.SELECT: true, token.SWITCH: true, token.TYPE: true, token.VAR: true, } var declStart = map[token.Token]bool{ token.CONST: true, token.TYPE: true, token.VAR: true, } var exprEnd = map[token.Token]bool{ token.COMMA: true, token.COLON: true, token.SEMICOLON: true, token.RPAREN: true, token.RBRACK: true, token.RBRACE: true, } // safePos returns a valid file position for a given position: If pos // is valid to begin with, safePos returns pos. If pos is out-of-range, // safePos returns the EOF position. // // This is hack to work around "artificial" end positions in the AST which // are computed by adding 1 to (presumably valid) token positions. If the // token positions are invalid due to parse errors, the resulting end position // may be past the file's EOF position, which would lead to panics if used // later on. func (p *parser) safePos(pos token.Pos) (res token.Pos) { defer func() { if recover() != nil { res = token.Pos(p.file.Base() + p.file.Size()) // EOF position } }() _ = p.file.Offset(pos) // trigger a panic if position is out-of-range return pos } // ---------------------------------------------------------------------------- // Identifiers func (p *parser) parseIdentOrOp() (*ast.Ident, bool) { // function Name if int(p.tok) < len(overloadOps) { flags := overloadOps[p.tok] if flags != 0 { pos := p.pos tok := p.tok p.next() if debugParseOutput { log.Printf("ast.Ident{Tok: %v}\n", tok) } return &ast.Ident{NamePos: pos, Name: tok.String()}, true } } return p.parseIdent(), false } const ( opUnary = 1 << iota opBinary opAssign opAssignOp opIncDec ) var overloadOps = [...]byte{ token.ADD: opBinary, // + token.SUB: opBinary | opUnary, // - token.MUL: opBinary | opUnary, // * token.QUO: opBinary, // / token.REM: opBinary, // % token.AND: opBinary, // & token.OR: opBinary, // | token.XOR: opBinary, // ^ token.SHL: opBinary, // << token.SHR: opBinary, // >> token.AND_NOT: opBinary, // &^ token.SRARROW: opBinary, // -> token.BIDIARROW: opBinary, // <> token.ADD_ASSIGN: opAssignOp, // += token.SUB_ASSIGN: opAssignOp, // -= token.MUL_ASSIGN: opAssignOp, // *= token.QUO_ASSIGN: opAssignOp, // /= token.REM_ASSIGN: opAssignOp, // %= token.AND_ASSIGN: opAssignOp, // &= token.OR_ASSIGN: opAssignOp, // |= token.XOR_ASSIGN: opAssignOp, // ^= token.SHL_ASSIGN: opAssignOp, // <<= token.SHR_ASSIGN: opAssignOp, // >>= token.AND_NOT_ASSIGN: opAssignOp, // &^= token.ASSIGN: opAssign, // = token.INC: opIncDec, // ++ token.DEC: opIncDec, // -- token.EQL: opBinary, // == token.LSS: opBinary, // < token.GTR: opBinary, // > token.NEQ: opBinary, // != token.LEQ: opBinary, // <= token.GEQ: opBinary, // >= token.LAND: opBinary, // && token.LOR: opBinary, // || token.NOT: opUnary, // ! token.ARROW: opBinary | opUnary, // <- } func (p *parser) parseIdent() *ast.Ident { pos := p.pos name := "_" if p.tok == token.IDENT { name = p.lit p.next() } else { p.expect(token.IDENT) // use expect() error handling } if debugParseOutput { log.Printf("ast.Ident{Name: %v}\n", name) } return &ast.Ident{NamePos: pos, Name: name} } func (p *parser) parseIdentList() (list []*ast.Ident) { if p.trace { defer un(trace(p, "IdentList")) } list = append(list, p.parseIdent()) for p.tok == token.COMMA { p.next() list = append(list, p.parseIdent()) } return } // ---------------------------------------------------------------------------- // Common productions // If flagInLHS is set in flags, result list elements which are identifiers are // not resolved. // flags support flagInLHS, flagAllowCmd func (p *parser) parseExprList(flags int) (list []ast.Expr) { if p.trace { defer un(trace(p, "ExpressionList")) } list = append(list, p.checkExpr(p.parseExpr(flags))) for p.tok == token.COMMA { p.next() list = append(list, p.checkExpr(p.parseExpr(flags&flagInLHS))) // clear all but flagInLHS } return } // flags support flagAllowCmd func (p *parser) parseLHSList(flags int) []ast.Expr { old := p.inRHS p.inRHS = false list := p.parseExprList(flags | flagInLHS) switch p.tok { case token.DEFINE: // lhs of a short variable declaration // but doesn't enter scope until later: // caller must call p.shortVarDecl(p.makeIdentList(list)) // at appropriate time. case token.COLON: // lhs of a label declaration or a communication clause of a select // statement (parseLhsList is not called when parsing the case clause // of a switch statement): // - labels are declared by the caller of parseLhsList // - for communication clauses, if there is a stand-alone identifier // followed by a colon, we have a syntax error; there is no need // to resolve the identifier in that case default: // identifiers must be declared elsewhere for _, x := range list { p.resolve(x) } } p.inRHS = old return list } func (p *parser) parseRHSList() []ast.Expr { old := p.inRHS p.inRHS = true list := p.parseExprList(0) p.inRHS = old return list } // ---------------------------------------------------------------------------- // Types func (p *parser) parseType() ast.Expr { if p.trace { defer un(trace(p, "Type")) } typ := p.tryType() if typ == nil { pos := p.pos p.errorExpected(pos, "type", 2) p.advance(exprEnd) return &ast.BadExpr{From: pos, To: p.pos} } return typ } // If the result is an identifier, it is not resolved. func (p *parser) parseTypeName(ident *ast.Ident) ast.Expr { if p.trace { defer un(trace(p, "TypeName")) } if ident == nil { ident = p.parseIdent() } // don't resolve ident yet - it may be a parameter or field name if p.tok == token.PERIOD { // ident is a package name p.next() p.resolve(ident) sel := p.parseIdent() return &ast.SelectorExpr{X: ident, Sel: sel} } return ident } const ( stateArrayTypeOrSliceLit = iota stateTypeOrSliceOp stateType ) const ( resultNone = 0 resultArrayType = 1 << iota resultSliceLit resultSliceOp resultComprehensionExpr resultParenType resultType resultIdent // expr or type resultExprFlags = resultSliceLit | resultSliceOp | resultComprehensionExpr resultTypeFlags = resultArrayType | resultParenType | resultType ) // state = stateArrayTypeOrSliceLit | stateTypeOrSliceOp | stateType | ... func (p *parser) parseArrayTypeOrSliceLit(state int, slice ast.Expr) (expr ast.Expr, result int) { if p.trace { defer un(trace(p, "ArrayType")) } lbrack := p.expect(token.LBRACK) p.exprLev++ var len ast.Expr // always permit ellipsis for more fault-tolerant parsing if p.tok == token.ELLIPSIS { len = &ast.Ellipsis{Ellipsis: p.pos} p.next() } else if p.tok != token.RBRACK { len = p.parseRHS() switch state { case stateArrayTypeOrSliceLit: switch p.tok { case token.COMMA: // [a, b, c, d ...] sliceLit := p.parseSliceOrMatrixLit(lbrack, len) p.exprLev-- return sliceLit, resultSliceLit case token.FOR: // [expr for k, v in container if cond] phrases := p.parseForPhrases() p.exprLev-- rbrack := p.expect(token.RBRACK) if debugParseOutput { log.Printf("ast.ComprehensionExpr{Tok: [, Elt: %v, Fors: %v}\n", len, phrases) } return &ast.ComprehensionExpr{ Lpos: lbrack, Tok: token.LBRACK, Elt: len, Fors: phrases, Rpos: rbrack, }, resultComprehensionExpr } case stateTypeOrSliceOp: switch p.tok { case token.COLON: // slice[i:j:k] return p.parseIndexOrSliceContinue(slice, lbrack, len), resultSliceOp } } } p.exprLev-- rbrack := p.expect(token.RBRACK) var elt ast.Expr switch state { case stateType: elt = p.parseType() case stateArrayTypeOrSliceLit: sliceLit := newSliceLit(lbrack, rbrack, len) elt, result = p.tryIdentOrType(stateTypeOrSliceOp, sliceLit) switch result { case resultNone: if debugParseOutput { log.Printf("ast.SliceLit{Elts: %v}\n", sliceLit.Elts) } return sliceLit, resultSliceLit case resultSliceOp: return elt, resultSliceOp } p.resolve(elt) case stateTypeOrSliceOp: elt = p.tryType() if elt == nil { if len == nil { log.Panicln("TODO: expect slice index") } if debugParseOutput { log.Printf("ast.IndexExpr{X: %v, Index: %v}\n", slice, len) } return &ast.IndexExpr{X: slice, Index: len}, resultSliceOp } default: panic("parseArrayTypeOrSliceLit: unexpected state") } if debugParseOutput { log.Printf("ast.ArrayType{Len: %v, Elt: %v}\n", len, elt) } return &ast.ArrayType{Lbrack: lbrack, Len: len, Elt: elt}, resultArrayType } // [first, ...] // [first, a2, a2, ...; b1, b2, b3, ...] func (p *parser) parseSliceOrMatrixLit(lbrack token.Pos, first ast.Expr) ast.Expr { var mat [][]ast.Expr elts := make([]ast.Expr, 1, 8) elts[0] = first for { switch p.tok { case token.COMMA: case token.SEMICOLON: mat = append(mat, elts) elts = make([]ast.Expr, 0, len(elts)) case token.ELLIPSIS: n := len(elts) elts[n-1] = &ast.ElemEllipsis{Ellipsis: p.pos, Elt: elts[n-1]} p.next() continue default: goto done } p.next() if p.tok != token.RBRACK { elt := p.parseRHS() elts = append(elts, elt) } } done: rbrack := p.expect(token.RBRACK) if mat != nil { if len(elts) > 0 { mat = append(mat, elts) } return &ast.MatrixLit{Lbrack: lbrack, Elts: mat, Rbrack: rbrack} } return &ast.SliceLit{Lbrack: lbrack, Elts: elts, Rbrack: rbrack} } func newSliceLit(lbrack, rbrack token.Pos, len ast.Expr) *ast.SliceLit { var elts []ast.Expr if len != nil { elts = []ast.Expr{len} } return &ast.SliceLit{Lbrack: lbrack, Elts: elts, Rbrack: rbrack} } func (p *parser) parseFieldDecl(scope *ast.Scope) *ast.Field { if p.trace { defer un(trace(p, "FieldDecl")) } doc := p.leadComment var names []*ast.Ident var typ ast.Expr switch p.tok { case token.IDENT: name := p.parseIdent() if p.tok == token.PERIOD || p.tok == token.STRING || p.tok == token.SEMICOLON || p.tok == token.RBRACE { // embedded type typ = name if p.tok == token.PERIOD { typ = p.parseQualifiedIdent(name) } } else { // name1, name2, ... T names = []*ast.Ident{name} for p.tok == token.COMMA { p.next() names = append(names, p.parseIdent()) } // Careful dance: We don't know if we have an embedded instantiated // type T[P1, P2, ...] or a field T of array type []E or [P]E. if len(names) == 1 && p.tok == token.LBRACK { name, typ = p.parseArrayFieldOrTypeInstance(name, stateType) if name == nil { names = nil } } else { // T P typ = p.parseType() } } case token.MUL: star := p.pos p.next() if p.tok == token.LPAREN { // *(T) p.error(p.pos, "cannot parenthesize embedded type") p.next() typ = p.parseQualifiedIdent(nil) // expect closing ')' but no need to complain if missing if p.tok == token.RPAREN { p.next() } } else { // *T typ = p.parseQualifiedIdent(nil) } typ = &ast.StarExpr{Star: star, X: typ} case token.LPAREN: p.error(p.pos, "cannot parenthesize embedded type") p.next() if p.tok == token.MUL { // (*T) star := p.pos p.next() typ = &ast.StarExpr{Star: star, X: p.parseQualifiedIdent(nil)} } else { // (T) typ = p.parseQualifiedIdent(nil) } // expect closing ')' but no need to complain if missing if p.tok == token.RPAREN { p.next() } default: pos := p.pos p.errorExpected(pos, "field name or embedded type", 2) p.advance(exprEnd) typ = &ast.BadExpr{From: pos, To: p.pos} } var tag *ast.BasicLit if p.tok == token.STRING { tag = &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit} p.next() } p.expectSemi() field := &ast.Field{Doc: doc, Names: names, Type: typ, Tag: tag, Comment: p.lineComment} p.declare(field, nil, scope, ast.Var, names...) return field } func (p *parser) parseStructType() *ast.StructType { if p.trace { defer un(trace(p, "StructType")) } pos := p.expect(token.STRUCT) lbrace := p.expect(token.LBRACE) scope := ast.NewScope(nil) // struct scope var list []*ast.Field for p.tok == token.IDENT || p.tok == token.MUL || p.tok == token.LPAREN { // a field declaration cannot start with a '(' but we accept // it here for more robust parsing and better error messages // (parseFieldDecl will check and complain if necessary) list = append(list, p.parseFieldDecl(scope)) } rbrace := p.expect(token.RBRACE) return &ast.StructType{ Struct: pos, Fields: &ast.FieldList{ Opening: lbrace, List: list, Closing: rbrace, }, } } func (p *parser) parsePointerType() *ast.StarExpr { if p.trace { defer un(trace(p, "PointerType")) } star := p.expect(token.MUL) base := p.parseType() return &ast.StarExpr{Star: star, X: base} } type field struct { name *ast.Ident typ ast.Expr optional token.Pos } func (p *parser) parseParameterList(scope *ast.Scope, name0 *ast.Ident, typ0 ast.Expr, closing token.Token) (params []*ast.Field) { if p.trace { defer un(trace(p, "ParameterList")) } // Type parameters are the only parameter list closed by ']'. tparams := closing == token.RBRACK // Type set notation is ok in type parameter lists. pos := p.pos if name0 != nil { pos = name0.Pos() } var list []field var named int // number of parameters that have an explicit name and type for name0 != nil || p.tok != closing && p.tok != token.EOF { var par field if typ0 != nil { par = field{name: name0, typ: typ0, optional: token.NoPos} } else { par = p.parseParamDecl(name0) } name0 = nil // 1st name was consumed if present typ0 = nil // 1st typ was consumed if present if par.name != nil || par.typ != nil { list = append(list, par) if par.name != nil && par.typ != nil { named++ } } if !p.atComma("parameter list", closing) { break } p.next() } if len(list) == 0 { return // not uncommon } // TODO(gri) parameter distribution and conversion to []*ast.Field // can be combined and made more efficient // distribute parameter types if named == 0 { // all unnamed => found names are type names for i := 0; i < len(list); i++ { par := &list[i] if typ := par.name; typ != nil { par.typ = typ par.name = nil } } if tparams { p.error(pos, "type parameters must be named") } } else if named != len(list) { // some named => all must be named ok := true var typ ast.Expr missingName := pos for i := len(list) - 1; i >= 0; i-- { if par := &list[i]; par.typ != nil { typ = par.typ if par.name == nil { ok = false missingName = par.typ.Pos() n := ast.NewIdent("_") n.NamePos = typ.Pos() // correct position par.name = n } } else if typ != nil { par.typ = typ } else { // par.typ == nil && typ == nil => we only have a par.name ok = false missingName = par.name.Pos() par.typ = &ast.BadExpr{From: par.name.Pos(), To: p.pos} } } if !ok { if tparams { p.error(missingName, "type parameters must be named") } else { p.error(pos, "mixed named and unnamed parameters") } } } // convert list []*ast.Field if named == 0 { // parameter list consists of types only for _, par := range list { assert(par.typ != nil, "nil type in unnamed parameter list") params = append(params, &ast.Field{Type: par.typ, Optional: par.optional}) } return } // parameter list consists of named parameters with types var names []*ast.Ident var typ ast.Expr var optional token.Pos addParams := func() { assert(typ != nil, "nil type in named parameter list") field := &ast.Field{Names: names, Type: typ, Optional: optional} // Go spec: The scope of an identifier denoting a function // parameter or result variable is the function body. p.declare(field, nil, scope, ast.Var, names...) params = append(params, field) names = nil } for _, par := range list { if par.typ != typ || par.optional != optional { if len(names) > 0 { addParams() } typ = par.typ optional = par.optional } names = append(names, par.name) } if len(names) > 0 { addParams() } return } func (p *parser) parseParamDecl(name *ast.Ident) (f field) { // TODO(rFindley) refactor to be more similar to paramDeclOrNil in the syntax // package if p.trace { defer un(trace(p, "ParamDeclOrNil")) } ptok := p.tok if name != nil { p.tok = token.IDENT // force token.IDENT case in switch below } switch p.tok { case token.IDENT: // name if name != nil { f.name = name p.tok = ptok } else { f.name = p.parseIdent() } switch p.tok { case token.IDENT, token.MUL, token.ARROW, token.FUNC, token.CHAN, token.MAP, token.STRUCT, token.INTERFACE, token.LPAREN: // name type f.typ = p.parseType() case token.LBRACK: // name "[" type1, ..., typeN "]" or name "[" n "]" type f.name, f.typ = p.parseArrayFieldOrTypeInstance(f.name, stateType) case token.ELLIPSIS: // name "..." type f.typ = p.parseDotsType() return // don't allow ...type "|" ... case token.PERIOD: // name "." ... f.typ = p.parseQualifiedIdent(f.name) f.name = nil } case token.MUL, token.ARROW, token.FUNC, token.LBRACK, token.CHAN, token.MAP, token.STRUCT, token.INTERFACE, token.LPAREN: // type f.typ = p.parseType() case token.ELLIPSIS: // "..." type // (always accepted) f.typ = p.parseDotsType() return // don't allow ...type "|" ... default: // TODO(rfindley): this is incorrect in the case of type parameter lists // (should be "']'" in that case) p.errorExpected(p.pos, "')'", 2) p.advance(exprEnd) } if p.tok == token.QUESTION { f.optional = p.pos p.next() } return } func (p *parser) parseQualifiedIdent(ident *ast.Ident) ast.Expr { if p.trace { defer un(trace(p, "QualifiedIdent")) } typ := p.parseTypeName(ident) if p.tok == token.LBRACK { typ = p.parseTypeInstance(typ) } return typ } func (p *parser) parseDotsType() *ast.Ellipsis { if p.trace { defer un(trace(p, "DotsType")) } pos := p.expect(token.ELLIPSIS) elt := p.parseType() return &ast.Ellipsis{Ellipsis: pos, Elt: elt} } func (p *parser) parseParameters(scope *ast.Scope, ellipsisOk bool) *ast.FieldList { if p.trace { defer un(trace(p, "Parameters")) } _ = ellipsisOk var params []*ast.Field lparen := p.expect(token.LPAREN) if p.tok != token.RPAREN { params = p.parseParameterList(scope, nil, nil, token.RPAREN) } rparen := p.expect(token.RPAREN) return &ast.FieldList{Opening: lparen, List: params, Closing: rparen} } func (p *parser) parseResult(scope *ast.Scope) *ast.FieldList { if p.trace { defer un(trace(p, "Result")) } if p.tok == token.LPAREN { return p.parseParameters(scope, false) } typ := p.tryType() if typ != nil { list := make([]*ast.Field, 1) list[0] = &ast.Field{Type: typ} return &ast.FieldList{List: list} } return nil } func (p *parser) parseSignature(scope *ast.Scope) (params, results *ast.FieldList) { if p.trace { defer un(trace(p, "Signature")) } params = p.parseParameters(scope, true) results = p.parseResult(scope) return } func (p *parser) parseFuncType() (*ast.FuncType, *ast.Scope) { if p.trace { defer un(trace(p, "FuncType")) } pos := p.expect(token.FUNC) scope := ast.NewScope(p.topScope) // function scope params, results := p.parseSignature(scope) return &ast.FuncType{Func: pos, Params: params, Results: results}, scope } func (p *parser) parseMethodSpec(scope *ast.Scope) *ast.Field { if p.trace { defer un(trace(p, "MethodSpec")) } doc := p.leadComment var idents []*ast.Ident var typ ast.Expr x := p.parseTypeName(nil) if ident, isIdent := x.(*ast.Ident); isIdent && p.tok == token.LPAREN { // method idents = []*ast.Ident{ident} scope := ast.NewScope(nil) // method scope params, results := p.parseSignature(scope) typ = &ast.FuncType{Func: token.NoPos, Params: params, Results: results} } else { // embedded interface typ = x p.resolve(typ) } p.expectSemi() // call before accessing p.linecomment spec := &ast.Field{Doc: doc, Names: idents, Type: typ, Comment: p.lineComment} p.declare(spec, nil, scope, ast.Fun, idents...) return spec } func (p *parser) parseInterfaceType() *ast.InterfaceType { if p.trace { defer un(trace(p, "InterfaceType")) } pos := p.expect(token.INTERFACE) lbrace := p.expect(token.LBRACE) scope := ast.NewScope(nil) // interface scope var list []*ast.Field for p.tok == token.IDENT { list = append(list, p.parseMethodSpec(scope)) } rbrace := p.expect(token.RBRACE) return &ast.InterfaceType{ Interface: pos, Methods: &ast.FieldList{ Opening: lbrace, List: list, Closing: rbrace, }, } } func (p *parser) parseMapType() *ast.MapType { if p.trace { defer un(trace(p, "MapType")) } pos := p.expect(token.MAP) p.expect(token.LBRACK) key := p.parseType() p.expect(token.RBRACK) value := p.parseType() if debugParseOutput { log.Printf("ast.MapType{Key: %v, Value: %v}\n", key, value) } return &ast.MapType{Map: pos, Key: key, Value: value} } func (p *parser) parseChanType() *ast.ChanType { if p.trace { defer un(trace(p, "ChanType")) } pos := p.pos dir := ast.SEND | ast.RECV var arrow token.Pos if p.tok == token.CHAN { p.next() if p.tok == token.ARROW { arrow = p.pos p.next() dir = ast.SEND } } else { arrow = p.expect(token.ARROW) p.expect(token.CHAN) dir = ast.RECV } value := p.parseType() return &ast.ChanType{Begin: pos, Arrow: arrow, Dir: dir, Value: value} } func (p *parser) parseTypeInstance(typ ast.Expr) ast.Expr { if p.trace { defer un(trace(p, "TypeInstance")) } opening := p.expect(token.LBRACK) p.exprLev++ var list []ast.Expr for p.tok != token.RBRACK && p.tok != token.EOF { list = append(list, p.parseType()) if !p.atComma("type argument list", token.RBRACK) { break } p.next() } p.exprLev-- closing := p.expectClosing(token.RBRACK, "type argument list") if len(list) == 0 { p.errorExpected(closing, "type argument list", 2) return &ast.IndexExpr{ X: typ, Lbrack: opening, Index: &ast.BadExpr{From: opening + 1, To: closing}, Rbrack: closing, } } return packIndexExpr(typ, opening, list, closing) } func (p *parser) parseArrayFieldOrTypeInstance(x *ast.Ident, state int) (*ast.Ident, ast.Expr) { if p.trace { defer un(trace(p, "ArrayFieldOrTypeInstance")) } lbrack := p.expect(token.LBRACK) trailingComma := token.NoPos // if valid, the position of a trailing comma preceding the ']' var args []ast.Expr if p.tok != token.RBRACK { p.exprLev++ args = append(args, p.parseRHS()) for p.tok == token.COMMA { comma := p.pos p.next() if p.tok == token.RBRACK { trailingComma = comma break } args = append(args, p.parseRHS()) } p.exprLev-- } rbrack := p.expect(token.RBRACK) if len(args) == 0 { // x []E elt := p.parseType() return x, &ast.ArrayType{Lbrack: lbrack, Elt: elt} } // x [P]E or x[P] if len(args) == 1 { elt, _ := p.tryIdentOrType(state, nil) if elt != nil { // x [P]E if trailingComma.IsValid() { // Trailing commas are invalid in array type fields. p.error(trailingComma, "unexpected comma; expecting ]") } return x, &ast.ArrayType{Lbrack: lbrack, Len: args[0], Elt: elt} } } // x[P], x[P1, P2], ... return nil, packIndexExpr(x, lbrack, args, rbrack) } // state = stateArrayTypeOrSliceLit | stateTypeOrSliceOp | stateType | ... // If the result is an identifier, it is not resolved. func (p *parser) tryIdentOrType(state int, len ast.Expr) (ast.Expr, int) { switch p.tok { case token.IDENT: typ := p.parseTypeName(nil) if p.tok == token.LBRACK { typ = p.parseTypeInstance(typ) } return typ, resultIdent case token.LBRACK: return p.parseArrayTypeOrSliceLit(state, len) case token.STRUCT: return p.parseStructType(), resultType case token.MUL: return p.parsePointerType(), resultType case token.FUNC: typ, _ := p.parseFuncType() return typ, resultType case token.INTERFACE: return p.parseInterfaceType(), resultType case token.MAP: return p.parseMapType(), resultType case token.CHAN, token.ARROW: return p.parseChanType(), resultType case token.LPAREN: return p.parseTupleType() } // no type found return nil, resultNone } // parseTupleType parses a tuple type or parenthesized type expression. // // Syntax: // // () - empty tuple, equivalent to struct{} // (T) - parenthesized type, degenerates to T (not a tuple) // (T1, T2, ..., TN) - anonymous tuple type // (name1 T1, name2 T2, ...) - named tuple type (names are compile-time only) // (x, y T) - type shorthand, equivalent to (x T, y T) // // Returns the parsed type and result code. func (p *parser) parseTupleType() (ast.Expr, int) { if p.trace { defer un(trace(p, "TupleType")) } lparen := p.pos p.next() // consume '(' // Parse tuple fields similar to parameter list parsing fields := p.parseTupleFieldList() rparen := p.expect(token.RPAREN) // Single-element tuple with no name degenerates to the type itself // (T) is just a parenthesized type expression, not a tuple if len(fields) == 1 && len(fields[0].Names) == 0 { return &ast.ParenExpr{Lparen: lparen, X: fields[0].Type, Rparen: rparen}, resultParenType } // Multi-element or named tuple return &ast.TupleType{ Lparen: lparen, Fields: &ast.FieldList{Opening: lparen, List: fields, Closing: rparen}, Rparen: rparen, }, resultType } // parseTupleFieldList parses a comma-separated list of tuple fields. // Each field can be: // - A type alone: int, string, etc. // - A named field: x int, name string, etc. // - Multiple names with shared type: x, y int (shorthand for x int, y int) // // The function handles type distribution similar to Go's function parameter syntax. func (p *parser) parseTupleFieldList() (params []*ast.Field) { if p.trace { defer un(trace(p, "TupleFieldList")) } // Use shared field struct for type distribution var list []field var named int // count of fields with explicit name and type for p.tok != token.RPAREN && p.tok != token.EOF { var f field switch p.tok { case token.IDENT: // Could be a name or a type f.name = p.parseIdent() // Check what follows the identifier switch p.tok { case token.IDENT, token.MUL, token.ARROW, token.FUNC, token.CHAN, token.MAP, token.STRUCT, token.INTERFACE, token.LPAREN: // name followed by type: "x int" or "x *int" etc. f.typ = p.parseType() named++ case token.LBRACK: // Could be "name [n]T" or "name []T" or just an array/slice type f.name, f.typ = p.parseArrayFieldOrTypeInstance(f.name, stateType) if f.typ != nil { named++ } case token.PERIOD: // Qualified type name: "pkg.Type" f.typ = p.parseQualifiedIdent(f.name) f.name = nil default: // Just an identifier - could be a type name or a field name // Will be resolved later during type distribution } case token.MUL, token.ARROW, token.FUNC, token.LBRACK, token.CHAN, token.MAP, token.STRUCT, token.INTERFACE, token.LPAREN: // Type without name f.typ = p.parseType() } if f.typ == nil && f.name == nil { break // Not a valid tuple field, stop parsing } list = append(list, f) if !p.atComma("tuple type", token.RPAREN) { break } p.next() // consume ',' } if len(list) == 0 { return nil } // distribute parameter types if named == 0 { // all unnamed => found names are type names for i := 0; i < len(list); i++ { par := &list[i] if typ := par.name; typ != nil { par.typ = typ par.name = nil } } } else if named != len(list) { // some named => all must be named ok := true var typ ast.Expr for i := len(list) - 1; i >= 0; i-- { if par := &list[i]; par.typ != nil { typ = par.typ if par.name == nil { ok = false n := ast.NewIdent("_") n.NamePos = typ.Pos() // correct position par.name = n } } else if typ != nil { par.typ = typ } else { // par.typ == nil && typ == nil => we only have a par.name ok = false par.typ = &ast.BadExpr{From: par.name.Pos(), To: p.pos} } } if !ok { p.error(list[0].name.Pos(), "mixed named and unnamed fields in tuple type") } } // convert list []*ast.Field if named == 0 { // parameter list consists of types only for _, par := range list { assert(par.typ != nil, "nil type in unnamed field list") params = append(params, &ast.Field{Type: par.typ}) } return } // parameter list consists of named parameters with types var names []*ast.Ident var typ ast.Expr addFields := func() { assert(typ != nil, "nil type in named field list") field := &ast.Field{Names: names, Type: typ} params = append(params, field) names = nil } for _, par := range list { if par.typ != typ { if len(names) > 0 { addFields() } typ = par.typ } names = append(names, par.name) } if len(names) > 0 { addFields() } return } func (p *parser) tryType() ast.Expr { typ, _ := p.tryIdentOrType(stateType, nil) if typ != nil { p.resolve(typ) } return typ } // ---------------------------------------------------------------------------- // Blocks func (p *parser) parseStmtList() (list []ast.Stmt) { if p.trace { defer un(trace(p, "StatementList")) } for p.tok != token.CASE && p.tok != token.DEFAULT && p.tok != token.RBRACE && p.tok != token.EOF { list = append(list, p.parseStmt(flagAllowCmd)) } return } func (p *parser) parseBody(scope *ast.Scope) *ast.BlockStmt { if p.trace { defer un(trace(p, "Body")) } lbrace := p.expect(token.LBRACE) p.topScope = scope // open function scope p.openLabelScope() list := p.parseStmtList() p.closeLabelScope() p.closeScope() rbrace := p.expect2(token.RBRACE) return &ast.BlockStmt{Lbrace: lbrace, List: list, Rbrace: rbrace} } func (p *parser) parseBlockStmt() *ast.BlockStmt { if p.trace { defer un(trace(p, "BlockStmt")) } lbrace := p.expect(token.LBRACE) p.openScope() list := p.parseStmtList() p.closeScope() rbrace := p.expect2(token.RBRACE) return &ast.BlockStmt{Lbrace: lbrace, List: list, Rbrace: rbrace} } // ---------------------------------------------------------------------------- // Expressions func (p *parser) parseFuncTypeOrLit() ast.Expr { if p.trace { defer un(trace(p, "FuncTypeOrLit")) } typ, scope := p.parseFuncType() if p.tok != token.LBRACE { // function type only return typ } p.exprLev++ body := p.parseBody(scope) p.exprLev-- return &ast.FuncLit{Type: typ, Body: body} } func (p *parser) stringLit(pos token.Pos, val string) *ast.StringLitEx { parts := p.stringLitEx(nil, pos+1, val[1:len(val)-1]) if parts != nil { return &ast.StringLitEx{Parts: parts} } return nil } func (p *parser) stringLitEx(parts []any, pos token.Pos, text string) []any { extra := false loop: at := strings.IndexByte(text, '$') if at < 0 || at+1 == len(text) { // no '$' or end with '$' if extra { goto normal } return nil } switch text[at+1] { case '{': // ${ from := at + 2 left := text[from:] if left == "" { // "...${" (string end with "${") goto normal } end := strings.IndexByte(left, '}') if end < 0 { p.error(pos+token.Pos(at+1), "invalid $ expression: ${ doesn't end with }") goto normal } if at != 0 { parts = append(parts, text[:at]) } to := pos + token.Pos(from+end) parts = p.stringLitExpr(parts, pos+token.Pos(from), to) pos = to + 1 text = left[end+1:] case '$': // $$ parts = append(parts, text[:at+2]) pos += token.Pos(at + 2) text = text[at+2:] default: if extra || hasExtra(text[at+1:]) { p.error(pos+token.Pos(at), "invalid $ expression: neither `${ ... }` nor `$$`") } return nil } if text != "" { extra = true goto loop } return parts normal: parts = append(parts, text) return parts } func hasExtra(text string) bool { for { at := strings.IndexByte(text, '$') if at < 0 || at+1 == len(text) { // no '$' or end with '$' return false } ch := text[at+1] if ch == '{' || ch == '$' { return true } text = text[at+2:] } } func (p *parser) stringLitExpr(parts []any, off, end token.Pos) []any { file := p.file base := file.Base() src := p.scanner.CodeTo(int(end) - base) expr, err := ParseExprEx(file, src, int(off)-base, 0) if err != nil { p.errors = append(p.errors, err...) expr = &ast.BadExpr{From: off, To: end} } parts = append(parts, expr) return parts } func (p *parser) domainTextLitEx(off, end token.Pos) *ast.DomainTextLitEx { file := p.file base := file.Base() src := p.scanner.CodeTo(int(end) - base) var args []ast.Expr var sp parser sp.initSub(file, src, int(off)-base, 0) for { expr := sp.parseRHS() args = append(args, expr) if sp.tok != token.COMMA { break } sp.next() } sp.expect(token.SEMICOLON) return &ast.DomainTextLitEx{ Args: args, RawPos: sp.pos, Raw: string(src[int(sp.pos)-base:]), } } func (p *parser) tplLit(off, end token.Pos) any { file := p.file base := file.Base() src := p.scanner.CodeTo(int(end) - base) expr, err := tpl.ParseEx(file, src, int(off)-base, &tpl.Config{ ParseRetProc: parseTplRetProc, }) if err != nil { p.errors = append(p.errors, err...) return nil } return expr } func parseTplRetProc(file *token.File, src []byte, offset int) (tplast.Node, scanner.ErrorList) { return ParseExprEx(file, src, offset, 0) } // parseOperand may return an expression or a raw type (incl. array // types of the form [...]T. Callers must verify the result. // If lhs is set and the result is an identifier, it is not resolved. // flags support flagInLHS, flagAllowCmd func (p *parser) parseOperand(flags int) (x ast.Expr, exprKind int) { if p.trace { defer un(trace(p, "Operand")) } switch p.tok { case token.IDENT: ident := p.parseIdent() if p.tok == token.STRING && p.pos == ident.End() && p.lit[0] == '`' { // domain text: tpl`...` var pos, lit = p.pos, p.lit var extra any if ident.Name == "tpl" { extra = p.tplLit(pos+1, pos+token.Pos(len(lit))-1) } else if strings.HasPrefix(lit, "`> ") { // domainTag`> ...` extra = p.domainTextLitEx(pos+3, pos+token.Pos(len(lit))-1) } x = &ast.DomainTextLit{ Domain: ident, ValuePos: pos, Value: lit, Extra: extra, } if debugParseOutput { log.Printf("ast.DomainTextLit{Domain: %s, Value: %s}\n", ident.Name, lit) } p.next() } else { x = ident if flags&flagInLHS == 0 { // not inLHS p.resolve(x) } } return case token.STRING, token.CSTRING, token.PYSTRING, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.RAT: bl := &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit} if p.tok == token.STRING && len(p.lit) > 1 { bl.Extra = p.stringLit(p.pos, p.lit) } p.next() if p.tok == token.UNIT { nu := &ast.NumberUnitLit{ ValuePos: bl.ValuePos, Kind: bl.Kind, Value: bl.Value, Unit: p.lit, } x = nu if debugParseOutput { log.Printf("ast.NumberUnitLit{Kind: %v, Value: %v, Unit: %v}\n", nu.Kind, nu.Value, nu.Unit) } p.next() } else { x = bl if debugParseOutput { log.Printf("ast.BasicLit{Kind: %v, Value: %v}\n", bl.Kind, bl.Value) } } return case token.LPAREN: lparen := p.pos p.next() if p.tok == token.RPAREN { // () => expr p.next() return &ast.TupleLit{Lparen: lparen, Rparen: p.pos}, exprTuple } p.exprLev++ x = p.parseRHSOrType() // types may be parenthesized: (some type) if p.tok == token.COMMA || p.tok == token.ELLIPSIS { // (x, y, ...) => expr items := make([]ast.Expr, 1, 2) items[0] = x for p.tok == token.COMMA { p.next() items = append(items, p.parseRHSOrType()) } t := &ast.TupleLit{Lparen: lparen, Elts: items, Rparen: p.pos} if p.tok == token.ELLIPSIS { t.Ellipsis = p.pos p.next() } p.exprLev-- p.expect(token.RPAREN) return t, exprTuple } p.exprLev-- rparen := p.expect(token.RPAREN) if debugParseOutput { log.Printf("ast.ParenExpr{X: %v}\n", x) } return &ast.ParenExpr{Lparen: lparen, X: x, Rparen: rparen}, 0 case token.FUNC: return p.parseFuncTypeOrLit(), 0 case token.LBRACE: if flags&flagInLHS == 0 { // in RHS: mapLit - {k1: v1, k2: v2, ...} return p.parseLiteralValueOrMapComprehension(), 0 } case token.MAP: oldpos, oldlit := p.pos, p.lit // XGo: save token to allow map() as a function p.next() pos, tok := p.pos, p.tok p.unget(oldpos, token.MAP, oldlit) if tok == token.LBRACK && (flags&flagAllowCmd == 0 || oldpos+3 == pos) { break } fallthrough case token.GOTO, token.TYPE, token.BREAK, token.CONTINUE, token.FALLTHROUGH: // token.RANGE, token.IMPORT, token.SELECT, token.INTERFACE: // XGo: allow goto() as a function p.tok = token.IDENT x = p.parseIdent() if flags&flagInLHS == 0 { // in RHS p.resolve(x) } return case token.ENV: return p.parseEnvExpr(), 0 } typ, result := p.tryIdentOrType(stateArrayTypeOrSliceLit, nil) if (result & resultExprFlags) != 0 { // is an expr, not a type return typ, 0 } if typ != nil { // could be type for composite literal or conversion _, isIdent := typ.(*ast.Ident) assert(!isIdent, "type cannot be identifier") return typ, 0 } // we have an error pos := p.pos p.errorExpected(pos, "operand", 2) p.advance(stmtStart) return &ast.BadExpr{From: pos, To: p.pos}, 0 } func (p *parser) parseEnvExpr() (ret *ast.EnvExpr) { if p.trace { defer un(trace(p, "EnvExpr")) } ret = &ast.EnvExpr{TokPos: p.pos} p.next() switch p.tok { case token.LBRACE: // ${name} ret.Lbrace = p.pos p.next() ret.Name = p.parseIdent() ret.Rbrace = p.expect(token.RBRACE) case token.STRING: // $"attr-name" ret.Name = &ast.Ident{NamePos: p.pos, Name: p.lit} p.next() default: // $name ret.Name = p.parseIdent() } return } func (p *parser) parseSelector(x ast.Expr) ast.Expr { if p.trace { defer un(trace(p, "Selector")) } sel := p.parseIdent() return &ast.SelectorExpr{X: x, Sel: sel} } func (p *parser) parseTypeAssertion(x ast.Expr) ast.Expr { if p.trace { defer un(trace(p, "TypeAssertion")) } lparen := p.expect(token.LPAREN) var typ ast.Expr if p.tok == token.TYPE { // type switch: typ == nil p.next() } else { typ = p.parseType() } rparen := p.expect(token.RPAREN) return &ast.TypeAssertExpr{X: x, Type: typ, Lparen: lparen, Rparen: rparen} } func (p *parser) parseIndexOrSlice(x ast.Expr) ast.Expr { if p.trace { defer un(trace(p, "IndexOrSlice")) } lbrack := p.expect(token.LBRACK) p.exprLev++ var idx ast.Expr if p.tok != token.COLON { idx = p.parseRHS() } return p.parseIndexOrSliceContinue(x, lbrack, idx) } func (p *parser) parseIndexOrSliceContinue(x ast.Expr, lbrack token.Pos, idx ast.Expr) ast.Expr { const N = 3 // change the 3 to 2 to disable 3-index slices var args []ast.Expr var index [N]ast.Expr var colons [N - 1]token.Pos if idx != nil { index[0] = idx } ncolons := 0 switch p.tok { case token.COLON: // slice expression for p.tok == token.COLON && ncolons < len(colons) { colons[ncolons] = p.pos ncolons++ p.next() if p.tok != token.COLON && p.tok != token.RBRACK && p.tok != token.EOF { index[ncolons] = p.parseRHS() } } case token.COMMA: // instance expression args = append(args, index[0]) for p.tok == token.COMMA { p.next() if p.tok != token.RBRACK && p.tok != token.EOF { args = append(args, p.parseType()) } } } p.exprLev-- rbrack := p.expect(token.RBRACK) if ncolons > 0 { // slice expression slice3 := false if ncolons == 2 { slice3 = true // Check presence of middle and final index here rather than during type-checking // to prevent erroneous programs from passing through gofmt (was issue 7305). if index[1] == nil { p.error(colons[0], "middle index required in 3-index slice") index[1] = &ast.BadExpr{From: colons[0] + 1, To: colons[1]} } if index[2] == nil { p.error(colons[1], "final index required in 3-index slice") index[2] = &ast.BadExpr{From: colons[1] + 1, To: rbrack} } } return &ast.SliceExpr{X: x, Lbrack: lbrack, Low: index[0], High: index[1], Max: index[2], Slice3: slice3, Rbrack: rbrack} } if len(args) == 0 { if debugParseOutput { log.Printf("ast.IndexExpr{X: %v, Index: %v}\n", x, index[0]) } // index expression return &ast.IndexExpr{X: x, Lbrack: lbrack, Index: index[0], Rbrack: rbrack} } // instance expression return packIndexExpr(x, lbrack, args, rbrack) } func packIndexExpr(x ast.Expr, lbrack token.Pos, exprs []ast.Expr, rbrack token.Pos) ast.Expr { switch len(exprs) { case 0: panic("internal error: packIndexExpr with empty expr slice") case 1: if debugParseOutput { log.Printf("ast.IndexExpr{X: %v, Index: %v}\n", x, exprs[0]) } return &ast.IndexExpr{ X: x, Lbrack: lbrack, Index: exprs[0], Rbrack: rbrack, } default: if debugParseOutput { log.Printf("ast.IndexListExpr{X: %v, Index: %v}\n", x, exprs) } return &ast.IndexListExpr{ X: x, Lbrack: lbrack, Indices: exprs, Rbrack: rbrack, } } } func (p *parser) parseCallOrConversion(fun ast.Expr, isCmd bool) *ast.CallExpr { if p.trace { defer un(trace(p, "CallOrConversion")) } var lparen, rparen token.Pos var endTok token.Token if isCmd { endTok = token.SEMICOLON } else { lparen, endTok = p.expect(token.LPAREN), token.RPAREN } p.exprLev++ var args []ast.Expr var kwargs []*ast.KwargExpr var ellipsis token.Pos for p.tok != endTok && p.tok != token.EOF && !ellipsis.IsValid() { flags := flagAllowKwargExpr expr, exprKind := p.parseRHSOrTypeEx(flags) if exprKind == exprKwarg { kwargs = append(kwargs, expr.(*ast.KwargExpr)) } else { if len(kwargs) > 0 { p.error(expr.Pos(), "positional argument follows keyword argument") } args = append(args, expr) // builtins may expect a type: make(some type, ...) if p.tok == token.ELLIPSIS { ellipsis = p.pos p.next() } } if isCmd && p.tok == token.RBRACE { break } if !p.atComma("argument list", endTok) { break } p.next() } p.exprLev-- var noParenEnd token.Pos if isCmd { noParenEnd = p.pos } else if rparen == token.NoPos { rparen = p.expectClosing(token.RPAREN, "argument list") } if debugParseOutput { log.Printf("ast.CallExpr{Fun: %v, Ellipsis: %v, isCmd: %v}\n", fun, ellipsis != 0, isCmd) } return &ast.CallExpr{ Fun: fun, Lparen: lparen, Args: args, Ellipsis: ellipsis, Kwargs: kwargs, Rparen: rparen, NoParenEnd: noParenEnd} } // flags support flagInLHS (keyOk = true) func (p *parser) parseValue(flags int) ast.Expr { if p.trace { defer un(trace(p, "Element")) } if p.tok == token.LBRACE { return p.parseLiteralValueOrMapComprehension() } // Because the parser doesn't know the composite literal type, it cannot // know if a key that's an identifier is a struct field name or a name // denoting a value. The former is not resolved by the parser or the // resolver. // // Instead, _try_ to resolve such a key if possible. If it resolves, // it a) has correctly resolved, or b) incorrectly resolved because // the key is a struct field with a name matching another identifier. // In the former case we are done, and in the latter case we don't // care because the type checker will do a separate field lookup. // // If the key does not resolve, it a) must be defined at the top // level in another file of the same package, the universe scope, or be // undeclared; or b) it is a struct field. In the former case, the type // checker can do a top-level lookup, and in the latter case it will do // a separate field lookup. x := p.checkExpr(p.parseExpr(flags)) if flags&flagInLHS != 0 { // keyOk if p.tok == token.COLON { // Try to resolve the key but don't collect it // as unresolved identifier if it fails so that // we don't get (possibly false) errors about // undeclared names. p.tryResolve(x, false) } else { // not a key p.resolve(x) } } return x } func (p *parser) parseElement() ast.Expr { if p.trace { defer un(trace(p, "Element")) } x := p.parseValue(flagInLHS) if p.tok == token.COLON { colon := p.pos p.next() x = &ast.KeyValueExpr{Key: x, Colon: colon, Value: p.parseValue(0)} } return x } // {k1: v1, k2: v2, ...} // {for k, v <- listOrMap, cond} // {expr for k, v <- listOrMap, cond} // {kexpr: vexpr for k, v <- listOrMap, cond} func (p *parser) parseLiteralValueOrMapComprehension() ast.Expr { if p.trace { defer un(trace(p, "LiteralValue")) } lbrace := p.expect(token.LBRACE) var elts []ast.Expr var mce *ast.ComprehensionExpr p.exprLev++ if p.tok != token.RBRACE { elts, mce = p.parseElementListOrComprehension() } p.exprLev-- rbrace := p.expectClosing(token.RBRACE, "composite literal") if mce != nil { mce.Lpos, mce.Rpos, mce.Tok = lbrace, rbrace, token.LBRACE return mce } return &ast.CompositeLit{Lbrace: lbrace, Elts: elts, Rbrace: rbrace} } func (p *parser) parseElementListOrComprehension() (list []ast.Expr, mce *ast.ComprehensionExpr) { if p.trace { defer un(trace(p, "ElementList")) } if p.tok == token.FOR { phrases := p.parseForPhrases() return nil, &ast.ComprehensionExpr{Fors: phrases} } for p.tok != token.RBRACE && p.tok != token.EOF { list = append(list, p.parseElement()) if p.tok == token.FOR { // for k, v <- container if len(list) != 1 { log.Panicln("TODO: invalid comprehension: too may elements.") } phrases := p.parseForPhrases() return nil, &ast.ComprehensionExpr{Elt: list[0], Fors: phrases} } if !p.atComma("composite literal", token.RBRACE) { break } p.next() } return } func (p *parser) parseElementList() (list []ast.Expr) { if p.trace { defer un(trace(p, "ElementList")) } for p.tok != token.RBRACE && p.tok != token.EOF { list = append(list, p.parseElement()) if !p.atComma("composite literal", token.RBRACE) { break } p.next() } return } func (p *parser) parseLiteralValue(typ ast.Expr) ast.Expr { if p.trace { defer un(trace(p, "LiteralValue")) } lbrace := p.expect(token.LBRACE) var elts []ast.Expr p.exprLev++ if p.tok != token.RBRACE { elts = p.parseElementList() } p.exprLev-- rbrace := p.expectClosing(token.RBRACE, "composite literal") return &ast.CompositeLit{Type: typ, Lbrace: lbrace, Elts: elts, Rbrace: rbrace} } // checkExpr checks that x is an expression (and not a type). func (p *parser) checkExpr(x ast.Expr) ast.Expr { switch unparen(x).(type) { case *ast.BadExpr: case *ast.Ident: case *ast.BasicLit: case *ast.FuncLit: case *ast.CompositeLit: case *ast.SliceLit: case *ast.ComprehensionExpr: case *ast.SelectorExpr: case *ast.AnySelectorExpr: case *ast.IndexExpr: case *ast.IndexListExpr: case *ast.ArrayType: case *ast.StructType: case *ast.InterfaceType: case *ast.FuncType: case *ast.MapType: case *ast.ChanType: case *ast.SliceExpr: case *ast.TypeAssertExpr: // If t.Type == nil we have a type assertion of the form // y.(type), which is only allowed in type switch expressions. // It's hard to exclude those but for the case where we are in // a type switch. Instead be lenient and test this in the type // checker. case *ast.CallExpr: case *ast.StarExpr: case *ast.UnaryExpr: case *ast.BinaryExpr: case *ast.RangeExpr: case *ast.ErrWrapExpr: case *ast.LambdaExpr: case *ast.LambdaExpr2: case *ast.TupleLit: case *ast.EnvExpr: case *ast.CondExpr: case *ast.ElemEllipsis: case *ast.NumberUnitLit: case *ast.DomainTextLit: default: // all other nodes are not proper expressions p.errorExpected(x.Pos(), "expression", 3) x = &ast.BadExpr{From: x.Pos(), To: p.safePos(x.End())} } return x } /* // If x is of the form *T, deref returns T, otherwise it returns x. func deref(x ast.Expr) ast.Expr { if p, isPtr := x.(*ast.StarExpr); isPtr { x = p.X } return x } */ // If x is of the form (T), unparen returns unparen(T), otherwise it returns x. func unparen(x ast.Expr) ast.Expr { if p, isParen := x.(*ast.ParenExpr); isParen { x = unparen(p.X) } return x } // checkExprOrType checks that x is an expression or a type // (and not a raw type such as [...]T). func (p *parser) checkExprOrType(x ast.Expr) ast.Expr { switch t := unparen(x).(type) { case *ast.ArrayType: if len, isEllipsis := t.Len.(*ast.Ellipsis); isEllipsis { p.error(len.Pos(), "expected array length, found '...'") x = &ast.BadExpr{From: x.Pos(), To: p.safePos(x.End())} } } // all other nodes are expressions or types return x } // If lhs is set and the result is an identifier, it is not resolved. // flags support flagInLHS, flagAllowCmd, flagAllowKwargExpr func (p *parser) parsePrimaryExpr(iden *ast.Ident, flags int) (x ast.Expr, exprKind int) { if p.trace { defer un(trace(p, "PrimaryExpr")) } if iden != nil { x = iden } else if x, exprKind = p.parseOperand(flags); exprKind > 0 { return } lhs := flags&flagInLHS != 0 allowCmd := flags&flagAllowCmd != 0 L: for { switch p.tok { case token.PERIOD: // . posDot := p.pos p.next() if lhs { p.resolve(x) } switch p.tok { case token.IDENT: x = p.parseSelector(p.checkExprOrType(x)) case token.LPAREN: x = p.parseTypeAssertion(p.checkExpr(x)) default: processed := posDot+1 == p.pos if processed { switch p.tok { case token.MUL: // .* .** posTok := p.pos p.next() if p.tok == token.MUL && p.pos == posDot+2 { // .** p.next() p.expect(token.PERIOD) sel := &ast.Ident{NamePos: p.pos} x = &ast.AnySelectorExpr{X: p.checkExpr(x), TokPos: posTok, Sel: sel} switch p.tok { case token.IDENT, token.STRING: // .**.name .**."name" sel.Name = p.lit p.next() case token.MUL: // .**.* sel.Name = "*" p.next() default: p.errorExpected(p.pos, "identifier after '**.'", 2) sel.Name = "_" if p.tok != token.RBRACE { // TODO(rFindley) p.next() // make progress } } } else { sel := &ast.Ident{NamePos: posTok, Name: "*"} x = &ast.SelectorExpr{X: p.checkExpr(x), Sel: sel} } case token.ENV: // .$attr .$"attr-name" sel := &ast.Ident{NamePos: p.pos} p.next() if sel.NamePos+1 != p.pos || (p.tok != token.IDENT && p.tok != token.STRING) { p.errorExpected(p.pos, "identifier after '$'", 2) sel.Name = "$_" } else { sel.Name = "$" + p.lit p.next() } x = &ast.SelectorExpr{X: p.checkExpr(x), Sel: sel} case token.STRING: // ."field-name" sel := &ast.Ident{NamePos: p.pos, Name: p.lit} p.next() x = &ast.SelectorExpr{X: p.checkExpr(x), Sel: sel} default: processed = false } } if !processed { pos := p.pos p.errorExpected(pos, "selector or type assertion", 2) // TODO(rFindley) The check for token.RBRACE below is a targeted fix // to error recovery sufficient to make the x/tools tests to // pass with the new parsing logic introduced for type // parameters. Remove this once error recovery has been // more generally reconsidered. if p.tok != token.RBRACE { p.next() // make progress } sel := &ast.Ident{NamePos: pos, Name: "_"} x = &ast.SelectorExpr{X: x, Sel: sel} } } case token.AT: // @ ce := &ast.CondExpr{X: x, OpPos: p.pos} p.next() switch p.tok { case token.LPAREN: cond, kind := p.parseOperand(0) // @(cond) if kind != exprNormal { p.error(cond.Pos(), "invalid condition expression") } ce.Cond = p.checkExpr(cond) case token.IDENT: fun := p.parseIdent() if p.tok == token.LPAREN { ce.Cond = p.parseCallOrConversion(fun, false) // @fun(...) } else { ce.Cond = fun // @name } case token.STRING: // @"elem-name" ce.Cond = &ast.Ident{NamePos: p.pos, Name: p.lit} p.next() default: pos := p.pos p.errorExpected(pos, "condition expression", 2) p.next() // make progress ce.Cond = &ast.Ident{NamePos: pos, Name: "_"} } x = ce case token.LBRACK: // [ if lhs { p.resolve(x) } if allowCmd && p.isCmd(x) { // println [...] x = p.parseCallOrConversion(x, true) } else { x = p.parseIndexOrSlice(p.checkExpr(x)) } case token.LPAREN: // ( if lhs { p.resolve(x) } isCmd := allowCmd && p.isCmd(x) // println (...) x = p.parseCallOrConversion(p.checkExprOrType(x), isCmd) case token.LBRACE: // { if allowCmd && p.isCmd(x) { // println {...} x = p.parseCallOrConversion(x, true) } else { t := unparen(x) // determine if '{' belongs to a composite literal or a block statement switch t.(type) { case *ast.BadExpr, *ast.Ident, *ast.SelectorExpr: if p.exprLev < 0 { break L } // x is possibly a composite literal type case *ast.IndexExpr, *ast.IndexListExpr: if p.exprLev < 0 { break L } // x is possibly a composite literal type case *ast.ArrayType, *ast.StructType, *ast.MapType: // x is a composite literal type default: break L } if t != x { p.error(t.Pos(), "cannot parenthesize type in composite literal") // already progressed, no need to advance } x = p.parseLiteralValue(x) } case token.NOT: // ! if allowCmd && p.isCmd(x) { x = p.parseCallOrConversion(x, true) } else { x = &ast.ErrWrapExpr{X: x, Tok: token.NOT, TokPos: p.pos} p.next() } case token.QUESTION: // ? x = &ast.ErrWrapExpr{X: x, Tok: p.tok, TokPos: p.pos} p.next() case token.ASSIGN: // = if flags&flagAllowKwargExpr != 0 { if name, ok := x.(*ast.Ident); ok { // name=expr p.next() val := p.parseExpr(0) return &ast.KwargExpr{Name: name, Value: val}, exprKwarg } } fallthrough default: if allowCmd && p.isCmd(x) && p.checkCmd() { if lhs { p.resolve(x) } x = p.parseCallOrConversion(x, true) } else if p.tok == token.FLOAT && p.lit[0] == '.' && x.End() == p.pos { // tuple field: .0 .1 etc. sel := &ast.Ident{NamePos: p.pos + 1, Name: p.lit[1:]} p.next() x = &ast.SelectorExpr{X: x, Sel: sel} } else { break L } } lhs = false // no need to try to resolve again } return } func (p *parser) isCmd(x ast.Expr) bool { switch x.(type) { case *ast.Ident, *ast.SelectorExpr, *ast.ErrWrapExpr: return x.End() != p.pos } return false } func (p *parser) checkCmd() bool { switch p.tok { case token.IDENT, token.DRARROW, token.STRING, token.CSTRING, token.PYSTRING, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.RAT, token.FUNC, token.GOTO, token.TYPE, token.MAP, token.INTERFACE, token.CHAN, token.STRUCT, token.ENV: return true case token.SUB, token.AND, token.MUL, token.ARROW, token.XOR, token.ADD: oldtok, oldpos := p.tok, p.pos p.next() newpos := int(p.pos) p.unget(oldpos, oldtok, "") return int(oldpos)+len(oldtok.String()) == newpos // x -y } return false } // parseErrWrapExpr: expr! expr? expr?:defval // flags support flagInLHS, flagAllowCmd, flagAllowKwargExpr func (p *parser) parseErrWrapExpr(flags int) (x ast.Expr, exprKind int) { if x, exprKind = p.parsePrimaryExpr(nil, flags); exprKind > 0 { return } if expr, ok := x.(*ast.ErrWrapExpr); ok { if p.tok == token.COLON { p.next() expr.Default, _ = p.parseUnaryExpr(0) } } return } // If lhs is set and the result is an identifier, it is not resolved. // flags support flagInLHS, flagAllowCmd, flagAllowKwargExpr func (p *parser) parseUnaryExpr(flags int) (ast.Expr, int) { if p.trace { defer un(trace(p, "UnaryExpr")) } switch p.tok { case token.ADD, token.SUB, token.NOT, token.XOR, token.AND: pos, op := p.pos, p.tok p.next() x, _ := p.parseUnaryExpr(0) return &ast.UnaryExpr{OpPos: pos, Op: op, X: p.checkExpr(x)}, 0 case token.ARROW: // channel type or receive expression arrow := p.pos p.next() // If the next token is token.CHAN we still don't know if it // is a channel type or a receive operation - we only know // once we have found the end of the unary expression. There // are two cases: // // <- type => (<-type) must be channel type // <- expr => <-(expr) is a receive from an expression // // In the first case, the arrow must be re-associated with // the channel type parsed already: // // <- (chan type) => (<-chan type) // <- (chan<- type) => (<-chan (<-type)) x, _ := p.parseUnaryExpr(0) // determine which case we have if typ, ok := x.(*ast.ChanType); ok { // (<-type) // re-associate position info and <- dir := ast.SEND for ok && dir == ast.SEND { if typ.Dir == ast.RECV { // error: (<-type) is (<-(<-chan T)) p.errorExpected(typ.Arrow, "'chan'", 2) } arrow, typ.Begin, typ.Arrow = typ.Arrow, arrow, arrow dir, typ.Dir = typ.Dir, ast.RECV typ, ok = typ.Value.(*ast.ChanType) } if dir == ast.SEND { p.errorExpected(arrow, "channel type", 2) } return x, 0 } // <-(expr) return &ast.UnaryExpr{OpPos: arrow, Op: token.ARROW, X: p.checkExpr(x)}, 0 case token.MUL: // pointer type or unary "*" expression pos := p.pos p.next() x, _ := p.parseUnaryExpr(0) return &ast.StarExpr{Star: pos, X: p.checkExprOrType(x)}, 0 } return p.parseErrWrapExpr(flags) } func (p *parser) tokPrec() (token.Token, int) { tok := p.tok if p.inRHS && tok == token.ASSIGN { tok = token.EQL } return tok, tok.Precedence() } // If lhs is set and the result is an identifier, it is not resolved. // flags support flagInLHS, flagAllowCmd, flagAllowKwargExpr func (p *parser) parseBinaryExpr(prec1 int, flags int) (x ast.Expr, exprKind int) { if p.trace { defer un(trace(p, "BinaryExpr")) } if x, exprKind = p.parseUnaryExpr(flags); exprKind > 0 { return } lhs := flags&flagInLHS != 0 for { op, oprec := p.tokPrec() if oprec < prec1 { return } pos := p.expect(op) if lhs { p.resolve(x) lhs = false } y, _ := p.parseBinaryExpr(oprec+1, 0) x = &ast.BinaryExpr{X: p.checkExpr(x), OpPos: pos, Op: op, Y: p.checkExpr(y)} } } // flags support flagAllowCmd, flagAllowKwargExpr func (p *parser) parseRangeExpr(first ast.Expr, flags int) (x ast.Expr, exprKind int) { if p.trace { defer un(trace(p, "RangeExpr")) } if p.tok != token.COLON { x, exprKind = p.parseBinaryExpr(token.LowestPrec+1, flags) if exprKind > 0 || p.tok != token.COLON { // not RangeExpr return } } else { x = first } to := p.pos p.next() high, _ := p.parseBinaryExpr(token.LowestPrec+1, 0) var colon2 token.Pos var expr3 ast.Expr if p.tok == token.COLON { colon2 = p.pos p.next() expr3, _ = p.parseBinaryExpr(token.LowestPrec+1, 0) } if debugParseOutput { log.Printf("ast.RangeExpr{First: %v, Last: %v, Expr3: %v}\n", x, high, expr3) } return &ast.RangeExpr{First: x, To: to, Last: high, Colon2: colon2, Expr3: expr3}, 0 } // flags support flagAllowCmd, flagAllowRangeExpr, flagAllowKwargExpr func (p *parser) parseLambdaExpr(flags int) (x ast.Expr, exprKind int) { var first = p.pos if p.tok != token.DRARROW { if flags&flagAllowRangeExpr != 0 { x, exprKind = p.parseRangeExpr(nil, flags) } else { x, exprKind = p.parseBinaryExpr(token.LowestPrec+1, flags) } if exprKind == exprKwarg { // not a lambda return } } if p.tok == token.DRARROW { // => var rarrow = p.pos var rhs []ast.Expr var body *ast.BlockStmt var lhsHasParen, rhsHasParen bool p.next() switch p.tok { case token.LPAREN: // ( rhsHasParen = true p.next() for { item := p.parseExpr(0) rhs = append(rhs, item) if p.tok != token.COMMA { break } p.next() } p.expect(token.RPAREN) case token.LBRACE: // { body = p.parseBlockStmt() default: rhs = []ast.Expr{p.parseExpr(0)} } var lhs []*ast.Ident if x != nil { e := x retry: switch v := e.(type) { case *ast.TupleLit: items := make([]*ast.Ident, len(v.Elts)) for i, item := range v.Elts { ident := p.toIdent(item) if ident == nil { return &ast.BadExpr{From: item.Pos(), To: p.safePos(item.End())}, 0 } items[i] = ident } lhs, lhsHasParen = items, true case *ast.ParenExpr: e, lhsHasParen = v.X, true goto retry default: ident := p.toIdent(v) if ident == nil { return &ast.BadExpr{From: v.Pos(), To: p.safePos(v.End())}, 0 } lhs = []*ast.Ident{ident} } } if debugParseOutput { log.Printf("ast.LambdaExpr{Lhs: %v}\n", lhs) } if body != nil { return &ast.LambdaExpr2{ First: first, Lhs: lhs, Rarrow: rarrow, Body: body, LhsHasParen: lhsHasParen, }, 0 } return &ast.LambdaExpr{ First: first, Last: p.pos, Lhs: lhs, Rarrow: rarrow, Rhs: rhs, LhsHasParen: lhsHasParen, RhsHasParen: rhsHasParen, }, 0 } return } // If lhs is set and the result is an identifier, it is not resolved. // The result may be a type or even a raw type ([...]int). Callers must // check the result (using checkExpr or checkExprOrType), depending on // context. // flags support flagInLHS, flagAllowCmd, flagAllowRangeExpr, flagAllowKwargExpr func (p *parser) parseExprEx(flags int) (ast.Expr, int) { if p.trace { defer un(trace(p, "Expression")) } if flags&flagInLHS != 0 { return p.parseBinaryExpr(token.LowestPrec+1, flags&(flagInLHS|flagAllowCmd)) } return p.parseLambdaExpr(flags) } // flags support flagInLHS, flagAllowCmd, flagAllowRangeExpr func (p *parser) parseExpr(flags int) ast.Expr { x, _ := p.parseExprEx(flags) return x } func (p *parser) parseRHS() ast.Expr { return p.parseRHSEx(0) } // flags support flagAllowRangeExpr func (p *parser) parseRHSEx(flags int) ast.Expr { old := p.inRHS p.inRHS = true x := p.checkExpr(p.parseExpr(flags)) p.inRHS = old return x } // flags support flagAllowKwargExpr func (p *parser) parseRHSOrTypeEx(flags int) (x ast.Expr, exprKind int) { old := p.inRHS p.inRHS = true x, exprKind = p.parseExprEx(flags) if exprKind == 0 { x = p.checkExprOrType(x) } p.inRHS = old return } func (p *parser) parseRHSOrType() ast.Expr { x, _ := p.parseRHSOrTypeEx(0) return x } // ---------------------------------------------------------------------------- // Statements // Parsing modes for parseSimpleStmt. const ( basic = iota labelOk rangeOk ) // parseSimpleStmt returns true as 2nd result if it parsed the assignment // of a range clause (with mode == rangeOk). The returned statement is an // assignment with a right-hand side that is a single unary expression of // the form "range x". No guarantees are given for the left-hand side. // flags support flagAllowCmd func (p *parser) parseSimpleStmt(mode int, flags int) ast.Stmt { ss, _ /* isRange */ := p.parseSimpleStmtEx(mode, flags) return ss } func (p *parser) parseBranchCmdStmt(iden *ast.Ident) ast.Stmt { // XGo: goto as command x, _ := p.parsePrimaryExpr(iden, flagAllowCmd) return &ast.ExprStmt{X: x} } // flags support flagAllowCmd, flagAllowRangeExpr func (p *parser) parseSimpleStmtEx(mode int, flags int) (ast.Stmt, bool) { if p.trace { defer un(trace(p, "SimpleStmt")) } if flags&flagAllowRangeExpr != 0 && p.tok == token.COLON { // rangeExpr re, _ := p.parseRangeExpr(nil, 0) return &ast.ExprStmt{X: re}, true } x := p.parseLHSList(flags & flagAllowCmd) switch p.tok { case token.DEFINE, token.ASSIGN, token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN, token.REM_ASSIGN, token.AND_ASSIGN, token.OR_ASSIGN, token.XOR_ASSIGN, token.SHL_ASSIGN, token.SHR_ASSIGN, token.AND_NOT_ASSIGN: // assignment statement, possibly part of a range clause pos, tok := p.pos, p.tok p.next() var y []ast.Expr isRange := false if mode == rangeOk && p.tok == token.RANGE && (tok == token.DEFINE || tok == token.ASSIGN) { pos := p.pos p.next() y = []ast.Expr{&ast.UnaryExpr{OpPos: pos, Op: token.RANGE, X: p.parseRHSEx(flagAllowRangeExpr)}} isRange = true } else { y = p.parseRHSList() } as := &ast.AssignStmt{Lhs: x, TokPos: pos, Tok: tok, Rhs: y} if tok == token.DEFINE { p.shortVarDecl(as, x) } return as, isRange case token.IDENT: // in if mode != rangeOk || p.lit != "in" { break } fallthrough case token.ARROW: // <- (backward compatibility) if mode == rangeOk { return p.parseForPhraseStmtPart(x), true } } if len(x) > 1 { p.errorExpected(x[0].Pos(), "1 expression", 2) // continue with first expression } switch p.tok { case token.COLON: if flags&flagAllowRangeExpr != 0 { re, _ := p.parseRangeExpr(x[0], 0) return &ast.ExprStmt{X: re}, true } // labeled statement colon := p.pos p.next() if label, isIdent := x[0].(*ast.Ident); mode == labelOk && isIdent { // Go spec: The scope of a label is the body of the function // in which it is declared and excludes the body of any nested // function. stmt := &ast.LabeledStmt{Label: label, Colon: colon, Stmt: p.parseStmt(flags)} p.declare(stmt, nil, p.labelScope, ast.Lbl, label) return stmt, false } // The label declaration typically starts at x[0].Pos(), but the label // declaration may be erroneous due to a token after that position (and // before the ':'). If SpuriousErrors is not set, the (only) error // reported for the line is the illegal label error instead of the token // before the ':' that caused the problem. Thus, use the (latest) colon // position for error reporting. p.error(colon, "illegal label declaration") return &ast.BadStmt{From: x[0].Pos(), To: colon + 1}, false case token.ARROW: // send statement arrow := p.pos p.next() var ellipsis token.Pos vals := make([]ast.Expr, 1) vals[0] = p.parseRHS() if p.tok == token.ELLIPSIS { // a <- v... (issues #2107) ellipsis = p.pos p.next() } else { for p.tok == token.COMMA { // a <- v1, v2, v3 (issues #2107) p.next() vals = append(vals, p.parseRHS()) } } return &ast.SendStmt{Chan: x[0], Arrow: arrow, Values: vals, Ellipsis: ellipsis}, false case token.INC, token.DEC: // increment or decrement s := &ast.IncDecStmt{X: x[0], TokPos: p.pos, Tok: p.tok} p.next() return s, false } // expression return &ast.ExprStmt{X: x[0]}, false } func (p *parser) parseCallExpr(callType string) *ast.CallExpr { x := p.parseRHSOrType() // could be a conversion: (some type)(x) if call, isCall := x.(*ast.CallExpr); isCall { return call } if _, isBad := x.(*ast.BadExpr); !isBad { // only report error if it's a new one p.error(p.safePos(x.End()), fmt.Sprintf("function must be invoked in %s statement", callType)) } return nil } func (p *parser) parseGoStmt() ast.Stmt { if p.trace { defer un(trace(p, "GoStmt")) } pos := p.expect(token.GO) call := p.parseCallExpr("go") p.expectSemi() if call == nil { return &ast.BadStmt{From: pos, To: pos + 2} // len("go") } return &ast.GoStmt{Go: pos, Call: call} } func (p *parser) parseDeferStmt() ast.Stmt { if p.trace { defer un(trace(p, "DeferStmt")) } pos := p.expect(token.DEFER) call := p.parseCallExpr("defer") p.expectSemi() if call == nil { return &ast.BadStmt{From: pos, To: pos + 5} // len("defer") } return &ast.DeferStmt{Defer: pos, Call: call} } func (p *parser) parseReturnStmt() *ast.ReturnStmt { if p.trace { defer un(trace(p, "ReturnStmt")) } pos := p.pos p.expect(token.RETURN) var x []ast.Expr if p.tok != token.SEMICOLON && p.tok != token.RBRACE { x = p.parseRHSList() } p.expectSemi() return &ast.ReturnStmt{Return: pos, Results: x} } func (p *parser) parseBranchStmt(tok token.Token) ast.Stmt { if p.trace { defer un(trace(p, "BranchStmt")) } oldpos, oldlit := p.pos, p.lit // XGo: save token to allow goto() as a function pos := p.expect(tok) next := p.tok if next != token.IDENT && next != token.SEMICOLON { // XGo: allow goto() as a function p.unget(oldpos, token.IDENT, oldlit) s := p.parseSimpleStmt(basic, flagAllowCmd) p.expectSemi() return s } var label *ast.Ident if tok != token.FALLTHROUGH && next == token.IDENT { label = p.parseIdent() // add to list of unresolved targets n := len(p.targetStack) - 1 p.targetStack[n] = append(p.targetStack[n], label) } if p.tok != token.SEMICOLON { // XGo: goto command if label != nil { p.unget(label.NamePos, token.IDENT, label.Name) } s := p.parseBranchCmdStmt(&ast.Ident{NamePos: oldpos, Name: oldlit}) p.expectSemi() return s } p.expectSemi() return &ast.BranchStmt{TokPos: pos, Tok: tok, Label: label} } func (p *parser) makeExpr(s ast.Stmt, want string) ast.Expr { if s == nil { return nil } if es, isExpr := s.(*ast.ExprStmt); isExpr { return p.checkExpr(es.X) } found := "simple statement" if _, isAss := s.(*ast.AssignStmt); isAss { found = "assignment" } p.error(s.Pos(), fmt.Sprintf("expected %s, found %s (missing parentheses around composite literal?)", want, found)) return &ast.BadExpr{From: s.Pos(), To: p.safePos(s.End())} } // parseIfHeader is an adjusted version of parser.header // in cmd/compile/internal/syntax/parser.go, which has // been tuned for better error handling. func (p *parser) parseIfHeader() (init ast.Stmt, cond ast.Expr) { if p.tok == token.LBRACE { p.error(p.pos, "missing condition in if statement") cond = &ast.BadExpr{From: p.pos, To: p.pos} return } // p.tok != token.LBRACE outer := p.exprLev p.exprLev = -1 if p.tok != token.SEMICOLON { // accept potential variable declaration but complain if p.tok == token.VAR { p.next() p.error(p.pos, "var declaration not allowed in 'IF' initializer") } init = p.parseSimpleStmt(basic, 0) } var condStmt ast.Stmt var semi struct { pos token.Pos lit string // ";" or "\n"; valid if pos.IsValid() } if p.tok != token.LBRACE { if p.tok == token.SEMICOLON { semi.pos = p.pos semi.lit = p.lit p.next() } else { p.expect(token.SEMICOLON) } if p.tok != token.LBRACE { condStmt = p.parseSimpleStmt(basic, 0) } } else { condStmt = init init = nil } if condStmt != nil { cond = p.makeExpr(condStmt, "boolean expression") } else if semi.pos.IsValid() { if semi.lit == "\n" { p.error(semi.pos, "unexpected newline, expecting { after if clause") } else { p.error(semi.pos, "missing condition in if statement") } } // make sure we have a valid AST if cond == nil { cond = &ast.BadExpr{From: p.pos, To: p.pos} } p.exprLev = outer return } func isForPhraseCondEnd(tok token.Token) bool { return tok == token.RBRACK || tok == token.RBRACE || tok == token.FOR } // parseForPhraseCond is an adjusted version of parseIfHeader func (p *parser) parseForPhraseCond() (init ast.Stmt, cond ast.Expr) { if isForPhraseCondEnd(p.tok) { p.error(p.pos, "missing condition in for..in statement") cond = &ast.BadExpr{From: p.pos, To: p.pos} return } outer := p.exprLev p.exprLev = -1 if p.tok != token.SEMICOLON { // accept potential variable declaration but complain if p.tok == token.VAR { p.next() p.error(p.pos, "var declaration not allowed in 'IF' initializer") } init = p.parseSimpleStmt(basic, 0) } var condStmt ast.Stmt var semiPos token.Pos if !isForPhraseCondEnd(p.tok) { if p.tok == token.SEMICOLON { semiPos = p.pos p.next() } else { p.expect(token.SEMICOLON) } if !isForPhraseCondEnd(p.tok) { condStmt = p.parseSimpleStmt(basic, 0) } } else { condStmt = init init = nil } if condStmt != nil { cond = p.makeExpr(condStmt, "boolean expression") } else if semiPos.IsValid() { p.error(semiPos, "missing condition in for..in statement") } // make sure we have a valid AST if cond == nil { cond = &ast.BadExpr{From: p.pos, To: p.pos} } p.exprLev = outer return } func (p *parser) parseIfStmt() *ast.IfStmt { if p.trace { defer un(trace(p, "IfStmt")) } pos := p.expect(token.IF) p.openScope() defer p.closeScope() init, cond := p.parseIfHeader() body := p.parseBlockStmt() var elseStmt ast.Stmt if p.tok == token.ELSE { p.next() switch p.tok { case token.IF: elseStmt = p.parseIfStmt() case token.LBRACE: elseStmt = p.parseBlockStmt() p.expectSemi() default: p.errorExpected(p.pos, "if statement or block", 2) elseStmt = &ast.BadStmt{From: p.pos, To: p.pos} } } else { p.expectSemi() } return &ast.IfStmt{If: pos, Init: init, Cond: cond, Body: body, Else: elseStmt} } func (p *parser) parseTypeList() (list []ast.Expr) { if p.trace { defer un(trace(p, "TypeList")) } list = append(list, p.parseType()) for p.tok == token.COMMA { p.next() list = append(list, p.parseType()) } return } func (p *parser) parseCaseClause(typeSwitch bool) *ast.CaseClause { if p.trace { defer un(trace(p, "CaseClause")) } pos := p.pos var list []ast.Expr if p.tok == token.CASE { p.next() if typeSwitch { list = p.parseTypeList() } else { list = p.parseRHSList() } } else { p.expect(token.DEFAULT) } colon := p.expect(token.COLON) p.openScope() body := p.parseStmtList() p.closeScope() return &ast.CaseClause{Case: pos, List: list, Colon: colon, Body: body} } func isTypeSwitchAssert(x ast.Expr) bool { a, ok := x.(*ast.TypeAssertExpr) return ok && a.Type == nil } func (p *parser) isTypeSwitchGuard(s ast.Stmt) bool { switch t := s.(type) { case *ast.ExprStmt: // x.(type) return isTypeSwitchAssert(t.X) case *ast.AssignStmt: // v := x.(type) if len(t.Lhs) == 1 && len(t.Rhs) == 1 && isTypeSwitchAssert(t.Rhs[0]) { switch t.Tok { case token.ASSIGN: // permit v = x.(type) but complain p.error(t.TokPos, "expected ':=', found '='") fallthrough case token.DEFINE: return true } } } return false } func (p *parser) parseSwitchStmt() ast.Stmt { if p.trace { defer un(trace(p, "SwitchStmt")) } pos := p.expect(token.SWITCH) p.openScope() defer p.closeScope() var s1, s2 ast.Stmt if p.tok != token.LBRACE { prevLev := p.exprLev p.exprLev = -1 if p.tok != token.SEMICOLON { s2 = p.parseSimpleStmt(basic, 0) } if p.tok == token.SEMICOLON { p.next() s1 = s2 s2 = nil if p.tok != token.LBRACE { // A TypeSwitchGuard may declare a variable in addition // to the variable declared in the initial SimpleStmt. // Introduce extra scope to avoid redeclaration errors: // // switch t := 0; t := x.(T) { ... } // // (this code is not valid Go because the first t // cannot be accessed and thus is never used, the extra // scope is needed for the correct error message). // // If we don't have a type switch, s2 must be an expression. // Having the extra nested but empty scope won't affect it. p.openScope() defer p.closeScope() s2 = p.parseSimpleStmt(basic, 0) } } p.exprLev = prevLev } typeSwitch := p.isTypeSwitchGuard(s2) lbrace := p.expect(token.LBRACE) var list []ast.Stmt for p.tok == token.CASE || p.tok == token.DEFAULT { list = append(list, p.parseCaseClause(typeSwitch)) } rbrace := p.expect(token.RBRACE) p.expectSemi() body := &ast.BlockStmt{Lbrace: lbrace, List: list, Rbrace: rbrace} if typeSwitch { return &ast.TypeSwitchStmt{Switch: pos, Init: s1, Assign: s2, Body: body} } return &ast.SwitchStmt{Switch: pos, Init: s1, Tag: p.makeExpr(s2, "switch expression"), Body: body} } func (p *parser) parseCommClause() *ast.CommClause { if p.trace { defer un(trace(p, "CommClause")) } p.openScope() pos := p.pos var comm ast.Stmt if p.tok == token.CASE { p.next() lhs := p.parseLHSList(0) if p.tok == token.ARROW { // SendStmt if len(lhs) > 1 { p.errorExpected(lhs[0].Pos(), "1 expression", 2) // continue with first expression } arrow := p.pos p.next() rhs := p.parseRHS() comm = &ast.SendStmt{Chan: lhs[0], Arrow: arrow, Values: []ast.Expr{rhs}} } else { // RecvStmt if tok := p.tok; tok == token.ASSIGN || tok == token.DEFINE { // RecvStmt with assignment if len(lhs) > 2 { p.errorExpected(lhs[0].Pos(), "1 or 2 expressions", 2) // continue with first two expressions lhs = lhs[0:2] } pos := p.pos p.next() rhs := p.parseRHS() as := &ast.AssignStmt{Lhs: lhs, TokPos: pos, Tok: tok, Rhs: []ast.Expr{rhs}} if tok == token.DEFINE { p.shortVarDecl(as, lhs) } comm = as } else { // lhs must be single receive operation if len(lhs) > 1 { p.errorExpected(lhs[0].Pos(), "1 expression", 2) // continue with first expression } comm = &ast.ExprStmt{X: lhs[0]} } } } else { p.expect(token.DEFAULT) } colon := p.expect(token.COLON) body := p.parseStmtList() p.closeScope() return &ast.CommClause{Case: pos, Comm: comm, Colon: colon, Body: body} } func (p *parser) parseSelectStmt() *ast.SelectStmt { if p.trace { defer un(trace(p, "SelectStmt")) } pos := p.expect(token.SELECT) lbrace := p.expect(token.LBRACE) var list []ast.Stmt for p.tok == token.CASE || p.tok == token.DEFAULT { list = append(list, p.parseCommClause()) } rbrace := p.expect(token.RBRACE) p.expectSemi() body := &ast.BlockStmt{Lbrace: lbrace, List: list, Rbrace: rbrace} return &ast.SelectStmt{Select: pos, Body: body} } func (p *parser) parseForPhrases() (phrases []*ast.ForPhrase) { for { phrase := p.parseForPhrase() phrases = append(phrases, phrase) if p.tok != token.FOR { return } } } func (p *parser) parseForPhraseStmtPart(lhs []ast.Expr) *ast.ForPhraseStmt { tokPos := p.expectIn() // in x := p.parseExpr(flagAllowRangeExpr) var cond ast.Expr var ifPos token.Pos if p.tok == token.IF || p.tok == token.COMMA { ifPos = p.pos p.next() cond = p.parseExpr(0) // NOTE(xsw): why not parseForPhraseCond? // init statements are not supported in for-phrase-if (can't use ';' in for loop) } stmt := &ast.ForPhraseStmt{ ForPhrase: &ast.ForPhrase{ TokPos: tokPos, X: x, IfPos: ifPos, Cond: cond, }, } switch len(lhs) { case 1: stmt.Value = p.toIdent(lhs[0]) case 2: stmt.Key, stmt.Value = p.toIdent(lhs[0]), p.toIdent(lhs[1]) default: p.errorExpected(lhs[0].Pos(), "expect 1 or 2 identifiers", 2) } return stmt } func (p *parser) toIdent(e ast.Expr) *ast.Ident { switch v := e.(type) { case *ast.Ident: return v case *ast.BasicLit: p.errorExpected(e.Pos(), fmt.Sprintf("'IDENT', found %v", v.Value), 2) case *ast.NumberUnitLit: p.errorExpected(e.Pos(), fmt.Sprintf("'IDENT', found %v", v.Value+v.Unit), 2) default: p.errorExpected(e.Pos(), "'IDENT'", 2) } return nil } func (p *parser) parseForPhrase() *ast.ForPhrase { // for k, v in container if cond if p.trace { defer un(trace(p, "ForPhrase")) } pos := p.expect(token.FOR) p.openScope() defer p.closeScope() var k, v *ast.Ident v = p.parseIdent() if p.tok == token.COMMA { // k, v p.next() k, v = v, p.parseIdent() } tokPos := p.expectIn() // in container x := p.parseExpr(flagAllowRangeExpr) var init ast.Stmt var cond ast.Expr var ifPos token.Pos if p.tok == token.IF || p.tok == token.COMMA { // `if condition` or `if init; condition` ifPos = p.pos p.next() init, cond = p.parseForPhraseCond() } return &ast.ForPhrase{For: pos, Key: k, Value: v, TokPos: tokPos, X: x, IfPos: ifPos, Init: init, Cond: cond} } func (p *parser) parseForStmt() ast.Stmt { if p.trace { defer un(trace(p, "ForStmt")) } pos := p.expect(token.FOR) p.openScope() defer p.closeScope() var s1, s2, s3 ast.Stmt var isRange bool if p.tok != token.LBRACE { prevLev := p.exprLev p.exprLev = -1 if p.tok != token.SEMICOLON { if p.tok == token.RANGE { // "for range x" (nil lhs in assignment) pos := p.pos p.next() y := []ast.Expr{&ast.UnaryExpr{OpPos: pos, Op: token.RANGE, X: p.parseRHSEx(flagAllowRangeExpr)}} s2 = &ast.AssignStmt{Rhs: y} isRange = true } else { s2, isRange = p.parseSimpleStmtEx(rangeOk, flagAllowRangeExpr) } } if !isRange && p.tok == token.SEMICOLON { p.next() s1 = s2 s2 = nil if p.tok != token.SEMICOLON { s2 = p.parseSimpleStmt(basic, 0) } p.expectSemi() if p.tok != token.LBRACE { s3 = p.parseSimpleStmt(basic, 0) } } p.exprLev = prevLev } body := p.parseBlockStmt() p.expectSemi() if isRange { switch stmt := s2.(type) { case *ast.ForPhraseStmt: stmt.For = pos stmt.Body = body return stmt case *ast.ExprStmt: return &ast.RangeStmt{ For: pos, X: stmt.X, Body: body, NoRangeOp: true, } } as := s2.(*ast.AssignStmt) // check lhs var key, value ast.Expr switch len(as.Lhs) { case 0: // nothing to do case 1: key = as.Lhs[0] case 2: key, value = as.Lhs[0], as.Lhs[1] default: p.errorExpected(as.Lhs[len(as.Lhs)-1].Pos(), "at most 2 expressions", 2) return &ast.BadStmt{From: pos, To: p.safePos(body.End())} } // parseSimpleStmt returned a right-hand side that // is a single unary expression of the form "range x" x := as.Rhs[0].(*ast.UnaryExpr).X return &ast.RangeStmt{ For: pos, Key: key, Value: value, TokPos: as.TokPos, Tok: as.Tok, X: x, Body: body, } } // regular for statement return &ast.ForStmt{ For: pos, Init: s1, Cond: p.makeExpr(s2, "boolean or range expression"), Post: s3, Body: body, } } // flags support flagAllowCmd func (p *parser) parseStmt(flags int) (s ast.Stmt) { if p.trace { defer un(trace(p, "Statement")) } switch p.tok { case token.TYPE: s = &ast.DeclStmt{Decl: p.parseGenDecl(p.tok, p.parseTypeSpec)} case token.CONST, token.VAR: s = &ast.DeclStmt{Decl: p.parseGenDecl(p.tok, p.parseValueSpec)} case // tokens that may start an expression token.INT, token.FLOAT, token.IMAG, token.RAT, token.CHAR, token.STRING, token.CSTRING, token.PYSTRING, token.FUNC, token.LPAREN, // operands token.ADD, token.SUB, token.MUL, token.AND, token.XOR, token.ARROW, token.NOT, token.ENV, // unary operators token.LBRACK, token.STRUCT, token.CHAN, token.INTERFACE: // composite types flags = 0 fallthrough case token.IDENT, token.MAP: // operands s = p.parseSimpleStmt(labelOk, flags) // because of the required look-ahead, labeled statements are // parsed by parseSimpleStmt - don't expect a semicolon after // them if _, isLabeledStmt := s.(*ast.LabeledStmt); !isLabeledStmt { p.expectSemi() } case token.GO: s = p.parseGoStmt() case token.DEFER: s = p.parseDeferStmt() case token.RETURN: s = p.parseReturnStmt() case token.BREAK, token.CONTINUE, token.GOTO, token.FALLTHROUGH: s = p.parseBranchStmt(p.tok) case token.LBRACE: s = p.parseBlockStmt() p.expectSemi() case token.IF: s = p.parseIfStmt() case token.SWITCH: s = p.parseSwitchStmt() case token.SELECT: s = p.parseSelectStmt() case token.FOR: s = p.parseForStmt() case token.SEMICOLON: // Is it ever possible to have an implicit semicolon // producing an empty statement in a valid program? // (handle correctly anyway) s = &ast.EmptyStmt{Semicolon: p.pos, Implicit: p.lit == "\n"} p.next() case token.RBRACE: // a semicolon may be omitted before a closing "}" s = &ast.EmptyStmt{Semicolon: p.pos, Implicit: true} default: // no statement found pos := p.pos p.errorExpected(pos, "statement", 2) p.advance(stmtStart) s = &ast.BadStmt{From: pos, To: p.pos} } return } // ---------------------------------------------------------------------------- // Declarations type parseSpecFunction func(doc *ast.CommentGroup, keyword token.Token, iota int) ast.Spec func isValidImport(lit string) bool { const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD" s, _ := strconv.Unquote(lit) // go/scanner returns a legal string literal for _, r := range s { if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) { return false } } return s != "" } func (p *parser) parseImportSpec(doc *ast.CommentGroup, _ token.Token, _ int) ast.Spec { if p.trace { defer un(trace(p, "ImportSpec")) } var ident *ast.Ident switch p.tok { case token.PERIOD: ident = &ast.Ident{NamePos: p.pos, Name: "."} p.next() case token.IDENT: ident = p.parseIdent() } pos := p.pos var path string if p.tok == token.STRING { path = p.lit if !isValidImport(path) { p.error(pos, "invalid import path: "+path) } p.next() } else { p.expect(token.STRING) // use expect() error handling } p.expectSemi() // call before accessing p.linecomment // collect imports spec := &ast.ImportSpec{ Doc: doc, Name: ident, Path: &ast.BasicLit{ValuePos: pos, Kind: token.STRING, Value: path}, Comment: p.lineComment, } p.imports = append(p.imports, spec) return spec } func (p *parser) inClassFile() bool { return p.mode&ParseXGoClass != 0 } func (p *parser) parseValueSpec(doc *ast.CommentGroup, keyword token.Token, iota int) ast.Spec { if p.trace { defer un(trace(p, keyword.String()+"Spec")) } pos := p.pos var idents []*ast.Ident var typ ast.Expr var tag *ast.BasicLit var values []ast.Expr if p.inClassFile() && p.topScope == p.pkgScope && keyword == token.VAR && p.varDeclCnt == 1 { var starPos token.Pos if p.tok == token.MUL { starPos = p.pos p.next() } ident := p.parseIdent() if p.tok == token.PERIOD { p.next() typ = &ast.SelectorExpr{ X: ident, Sel: p.parseIdent(), } if starPos != token.NoPos { typ = &ast.StarExpr{ Star: starPos, X: typ, } } } else if starPos != token.NoPos { typ = &ast.StarExpr{ Star: starPos, X: ident, } } else { idents = append(idents, ident) for p.tok == token.COMMA { p.next() idents = append(idents, p.parseIdent()) } typ = p.tryType() if p.tok == token.ASSIGN { p.next() values = p.parseRHSList() } else if len(idents) == 1 && typ == nil { typ = ident idents = nil } } if p.tok == token.STRING { tag = &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit} p.next() } p.expect(token.SEMICOLON) } else { idents = p.parseIdentList() typ = p.tryType() // always permit optional initialization for more tolerant parsing if p.tok == token.ASSIGN { p.next() values = p.parseRHSList() } p.expectSemi() // call before accessing p.linecomment } switch keyword { case token.VAR: if typ == nil && values == nil { p.error(pos, "missing variable type or initialization") } case token.CONST: if values == nil && (iota == 0 || typ != nil) { p.error(pos, "missing constant value") } } // Go spec: The scope of a constant or variable identifier declared inside // a function begins at the end of the ConstSpec or VarSpec and ends at // the end of the innermost containing block. // (Global identifiers are resolved in a separate phase after parsing.) spec := &ast.ValueSpec{ Doc: doc, Names: idents, Type: typ, Tag: tag, Values: values, Comment: p.lineComment, } kind := ast.Con if keyword == token.VAR { kind = ast.Var } p.declare(spec, iota, p.topScope, kind, idents...) return spec } func (p *parser) parseTypeSpec(doc *ast.CommentGroup, _ token.Token, _ int) ast.Spec { if p.trace { defer un(trace(p, "TypeSpec")) } ident := p.parseIdent() // Go spec: The scope of a type identifier declared inside a function begins // at the identifier in the TypeSpec and ends at the end of the innermost // containing block. // (Global identifiers are resolved in a separate phase after parsing.) spec := &ast.TypeSpec{Doc: doc, Name: ident} p.declare(spec, nil, p.topScope, ast.Typ, ident) if p.tok == token.ASSIGN { spec.Assign = p.pos p.next() } spec.Type = p.parseType() p.expectSemi() // call before accessing p.linecomment spec.Comment = p.lineComment return spec } func (p *parser) parseGenDecl(keyword token.Token, f parseSpecFunction) *ast.GenDecl { if p.trace { defer un(trace(p, "GenDecl("+keyword.String()+")")) } if keyword == token.VAR { p.varDeclCnt++ } doc := p.leadComment pos := p.expect(keyword) var lparen, rparen token.Pos var list []ast.Spec if p.tok == token.LPAREN { lparen = p.pos p.next() for iota := 0; p.tok != token.RPAREN && p.tok != token.EOF; iota++ { list = append(list, f(p.leadComment, keyword, iota)) } rparen = p.expect(token.RPAREN) p.expectSemi() } else { list = append(list, f(nil, keyword, 0)) } return &ast.GenDecl{ Doc: doc, TokPos: pos, Tok: keyword, Lparen: lparen, Specs: list, Rparen: rparen, } } func isOverloadOp(tok token.Token) bool { return int(tok) < len(overloadOps) && overloadOps[tok] != 0 } // `funcName` // `(*T).methodName` // `func(params) results {...}` func (p *parser) parseOverloadFunc() (ast.Expr, bool) { switch p.tok { case token.IDENT: return p.parseIdent(), true case token.FUNC: return p.parseFuncTypeOrLit(), true case token.LPAREN: x, _ := p.parsePrimaryExpr(nil, 0) return x, true } return nil, false } // `= (overloadFuncs)` // // here overloadFunc represents // // `funcName` // `(*T).methodName` // `func(params) results {...}` func (p *parser) parseOverloadDecl(decl *ast.OverloadFuncDecl) *ast.OverloadFuncDecl { decl.Assign = p.expect(token.ASSIGN) decl.Lparen = p.expect(token.LPAREN) funcs := make([]ast.Expr, 0, 4) for { f, ok := p.parseOverloadFunc() if !ok { break } funcs = append(funcs, f) if p.tok == token.SEMICOLON { p.next() } } decl.Funcs = funcs decl.Rparen = p.expect(token.RPAREN) p.expectSemi() if debugParseOutput { var recvt ast.Expr if recv := decl.Recv; recv != nil { recvt = recv.List[0].Type } log.Printf("ast.OverloadFuncDecl{Recv: %v, Name: %v, ...}\n", recvt, decl.Name) } return decl } // `func identOrOp(params) results {...}` // `func identOrOp = (overloadFuncs)` // // `func T.ident(params) results { ... }` // `func .ident(params) results { ... }` (only in classfile) // // `func (recv) identOrOp(params) results { ... }` // `func (T).identOrOp = (overloadFuncs)` // `func (params) results { ... }()` func (p *parser) parseFuncDeclOrCall() (ast.Decl, *ast.CallExpr) { if p.trace { defer un(trace(p, "FunctionDeclOrCall")) } doc := p.leadComment pos := p.expect(token.FUNC) scope := ast.NewScope(p.topScope) // function scope var recv, params, results *ast.FieldList var ident *ast.Ident var isOp, isStatic, isFunLit, ok bool switch p.tok { case token.LPAREN: // method: `func (recv) identOrOp(params) results { ... }` // overload: `func (T).identOrOp = (overloadFuncs)` // funlit: `func (params) results { ... }()` params = p.parseParameters(scope, true) if p.tok == token.LPAREN { // func (params) (results) { ... }() isFunLit, results = true, p.parseParameters(scope, false) } else if p.tok == token.PERIOD { p.next() // func (T).identOrOp = (overloadFuncs) ident, isOp = p.parseIdentOrOp() return p.parseOverloadDecl(&ast.OverloadFuncDecl{ Doc: doc, Func: pos, Recv: params, Name: ident, Operator: isOp, }), nil } else if isOp = isOverloadOp(p.tok); isOp { oldtok, oldpos, oldlit := p.tok, p.pos, p.lit p.next() if p.tok == token.LPAREN { // func (recv) op(params) results { ... } recv, ident = params, &ast.Ident{NamePos: oldpos, Name: oldtok.String()} params, results = p.parseSignature(scope) } else { // func (params) typ { ... }() p.unget(oldpos, oldtok, oldlit) typ := p.tryType() if typ == nil { p.errorExpected(oldpos, "type", 1) p.next() typ = &ast.BadExpr{From: oldpos, To: p.pos} } isFunLit, results = true, &ast.FieldList{List: []*ast.Field{{Type: typ}}} } } else if typ := p.tryType(); typ == nil { // func (params) { ... }() isFunLit = true } else if ident, ok = typ.(*ast.Ident); ok && p.tok == token.LPAREN { // func (recv) ident(params) results { ... } recv = params params, results = p.parseSignature(scope) } else { // func (params) typ { ... }() isFunLit, results = true, &ast.FieldList{List: []*ast.Field{{Type: typ}}} } if isFunLit { body := p.parseBody(scope) funLit := &ast.FuncLit{ Type: &ast.FuncType{ Func: pos, Params: params, Results: results, }, Body: body, } call := p.parseCallOrConversion(funLit, false) p.expectSemi() return nil, call } case token.PERIOD: // func .ident(...) results (only in classfile) if p.inClassFile() { p.next() recv = &ast.FieldList{} isStatic = true } ident = p.parseIdent() params, results = p.parseSignature(scope) default: // func: `func identOrOp(...) results` // overload: `func identOrOp = (overloadFuncs)` // static method: `func T.ident(...) results` ident, isOp = p.parseIdentOrOp() switch p.tok { case token.ASSIGN: // func identOrOp = (overloadFuncs) return p.parseOverloadDecl(&ast.OverloadFuncDecl{ Doc: doc, Func: pos, Name: ident, Operator: isOp, }), nil case token.PERIOD: // func T.ident(...) results if !isOp { p.next() recv = &ast.FieldList{List: []*ast.Field{{Type: ident}}} ident = p.parseIdent() isStatic = true } } params, results = p.parseSignature(scope) } if isOp { if params == nil || len(params.List) != 1 { p.error(ident.Pos(), "overload operator can only have one parameter") } } var body *ast.BlockStmt switch p.tok { case token.LBRACE: body = p.parseBody(scope) p.expectSemi() case token.SEMICOLON: p.next() if p.tok == token.LBRACE { // opening { of function declaration on next line p.error(p.pos, "unexpected semicolon or newline before {") body = p.parseBody(scope) p.expectSemi() } default: p.expectSemi() } decl := &ast.FuncDecl{ Doc: doc, Recv: recv, Name: ident, Type: &ast.FuncType{ Func: pos, Params: params, Results: results, }, Body: body, Operator: isOp, Static: isStatic, } if recv == nil { // Go spec: The scope of an identifier denoting a constant, type, // variable, or function (but not method) declared at top level // (outside any function) is the package block. // // init() functions cannot be referred to and there may // be more than one - don't put them in the pkgScope if ident.Name != "init" { p.declare(decl, nil, p.pkgScope, ast.Fun, ident) } } if debugParseOutput { log.Printf("ast.FuncDecl{Name: %v, ...}\n", ident.Name) } return decl, nil } func (p *parser) parseDecl(sync map[token.Token]bool) ast.Decl { if p.trace { defer un(trace(p, "Declaration")) } var f parseSpecFunction pos := p.pos switch p.tok { case token.CONST, token.VAR: f = p.parseValueSpec case token.TYPE: f = p.parseTypeSpec case token.FUNC: decl, call := p.parseFuncDeclOrCall() if decl != nil { if p.errors.Len() != 0 { p.advance(sync) } return decl } return p.parseGlobalStmts(sync, pos, &ast.ExprStmt{X: call}) default: return p.parseGlobalStmts(sync, pos) } return p.parseGenDecl(p.tok, f) } func (p *parser) parseGlobalStmts(sync map[token.Token]bool, pos token.Pos, stmts ...ast.Stmt) *ast.FuncDecl { p.topScope = ast.NewScope(p.topScope) doc := p.leadComment p.openLabelScope() list := p.parseStmtList() p.closeLabelScope() p.closeScope() if stmts != nil { list = append(stmts, list...) } if p.errors.Len() != 0 { // TODO(xsw): error p.advance(sync) } if p.tok != token.EOF { p.errorExpected(p.pos, "statement", 2) } // Determine appropriate positions for the block statement // Use file beginning position as fallback when no statements are available startPos := pos endPos := p.pos if len(list) > 0 { startPos = list[0].Pos() endPos = list[len(list)-1].End() } return &ast.FuncDecl{ Name: ast.NewIdentEx(pos, "main", ast.ImplicitFun), Doc: doc, Type: &ast.FuncType{ Func: pos, Params: &ast.FieldList{}, }, Body: &ast.BlockStmt{ Lbrace: startPos, List: list, Rbrace: endPos, }, Shadow: true, } } // ---------------------------------------------------------------------------- // Source files func (p *parser) parseFile() *ast.File { if p.trace { defer un(trace(p, "File")) } // Don't bother parsing the rest if we had errors scanning the first token. // Likely not a Go source file at all. if p.errors.Len() != 0 { return nil } var noPkgDecl bool // package clause doc := p.leadComment var pos token.Pos var ident *ast.Ident if p.tok == token.PACKAGE { pos = p.expect(token.PACKAGE) // Go spec: The package clause is not a declaration; // the package name does not appear in any scope. ident = p.parseIdent() if ident.Name == "_" && p.mode&DeclarationErrors != 0 { p.error(p.pos, "invalid package name _") } p.expectSemi() // Don't bother parsing the rest if we had errors parsing the package clause. // Likely not a Go source file at all. if p.errors.Len() != 0 { return nil } } else { noPkgDecl = true pos = token.NoPos ident = ast.NewIdentEx(p.file.Pos(0), "main", ast.ImplicitPkg) } p.openScope() p.pkgScope = p.topScope var decls []ast.Decl var shadowEntry *ast.FuncDecl if p.mode&PackageClauseOnly == 0 { // import decls for p.tok == token.IMPORT { decls = append(decls, p.parseGenDecl(token.IMPORT, p.parseImportSpec)) } if p.mode&ImportsOnly == 0 { // rest of package body for p.tok != token.EOF { decls = append(decls, p.parseDecl(declStart)) } if n := len(decls); n > 0 { if f, ok := decls[n-1].(*ast.FuncDecl); ok && f.Shadow { shadowEntry = f } } } } p.closeScope() assert(p.topScope == nil, "unbalanced scopes") assert(p.labelScope == nil, "unbalanced label scopes") return &ast.File{ Doc: doc, Package: pos, Name: ident, Decls: decls, Imports: p.imports, Comments: p.comments, ShadowEntry: shadowEntry, NoPkgDecl: noPkgDecl, } } ================================================ FILE: parser/parser_test.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package parser import ( "io/fs" "testing" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/token" fsx "github.com/qiniu/x/http/fs" ) // ----------------------------------------------------------------------------- type fileInfo struct { *fsx.FileInfo } func (p fileInfo) Info() (fs.FileInfo, error) { return nil, fs.ErrNotExist } func TestFilter(t *testing.T) { d := fsx.NewFileInfo("foo.go", 10) if filter(d, func(fi fs.FileInfo) bool { return false }) { t.Fatal("TestFilter: true?") } if !filter(d, func(fi fs.FileInfo) bool { return true }) { t.Fatal("TestFilter: false?") } d2 := fileInfo{d} if filter(d2, func(fi fs.FileInfo) bool { return true }) { t.Fatal("TestFilter: true?") } } func TestAssert(t *testing.T) { defer func() { if e := recover(); e != "go/parser internal error: panic msg" { t.Fatal("TestAssert:", e) } }() assert(false, "panic msg") } func panicMsg(e any) string { switch v := e.(type) { case string: return v case error: return v.Error() } return "" } func testErrCode(t *testing.T, code string, errExp, panicExp string) { defer func() { if e := recover(); e != nil { if panicMsg(e) != panicExp { t.Fatal("testErrCode panic:", e) } } }() t.Helper() fset := token.NewFileSet() _, err := Parse(fset, "/foo/bar.xgo", code, 0) if err == nil || err.Error() != errExp { t.Fatal("testErrCode error:", err) } } func testShadowEntry(t *testing.T, code string, errExp string, decl *ast.FuncDecl) { t.Helper() fset := token.NewFileSet() f, err := ParseEntry(fset, "/foo/bar.xgo", code, Config{ Mode: ParseXGoClass | ParseComments | AllErrors, }) if err != nil && err.Error() != errExp { t.Fatal("testShadowEntry error:", err) } if err == nil && errExp != "" { t.Fatal("testShadowEntry: nil, errExp:", errExp) } if f.ShadowEntry == nil { t.Fatal("testShadowEntry: nil") } if f.ShadowEntry.Body.Lbrace != decl.Body.Lbrace || f.ShadowEntry.Body.Rbrace != decl.Body.Rbrace { t.Fatal("testShadowEntry: brace mismatch", f.ShadowEntry.Body.Lbrace, decl.Body.Lbrace, f.ShadowEntry.Body.Rbrace, decl.Body.Rbrace) } } func testErrCodeParseExpr(t *testing.T, code string, errExp, panicExp string) { defer func() { if e := recover(); e != nil { if panicMsg(e) != panicExp { t.Fatal("testErrCodeParseExpr panic:", e) } } }() t.Helper() _, err := ParseExpr(code) if err == nil || err.Error() != errExp { t.Fatal("testErrCodeParseExpr error:", err) } } func testClassErrCode(t *testing.T, code string, errExp, panicExp string) { defer func() { if e := recover(); e != nil { if panicMsg(e) != panicExp { t.Fatal("testErrCode panic:", e) } } }() t.Helper() fset := token.NewFileSet() _, err := Parse(fset, "/foo/bar.gox", code, ParseXGoClass) if err == nil || err.Error() != errExp { t.Fatal("testErrCode error:", err) } } func TestErrLabel(t *testing.T) { testErrCode(t, `a.x:`, `/foo/bar.xgo:1:4: illegal label declaration`, ``) } func TestErrTplLit(t *testing.T) { testErrCode(t, "tpl`a =`", `/foo/bar.xgo:1:8: expected ';', found 'EOF' (and 1 more errors)`, ``) } func TestErrOperand(t *testing.T) { testErrCode(t, `a :=`, `/foo/bar.xgo:1:5: expected operand, found 'EOF'`, ``) } func TestErrMissingComma(t *testing.T) { testErrCode(t, `func a(b int c)`, `/foo/bar.xgo:1:14: missing ',' in parameter list`, ``) } func TestErrLambda(t *testing.T) { testErrCode(t, `func test(v string, f func( int)) { } test "hello" => { println "lambda",x } `, `/foo/bar.xgo:3:6: expected 'IDENT', found "hello"`, ``) testErrCode(t, `func test(v string, f func( int)) { } test "hello", "x" => { println "lambda",x } `, `/foo/bar.xgo:3:15: expected 'IDENT', found "x"`, ``) testErrCode(t, `func test(v string, f func( int)) { } test "hello", ("x") => { println "lambda",x } `, `/foo/bar.xgo:3:16: expected 'IDENT', found "x"`, ``) testErrCode(t, `func test(v string, f func(int,int)) { } test "hello", (x, "y") => { println "lambda",x,y } `, `/foo/bar.xgo:3:19: expected 'IDENT', found "y"`, ``) testErrCode(t, `onTouchStart "someone" => { say "touched by someone" } `, `/foo/bar.xgo:1:14: expected 'IDENT', found "someone"`, ``) } func TestErrTooManyParseExpr(t *testing.T) { testErrCodeParseExpr(t, `func() int { var var var var var var var var var var var var }() `, `3:3: expected 'IDENT', found 'var' (and 10 more errors)`, ``) } func TestErrTooMany(t *testing.T) { testErrCode(t, ` func f() { var } func g() { var } func h() { var } func h() { var } func h() { var } func h() { var } func h() { var } func h() { var } func h() { var } func h() { var } func h() { var } func h() { var } `, `/foo/bar.xgo:2:16: expected 'IDENT', found '}' (and 10 more errors)`, ``) } func TestErrInFunc(t *testing.T) { testErrCode(t, `func test() { a, }`, `/foo/bar.xgo:2:2: expected 1 expression (and 2 more errors)`, ``) testErrCode(t, `func test() { a.test, => { } }`, `/foo/bar.xgo:2:10: expected operand, found '=>' (and 1 more errors)`, ``) testErrCode(t, `func test() { , } }`, `/foo/bar.xgo:2:3: expected statement, found ',' (and 1 more errors)`, ``) } // ----------------------------------------------------------------------------- func TestClassErrCode(t *testing.T) { testClassErrCode(t, `var ( A,B v int ) `, `/foo/bar.gox:2:2: missing variable type or initialization`, ``) testClassErrCode(t, `var ( A.*B v int ) `, `/foo/bar.gox:2:4: expected 'IDENT', found '*' (and 2 more errors)`, ``) testClassErrCode(t, `var ( []A v int ) `, `/foo/bar.gox:2:2: expected 'IDENT', found '['`, ``) testClassErrCode(t, `var ( *[]A v int ) `, `/foo/bar.gox:2:3: expected 'IDENT', found '['`, ``) testClassErrCode(t, ` var ( ) const c = 100 const d `, `/foo/bar.gox:5:7: missing constant value`, ``) } func TestErrGlobal(t *testing.T) { testErrCode(t, `func test() {} }`, `/foo/bar.xgo:2:1: expected statement, found '}'`, ``) } func TestErrCompositeLiteral(t *testing.T) { testErrCode(t, `println (T[int]){a: 1, b: 2} `, `/foo/bar.xgo:1:10: cannot parenthesize type in composite literal`, ``) } func TestErrCondExpr(t *testing.T) { testErrCode(t, ` x@*p `, `/foo/bar.xgo:2:3: expected condition expression, found '*'`, ``) testErrCode(t, ` x@(a, b) `, `/foo/bar.xgo:2:3: invalid condition expression`, ``) } func TestErrSelectorExpr(t *testing.T) { testErrCode(t, ` x. *p `, `/foo/bar.xgo:3:1: expected selector or type assertion, found '*'`, ``) testErrCode(t, ` x.$ a = 1 `, "/foo/bar.xgo:3:1: expected identifier after '$', found a", ``) testErrCode(t, ` x./ `, `/foo/bar.xgo:2:3: expected selector or type assertion, found '/'`, ``) testErrCode(t, ` x.**.$ `, `/foo/bar.xgo:2:6: expected identifier after '**.', found '$'`, ``) } func TestErrStringLitEx(t *testing.T) { testErrCode(t, ` println "${ ... }" `, "/foo/bar.xgo:2:13: expected operand, found '...'", ``) testErrCode(t, ` println "${b" `, "/foo/bar.xgo:2:11: invalid $ expression: ${ doesn't end with }", ``) testErrCode(t, ` println "$a${b}" `, "/foo/bar.xgo:2:10: invalid $ expression: neither `${ ... }` nor `$$`", ``) } func TestErrStringLiteral(t *testing.T) { testErrCode(t, `run " `, `/foo/bar.xgo:1:5: string literal not terminated`, ``) } func TestErrFieldDecl(t *testing.T) { testErrCode(t, ` type T struct { *(Foo) } `, `/foo/bar.xgo:3:3: cannot parenthesize embedded type`, ``) testErrCode(t, ` type T struct { (Foo) } `, `/foo/bar.xgo:3:2: cannot parenthesize embedded type`, ``) testErrCode(t, ` type T struct { (*Foo) } `, `/foo/bar.xgo:3:2: cannot parenthesize embedded type`, ``) } func TestParseFieldDecl(t *testing.T) { var p parser p.init(token.NewFileSet(), "/foo/bar.xgo", []byte(`type T struct { } `), 0) p.parseFieldDecl(nil) } func TestCheckExpr(t *testing.T) { var p parser p.init(token.NewFileSet(), "/foo/bar.xgo", []byte(``), 0) p.checkExpr(&ast.Ellipsis{}) p.checkExpr(&ast.ElemEllipsis{}) p.checkExpr(&ast.StarExpr{}) p.checkExpr(&ast.IndexListExpr{}) p.checkExpr(&ast.FuncType{}) p.checkExpr(&ast.FuncLit{}) } func TestErrTupleType(t *testing.T) { testErrCode(t, `var a (a float64, x int, chan int) `, `/foo/bar.xgo:1:8: mixed named and unnamed fields in tuple type`, ``) testErrCode(t, `var a (a float64, x int, string) `, `/foo/bar.xgo:1:8: mixed named and unnamed fields in tuple type`, ``) } func TestErrFuncDecl(t *testing.T) { testErrCode(t, `func test() { } `, `/foo/bar.xgo:2:1: unexpected semicolon or newline before {`, ``) testErrCode(t, `func test() +1 `, `/foo/bar.xgo:1:13: expected ';', found '+'`, ``) testErrCode(t, ` func (a T) +{} `, `/foo/bar.xgo:2:12: expected type, found '+'`, ``) testErrCode(t, `func +(a T, b T) {} `, `/foo/bar.xgo:1:6: overload operator can only have one parameter`, ``) } func TestErrForIn(t *testing.T) { testErrCode(t, `x := [a for a i b] `, `/foo/bar.xgo:1:15: expected 'in', found i`, ``) } func TestErrKwargExpr(t *testing.T) { testErrCode(t, ` f a=1, 13 `, `/foo/bar.xgo:2:8: positional argument follows keyword argument`, ``) } func TestNumberUnitLit(t *testing.T) { var p parser p.checkExpr(&ast.NumberUnitLit{}) p.toIdent(&ast.NumberUnitLit{}) } func TestImplicitIdent(t *testing.T) { if ast.NewIdentEx(100, "foo", ast.ImplicitPkg).End() != 100 { t.Fatal("TestImplicitPkg: not 100") } if ast.NewIdentEx(100, "foo", ast.ImplicitFun).End() != 100 { t.Fatal("TestImplicitFun: not 100") } if ast.NewIdentEx(100, "foo", ast.Fun).End() != 103 { t.Fatal("TestFun: not 103") } } func TestErrGlobalVarWithSyntaxError(t *testing.T) { // Parse the code testShadowEntry(t, `var ( foo int.=2 sprites []Sprite ) func reset() { foo = 10 sprites = make([]Sprite, 0) } onStart => { reset() } `, `/foo/bar.xgo:2:10: expected 'IDENT', found '=' (and 19 more errors)`, &ast.FuncDecl{ Body: &ast.BlockStmt{ Lbrace: 119, Rbrace: 121, }, }) testShadowEntry(t, `var ( foo int sprites []Sprite ) func reset() { foo = 10 sprites = make([]Sprite, 0) } onStart => { reset() }`, "", &ast.FuncDecl{ Body: &ast.BlockStmt{ Lbrace: 94, Rbrace: 117, }, }) } // ----------------------------------------------------------------------------- ================================================ FILE: parser/parser_xgo.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package parser import ( "errors" "io/fs" "path" "strings" goast "go/ast" goparser "go/parser" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/parser/fsx" "github.com/goplus/xgo/token" "github.com/qiniu/x/stream" ) const ( DbgFlagParseOutput = 1 << iota DbgFlagParseError DbgFlagAll = DbgFlagParseOutput | DbgFlagParseError ) var ( debugParseOutput bool debugParseError bool ) func SetDebug(dbgFlags int) { debugParseOutput = (dbgFlags & DbgFlagParseOutput) != 0 debugParseError = (dbgFlags & DbgFlagParseError) != 0 } var ( ErrUnknownFileKind = errors.New("unknown file kind") ) type FileSystem = fsx.FileSystem // ----------------------------------------------------------------------------- // Parse parses a single XGo source file. The filename specifies the XGo source file. // If the file couldn't be read, a nil map and the respective error are returned. func Parse(fset *token.FileSet, filename string, src any, mode Mode) (pkgs map[string]*ast.Package, err error) { file, err := ParseFile(fset, filename, src, mode) if err != nil { return } pkgs = make(map[string]*ast.Package) pkgs[file.Name.Name] = astFileToPkg(file, filename) return } // astFileToPkg translate ast.File to ast.Package func astFileToPkg(file *ast.File, fileName string) (pkg *ast.Package) { pkg = &ast.Package{ Name: file.Name.Name, Files: make(map[string]*ast.File), } pkg.Files[fileName] = file return } // ----------------------------------------------------------------------------- // ParseDir calls ParseFSDir by passing a local filesystem. func ParseDir(fset *token.FileSet, path string, filter func(fs.FileInfo) bool, mode Mode) (pkgs map[string]*ast.Package, first error) { return ParseFSDir(fset, fsx.Local, path, Config{Filter: filter, Mode: mode}) } type Config struct { ClassKind func(fname string) (isProj, ok bool) Filter func(fs.FileInfo) bool Mode Mode } // ParseDirEx calls ParseFSDir by passing a local filesystem. func ParseDirEx(fset *token.FileSet, path string, conf Config) (pkgs map[string]*ast.Package, first error) { return ParseFSDir(fset, fsx.Local, path, conf) } // ParseEntry calls ParseFSEntry by passing a local filesystem. func ParseEntry(fset *token.FileSet, filename string, src any, conf Config) (f *ast.File, err error) { return ParseFSEntry(fset, fsx.Local, filename, src, conf) } // ParseFSDir calls ParseFile for all XGo files in the directory specified by // path and returns a map of package name -> package AST with all the packages // found. // // If filter != nil, only the XGo files with fs.FileInfo entries passing through // the filter are considered. The mode bits are passed to ParseFile unchanged. // Position information is recorded in fset, which must not be nil. // // If the directory couldn't be read, a nil map and the respective error are // returned. If a parse error occurred, a non-nil but incomplete map and the // first error encountered are returned. func ParseFSDir(fset *token.FileSet, fs FileSystem, dir string, conf Config) (pkgs map[string]*ast.Package, first error) { if conf.Mode&SaveAbsFile != 0 { dir, _ = fs.Abs(dir) conf.Mode &^= SaveAbsFile } list, err := fs.ReadDir(dir) if err != nil { return nil, err } if conf.ClassKind == nil { conf.ClassKind = defaultClassKind } pkgs = make(map[string]*ast.Package) for _, d := range list { if d.IsDir() { continue } fname := d.Name() ext := path.Ext(fname) var isProj, isClass, isNormalGox, useGoParser bool switch ext { case ".xgo", ".gop": case ".go": if strings.HasPrefix(fname, "xgo_autogen") || strings.HasPrefix(fname, "gop_autogen") { continue } useGoParser = (conf.Mode & ParseGoAsGoPlus) == 0 case ".gox": isNormalGox = true fallthrough default: if isProj, isClass = conf.ClassKind(fname); isClass { isNormalGox = false } else if isNormalGox { // not found XGo class by ext, but is a .gox file isClass = true } else { // unknown fileKind continue } } mode := conf.Mode if isClass { mode |= ParseXGoClass } if !strings.HasPrefix(fname, "_") && (conf.Filter == nil || filter(d, conf.Filter)) { filename := fs.Join(dir, fname) if useGoParser { if filedata, err := fs.ReadFile(filename); err == nil { if src, err := goparser.ParseFile(fset, filename, filedata, goparser.Mode(conf.Mode&goReservedFlags)); err == nil { pkg := reqPkg(pkgs, src.Name.Name) if pkg.GoFiles == nil { pkg.GoFiles = make(map[string]*goast.File) } pkg.GoFiles[filename] = src } else { first = err } } else { first = err } } else { f, err := ParseFSFile(fset, fs, filename, nil, mode) if f != nil { f.IsProj, f.IsClass = isProj, isClass f.IsNormalGox = isNormalGox if f.Name != nil { pkg := reqPkg(pkgs, f.Name.Name) pkg.Files[filename] = f } } if err != nil && first == nil { first = err } } } } return } // ParseFSEntry parses the source code of a single XGo source file and returns the corresponding ast.File node. // Compared to ParseFSFile, ParseFSEntry detects fileKind by its filename. func ParseFSEntry(fset *token.FileSet, fs FileSystem, filename string, src any, conf Config) (f *ast.File, err error) { fname := fs.Base(filename) ext := path.Ext(fname) var isProj, isClass, isNormalGox bool switch ext { case ".xgo", ".gop", ".go": case ".gox": isNormalGox = true fallthrough default: if conf.ClassKind == nil { conf.ClassKind = defaultClassKind } if isProj, isClass = conf.ClassKind(fname); isClass { isNormalGox = false } else if isNormalGox { // not found XGo class by ext, but is a .gox file isClass = true } else { return nil, ErrUnknownFileKind } } mode := conf.Mode if isClass { mode |= ParseXGoClass } f, err = ParseFSFile(fset, fs, filename, src, mode) if f != nil { f.IsProj, f.IsClass = isProj, isClass f.IsNormalGox = isNormalGox } return } func filter(d fs.DirEntry, fn func(fs.FileInfo) bool) bool { fi, err := d.Info() return err == nil && fn(fi) } func reqPkg(pkgs map[string]*ast.Package, name string) *ast.Package { pkg, found := pkgs[name] if !found { pkg = &ast.Package{ Name: name, Files: make(map[string]*ast.File), } pkgs[name] = pkg } return pkg } func defaultClassKind(fname string) (isProj bool, ok bool) { ext := path.Ext(fname) switch ext { case ".spx": return fname == "main.spx", true case ".gsh", ".gmx": return true, true } return } // ----------------------------------------------------------------------------- // ParseFiles parses XGo source files and returns the corresponding packages. func ParseFiles(fset *token.FileSet, files []string, mode Mode) (map[string]*ast.Package, error) { return ParseFSFiles(fset, fsx.Local, files, mode) } // ParseFSFiles parses XGo source files and returns the corresponding packages. func ParseFSFiles(fset *token.FileSet, fs FileSystem, files []string, mode Mode) (map[string]*ast.Package, error) { ret := map[string]*ast.Package{} fabs := (mode & SaveAbsFile) != 0 if fabs { mode &^= SaveAbsFile } for _, file := range files { if fabs { file, _ = fs.Abs(file) } f, err := ParseFSFile(fset, fs, file, nil, mode) if err != nil { return nil, err } pkgName := f.Name.Name pkg, ok := ret[pkgName] if !ok { pkg = &ast.Package{Name: pkgName, Files: make(map[string]*ast.File)} ret[pkgName] = pkg } pkg.Files[file] = f } return ret, nil } // ParseEntries parses XGo source files and returns the corresponding packages. // Compared to ParseFiles, ParseEntries detects fileKind by its filename. func ParseEntries(fset *token.FileSet, files []string, conf Config) (map[string]*ast.Package, error) { return ParseFSEntries(fset, fsx.Local, files, conf) } // ParseFSEntries parses XGo source files and returns the corresponding packages. // Compared to ParseFSFiles, ParseFSEntries detects fileKind by its filename. func ParseFSEntries(fset *token.FileSet, fs FileSystem, files []string, conf Config) (map[string]*ast.Package, error) { ret := map[string]*ast.Package{} fabs := (conf.Mode & SaveAbsFile) != 0 if fabs { conf.Mode &^= SaveAbsFile } for _, file := range files { if fabs { file, _ = fs.Abs(file) } f, err := ParseFSEntry(fset, fs, file, nil, conf) if err != nil { return nil, err } pkgName := f.Name.Name pkg, ok := ret[pkgName] if !ok { pkg = &ast.Package{Name: pkgName, Files: make(map[string]*ast.File)} ret[pkgName] = pkg } pkg.Files[file] = f } return ret, nil } // ----------------------------------------------------------------------------- // ParseFile parses the source code of a single XGo source file and returns the corresponding ast.File node. func ParseFile(fset *token.FileSet, filename string, src any, mode Mode) (f *ast.File, err error) { return ParseFSFile(fset, fsx.Local, filename, src, mode) } // ParseFSFile parses the source code of a single XGo source file and returns the corresponding ast.File node. func ParseFSFile(fset *token.FileSet, fs FileSystem, filename string, src any, mode Mode) (f *ast.File, err error) { code, err := readSourceFS(fs, filename, src) if err != nil { return } if mode&SaveAbsFile != 0 { filename, _ = fs.Abs(filename) } return parseFile(fset, filename, code, mode) } func readSourceFS(fs FileSystem, filename string, src any) ([]byte, error) { if src != nil { return stream.ReadSource(src) } return fs.ReadFile(filename) } // ----------------------------------------------------------------------------- ================================================ FILE: parser/parserdir_go118_test.go ================================================ //go:build go1.18 // +build go1.18 /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package parser import ( "testing" "github.com/goplus/xgo/parser/parsertest" "github.com/goplus/xgo/token" ) var testStdCode = `package bar; import "io" // comment x := 0 if t := false; t { x = 3 } else { x = 5 } println("x:", x) // comment 1 // comment 2 x = 0 switch s := "Hello"; s { default: x = 7 case "world", "hi": x = 5 case "xsw": x = 3 } println("x:", x) c := make(chan bool, 100) select { case c <- true: case v := <-c: default: panic("error") } ` func TestStd(t *testing.T) { fset := token.NewFileSet() pkgs, err := Parse(fset, "/foo/bar.xgo", testStdCode, ParseComments) if err != nil || len(pkgs) != 1 { t.Fatal("Parse failed:", err, len(pkgs)) } bar := pkgs["bar"] parsertest.Expect(t, bar, `package bar file bar.xgo noEntrypoint ast.GenDecl: Tok: import Specs: ast.ImportSpec: Path: ast.BasicLit: Kind: STRING Value: "io" ast.FuncDecl: Doc: ast.CommentGroup: List: ast.Comment: Text: // comment Name: ast.Ident: Name: main Type: ast.FuncType: Params: ast.FieldList: Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: x Tok: := Rhs: ast.BasicLit: Kind: INT Value: 0 ast.IfStmt: Init: ast.AssignStmt: Lhs: ast.Ident: Name: t Tok: := Rhs: ast.Ident: Name: false Cond: ast.Ident: Name: t Body: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: x Tok: = Rhs: ast.BasicLit: Kind: INT Value: 3 Else: ast.BlockStmt: List: ast.AssignStmt: Lhs: ast.Ident: Name: x Tok: = Rhs: ast.BasicLit: Kind: INT Value: 5 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "x:" ast.Ident: Name: x ast.AssignStmt: Lhs: ast.Ident: Name: x Tok: = Rhs: ast.BasicLit: Kind: INT Value: 0 ast.SwitchStmt: Init: ast.AssignStmt: Lhs: ast.Ident: Name: s Tok: := Rhs: ast.BasicLit: Kind: STRING Value: "Hello" Tag: ast.Ident: Name: s Body: ast.BlockStmt: List: ast.CaseClause: Body: ast.AssignStmt: Lhs: ast.Ident: Name: x Tok: = Rhs: ast.BasicLit: Kind: INT Value: 7 ast.CaseClause: List: ast.BasicLit: Kind: STRING Value: "world" ast.BasicLit: Kind: STRING Value: "hi" Body: ast.AssignStmt: Lhs: ast.Ident: Name: x Tok: = Rhs: ast.BasicLit: Kind: INT Value: 5 ast.CaseClause: List: ast.BasicLit: Kind: STRING Value: "xsw" Body: ast.AssignStmt: Lhs: ast.Ident: Name: x Tok: = Rhs: ast.BasicLit: Kind: INT Value: 3 ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: println Args: ast.BasicLit: Kind: STRING Value: "x:" ast.Ident: Name: x ast.AssignStmt: Lhs: ast.Ident: Name: c Tok: := Rhs: ast.CallExpr: Fun: ast.Ident: Name: make Args: ast.ChanType: Value: ast.Ident: Name: bool ast.BasicLit: Kind: INT Value: 100 ast.SelectStmt: Body: ast.BlockStmt: List: ast.CommClause: Comm: ast.SendStmt: Chan: ast.Ident: Name: c Values: ast.Ident: Name: true ast.CommClause: Comm: ast.AssignStmt: Lhs: ast.Ident: Name: v Tok: := Rhs: ast.UnaryExpr: Op: <- X: ast.Ident: Name: c ast.CommClause: Body: ast.ExprStmt: X: ast.CallExpr: Fun: ast.Ident: Name: panic Args: ast.BasicLit: Kind: STRING Value: "error" `) } func TestFromInstance(t *testing.T) { testFromDir(t, "", "./_instance") } // ----------------------------------------------------------------------------- ================================================ FILE: parser/parserdir_test.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package parser import ( "bytes" "os" "path" "reflect" "strings" "syscall" "testing" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/parser/fsx" "github.com/goplus/xgo/parser/fsx/memfs" "github.com/goplus/xgo/parser/parsertest" "github.com/goplus/xgo/scanner" "github.com/goplus/xgo/token" "github.com/qiniu/x/log" "github.com/qiniu/x/stream" ) // ----------------------------------------------------------------------------- func init() { log.SetFlags(log.Llongfile) SetDebug(DbgFlagAll) } func TestParseExprFrom(t *testing.T) { fset := token.NewFileSet() if _, err := ParseExprFrom(fset, "/foo/bar/not-exists", nil, 0); err == nil { t.Fatal("ParseExprFrom: no error?") } if _, err := ParseExpr("1+1\n;"); err == nil || err.Error() != "2:1: expected 'EOF', found ';'" { t.Fatal("ParseExpr:", err) } } func TestReadSource(t *testing.T) { buf := bytes.NewBuffer(nil) if _, err := stream.ReadSource(buf); err != nil { t.Fatal("readSource failed:", err) } sr := strings.NewReader("") if _, err := stream.ReadSource(sr); err != nil { t.Fatal("readSource strings.Reader failed:", err) } if _, err := stream.ReadSource(0); err == nil { t.Fatal("readSource int failed: no error?") } if _, err := stream.ReadSourceLocal("/foo/bar/not-exists", nil); err == nil { t.Fatal("readSourceLocal int failed: no error?") } } func TestParseFiles(t *testing.T) { fset := token.NewFileSet() if _, err := ParseFSFiles(fset, fsx.Local, []string{"/foo/bar/not-exists"}, PackageClauseOnly|SaveAbsFile); err == nil { t.Fatal("ParseFiles failed: no error?") } } func TestIparseFileInvalidSrc(t *testing.T) { fset := token.NewFileSet() if _, err := parseFile(fset, "/foo/bar/not-exists", 1, PackageClauseOnly); err != stream.ErrInvalidSource { t.Fatal("ParseFile failed: not errInvalidSource?") } } func TestIparseFileNoFset(t *testing.T) { defer func() { if e := recover(); e == nil { t.Fatal("ParseFile failed: no error?") } }() parseFile(nil, "/foo/bar/not-exists", nil, PackageClauseOnly) } func TestParseDir(t *testing.T) { fset := token.NewFileSet() if _, err := ParseDir(fset, "/foo/bar/not-exists", nil, PackageClauseOnly); err == nil { t.Fatal("ParseDir failed: no error?") } } func testFrom(t *testing.T, pkgDir, sel string, exclude Mode) { if sel != "" && !strings.Contains(pkgDir, sel) { return } t.Helper() log.Println("Parsing", pkgDir) fset := token.NewFileSet() pkgs, err := ParseDir(fset, pkgDir, nil, (Trace|ParseComments|ParseGoAsGoPlus)&^exclude) if err != nil || len(pkgs) != 1 { if errs, ok := err.(scanner.ErrorList); ok { for _, e := range errs { t.Log(e) } } t.Fatal("ParseDir failed:", err, reflect.TypeOf(err), len(pkgs)) } for _, pkg := range pkgs { b, _ := os.ReadFile(pkgDir + "/parser.expect") parsertest.ExpectEx(t, pkgDir+"/result.txt", pkg, b) return } } func testExprFrom(t *testing.T, pkgDir, sel string, exclude Mode) { if sel != "" && !strings.Contains(pkgDir, sel) { return } t.Helper() log.Println("Parsing", pkgDir) fset := token.NewFileSet() fname := pkgDir + "/in.xgo" src, err := os.ReadFile(fname) if err != nil { t.Fatal("os.ReadFile:", err) } file := fset.AddFile(fname, -1, len(src)) expr, errs := ParseExprEx(file, src, 0, (Trace|ParseComments)&^exclude) if errs != nil { if len(errs) > 0 { for _, e := range errs { t.Log(e) } } t.Fatal("ParseExprEx failed:", errs) } b, err := os.ReadFile(pkgDir + "/out.expect") if err != nil { t.Fatal("Parsing", pkgDir, "-", err) } parsertest.ExpectNode(t, expr, string(b)) } func TestParseGo(t *testing.T) { fset := token.NewFileSet() pkgs, err := ParseDirEx(fset, "./_testdata/functype", Config{Mode: Trace | ParseGoAsGoPlus}) if err != nil { t.Fatal("TestParseGo: ParseDir failed -", err) } if len(pkgs) != 1 { t.Fatal("TestParseGo failed: len(pkgs) =", len(pkgs)) } } func TestParseGoFiles(t *testing.T) { fset := token.NewFileSet() pkgs, err := ParseFiles(fset, []string{"./_testdata/functype/functype.go"}, Trace|ParseGoAsGoPlus) if err != nil { t.Fatal("TestParseGoFiles: ParseFiles failed -", err) } if len(pkgs) != 1 { t.Fatal("TestParseGoFiles failed: len(pkgs) =", len(pkgs)) } } func TestParseEntries(t *testing.T) { doTestParseEntries(t, Config{}) _, err := ParseEntries(nil, []string{"/not-found/gopfile.gox"}, Config{}) if err == nil { t.Fatal("ParseEntries: no error?") } } func TestParseEntries_SaveAbsFile(t *testing.T) { doTestParseEntries(t, Config{Mode: SaveAbsFile}) } func doTestParseEntries(t *testing.T, confReal Config) { doTestParseEntry(t, func(fset *token.FileSet, filename string, src any, conf Config) (f *ast.File, err error) { fs, _ := memfs.File(filename, src) pkgs, err := ParseFSEntries(fset, fs, []string{filename}, confReal) if err != nil { return } for _, pkg := range pkgs { for _, file := range pkg.Files { f = file return } } t.Fatal("TestParseEntries: no source?") return nil, syscall.ENOENT }) } func TestParseEntry(t *testing.T) { doTestParseEntry(t, ParseEntry) } func doTestParseEntry(t *testing.T, parseEntry func(fset *token.FileSet, filename string, src any, conf Config) (f *ast.File, err error)) { fset := token.NewFileSet() src, err := os.ReadFile("./_testdata/functype/functype.go") if err != nil { t.Fatal("os.ReadFile:", err) } conf := Config{} t.Run(".xgo file", func(t *testing.T) { f, err := parseEntry(fset, "./functype.xgo", src, conf) if err != nil { t.Fatal("ParseEntry failed:", err) } if f.IsClass || f.IsProj || f.IsNormalGox { t.Fatal("ParseEntry functype.xgo:", f.IsClass, f.IsProj, f.IsNormalGox) } }) t.Run(".gox file", func(t *testing.T) { f, err := parseEntry(fset, "./functype.gox", src, conf) if err != nil { t.Fatal("ParseEntry failed:", err) } if !f.IsClass || f.IsProj || !f.IsNormalGox { t.Fatal("ParseEntry functype.gox:", f.IsClass, f.IsProj, f.IsNormalGox) } }) t.Run(".foo.gox file", func(t *testing.T) { f, err := parseEntry(fset, "./functype.foo.gox", src, conf) if err != nil { t.Fatal("ParseEntry failed:", err) } if !f.IsClass || f.IsProj || !f.IsNormalGox { t.Fatal("ParseEntry functype.foo.gox:", f.IsClass, f.IsProj, f.IsNormalGox) } }) t.Run(".foo file", func(t *testing.T) { _, err := parseEntry(fset, "./functype.foo", src, conf) if err != ErrUnknownFileKind { t.Fatal("ParseEntry failed:", err) } }) t.Run(".spx file", func(t *testing.T) { f, err := parseEntry(fset, "./main.spx", src, conf) if err != nil { t.Fatal("ParseEntry failed:", err) } if !f.IsClass || !f.IsProj || f.IsNormalGox { t.Fatal("ParseEntry main.spx:", f.IsClass, f.IsProj, f.IsNormalGox) } }) } func TestParseEntry2(t *testing.T) { fset := token.NewFileSet() src, err := os.ReadFile("./_testdata/functype/functype.go") if err != nil { t.Fatal("os.ReadFile:", err) } conf := Config{} conf.ClassKind = func(fname string) (isProj bool, ok bool) { if strings.HasSuffix(fname, "_yap.gox") { return true, true } return defaultClassKind(fname) } t.Run("_yap.gox file", func(t *testing.T) { f, err := ParseEntry(fset, "./functype_yap.gox", src, conf) if err != nil { t.Fatal("ParseEntry failed:", err) } if !f.IsClass || !f.IsProj || f.IsNormalGox { t.Fatal("ParseEntry functype_yap.gox:", f.IsClass, f.IsProj, f.IsNormalGox) } }) } func TestSaveAbsFile(t *testing.T) { fset := token.NewFileSet() src, err := os.ReadFile("./_testdata/functype/functype.go") if err != nil { t.Fatal("os.ReadFile:", err) } conf := Config{} conf.Mode = SaveAbsFile t.Run(".xgo file", func(t *testing.T) { f, err := ParseEntry(fset, "./functype.xgo", src, conf) if err != nil { t.Fatal("ParseEntry failed:", err) } if f.IsClass || f.IsProj || f.IsNormalGox { t.Fatal("ParseEntry functype.xgo:", f.IsClass, f.IsProj, f.IsNormalGox) } }) t.Run(".xgo file", func(t *testing.T) { f, err := ParseFile(fset, "./functype.xgo", src, conf.Mode) if err != nil { t.Fatal("ParseEntry failed:", err) } if f.IsClass || f.IsProj || f.IsNormalGox { t.Fatal("ParseEntry functype.xgo:", f.IsClass, f.IsProj, f.IsNormalGox) } }) t.Run("dir", func(t *testing.T) { _, err := ParseDirEx(fset, "./_nofmt/cmdlinestyle1", conf) if err != nil { t.Fatal("ParseDirEx failed:", err) } }) } func TestGopAutoGen(t *testing.T) { fset := token.NewFileSet() fs := memfs.SingleFile("/foo", "gop_autogen.go", ``) pkgs, err := ParseFSDir(fset, fs, "/foo", Config{}) if err != nil { t.Fatal("ParseFSDir:", err) } if len(pkgs) != 0 { t.Fatal("TestGopAutoGen:", len(pkgs)) } } func TestGoFile(t *testing.T) { fset := token.NewFileSet() fs := memfs.SingleFile("/foo", "test.go", `package foo`) pkgs, err := ParseFSDir(fset, fs, "/foo", Config{}) if err != nil { t.Fatal("ParseFSDir:", err) } if len(pkgs) != 1 || pkgs["foo"].GoFiles["/foo/test.go"] == nil { t.Fatal("TestGoFile:", len(pkgs)) } } func TestErrParse(t *testing.T) { fset := token.NewFileSet() fs := memfs.SingleFile("/foo", "test.go", `package foo bar`) _, err := ParseFSDir(fset, fs, "/foo", Config{}) if err == nil { t.Fatal("ParseFSDir test.go: no error?") } fs = memfs.SingleFile("/foo", "test.xgo", `package foo bar`) _, err = ParseFSDir(fset, fs, "/foo", Config{}) if err == nil { t.Fatal("ParseFSDir test.xgo: no error?") } fs = memfs.New(map[string][]string{"/foo": {"test.go"}}, map[string]string{}) _, err = ParseFSDir(fset, fs, "/foo", Config{}) if err != syscall.ENOENT { t.Fatal("ParseFSDir:", err) } fs = memfs.SingleFile("/foo", "test.abc.gox", `package foo`) _, err = ParseFSDir(fset, fs, "/foo", Config{}) if err != nil { t.Fatal("ParseFSDir failed:", err) } } func testFromDir(t *testing.T, sel, relDir string, testExpr ...bool) { dir, err := os.Getwd() if err != nil { t.Fatal("Getwd failed:", err) } dir = path.Join(dir, relDir) fis, err := os.ReadDir(dir) if err != nil { t.Fatal("ReadDir failed:", err) } isTestExpr := len(testExpr) > 0 && testExpr[0] for _, fi := range fis { name := fi.Name() if strings.HasPrefix(name, "_") { continue } t.Run(name, func(t *testing.T) { pkgDir := dir + "/" + name if isTestExpr { testExprFrom(t, pkgDir, sel, 0) } else { testFrom(t, pkgDir, sel, 0) } }) } } func TestFromTestexpr(t *testing.T) { testFromDir(t, "", "./_testexpr", true) } func TestFromTestdata(t *testing.T) { testFromDir(t, "", "./_testdata") } func TestFromNofmt(t *testing.T) { testFromDir(t, "", "./_nofmt") } // ----------------------------------------------------------------------------- ================================================ FILE: parser/parsertest/parsertest.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package parsertest import ( "bytes" "fmt" "io" "log" "os" "path/filepath" "reflect" "sort" "testing" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/token" tpltoken "github.com/goplus/xgo/tpl/token" "github.com/qiniu/x/test" ) func sortedKeys(m any) []string { iter := reflect.ValueOf(m).MapRange() keys := make([]string, 0, 8) for iter.Next() { key := iter.Key() keys = append(keys, key.String()) } sort.Strings(keys) return keys } var ( tyNode = reflect.TypeOf((*ast.Node)(nil)).Elem() tyString = reflect.TypeOf("") tyBytes = reflect.TypeOf([]byte(nil)) tyToken = reflect.TypeOf(token.Token(0)) tyObjectPtr = reflect.TypeOf((*ast.Object)(nil)) tyScopePtr = reflect.TypeOf((*ast.Scope)(nil)) tplToken = reflect.TypeOf(tpltoken.Token(0)) ) // FprintNode prints an XGo AST node. func FprintNode(w io.Writer, lead string, v any, prefix, indent string) { val := reflect.ValueOf(v) switch val.Kind() { case reflect.Slice: n := val.Len() if n > 0 && lead != "" { io.WriteString(w, lead) } for i := 0; i < n; i++ { FprintNode(w, "", val.Index(i).Interface(), prefix, indent) } case reflect.Ptr: t := val.Type() if val.IsNil() || t == tyObjectPtr || t == tyScopePtr { return } if t.Implements(tyNode) { if lead != "" { io.WriteString(w, lead) } elem, tyElem := val.Elem(), t.Elem() fmt.Fprintf(w, "%s%v:\n", prefix, tyElem) n := elem.NumField() prefix += indent for i := 0; i < n; i++ { sf := tyElem.Field(i) sfv := elem.Field(i).Interface() switch sf.Type { case tyString, tyToken, tplToken: fmt.Fprintf(w, "%s%v: %v\n", prefix, sf.Name, sfv) case tyBytes: // skip default: if sf.Name == "Optional" { if pos, ok := sfv.(token.Pos); ok && pos != token.NoPos { fmt.Fprintf(w, "%s%v: true\n", prefix, sf.Name) } } else { FprintNode(w, fmt.Sprintf("%s%v:\n", prefix, sf.Name), sfv, prefix+indent, indent) } } } if m, ok := v.(*ast.MatrixLit); ok { fmt.Fprintf(w, "%sNElt: %d\n", prefix, len(m.Elts)) } } else if lit, ok := v.(*ast.StringLitEx); ok { fmt.Fprintf(w, "%sExtra:\n", prefix) prefix += indent for _, part := range lit.Parts { if val, ok := part.(string); ok { fmt.Fprintf(w, "%s%v\n", prefix, val) } else { FprintNode(w, "", part, prefix, indent) } } } else if lit, ok := v.(*ast.DomainTextLitEx); ok { fmt.Fprintf(w, "%sExtra: args=%d\n", prefix, len(lit.Args)) prefix += indent for _, arg := range lit.Args { FprintNode(w, "", arg, prefix, indent) } fmt.Fprintf(w, "%s%v\n", prefix, lit.Raw) } else { log.Panicln("FprintNode unexpected type:", t) } case reflect.Int, reflect.Bool, reflect.Invalid: // skip default: log.Panicln("FprintNode unexpected kind:", val.Kind(), "type:", val.Type()) } } // Fprint prints an XGo package. func Fprint(w io.Writer, pkg *ast.Package) { fmt.Fprintf(w, "package %s\n", pkg.Name) paths := sortedKeys(pkg.Files) for _, fpath := range paths { fmt.Fprintf(w, "\nfile %s\n", filepath.Base(fpath)) file := pkg.Files[fpath] if file.HasShadowEntry() { fmt.Fprintf(w, "noEntrypoint\n") } FprintNode(w, "", file.Decls, "", " ") } } // Expect asserts an XGo AST package equals output or not. func Expect(t *testing.T, pkg *ast.Package, expected string) { b := bytes.NewBuffer(nil) Fprint(b, pkg) output := b.String() if expected != output { fmt.Fprint(os.Stderr, output) t.Fatal("xgo.Parser: unexpect result") } } func ExpectEx(t *testing.T, outfile string, pkg *ast.Package, expected []byte) { b := bytes.NewBuffer(nil) Fprint(b, pkg) if test.Diff(t, outfile, b.Bytes(), expected) { t.Fatal("xgo.Parser: unexpect result") } } // ExpectNode asserts an XGo AST node equals output or not. func ExpectNode(t *testing.T, node any, expected string) { b := bytes.NewBuffer(nil) FprintNode(b, "", node, "", " ") output := b.String() if expected != output { fmt.Fprint(os.Stderr, output) t.Fatal("xgo.Parser: unexpect result") } } ================================================ FILE: printer/_testdata/02-Var-and-operator/var_and_op.xgo ================================================ x := 123.1 - 3i y, z := "Hello, ", 123 println(y+"complex:", x+1, "int:", z) ================================================ FILE: printer/_testdata/03-Import-go-package/import.xgo ================================================ import ( "fmt" "strings" ) x := strings.NewReplacer("?", "!").Replace("hello, world???") fmt.Println("x:", x) ================================================ FILE: printer/_testdata/04-Func/func.xgo ================================================ import ( "fmt" "strings" ) func foo(x string) string { return strings.NewReplacer("?", "!").Replace(x) } func printf(format string, args ...interface{}) (n int, err error) { n, err = fmt.Printf(format, args...) return } func bar(foo func(string, ...interface{}) (int, error)) { foo("Hello, %v!\n", "XGo") } bar(printf) fmt.Println(foo("Hello, world???")) fmt.Println(printf("Hello, %v\n", "XGo")) ================================================ FILE: printer/_testdata/05-Closure/closure.xgo ================================================ import "fmt" var x = "Hello, world!" foo := func(prompt string) (n int, err error) { n, err = fmt.Println(prompt + x) return } fmt.Println(foo("x: ")) printf := func(format string, args ...interface{}) (n int, err error) { n, err = fmt.Printf(format, args...) return } bar := func(foo func(string, ...interface{}) (int, error)) { foo("Hello, %v!\n", "XGo") } bar(printf) fmt.Println(printf("Hello, %v\n", "XGo")) ================================================ FILE: printer/_testdata/06-String-Map-Array-Slice/datastruct.xgo ================================================ x := []float64{1, 3.4, 5} y := map[string]float64{"Hello": 1, "xsw": 3.4} a := [...]float64{1, 3.4, 5} b := [...]float64{1, 3: 3.4, 5} c := []float64{2: 1.2, 3, 6: 4.5} x[1], y["xsw"] = 1.7, 2.8 println("x:", x, "y:", y) println(`x[1]:`, x[1], `y["xsw"]:`, y["xsw"], `a[1]:`, a[1]) i := uint16(4) b[uint32(4)], c[i] = 123, 1.7 println("a:", a, "b:", b, "c:", c) arr := [...]float64{1, 2} title := "Hello,world!" + "2020-05-27" println(title[:len(title)-len("2006-01-02")], len(arr), arr[1:]) ================================================ FILE: printer/_testdata/07-MapLit/maplit.xgo ================================================ x := {"Hello": 1, "xsw": 3.4} // map[string]float64 println("x:", x) y := {"Hello": 1, "xsw": "XGo"} // map[string]any println("y:", y) println({"Hello": 1, "xsw": 3}) // map[string]int println({1: 1.4, 3: "XGo"}) // map[int]any println("empty map:", {}) // map[string]any ================================================ FILE: printer/_testdata/08-SliceLit/slicelit.xgo ================================================ x := [1, 3.4] // []float64 println("x:", x) y := [1] // []int println("y:", y) z := [1+2i, "xsw"] // []interface{} println("z:", z) println([1, 3.4, 3+4i]) // []complex128 println([5+6i]) // []complex128 println(["xsw", 3]) // []interface{} println("empty slice:", []) // []interface{} ================================================ FILE: printer/_testdata/09-IfElse-SwitchCase/flow.xgo ================================================ x := 0 if t := false; t { x = 3 } else { x = 5 } println("x:", x) x = 0 switch s := "Hello"; s { default: x = 7 case "world", "hi": x = 5 case "xsw": x = 3 } println("x:", x) v := "Hello" switch { case v == "xsw": x = 3 case v == "Hello", v == "world": x = 9 default: x = 7 } println("x:", x) v = "Hello" switch { case v == "xsw": x = 3 case v == "hi", v == "world": x = 9 default: x = 11 } println("x:", x) switch v { case "Hello": println(v) fallthrough case "hi": println(v) fallthrough default: println(v) } z := 3 switch { case z < 10: println(z) fallthrough case z == 10: println(z) fallthrough case z > 10: println(z) fallthrough default: println(z) } ================================================ FILE: printer/_testdata/10-List-comprehension/list_comprehens.xgo ================================================ y := [x*x for x in [1, 3, 5, 7, 11]] println(y) y = [x*x for x in [1, 3, 5, 7, 11] if x > 3] println(y) z := [i+v for i, v in [1, 3, 5, 7, 11] if i%2 == 1] println(z) println([k+","+s for k, s in {"Hello": "xsw", "Hi": "XGo"}]) arr := [1, 2, 3, 4, 5, 6] x := [[a, b] for a in arr if a < b for b in arr if b > 2] println("x:", x) ================================================ FILE: printer/_testdata/11-Map-comprehension/map_comprehens.xgo ================================================ y := {x: i for i, x in [1, 3, 5, 7, 11]} println(y) y = {x: i for i, x in [1, 3, 5, 7, 11] if i%2 == 1} println(y) z := {v: k for k, v in {1: "Hello", 3: "Hi", 5: "xsw", 7: "XGo"} if k > 3} println(z) ================================================ FILE: printer/_testdata/12-Select-comprehension/select.xgo ================================================ a := [2, 3, 5, 7, 9, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59] where := {i for i, v in a if v == 19} println("19 is at index", where) if at, ok := {i for i, v in a if v == 37}; ok { println("37 is found! index =", at) } println("first element of a is:", {v for v in a}) ================================================ FILE: printer/_testdata/12-Select-comprehension2/findscore.xgo ================================================ type student struct { name string score int } func findScore(a []student, name string) (score int, ok bool) { return {x.score for x in a if x.name == name} } a := [student{"ken", 95}, student{"john", 90}, student{"tom", 58}] println(findScore(a, "tom")) ================================================ FILE: printer/_testdata/13-Exists-comprehension/exists.xgo ================================================ type student struct { name string score int } a := [student{"du", 84}, student{"wang", 70}, student{"ken", 100}] hasFullMark := {for x in a if x.score == 100} println("is any student full mark:", hasFullMark) hasFailed := {for x in a if x.score < 60} println("is any student failed:", hasFailed) ================================================ FILE: printer/_testdata/14-Using-goplus-in-Go/foo/foo.xgo ================================================ package foo func ReverseMap(m map[string]int) map[int]string { return {v: k for k, v in m} } ================================================ FILE: printer/_testdata/14-Using-goplus-in-Go/foo/foo_test.xgo ================================================ package foo import ( "testing" ) func TestReverseMap(t *testing.T) { out := ReverseMap({"a": 1}) if len(out) != 1 || out[1] != "a" { t.Fatal("ReverseMap failed:", out) } } ================================================ FILE: printer/_testdata/14-Using-goplus-in-Go/foo/footest_test.xgo ================================================ package foo_test import ( "testing" "github.com/goplus/xgo/tutorial/14-Using-goplus-in-Go/foo" ) func TestReverseMap(t *testing.T) { out := foo.ReverseMap({"b": 2}) if len(out) != 1 || out[2] != "b" { t.Fatal("ReverseMap failed:", out) } } ================================================ FILE: printer/_testdata/15-ErrWrap/err_wrap.xgo ================================================ import ( "strconv" ) func add(x, y string) (int, error) { return strconv.Atoi(x)? + strconv.Atoi(y)?, nil } func addSafe(x, y string) int { return strconv.Atoi(x)?:0 + strconv.Atoi(y)?:0 } println(`add("100", "23"):`, add("100", "23")!) sum, err := add("10", "abc") println(`add("10", "abc"):`, sum, err) println(`addSafe("10", "abc"):`, addSafe("10", "abc")) ================================================ FILE: printer/_testdata/16-Fib/fib.xgo ================================================ func fib(n int) int { if n == 0 { return 0 } if n == 1 { return 1 } return fib(n-1) + fib(n-2) } println `fib(27):`, fib(27) ================================================ FILE: printer/_testdata/17-Fibtc/fibtc.xgo ================================================ func fibtc(n, a, b int) int { if n == 0 { return a } if n == 1 { return b } return fibtc(n-1, b, a+b) } println `fibtc(27):`, fibtc(27, 0, 1) ================================================ FILE: printer/_testdata/18-Rational/rational.xgo ================================================ import "math/big" var a bigint = 1r << 65 // bigint, large than int64 var b bigrat = 4/5r // bigrat c := b - 1/3r + 3*1/2r // bigrat println(a, b, c) var x *big.Int = 1r << 65 // (1r << 65) is untyped bigint, and can be assigned to *big.Int var y *big.Rat = 4/5r println(x, y) a = new(big.Int).Abs(-265r) println("abs(-265r):", a) ================================================ FILE: printer/_testdata/21-Break-continue-goto/flow.xgo ================================================ println("start") goto L println("before") L: println("over") i := 0 L2: if i < 3 { println(i) i++ goto L2 } println("over") sum := 0 arr := [1, 3, 5, 7, 11, 13, 17] for i = 0; i < len(arr); i++ { if arr[i] < 3 { continue } if arr[i] > 11 { break } sum += arr[i] } println("sum(3,5,7,11):", sum == 26, sum) sum = 0 L3: for i = 0; i < len(arr); i++ { if arr[i] < 3 { continue L3 } if arr[i] > 11 { break L3 } sum += arr[i] } println("sum(3,5,7,11):", sum == 26, sum) z := 3 v := "Hello" switch z { case 3: if v == "Hello" { println("break") break } println("break fail") default: println(z) } L4: switch z { case 3: if v == "Hello" { println("break") break L4 } println("break fail") default: println(z) } ================================================ FILE: printer/_testdata/22-For-loop/for.xgo ================================================ // ----------------------------------------------------------------------------- // for..in sum := 0 for x in [1, 3, 5, 7, 11, 13, 17] if x > 3 { sum += x } println("sum(5,7,11,13,17):", sum) fns := make([]func() int, 3) for i, x in [3, 15, 777] { v := x fns[i] = func() int { return v } } println("values:", fns[0](), fns[1](), fns[2]()) // ----------------------------------------------------------------------------- // for with range with define tok (k,v := range x) sum = 0 for _, x := range [1, 3, 5, 7, 11, 13, 17] { if x > 3 { sum += x } } println("sum(5,7,11,13,17):", sum) sum = 0 for i, x := range [3, 15, 777] { v := x fns[i] = func() int { return v } } println("values:", fns[0](), fns[1](), fns[2]()) // ----------------------------------------------------------------------------- // for with range with assign tok (k,v = range x) sum = 0 x := 0 for _, x = range [1, 3, 5, 7, 11, 13, 17] { if x > 3 { sum += x } } println("x:", x, x == 17) println("sum(5,7,11,13,17):", sum) // ----------------------------------------------------------------------------- // normal for sum = 0 arr := [1, 3, 5, 7, 11, 13, 17] i := 10 for i = 0; i < len(arr); i++ { if arr[i] > 3 { sum += arr[i] } } println("sum(5,7,11,13,17):", sum) /* TODO: arr = [3, 15, 777] sum = 0 for i = 0; i < len(arr); i++ { v := arr[i] fns[i] = func() int { return v } } println("values:", fns[0](), fns[1](), fns[2]()) */ // ----------------------------------------------------------------------------- ================================================ FILE: printer/_testdata/23-Defer/defer.xgo ================================================ func f() (x int) { defer func() { x = 3 }() return 1 } func g() (x int) { defer func() { x = 3 }() x = 1 return } func h() (x int) { for i in [3, 2, 1] { v := i defer func() { x = v }() } return } println("f-x:", f()) println("g-x:", g()) println("h-x:", h()) defer println(println("Hello, defer")) println("Hello, XGo") ================================================ FILE: printer/_testdata/24-Goroutine/goroutine.xgo ================================================ import "time" go func() { println("Hello, goroutine!") }() time.Sleep(1e8) ================================================ FILE: printer/_testdata/25-Struct/struct.xgo ================================================ a := struct { A int `json:"a"` // comment a B string `b` // comment b }{1, "Hello"} println(a) b := &struct { A int // a B string }{1, "Hello"} c := &struct { a int b string }{1, "Hello"} println(b) a.A, a.B = 1, "Hi" println(a) b.A, b.B = 2, "Hi2" println(b) c.a, c.b = 3, "Hi3" println(c) ================================================ FILE: printer/_testdata/26-Method/method.xgo ================================================ type Person struct { Name string Age int Friends []string } func (p *Person) SetName(name string) { p.Name = name println(p.Name) } func (p *Person) SetAge(age int) { age = age + 5 p.Age = age println(p.Age) } func (p *Person) AddFriends(args ...string) { p.Friends = append(p.Friends, args...) } type M int func (m M) Foo() { println("foo", m) } p := Person{ Name: "bar", Age: 30, } p.Name, p.Age = "bar2", 31 p.SetName("foo") p.SetAge(32) p.AddFriends("a", "b", "c") a := int32(0) m := M(a) m.Foo() println(p.Name) println(p.Age) println(p.Friends) println(m) ================================================ FILE: printer/_testdata/27-Func-Set/func.xgo ================================================ func A(a *int, c *struct { b *int m map[string]*int s []*int }) { *a = 5 *c.b = 3 *c.m["foo"] = 7 *c.s[0] = 9 } func Index() int { return 0 } a1 := 6 a2 := 6 a3 := 6 c := struct { b *int m map[string]*int s []*int }{ b: &a1, m: map[string]*int{ "foo": &a2, }, s: []*int{&a3}, } A(&a1, &c) *c.m["foo"] = 8 *c.s[0] = 10 *c.s[Index()] = 11 println(a1, *c.b, *c.m["foo"], *c.s[0]) ================================================ FILE: printer/_testdata/28-Chan/chan.xgo ================================================ c := make(chan int, 10) c <- 3 close(c) d := <-c e, ok := <-c println(d) println(e, ok) ================================================ FILE: printer/_testdata/29-CompareToNil/ref.xgo ================================================ func foo() []int { return nil } func foo1() map[int]int { return make(map[int]int, 10) } func foo2() chan int { return make(chan int, 10) } func foo3() *int { return nil } println(foo() == nil) println(nil == foo()) println(foo() != nil) println(nil != foo()) println(foo1() == nil) println(nil == foo1()) println(foo1() != nil) println(nil != foo1()) println(foo2() == nil) println(nil == foo2()) println(foo2() != nil) println(nil != foo2()) println(foo3() == nil) println(nil == foo3()) println(foo3() != nil) println(nil != foo3()) ================================================ FILE: printer/_testdata/30-Recover/recover.xgo ================================================ defer func() { var err interface{} if err = recover(); err != nil { println(err) } }() panic("hello recover") ================================================ FILE: printer/_testdata/31-Builtin-Typecast/builtin_and_typecast.xgo ================================================ n := 2 a := make([]int, uint64(n)) a = append(a, 1, 2, 3) println(a) println("len:", len(a)) b := make([]int, 0, uint16(4)) c := [1, 2, 3] b = append(b, c...) println(b) println("len:", len(b), "cap:", cap(b)) ================================================ FILE: printer/_testdata/32-Import-gop-package/import_gop_pkg.xgo ================================================ import "github.com/goplus/xgo/tutorial/14-Using-goplus-in-Go/foo" rmap := foo.ReverseMap({"Hi": 1, "Hello": 2}) println(rmap) ================================================ FILE: printer/_testdata/33-Interface/shape.xgo ================================================ import "math" type Shape interface { Area() float64 } type Rect struct { x, y, w, h float64 } func (p *Rect) Area() float64 { return p.w * p.h } type Circle struct { x, y, r float64 } func (p *Circle) Area() float64 { return math.Pi * p.r * p.r } func Area(shapes ...Shape) float64 { s := 0.0 for shape in shapes { s += shape.Area() } return s } rect := &Rect{0, 0, 2, 5} circle := &Circle{0, 0, 3} println("area:", Area(circle, rect)) ================================================ FILE: printer/_testdata/34-Type-assert/type_assert.xgo ================================================ func foo(v interface{}) string { if _, ok := v.(bool); ok { return "bool" } switch v.(type) { case int: return "int" case string: return "string" default: return "unknown" } } func add(v, delta interface{}) interface{} { switch a := v.(type) { case int: return a + delta.(int) case float64: return a + delta.(float64) case string: return a + delta.(string) } return nil } println(foo(1), foo("Hi")) println(add(4, 3), add("n", "iu")) ================================================ FILE: printer/_testdata/35-Chan-select/select.xgo ================================================ var done = make(chan bool, 1) var exited = make(chan bool, 1) func consume(xchg chan int) { for { select { case c := <-xchg: println(c) case <-done: println("done!") exited <- true return } } } func product(xchg chan int, from chan int) { for x in from { xchg <- x } done <- true } from := make(chan int, 10) xchg := make(chan int, 1) go consume(xchg) go product(xchg, from) for i := 1; i <= 10; i++ { from <- i } close(from) <-exited ================================================ FILE: printer/_testdata/36-Auto-Property/autoprop.xgo ================================================ import ( "gop/ast/goptest" ) script := ` import ( gio "io" ) func New() (*Bar, error) { return nil, gio.EOF } bar, err := New() if err != nil { log.Println(err) } ` doc := goptest.New(script)! println(doc.any.funcDecl.name) println(doc.any.importSpec.name) ================================================ FILE: printer/_testdata/37-Cmdline/cmdline.xgo ================================================ import "os" println("args:", os.Args[1:]) ================================================ FILE: printer/_testdata/38-Overload-operator/overload_op.xgo ================================================ import "math/big" type MyBigInt struct { *big.Int } func Int(v *big.Int) MyBigInt { return MyBigInt{v} } func (a MyBigInt) + (b MyBigInt) MyBigInt { return MyBigInt{new(big.Int).Add(a.Int, b.Int)} } func (a MyBigInt) += (b MyBigInt) { a.Int.Add(a.Int, b.Int) } func -(a MyBigInt) MyBigInt { return MyBigInt{new(big.Int).Neg(a.Int)} } a := Int(1r) a += Int(2r) println(a + Int(3r)) println(-a) ================================================ FILE: printer/_testdata/39-Lambda-expression/lambda.xgo ================================================ func Map(c []float64, t func(float64) float64) []float64 { return [t(x) for x in c] } println(Map([1.2, 3.5, 6], x => x * x)) ================================================ FILE: printer/_testdata/40-Deduce-struct-type/deduce.xgo ================================================ type Config struct { Dir string Level int } type Result struct { Text string } func foo(conf *Config) *Result { println("conf:", *conf) return {Text: "Hello, XGo"} } ret := foo({Dir: "/foo/bar", Level: 1}) println(ret) ================================================ FILE: printer/_testdata/41-UDT-RangeForEach/udt_range.xgo ================================================ type foo struct { } func (p *foo) Gop_Enum(proc func(key int, val string)) { proc(3, "Hi") proc(7, "XGo") } for k, v in new(foo) { println(k, v) } println({v: k for k, v in new(foo)}) ================================================ FILE: printer/_testdata/42-UDT-RangeIterator/udt_range_iter.xgo ================================================ type fooIter struct { data *foo idx int } func (p *fooIter) Next() (key int, val string, ok bool) { if p.idx < len(p.data.key) { key, val, ok = p.data.key[p.idx], p.data.val[p.idx], true p.idx++ } return } type foo struct { key []int val []string } func newFoo() *foo { return &foo{key: [3, 7], val: ["Hi", "XGo"]} } func (p *foo) Gop_Enum() *fooIter { return &fooIter{data: p} } obj := newFoo() for k, v in obj { println(k, v) } println({v: k for k, v in obj}) ================================================ FILE: printer/_testdata/43-RangeExpr/rangeexpr.xgo ================================================ println "---------------------------" for i in :10 { println(i) } println "---------------------------" for i in 9:-1:-1 { println(i) } println "---------------------------" for i := range :10:2 { println(i) } println "---------------------------" for i := range 1:10:3 { println(i) } println "---------------------------" for range :10 { println("Range expression") } println "---------------------------" ================================================ FILE: printer/bugfix_test.go ================================================ package printer_test import ( "bytes" "testing" goast "go/ast" goprinter "go/printer" gotoken "go/token" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/printer" "github.com/goplus/xgo/token" ) func goIdents(name string) []*goast.Ident { return []*goast.Ident{{Name: name}} } func TestGoFormat(t *testing.T) { const ( mode = goprinter.UseSpaces | goprinter.TabIndent ) config := goprinter.Config{Mode: mode, Indent: 0, Tabwidth: 8} decl := &goast.GenDecl{Tok: gotoken.VAR, Lparen: 1, Rparen: 1} decl.Specs = []goast.Spec{ &goast.ValueSpec{Names: goIdents("foo"), Type: goast.NewIdent("int")}, &goast.ValueSpec{Names: goIdents("bar"), Type: goast.NewIdent("string")}, } b := bytes.NewBuffer(nil) fset := gotoken.NewFileSet() config.Fprint(b, fset, decl) const codeExp = `var ( foo int bar string )` if code := b.String(); code != codeExp { t.Fatal("config.Fprint:", code, codeExp) } } func gopIdents(name string) []*ast.Ident { return []*ast.Ident{{Name: name}} } func TestGopFormat(t *testing.T) { const ( mode = printer.UseSpaces | printer.TabIndent ) config := printer.Config{Mode: mode, Indent: 0, Tabwidth: 8} decl := &ast.GenDecl{Tok: token.VAR, Lparen: 1, Rparen: 1} decl.Specs = []ast.Spec{ &ast.ValueSpec{Names: gopIdents("foo"), Type: ast.NewIdent("int")}, &ast.ValueSpec{Names: gopIdents("bar"), Type: ast.NewIdent("string")}, } b := bytes.NewBuffer(nil) fset := token.NewFileSet() config.Fprint(b, fset, decl) const codeExp = `var ( foo int bar string )` if code := b.String(); code != codeExp { t.Fatal("config.Fprint:", code, codeExp) } } ================================================ FILE: printer/nodes.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // This file implements printing of AST nodes; specifically // expressions, statements, declarations, and files. It uses // the print functionality implemented in printer.go. package printer import ( "bytes" "log" "math" "strconv" "strings" "unicode" "unicode/utf8" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/token" ) const ( DbgFlagAll = 1 ) var ( debugFormat bool ) func SetDebug(flags int) { if flags != 0 { debugFormat = true } } // Formatting issues: // - better comment formatting for /*-style comments at the end of a line (e.g. a declaration) // when the comment spans multiple lines; if such a comment is just two lines, formatting is // not idempotent // - formatting of expression lists // - should use blank instead of tab to separate one-line function bodies from // the function header unless there is a group of consecutive one-liners // ---------------------------------------------------------------------------- // Common AST nodes. // Print as many newlines as necessary (but at least min newlines) to get to // the current line. ws is printed before the first line break. If newSection // is set, the first line break is printed as formfeed. Returns 0 if no line // breaks were printed, returns 1 if there was exactly one newline printed, // and returns a value > 1 if there was a formfeed or more than one newline // printed. // // TODO(gri): linebreak may add too many lines if the next statement at "line" // // is preceded by comments because the computation of n assumes // the current position before the comment and the target position // after the comment. Thus, after interspersing such comments, the // space taken up by them is not considered to reduce the number of // linebreaks. At the moment there is no easy way to know about // future (not yet interspersed) comments in this function. func (p *printer) linebreak(line, min int, ws whiteSpace, newSection bool) (nbreaks int) { n := nlimit(line - p.pos.Line) if n < min { n = min } if n > 0 { p.print(ws) if newSection { p.print(formfeed) n-- nbreaks = 2 } nbreaks += n for ; n > 0; n-- { p.print(newline) } } return } // setComment sets g as the next comment if g != nil and if node comments // are enabled - this mode is used when printing source code fragments such // as exports only. It assumes that there is no pending comment in p.comments // and at most one pending comment in the p.comment cache. func (p *printer) setComment(g *ast.CommentGroup) { if g == nil || !p.useNodeComments { return } if p.comments == nil { // initialize p.comments lazily p.comments = make([]*ast.CommentGroup, 1) } else if p.cindex < len(p.comments) { // for some reason there are pending comments; this // should never happen - handle gracefully and flush // all comments up to g, ignore anything after that p.flush(p.posFor(g.List[0].Pos()), token.ILLEGAL) p.comments = p.comments[0:1] // in debug mode, report error p.internalError("setComment found pending comments") } p.comments[0] = g p.cindex = 0 // don't overwrite any pending comment in the p.comment cache // (there may be a pending comment when a line comment is // immediately followed by a lead comment with no other // tokens between) if p.commentOffset == infinity { p.nextComment() // get comment ready for use } } type exprListMode uint const ( commaTerm exprListMode = 1 << iota // list is optionally terminated by a comma noIndent // no extra indentation in multi-line lists ) // If indent is set, a multi-line identifier list is indented after the // first linebreak encountered. func (p *printer) identList(list []*ast.Ident, indent bool) { // convert into an expression list so we can re-use exprList formatting xlist := make([]ast.Expr, len(list)) for i, x := range list { xlist[i] = x } var mode exprListMode if !indent { mode = noIndent } p.exprList(token.NoPos, xlist, 1, mode, token.NoPos, false) } const filteredMsg = "contains filtered or unexported fields" // Print a list of expressions. If the list spans multiple // source lines, the original line breaks are respected between // expressions. // // TODO(gri) Consider rewriting this to be independent of []ast.Expr // // so that we can use the algorithm for any kind of list // (e.g., pass list via a channel over which to range). func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exprListMode, next0 token.Pos, isIncomplete bool) { if len(list) == 0 { if isIncomplete { prev := p.posFor(prev0) next := p.posFor(next0) if prev.IsValid() && prev.Line == next.Line { p.print("/* " + filteredMsg + " */") } else { p.print(newline) p.print(indent, "// "+filteredMsg, unindent, newline) } } return } prev := p.posFor(prev0) next := p.posFor(next0) line := p.lineFor(list[0].Pos()) endLine := p.lineFor(list[len(list)-1].End()) if prev.IsValid() && prev.Line == line && line == endLine { // all list entries on a single line for i, x := range list { if i > 0 { // use position of expression following the comma as // comma position for correct comment placement p.print(x.Pos(), token.COMMA, blank) } p.expr0(x, depth) } if isIncomplete { p.print(token.COMMA, blank, "/* "+filteredMsg+" */") } return } // list entries span multiple lines; // use source code positions to guide line breaks // Don't add extra indentation if noIndent is set; // i.e., pretend that the first line is already indented. ws := ignore if mode&noIndent == 0 { ws = indent } // The first linebreak is always a formfeed since this section must not // depend on any previous formatting. prevBreak := -1 // index of last expression that was followed by a linebreak if prev.IsValid() && prev.Line < line && p.linebreak(line, 0, ws, true) > 0 { ws = ignore prevBreak = 0 } // initialize expression/key size: a zero value indicates expr/key doesn't fit on a single line size := 0 // We use the ratio between the geometric mean of the previous key sizes and // the current size to determine if there should be a break in the alignment. // To compute the geometric mean we accumulate the ln(size) values (lnsum) // and the number of sizes included (count). lnsum := 0.0 count := 0 // print all list elements prevLine := prev.Line for i, x := range list { line = p.lineFor(x.Pos()) // Determine if the next linebreak, if any, needs to use formfeed: // in general, use the entire node size to make the decision; for // key:value expressions, use the key size. // TODO(gri) for a better result, should probably incorporate both // the key and the node size into the decision process useFF := true // Determine element size: All bets are off if we don't have // position information for the previous and next token (likely // generated code - simply ignore the size in this case by setting // it to 0). prevSize := size const infinity = 1e6 // larger than any source line size = p.nodeSize(x, infinity) pair, isPair := x.(*ast.KeyValueExpr) if size <= infinity && prev.IsValid() && next.IsValid() { // x fits on a single line if isPair { size = p.nodeSize(pair.Key, infinity) // size <= infinity } } else { // size too large or we don't have good layout information size = 0 } // If the previous line and the current line had single- // line-expressions and the key sizes are small or the // ratio between the current key and the geometric mean // if the previous key sizes does not exceed a threshold, // align columns and do not use formfeed. if prevSize > 0 && size > 0 { const smallSize = 40 if count == 0 || prevSize <= smallSize && size <= smallSize { useFF = false } else { const r = 2.5 // threshold geomean := math.Exp(lnsum / float64(count)) // count > 0 ratio := float64(size) / geomean useFF = r*ratio <= 1 || r <= ratio } } needsLinebreak := 0 < prevLine && prevLine < line if i > 0 { // Use position of expression following the comma as // comma position for correct comment placement, but // only if the expression is on the same line. if !needsLinebreak { p.print(x.Pos()) } p.print(token.COMMA) needsBlank := true if needsLinebreak { // Lines are broken using newlines so comments remain aligned // unless useFF is set or there are multiple expressions on // the same line in which case formfeed is used. nbreaks := p.linebreak(line, 0, ws, useFF || prevBreak+1 < i) if nbreaks > 0 { ws = ignore prevBreak = i needsBlank = false // we got a line break instead } // If there was a new section or more than one new line // (which means that the tabwriter will implicitly break // the section), reset the geomean variables since we are // starting a new group of elements with the next element. if nbreaks > 1 { lnsum = 0 count = 0 } } if needsBlank { p.print(blank) } } if len(list) > 1 && isPair && size > 0 && needsLinebreak { // We have a key:value expression that fits onto one line // and it's not on the same line as the prior expression: // Use a column for the key such that consecutive entries // can align if possible. // (needsLinebreak is set if we started a new line before) p.expr(pair.Key) p.print(pair.Colon, token.COLON, vtab) p.expr(pair.Value) } else { p.expr0(x, depth) } if size > 0 { lnsum += math.Log(float64(size)) count++ } prevLine = line } if mode&commaTerm != 0 && next.IsValid() && p.pos.Line < next.Line { // Print a terminating comma if the next token is on a new line. p.print(token.COMMA) if isIncomplete { p.print(newline) p.print("// " + filteredMsg) } if ws == ignore && mode&noIndent == 0 { // unindent if we indented p.print(unindent) } p.print(formfeed) // terminating comma needs a line break to look good return } if isIncomplete { p.print(token.COMMA, newline) p.print("// "+filteredMsg, newline) } if ws == ignore && mode&noIndent == 0 { // unindent if we indented p.print(unindent) } } func (p *printer) parameters(fields *ast.FieldList) { p.print(fields.Opening, token.LPAREN) if len(fields.List) > 0 { prevLine := p.lineFor(fields.Opening) ws := indent for i, par := range fields.List { // determine par begin and end line (may be different // if there are multiple parameter names for this par // or the type is on a separate line) var parLineBeg int if len(par.Names) > 0 { parLineBeg = p.lineFor(par.Names[0].Pos()) } else { parLineBeg = p.lineFor(par.Type.Pos()) } var parLineEnd = p.lineFor(par.Type.End()) // separating "," if needed needsLinebreak := 0 < prevLine && prevLine < parLineBeg if i > 0 { // use position of parameter following the comma as // comma position for correct comma placement, but // only if the next parameter is on the same line if !needsLinebreak { p.print(par.Pos()) } p.print(token.COMMA) } // separator if needed (linebreak or blank) if needsLinebreak && p.linebreak(parLineBeg, 0, ws, true) > 0 { // break line if the opening "(" or previous parameter ended on a different line ws = ignore } else if i > 0 { p.print(blank) } // parameter names if len(par.Names) > 0 { // Very subtle: If we indented before (ws == ignore), identList // won't indent again. If we didn't (ws == indent), identList will // indent if the identList spans multiple lines, and it will outdent // again at the end (and still ws == indent). Thus, a subsequent indent // by a linebreak call after a type, or in the next multi-line identList // will do the right thing. p.identList(par.Names, ws == indent) p.print(blank) } // parameter type p.expr(stripParensAlways(par.Type)) // optional parameter marker if par.Optional.IsValid() { p.print(token.QUESTION) } prevLine = parLineEnd } // if the closing ")" is on a separate line from the last parameter, // print an additional "," and line break if closing := p.lineFor(fields.Closing); 0 < prevLine && prevLine < closing { p.print(token.COMMA) p.linebreak(closing, 0, ignore, true) } // unindent if we indented if ws == ignore { p.print(unindent) } } p.print(fields.Closing, token.RPAREN) } func (p *printer) signature(params, result *ast.FieldList) { if params != nil { p.parameters(params) } else { p.print(token.LPAREN, token.RPAREN) } n := result.NumFields() if n > 0 { // result != nil p.print(blank) if n == 1 && result.List[0].Names == nil { // single anonymous result; no ()'s p.expr(stripParensAlways(result.List[0].Type)) return } p.parameters(result) } } func identListSize(list []*ast.Ident, maxSize int) (size int) { for i, x := range list { if i > 0 { size += len(", ") } size += utf8.RuneCountInString(x.Name) if size >= maxSize { break } } return } func (p *printer) isOneLineFieldList(list []*ast.Field) bool { if len(list) != 1 { return false // allow only one field } f := list[0] if f.Tag != nil || f.Comment != nil { return false // don't allow tags or comments } // only name(s) and type const maxSize = 30 // adjust as appropriate, this is an approximate value namesSize := identListSize(f.Names, maxSize) if namesSize > 0 { namesSize = 1 // blank between names and types } typeSize := p.nodeSize(f.Type, maxSize) return namesSize+typeSize <= maxSize } func (p *printer) setLineComment(text string) { p.setComment(&ast.CommentGroup{List: []*ast.Comment{{Slash: token.NoPos, Text: text}}}) } func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool) { lbrace := fields.Opening list := fields.List rbrace := fields.Closing hasComments := isIncomplete || p.commentBefore(p.posFor(rbrace)) srcIsOneLine := lbrace.IsValid() && rbrace.IsValid() && p.lineFor(lbrace) == p.lineFor(rbrace) if !hasComments && srcIsOneLine { // possibly a one-line struct/interface if len(list) == 0 { // no blank between keyword and {} in this case p.print(lbrace, token.LBRACE, rbrace, token.RBRACE) return } else if p.isOneLineFieldList(list) { // small enough - print on one line // (don't use identList and ignore source line breaks) p.print(lbrace, token.LBRACE, blank) f := list[0] if isStruct { for i, x := range f.Names { if i > 0 { // no comments so no need for comma position p.print(token.COMMA, blank) } p.expr(x) } if len(f.Names) > 0 { p.print(blank) } p.expr(f.Type) } else { // interface if ftyp, isFtyp := f.Type.(*ast.FuncType); isFtyp { // method p.expr(f.Names[0]) p.signature(ftyp.Params, ftyp.Results) } else { // embedded interface p.expr(f.Type) } } p.print(blank, rbrace, token.RBRACE) return } } // hasComments || !srcIsOneLine p.print(blank, lbrace, token.LBRACE, indent) if hasComments || len(list) > 0 { p.print(formfeed) } if isStruct { sep := vtab if len(list) == 1 { sep = blank } var line int for i, f := range list { if i > 0 { p.linebreak(p.lineFor(f.Pos()), 1, ignore, p.linesFrom(line) > 0) } extraTabs := 0 p.setComment(f.Doc) p.recordLine(&line) if len(f.Names) > 0 { // named fields p.identList(f.Names, false) p.print(sep) p.expr(f.Type) extraTabs = 1 } else { // anonymous field p.expr(f.Type) extraTabs = 2 } if f.Tag != nil { if len(f.Names) > 0 && sep == vtab { p.print(sep) } p.print(sep) p.expr(f.Tag) extraTabs = 0 } if f.Comment != nil { for ; extraTabs > 0; extraTabs-- { p.print(sep) } p.setComment(f.Comment) } } if isIncomplete { if len(list) > 0 { p.print(formfeed) } p.flush(p.posFor(rbrace), token.RBRACE) // make sure we don't lose the last line comment p.setLineComment("// " + filteredMsg) } } else { // interface var line int for i, f := range list { if i > 0 { p.linebreak(p.lineFor(f.Pos()), 1, ignore, p.linesFrom(line) > 0) } p.setComment(f.Doc) p.recordLine(&line) if ftyp, isFtyp := f.Type.(*ast.FuncType); isFtyp { // method p.expr(f.Names[0]) p.signature(ftyp.Params, ftyp.Results) } else { // embedded interface p.expr(f.Type) } p.setComment(f.Comment) } if isIncomplete { if len(list) > 0 { p.print(formfeed) } p.flush(p.posFor(rbrace), token.RBRACE) // make sure we don't lose the last line comment p.setLineComment("// contains filtered or unexported methods") } } p.print(unindent, formfeed, rbrace, token.RBRACE) } // ---------------------------------------------------------------------------- // Expressions func walkBinary(e *ast.BinaryExpr) (has4, has5 bool, maxProblem int) { switch e.Op.Precedence() { case 4: has4 = true case 5: has5 = true } switch l := e.X.(type) { case *ast.BinaryExpr: if l.Op.Precedence() < e.Op.Precedence() { // parens will be inserted. // pretend this is an *ast.ParenExpr and do nothing. break } h4, h5, mp := walkBinary(l) has4 = has4 || h4 has5 = has5 || h5 if maxProblem < mp { maxProblem = mp } } switch r := e.Y.(type) { case *ast.BinaryExpr: if r.Op.Precedence() <= e.Op.Precedence() { // parens will be inserted. // pretend this is an *ast.ParenExpr and do nothing. break } h4, h5, mp := walkBinary(r) has4 = has4 || h4 has5 = has5 || h5 if maxProblem < mp { maxProblem = mp } case *ast.StarExpr: if e.Op == token.QUO { // `*/` maxProblem = 5 } case *ast.UnaryExpr: switch e.Op.String() + r.Op.String() { case "/*", "&&", "&^": maxProblem = 5 case "++", "--": if maxProblem < 4 { maxProblem = 4 } } } return } func cutoff(e *ast.BinaryExpr, depth int) int { has4, has5, maxProblem := walkBinary(e) if maxProblem > 0 { return maxProblem + 1 } if has4 && has5 { if depth == 1 { return 5 } return 4 } if depth == 1 { return 6 } return 4 } func diffPrec(expr ast.Expr, prec int) int { x, ok := expr.(*ast.BinaryExpr) if !ok || prec != x.Op.Precedence() { return 1 } return 0 } func reduceDepth(depth int) int { depth-- if depth < 1 { depth = 1 } return depth } // Format the binary expression: decide the cutoff and then format. // Let's call depth == 1 Normal mode, and depth > 1 Compact mode. // (Algorithm suggestion by Russ Cox.) // // The precedences are: // // 5 * / % << >> & &^ // 4 + - | ^ // 3 == != < <= > >= // 2 && // 1 || // // The only decision is whether there will be spaces around levels 4 and 5. // There are never spaces at level 6 (unary), and always spaces at levels 3 and below. // // To choose the cutoff, look at the whole expression but excluding primary // expressions (function calls, parenthesized exprs), and apply these rules: // // 1. If there is a binary operator with a right side unary operand // that would clash without a space, the cutoff must be (in order): // // /* 6 // && 6 // &^ 6 // ++ 5 // -- 5 // // (Comparison operators always have spaces around them.) // // 2. If there is a mix of level 5 and level 4 operators, then the cutoff // is 5 (use spaces to distinguish precedence) in Normal mode // and 4 (never use spaces) in Compact mode. // // 3. If there are no level 4 operators or no level 5 operators, then the // cutoff is 6 (always use spaces) in Normal mode // and 4 (never use spaces) in Compact mode. func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int) { prec := x.Op.Precedence() if prec < prec1 { // parenthesis needed // Note: The parser inserts an ast.ParenExpr node; thus this case // can only occur if the AST is created in a different way. p.print(token.LPAREN) p.expr0(x, reduceDepth(depth)) // parentheses undo one level of depth p.print(token.RPAREN) return } printBlank := prec < cutoff ws := indent p.expr1(x.X, prec, depth+diffPrec(x.X, prec)) if printBlank { p.print(blank) } xline := p.pos.Line // before the operator (it may be on the next line!) yline := p.lineFor(x.Y.Pos()) p.print(x.OpPos, x.Op) if xline != yline && xline > 0 && yline > 0 { // at least one line break, but respect an extra empty line // in the source if p.linebreak(yline, 1, ws, true) > 0 { ws = ignore printBlank = false // no blank after line break } } if printBlank { p.print(blank) } p.expr1(x.Y, prec+1, depth+1) if ws == ignore { p.print(unindent) } } func isBinary(expr ast.Expr) bool { _, ok := expr.(*ast.BinaryExpr) return ok } func (p *printer) expr1(expr ast.Expr, prec1, depth int) { p.print(expr.Pos()) switch x := expr.(type) { case *ast.BadExpr: p.print("BadExpr") case *ast.Ident: p.print(x) case *ast.BinaryExpr: if depth < 1 { p.internalError("depth < 1:", depth) depth = 1 } if v, ok := x.Y.(*ast.BasicLit); ok && v.Kind == token.RAT { depth++ } p.binaryExpr(x, prec1, cutoff(x, depth), depth) case *ast.KeyValueExpr: p.expr(x.Key) p.print(x.Colon, token.COLON, blank) p.expr(x.Value) case *ast.StarExpr: const prec = token.UnaryPrec if prec < prec1 { // parenthesis needed p.print(token.LPAREN) p.print(token.MUL) p.expr(x.X) p.print(token.RPAREN) } else { // no parenthesis needed p.print(token.MUL) p.expr(x.X) } case *ast.UnaryExpr: const prec = token.UnaryPrec if prec < prec1 { // parenthesis needed p.print(token.LPAREN) p.expr(x) p.print(token.RPAREN) } else { // no parenthesis needed p.print(x.Op) if x.Op == token.RANGE { // TODO(gri) Remove this code if it cannot be reached. p.print(blank) } p.expr1(x.X, prec, depth) } case *ast.BasicLit: p.print(x) case *ast.NumberUnitLit: p.print(&ast.BasicLit{Kind: x.Kind, Value: x.Value}) p.print(&ast.Ident{Name: x.Unit}) case *ast.FuncLit: p.print(x.Type.Pos(), token.FUNC) // See the comment in funcDecl about how the header size is computed. startCol := p.out.Column - len("func") p.signature(x.Type.Params, x.Type.Results) p.funcBody(p.distanceFrom(x.Type.Pos(), startCol), blank, x.Body) case *ast.ParenExpr: if _, hasParens := x.X.(*ast.ParenExpr); hasParens { // don't print parentheses around an already parenthesized expression // TODO(gri) consider making this more general and incorporate precedence levels p.expr0(x.X, depth) } else { p.print(token.LPAREN) p.expr0(x.X, reduceDepth(depth)) // parentheses undo one level of depth p.print(x.Rparen, token.RPAREN) } case *ast.SelectorExpr: p.selectorExpr(x, depth, false) case *ast.AnySelectorExpr: p.anySelectorExpr(x, depth) case *ast.TypeAssertExpr: p.expr1(x.X, token.HighestPrec, depth) p.print(token.PERIOD, x.Lparen, token.LPAREN) if x.Type != nil { p.expr(x.Type) } else { p.print(token.TYPE) } p.print(x.Rparen, token.RPAREN) case *ast.IndexExpr: // TODO(gri): should treat[] like parentheses and undo one level of depth p.expr1(x.X, token.HighestPrec, 1) p.print(x.Lbrack, token.LBRACK) p.expr0(x.Index, depth+1) p.print(x.Rbrack, token.RBRACK) case *ast.IndexListExpr: // TODO(gri): should treat[] like parentheses and undo one level of depth p.expr1(x.X, token.HighestPrec, 1) p.print(x.Lbrack, token.LBRACK) p.exprList(x.Lbrack, x.Indices, depth+1, commaTerm, x.Rbrack, false) p.print(x.Rbrack, token.RBRACK) case *ast.SliceExpr: // TODO(gri): should treat[] like parentheses and undo one level of depth p.expr1(x.X, token.HighestPrec, 1) p.print(x.Lbrack, token.LBRACK) indices := []ast.Expr{x.Low, x.High} if x.Max != nil { indices = append(indices, x.Max) } // determine if we need extra blanks around ':' var needsBlanks bool if depth <= 1 { var indexCount int var hasBinaries bool for _, x := range indices { if x != nil { indexCount++ if isBinary(x) { hasBinaries = true } } } if indexCount > 1 && hasBinaries { needsBlanks = true } } for i, x := range indices { if i > 0 { if indices[i-1] != nil && needsBlanks { p.print(blank) } p.print(token.COLON) if x != nil && needsBlanks { p.print(blank) } } if x != nil { p.expr0(x, depth+1) } } p.print(x.Rbrack, token.RBRACK) case *ast.CallExpr: if len(x.Args) > 1 { depth++ } var wasIndented bool if _, ok := x.Fun.(*ast.FuncType); ok { // conversions to literal function types require parentheses around the type p.print(token.LPAREN) wasIndented = p.possibleSelectorExpr(x.Fun, token.HighestPrec, depth) p.print(token.RPAREN) } else { wasIndented = p.possibleSelectorExpr(x.Fun, token.HighestPrec, depth) } if x.NoParenEnd != token.NoPos { p.print(blank) depth++ } else { p.print(x.Lparen, token.LPAREN) } if x.Ellipsis.IsValid() { p.exprList(x.Lparen, x.Args, depth, 0, x.Ellipsis, false) p.print(x.Ellipsis, token.ELLIPSIS) if x.Rparen.IsValid() && p.lineFor(x.Ellipsis) < p.lineFor(x.Rparen) { p.print(token.COMMA, formfeed) } } else { p.exprList(x.Lparen, x.Args, depth, commaTerm, x.Rparen, false) } if len(x.Kwargs) > 0 { if len(x.Args) > 0 { p.print(token.COMMA, blank) } for i, arg := range x.Kwargs { if i > 0 { p.print(token.COMMA, blank) } p.print(arg.Name, blank, token.ASSIGN, blank) p.expr0(arg.Value, depth) } } if x.NoParenEnd == token.NoPos { p.print(x.Rparen, token.RPAREN) } if wasIndented { p.print(unindent) } case *ast.CompositeLit: // composite literal elements that are composite literals themselves may have the type omitted if x.Type != nil { p.expr1(x.Type, token.HighestPrec, depth) } p.level++ p.print(x.Lbrace, token.LBRACE) p.exprList(x.Lbrace, x.Elts, 1, commaTerm, x.Rbrace, x.Incomplete) // do not insert extra line break following a /*-style comment // before the closing '}' as it might break the code if there // is no trailing ',' mode := noExtraLinebreak // do not insert extra blank following a /*-style comment // before the closing '}' unless the literal is empty if len(x.Elts) > 0 { mode |= noExtraBlank } // need the initial indent to print lone comments with // the proper level of indentation p.print(indent, unindent, mode, x.Rbrace, token.RBRACE, mode) p.level-- case *ast.MatrixLit: p.level++ p.print(x.Lbrack, token.LBRACK, newline, indent) var last = len(x.Elts) - 1 var incomplete bool for i, elts := range x.Elts { if i == last { incomplete = x.Incomplete } p.exprList(elts[0].Pos(), elts, 1, 0, elts[len(elts)-1].End(), incomplete) p.print(newline) } mode := noExtraLinebreak | noExtraBlank // need the initial indent to print lone comments with // the proper level of indentation p.print(unindent, mode, x.Rbrack, token.RBRACK, mode) p.level-- case *ast.Ellipsis: p.print(token.ELLIPSIS) if x.Elt != nil { p.expr(x.Elt) } case *ast.ArrayType: p.print(token.LBRACK) if x.Len != nil { p.expr(x.Len) } p.print(token.RBRACK) p.expr(x.Elt) case *ast.StructType: p.print(token.STRUCT) p.fieldList(x.Fields, true, x.Incomplete) case *ast.FuncType: p.print(token.FUNC) p.signature(x.Params, x.Results) case *ast.InterfaceType: p.print(token.INTERFACE) p.fieldList(x.Methods, false, x.Incomplete) case *ast.MapType: p.print(token.MAP, token.LBRACK) p.expr(x.Key) p.print(token.RBRACK) p.expr(x.Value) case *ast.ChanType: switch x.Dir { case ast.SEND | ast.RECV: p.print(token.CHAN) case ast.RECV: p.print(token.ARROW, token.CHAN) // x.Arrow and x.Pos() are the same case ast.SEND: p.print(token.CHAN, x.Arrow, token.ARROW) } p.print(blank) p.expr(x.Value) case *ast.TupleType: p.parameters(x.Fields) /* case *ast.TernaryExpr: p.expr1(x.X, token.HighestPrec, 1) p.expr0(x.Cond, 1) p.print(x.Question, token.QUESTION) p.expr0(x.Y, depth+1) p.print(x.Colon, token.COLON) p.expr0(x.Y, depth+1) */ case *ast.SliceLit: p.print(token.LBRACK) p.exprList(x.Lbrack, x.Elts, depth+1, commaTerm, x.Rbrack, x.Incomplete) mode := noExtraLinebreak if len(x.Elts) > 0 { mode |= noExtraBlank } p.print(mode, x.Rbrack, token.RBRACK, mode) case *ast.TupleLit: p.print(token.LPAREN) p.exprList(x.Lparen, x.Elts, depth+1, commaTerm, x.Rparen, false) mode := noExtraLinebreak if len(x.Elts) > 0 { mode |= noExtraBlank } p.print(mode, x.Rparen, token.RPAREN, mode) case *ast.ComprehensionExpr: switch x.Tok { case token.LBRACK: // [...] p.print(token.LBRACK) p.expr0(x.Elt, depth+1) p.print(blank) p.listForPhrase(x.Fors) p.print(token.RBRACK) default: // {...} p.print(token.LBRACE) if x.Elt != nil { if elt, ok := x.Elt.(*ast.KeyValueExpr); ok { p.expr0(elt.Key, depth+1) p.print(elt.Colon, token.COLON, blank) p.expr0(elt.Value, depth+1) } else { p.expr0(x.Elt, depth+1) } p.print(blank) } p.listForPhrase(x.Fors) p.print(token.RBRACE) } case *ast.ErrWrapExpr: p.expr(x.X) p.print(x.Tok) if x.Default != nil { p.print(token.COLON) p.expr(x.Default) } case *ast.LambdaExpr: if x.LhsHasParen { p.print(token.LPAREN) p.identList(x.Lhs, false) p.print(token.RPAREN, blank) } else if x.Lhs != nil { p.expr(x.Lhs[0]) p.print(blank) } p.print(token.DRARROW, blank) if x.RhsHasParen { p.print(token.LPAREN) p.exprList(token.NoPos, x.Rhs, 1, noIndent, token.NoPos, false) p.print(token.RPAREN) } else { p.expr(x.Rhs[0]) } case *ast.LambdaExpr2: if x.LhsHasParen { p.print(token.LPAREN) p.identList(x.Lhs, false) p.print(token.RPAREN, blank) } else if x.Lhs != nil { p.expr(x.Lhs[0]) p.print(blank) } p.print(token.DRARROW, blank) p.block(x.Body, 1) case *ast.RangeExpr: if x.First != nil { p.expr(x.First) } p.print(token.COLON) if x.Last != nil { p.expr(x.Last) } if x.Expr3 != nil { p.print(token.COLON) p.expr(x.Expr3) } case *ast.EnvExpr: p.print(token.ENV) if x.HasBrace() { p.print(token.LBRACE, x.Name, token.RBRACE) } else { p.print(x.Name) } case *ast.CondExpr: p.expr(x.X) p.print(token.AT) p.expr(x.Cond) case *ast.ElemEllipsis: p.expr(x.Elt) p.print(token.ELLIPSIS) case *ast.DomainTextLit: p.print(x.Domain) p.print(&ast.BasicLit{Kind: token.STRING, Value: x.Value}) default: log.Fatalf("unreachable %T\n", x) } } var ( in = &ast.Ident{Name: "in"} ) func (p *printer) listForPhrase(list []*ast.ForPhrase) { for i, x := range list { if i > 0 { p.print(blank) } p.print(token.FOR, blank) if x.Key != nil { p.expr(x.Key) p.print(token.COMMA, blank) } p.print(x.Value, blank) p.print(x.TokPos, in, blank) p.expr(x.X) if x.Cond != nil { p.print(blank, x.Cond.Pos(), token.IF, blank) if x.Init != nil { p.stmt(x.Init, false) p.print(token.SEMICOLON, blank) } p.expr(x.Cond) } } } func (p *printer) possibleSelectorExpr(expr ast.Expr, prec1, depth int) bool { if x, ok := expr.(*ast.SelectorExpr); ok { return p.selectorExpr(x, depth, true) } p.expr1(expr, prec1, depth) return false } // selectorExpr handles an *ast.SelectorExpr node and reports whether x spans // multiple lines. func (p *printer) selectorExpr(x *ast.SelectorExpr, depth int, isMethod bool) bool { p.expr1(x.X, token.HighestPrec, depth) p.print(token.PERIOD) if line := p.lineFor(x.Sel.Pos()); p.pos.IsValid() && p.pos.Line < line { p.print(indent, newline, x.Sel.Pos(), x.Sel) if !isMethod { p.print(unindent) } return true } p.print(x.Sel.Pos(), x.Sel) return false } func (p *printer) anySelectorExpr(x *ast.AnySelectorExpr, depth int) bool { p.expr1(x.X, token.HighestPrec, depth) p.print(token.PERIOD, token.MUL, token.MUL, token.PERIOD) // .**. if line := p.lineFor(x.Sel.Pos()); p.pos.IsValid() && p.pos.Line < line { p.print(indent, newline, x.Sel.Pos(), x.Sel, unindent) return true } p.print(x.Sel.Pos(), x.Sel) return false } func (p *printer) expr0(x ast.Expr, depth int) { p.expr1(x, token.LowestPrec, depth) } func (p *printer) expr(x ast.Expr) { const depth = 1 p.expr1(x, token.LowestPrec, depth) } // ---------------------------------------------------------------------------- // Statements // Print the statement list indented, but without a newline after the last statement. // Extra line breaks between statements in the source are respected but at most one // empty line is printed between statements. func (p *printer) stmtList(list []ast.Stmt, nindent int, nextIsRBrace bool) { if nindent > 0 { p.print(indent) } var line int i := 0 for _, s := range list { // ignore empty statements (was issue 3466) if _, isEmpty := s.(*ast.EmptyStmt); !isEmpty { // nindent == 0 only for lists of switch/select case clauses; // in those cases each clause is a new section if len(p.output) > 0 { // only print line break if we are not at the beginning of the output // (i.e., we are not printing only a partial program) p.linebreak(p.lineFor(s.Pos()), 1, ignore, i == 0 || nindent == 0 || p.linesFrom(line) > 0) } p.recordLine(&line) p.stmt(s, nextIsRBrace && i == len(list)-1) // labeled statements put labels on a separate line, but here // we only care about the start line of the actual statement // without label - correct line for each label for t := s; ; { lt, _ := t.(*ast.LabeledStmt) if lt == nil { break } line++ t = lt.Stmt } i++ } } if nindent > 0 { p.print(unindent) } } // block prints an *ast.BlockStmt; it always spans at least two lines. func (p *printer) block(b *ast.BlockStmt, nindent int) { p.print(b.Lbrace, token.LBRACE) p.stmtList(b.List, nindent, true) p.linebreak(p.lineFor(b.Rbrace), 1, ignore, true) p.print(b.Rbrace, token.RBRACE) } func isTypeName(x ast.Expr) bool { switch t := x.(type) { case *ast.Ident: return true case *ast.SelectorExpr: return isTypeName(t.X) } return false } func stripParens(x ast.Expr) ast.Expr { if px, strip := x.(*ast.ParenExpr); strip { // parentheses must not be stripped if there are any // unparenthesized composite literals starting with // a type name ast.Inspect(px.X, func(node ast.Node) bool { switch x := node.(type) { case *ast.ParenExpr: // parentheses protect enclosed composite literals return false case *ast.CompositeLit: if isTypeName(x.Type) { strip = false // do not strip parentheses } return false } // in all other cases, keep inspecting return true }) if strip { return stripParens(px.X) } } return x } func stripParensAlways(x ast.Expr) ast.Expr { if x, ok := x.(*ast.ParenExpr); ok { return stripParensAlways(x.X) } return x } func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, post ast.Stmt) { p.print(blank) needsBlank := false if init == nil && post == nil { // no semicolons required if expr != nil { p.expr(stripParens(expr)) needsBlank = true } } else { // all semicolons required // (they are not separators, print them explicitly) if init != nil { p.stmt(init, false) } p.print(token.SEMICOLON, blank) if expr != nil { p.expr(stripParens(expr)) needsBlank = true } if isForStmt { p.print(token.SEMICOLON, blank) needsBlank = false if post != nil { p.stmt(post, false) needsBlank = true } } } if needsBlank { p.print(blank) } } // indentList reports whether an expression list would look better if it // were indented wholesale (starting with the very first element, rather // than starting at the first line break). func (p *printer) indentList(list []ast.Expr) bool { // Heuristic: indentList reports whether there are more than one multi- // line element in the list, or if there is any element that is not // starting on the same line as the previous one ends. if len(list) >= 2 { var b = p.lineFor(list[0].Pos()) var e = p.lineFor(list[len(list)-1].End()) if 0 < b && b < e { // list spans multiple lines n := 0 // multi-line element count line := b for _, x := range list { xb := p.lineFor(x.Pos()) xe := p.lineFor(x.End()) if line < xb { // x is not starting on the same // line as the previous one ended return true } if xb < xe { // x is a multi-line element n++ } line = xe } return n > 1 } } return false } func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool) { p.print(stmt.Pos()) if p.commentedStmts != nil { if comments, ok := p.commentedStmts[stmt]; ok { p.setComment(comments) } } switch s := stmt.(type) { case *ast.BadStmt: p.print("BadStmt") case *ast.DeclStmt: p.decl(s.Decl) case *ast.EmptyStmt: // nothing to do case *ast.LabeledStmt: // a "correcting" unindent immediately following a line break // is applied before the line break if there is no comment // between (see writeWhitespace) p.print(unindent) p.expr(s.Label) p.print(s.Colon, token.COLON, indent) if e, isEmpty := s.Stmt.(*ast.EmptyStmt); isEmpty { if !nextIsRBrace { p.print(newline, e.Pos(), token.SEMICOLON) break } } else { p.linebreak(p.lineFor(s.Stmt.Pos()), 1, ignore, true) } p.stmt(s.Stmt, nextIsRBrace) case *ast.ExprStmt: if debugFormat { if e, ok := s.X.(*ast.CallExpr); ok { log.Println("==> ExprStmt", e.Fun) } } const depth = 1 p.expr0(s.X, depth) case *ast.SendStmt: const depth = 1 p.expr0(s.Chan, depth) p.print(blank, s.Arrow, token.ARROW, blank) for i, val := range s.Values { if i > 0 { p.print(token.COMMA, blank) } p.expr0(val, depth) } if s.Ellipsis.IsValid() { p.print(s.Ellipsis, token.ELLIPSIS) } case *ast.IncDecStmt: const depth = 1 p.expr0(s.X, depth+1) p.print(s.TokPos, s.Tok) case *ast.AssignStmt: if debugFormat { log.Println("==> AssignStmt", s.Lhs) } var depth = 1 if len(s.Lhs) > 1 && len(s.Rhs) > 1 { depth++ } p.exprList(s.Pos(), s.Lhs, depth, 0, s.TokPos, false) p.print(blank, s.TokPos, s.Tok, blank) p.exprList(s.TokPos, s.Rhs, depth, 0, token.NoPos, false) case *ast.GoStmt: p.print(token.GO, blank) p.expr(s.Call) case *ast.DeferStmt: p.print(token.DEFER, blank) p.expr(s.Call) case *ast.ReturnStmt: p.print(token.RETURN) if s.Results != nil { p.print(blank) // Use indentList heuristic to make corner cases look // better (issue 1207). A more systematic approach would // always indent, but this would cause significant // reformatting of the code base and not necessarily // lead to more nicely formatted code in general. if p.indentList(s.Results) { p.print(indent) // Use NoPos so that a newline never goes before // the results (see issue #32854). p.exprList(token.NoPos, s.Results, 1, noIndent, token.NoPos, false) p.print(unindent) } else { p.exprList(token.NoPos, s.Results, 1, 0, token.NoPos, false) } } case *ast.BranchStmt: p.print(s.Tok) if s.Label != nil { p.print(blank) p.expr(s.Label) } case *ast.BlockStmt: p.block(s, 1) case *ast.IfStmt: p.print(token.IF) p.controlClause(false, s.Init, s.Cond, nil) p.block(s.Body, 1) if s.Else != nil { p.print(blank, token.ELSE, blank) switch s.Else.(type) { case *ast.BlockStmt, *ast.IfStmt: p.stmt(s.Else, nextIsRBrace) default: // This can only happen with an incorrectly // constructed AST. Permit it but print so // that it can be parsed without errors. p.print(token.LBRACE, indent, formfeed) p.stmt(s.Else, true) p.print(unindent, formfeed, token.RBRACE) } } case *ast.CaseClause: if s.List != nil { p.print(token.CASE, blank) p.exprList(s.Pos(), s.List, 1, 0, s.Colon, false) } else { p.print(token.DEFAULT) } p.print(s.Colon, token.COLON) p.stmtList(s.Body, 1, nextIsRBrace) case *ast.SwitchStmt: p.print(token.SWITCH) p.controlClause(false, s.Init, s.Tag, nil) p.block(s.Body, 0) case *ast.TypeSwitchStmt: p.print(token.SWITCH) if s.Init != nil { p.print(blank) p.stmt(s.Init, false) p.print(token.SEMICOLON) } p.print(blank) p.stmt(s.Assign, false) p.print(blank) p.block(s.Body, 0) case *ast.CommClause: if s.Comm != nil { p.print(token.CASE, blank) p.stmt(s.Comm, false) } else { p.print(token.DEFAULT) } p.print(s.Colon, token.COLON) p.stmtList(s.Body, 1, nextIsRBrace) case *ast.SelectStmt: p.print(token.SELECT, blank) body := s.Body if len(body.List) == 0 && !p.commentBefore(p.posFor(body.Rbrace)) { // print empty select statement w/o comments on one line p.print(body.Lbrace, token.LBRACE, body.Rbrace, token.RBRACE) } else { p.block(body, 0) } case *ast.ForStmt: p.print(token.FOR) p.controlClause(true, s.Init, s.Cond, s.Post) p.block(s.Body, 1) case *ast.RangeStmt: p.print(token.FOR, blank) if s.Key != nil { p.expr(s.Key) if s.Value != nil { // use position of value following the comma as // comma position for correct comment placement p.print(s.Value.Pos(), token.COMMA, blank) p.expr(s.Value) } p.print(blank, s.TokPos, s.Tok, blank) } if !s.NoRangeOp { p.print(token.RANGE, blank) } p.expr(stripParens(s.X)) p.print(blank) p.block(s.Body, 1) case *ast.ForPhraseStmt: p.print(token.FOR, blank) if s.Key != nil { p.expr(s.Key) p.print(token.COMMA, blank) } p.expr(s.Value) p.print(blank, s.TokPos, in, blank) p.expr(s.X) if s.Cond != nil { p.print(blank, s.Cond.Pos(), token.IF, blank) p.expr(s.Cond) } p.print(blank) p.block(s.Body, 1) case *NewlineStmt: p.print(ignore) default: log.Printf("unreachable %T\n", s) } } // NewlineStmt represents a statement that formats as a newline type NewlineStmt struct { ast.EmptyStmt } // ---------------------------------------------------------------------------- // Declarations // The keepTypeColumn function determines if the type column of a series of // consecutive const or var declarations must be kept, or if initialization // values (V) can be placed in the type column (T) instead. The i'th entry // in the result slice is true if the type column in spec[i] must be kept. // // For example, the declaration: // // const ( // foobar int = 42 // comment // x = 7 // comment // foo // bar = 991 // ) // // leads to the type/values matrix below. A run of value columns (V) can // be moved into the type column if there is no type for any of the values // in that column (we only move entire columns so that they align properly). // // matrix formatted result // matrix // T V -> T V -> true there is a T and so the type // - V - V true column must be kept // - - - - false // - V V - false V is moved into T column func keepTypeColumn(specs []ast.Spec) []bool { m := make([]bool, len(specs)) populate := func(i, j int, keepType bool) { if keepType { for ; i < j; i++ { m[i] = true } } } i0 := -1 // if i0 >= 0 we are in a run and i0 is the start of the run var keepType bool for i, s := range specs { t := s.(*ast.ValueSpec) if t.Values != nil { if i0 < 0 { // start of a run of ValueSpecs with non-nil Values i0 = i keepType = false } } else { if i0 >= 0 { // end of a run populate(i0, i, keepType) i0 = -1 } } if t.Type != nil { keepType = true } } if i0 >= 0 { // end of a run populate(i0, len(specs), keepType) } return m } func (p *printer) valueSpec(s *ast.ValueSpec, keepType bool) { p.setComment(s.Doc) p.identList(s.Names, false) // always present extraTabs := 3 if s.Type != nil || keepType { if len(s.Names) > 0 { p.print(vtab) } extraTabs-- } if s.Type != nil { p.expr(s.Type) } if s.Tag != nil { if len(s.Names) > 0 { p.print(vtab) } p.print(vtab) p.expr(s.Tag) extraTabs-- } if s.Values != nil { p.print(vtab, token.ASSIGN, blank) p.exprList(token.NoPos, s.Values, 1, 0, token.NoPos, false) extraTabs-- } if s.Comment != nil { for ; extraTabs > 0; extraTabs-- { p.print(vtab) } p.setComment(s.Comment) } } func sanitizeImportPath(lit *ast.BasicLit) *ast.BasicLit { // Note: An unmodified AST generated by go/parser will already // contain a backward- or double-quoted path string that does // not contain any invalid characters, and most of the work // here is not needed. However, a modified or generated AST // may possibly contain non-canonical paths. Do the work in // all cases since it's not too hard and not speed-critical. // if we don't have a proper string, be conservative and return whatever we have if lit.Kind != token.STRING { return lit } s, err := strconv.Unquote(lit.Value) if err != nil { return lit } // if the string is an invalid path, return whatever we have // // spec: "Implementation restriction: A compiler may restrict // ImportPaths to non-empty strings using only characters belonging // to Unicode's L, M, N, P, and S general categories (the Graphic // characters without spaces) and may also exclude the characters // !"#$%&'()*,:;<=>?[\]^`{|} and the Unicode replacement character // U+FFFD." if s == "" { return lit } const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD" for _, r := range s { if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) { return lit } } // otherwise, return the double-quoted path s = strconv.Quote(s) if s == lit.Value { return lit // nothing wrong with lit } return &ast.BasicLit{ValuePos: lit.ValuePos, Kind: token.STRING, Value: s} } // The parameter n is the number of specs in the group. If doIndent is set, // multi-line identifier lists in the spec are indented when the first // linebreak is encountered. func (p *printer) spec(spec ast.Spec, n int, doIndent bool) { switch s := spec.(type) { case *ast.ImportSpec: p.setComment(s.Doc) if s.Name != nil { p.expr(s.Name) p.print(blank) } p.expr(sanitizeImportPath(s.Path)) p.setComment(s.Comment) p.print(s.EndPos) case *ast.ValueSpec: if n != 1 { p.internalError("expected n = 1; got", n) } p.setComment(s.Doc) p.identList(s.Names, doIndent) // always present if s.Type != nil { if len(s.Names) > 0 { p.print(blank) } p.expr(s.Type) } if s.Values != nil { p.print(blank, token.ASSIGN, blank) p.exprList(token.NoPos, s.Values, 1, 0, token.NoPos, false) } p.setComment(s.Comment) case *ast.TypeSpec: p.setComment(s.Doc) p.expr(s.Name) if n == 1 { p.print(blank) } else { p.print(vtab) } if s.Assign.IsValid() { p.print(token.ASSIGN, blank) } p.expr(s.Type) p.setComment(s.Comment) default: panic("unreachable") } } func (p *printer) genDecl(d *ast.GenDecl) { p.setComment(d.Doc) p.setPos(d.Pos()) p.print(d.Tok, blank) if d.Lparen.IsValid() || len(d.Specs) > 1 { // group of parenthesized declarations p.setPos(d.Lparen) p.print(token.LPAREN) if n := len(d.Specs); n > 0 { p.print(indent, formfeed) if n > 1 && (d.Tok == token.CONST || d.Tok == token.VAR) { // two or more grouped const/var declarations: // determine if the type column must be kept keepType := keepTypeColumn(d.Specs) var line int for i, s := range d.Specs { if i > 0 { p.linebreak(p.lineFor(s.Pos()), 1, ignore, p.linesFrom(line) > 0) } p.recordLine(&line) p.valueSpec(s.(*ast.ValueSpec), keepType[i]) } } else { var line int for i, s := range d.Specs { if i > 0 { p.linebreak(p.lineFor(s.Pos()), 1, ignore, p.linesFrom(line) > 0) } p.recordLine(&line) p.spec(s, n, false) } } p.print(unindent, formfeed) } p.setPos(d.Rparen) p.print(token.RPAREN) } else if len(d.Specs) > 0 { // single declaration p.spec(d.Specs[0], 1, true) } } // nodeSize determines the size of n in chars after formatting. // The result is <= maxSize if the node fits on one line with at // most maxSize chars and the formatted output doesn't contain // any control chars. Otherwise, the result is > maxSize. func (p *printer) nodeSize(n ast.Node, maxSize int) (size int) { // nodeSize invokes the printer, which may invoke nodeSize // recursively. For deep composite literal nests, this can // lead to an exponential algorithm. Remember previous // results to prune the recursion (was issue 1628). if size, found := p.nodeSizes[n]; found { return size } size = maxSize + 1 // assume n doesn't fit p.nodeSizes[n] = size // nodeSize computation must be independent of particular // style so that we always get the same decision; print // in RawFormat cfg := Config{Mode: RawFormat} var buf bytes.Buffer if err := cfg.fprint(&buf, p.fset, n, p.nodeSizes); err != nil { return } if buf.Len() <= maxSize { for _, ch := range buf.Bytes() { if ch < ' ' { return } } size = buf.Len() // n fits p.nodeSizes[n] = size } return } // numLines returns the number of lines spanned by node n in the original source. func (p *printer) numLines(n ast.Node) int { if from := n.Pos(); from.IsValid() { if to := n.End(); to.IsValid() { return p.lineFor(to) - p.lineFor(from) + 1 } } return infinity } // bodySize is like nodeSize but it is specialized for *ast.BlockStmt's. func (p *printer) bodySize(b *ast.BlockStmt, maxSize int) int { pos1 := b.Pos() pos2 := b.Rbrace if pos1.IsValid() && pos2.IsValid() && p.lineFor(pos1) != p.lineFor(pos2) { // opening and closing brace are on different lines - don't make it a one-liner return maxSize + 1 } if len(b.List) > 5 { // too many statements - don't make it a one-liner return maxSize + 1 } // otherwise, estimate body size bodySize := p.commentSizeBefore(p.posFor(pos2)) for i, s := range b.List { if bodySize > maxSize { break // no need to continue } if i > 0 { bodySize += 2 // space for a semicolon and blank } bodySize += p.nodeSize(s, maxSize) } return bodySize } // funcBody prints a function body following a function header of given headerSize. // If the header's and block's size are "small enough" and the block is "simple enough", // the block is printed on the current line, without line breaks, spaced from the header // by sep. Otherwise the block's opening "{" is printed on the current line, followed by // lines for the block's statements and its closing "}". func (p *printer) funcBody(headerSize int, sep whiteSpace, b *ast.BlockStmt) { if b == nil { return } // save/restore composite literal nesting level defer func(level int) { p.level = level }(p.level) p.level = 0 const maxSize = 100 if headerSize+p.bodySize(b, maxSize) <= maxSize { p.print(sep, b.Lbrace, token.LBRACE) if len(b.List) > 0 { p.print(blank) for i, s := range b.List { if i > 0 { p.print(token.SEMICOLON, blank) } p.stmt(s, i == len(b.List)-1) } p.print(blank) } p.print(noExtraLinebreak, b.Rbrace, token.RBRACE, noExtraLinebreak) return } if sep != ignore { p.print(blank) // always use blank } p.block(b, 1) } // funcBodyUnnamed prints a function body following a function header of given headerSize. // If the header's and block's size are "small enough" and the block is "simple enough", // the block is printed on the current line, without line breaks, spaced from the header // by sep. Otherwise the block's opening "{" is printed on the current line, followed by // lines for the block's statements and its closing "}". func (p *printer) funcBodyUnnamed(headerSize int, sep whiteSpace, b *ast.BlockStmt) { _, _ = headerSize, sep if b == nil { return } // save/restore composite literal nesting level defer func(level int) { p.level = level }(p.level) p.level = 0 // const maxSize = 100 // if headerSize+p.bodySize(b, maxSize) <= maxSize { // if len(b.List) > 0 { // p.print(blank) // for i, s := range b.List { // if i > 0 { // p.print(token.SEMICOLON, blank) // } // p.stmt(s, i == len(b.List)-1) // } // p.print(blank) // } // return // } /* if sep != ignore { // p.print(blank) // always use blank } */ var line int i := 0 for _, s := range b.List { // ignore empty statements (was issue 3466) if _, isEmpty := s.(*ast.EmptyStmt); !isEmpty { // nindent == 0 only for lists of switch/select case clauses; // in those cases each clause is a new section if len(p.output) > 0 && i > 0 { // only print line break if we are not at the beginning of the output // (i.e., we are not printing only a partial program) p.linebreak(p.lineFor(s.Pos()), 1, ignore, p.linesFrom(line) > 0) } p.recordLine(&line) p.stmt(s, true && i == len(b.List)-1) // labeled statements put labels on a separate line, but here // we only care about the start line of the actual statement // without label - correct line for each label for t := s; ; { lt, _ := t.(*ast.LabeledStmt) if lt == nil { break } line++ t = lt.Stmt } i++ } } } // distanceFrom returns the column difference between p.out (the current output // position) and startOutCol. If the start position is on a different line from // the current position (or either is unknown), the result is infinity. func (p *printer) distanceFrom(startPos token.Pos, startOutCol int) int { if startPos.IsValid() && p.pos.IsValid() && p.posFor(startPos).Line == p.pos.Line { return p.out.Column - startOutCol } return infinity } func (p *printer) funcDecl(d *ast.FuncDecl) { if debugFormat { log.Println("==> Format Func", d.Name.Name) } p.setComment(d.Doc) if p.shadowEntry == d { p.funcBodyUnnamed(0, vtab, d.Body) return } pos := d.Pos() p.print(pos, token.FUNC, blank) // We have to save startCol only after emitting FUNC; otherwise it can be on a // different line (all whitespace preceding the FUNC is emitted only when the // FUNC is emitted). startCol := p.out.Column - len("func ") if d.Recv != nil { if d.Static { // static method if !d.IsClass { if list := d.Recv.List; len(list) > 0 { p.expr(list[0].Type) } } p.print(token.PERIOD) } else if !d.IsClass { p.parameters(d.Recv) // method: print receiver p.print(blank) } } p.expr(d.Name) if d.Operator && d.Recv != nil { p.print(blank) } p.signature(d.Type.Params, d.Type.Results) p.funcBody(p.distanceFrom(d.Pos(), startCol), vtab, d.Body) } func (p *printer) overloadFuncDecl(d *ast.OverloadFuncDecl) { if debugFormat { log.Println("==> Format OverloadFunc", d.Name.Name) } p.setComment(d.Doc) pos := d.Pos() p.print(pos, token.FUNC, blank) if d.Recv != nil && !d.IsClass { p.parameters(d.Recv) // method: print receiver p.print(token.PERIOD) } p.expr(d.Name) p.print(blank, token.ASSIGN, blank, token.LPAREN, newline) for _, fn := range d.Funcs { p.print(indent) p.expr1(fn, token.LowestPrec, 1) p.print(unindent, newline) } p.print(token.RPAREN) } func (p *printer) decl(decl ast.Decl) { switch d := decl.(type) { case *ast.BadDecl: p.print(d.Pos(), "BadDecl") case *ast.GenDecl: p.genDecl(d) case *ast.FuncDecl: p.funcDecl(d) case *ast.OverloadFuncDecl: p.overloadFuncDecl(d) default: panic("unreachable") } } // ---------------------------------------------------------------------------- // Files func declToken(decl ast.Decl) (tok token.Token) { tok = token.ILLEGAL switch d := decl.(type) { case *ast.GenDecl: tok = d.Tok case *ast.FuncDecl: tok = token.FUNC } return } func (p *printer) declList(list []ast.Decl) { tok := token.ILLEGAL for _, d := range list { // skip no entry shadow if decl, ok := d.(*ast.FuncDecl); ok && decl.Shadow && decl != p.shadowEntry { continue } prev := tok tok = declToken(d) // If the declaration token changed (e.g., from CONST to TYPE) // or the next declaration has documentation associated with it, // print an empty line between top-level declarations. // (because p.linebreak is called with the position of d, which // is past any documentation, the minimum requirement is satisfied // even w/o the extra getDoc(d) nil-check - leave it in case the // linebreak logic improves - there's already a TODO). if len(p.output) > 0 { // only print line break if we are not at the beginning of the output // (i.e., we are not printing only a partial program) min := 1 if tok == token.FUNC || tok == token.TYPE || prev != tok || getDoc(d) != nil { min = 2 } // start a new section if the next declaration is a function // that spans multiple lines (see also issue #19544) p.linebreak(p.lineFor(d.Pos()), min, ignore, tok == token.FUNC && p.numLines(d) > 1) } p.decl(d) } } func (p *printer) file(src *ast.File) { p.shadowEntry = src.ShadowEntry p.setComment(src.Doc) if !src.NoPkgDecl { p.print(src.Pos(), token.PACKAGE, blank) p.expr(src.Name) } p.declList(src.Decls) p.print(newline) } ================================================ FILE: printer/printer.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package printer implements printing of AST nodes. package printer import ( "fmt" "io" "os" "strings" "text/tabwriter" "unicode" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/token" ) const ( maxNewlines = 2 // max. number of newlines between source text debug = false // enable for debugging infinity = 1 << 30 ) type whiteSpace byte const ( ignore = whiteSpace(0) blank = whiteSpace(' ') vtab = whiteSpace('\v') newline = whiteSpace('\n') formfeed = whiteSpace('\f') indent = whiteSpace('>') unindent = whiteSpace('<') ) // A pmode value represents the current printer mode. type pmode int const ( noExtraBlank pmode = 1 << iota // disables extra blank after /*-style comment noExtraLinebreak // disables extra line break after /*-style comment ) type commentInfo struct { cindex int // current comment index comment *ast.CommentGroup // = printer.comments[cindex]; or nil commentOffset int // = printer.posFor(printer.comments[cindex].List[0].Pos()).Offset; or infinity commentNewline bool // true if the comment group contains newlines } type printer struct { // Configuration (does not change after initialization) Config fset *token.FileSet // Current state output []byte // raw printer result indent int // current indentation level int // level == 0: outside composite literal; level > 0: inside composite literal mode pmode // current printer mode endAlignment bool // if set, terminate alignment immediately impliedSemi bool // if set, a linebreak implies a semicolon lastTok token.Token // last token printed (token.ILLEGAL if it's whitespace) prevOpen token.Token // previous non-brace "open" token (, [, or token.ILLEGAL wsbuf []whiteSpace // delayed white space // Positions // The out position differs from the pos position when the result // formatting differs from the source formatting (in the amount of // white space). If there's a difference and SourcePos is set in // ConfigMode, //line directives are used in the output to restore // original source positions for a reader. pos token.Position // current position in AST (source) space out token.Position // current position in output space last token.Position // value of pos after calling writeString linePtr *int // if set, record out.Line for the next token in *linePtr // The list of all source comments, in order of appearance. comments []*ast.CommentGroup // may be nil useNodeComments bool // if not set, ignore lead and line comments of nodes // Information about p.comments[p.cindex]; set up by nextComment. commentInfo // Cache of already computed node sizes. nodeSizes map[ast.Node]int // Cache of most recently computed line position. cachedPos token.Pos cachedLine int // line corresponding to cachedPos shadowEntry *ast.FuncDecl // ast.File NoEntrypoint commentedStmts map[ast.Stmt]*ast.CommentGroup // statements with leading comments (XGo) } func (p *printer) init(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) { p.Config = *cfg p.fset = fset p.pos = token.Position{Line: 1, Column: 1} p.out = token.Position{Line: 1, Column: 1} p.wsbuf = make([]whiteSpace, 0, 16) // whitespace sequences are short p.nodeSizes = nodeSizes p.cachedPos = -1 } func (p *printer) internalError(msg ...any) { if debug { fmt.Print(p.pos.String() + ": ") fmt.Println(msg...) panic("xgo/printer") } } // commentsHaveNewline reports whether a list of comments belonging to // an *ast.CommentGroup contains newlines. Because the position information // may only be partially correct, we also have to read the comment text. func (p *printer) commentsHaveNewline(list []*ast.Comment) bool { // len(list) > 0 line := p.lineFor(list[0].Pos()) for i, c := range list { if i > 0 && p.lineFor(list[i].Pos()) != line { // not all comments on the same line return true } if t := c.Text; len(t) >= 2 && (t[1] == '/' || strings.Contains(t, "\n")) { return true } } _ = line return false } func (p *printer) nextComment() { for p.cindex < len(p.comments) { c := p.comments[p.cindex] p.cindex++ if list := c.List; len(list) > 0 { p.comment = c p.commentOffset = p.posFor(list[0].Pos()).Offset p.commentNewline = p.commentsHaveNewline(list) return } // we should not reach here (correct ASTs don't have empty // ast.CommentGroup nodes), but be conservative and try again } // no more comments p.commentOffset = infinity } // commentBefore reports whether the current comment group occurs // before the next position in the source code and printing it does // not introduce implicit semicolons. func (p *printer) commentBefore(next token.Position) bool { return p.commentOffset < next.Offset && (!p.impliedSemi || !p.commentNewline) } // commentSizeBefore returns the estimated size of the // comments on the same line before the next position. func (p *printer) commentSizeBefore(next token.Position) int { // save/restore current p.commentInfo (p.nextComment() modifies it) defer func(info commentInfo) { p.commentInfo = info }(p.commentInfo) size := 0 for p.commentBefore(next) { for _, c := range p.comment.List { size += len(c.Text) } p.nextComment() } return size } // recordLine records the output line number for the next non-whitespace // token in *linePtr. It is used to compute an accurate line number for a // formatted construct, independent of pending (not yet emitted) whitespace // or comments. func (p *printer) recordLine(linePtr *int) { p.linePtr = linePtr } // linesFrom returns the number of output lines between the current // output line and the line argument, ignoring any pending (not yet // emitted) whitespace or comments. It is used to compute an accurate // size (in number of lines) for a formatted construct. func (p *printer) linesFrom(line int) int { return p.out.Line - line } func (p *printer) posFor(pos token.Pos) token.Position { // not used frequently enough to cache entire token.Position return p.fset.PositionFor(pos, false /* absolute position */) } func (p *printer) lineFor(pos token.Pos) int { if pos != p.cachedPos { p.cachedPos = pos p.cachedLine = p.fset.PositionFor(pos, false /* absolute position */).Line } return p.cachedLine } // writeLineDirective writes a //line directive if necessary. func (p *printer) writeLineDirective(pos token.Position) { if pos.IsValid() && (p.out.Line != pos.Line || p.out.Filename != pos.Filename) { p.output = append(p.output, tabwriter.Escape) // protect '\n' in //line from tabwriter interpretation p.output = append(p.output, fmt.Sprintf("//line %s:%d\n", pos.Filename, pos.Line)...) p.output = append(p.output, tabwriter.Escape) // p.out must match the //line directive p.out.Filename = pos.Filename p.out.Line = pos.Line } } // writeIndent writes indentation. func (p *printer) writeIndent() { // use "hard" htabs - indentation columns // must not be discarded by the tabwriter n := p.Config.Indent + p.indent // include base indentation for i := 0; i < n; i++ { p.output = append(p.output, '\t') } // update positions p.pos.Offset += n p.pos.Column += n p.out.Column += n } // writeByte writes ch n times to p.output and updates p.pos. // Only used to write formatting (white space) characters. func (p *printer) writeByte(ch byte, n int) { if p.endAlignment { // Ignore any alignment control character; // and at the end of the line, break with // a formfeed to indicate termination of // existing columns. switch ch { case '\t', '\v': ch = ' ' case '\n', '\f': ch = '\f' p.endAlignment = false } } if p.out.Column == 1 { // no need to write line directives before white space p.writeIndent() } for i := 0; i < n; i++ { p.output = append(p.output, ch) } // update positions p.pos.Offset += n if ch == '\n' || ch == '\f' { p.pos.Line += n p.out.Line += n p.pos.Column = 1 p.out.Column = 1 return } p.pos.Column += n p.out.Column += n } // writeString writes the string s to p.output and updates p.pos, p.out, // and p.last. If isLit is set, s is escaped w/ tabwriter.Escape characters // to protect s from being interpreted by the tabwriter. // // Note: writeString is only used to write Go tokens, literals, and // comments, all of which must be written literally. Thus, it is correct // to always set isLit = true. However, setting it explicitly only when // needed (i.e., when we don't know that s contains no tabs or line breaks) // avoids processing extra escape characters and reduces run time of the // printer benchmark by up to 10%. func (p *printer) writeString(pos token.Position, s string, isLit bool) { if p.out.Column == 1 { if p.Config.Mode&SourcePos != 0 { p.writeLineDirective(pos) } p.writeIndent() } if pos.IsValid() { // update p.pos (if pos is invalid, continue with existing p.pos) // Note: Must do this after handling line beginnings because // writeIndent updates p.pos if there's indentation, but p.pos // is the position of s. p.pos = pos } if isLit { // Protect s such that is passes through the tabwriter // unchanged. Note that valid Go programs cannot contain // tabwriter.Escape bytes since they do not appear in legal // UTF-8 sequences. p.output = append(p.output, tabwriter.Escape) switch p.lastTok { case token.CSTRING: p.output = append(p.output, 'c') case token.PYSTRING: p.output = append(p.output, 'p', 'y') } } if debug { p.output = append(p.output, fmt.Sprintf("/*%s*/", pos)...) // do not update p.pos! } p.output = append(p.output, s...) // update positions nlines := 0 var li int // index of last newline; valid if nlines > 0 for i := 0; i < len(s); i++ { // Raw string literals may contain any character except back quote (`). if ch := s[i]; ch == '\n' || ch == '\f' { // account for line break nlines++ li = i // A line break inside a literal will break whatever column // formatting is in place; ignore any further alignment through // the end of the line. p.endAlignment = true } } p.pos.Offset += len(s) if nlines > 0 { p.pos.Line += nlines p.out.Line += nlines c := len(s) - li p.pos.Column = c p.out.Column = c } else { p.pos.Column += len(s) p.out.Column += len(s) } if isLit { p.output = append(p.output, tabwriter.Escape) } p.last = p.pos } // writeCommentPrefix writes the whitespace before a comment. // If there is any pending whitespace, it consumes as much of // it as is likely to help position the comment nicely. // pos is the comment position, next the position of the item // after all pending comments, prev is the previous comment in // a group of comments (or nil), and tok is the next token. func (p *printer) writeCommentPrefix(pos, next token.Position, prev *ast.Comment, tok token.Token) { if len(p.output) == 0 { // the comment is the first item to be printed - don't write any whitespace return } if pos.IsValid() && pos.Filename != p.last.Filename { // comment in a different file - separate with newlines p.writeByte('\f', maxNewlines) return } if pos.Line == p.last.Line && (prev == nil || prev.Text[1] != '/') { // comment on the same line as last item: // separate with at least one separator hasSep := false if prev == nil { // first comment of a comment group j := 0 for i, ch := range p.wsbuf { switch ch { case blank: // ignore any blanks before a comment p.wsbuf[i] = ignore continue case vtab: // respect existing tabs - important // for proper formatting of commented structs hasSep = true continue case indent: // apply pending indentation continue } j = i break } p.writeWhitespace(j) } // make sure there is at least one separator if !hasSep { sep := byte('\t') if pos.Line == next.Line { // next item is on the same line as the comment // (which must be a /*-style comment): separate // with a blank instead of a tab sep = ' ' } p.writeByte(sep, 1) } } else { // comment on a different line: // separate with at least one line break droppedLinebreak := false j := 0 for i, ch := range p.wsbuf { switch ch { case blank, vtab: // ignore any horizontal whitespace before line breaks p.wsbuf[i] = ignore continue case indent: // apply pending indentation continue case unindent: // if this is not the last unindent, apply it // as it is (likely) belonging to the last // construct (e.g., a multi-line expression list) // and is not part of closing a block if i+1 < len(p.wsbuf) && p.wsbuf[i+1] == unindent { continue } // if the next token is not a closing }, apply the unindent // if it appears that the comment is aligned with the // token; otherwise assume the unindent is part of a // closing block and stop (this scenario appears with // comments before a case label where the comments // apply to the next case instead of the current one) if tok != token.RBRACE && pos.Column == next.Column { continue } case newline, formfeed: p.wsbuf[i] = ignore droppedLinebreak = prev == nil // record only if first comment of a group } j = i break } p.writeWhitespace(j) // determine number of linebreaks before the comment n := 0 if pos.IsValid() && p.last.IsValid() { n = pos.Line - p.last.Line if n < 0 { // should never happen n = 0 } } // at the package scope level only (p.indent == 0), // add an extra newline if we dropped one before: // this preserves a blank line before documentation // comments at the package scope level (issue 2570) if p.indent == 0 && droppedLinebreak { n++ } // make sure there is at least one line break // if the previous comment was a line comment if n == 0 && prev != nil && prev.Text[1] == '/' { n = 1 } if n > 0 { // use formfeeds to break columns before a comment; // this is analogous to using formfeeds to separate // individual lines of /*-style comments p.writeByte('\f', nlimit(n)) } } } // Returns true if s contains only white space // (only tabs and blanks can appear in the printer's context). func isBlank(s string) bool { for i := 0; i < len(s); i++ { if s[i] > ' ' { return false } } return true } // commonPrefix returns the common prefix of a and b. func commonPrefix(a, b string) string { i := 0 for i < len(a) && i < len(b) && a[i] == b[i] && (a[i] <= ' ' || a[i] == '*') { i++ } return a[0:i] } // trimRight returns s with trailing whitespace removed. func trimRight(s string) string { return strings.TrimRightFunc(s, unicode.IsSpace) } // stripCommonPrefix removes a common prefix from /*-style comment lines (unless no // comment line is indented, all but the first line have some form of space prefix). // The prefix is computed using heuristics such that is likely that the comment // contents are nicely laid out after re-printing each line using the printer's // current indentation. func stripCommonPrefix(lines []string) { if len(lines) <= 1 { return // at most one line - nothing to do } // len(lines) > 1 // The heuristic in this function tries to handle a few // common patterns of /*-style comments: Comments where // the opening /* and closing */ are aligned and the // rest of the comment text is aligned and indented with // blanks or tabs, cases with a vertical "line of stars" // on the left, and cases where the closing */ is on the // same line as the last comment text. // Compute maximum common white prefix of all but the first, // last, and blank lines, and replace blank lines with empty // lines (the first line starts with /* and has no prefix). // In cases where only the first and last lines are not blank, // such as two-line comments, or comments where all inner lines // are blank, consider the last line for the prefix computation // since otherwise the prefix would be empty. // // Note that the first and last line are never empty (they // contain the opening /* and closing */ respectively) and // thus they can be ignored by the blank line check. prefix := "" prefixSet := false if len(lines) > 2 { for i, line := range lines[1 : len(lines)-1] { if isBlank(line) { lines[1+i] = "" // range starts with lines[1] } else { if !prefixSet { prefix = line prefixSet = true } prefix = commonPrefix(prefix, line) } } } // If we don't have a prefix yet, consider the last line. if !prefixSet { line := lines[len(lines)-1] prefix = commonPrefix(line, line) } /* * Check for vertical "line of stars" and correct prefix accordingly. */ lineOfStars := false if i := strings.Index(prefix, "*"); i >= 0 { // Line of stars present. if i > 0 && prefix[i-1] == ' ' { i-- // remove trailing blank from prefix so stars remain aligned } prefix = prefix[0:i] lineOfStars = true } else { // No line of stars present. // Determine the white space on the first line after the /* // and before the beginning of the comment text, assume two // blanks instead of the /* unless the first character after // the /* is a tab. If the first comment line is empty but // for the opening /*, assume up to 3 blanks or a tab. This // whitespace may be found as suffix in the common prefix. first := lines[0] if isBlank(first[2:]) { // no comment text on the first line: // reduce prefix by up to 3 blanks or a tab // if present - this keeps comment text indented // relative to the /* and */'s if it was indented // in the first place i := len(prefix) for n := 0; n < 3 && i > 0 && prefix[i-1] == ' '; n++ { i-- } if i == len(prefix) && i > 0 && prefix[i-1] == '\t' { i-- } prefix = prefix[0:i] } else { // comment text on the first line suffix := make([]byte, len(first)) n := 2 // start after opening /* for n < len(first) && first[n] <= ' ' { suffix[n] = first[n] n++ } if n > 2 && suffix[2] == '\t' { // assume the '\t' compensates for the /* suffix = suffix[2:n] } else { // otherwise assume two blanks suffix[0], suffix[1] = ' ', ' ' suffix = suffix[0:n] } // Shorten the computed common prefix by the length of // suffix, if it is found as suffix of the prefix. prefix = strings.TrimSuffix(prefix, string(suffix)) } } // Handle last line: If it only contains a closing */, align it // with the opening /*, otherwise align the text with the other // lines. last := lines[len(lines)-1] closing := "*/" i := strings.Index(last, closing) // i >= 0 (closing is always present) if isBlank(last[0:i]) { // last line only contains closing */ if lineOfStars { closing = " */" // add blank to align final star } lines[len(lines)-1] = prefix + closing } else { // last line contains more comment text - assume // it is aligned like the other lines and include // in prefix computation prefix = commonPrefix(prefix, last) } // Remove the common prefix from all but the first and empty lines. for i, line := range lines { if i > 0 && line != "" { lines[i] = line[len(prefix):] } } } func (p *printer) writeComment(comment *ast.Comment) { text := comment.Text pos := p.posFor(comment.Pos()) const linePrefix = "//line " if strings.HasPrefix(text, linePrefix) && (!pos.IsValid() || pos.Column == 1) { // Possibly a //-style line directive. // Suspend indentation temporarily to keep line directive valid. defer func(indent int) { p.indent = indent }(p.indent) p.indent = 0 } // shortcut common case of //-style comments if text[1] == '/' { p.writeString(pos, trimRight(text), true) return } // for /*-style comments, print line by line and let the // write function take care of the proper indentation lines := strings.Split(text, "\n") // The comment started in the first column but is going // to be indented. For an idempotent result, add indentation // to all lines such that they look like they were indented // before - this will make sure the common prefix computation // is the same independent of how many times formatting is // applied (was issue 1835). if pos.IsValid() && pos.Column == 1 && p.indent > 0 { for i, line := range lines[1:] { lines[1+i] = " " + line } } stripCommonPrefix(lines) // write comment lines, separated by formfeed, // without a line break after the last line for i, line := range lines { if i > 0 { p.writeByte('\f', 1) pos = p.pos } if len(line) > 0 { p.writeString(pos, trimRight(line), true) } } } // writeCommentSuffix writes a line break after a comment if indicated // and processes any leftover indentation information. If a line break // is needed, the kind of break (newline vs formfeed) depends on the // pending whitespace. The writeCommentSuffix result indicates if a // newline was written or if a formfeed was dropped from the whitespace // buffer. func (p *printer) writeCommentSuffix(needsLinebreak bool) (wroteNewline, droppedFF bool) { for i, ch := range p.wsbuf { switch ch { case blank, vtab: // ignore trailing whitespace p.wsbuf[i] = ignore case indent, unindent: // don't lose indentation information case newline, formfeed: // if we need a line break, keep exactly one // but remember if we dropped any formfeeds if needsLinebreak { needsLinebreak = false wroteNewline = true } else { if ch == formfeed { droppedFF = true } p.wsbuf[i] = ignore } } } p.writeWhitespace(len(p.wsbuf)) // make sure we have a line break if needsLinebreak { p.writeByte('\n', 1) wroteNewline = true } return } // containsLinebreak reports whether the whitespace buffer contains any line breaks. func (p *printer) containsLinebreak() bool { for _, ch := range p.wsbuf { if ch == newline || ch == formfeed { return true } } return false } // intersperseComments consumes all comments that appear before the next token // tok and prints it together with the buffered whitespace (i.e., the whitespace // that needs to be written before the next token). A heuristic is used to mix // the comments and whitespace. The intersperseComments result indicates if a // newline was written or if a formfeed was dropped from the whitespace buffer. func (p *printer) intersperseComments(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) { var last *ast.Comment for p.commentBefore(next) { for _, c := range p.comment.List { p.writeCommentPrefix(p.posFor(c.Pos()), next, last, tok) p.writeComment(c) last = c } p.nextComment() } if last != nil { // If the last comment is a /*-style comment and the next item // follows on the same line but is not a comma, and not a "closing" // token immediately following its corresponding "opening" token, // add an extra separator unless explicitly disabled. Use a blank // as separator unless we have pending linebreaks, they are not // disabled, and we are outside a composite literal, in which case // we want a linebreak (issue 15137). // TODO(gri) This has become overly complicated. We should be able // to track whether we're inside an expression or statement and // use that information to decide more directly. needsLinebreak := false if p.mode&noExtraBlank == 0 && last.Text[1] == '*' && p.lineFor(last.Pos()) == next.Line && tok != token.COMMA && (tok != token.RPAREN || p.prevOpen == token.LPAREN) && (tok != token.RBRACK || p.prevOpen == token.LBRACK) { if p.containsLinebreak() && p.mode&noExtraLinebreak == 0 && p.level == 0 { needsLinebreak = true } else { p.writeByte(' ', 1) } } // Ensure that there is a line break after a //-style comment, // before EOF, and before a closing '}' unless explicitly disabled. if last.Text[1] == '/' || tok == token.EOF || tok == token.RBRACE && p.mode&noExtraLinebreak == 0 { needsLinebreak = true } return p.writeCommentSuffix(needsLinebreak) } // no comment was written - we should never reach here since // intersperseComments should not be called in that case p.internalError("intersperseComments called without pending comments") return } // writeWhitespace writes the first n whitespace entries. func (p *printer) writeWhitespace(n int) { // write entries for i := 0; i < n; i++ { switch ch := p.wsbuf[i]; ch { case ignore: // ignore! case indent: p.indent++ case unindent: p.indent-- if p.indent < 0 { p.internalError("negative indentation:", p.indent) p.indent = 0 } case newline, formfeed: // A line break immediately followed by a "correcting" // unindent is swapped with the unindent - this permits // proper label positioning. If a comment is between // the line break and the label, the unindent is not // part of the comment whitespace prefix and the comment // will be positioned correctly indented. if i+1 < n && p.wsbuf[i+1] == unindent { // Use a formfeed to terminate the current section. // Otherwise, a long label name on the next line leading // to a wide column may increase the indentation column // of lines before the label; effectively leading to wrong // indentation. p.wsbuf[i], p.wsbuf[i+1] = unindent, formfeed i-- // do it again continue } fallthrough default: p.writeByte(byte(ch), 1) } } // shift remaining entries down l := copy(p.wsbuf, p.wsbuf[n:]) p.wsbuf = p.wsbuf[:l] } // ---------------------------------------------------------------------------- // Printing interface // nlimit limits n to maxNewlines. func nlimit(n int) int { if n > maxNewlines { n = maxNewlines } return n } func mayCombine(prev token.Token, next byte) (b bool) { switch prev { case token.INT: b = next == '.' // 1. case token.ADD: b = next == '+' // ++ case token.SUB: b = next == '-' // -- case token.QUO: b = next == '*' // /* case token.LSS: b = next == '-' || next == '<' // <- or << case token.AND: b = next == '&' || next == '^' // && or &^ } return } func (p *printer) setPos(pos token.Pos) { if pos.IsValid() { p.pos = p.posFor(pos) // accurate position of next item } } // print prints a list of "items" (roughly corresponding to syntactic // tokens, but also including whitespace and formatting information). // It is the only print function that should be called directly from // any of the AST printing functions in nodes.go. // // Whitespace is accumulated until a non-whitespace token appears. Any // comments that need to appear before that token are printed first, // taking into account the amount and structure of any pending white- // space for best comment placement. Then, any leftover whitespace is // printed, followed by the actual token. func (p *printer) print(args ...any) { for _, arg := range args { // information about the current arg var data string var isLit bool var impliedSemi bool // value for p.impliedSemi after this arg // record previous opening token, if any switch p.lastTok { case token.ILLEGAL: // ignore (white space) case token.LPAREN, token.LBRACK: p.prevOpen = p.lastTok default: // other tokens followed any opening token p.prevOpen = token.ILLEGAL } switch x := arg.(type) { case pmode: // toggle printer mode p.mode ^= x continue case whiteSpace: if x == ignore { // don't add ignore's to the buffer; they // may screw up "correcting" unindents (see // LabeledStmt) continue } i := len(p.wsbuf) if i == cap(p.wsbuf) { // Whitespace sequences are very short so this should // never happen. Handle gracefully (but possibly with // bad comment placement) if it does happen. p.writeWhitespace(i) i = 0 } p.wsbuf = p.wsbuf[0 : i+1] p.wsbuf[i] = x if x == newline || x == formfeed { // newlines affect the current state (p.impliedSemi) // and not the state after printing arg (impliedSemi) // because comments can be interspersed before the arg // in this case p.impliedSemi = false } p.lastTok = token.ILLEGAL continue case *ast.Ident: data = x.Name impliedSemi = true p.lastTok = token.IDENT case *ast.BasicLit: data = x.Value isLit = true impliedSemi = true p.lastTok = x.Kind case token.Token: s := x.String() if mayCombine(p.lastTok, s[0]) { // the previous and the current token must be // separated by a blank otherwise they combine // into a different incorrect token sequence // (except for token.INT followed by a '.' this // should never happen because it is taken care // of via binary expression formatting) if len(p.wsbuf) != 0 { p.internalError("whitespace buffer not empty") } p.wsbuf = p.wsbuf[0:1] p.wsbuf[0] = ' ' } data = s // some keywords followed by a newline imply a semicolon switch x { case token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN, token.INC, token.DEC, token.RPAREN, token.RBRACK, token.RBRACE: impliedSemi = true } p.lastTok = x case token.Pos: if x.IsValid() { p.pos = p.posFor(x) // accurate position of next item } continue case string: // incorrect AST - print error message data = x isLit = true impliedSemi = true p.lastTok = token.STRING default: fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", arg, arg) panic("xgo/printer type") } // data != "" next := p.pos // estimated/accurate position of next item wroteNewline, droppedFF := p.flush(next, p.lastTok) // intersperse extra newlines if present in the source and // if they don't cause extra semicolons (don't do this in // flush as it will cause extra newlines at the end of a file) if !p.impliedSemi { n := nlimit(next.Line - p.pos.Line) // don't exceed maxNewlines if we already wrote one if wroteNewline && n == maxNewlines { n = maxNewlines - 1 } if n > 0 { ch := byte('\n') if droppedFF { ch = '\f' // use formfeed since we dropped one before } p.writeByte(ch, n) impliedSemi = false } } // the next token starts now - record its line number if requested if p.linePtr != nil { *p.linePtr = p.out.Line p.linePtr = nil } p.writeString(next, data, isLit) p.impliedSemi = impliedSemi } } // flush prints any pending comments and whitespace occurring textually // before the position of the next token tok. The flush result indicates // if a newline was written or if a formfeed was dropped from the whitespace // buffer. func (p *printer) flush(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) { if p.commentBefore(next) { // if there are comments before the next item, intersperse them wroteNewline, droppedFF = p.intersperseComments(next, tok) } else { // otherwise, write any leftover whitespace p.writeWhitespace(len(p.wsbuf)) } return } // getDoc returns the ast.CommentGroup associated with n, if any. func getDoc(n ast.Node) *ast.CommentGroup { switch n := n.(type) { case *ast.Field: return n.Doc case *ast.ImportSpec: return n.Doc case *ast.ValueSpec: return n.Doc case *ast.TypeSpec: return n.Doc case *ast.GenDecl: return n.Doc case *ast.FuncDecl: return n.Doc case *ast.File: return n.Doc } return nil } func getLastComment(n ast.Node) *ast.CommentGroup { switch n := n.(type) { case *ast.Field: return n.Comment case *ast.ImportSpec: return n.Comment case *ast.ValueSpec: return n.Comment case *ast.TypeSpec: return n.Comment case *ast.GenDecl: if len(n.Specs) > 0 { return getLastComment(n.Specs[len(n.Specs)-1]) } case *ast.File: if len(n.Comments) > 0 { return n.Comments[len(n.Comments)-1] } } return nil } func (p *printer) printNode(node any) error { // unpack *CommentedNode or *CommentedNodes, if any var comments []*ast.CommentGroup if cnodes, ok := node.(*CommentedNodes); ok { node = cnodes.Node p.commentedStmts = cnodes.CommentedStmts } else if cnode, ok := node.(*CommentedNode); ok { node = cnode.Node comments = cnode.Comments } if comments != nil { // commented node - restrict comment list to relevant range n, ok := node.(ast.Node) if !ok { goto unsupported } beg := n.Pos() end := n.End() // if the node has associated documentation, // include that commentgroup in the range // (the comment list is sorted in the order // of the comment appearance in the source code) if doc := getDoc(n); doc != nil { beg = doc.Pos() } if com := getLastComment(n); com != nil { if e := com.End(); e > end { end = e } } // token.Pos values are global offsets, we can // compare them directly i := 0 for i < len(comments) && comments[i].End() < beg { i++ } j := i for j < len(comments) && comments[j].Pos() < end { j++ } if i < j { p.comments = comments[i:j] } } else if n, ok := node.(*ast.File); ok { // use ast.File comments, if any p.comments = n.Comments } // if there are no comments, use node comments p.useNodeComments = p.comments == nil // get comments ready for use p.nextComment() // format node switch n := node.(type) { case ast.Expr: p.expr(n) case ast.Stmt: // A labeled statement will un-indent to position the label. // Set p.indent to 1 so we don't get indent "underflow". if _, ok := n.(*ast.LabeledStmt); ok { p.indent = 1 } p.stmt(n, false) case ast.Decl: p.decl(n) case ast.Spec: p.spec(n, 1, false) case []ast.Stmt: // A labeled statement will un-indent to position the label. // Set p.indent to 1 so we don't get indent "underflow". for _, s := range n { if _, ok := s.(*ast.LabeledStmt); ok { p.indent = 1 } } p.stmtList(n, 0, false) case []ast.Decl: p.declList(n) case *ast.File: p.file(n) default: goto unsupported } return nil unsupported: return fmt.Errorf("go/printer: unsupported node type %T", node) } // ---------------------------------------------------------------------------- // Trimmer // A trimmer is an io.Writer filter for stripping tabwriter.Escape // characters, trailing blanks and tabs, and for converting formfeed // and vtab characters into newlines and htabs (in case no tabwriter // is used). Text bracketed by tabwriter.Escape characters is passed // through unchanged. type trimmer struct { output io.Writer state int space []byte } // trimmer is implemented as a state machine. // It can be in one of the following states: const ( inSpace = iota // inside space inEscape // inside text bracketed by tabwriter.Escapes inText // inside text ) func (p *trimmer) resetSpace() { p.state = inSpace p.space = p.space[0:0] } // Design note: It is tempting to eliminate extra blanks occurring in // whitespace in this function as it could simplify some // of the blanks logic in the node printing functions. // However, this would mess up any formatting done by // the tabwriter. var aNewline = []byte("\n") func (p *trimmer) Write(data []byte) (n int, err error) { // invariants: // p.state == inSpace: // p.space is unwritten // p.state == inEscape, inText: // data[m:n] is unwritten m := 0 var b byte for n, b = range data { if b == '\v' { b = '\t' // convert to htab } switch p.state { case inSpace: switch b { case '\t', ' ': p.space = append(p.space, b) case '\n', '\f': p.resetSpace() // discard trailing space _, err = p.output.Write(aNewline) case tabwriter.Escape: _, err = p.output.Write(p.space) p.state = inEscape m = n + 1 // +1: skip tabwriter.Escape default: _, err = p.output.Write(p.space) p.state = inText m = n } case inEscape: if b == tabwriter.Escape { _, err = p.output.Write(data[m:n]) p.resetSpace() } case inText: switch b { case '\t', ' ': _, err = p.output.Write(data[m:n]) p.resetSpace() p.space = append(p.space, b) case '\n', '\f': _, err = p.output.Write(data[m:n]) p.resetSpace() if err == nil { _, err = p.output.Write(aNewline) } case tabwriter.Escape: _, err = p.output.Write(data[m:n]) p.state = inEscape m = n + 1 // +1: skip tabwriter.Escape } default: panic("unreachable") } if err != nil { return } } n = len(data) switch p.state { case inEscape, inText: _, err = p.output.Write(data[m:n]) p.resetSpace() } return } // ---------------------------------------------------------------------------- // Public interface // A Mode value is a set of flags (or 0). They control printing. type Mode uint const ( RawFormat Mode = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored TabIndent // use tabs for indentation independent of UseSpaces UseSpaces // use spaces instead of tabs for alignment SourcePos // emit //line directives to preserve original source positions ) // A Config node controls the output of Fprint. type Config struct { Mode Mode // default: 0 Tabwidth int // default: 8 Indent int // default: 0 (all code is indented at least by this much) } // fprint implements Fprint and takes a nodesSizes map for setting up the printer state. func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node any, nodeSizes map[ast.Node]int) (err error) { // print node var p printer p.init(cfg, fset, nodeSizes) if err = p.printNode(node); err != nil { return } // print outstanding comments p.impliedSemi = false // EOF acts like a newline p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF) // redirect output through a trimmer to eliminate trailing whitespace // (Input to a tabwriter must be untrimmed since trailing tabs provide // formatting information. The tabwriter could provide trimming // functionality but no tabwriter is used when RawFormat is set.) output = &trimmer{output: output} // redirect output through a tabwriter if necessary if cfg.Mode&RawFormat == 0 { minwidth := cfg.Tabwidth padchar := byte('\t') if cfg.Mode&UseSpaces != 0 { padchar = ' ' } twmode := tabwriter.DiscardEmptyColumns if cfg.Mode&TabIndent != 0 { minwidth = 0 twmode |= tabwriter.TabIndent } output = tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode) } // write printer result via tabwriter/trimmer to output if _, err = output.Write(p.output); err != nil { return } // flush tabwriter, if any if tw, _ := output.(*tabwriter.Writer); tw != nil { err = tw.Flush() } return } // A CommentedNode bundles an AST node and corresponding comments. // It may be provided as argument to any of the Fprint functions. type CommentedNode struct { Node any // *ast.File, or ast.Expr, ast.Decl, ast.Spec, or ast.Stmt Comments []*ast.CommentGroup } // CommentedNodes holds an AST node and a map of statements with their leading comments. // This allows attaching diagnostic comments to specific statements during code generation. type CommentedNodes struct { Node any // *ast.File, or ast.Expr, ast.Decl, ast.Spec, or ast.Stmt CommentedStmts map[ast.Stmt]*ast.CommentGroup // statements with their leading comments } // Fprint "pretty-prints" an AST node to output for a given configuration cfg. // Position information is interpreted relative to the file set fset. // The node type must be *ast.File, *CommentedNode, *CommentedNodes, []ast.Decl, []ast.Stmt, // or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt. func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node any) error { return cfg.fprint(output, fset, node, make(map[ast.Node]int)) } // Fprint "pretty-prints" an AST node to output. // It calls Config.Fprint with default settings. // Note that gofmt uses tabs for indentation but spaces for alignment; // use format.Node (package go/format) for output that matches gofmt. func Fprint(output io.Writer, fset *token.FileSet, node any) error { return (&Config{Tabwidth: 8}).Fprint(output, fset, node) } ================================================ FILE: printer/printer_test.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package printer import ( "bytes" "fmt" "io" "io/ioutil" "testing" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/token" ) const ( dataDir = "demo" tabwidth = 8 ) var fset = token.NewFileSet() type checkMode uint const ( export checkMode = 1 << iota rawFormat idempotent ) // format parses src, prints the corresponding AST, verifies the resulting // src is syntactically correct, and returns the resulting src or an error // if any. func format(src []byte, mode checkMode) ([]byte, error) { // parse src f, err := parser.ParseFile(fset, "", src, parser.ParseComments) if err != nil { return nil, fmt.Errorf("parse: %s\n%s", err, src) } // filter exports if necessary if mode&export != 0 { ast.FileExports(f) // ignore result f.Comments = nil // don't print comments that are not in AST } // determine printer configuration cfg := Config{Tabwidth: tabwidth} if mode&rawFormat != 0 { cfg.Mode |= RawFormat } // print AST var buf bytes.Buffer if err := cfg.Fprint(&buf, fset, f); err != nil { return nil, fmt.Errorf("print: %s", err) } // make sure formatted output is syntactically correct res := buf.Bytes() if _, err := parser.ParseFile(fset, "", res, 0); err != nil { return nil, fmt.Errorf("re-parse: %s\n%s", err, buf.Bytes()) } return res, nil } // TestLineComments, using a simple test case, checks that consecutive line // comments are properly terminated with a newline even if the AST position // information is incorrect. func TestLineComments(t *testing.T) { const src = `// comment 1 // comment 2 // comment 3 # comment 4 package main ` fset := token.NewFileSet() f, err := parser.ParseFile(fset, "", src, parser.ParseComments) if err != nil { panic(err) // error in test } var buf bytes.Buffer fset = token.NewFileSet() // use the wrong file set Fprint(&buf, fset, f) nlines := 0 for _, ch := range buf.Bytes() { if ch == '\n' { nlines++ } } const expected = 4 if nlines < expected { t.Errorf("got %d, expected %d\n", nlines, expected) t.Errorf("result:\n%s", buf.Bytes()) } } // Verify that the printer can be invoked during initialization. func init() { const name = "foobar" var buf bytes.Buffer if err := Fprint(&buf, fset, &ast.Ident{Name: name}); err != nil { panic(err) // error in test } // in debug mode, the result contains additional information; // ignore it if s := buf.String(); !debug && s != name { panic("got " + s + ", want " + name) } } // testComment verifies that f can be parsed again after printing it // with its first comment set to comment at any possible source offset. func testComment(t *testing.T, f *ast.File, srclen int, comment *ast.Comment) { f.Comments[0].List[0] = comment var buf bytes.Buffer for offs := 0; offs <= srclen; offs++ { buf.Reset() // Printing f should result in a correct program no // matter what the (incorrect) comment position is. if err := Fprint(&buf, fset, f); err != nil { t.Error(err) } if _, err := parser.ParseFile(fset, "", buf.Bytes(), 0); err != nil { t.Fatalf("incorrect program for pos = %d:\n%s", comment.Slash, buf.String()) } // Position information is just an offset. // Move comment one byte down in the source. comment.Slash++ } } // Verify that the printer produces a correct program // even if the position information of comments introducing newlines // is incorrect. func TestBadComments(t *testing.T) { t.Parallel() const src = ` // first comment - text and position changed by test package p import "fmt" const pi = 3.14 // rough circle var ( x, y, z int = 1, 2, 3 u, v float64 ) func fibo(n int) { if n < 2 { return n /* seed values */ } return fibo(n-1) + fibo(n-2) } ` f, err := parser.ParseFile(fset, "", src, parser.ParseComments) if err != nil { t.Error(err) // error in test } comment := f.Comments[0].List[0] pos := comment.Pos() if fset.PositionFor(pos, false /* absolute position */).Offset != 1 { t.Error("expected offset 1") // error in test } testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "//-style comment"}) testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style comment */"}) testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style \n comment */"}) testComment(t, f, len(src), &ast.Comment{Slash: pos, Text: "/*-style comment \n\n\n */"}) } type visitor chan *ast.Ident func (v visitor) Visit(n ast.Node) (w ast.Visitor) { if ident, ok := n.(*ast.Ident); ok { v <- ident } return v } // idents is an iterator that returns all idents in f via the result channel. func idents(f *ast.File) <-chan *ast.Ident { v := make(visitor) go func() { ast.Walk(v, f) close(v) }() return v } // identCount returns the number of identifiers found in f. func identCount(f *ast.File) int { n := 0 for range idents(f) { n++ } return n } // Verify that the SourcePos mode emits correct //line directives // by testing that position information for matching identifiers // is maintained. func TestSourcePos(t *testing.T) { const src = ` package p import ( "go/printer"; "math" ) const pi = 3.14; var x = 0 type t struct{ x, y, z int; u, v, w float32 } func (t *t) foo(a, b, c int) int { return a*t.x + b*t.y + // two extra lines here // ... c*t.z } ` // parse original f1, err := parser.ParseFile(fset, "src", src, parser.ParseComments) if err != nil { t.Fatal(err) } // pretty-print original var buf bytes.Buffer err = (&Config{Mode: UseSpaces | SourcePos, Tabwidth: 8}).Fprint(&buf, fset, f1) if err != nil { t.Fatal(err) } // parse pretty printed original // (//line directives must be interpreted even w/o parser.ParseComments set) f2, err := parser.ParseFile(fset, "", buf.Bytes(), 0) if err != nil { t.Fatalf("%s\n%s", err, buf.Bytes()) } // At this point the position information of identifiers in f2 should // match the position information of corresponding identifiers in f1. // number of identifiers must be > 0 (test should run) and must match n1 := identCount(f1) n2 := identCount(f2) if n1 == 0 { t.Fatal("got no idents") } if n2 != n1 { t.Errorf("got %d idents; want %d", n2, n1) } // verify that all identifiers have correct line information i2range := idents(f2) for i1 := range idents(f1) { i2 := <-i2range if i2.Name != i1.Name { t.Errorf("got ident %s; want %s", i2.Name, i1.Name) } // here we care about the relative (line-directive adjusted) positions l1 := fset.Position(i1.Pos()).Line l2 := fset.Position(i2.Pos()).Line if l2 != l1 { t.Errorf("got line %d; want %d for %s", l2, l1, i1.Name) } } if t.Failed() { t.Logf("\n%s", buf.Bytes()) } } // Verify that the SourcePos mode doesn't emit unnecessary //line directives // before empty lines. func TestIssue5945(t *testing.T) { const orig = ` package p // line 2 func f() {} // line 3 var x, y, z int func g() { // line 8 } ` const want = `//line src.go:2 package p //line src.go:3 func f() {} var x, y, z int //line src.go:8 func g() { } ` // parse original f1, err := parser.ParseFile(fset, "src.go", orig, 0) if err != nil { t.Fatal(err) } // pretty-print original var buf bytes.Buffer err = (&Config{Mode: UseSpaces | SourcePos, Tabwidth: 8}).Fprint(&buf, fset, f1) if err != nil { t.Fatal(err) } got := buf.String() // compare original with desired output if got != want { t.Errorf("got:\n%s\nwant:\n%s\n", got, want) } } var decls = []string{ `import "fmt"`, "const pi = 3.1415\nconst e = 2.71828\n\nvar x = pi", "func sum(x, y int) int\t{ return x + y }", } func TestDeclLists(t *testing.T) { for _, src := range decls { file, err := parser.ParseFile(fset, "", "package p;"+src, parser.ParseComments) if err != nil { panic(err) // error in test } var buf bytes.Buffer err = Fprint(&buf, fset, file.Decls) // only print declarations if err != nil { panic(err) // error in test } out := buf.String() if out != src { t.Errorf("\ngot : %q\nwant: %q\n", out, src) } } } var stmts = []string{ "i := 0", "select {}\nvar a, b = 1, 2\nreturn a + b", "go f()\ndefer func() {}()", } func TestStmtLists(t *testing.T) { for _, src := range stmts { file, err := parser.ParseFile(fset, "", "package p; func _() {"+src+"}", parser.ParseComments) if err != nil { panic(err) // error in test } var buf bytes.Buffer err = Fprint(&buf, fset, file.Decls[0].(*ast.FuncDecl).Body.List) // only print statements if err != nil { panic(err) // error in test } out := buf.String() if out != src { t.Errorf("\ngot : %q\nwant: %q\n", out, src) } } } func TestBaseIndent(t *testing.T) { t.Parallel() // The testfile must not contain multi-line raw strings since those // are not indented (because their values must not change) and make // this test fail. const filename = "printer.go" src, err := ioutil.ReadFile(filename) if err != nil { panic(err) // error in test } file, err := parser.ParseFile(fset, filename, src, 0) if err != nil { panic(err) // error in test } for indent := 0; indent < 4; indent++ { indent := indent t.Run(fmt.Sprint(indent), func(t *testing.T) { t.Parallel() var buf bytes.Buffer (&Config{Tabwidth: tabwidth, Indent: indent}).Fprint(&buf, fset, file) // all code must be indented by at least 'indent' tabs lines := bytes.Split(buf.Bytes(), []byte{'\n'}) for i, line := range lines { if len(line) == 0 { continue // empty lines don't have indentation } n := 0 for j, b := range line { if b != '\t' { // end of indentation n = j break } } if n < indent { t.Errorf("line %d: got only %d tabs; want at least %d: %q", i, n, indent, line) } } }) } } // TestFuncType tests that an ast.FuncType with a nil Params field // can be printed (per go/ast specification). Test case for issue 3870. func TestFuncType(t *testing.T) { src := &ast.File{ Name: &ast.Ident{Name: "p"}, Decls: []ast.Decl{ &ast.FuncDecl{ Name: &ast.Ident{Name: "f"}, Type: &ast.FuncType{}, }, }, } var buf bytes.Buffer if err := Fprint(&buf, fset, src); err != nil { t.Fatal(err) } got := buf.String() const want = `package p func f() ` if got != want { t.Fatalf("got:\n%s\nwant:\n%s\n", got, want) } } type limitWriter struct { remaining int errCount int } func (l *limitWriter) Write(buf []byte) (n int, err error) { n = len(buf) if n >= l.remaining { n = l.remaining err = io.EOF l.errCount++ } l.remaining -= n return n, err } // Test whether the printer stops writing after the first error func TestWriteErrors(t *testing.T) { t.Parallel() const filename = "printer.go" src, err := ioutil.ReadFile(filename) if err != nil { panic(err) // error in test } file, err := parser.ParseFile(fset, filename, src, 0) if err != nil { panic(err) // error in test } for i := 0; i < 20; i++ { lw := &limitWriter{remaining: i} err := (&Config{Mode: RawFormat}).Fprint(lw, fset, file) if lw.errCount > 1 { t.Fatal("Writes continued after first error returned") } // We expect errCount be 1 iff err is set if (lw.errCount != 0) != (err != nil) { t.Fatal("Expected err when errCount != 0") } } } // TestX is a skeleton test that can be filled in for debugging one-off cases. // Do not remove. func TestX(t *testing.T) { const src = ` package p func _() {} ` _, err := format([]byte(src), 0) if err != nil { t.Error(err) } } func TestCommentedNode(t *testing.T) { const ( input = `package main func foo() { // comment inside func } // leading comment type bar int // comment2 ` foo = `func foo() { // comment inside func }` bar = `// leading comment type bar int // comment2 ` ) fset := token.NewFileSet() f, err := parser.ParseFile(fset, "input.go", input, parser.ParseComments) if err != nil { t.Fatal(err) } var buf bytes.Buffer err = Fprint(&buf, fset, &CommentedNode{Node: f.Decls[0], Comments: f.Comments}) if err != nil { t.Fatal(err) } if buf.String() != foo { t.Errorf("got %q, want %q", buf.String(), foo) } buf.Reset() err = Fprint(&buf, fset, &CommentedNode{Node: f.Decls[1], Comments: f.Comments}) if err != nil { t.Fatal(err) } if buf.String() != bar { t.Errorf("got %q, want %q", buf.String(), bar) } } func TestIssue11151(t *testing.T) { const src = "package p\t/*\r/1\r*\r/2*\r\r\r\r/3*\r\r+\r\r/4*/\n" fset := token.NewFileSet() f, err := parser.ParseFile(fset, "", src, parser.ParseComments) if err != nil { t.Fatal(err) } var buf bytes.Buffer Fprint(&buf, fset, f) got := buf.String() const want = "package p\t/*/1*\r/2*\r/3*+/4*/\n" // \r following opening /* should be stripped if got != want { t.Errorf("\ngot : %q\nwant: %q", got, want) } // the resulting program must be valid _, err = parser.ParseFile(fset, "", got, 0) if err != nil { t.Errorf("%v\norig: %q\ngot : %q", err, src, got) } } // If a declaration has multiple specifications, a parenthesized // declaration must be printed even if Lparen is token.NoPos. func TestParenthesizedDecl(t *testing.T) { // a package with multiple specs in a single declaration const src = "package p; var ( a float64; b int )" fset := token.NewFileSet() f, err := parser.ParseFile(fset, "", src, 0) if err != nil { t.Fatal(err) } // print the original package var buf bytes.Buffer err = Fprint(&buf, fset, f) if err != nil { t.Fatal(err) } original := buf.String() // now remove parentheses from the declaration for i := 0; i != len(f.Decls); i++ { f.Decls[i].(*ast.GenDecl).Lparen = token.NoPos } buf.Reset() err = Fprint(&buf, fset, f) if err != nil { t.Fatal(err) } noparen := buf.String() if noparen != original { t.Errorf("got %q, want %q", noparen, original) } } // Verify that we don't print a newline between "return" and its results, as // that would incorrectly cause a naked return. func TestIssue32854(t *testing.T) { src := `package foo func f() { return Composite{ call(), } }` fset := token.NewFileSet() file, err := parser.ParseFile(fset, "", src, 0) if err != nil { panic(err) } // Replace the result with call(), which is on the next line. fd := file.Decls[0].(*ast.FuncDecl) ret := fd.Body.List[0].(*ast.ReturnStmt) ret.Results[0] = ret.Results[0].(*ast.CompositeLit).Elts[0] var buf bytes.Buffer if err := Fprint(&buf, fset, ret); err != nil { t.Fatal(err) } want := "return call()" if got := buf.String(); got != want { t.Fatalf("got %q, want %q", got, want) } } func TestStripParens(t *testing.T) { x := stripParens(&ast.ParenExpr{ X: &ast.CompositeLit{ Type: &ast.Ident{Name: "foo"}, }, }) if _, ok := x.(*ast.ParenExpr); !ok { t.Fatal("TestStripParens failed:", x) } x = stripParens(&ast.ParenExpr{ X: &ast.CompositeLit{ Type: &ast.SelectorExpr{ X: &ast.Ident{Name: "foo"}, }, }, }) if _, ok := x.(*ast.ParenExpr); !ok { t.Fatal("TestStripParens failed:", x) } x = stripParens(&ast.ParenExpr{ X: &ast.ParenExpr{ X: &ast.CompositeLit{ Type: &ast.Ident{Name: "foo"}, }, }, }) if y, ok := x.(*ast.ParenExpr); !ok { t.Fatal("TestStripParens failed:", x) } else if _, ok := y.X.(*ast.ParenExpr); ok { t.Fatal("TestStripParens failed:", x) } x = stripParens(&ast.ParenExpr{ X: &ast.CompositeLit{ Type: &ast.BasicLit{}, }, }) if _, ok := x.(*ast.ParenExpr); ok { t.Fatal("TestStripParens stripParens failed:", x) } x = stripParens(&ast.ParenExpr{ X: &ast.BasicLit{}, }) if _, ok := x.(*ast.ParenExpr); ok { t.Fatal("TestStripParens stripParens failed:", x) } x = stripParensAlways(&ast.ParenExpr{ X: &ast.ParenExpr{ X: &ast.CompositeLit{ Type: &ast.Ident{Name: "foo"}, }, }, }) if _, ok := x.(*ast.ParenExpr); ok { t.Fatal("TestStripParens stripParensAlways failed:", x) } } // TestCommentedNodes tests the CommentedNodes type which allows attaching // diagnostic comments to specific statements during code generation. func TestCommentedNodes(t *testing.T) { const input = `package main func foo() { x := 1 y := 2 println(x + y) } ` fset := token.NewFileSet() f, err := parser.ParseFile(fset, "input.go", input, parser.ParseComments) if err != nil { t.Fatal(err) } // Get the function body statements funcDecl := f.Decls[0].(*ast.FuncDecl) stmts := funcDecl.Body.List // Test case 1: Single statement with comment t.Run("SingleStatement", func(t *testing.T) { commentedStmts := make(map[ast.Stmt]*ast.CommentGroup) commentedStmts[stmts[0]] = &ast.CommentGroup{ List: []*ast.Comment{ {Text: "// TYPECHECK(sb2xbp): type inference failed"}, }, } var buf bytes.Buffer node := &CommentedNodes{ Node: stmts[0], CommentedStmts: commentedStmts, } err := Fprint(&buf, fset, node) if err != nil { t.Fatal(err) } got := buf.String() want := "// TYPECHECK(sb2xbp): type inference failed\nx := 1" if got != want { t.Errorf("got %q, want %q", got, want) } }) // Test case 2: Multiple statements with comments t.Run("MultipleStatements", func(t *testing.T) { commentedStmts := make(map[ast.Stmt]*ast.CommentGroup) commentedStmts[stmts[0]] = &ast.CommentGroup{ List: []*ast.Comment{ {Text: "// FIXME(sb2xbp): unsupported block type"}, }, } commentedStmts[stmts[2]] = &ast.CommentGroup{ List: []*ast.Comment{ {Text: "// TYPECHECK(sb2xbp): type mismatch"}, }, } var buf bytes.Buffer node := &CommentedNodes{ Node: stmts, CommentedStmts: commentedStmts, } err := Fprint(&buf, fset, node) if err != nil { t.Fatal(err) } got := buf.String() want := "// FIXME(sb2xbp): unsupported block type\nx := 1\ny := 2\n// TYPECHECK(sb2xbp): type mismatch\nprintln(x + y)" if got != want { t.Errorf("got %q, want %q", got, want) } }) // Test case 3: Complete file with commented statements t.Run("CompleteFile", func(t *testing.T) { commentedStmts := make(map[ast.Stmt]*ast.CommentGroup) commentedStmts[stmts[1]] = &ast.CommentGroup{ List: []*ast.Comment{ {Text: "// NOTE: converted from Scratch block"}, }, } var buf bytes.Buffer node := &CommentedNodes{ Node: f, CommentedStmts: commentedStmts, } err := Fprint(&buf, fset, node) if err != nil { t.Fatal(err) } got := buf.String() // The output should contain the comment before the second statement if !bytes.Contains([]byte(got), []byte("// NOTE: converted from Scratch block\n\ty := 2")) { t.Errorf("output does not contain expected comment:\n%s", got) } }) // Test case 4: Empty CommentedStmts map (no comments added) t.Run("EmptyComments", func(t *testing.T) { var buf bytes.Buffer node := &CommentedNodes{ Node: stmts[0], CommentedStmts: make(map[ast.Stmt]*ast.CommentGroup), } err := Fprint(&buf, fset, node) if err != nil { t.Fatal(err) } got := buf.String() want := "x := 1" if got != want { t.Errorf("got %q, want %q", got, want) } }) // Test case 5: Nil CommentedStmts map t.Run("NilComments", func(t *testing.T) { var buf bytes.Buffer node := &CommentedNodes{ Node: stmts[0], CommentedStmts: nil, } err := Fprint(&buf, fset, node) if err != nil { t.Fatal(err) } got := buf.String() want := "x := 1" if got != want { t.Errorf("got %q, want %q", got, want) } }) // Test case 6: Multi-line comment t.Run("MultiLineComment", func(t *testing.T) { commentedStmts := make(map[ast.Stmt]*ast.CommentGroup) commentedStmts[stmts[0]] = &ast.CommentGroup{ List: []*ast.Comment{ {Text: "// TYPECHECK(sb2xbp): type inference failed"}, {Text: "// TODO: manual type annotation required"}, }, } var buf bytes.Buffer node := &CommentedNodes{ Node: stmts[0], CommentedStmts: commentedStmts, } err := Fprint(&buf, fset, node) if err != nil { t.Fatal(err) } got := buf.String() // Should contain both comment lines if !bytes.Contains([]byte(got), []byte("TYPECHECK(sb2xbp)")) || !bytes.Contains([]byte(got), []byte("TODO: manual type annotation")) { t.Errorf("output does not contain expected multi-line comments:\n%s", got) } }) } ================================================ FILE: printer/xgo_test.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package printer_test import ( "bytes" "io/fs" "os" "path/filepath" "strings" "testing" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/format" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/printer" "github.com/goplus/xgo/token" ) func init() { printer.SetDebug(printer.DbgFlagAll) } func TestNoPkgDecl(t *testing.T) { var dst bytes.Buffer fset := token.NewFileSet() if err := format.Node(&dst, fset, &ast.File{ Name: &ast.Ident{Name: "main"}, NoPkgDecl: true, }); err != nil { t.Fatal("format.Node failed:", err) } if dst.String() != "\n" { t.Fatal("TestNoPkgDecl:", dst.String()) } } func TestFuncs(t *testing.T) { var dst bytes.Buffer fset := token.NewFileSet() if err := format.Node(&dst, fset, &ast.File{ Name: &ast.Ident{Name: "main"}, Decls: []ast.Decl{ &ast.FuncDecl{ Type: &ast.FuncType{Params: &ast.FieldList{}}, Name: &ast.Ident{Name: "foo"}, Body: &ast.BlockStmt{ List: []ast.Stmt{&printer.NewlineStmt{}}, }, }, &ast.FuncDecl{ Type: &ast.FuncType{Params: &ast.FieldList{}}, Name: &ast.Ident{Name: "bar"}, Body: &ast.BlockStmt{}, }, &ast.FuncDecl{ Type: &ast.FuncType{Params: &ast.FieldList{}}, Name: &ast.Ident{Name: "Classname"}, Body: &ast.BlockStmt{}, Shadow: true, }, }, NoPkgDecl: true, }); err != nil { t.Fatal("format.Node failed:", err) } if dst.String() != `func foo() { } func bar() { } ` { t.Fatal("TestNoPkgDecl:", dst.String()) } } func diffBytes(t *testing.T, dst, src []byte) { line := 1 offs := 0 // line offset for i := 0; i < len(dst) && i < len(src); i++ { d := dst[i] s := src[i] if d != s { t.Errorf("dst:%d: %s\n", line, dst[offs:]) t.Errorf("src:%d: %s\n", line, src[offs:]) return } if s == '\n' { line++ offs = i + 1 } } if len(dst) != len(src) { t.Errorf("len(dst) = %d, len(src) = %d\ndst = %q\nsrc = %q", len(dst), len(src), dst, src) } } const ( excludeFormatSource = 1 excludeFormatNode = 2 ) func testFrom(t *testing.T, fpath, sel string, mode int) { if sel != "" && !strings.Contains(fpath, sel) { return } src, err := os.ReadFile(fpath) if err != nil { t.Fatal(err) } if (mode & excludeFormatSource) == 0 { t.Run("format.Source "+fpath, func(t *testing.T) { var class bool if filepath.Ext(fpath) == ".gox" { class = true } res, err := format.Source(src, class, fpath) if err != nil { t.Fatal("Source failed:", err) } diffBytes(t, res, src) }) } if (mode & excludeFormatNode) == 0 { t.Run("format.Node "+fpath, func(t *testing.T) { fset := token.NewFileSet() m := parser.ParseComments if filepath.Ext(fpath) == ".gox" { m |= parser.ParseXGoClass } f, err := parser.ParseFile(fset, fpath, src, m) if err != nil { t.Fatal(err) } var buf bytes.Buffer err = format.Node(&buf, fset, f) if err != nil { t.Fatal(err) } diffBytes(t, buf.Bytes(), src) }) } } func TestFromGopPrinter(t *testing.T) { testFrom(t, "nodes.go", "", 0) testFrom(t, "printer.go", "", 0) testFrom(t, "printer_test.go", "", 0) } func TestFromTestdata(t *testing.T) { sel := "" dir, err := os.Getwd() if err != nil { t.Fatal("Getwd failed:", err) } dir = filepath.Join(dir, "./_testdata") filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() && filepath.Ext(path) == ".xgo" { testFrom(t, path, sel, 0) } return nil }) } func TestFromParse(t *testing.T) { sel := "" dir, err := os.Getwd() if err != nil { t.Fatal("Getwd failed:", err) } dir = filepath.Join(dir, "../parser/_testdata") filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error { if err != nil { return err } name := info.Name() ext := filepath.Ext(name) if !info.IsDir() && (ext == ".xgo" || ext == ".gox") { testFrom(t, path, sel, 0) } return nil }) } ================================================ FILE: scanner/gop.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package scanner import ( "go/scanner" "io" "github.com/goplus/xgo/token" "github.com/qiniu/x/byteutil" ) // ----------------------------------------------------------------------------- // Error is an alias of go/scanner.Error type Error = scanner.Error // ErrorList is an alias of go/scanner.ErrorList type ErrorList = scanner.ErrorList // PrintError is a utility function that prints a list of errors to w, // one error per line, if the err parameter is an ErrorList. Otherwise // it prints the err string. func PrintError(w io.Writer, err error) { scanner.PrintError(w, err) } // ----------------------------------------------------------------------------- // New creates a scanner to tokenize the text src. // // Calls to Scan will invoke the error handler err if they encounter a // syntax error and err is not nil. Also, for each error encountered, // the Scanner field ErrorCount is incremented by one. The mode parameter // determines how comments are handled. func New(src string, err ErrorHandler, mode Mode) *Scanner { fset := token.NewFileSet() file := fset.AddFile("", fset.Base(), len(src)) scanner := new(Scanner) scanner.Init(file, byteutil.Bytes(src), err, mode) return scanner } // ----------------------------------------------------------------------------- ================================================ FILE: scanner/scanner.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package scanner implements a scanner for XGo source text. // It takes a []byte as source which can then be tokenized // through repeated calls to the Scan method. package scanner import ( "bytes" "fmt" "go/scanner" "path/filepath" "strconv" "unicode" "unicode/utf8" "github.com/goplus/xgo/token" ) // An ErrorHandler may be provided to Scanner.Init. If a syntax error is // encountered and a handler was installed, the handler is called with a // position and an error message. The position points to the beginning of // the offending token. type ErrorHandler = scanner.ErrorHandler // A Scanner holds the scanner's internal state while processing // a given text. It can be allocated as part of another data // structure but must be initialized via Init before use. type Scanner struct { // immutable state file *token.File // source file handle dir string // directory portion of file.Name() src []byte // source err ErrorHandler // error reporting; or nil mode Mode // scanning mode // scanning state ch rune // current character offset int // character offset rdOffset int // reading offset (position after current character) lineOffset int // current line offset nParen int peekLit string peekTok token.Token insertSemi bool // insert a semicolon before next newline // public state - ok to modify ErrorCount int // number of errors encountered } const bom = 0xFEFF // byte order mark, only permitted as very first character // Read the next Unicode char into s.ch. // s.ch < 0 means end-of-file. func (s *Scanner) next() { if s.rdOffset < len(s.src) { s.offset = s.rdOffset if s.ch == '\n' { s.lineOffset = s.offset s.file.AddLine(s.offset) } r, w := rune(s.src[s.rdOffset]), 1 switch { case r == 0: s.error(s.offset, "illegal character NUL") case r >= utf8.RuneSelf: // not ASCII r, w = utf8.DecodeRune(s.src[s.rdOffset:]) if r == utf8.RuneError && w == 1 { s.error(s.offset, "illegal UTF-8 encoding") } else if r == bom && s.offset > 0 { s.error(s.offset, "illegal byte order mark") } } s.rdOffset += w s.ch = r } else { s.offset = len(s.src) if s.ch == '\n' { s.lineOffset = s.offset s.file.AddLine(s.offset) } s.ch = -1 // eof } } // peek returns the byte following the most recently read character without // advancing the scanner. If the scanner is at EOF, peek returns 0. func (s *Scanner) peek() byte { if s.rdOffset < len(s.src) { return s.src[s.rdOffset] } return 0 } // A Mode value is a set of flags (or 0). // They control scanner behavior. type Mode uint const ( // ScanComments - return comments as COMMENT tokens ScanComments Mode = 1 << iota dontInsertSemis // do not automatically insert semicolons - for testing only ) // Init prepares the scanner s to tokenize the text src by setting the // scanner at the beginning of src. The scanner uses the file set file // for position information and it adds line information for each line. // It is ok to re-use the same file when re-scanning the same file as // line information which is already present is ignored. Init causes a // panic if the file size does not match the src size. // // Calls to Scan will invoke the error handler err if they encounter a // syntax error and err is not nil. Also, for each error encountered, // the Scanner field ErrorCount is incremented by one. The mode parameter // determines how comments are handled. // // Note that Init may call err if there is an error in the first character // of the file. func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode) { // Explicitly initialize all fields since a scanner may be reused. if file.Size() != len(src) { panic(fmt.Sprintf("file size (%d) does not match src len (%d)", file.Size(), len(src))) } s.InitEx(file, src, 0, err, mode) } // InitEx init the scanner with an offset (this means src[offset:] is all the code to scan). func (s *Scanner) InitEx(file *token.File, src []byte, offset int, err ErrorHandler, mode Mode) { s.file = file s.dir, _ = filepath.Split(file.Name()) s.src = src s.err = err s.mode = mode s.ch = ' ' s.offset = 0 s.rdOffset = offset s.lineOffset = 0 s.insertSemi = false s.ErrorCount = 0 s.next() if s.ch == bom { s.next() // ignore BOM at file beginning } } func (s *Scanner) CodeTo(end int) []byte { return s.src[:end] } func (s *Scanner) error(offs int, msg string) { if s.err != nil { s.err(s.file.Position(s.file.Pos(offs)), msg) } s.ErrorCount++ } func (s *Scanner) errorf(offs int, format string, args ...any) { s.error(offs, fmt.Sprintf(format, args...)) } func (s *Scanner) scanComment() string { // initial '/' already consumed; s.ch == '/' || s.ch == '*' offs := s.offset - 1 // position of initial '/' next := -1 // position immediately following the comment; < 0 means invalid comment numCR := 0 if s.ch == '/' { //-style comment // (the final '\n' is not considered part of the comment) s.next() for s.ch != '\n' && s.ch >= 0 { if s.ch == '\r' { numCR++ } s.next() } // if we are at '\n', the position following the comment is afterwards next = s.offset if s.ch == '\n' { next++ } goto exit } /*-style comment */ if s.ch == '*' { s.next() for s.ch >= 0 { ch := s.ch if ch == '\r' { numCR++ } s.next() if ch == '*' && s.ch == '/' { s.next() next = s.offset goto exit } } s.error(offs, "comment not terminated") goto exit } // # - style comment, as default s.next() for s.ch != '\n' && s.ch >= 0 { if s.ch == '\r' { numCR++ } s.next() } // if we are at '\n', the position following the comment is afterwards next = s.offset if s.ch == '\n' { next++ } exit: lit := s.src[offs:s.offset] // On Windows, a (//-comment) line may end in "\r\n". // Remove the final '\r' before analyzing the text for // line directives (matching the compiler). Remove any // other '\r' afterwards (matching the pre-existing be- // havior of the scanner). if numCR > 0 && len(lit) >= 2 && lit[1] == '/' && lit[len(lit)-1] == '\r' { lit = lit[:len(lit)-1] numCR-- } // interpret line directives // (//line directives must start at the beginning of the current line) if next >= 0 /* implies valid comment */ && (lit[1] == '*' || offs == s.lineOffset) && bytes.HasPrefix(lit[2:], prefix) { s.updateLineInfo(next, offs, lit) } if numCR > 0 { lit = stripCR(lit, lit[1] == '*') } return string(lit) } var prefix = []byte("line ") // updateLineInfo parses the incoming comment text at offset offs // as a line directive. If successful, it updates the line info table // for the position next per the line directive. func (s *Scanner) updateLineInfo(next, offs int, text []byte) { // extract comment text if text[1] == '*' { text = text[:len(text)-2] // lop off trailing "*/" } text = text[7:] // lop off leading "//line " or "/*line " offs += 7 i, n, ok := trailingDigits(text) if i == 0 { return // ignore (not a line directive) } // i > 0 if !ok { // text has a suffix :xxx but xxx is not a number s.error(offs+i, "invalid line number: "+string(text[i:])) return } var line, col int i2, n2, ok2 := trailingDigits(text[:i-1]) if ok2 { //line filename:line:col i, i2 = i2, i line, col = n2, n if col == 0 { s.error(offs+i2, "invalid column number: "+string(text[i2:])) return } text = text[:i2-1] // lop off ":col" } else { //line filename:line line = n } if line == 0 { s.error(offs+i, "invalid line number: "+string(text[i:])) return } // If we have a column (//line filename:line:col form), // an empty filename means to use the previous filename. filename := string(text[:i-1]) // lop off ":line", and trim white space if filename == "" && ok2 { filename = s.file.Position(s.file.Pos(offs)).Filename } else if filename != "" { // Put a relative filename in the current directory. // This is for compatibility with earlier releases. // See issue 26671. filename = filepath.Clean(filename) if !filepath.IsAbs(filename) { filename = filepath.Join(s.dir, filename) } } s.file.AddLineColumnInfo(next, filename, line, col) } func trailingDigits(text []byte) (int, int, bool) { i := bytes.LastIndexByte(text, ':') // look from right (Windows filenames may contain ':') if i < 0 { return 0, 0, false // no ":" } // i >= 0 n, err := strconv.ParseUint(string(text[i+1:]), 10, 0) return i + 1, int(n), err == nil } func (s *Scanner) findLineEnd() bool { // initial '/' already consumed defer func(offs int) { // reset scanner state to where it was upon calling findLineEnd s.ch = '/' s.offset = offs s.rdOffset = offs + 1 s.next() // consume initial '/' again }(s.offset - 1) // read ahead until a newline, EOF, or non-comment token is found for s.ch == '/' || s.ch == '*' { if s.ch == '/' { //-style comment always contains a newline return true } /*-style comment: look for newline */ s.next() for s.ch >= 0 { ch := s.ch if ch == '\n' { return true } s.next() if ch == '*' && s.ch == '/' { s.next() break } } s.skipWhitespace() // s.insertSemi is set if s.ch < 0 || s.ch == '\n' { return true } if s.ch != '/' { // non-comment token return false } s.next() // consume '/' } return false } func isLetter(ch rune) bool { return 'a' <= lower(ch) && lower(ch) <= 'z' || ch == '_' || ch >= utf8.RuneSelf && unicode.IsLetter(ch) } func isDigit(ch rune) bool { return isDecimal(ch) || ch >= utf8.RuneSelf && unicode.IsDigit(ch) } func (s *Scanner) scanIdentifier() string { offs := s.offset for isLetter(s.ch) || isDigit(s.ch) { s.next() } return string(s.src[offs:s.offset]) } func digitVal(ch rune) int { switch { case '0' <= ch && ch <= '9': return int(ch - '0') case 'a' <= lower(ch) && lower(ch) <= 'f': return int(lower(ch) - 'a' + 10) } return 16 // larger than any legal digit val } func lower(ch rune) rune { return ('a' - 'A') | ch } // returns lower-case ch iff ch is ASCII letter func isDecimal(ch rune) bool { return '0' <= ch && ch <= '9' } func isHex(ch rune) bool { return '0' <= ch && ch <= '9' || 'a' <= lower(ch) && lower(ch) <= 'f' } // digits accepts the sequence { digit | '_' }. // If base <= 10, digits accepts any decimal digit but records // the offset (relative to the source start) of a digit >= base // in *invalid, if *invalid < 0. // digits returns a bitset describing whether the sequence contained // digits (bit 0 is set), or separators '_' (bit 1 is set). func (s *Scanner) digits(base int, invalid *int) (digsep int) { if base <= 10 { max := rune('0' + base) for isDecimal(s.ch) || s.ch == '_' { ds := 1 if s.ch == '_' { ds = 2 } else if s.ch >= max && *invalid < 0 { *invalid = int(s.offset) // record invalid rune offset } digsep |= ds s.next() } } else { for isHex(s.ch) || s.ch == '_' { ds := 1 if s.ch == '_' { ds = 2 } digsep |= ds s.next() } } return } func (s *Scanner) scanNumber() (token.Token, string) { offs := s.offset tok := token.ILLEGAL base := 10 // number base prefix := rune(0) // one of 0 (decimal), '0' (0-octal), 'x', 'o', or 'b' digsep := 0 // bit 0: digit present, bit 1: '_' present invalid := -1 // index of invalid digit in literal, or < 0 // integer part if s.ch != '.' { tok = token.INT if s.ch == '0' { s.next() switch lower(s.ch) { case 'x': s.next() base, prefix = 16, 'x' case 'o': s.next() base, prefix = 8, 'o' case 'b': s.next() base, prefix = 2, 'b' default: base, prefix = 8, '0' digsep = 1 // leading 0 } } digsep |= s.digits(base, &invalid) } // fractional part if s.ch == '.' { tok = token.FLOAT if prefix == 'o' || prefix == 'b' { s.error(s.offset, "invalid radix point in "+litname(prefix)) } s.next() digsep |= s.digits(base, &invalid) } if digsep&1 == 0 { s.error(s.offset, litname(prefix)+" has no digits") } // exponent if e := lower(s.ch); e == 'e' || e == 'p' { switch { case e == 'e' && prefix != 0 && prefix != '0': s.errorf(s.offset, "%q exponent requires decimal mantissa", s.ch) case e == 'p' && prefix != 'x': s.errorf(s.offset, "%q exponent requires hexadecimal mantissa", s.ch) } s.next() tok = token.FLOAT if s.ch == '+' || s.ch == '-' { s.next() } ds := s.digits(10, nil) digsep |= ds if ds&1 == 0 { s.error(s.offset, "exponent has no digits") } } else if prefix == 'x' && tok == token.FLOAT { s.error(s.offset, "hexadecimal mantissa requires a 'p' exponent") } if isLetter(s.ch) { id := s.scanIdentifier() switch id { case "i": tok = token.IMAG case "r": tok = token.RAT default: s.peekTok, s.peekLit = token.UNIT, id } } lit := string(s.src[offs : s.offset-len(s.peekLit)]) if tok == token.INT && invalid >= 0 { s.errorf(invalid, "invalid digit %q in %s", lit[invalid-offs], litname(prefix)) } if digsep&2 != 0 { if i := invalidSep(lit); i >= 0 { s.error(offs+i, "'_' must separate successive digits") } } return tok, lit } func litname(prefix rune) string { switch prefix { case 'x': return "hexadecimal literal" case 'o', '0': return "octal literal" case 'b': return "binary literal" } return "decimal literal" } // invalidSep returns the index of the first invalid separator in x, or -1. func invalidSep(x string) int { x1 := ' ' // prefix char, we only care if it's 'x' d := '.' // digit, one of '_', '0' (a digit), or '.' (anything else) i := 0 // a prefix counts as a digit if len(x) >= 2 && x[0] == '0' { x1 = lower(rune(x[1])) if x1 == 'x' || x1 == 'o' || x1 == 'b' { d = '0' i = 2 } } // mantissa and exponent for ; i < len(x); i++ { p := d // previous digit d = rune(x[i]) switch { case d == '_': if p != '0' { return i } case isDecimal(d) || x1 == 'x' && isHex(d): d = '0' default: if p == '_' { return i - 1 } d = '.' } } if d == '_' { return len(x) - 1 } return -1 } // scanEscape parses an escape sequence where rune is the accepted // escaped quote. In case of a syntax error, it stops at the offending // character (without consuming it) and returns false. Otherwise // it returns true. func (s *Scanner) scanEscape(quote rune) bool { offs := s.offset var n int var base, max uint32 switch s.ch { case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', quote: s.next() return true case '0', '1', '2', '3', '4', '5', '6', '7': n, base, max = 3, 8, 255 case 'x': s.next() n, base, max = 2, 16, 255 case 'u': s.next() n, base, max = 4, 16, unicode.MaxRune case 'U': s.next() n, base, max = 8, 16, unicode.MaxRune default: msg := "unknown escape sequence" if s.ch < 0 { msg = "escape sequence not terminated" } s.error(offs, msg) return false } var x uint32 for n > 0 { d := uint32(digitVal(s.ch)) if d >= base { msg := fmt.Sprintf("illegal character %#U in escape sequence", s.ch) if s.ch < 0 { msg = "escape sequence not terminated" } s.error(s.offset, msg) return false } x = x*base + d s.next() n-- } if x > max || 0xD800 <= x && x < 0xE000 { s.error(offs, "escape sequence is invalid Unicode code point") return false } return true } func (s *Scanner) scanRune() string { // '\'' opening already consumed offs := s.offset - 1 valid := true n := 0 for { ch := s.ch if ch == '\n' || ch < 0 { // only report error if we don't have one already if valid { s.error(offs, "rune literal not terminated") valid = false } break } s.next() if ch == '\'' { break } n++ if ch == '\\' { if !s.scanEscape('\'') { valid = false } // continue to read to closing quote } } if valid && n != 1 { s.error(offs, "illegal rune literal") } return string(s.src[offs:s.offset]) } func (s *Scanner) scanString() string { // '"' opening already consumed offs := s.offset - 1 for { ch := s.ch if ch == '\n' || ch < 0 { s.error(offs, "string literal not terminated") break } s.next() if ch == '"' { break } if ch == '\\' { s.scanEscape('"') } } return string(s.src[offs:s.offset]) } func stripCR(b []byte, comment bool) []byte { c := make([]byte, len(b)) i := 0 for j, ch := range b { // In a /*-style comment, don't strip \r from *\r/ (incl. // sequences of \r from *\r\r...\r/) since the resulting // */ would terminate the comment too early unless the \r // is immediately following the opening /* in which case // it's ok because /*/ is not closed yet (issue #11151). if ch != '\r' || comment && i > len("/*") && c[i-1] == '*' && j+1 < len(b) && b[j+1] == '/' { c[i] = ch i++ } } return c[:i] } func (s *Scanner) scanRawString() string { // '`' opening already consumed offs := s.offset - 1 hasCR := false for { ch := s.ch if ch < 0 { s.error(offs, "raw string literal not terminated") break } s.next() if ch == '`' { break } if ch == '\r' { hasCR = true } } lit := s.src[offs:s.offset] if hasCR { lit = stripCR(lit, false) } return string(lit) } func (s *Scanner) skipWhitespace() { for s.ch == ' ' || s.ch == '\t' || s.ch == '\n' && !s.insertSemi || s.ch == '\r' { s.next() } } // Helper functions for scanning multi-byte tokens such as >> += >>= . // Different routines recognize different length tok_i based on matches // of ch_i. If a token ends in '=', the result is tok1 or tok3 // respectively. Otherwise, the result is tok0 if there was no other // matching character, or tok2 if the matching character was ch2. func (s *Scanner) switch2(tok0, tok1 token.Token) token.Token { if s.ch == '=' { s.next() return tok1 } return tok0 } func (s *Scanner) switch3(tok0, tok1 token.Token, ch2 rune, tok2 token.Token) token.Token { if s.ch == '=' { s.next() return tok1 } if s.ch == ch2 { s.next() return tok2 } return tok0 } func (s *Scanner) switch4(tok0, tok1 token.Token, ch2 rune, tok2, tok3 token.Token) token.Token { if s.ch == '=' { s.next() return tok1 } if s.ch == ch2 { s.next() if s.ch == '=' { s.next() return tok3 } return tok2 } return tok0 } func (s *Scanner) tokSEMICOLON() token.Token { s.nParen = 0 return token.SEMICOLON } // Scan scans the next token and returns the token position, the token, // and its literal string if applicable. The source end is indicated by // token.EOF. // // If the returned token is a literal (token.IDENT, token.INT, token.FLOAT, // token.IMAG, token.CHAR, token.STRING) or token.COMMENT, the literal string // has the corresponding value. // // If the returned token is a keyword, the literal string is the keyword. // // If the returned token is token.SEMICOLON, the corresponding // literal string is ";" if the semicolon was present in the source, // and "\n" if the semicolon was inserted because of a newline or // at EOF. // // If the returned token is token.ILLEGAL, the literal string is the // offending character. // // In all other cases, Scan returns an empty literal string. // // For more tolerant parsing, Scan will return a valid token if // possible even if a syntax error was encountered. Thus, even // if the resulting token sequence contains no illegal tokens, // a client may not assume that no error occurred. Instead it // must check the scanner's ErrorCount or the number of calls // of the error handler, if there was one installed. // // Scan adds line information to the file added to the file // set with Init. Token positions are relative to that file // and thus relative to the file set. func (s *Scanner) Scan() (pos token.Pos, tok token.Token, lit string) { scanAgain: insertSemi := false if s.peekTok != 0 { insertSemi = true pos = s.file.Pos(s.offset - len(s.peekLit)) tok, lit = s.peekTok, s.peekLit s.peekTok, s.peekLit = 0, "" goto done } s.skipWhitespace() // current token start pos = s.file.Pos(s.offset) // determine token value switch ch := s.ch; { case isLetter(ch): lit = s.scanIdentifier() if len(lit) > 1 { // keywords are longer than one letter - avoid lookup otherwise tok = token.Lookup(lit) switch tok { case token.IDENT: insertSemi = true if lit == "py" && s.ch == '"' { // py"..." s.next() tok = token.PYSTRING lit = s.scanString() } case token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN: insertSemi = true } } else if (lit == "c" || lit == "C") && s.ch == '"' { // c"..." s.next() insertSemi = true tok = token.CSTRING lit = s.scanString() } else { insertSemi = true tok = token.IDENT } case isDecimal(ch) || ch == '.' && isDecimal(rune(s.peek())): insertSemi = true tok, lit = s.scanNumber() default: s.next() // always make progress switch ch { case -1: if s.insertSemi { s.insertSemi = false // EOF consumed return pos, s.tokSEMICOLON(), "\n" } tok = token.EOF case '\n': // we only reach here if s.insertSemi was // set in the first place and exited early // from s.skipWhitespace() s.insertSemi = false // newline consumed return pos, s.tokSEMICOLON(), "\n" case '"': insertSemi = true tok = token.STRING lit = s.scanString() case '\'': insertSemi = true tok = token.CHAR lit = s.scanRune() case '`': insertSemi = true tok = token.STRING lit = s.scanRawString() case ':': tok = s.switch2(token.COLON, token.DEFINE) case '.': // fractions starting with a '.' are handled by outer switch if s.ch == '.' && s.peek() == '.' { s.next() s.next() // consume last '.' if s.nParen == 0 { insertSemi = true } tok = token.ELLIPSIS } else { tok = token.PERIOD if ch := ('a' - 'A') | s.ch; 'a' <= ch && ch <= 'z' { s.peekTok, s.peekLit = token.IDENT, s.scanIdentifier() } } case ',': tok = token.COMMA case ';': tok = s.tokSEMICOLON() lit = ";" case '(': s.nParen++ tok = token.LPAREN case ')': s.nParen-- insertSemi = true tok = token.RPAREN case '[': tok = token.LBRACK case ']': insertSemi = true tok = token.RBRACK case '{': tok = token.LBRACE case '}': insertSemi = true tok = token.RBRACE case '+': tok = s.switch3(token.ADD, token.ADD_ASSIGN, '+', token.INC) if tok == token.INC { insertSemi = true } case '-': if s.ch == '>' { // -> s.next() tok = token.SRARROW } else { // -- -= tok = s.switch3(token.SUB, token.SUB_ASSIGN, '-', token.DEC) if tok == token.DEC { insertSemi = true } } case '*': tok = s.switch2(token.MUL, token.MUL_ASSIGN) case '#': if s.insertSemi { s.ch = '#' s.offset = s.file.Offset(pos) s.rdOffset = s.offset + 1 s.insertSemi = false // newline consumed return pos, s.tokSEMICOLON(), "\n" } comment := s.scanComment() if s.mode&ScanComments == 0 { // skip comment s.insertSemi = false // newline consumed goto scanAgain } tok = token.COMMENT lit = comment case '/': if s.ch == '/' || s.ch == '*' { // comment if s.insertSemi && s.findLineEnd() { // reset position to the beginning of the comment s.ch = '/' s.offset = s.file.Offset(pos) s.rdOffset = s.offset + 1 s.insertSemi = false // newline consumed return pos, s.tokSEMICOLON(), "\n" } comment := s.scanComment() if s.mode&ScanComments == 0 { // skip comment s.insertSemi = false // newline consumed goto scanAgain } tok = token.COMMENT lit = comment } else { tok = s.switch2(token.QUO, token.QUO_ASSIGN) } case '%': tok = s.switch2(token.REM, token.REM_ASSIGN) case '^': tok = s.switch2(token.XOR, token.XOR_ASSIGN) case '<': switch s.ch { case '-': // <- s.next() tok = token.ARROW case '>': // <> s.next() tok = token.BIDIARROW default: // < <= << <<= tok = s.switch4(token.LSS, token.LEQ, '<', token.SHL, token.SHL_ASSIGN) } case '>': tok = s.switch4(token.GTR, token.GEQ, '>', token.SHR, token.SHR_ASSIGN) case '=': tok = s.switch3(token.ASSIGN, token.EQL, '>', token.DRARROW) case '!': tok = s.switch2(token.NOT, token.NEQ) if tok == token.NOT { insertSemi = true } case '&': if s.ch == '^' { s.next() tok = s.switch2(token.AND_NOT, token.AND_NOT_ASSIGN) } else { tok = s.switch3(token.AND, token.AND_ASSIGN, '&', token.LAND) } case '|': tok = s.switch3(token.OR, token.OR_ASSIGN, '|', token.LOR) case '?': tok = token.QUESTION insertSemi = true case '$': tok = token.ENV case '~': tok = token.TILDE case '@': tok = token.AT default: // next reports unexpected BOMs - don't repeat if ch != bom { s.errorf(s.file.Offset(pos), "illegal character %#U", ch) } insertSemi = s.insertSemi // preserve insertSemi info tok = token.ILLEGAL lit = string(ch) } } done: if s.mode&dontInsertSemis == 0 { s.insertSemi = insertSemi } return } ================================================ FILE: test/classfile.go ================================================ /* * Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package test import ( "os" "testing" ) const ( GopPackage = true ) // ----------------------------------------------------------------------------- type testingT = testing.T // Case represents an XGo testcase. type Case struct { t *testingT } func (p *Case) initCase(t *testing.T) { p.t = t } // T returns the *testing.T object . func (p Case) T() *testing.T { return p.t } // Run runs f as a subtest of t called name. It runs f in a separate goroutine // and blocks until f returns or calls t.Parallel to become a parallel test. // Run reports whether f succeeded (or at least did not fail before calling t.Parallel). func (p Case) Run(name string, f func(t *testing.T)) bool { return p.t.Run(name, f) } // Gopt_Case_TestMain is required by XGo compiler as the test case entry. func Gopt_Case_TestMain(c interface{ initCase(t *testing.T) }, t *testing.T) { c.initCase(t) c.(interface{ Main() }).Main() } // ----------------------------------------------------------------------------- // App represents an XGo testing main application. type App struct { m *testing.M } func (p *App) initApp(m *testing.M) { p.m = m } // M returns the *testing.M object. func (p App) M() *testing.M { return p.m } // Gopt_App_TestMain is required by XGo compiler as the entry of an XGo testing project. func Gopt_App_TestMain(app interface{ initApp(m *testing.M) }, m *testing.M) { app.initApp(m) if me, ok := app.(interface{ MainEntry() }); ok { me.MainEntry() } os.Exit(m.Run()) } // ----------------------------------------------------------------------------- ================================================ FILE: token/internal/tokenutil/lines_go118.go ================================================ //go:build !go1.19 // +build !go1.19 /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package tokenutil import ( "go/token" "sync" "unsafe" ) // ----------------------------------------------------------------------------- // File // A File is a handle for a file belonging to a FileSet. // A File has a name, size, and line offset table. type File struct { set *token.FileSet name string // file name as provided to AddFile base int // Pos value range for this file is [base...base+size] size int // file size as provided to AddFile // lines and infos are protected by mutex mutex sync.Mutex lines []int // lines contains the offset of the first character for each line (the first entry is always 0) infos []lineInfo } // A lineInfo object describes alternative file, line, and column // number information (such as provided via a //line directive) // for a given file offset. type lineInfo struct { // fields are exported to make them accessible to gob Offset int Filename string Line, Column int } // Lines returns the effective line offset table of the form described by SetLines. // Callers must not mutate the result. func (f *File) Lines() []int { f.mutex.Lock() lines := f.lines f.mutex.Unlock() return lines } // Lines returns the effective line offset table of the form described by SetLines. // Callers must not mutate the result. func Lines(f *token.File) []int { file := (*File)(unsafe.Pointer(f)) return file.Lines() } // ----------------------------------------------------------------------------- ================================================ FILE: token/internal/tokenutil/lines_go120.go ================================================ //go:build go1.19 && !go1.21 // +build go1.19,!go1.21 /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package tokenutil import ( "go/token" "sync" "unsafe" ) // ----------------------------------------------------------------------------- // File // A File is a handle for a file belonging to a FileSet. // A File has a name, size, and line offset table. type File struct { name string // file name as provided to AddFile base int // Pos value range for this file is [base...base+size] size int // file size as provided to AddFile // lines and infos are protected by mutex mutex sync.Mutex lines []int // lines contains the offset of the first character for each line (the first entry is always 0) infos []lineInfo } // A lineInfo object describes alternative file, line, and column // number information (such as provided via a //line directive) // for a given file offset. type lineInfo struct { // fields are exported to make them accessible to gob Offset int Filename string Line, Column int } // Lines returns the effective line offset table of the form described by SetLines. // Callers must not mutate the result. func (f *File) Lines() []int { f.mutex.Lock() lines := f.lines f.mutex.Unlock() return lines } // Lines returns the effective line offset table of the form described by SetLines. // Callers must not mutate the result. func Lines(f *token.File) []int { file := (*File)(unsafe.Pointer(f)) return file.Lines() } // ----------------------------------------------------------------------------- ================================================ FILE: token/internal/tokenutil/lines_go121.go ================================================ //go:build go1.21 // +build go1.21 /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package tokenutil import ( "go/token" ) // Lines returns the effective line offset table of the form described by SetLines. // Callers must not mutate the result. func Lines(f *token.File) []int { return f.Lines() } ================================================ FILE: token/internal/tokenutil/lines_test.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package tokenutil import ( "go/token" "reflect" "testing" ) func TestLines(t *testing.T) { fset := token.NewFileSet() f := fset.AddFile("foo.go", 100, 100) lines := []int{0, 10, 50} f.SetLines(lines) ret := Lines(f) if !reflect.DeepEqual(ret, lines) { t.Fatal("TestLines failed:", ret) } } ================================================ FILE: token/token.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package token defines constants representing the lexical tokens of the XGo // programming language and basic operations on tokens (printing, predicates). package token import ( "go/token" "strconv" xtoken "github.com/goplus/gogen/token" ) // Token is the set of lexical tokens of the XGo programming language. type Token int // The list of tokens. const ( // Special tokens ILLEGAL Token = iota EOF COMMENT literal_beg // Identifiers and basic type literals // (these tokens stand for classes of literals) IDENT // main INT // 12345 FLOAT // 123.45 IMAG // 123.45i CHAR // 'a' STRING // "abc" literal_end operator_beg // Operators and delimiters ADD // + SUB // - MUL // * QUO // / REM // % AND // & OR // | XOR // ^ SHL // << SHR // >> AND_NOT // &^ ADD_ASSIGN // += SUB_ASSIGN // -= MUL_ASSIGN // *= QUO_ASSIGN // /= REM_ASSIGN // %= AND_ASSIGN // &= OR_ASSIGN // |= XOR_ASSIGN // ^= SHL_ASSIGN // <<= SHR_ASSIGN // >>= AND_NOT_ASSIGN // &^= LAND // && LOR // || ARROW // <- INC // ++ DEC // -- EQL // == LSS // < GTR // > ASSIGN // = NOT // ! NEQ // != LEQ // <= GEQ // >= DEFINE // := ELLIPSIS // ... LPAREN // ( LBRACK // [ LBRACE // { COMMA // , PERIOD // . RPAREN // ) RBRACK // ] RBRACE // } SEMICOLON // ; COLON // : operator_end keyword_beg // Keywords BREAK CASE CHAN CONST CONTINUE DEFAULT DEFER ELSE FALLTHROUGH FOR FUNC GO GOTO IF IMPORT INTERFACE MAP PACKAGE RANGE RETURN SELECT STRUCT SWITCH TYPE VAR keyword_end additional_beg TILDE // additional tokens, handled in an ad-hoc manner additional_op1 additional_op2 additional_op3 additional_end = additional_op3 additional_literal_beg = 96 additional_literal_end = 97 AT = additional_op2 // @ ENV = additional_op3 // ${name} PYSTRING = additional_literal_beg // py"Hello" UNIT = additional_literal_end // 1m, 2.3s, 3ms, 4us, 5ns, 6.5m, 7h, 8d, 9w, 10y CSTRING = literal_beg // c"Hello" RAT = literal_end // 123.5r DRARROW = operator_beg // => (double right arrow) QUESTION = operator_end // ? SRARROW = Token(xtoken.SRARROW) // -> (single right arrow) = additional_beg BIDIARROW = Token(xtoken.BIDIARROW) // <> (bidirectional arrow) = additional_op1 // Deprecated: use DRARROW instead of RARROW RARROW = DRARROW ) var tokens = [...]string{ ILLEGAL: "ILLEGAL", EOF: "EOF", COMMENT: "COMMENT", IDENT: "IDENT", INT: "INT", FLOAT: "FLOAT", IMAG: "IMAG", CHAR: "CHAR", STRING: "STRING", CSTRING: "CSTRING", PYSTRING: "PYSTRING", RAT: "RAT", UNIT: "UNIT", ADD: "+", SUB: "-", MUL: "*", QUO: "/", REM: "%", AND: "&", OR: "|", XOR: "^", SHL: "<<", SHR: ">>", AND_NOT: "&^", ADD_ASSIGN: "+=", SUB_ASSIGN: "-=", MUL_ASSIGN: "*=", QUO_ASSIGN: "/=", REM_ASSIGN: "%=", AND_ASSIGN: "&=", OR_ASSIGN: "|=", XOR_ASSIGN: "^=", SHL_ASSIGN: "<<=", SHR_ASSIGN: ">>=", AND_NOT_ASSIGN: "&^=", LAND: "&&", LOR: "||", ARROW: "<-", INC: "++", DEC: "--", EQL: "==", LSS: "<", GTR: ">", ASSIGN: "=", NOT: "!", NEQ: "!=", LEQ: "<=", GEQ: ">=", DEFINE: ":=", ELLIPSIS: "...", LPAREN: "(", LBRACK: "[", LBRACE: "{", COMMA: ",", PERIOD: ".", RPAREN: ")", RBRACK: "]", RBRACE: "}", SEMICOLON: ";", COLON: ":", QUESTION: "?", DRARROW: "=>", SRARROW: "->", BIDIARROW: "<>", ENV: "$", TILDE: "~", AT: "@", BREAK: "break", CASE: "case", CHAN: "chan", CONST: "const", CONTINUE: "continue", DEFAULT: "default", DEFER: "defer", ELSE: "else", FALLTHROUGH: "fallthrough", FOR: "for", FUNC: "func", GO: "go", GOTO: "goto", IF: "if", IMPORT: "import", INTERFACE: "interface", MAP: "map", PACKAGE: "package", RANGE: "range", RETURN: "return", SELECT: "select", STRUCT: "struct", SWITCH: "switch", TYPE: "type", VAR: "var", } // String returns the string corresponding to the token tok. // For operators, delimiters, and keywords the string is the actual // token character sequence (e.g., for the token ADD, the string is // "+"). For all other tokens the string corresponds to the token // constant name (e.g. for the token IDENT, the string is "IDENT"). func (tok Token) String() string { s := "" if 0 <= tok && tok < Token(len(tokens)) { s = tokens[tok] } if s == "" { s = "token(" + strconv.Itoa(int(tok)) + ")" } return s } // A set of constants for precedence-based expression parsing. // Non-operators have lowest precedence, followed by operators // starting with precedence 1 up to unary operators. The highest // precedence serves as "catch-all" precedence for selector, // indexing, and other operator and delimiter tokens. const ( LowestPrec = 0 // non-operators UnaryPrec = 6 HighestPrec = 7 ) // Precedence returns the operator precedence of the binary // operator op. If op is not a binary operator, the result // is LowestPrecedence. func (op Token) Precedence() int { switch op { case LOR: return 1 case LAND: return 2 case EQL, NEQ, LSS, LEQ, GTR, GEQ, SRARROW, BIDIARROW: return 3 case ADD, SUB, OR, XOR: return 4 case MUL, QUO, REM, SHL, SHR, AND, AND_NOT: return 5 } return LowestPrec } var keywords map[string]Token func init() { keywords = make(map[string]Token) for i := keyword_beg + 1; i < keyword_end; i++ { keywords[tokens[i]] = i } } // Lookup maps an identifier to its keyword token or IDENT (if not a keyword). func Lookup(ident string) Token { if tok, is_keyword := keywords[ident]; is_keyword { return tok } return IDENT } // Predicates // IsLiteral returns true for tokens corresponding to identifiers // and basic type literals; it returns false otherwise. func (tok Token) IsLiteral() bool { return literal_beg <= tok && tok <= literal_end || tok == PYSTRING } // IsOperator returns true for tokens corresponding to operators and // delimiters; it returns false otherwise. func (tok Token) IsOperator() bool { return operator_beg <= tok && tok <= operator_end || tok >= additional_beg && tok <= additional_end } // IsKeyword returns true for tokens corresponding to keywords; // it returns false otherwise. func (tok Token) IsKeyword() bool { return keyword_beg < tok && tok < keyword_end } // IsExported reports whether name starts with an upper-case letter. func IsExported(name string) bool { return token.IsExported(name) } // IsKeyword reports whether name is a Go keyword, such as "func" or "return". func IsKeyword(name string) bool { return token.IsKeyword(name) } // IsIdentifier reports whether name is a Go identifier, that is, a non-empty // string made up of letters, digits, and underscores, where the first character // is not a digit. Keywords are not identifiers. func IsIdentifier(name string) bool { return token.IsIdentifier(name) } ================================================ FILE: token/token_test.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package token import ( "reflect" "testing" ) func TestArrowOp(t *testing.T) { if v := BIDIARROW.IsOperator(); !v { t.Fatal("BIDIARROW not op?") } if v := SRARROW.IsOperator(); !v { t.Fatal("SRARROW not op?") } if BIDIARROW.Precedence() != NEQ.Precedence() { t.Fatal("BIDIARROW.Precedence") } if v := BIDIARROW.String(); v != "<>" { t.Fatal("BIDIARROW.String:", v) } if v := SRARROW.String(); v != "->" { t.Fatal("SRARROW.String:", v) } if v := AT.String(); v != "@" { t.Fatal("AT.String:", v) } if v := (additional_end + 100).String(); v != "token(191)" { t.Fatal("token.String:", v) } } func TestPrecedence(t *testing.T) { cases := map[Token]int{ LOR: 1, LAND: 2, EQL: 3, SUB: 4, MUL: 5, ARROW: LowestPrec, } for op, prec := range cases { if v := op.Precedence(); v != prec { t.Fatal("Precedence:", op, v) } } } func TestLookup(t *testing.T) { if v := Lookup("type"); v != TYPE { t.Fatal("TestLookup type:", v) } else if !v.IsKeyword() { t.Fatal("v.IsKeyword:", v) } if v := Lookup("new"); v != IDENT { t.Fatal("TestLookup new:", v) } else if !v.IsLiteral() { t.Fatal("v.IsLiteral:", v) } } func TestBasic(t *testing.T) { if !IsExported("Name") { t.Fatal("IsExported") } if !IsKeyword("func") { t.Fatal("IsKeyword") } if !IsIdentifier("new") { t.Fatal("IsIdentifier") } } func TestLines(t *testing.T) { fset := NewFileSet() f := fset.AddFile("foo.go", 100, 100) lines := []int{0, 10, 50} f.SetLines(lines) ret := Lines(f) if !reflect.DeepEqual(ret, lines) { t.Fatal("TestLines failed:", ret) } } ================================================ FILE: token/types.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package token import ( "go/token" "github.com/goplus/xgo/token/internal/tokenutil" ) // Pos is a compact encoding of a source position within a file set. // It can be converted into a Position for a more convenient, but much // larger, representation. // // The Pos value for a given file is a number in the range [base, base+size], // where base and size are specified when adding the file to the file set via // AddFile. // // To create the Pos value for a specific source offset (measured in bytes), // first add the respective file to the current file set using FileSet.AddFile // and then call File.Pos(offset) for that file. Given a Pos value p // for a specific file set fset, the corresponding Position value is // obtained by calling fset.Position(p). // // Pos values can be compared directly with the usual comparison operators: // If two Pos values p and q are in the same file, comparing p and q is // equivalent to comparing the respective source file offsets. If p and q // are in different files, p < q is true if the file implied by p was added // to the respective file set before the file implied by q. type Pos = token.Pos const ( // NoPos - The zero value for Pos is NoPos; there is no file and line // information associated with it, and NoPos.IsValid() is false. NoPos // is always smaller than any other Pos value. The corresponding // Position value for NoPos is the zero value for Position. NoPos = token.NoPos ) // Position describes an arbitrary source position // including the file, line, and column location. // A Position is valid if the line number is > 0. type Position = token.Position // A File is a handle for a file belonging to a FileSet. // A File has a name, size, and line offset table. type File = token.File // Lines returns the effective line offset table of the form described by SetLines. // Callers must not mutate the result. func Lines(f *File) []int { return tokenutil.Lines(f) } // A FileSet represents a set of source files. Methods of file sets are // synchronized; multiple goroutines may invoke them concurrently. type FileSet = token.FileSet // NewFileSet creates a new file set. func NewFileSet() *FileSet { return token.NewFileSet() } ================================================ FILE: tool/_gendeps.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package tool import ( "fmt" "go/token" "io/fs" "os" "path/filepath" "strings" "github.com/goplus/mod/gopmod" "github.com/goplus/mod/modfetch" "github.com/goplus/xgo/parser" astmod "github.com/goplus/xgo/ast/mod" ) // ----------------------------------------------------------------------------- func GenDepMods(mod *gopmod.Module, dir string, recursively bool) (ret map[string]struct{}, err error) { modBase := mod.Path() ret = make(map[string]struct{}) for _, r := range mod.Opt.Import { ret[r.ClassfileMod] = struct{}{} } err = HandleDeps(mod, dir, recursively, func(pkgPath string) { modPath, _ := modfetch.Split(pkgPath, modBase) if modPath != "" && modPath != modBase { ret[modPath] = struct{}{} } }) return } func HandleDeps(mod *gopmod.Module, dir string, recursively bool, h func(pkgPath string)) (err error) { g := depsGen{ deps: astmod.Deps{HandlePkg: h}, mod: mod, fset: token.NewFileSet(), } if recursively { err = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if err == nil && d.IsDir() { if strings.HasPrefix(d.Name(), "_") { // skip _ return filepath.SkipDir } err = g.gen(path) if err != nil { fmt.Fprintln(os.Stderr, err) } } return err }) } else { err = g.gen(dir) } return } type depsGen struct { deps astmod.Deps mod *gopmod.Module fset *token.FileSet } func (p depsGen) gen(dir string) (err error) { pkgs, err := parser.ParseDirEx(p.fset, dir, parser.Config{ ClassKind: p.mod.ClassKind, Mode: parser.ImportsOnly, }) if err != nil { return } for _, pkg := range pkgs { p.deps.Load(pkg, false) } return } // ----------------------------------------------------------------------------- ================================================ FILE: tool/build_install_run.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package tool import ( "log" "os" "github.com/goplus/xgo/x/gocmd" "github.com/qiniu/x/errors" ) func genFlags(flags []GenFlags) GenFlags { if flags != nil { return flags[0] } return 0 } // ----------------------------------------------------------------------------- // InstallDir installs an XGo package directory. func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig, flags ...GenFlags) (err error) { _, _, err = GenGoEx(dir, conf, false, genFlags(flags)) if err != nil { return errors.NewWith(err, `GenGo(dir, conf, false)`, -2, "tool.GenGo", dir, conf, false) } return gocmd.Install(dir, install) } // InstallPkgPath installs an XGo package. func InstallPkgPath(workDir, pkgPath string, conf *Config, install *gocmd.InstallConfig, flags ...GenFlags) (err error) { localDir, recursively, err := GenGoPkgPathEx(workDir, pkgPath, conf, true, genFlags(flags)) if err != nil { return errors.NewWith(err, `GenGoPkgPath(workDir, pkgPath, conf, true)`, -2, "tool.GenGoPkgPath", workDir, pkgPath, conf, true) } old := chdir(localDir) defer os.Chdir(old) return gocmd.Install(cwdParam(recursively), install) } func cwdParam(recursively bool) string { if recursively { return "./..." } return "." } // InstallFiles installs specified XGo files. func InstallFiles(files []string, conf *Config, install *gocmd.InstallConfig) (err error) { files, err = GenGoFiles("", files, conf) if err != nil { return errors.NewWith(err, `GenGoFiles("", files, conf)`, -2, "tool.GenGoFiles", "", files, conf) } return gocmd.InstallFiles(files, install) } func chdir(dir string) string { old, err := os.Getwd() if err != nil { log.Panicln(err) } err = os.Chdir(dir) if err != nil { log.Panicln(err) } return old } // ----------------------------------------------------------------------------- // BuildDir builds an XGo package directory. func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig, flags ...GenFlags) (err error) { _, _, err = GenGoEx(dir, conf, false, genFlags(flags)) if err != nil { return errors.NewWith(err, `GenGo(dir, conf, false)`, -2, "tool.GenGo", dir, conf, false) } return gocmd.Build(dir, build) } // BuildPkgPath builds an XGo package. func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfig, flags ...GenFlags) (err error) { localDir, recursively, err := GenGoPkgPathEx(workDir, pkgPath, conf, false, genFlags(flags)) if err != nil { return errors.NewWith(err, `GenGoPkgPath(workDir, pkgPath, conf, false)`, -2, "tool.GenGoPkgPath", workDir, pkgPath, conf, false) } old, mod := chdirAndMod(localDir) defer restoreDirAndMod(old, mod) return gocmd.Build(cwdParam(recursively), build) } // BuildFiles builds specified XGo files. func BuildFiles(files []string, conf *Config, build *gocmd.BuildConfig) (err error) { files, err = GenGoFiles("", files, conf) if err != nil { return errors.NewWith(err, `GenGoFiles("", files, conf)`, -2, "tool.GenGoFiles", "", files, conf) } return gocmd.BuildFiles(files, build) } func chdirAndMod(dir string) (old string, mod os.FileMode) { mod = 0755 if info, err := os.Stat(dir); err == nil { mod = info.Mode().Perm() } os.Chmod(dir, 0777) old = chdir(dir) return } func restoreDirAndMod(old string, mod os.FileMode) { os.Chmod(".", mod) os.Chdir(old) } // ----------------------------------------------------------------------------- // If no go.mod and used XGo, use XGOROOT as buildDir. func getBuildDir(conf *Config) string { if conf != nil && conf.XGoDeps != nil && *conf.XGoDeps != 0 { return conf.XGo.Root } return "" } // RunDir runs an application from an XGo package directory. func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig, flags ...GenFlags) (err error) { _, _, err = GenGoEx(dir, conf, false, genFlags(flags)) if err != nil { return errors.NewWith(err, `GenGo(dir, conf, false)`, -2, "tool.GenGo", dir, conf, false) } return gocmd.RunDir(getBuildDir(conf), dir, args, run) } // RunPkgPath runs an application from an XGo package. func RunPkgPath(pkgPath string, args []string, chDir bool, conf *Config, run *gocmd.RunConfig, flags ...GenFlags) (err error) { localDir, recursively, err := GenGoPkgPathEx("", pkgPath, conf, true, genFlags(flags)) if err != nil { return errors.NewWith(err, `GenGoPkgPath("", pkgPath, conf, true)`, -2, "tool.GenGoPkgPath", "", pkgPath, conf, true) } if recursively { return errors.NewWith(errors.New("can't use ... pattern for `xgo run` command"), `recursively`, -1, "", recursively) } if chDir { old := chdir(localDir) defer os.Chdir(old) localDir = "." } return gocmd.RunDir("", localDir, args, run) } // RunFiles runs an application from specified XGo files. func RunFiles(autogen string, files []string, args []string, conf *Config, run *gocmd.RunConfig) (err error) { files, err = GenGoFiles(autogen, files, conf) if err != nil { return errors.NewWith(err, `GenGoFiles(autogen, files, conf)`, -2, "tool.GenGoFiles", autogen, files, conf) } return gocmd.RunFiles(getBuildDir(conf), files, args, run) } // ----------------------------------------------------------------------------- // TestDir tests an XGo package directory. func TestDir(dir string, conf *Config, test *gocmd.TestConfig, flags ...GenFlags) (err error) { _, _, err = GenGoEx(dir, conf, true, genFlags(flags)) if err != nil { return errors.NewWith(err, `GenGo(dir, conf, true)`, -2, "tool.GenGo", dir, conf, true) } return gocmd.Test(dir, test) } // TestPkgPath tests an XGo package. func TestPkgPath(workDir, pkgPath string, conf *Config, test *gocmd.TestConfig, flags ...GenFlags) (err error) { localDir, recursively, err := GenGoPkgPathEx(workDir, pkgPath, conf, false, genFlags(flags)) if err != nil { return errors.NewWith(err, `GenGoPkgPath(workDir, pkgPath, conf, false)`, -2, "tool.GenGoPkgPath", workDir, pkgPath, conf, false) } old, mod := chdirAndMod(localDir) defer restoreDirAndMod(old, mod) return gocmd.Test(cwdParam(recursively), test) } // TestFiles tests specified XGo files. func TestFiles(files []string, conf *Config, test *gocmd.TestConfig) (err error) { files, err = GenGoFiles("", files, conf) if err != nil { return errors.NewWith(err, `GenGoFiles("", files, conf)`, -2, "tool.GenGoFiles", "", files, conf) } return gocmd.TestFiles(files, test) } // ----------------------------------------------------------------------------- ================================================ FILE: tool/gengo.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package tool import ( "fmt" "io/fs" "os" "path/filepath" "strings" "syscall" "github.com/goplus/mod/modcache" "github.com/goplus/mod/modfetch" "github.com/goplus/mod/xgomod" "github.com/qiniu/x/errors" ) const ( testingGoFile = "_test" autoGenFile = "xgo_autogen.go" autoGenTestFile = "xgo_autogen_test.go" autoGen2TestFile = "xgo_autogen2_test.go" ) type GenFlags int const ( GenFlagCheckOnly GenFlags = 1 << iota GenFlagSingleFile GenFlagPrintError GenFlagPrompt ) // ----------------------------------------------------------------------------- // GenGo generates xgo_autogen.go for an XGo package directory. func GenGo(dir string, conf *Config, genTestPkg bool) (string, bool, error) { return GenGoEx(dir, conf, genTestPkg, 0) } // GenGoEx generates xgo_autogen.go for an XGo package directory. func GenGoEx(dir string, conf *Config, genTestPkg bool, flags GenFlags) (string, bool, error) { recursively := strings.HasSuffix(dir, "/...") if recursively { dir = dir[:len(dir)-4] } return dir, recursively, genGoDir(dir, conf, genTestPkg, recursively, flags) } func genGoDir(dir string, conf *Config, genTestPkg, recursively bool, flags GenFlags) (err error) { if conf == nil { conf = new(Config) } if recursively { var ( list errors.List fn func(path string, d fs.DirEntry, err error) error ) if flags&GenFlagSingleFile != 0 { fn = func(path string, d fs.DirEntry, err error) error { if err != nil { return err } return genGoEntry(&list, path, d, conf, flags) } } else { fn = func(path string, d fs.DirEntry, err error) error { if err == nil && d.IsDir() { if strings.HasPrefix(d.Name(), "_") || (path != dir && hasMod(path)) { // skip _ return filepath.SkipDir } if e := genGoIn(path, conf, genTestPkg, flags); e != nil && notIgnNotated(e, conf) { if flags&GenFlagPrintError != 0 { fmt.Fprintln(os.Stderr, e) } list.Add(e) } } return err } } err = filepath.WalkDir(dir, fn) if err != nil { return errors.NewWith(err, `filepath.WalkDir(dir, fn)`, -2, "filepath.WalkDir", dir, fn) } return list.ToError() } if flags&GenFlagSingleFile != 0 { var list errors.List var entries, e = os.ReadDir(dir) if e != nil { return errors.NewWith(e, `os.ReadDir(dir)`, -2, "os.ReadDir", dir) } for _, d := range entries { genGoEntry(&list, filepath.Join(dir, d.Name()), d, conf, flags) } return list.ToError() } if e := genGoIn(dir, conf, genTestPkg, flags); e != nil && notIgnNotated(e, conf) { if (flags & GenFlagPrintError) != 0 { fmt.Fprintln(os.Stderr, e) } err = e } return } func hasMod(dir string) bool { _, err := os.Lstat(dir + "/go.mod") return err == nil } func notIgnNotated(e error, conf *Config) bool { return !(conf != nil && conf.IgnoreNotatedError && IgnoreNotated(e)) } func genGoEntry(list *errors.List, path string, d fs.DirEntry, conf *Config, flags GenFlags) error { fname := d.Name() if strings.HasPrefix(fname, "_") { // skip _ if d.IsDir() { return filepath.SkipDir } } else if !d.IsDir() && (strings.HasSuffix(fname, ".xgo") || strings.HasSuffix(fname, ".gop")) { if e := genGoSingleFile(path, 4, conf, flags); e != nil && notIgnNotated(e, conf) { if flags&GenFlagPrintError != 0 { fmt.Fprintln(os.Stderr, e) } list.Add(e) } } return nil } func genGoSingleFile(file string, extn int, conf *Config, flags GenFlags) (err error) { dir, fname := filepath.Split(file) autogen := dir + fname[:len(fname)-extn] + "_autogen.go" if (flags & GenFlagPrompt) != 0 { fmt.Fprintln(os.Stderr, "GenGo", file, "...") } out, err := LoadFiles(".", []string{file}, conf) if err != nil { return errors.NewWith(err, `LoadFiles(files, conf)`, -2, "tool.LoadFiles", file) } if flags&GenFlagCheckOnly != 0 { return nil } if err := out.WriteFile(autogen); err != nil { return errors.NewWith(err, `out.WriteFile(autogen)`, -2, "(*gogen.Package).WriteFile", out, autogen) } return nil } func genGoIn(dir string, conf *Config, genTestPkg bool, flags GenFlags, gen ...*bool) (err error) { out, test, err := LoadDir(dir, conf, genTestPkg, (flags&GenFlagPrompt) != 0) if err != nil { if NotFound(err) { // no XGo source files return nil } return errors.NewWith(err, `LoadDir(dir, conf, genTestPkg)`, -5, "tool.LoadDir", dir, conf, genTestPkg) } if flags&GenFlagCheckOnly != 0 { return nil } os.MkdirAll(dir, 0755) file := filepath.Join(dir, autoGenFile) err = out.WriteFile(file) if err != nil { return errors.NewWith(err, `out.WriteFile(file)`, -2, "(*gogen.Package).WriteFile", out, file) } if gen != nil { // say `xgo_autogen.go generated` *gen[0] = true } testFile := filepath.Join(dir, autoGenTestFile) err = out.WriteFile(testFile, testingGoFile) if err != nil && err != syscall.ENOENT { return errors.NewWith(err, `out.WriteFile(testFile, testingGoFile)`, -2, "(*gogen.Package).WriteFile", out, testFile, testingGoFile) } if test != nil { testFile = filepath.Join(dir, autoGen2TestFile) err = test.WriteFile(testFile, testingGoFile) if err != nil { return errors.NewWith(err, `test.WriteFile(testFile, testingGoFile)`, -2, "(*gogen.Package).WriteFile", test, testFile, testingGoFile) } } else { err = nil } return } // ----------------------------------------------------------------------------- const ( modWritable = 0755 modReadonly = 0555 ) // GenGoPkgPath generates xgo_autogen.go for an XGo package. func GenGoPkgPath(workDir, pkgPath string, conf *Config, allowExtern bool) (localDir string, recursively bool, err error) { return GenGoPkgPathEx(workDir, pkgPath, conf, allowExtern, 0) } func remotePkgPath(pkgPath string, conf *Config, recursively bool, flags GenFlags) (localDir string, _recursively bool, err error) { remotePkgPathDo(pkgPath, func(dir, _ string) { os.Chmod(dir, modWritable) defer os.Chmod(dir, modReadonly) localDir = dir _recursively = recursively err = genGoDir(dir, conf, false, recursively, flags) }, func(e error) { err = e }) return } // GenGoPkgPathEx generates xgo_autogen.go for an XGo package. func GenGoPkgPathEx(workDir, pkgPath string, conf *Config, allowExtern bool, flags GenFlags) (localDir string, recursively bool, err error) { recursively = strings.HasSuffix(pkgPath, "/...") if recursively { pkgPath = pkgPath[:len(pkgPath)-4] } else if allowExtern && strings.Contains(pkgPath, "@") { return remotePkgPath(pkgPath, conf, false, flags) } mod, err := xgomod.Load(workDir) if NotFound(err) && allowExtern { return remotePkgPath(pkgPath, conf, recursively, flags) } else if err != nil { return } pkg, err := mod.Lookup(pkgPath) if err != nil { return } localDir = pkg.Dir if pkg.Type == xgomod.PkgtExtern { os.Chmod(localDir, modWritable) defer os.Chmod(localDir, modReadonly) } err = genGoDir(localDir, conf, false, recursively, flags) return } func remotePkgPathDo(pkgPath string, doSth func(pkgDir, modDir string), onErr func(e error)) { modVer, leftPart, err := modfetch.GetPkg(pkgPath, "") if err != nil { onErr(err) } else if dir, err := modcache.Path(modVer); err != nil { onErr(err) } else { doSth(filepath.Join(dir, leftPart), dir) } } // ----------------------------------------------------------------------------- // GenGoFiles generates xgo_autogen.go for specified XGo files. func GenGoFiles(autogen string, files []string, conf *Config) (outFiles []string, err error) { if conf == nil { conf = new(Config) } if autogen == "" { autogen = "xgo_autogen.go" if len(files) == 1 { file := files[0] srcDir, fname := filepath.Split(file) if hasMultiXgoFiles(srcDir) { autogen = filepath.Join(srcDir, "xgo_autogen_"+fname+".go") } } } out, err := LoadFiles(".", files, conf) if err != nil { err = errors.NewWith(err, `LoadFiles(files, conf)`, -2, "tool.LoadFiles", files, conf) return } err = out.WriteFile(autogen) if err != nil { err = errors.NewWith(err, `out.WriteFile(autogen)`, -2, "(*gogen.Package).WriteFile", out, autogen) } outFiles = []string{autogen} return } func hasMultiXgoFiles(srcDir string) bool { var has bool if f, err := os.Open(srcDir); err == nil { defer f.Close() fis, _ := f.ReadDir(-1) for _, fi := range fis { ext := filepath.Ext(fi.Name()) if !fi.IsDir() && (ext == ".xgo" || ext == ".gop") { if has { return true } has = true } } } return false } // ----------------------------------------------------------------------------- ================================================ FILE: tool/imp.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package tool import ( "crypto/sha256" "encoding/base64" "fmt" "go/token" "go/types" "io" "log" "os" "os/exec" "path" "path/filepath" "runtime" "strings" "github.com/goplus/gogen/packages" "github.com/goplus/gogen/packages/cache" "github.com/goplus/mod/env" "github.com/goplus/mod/modfetch" "github.com/goplus/mod/modfile" "github.com/goplus/mod/xgomod" ) // ----------------------------------------------------------------------------- // Importer represents an XGo importer. type Importer struct { impFrom *packages.Importer mod *xgomod.Module xgo *env.XGo fset *token.FileSet Flags GenFlags // can change this for loading XGo modules importStack map[string]bool } // NewImporter creates an XGo Importer. func NewImporter(mod *xgomod.Module, xgo *env.XGo, fset *token.FileSet) *Importer { const ( defaultFlags = GenFlagPrompt | GenFlagPrintError ) if mod == nil || !mod.HasModfile() { if modGop, e := xgomod.LoadFrom(filepath.Join(xgo.Root, "go.mod"), ""); e == nil { modGop.ImportClasses() mod = modGop } else { mod = xgomod.Default } } dir := mod.Root() impFrom := packages.NewImporter(fset, dir) ret := &Importer{mod: mod, xgo: xgo, impFrom: impFrom, fset: fset, Flags: defaultFlags, importStack: make(map[string]bool)} impFrom.SetCache(cache.New(ret.PkgHash)) return ret } func (p *Importer) SetTags(tags string) { p.impFrom.SetTags(tags) if c, ok := p.impFrom.Cache().(*cache.Impl); ok { c.SetTags(tags) } } // CacheFile returns file path of the cache. func (p *Importer) CacheFile() string { cacheDir, _ := os.UserCacheDir() cacheDir += "/xgo-build/" os.MkdirAll(cacheDir, 0755) fname := "" h := sha256.New() io.WriteString(h, runtime.Version()) if root := p.mod.Root(); root != "" { io.WriteString(h, root) fname = filepath.Base(root) } if tags := p.impFrom.Tags(); tags != "" { io.WriteString(h, tags) } hash := base64.RawURLEncoding.EncodeToString(h.Sum(nil)) return cacheDir + hash + fname } // Cache returns the cache object. func (p *Importer) Cache() *cache.Impl { return p.impFrom.Cache().(*cache.Impl) } // PkgHash calculates hash value for a package. // It is required by cache.New func. func (p *Importer) PkgHash(pkgPath string, self bool) string { if pkg, e := p.mod.Lookup(pkgPath); e == nil { switch pkg.Type { case xgomod.PkgtStandard: return cache.HashSkip case xgomod.PkgtExtern: if pkg.Real.Version != "" { return pkg.Real.String() } fallthrough case xgomod.PkgtModule: return dirHash(p.mod, p.xgo, pkg.Dir, self) } } if isPkgInMod(pkgPath, xgoMod) || isPkgInMod(pkgPath, xMod) { return cache.HashSkip } log.Println("PkgHash: unexpected package -", pkgPath) return cache.HashInvalid } const ( xgoMod = "github.com/goplus/xgo" xMod = "github.com/qiniu/x" ) // Import imports a Go/XGo package. func (p *Importer) Import(pkgPath string) (pkg *types.Package, err error) { if p.importStack[pkgPath] { return nil, fmt.Errorf("cycle import detected: package %s imports itself", pkgPath) } p.importStack[pkgPath] = true defer delete(p.importStack, pkgPath) if strings.HasPrefix(pkgPath, xgoMod) { if suffix := pkgPath[len(xgoMod):]; suffix == "" || suffix[0] == '/' { xgoRoot := p.xgo.Root if suffix == "/cl/internal/gop-in-go/foo" { // for test github.com/goplus/xgo/cl if err = p.genGoExtern(xgoRoot+suffix, false); err != nil { return } } return p.impFrom.ImportFrom(pkgPath, xgoRoot, 0) } } if isPkgInMod(pkgPath, xMod) { return p.impFrom.ImportFrom(pkgPath, p.xgo.Root, 0) } if mod := p.mod; mod.HasModfile() { ret, e := mod.Lookup(pkgPath) if e != nil { return nil, e } switch ret.Type { case xgomod.PkgtExtern: isExtern := ret.Real.Version != "" if isExtern { if _, err = modfetch.Get(ret.Real.String()); err != nil { return } } modDir := ret.ModDir goModfile := filepath.Join(modDir, "go.mod") if _, e := os.Lstat(goModfile); e != nil { // no go.mod os.Chmod(modDir, modWritable) defer os.Chmod(modDir, modReadonly) os.WriteFile(goModfile, defaultGoMod(ret.ModPath), 0644) } return p.impFrom.ImportFrom(pkgPath, ret.ModDir, 0) case xgomod.PkgtModule, xgomod.PkgtLocal: if pkgPath == p.mod.Path() { break } if err = p.genGoExtern(ret.Dir, false); err != nil { return } case xgomod.PkgtStandard: return p.impFrom.ImportFrom(pkgPath, p.xgo.Root, 0) } } return p.impFrom.Import(pkgPath) } func (p *Importer) genGoExtern(dir string, isExtern bool) (err error) { genfile := filepath.Join(dir, autoGenFile) if _, err = os.Lstat(genfile); err != nil { // no xgo_autogen.go if isExtern { os.Chmod(dir, modWritable) defer os.Chmod(dir, modReadonly) } gen := false err = genGoIn(dir, &Config{XGo: p.xgo, Importer: p, Fset: p.fset}, false, p.Flags, &gen) if err != nil { return } if gen { cmd := exec.Command("go", "mod", "tidy") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Dir = dir err = cmd.Run() } } return } func isPkgInMod(pkgPath, modPath string) bool { if strings.HasPrefix(pkgPath, modPath) { suffix := pkgPath[len(modPath):] return suffix == "" || suffix[0] == '/' } return false } func defaultGoMod(modPath string) []byte { return []byte(`module ` + modPath + ` go 1.16 `) } func dirHash(mod *xgomod.Module, xgo *env.XGo, dir string, self bool) string { h := sha256.New() if self { fmt.Fprintf(h, "go\t%s\n", runtime.Version()) fmt.Fprintf(h, "xgo\t%s\n", xgo.Version) } if fis, err := os.ReadDir(dir); err == nil { for _, fi := range fis { if fi.IsDir() { continue } fname := fi.Name() if strings.HasPrefix(fname, "_") || !canCl(mod, fname) { continue } if v, e := fi.Info(); e == nil { fmt.Fprintf(h, "file\t%s\t%x\t%x\n", fname, v.Size(), v.ModTime().UnixNano()) } } } return base64.RawStdEncoding.EncodeToString(h.Sum(nil)) } func canCl(mod *xgomod.Module, fname string) bool { switch path.Ext(fname) { case ".go", ".xgo", ".gop", ".gox": return true default: ext := modfile.ClassExt(fname) return mod.IsClass(ext) } } // ----------------------------------------------------------------------------- ================================================ FILE: tool/load.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package tool import ( "fmt" "io/fs" "os" "path" "strings" "github.com/goplus/gogen" "github.com/goplus/mod/env" "github.com/goplus/mod/xgomod" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/token" "github.com/goplus/xgo/x/gocmd" "github.com/goplus/xgo/x/xgoenv" "github.com/qiniu/x/errors" ) var ( ErrNotFound = xgomod.ErrNotFound ErrIgnoreNotated = errors.New("notated error ignored") ) // NotFound returns if cause err is ErrNotFound or not func NotFound(err error) bool { return xgomod.IsNotFound(err) } // IgnoreNotated returns if cause err is ErrIgnoreNotated or not. func IgnoreNotated(err error) bool { return errors.Err(err) == ErrIgnoreNotated } // ErrorPos returns where the error occurs. func ErrorPos(err error) token.Pos { switch v := err.(type) { case *gogen.CodeError: return v.Pos case *gogen.MatchError: return v.Pos() case *gogen.ImportError: return v.Pos } return token.NoPos } func ignNotatedErrs(err error, pkg *ast.Package, fset *token.FileSet) error { switch v := err.(type) { case errors.List: var ret errors.List for _, e := range v { if isNotatedErr(e, pkg, fset) { continue } ret = append(ret, e) } if len(ret) == 0 { return ErrIgnoreNotated } return ret default: if isNotatedErr(err, pkg, fset) { return ErrIgnoreNotated } return err } } func isNotatedErr(err error, pkg *ast.Package, fset *token.FileSet) (notatedErr bool) { pos := ErrorPos(err) f := fset.File(pos) if f == nil { return } xgof, ok := pkg.Files[f.Name()] if !ok { return } lines := token.Lines(f) i := f.Line(pos) - 1 // base 0 start := lines[i] var end int if i+1 < len(lines) { end = lines[i+1] } else { end = f.Size() } text := string(xgof.Code[start:end]) commentOff := strings.Index(text, "//") if commentOff < 0 { return } return strings.Contains(text[commentOff+2:], "compile error:") } // ----------------------------------------------------------------------------- // Config represents a configuration for loading XGo packages. type Config struct { XGo *env.XGo Fset *token.FileSet Mod *xgomod.Module Importer *Importer Filter func(fs.FileInfo) bool // If not nil, it is used for returning result of checks XGo dependencies. // see https://pkg.go.dev/github.com/goplus/gogen#File.CheckGopDeps XGoDeps *int // CacheFile specifies the file path of the cache. CacheFile string IgnoreNotatedError bool DontUpdateGoMod bool } // ConfFlags represents configuration flags. type ConfFlags int const ( ConfFlagIgnoreNotatedError ConfFlags = 1 << iota ConfFlagDontUpdateGoMod ConfFlagNoTestFiles ConfFlagNoCacheFile ) // NewDefaultConf creates a dfault configuration for common cases. func NewDefaultConf(dir string, flags ConfFlags, tags ...string) (conf *Config, err error) { mod, err := LoadMod(dir) if err != nil { return } xgo := xgoenv.Get() fset := token.NewFileSet() imp := NewImporter(mod, xgo, fset) if len(tags) > 0 { imp.SetTags(strings.Join(tags, ",")) } conf = &Config{ XGo: xgo, Fset: fset, Mod: mod, Importer: imp, IgnoreNotatedError: flags&ConfFlagIgnoreNotatedError != 0, DontUpdateGoMod: flags&ConfFlagDontUpdateGoMod != 0, } if flags&ConfFlagNoCacheFile == 0 { conf.CacheFile = imp.CacheFile() imp.Cache().Load(conf.CacheFile) } if flags&ConfFlagNoTestFiles != 0 { conf.Filter = FilterNoTestFiles } return } func (conf *Config) NewGoCmdConf() *gocmd.Config { if cl := conf.Mod.Opt.Compiler; cl != nil { if os.Getenv("XGO_GOCMD") == "" { os.Setenv("XGO_GOCMD", cl.Name) } } return &gocmd.Config{ XGo: conf.XGo, } } // UpdateCache updates the cache. func (conf *Config) UpdateCache(verbose ...bool) { if conf.CacheFile != "" { c := conf.Importer.Cache() c.Save(conf.CacheFile) if verbose != nil && verbose[0] { fmt.Println("Times of calling go list:", c.ListTimes()) } } } // LoadMod loads an XGo module from a specified directory. func LoadMod(dir string) (mod *xgomod.Module, err error) { mod, err = xgomod.Load(dir) if err != nil && !xgomod.IsNotFound(err) { err = errors.NewWith(err, `xgomod.Load(dir, 0)`, -2, "xgomod.Load", dir, 0) return } if mod == nil { mod = xgomod.Default } err = mod.ImportClasses() if err != nil { err = errors.NewWith(err, `mod.ImportClasses()`, -2, "(*xgomod.Module).ImportClasses", mod) } return } // FilterNoTestFiles filters to skip all testing files. func FilterNoTestFiles(fi fs.FileInfo) bool { fname := fi.Name() suffix := "" switch path.Ext(fname) { case ".gox": suffix = "test.gox" case ".xgo": suffix = "_test.xgo" case ".gop": suffix = "_test.gop" case ".go": suffix = "_test.go" default: return true } return !strings.HasSuffix(fname, suffix) } // ----------------------------------------------------------------------------- // LoadDir loads XGo packages from a specified directory. func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (out, test *gogen.Package, err error) { if conf == nil { conf = new(Config) } mod := conf.Mod if mod == nil { if mod, err = LoadMod(dir); err != nil { err = errors.NewWith(err, `LoadMod(dir)`, -2, "tool.LoadMod", dir) return } } fset := conf.Fset if fset == nil { fset = token.NewFileSet() } pkgs, err := parser.ParseDirEx(fset, dir, parser.Config{ ClassKind: mod.ClassKind, Filter: conf.Filter, Mode: parser.ParseComments | parser.SaveAbsFile, }) if err != nil { return } if len(pkgs) == 0 { return nil, nil, ErrNotFound } xgo := conf.XGo if xgo == nil { xgo = xgoenv.Get() } imp := conf.Importer if imp == nil { imp = NewImporter(mod, xgo, fset) } var pkgTest *ast.Package var clConf = &cl.Config{ Fset: fset, RelativeBase: relativeBaseOf(mod), Importer: imp, LookupClass: mod.LookupClass, } for name, pkg := range pkgs { if strings.HasSuffix(name, "_test") { if pkgTest != nil { return nil, nil, ErrMultiTestPackges } pkgTest = pkg continue } if out != nil { return nil, nil, ErrMultiPackges } if len(pkg.Files) == 0 { // no XGo source files continue } if promptGenGo != nil && promptGenGo[0] { fmt.Fprintln(os.Stderr, "GenGo", dir, "...") } out, err = cl.NewPackage("", pkg, clConf) if err != nil { if conf.IgnoreNotatedError { err = ignNotatedErrs(err, pkg, fset) } return } } if out == nil { return nil, nil, ErrNotFound } if genTestPkg && pkgTest != nil { test, err = cl.NewPackage("", pkgTest, clConf) } afterLoad(mod, xgo, out, test, conf) return } func afterLoad(mod *xgomod.Module, xgo *env.XGo, out, test *gogen.Package, conf *Config) { if mod.Path() == xgoMod { // nothing to do for XGo itself return } updateMod := !conf.DontUpdateGoMod && mod.HasModfile() if updateMod || conf.XGoDeps != nil { flags := checkGopDeps(out) if conf.XGoDeps != nil { // for `xgo run` *conf.XGoDeps = flags } if updateMod { if test != nil { flags |= checkGopDeps(test) } if flags != 0 { mod.SaveWithXGoMod(xgo, flags) } } } } func checkGopDeps(pkg *gogen.Package) (flags int) { pkg.ForEachFile(func(fname string, file *gogen.File) { flags |= file.CheckXGoDeps(pkg) }) return } func relativeBaseOf(mod *xgomod.Module) string { if mod.HasModfile() { return mod.Root() } dir, _ := os.Getwd() return dir } // ----------------------------------------------------------------------------- // LoadFiles loads an XGo package from specified files. func LoadFiles(dir string, files []string, conf *Config) (out *gogen.Package, err error) { if conf == nil { conf = new(Config) } mod := conf.Mod if mod == nil { if mod, err = LoadMod(dir); err != nil { err = errors.NewWith(err, `LoadMod(dir)`, -2, "tool.LoadMod", dir) return } } fset := conf.Fset if fset == nil { fset = token.NewFileSet() } pkgs, err := parser.ParseEntries(fset, files, parser.Config{ ClassKind: mod.ClassKind, Filter: conf.Filter, Mode: parser.ParseComments | parser.SaveAbsFile, }) if err != nil { err = errors.NewWith(err, `parser.ParseFiles(fset, files, parser.ParseComments)`, -2, "parser.ParseFiles", fset, files, parser.ParseComments) return } if len(pkgs) != 1 { err = errors.NewWith(ErrMultiPackges, `len(pkgs) != 1`, -1, "!=", len(pkgs), 1) return } xgo := conf.XGo if xgo == nil { xgo = xgoenv.Get() } for _, pkg := range pkgs { imp := conf.Importer if imp == nil { imp = NewImporter(mod, xgo, fset) } clConf := &cl.Config{ Fset: fset, RelativeBase: relativeBaseOf(mod), Importer: imp, LookupClass: mod.LookupClass, } out, err = cl.NewPackage("", pkg, clConf) if err != nil { if conf.IgnoreNotatedError { err = ignNotatedErrs(err, pkg, fset) } } break } afterLoad(mod, xgo, out, nil, conf) return } // ----------------------------------------------------------------------------- var ( ErrMultiPackges = errors.New("multiple packages") ErrMultiTestPackges = errors.New("multiple test packages") ) // ----------------------------------------------------------------------------- // GetFileClassType get xgo module file classType. func GetFileClassType(mod *xgomod.Module, file *ast.File, filename string) (classType string, isTest bool) { return cl.GetFileClassType(file, filename, mod.LookupClass) } // ----------------------------------------------------------------------------- ================================================ FILE: tool/outline.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package tool import ( "fmt" "go/token" "io/fs" "os" "path" "path/filepath" "strings" "github.com/goplus/mod/xgomod" "github.com/goplus/xgo/cl/outline" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/x/xgoenv" "github.com/qiniu/x/errors" ) // ----------------------------------------------------------------------------- func Outline(dir string, conf *Config) (out outline.Package, err error) { if dir, err = filepath.Abs(dir); err != nil { return } if conf == nil { conf = new(Config) } mod := conf.Mod if mod == nil { if mod, err = LoadMod(dir); err != nil { err = errors.NewWith(err, `LoadMod(dir)`, -2, "tool.LoadMod", dir) return } } filterConf := conf.Filter filter := func(fi fs.FileInfo) bool { if filterConf != nil && !filterConf(fi) { return false } fname := fi.Name() if pos := strings.Index(fname, "."); pos > 0 { fname = fname[:pos] } return !strings.HasSuffix(fname, "_test") } fset := conf.Fset if fset == nil { fset = token.NewFileSet() } pkgs, err := parser.ParseDirEx(fset, dir, parser.Config{ ClassKind: mod.ClassKind, Filter: filter, Mode: parser.ParseComments, }) if err != nil { return } if len(pkgs) == 0 { err = ErrNotFound return } imp := conf.Importer if imp == nil { xgo := conf.XGo if xgo == nil { xgo = xgoenv.Get() } imp = NewImporter(mod, xgo, fset) } for name, pkg := range pkgs { if out.Valid() { err = fmt.Errorf("%w: %s, %s", ErrMultiPackges, name, out.Pkg().Name()) return } if len(pkg.Files)+len(pkg.GoFiles) == 0 { // no Go/XGo source files break } relPart, _ := filepath.Rel(mod.Root(), dir) pkgPath := path.Join(mod.Path(), filepath.ToSlash(relPart)) out, err = outline.NewPackage(pkgPath, pkg, &outline.Config{ Fset: fset, Importer: imp, LookupClass: mod.LookupClass, }) if err != nil { return } } if !out.Valid() { err = ErrNotFound } return } // ----------------------------------------------------------------------------- func OutlinePkgPath(workDir, pkgPath string, conf *Config, allowExtern bool) (out outline.Package, err error) { mod := conf.Mod if mod == nil { if mod, err = LoadMod(workDir); err != nil { err = errors.NewWith(err, `LoadMod(dir)`, -2, "tool.LoadMod", workDir) return } } if NotFound(err) && allowExtern { remotePkgPathDo(pkgPath, func(pkgDir, modDir string) { modFile := chmodModfile(modDir) defer os.Chmod(modFile, modReadonly) out, err = Outline(pkgDir, conf) }, func(e error) { err = e }) return } else if err != nil { return } pkg, err := mod.Lookup(pkgPath) if err != nil { return } if pkg.Type == xgomod.PkgtExtern { modFile := chmodModfile(pkg.ModDir) defer os.Chmod(modFile, modReadonly) } return Outline(pkg.Dir, conf) } func chmodModfile(modDir string) string { modFile := modDir + "/go.mod" os.Chmod(modFile, modWritable) return modFile } // ----------------------------------------------------------------------------- ================================================ FILE: tool/tidy.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package tool import ( "os" "os/exec" "github.com/goplus/mod/env" "github.com/goplus/mod/xgomod" "github.com/qiniu/x/errors" ) func Tidy(dir string, xgo *env.XGo) (err error) { modObj, err := xgomod.Load(dir) if err != nil { return errors.NewWith(err, `xgomod.Load(dir, mod.GopModOnly)`, -2, "xgomod.Load", dir) } modRoot := modObj.Root() /* depMods, err := GenDepMods(modObj, modRoot, true) if err != nil { return errors.NewWith(err, `GenDepMods(modObj, modRoot, true)`, -2, "tool.GenDepMods", modObj, modRoot, true) } old := modObj.DepMods() for modPath := range old { if _, ok := depMods[modPath]; !ok { // removed modObj.DropRequire(modPath) } } for modPath := range depMods { if _, ok := old[modPath]; !ok { // added if newMod, e := modfetch.Get(modPath); e != nil { return errors.NewWith(e, `modfetch.Get(modPath)`, -1, "modfetch.Get", modPath) } else { modObj.AddRequire(newMod.Path, newMod.Version) } } } modObj.Cleanup() err = modObj.Save() if err != nil { return errors.NewWith(err, `modObj.Save()`, -2, "(*xgomod.Module).Save") } */ conf := &Config{XGo: xgo} err = genGoDir(modRoot, conf, true, true, 0) if err != nil { return errors.NewWith(err, `genGoDir(modRoot, conf, true, true)`, -2, "tool.genGoDir", modRoot, conf, true, true) } cmd := exec.Command("go", "mod", "tidy") cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Dir = modRoot err = cmd.Run() if err != nil { err = errors.NewWith(err, `cmd.Run()`, -2, "(*exec.Cmd).Run") } return } ================================================ FILE: tpl/README.md ================================================ TPL: Text Processing Language ===== Text processing is a common task in programming, and regular expressions have long been the go-to solution. However, regular expressions are notorious for their cryptic syntax and poor readability. Enter XGo TPL (Text Processing Language), an enhanced alternative that offers both power and intuitive syntax. XGo TPL is a grammar-based language similar to EBNF (Extended Backus-Naur Form) that seamlessly integrates with XGo. It provides a more readable and maintainable approach to text processing while offering capabilities beyond what regular expressions can achieve. ## Understanding XGo TPL To understand XGo TPL, you need to grasp three key concepts: ### 1. Naming Rules The foundation of TPL is its naming rules, expressed as `name = rule`. A TPL grammar consists of a series of named rules, with the first one being the root rule. The `rule` can be a combination of: * **Basic Tokens**: Fundamental syntax units like `INT`, `FLOAT`, `CHAR`, `STRING`, `IDENT`, `"+"`, `"++"`, `"+="`, `"<<="`, etc. * **Keywords**: An `IDENT` enclosed in quotes, such as `"if"`, `"else"`, `"for"`. * **References**: References to other named rules, including self-references. * **Sequence**: `R1 R2 ... Rn` - matches a sequence of rules. * **Alternatives**: `R1 | R2 | ... | Rn` - matches any one of the rules. * **Repetition Operators**: * `*R` - matches the rule zero or more times * `+R` - matches the rule one or more times * `?R` - matches the rule zero or one time (optional) * **List Operator**: `R1 % R2` - shorthand for `R1 *(R2 R1)`, representing a sequence of R1 separated by R2. For example, `INT % ","` represents a comma-separated list of integers. * **Adjacency Operator**: `R1 ++ R2` - indicates that R1 and R2 must be adjacent with no whitespace or comments between them. The default operator precedence is: unary operators (`*R`, `+R`, `?R`) > `++` > `%` > sequence (space) > `|`. Parentheses can be used to change the precedence. #### String Literals in Detail `STRING` (string literals) can take two forms: ```go "Hello\nWorld\n" // QSTRING (quoted string) `Hello World ` // RAWSTRING (raw string) ``` `STRING` can be defined as: ```go STRING = QSTRING | RAWSTRING ``` #### The Adjacency Operator Explained Since TPL rules automatically filter whitespace and comments, the sequence `R1 R2` doesn't express that R1 and R2 are adjacent. This is where the adjacency operator `++` comes in. For example, XGo [domain text literal](../doc/domian-text-lit.md) is defined as `IDENT ++ RAWSTRING`, making these valid: ```go tpl`expr = INT % ","` json`{"name": "Ken", age: 15}` ``` While these would match `IDENT STRING` but are not valid domain text literals: ```go tpl"expr = *INT" // IDENT must be followed by RAWSTRING, not QSTRING tpl/* comment */`expr = *INT` // No whitespace or comments allowed between IDENT and RAWSTRING ``` ### 2. Matching Results Each rule has its built-in matching result: * **Tokens and Keywords**: Result is `*tpl.Token`. * **Sequence** (`R1 R2 ... Rn`): Result is a list (`[]any`) with n elements. * **Repetition** (`*R`, `+R`): Result is a list (`[]any`) with elements depending on how many times R matches. * **Alternatives** (`R1 | R2 | ... | Rn`): Result depends on which rule matches. * **Optional** (`?R`): Result is either the result of R or `nil` if no match. * **List Operator** (`R1 % R2`): Result is a complex tree-like structure with three levels. Let's explain why it has three levels: 1. The first level is the result of the entire expression `R1 % R2` (i.e.`R1 *(R2 R1)`), which is a list with two elements. 2. The first element of this list is the result of the first `R1`. 3. The second element is a list containing the results of all subsequent `(R2 R1)` matches. - Each element in this second-level list is itself a list with two elements: the result of `R2` and the result of `R1`. For example, when parsing `"1, 2, 3"` with `INT % ","`, the result structure would be: ``` [ , // First R1 [ [, ], // First (R2 R1) [, ] // Second (R2 R1) ] ] ``` This tree-like structure preserves all the information about the matched elements and their relationships, but can be complex to work with directly. That's why TPL provides helper functions like `ListOp` and `BinaryOp` to transform this structure into more usable forms. * **Adjacency Operator** (`R1 ++ R2`): Result is a list (`[]any`) with 2 elements, similar to a `R1 R2` sequence. ### 3. Rewriting Matching Results The default matching result is called "self" in TPL. You can rewrite this result using an XGo closure `=> { ... }`. This feature is crucial as it allows seamless integration between TPL and XGo. In XGo, you reference TPL through [domain text literal](../doc/domian-text-lit.md), and within TPL, you can call XGo code through result rewriting. ## Practical Examples ### Basic Example: Parsing Integers ```go import "xgo/tpl" cl := tpl` expr = INT % "," => { return tpl.ListOp[int](self, v => { return v.(*tpl.Token).Lit.int! }) } `! echo cl.parseExpr("1, 2, 3", nil)! // Outputs: [1 2 3] ``` This example parses a comma-separated list of integers and converts it to a flat list of integers using TPL's `ListOp` function. ### Building a Calculator Creating a calculator with XGo TPL is remarkably concise: ```go import "xgo/tpl" cl := tpl` expr = operand % ("*" | "/") % ("+" | "-") => { return tpl.BinaryOp(true, self, (op, x, y) => { switch op.Tok { case '+': return x.(float64) + y.(float64) case '-': return x.(float64) - y.(float64) case '*': return x.(float64) * y.(float64) case '/': return x.(float64) / y.(float64) } panic("unexpected") }) } operand = basicLit | unaryExpr unaryExpr = "-" operand => { return -(self[1].(float64)) } basicLit = INT | FLOAT => { return self.(*tpl.Token).Lit.float! } `! echo cl.parseExpr("1 + 2 * -3", nil)! // Outputs: -5 ``` This calculator handles basic arithmetic operations with proper operator precedence in less than 30 lines of code. ## Conclusion XGo TPL offers a powerful yet intuitive alternative to regular expressions for text processing. By combining grammar-based parsing with seamless XGo integration, it enables developers to create clear, maintainable text processing solutions. For more examples of TPL in action, check out the XGo demos starting with `tpl-` at [https://github.com/goplus/xgo/tree/main/demo](https://github.com/goplus/xgo/tree/main/demo). These examples showcase how to implement calculators, parse text to generate ASTs, and even implement entire languages in just a few hundred lines of code. Whether you're parsing structured text, building domain-specific languages, or implementing complex text transformations, XGo TPL provides a robust and readable approach that surpasses traditional regular expressions. ================================================ FILE: tpl/ast/ast.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ast import ( "github.com/goplus/xgo/tpl/token" ) // ----------------------------------------------------------------------------- // Node: File, Decl, Expr type Node interface { Pos() token.Pos End() token.Pos } // Decl: Rule type Decl interface { Node declNode() } // Expr: Ident, BasicLit, Choice, Sequence, UnaryExpr, BinaryExpr type Expr interface { Node exprNode() } // ----------------------------------------------------------------------------- // File: *Decl type File struct { Decls []Decl FileStart token.Pos } func (p *File) Pos() token.Pos { if n := len(p.Decls); n > 0 { return p.Decls[0].Pos() } return p.FileStart } func (p *File) End() token.Pos { if n := len(p.Decls); n > 0 { return p.Decls[n-1].End() } return p.FileStart } // ----------------------------------------------------------------------------- // Rule: // // IDENT '=' Expr // IDENT '=' Expr => { ... } type Rule struct { Name *Ident TokPos token.Pos // position of '=' Expr Expr RetProc Node // => { ... } (see xgo/ast.LambdaExpr2) or nil } // IsList reports whether the rule is a list rule. func (p *Rule) IsList() bool { switch e := p.Expr.(type) { case *Sequence: return true case *UnaryExpr: switch e.Op { case token.MUL, token.ADD: // *R, +R return true } case *BinaryExpr: switch e.Op { case token.REM, token.INC: // R1 % R2, R1 ++ R2 return true } } return false } func (p *Rule) Pos() token.Pos { return p.Name.Pos() } func (p *Rule) End() token.Pos { if p.RetProc != nil { return p.RetProc.End() } return p.Expr.End() } func (p *Rule) declNode() {} // ----------------------------------------------------------------------------- // Ident: IDENT type Ident struct { NamePos token.Pos // identifier position Name string // identifier name } func (p *Ident) Pos() token.Pos { return p.NamePos } func (p *Ident) End() token.Pos { return p.NamePos + token.Pos(len(p.Name)) } func (p *Ident) exprNode() {} // ----------------------------------------------------------------------------- // BasicLit: STRING | CHAR type BasicLit struct { ValuePos token.Pos // literal position Kind token.Token // token.STRING or token.CHAR Value string } func (p *BasicLit) Pos() token.Pos { return p.ValuePos } func (p *BasicLit) End() token.Pos { return p.ValuePos + token.Pos(len(p.Value)) } func (p *BasicLit) exprNode() {} // ----------------------------------------------------------------------------- // Choice: R1 | R2 | ... | Rn type Choice struct { Options []Expr // multiple options } func (p *Choice) Pos() token.Pos { return p.Options[0].Pos() } func (p *Choice) End() token.Pos { return p.Options[len(p.Options)-1].End() } func (p *Choice) exprNode() {} // ----------------------------------------------------------------------------- // Sequence: R1 R2 ... Rn type Sequence struct { Items []Expr // multiple items } func (p *Sequence) Pos() token.Pos { return p.Items[0].Pos() } func (p *Sequence) End() token.Pos { return p.Items[len(p.Items)-1].End() } func (p *Sequence) exprNode() {} // ----------------------------------------------------------------------------- // UnaryExpr: *R, +R or ?R type UnaryExpr struct { OpPos token.Pos // operator position Op token.Token // operator: token.MUL, token.ADD or token.QUESTION X Expr // operand } func (p *UnaryExpr) Pos() token.Pos { return p.OpPos } func (p *UnaryExpr) End() token.Pos { return p.X.End() } func (p *UnaryExpr) exprNode() {} // ----------------------------------------------------------------------------- // BinaryExpr: R1 % R2, R1 ++ R2 type BinaryExpr struct { X Expr // left operand OpPos token.Pos // operator position Op token.Token // operator: token.REM (list operator), token.INC (adjoin operator) Y Expr // right operand } func (p *BinaryExpr) Pos() token.Pos { return p.X.Pos() } func (p *BinaryExpr) End() token.Pos { return p.Y.End() } func (p *BinaryExpr) exprNode() {} // ----------------------------------------------------------------------------- ================================================ FILE: tpl/cl/compile.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cl import ( "fmt" "os" "strconv" "github.com/goplus/xgo/tpl/ast" "github.com/goplus/xgo/tpl/matcher" "github.com/goplus/xgo/tpl/token" "github.com/qiniu/x/errors" ) var ( // ErrNoDocFound error ErrNoDocFound = errors.New("no document rule found") ) // Result represents the result of compiling a set of rules. type Result struct { Doc *matcher.Var Rules map[string]*matcher.Var } type choice struct { m *matcher.Choices c *ast.Choice } type context struct { rules map[string]*matcher.Var choices []choice errs errors.List fset *token.FileSet } func (p *context) newErrorf(pos token.Pos, format string, args ...any) error { return &matcher.Error{Fset: p.fset, Pos: pos, Msg: fmt.Sprintf(format, args...)} } func (p *context) addErrorf(pos token.Pos, format string, args ...any) { p.errs.Add(p.newErrorf(pos, format, args...)) } func (p *context) addError(pos token.Pos, msg string) { p.errs.Add(&matcher.Error{Fset: p.fset, Pos: pos, Msg: msg}) } // New compiles a set of rules from the given files. func New(fset *token.FileSet, files ...*ast.File) (ret Result, err error) { return NewEx(nil, fset, files...) } // Config configures the behavior of the compiler. type Config struct { RetProcs map[string]any OnConflict func(fset *token.FileSet, c *ast.Choice, firsts [][]any, i, at int) } // NewEx compiles a set of rules from the given files. func NewEx(conf *Config, fset *token.FileSet, files ...*ast.File) (ret Result, err error) { if conf == nil { conf = &Config{} } retProcs := conf.RetProcs rules := make(map[string]*matcher.Var) ctx := &context{rules: rules, fset: fset} for _, f := range files { for _, decl := range f.Decls { switch decl := decl.(type) { case *ast.Rule: ident := decl.Name name := ident.Name if old, ok := rules[name]; ok { oldPos := fset.Position(old.Pos) ctx.addErrorf(ident.Pos(), "duplicate rule `%s`, previous declaration at %v", name, oldPos) continue } v := matcher.NewVar(ident.Pos(), name) rules[name] = v default: ctx.addError(decl.Pos(), "unknown declaration") } } } var doc *matcher.Var for _, f := range files { for _, decl := range f.Decls { switch decl := decl.(type) { case *ast.Rule: ident := decl.Name name := ident.Name v := rules[name] if r, ok := compileExpr(decl.Expr, ctx); ok { v.RetProc = retProcs[name] if e := v.Assign(r); e != nil { ctx.addError(ident.Pos(), e.Error()) } if doc == nil { doc = v } } } } } if doc == nil { err = ErrNoDocFound return } defer func() { if e := recover(); e != nil { switch e := e.(type) { case matcher.RecursiveError: ctx.addError(e.Pos, e.Error()) default: panic(e) } } err = ctx.errs.ToError() }() onConflict := conf.OnConflict if onConflict == nil { onConflict = onConflictDefault } for _, item := range ctx.choices { item.m.CheckConflicts(func(firsts [][]any, i, at int) { onConflict(fset, item.c, firsts, i, at) }) } ret = Result{doc, rules} return } func onConflictDefault(fset *token.FileSet, c *ast.Choice, firsts [][]any, i, at int) { pos := fset.Position(c.Options[i].Pos()) LogConflict(pos, firsts, i, at) } // LogConflict logs a conflict between two choices. func LogConflict(pos token.Position, firsts [][]any, i, at int) { fmt.Fprintf(os.Stderr, "%v: [WARN] conflict between %v and %v\n", pos, firsts[i], firsts[at]) } var ( idents = map[string]token.Token{ "EOF": token.EOF, "COMMENT": token.COMMENT, "IDENT": token.IDENT, "INT": token.INT, "FLOAT": token.FLOAT, "IMAG": token.IMAG, "CHAR": token.CHAR, "STRING": token.STRING, "RAT": token.RAT, "UNIT": token.UNIT, "LPAREN": token.LPAREN, "RPAREN": token.RPAREN, "LBRACK": token.LBRACK, "RBRACK": token.RBRACK, "LBRACE": token.LBRACE, "RBRACE": token.RBRACE, } ) func compileExpr(expr ast.Expr, ctx *context) (matcher.Matcher, bool) { switch expr := expr.(type) { case *ast.Ident: name := expr.Name if v, ok := ctx.rules[name]; ok { return v, true } else if tok, ok := idents[name]; ok { return matcher.Token(tok), true } var quoteCh byte switch name { case "RAWSTRING": quoteCh = '`' case "QSTRING": quoteCh = '"' case "SPACE": return matcher.WhiteSpace(), true default: ctx.addErrorf(expr.Pos(), "`%s` is undefined", name) } return matcher.String(quoteCh), true case *ast.BasicLit: lit := expr.Value switch expr.Kind { case token.CHAR: v, multibyte, tail, e := strconv.UnquoteChar(lit[1:len(lit)-1], '\'') if e != nil { ctx.addErrorf(expr.Pos(), "invalid literal %s: %v", lit, e) break } if tail != "" || multibyte { ctx.addError(expr.Pos(), "invalid literal "+lit) break } return tokenExpr(token.Token(v), expr, ctx) case token.STRING: v, e := strconv.Unquote(lit) if e != nil { ctx.addError(expr.Pos(), "invalid literal "+lit) break } if v == "" { return matcher.True(), true } if c := v[0]; c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '_' { return matcher.Literal(token.IDENT, v), true } if t, ok := checkToken(v); ok { return tokenExpr(t, expr, ctx) } fallthrough default: ctx.addError(expr.Pos(), "invalid literal "+lit) } case *ast.Sequence: items := make([]matcher.Matcher, len(expr.Items)) for i, item := range expr.Items { if r, ok := compileExpr(item, ctx); ok { items[i] = r } else { return nil, false } } return matcher.Sequence(items...), true case *ast.Choice: options := make([]matcher.Matcher, len(expr.Options)) for i, option := range expr.Options { if r, ok := compileExpr(option, ctx); ok { options[i] = r } else { return nil, false } } ret := matcher.Choice(options...) ctx.choices = append(ctx.choices, choice{ret, expr}) return ret, true case *ast.UnaryExpr: if x, ok := compileExpr(expr.X, ctx); ok { switch expr.Op { case token.QUESTION: return matcher.Repeat01(x), true case token.MUL: return matcher.Repeat0(x), true case token.ADD: return matcher.Repeat1(x), true default: ctx.addErrorf(expr.Pos(), "invalid token %v", expr.Op) } } case *ast.BinaryExpr: x, ok1 := compileExpr(expr.X, ctx) y, ok2 := compileExpr(expr.Y, ctx) if ok1 && ok2 { switch expr.Op { case token.REM: // % return matcher.List(x, y), true case token.INC: // ++ return matcher.Adjoin(x, y), true default: ctx.addErrorf(expr.Pos(), "invalid token %v", expr.Op) } } default: ctx.addError(expr.Pos(), "unknown expression") } return nil, false } func tokenExpr(tok token.Token, expr *ast.BasicLit, ctx *context) (matcher.Matcher, bool) { if tok.Len() > 0 { return matcher.Token(tok), true } ctx.addErrorf(expr.Pos(), "invalid token: %s", expr.Value) return nil, false } func checkToken(v string) (ret token.Token, ok bool) { if len(v) == 1 { return token.Token(v[0]), true } token.ForEach(0, func(tok token.Token, lit string) int { if lit == v { ret, ok = tok, true return token.Break } return 0 }) return } ================================================ FILE: tpl/matcher/match.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package matcher import ( "errors" "fmt" "log" "github.com/goplus/xgo/tpl/token" "github.com/goplus/xgo/tpl/types" ) var ( // ErrVarAssigned error ErrVarAssigned = errors.New("variable is already assigned") errNoWhitespace = errors.New("no whitespace") errAdjoinEmpty = errors.New("adjoin empty") errMultiMismatch = errors.New("multiple mismatch") ) // ----------------------------------------------------------------------------- type dbgFlags int const ( DbgFlagMatchVar dbgFlags = 1 << iota DbgFlagAll = DbgFlagMatchVar ) var ( enableMatchVar bool ) func SetDebug(flags dbgFlags) { enableMatchVar = (flags & DbgFlagMatchVar) != 0 } // ----------------------------------------------------------------------------- // Error represents a matching error. type Error struct { Fset *token.FileSet Pos token.Pos Msg string // is a runtime error Dyn bool } func (p *Error) Error() string { pos := p.Fset.Position(p.Pos) return fmt.Sprintf("%v: %s", pos, p.Msg) } func isDyn(err error) bool { if e, ok := err.(*Error); ok { return e.Dyn } return false } // RecursiveError represents a recursive error. type RecursiveError struct { *Var } func (e RecursiveError) Error() string { return "recursive variable " + e.Name } // ----------------------------------------------------------------------------- // Context represents the context of a matching process. type Context struct { Fset *token.FileSet FileEnd token.Pos toks []*types.Token Left int LastErr error } // NewContext creates a new matching context. func NewContext(fset *token.FileSet, fileEnd token.Pos, toks []*types.Token) *Context { return &Context{ Fset: fset, FileEnd: fileEnd, toks: toks, Left: len(toks), } } // SetLastError sets the last error. func (p *Context) SetLastError(left int, err error) { if left < p.Left { p.Left, p.LastErr = left, err } } // NewError creates a new error. func (p *Context) NewError(pos token.Pos, msg string) *Error { return &Error{p.Fset, pos, msg, false} } // NewErrorf creates a new error with a format string. func (p *Context) NewErrorf(pos token.Pos, format string, args ...any) error { return &Error{p.Fset, pos, fmt.Sprintf(format, args...), false} } // ----------------------------------------------------------------------------- // MatchToken represents a matching literal. type MatchToken struct { Tok token.Token Lit string } func (p *MatchToken) String() string { return p.Lit } func hasConflictToken(me token.Token, next []any) bool { for _, n := range next { switch n := n.(type) { case *MatchToken: if n.Tok == me { return true } case token.Token: if n == me { return true } default: panic("unreachable") } } return false } func hasConflictMatchToken(me *MatchToken, next []any) bool { for _, n := range next { switch n := n.(type) { case *MatchToken: if n.Tok == me.Tok && n.Lit == me.Lit { return true } case token.Token: default: panic("unreachable") } } return false } func hasConflictMe(me any, next []any) bool { switch me := me.(type) { case token.Token: return hasConflictToken(me, next) case *MatchToken: return hasConflictMatchToken(me, next) } panic("unreachable") } func hasConflict(me []any, next []any) bool { for _, m := range me { if hasConflictMe(m, next) { return true } } return false } func conflictWith(me []any, next [][]any, from int) int { for i, n := from, len(next); i < n; i++ { if hasConflict(me, next[i]) { return i } } return -1 } // ----------------------------------------------------------------------------- // Matcher // Matcher represents a matcher. type Matcher interface { Match(src []*types.Token, ctx *Context) (n int, result any, err error) First(in []any) (first []any, mayEmpty bool) // can be token.Token or *MatchToken } // ----------------------------------------------------------------------------- type gTrue struct{} func (p gTrue) Match(src []*types.Token, ctx *Context) (n int, result any, err error) { return 0, nil, nil } func (p gTrue) First(in []any) (first []any, mayEmpty bool) { return in, true } // True returns a matcher that always succeeds. func True() Matcher { return gTrue{} } // ----------------------------------------------------------------------------- type gWS struct{} func (p gWS) Match(src []*types.Token, ctx *Context) (n int, result any, err error) { if left := len(src); left > 0 { toks := ctx.toks if n := len(toks); n > left { last := n - left - 1 if toks[last].End() != src[0].Pos { return 0, nil, nil } } } return 0, nil, errNoWhitespace } func (p gWS) First(in []any) (first []any, mayEmpty bool) { return in, true } // WhiteSpace returns a matcher that matches whitespace. func WhiteSpace() Matcher { return gWS{} } // ----------------------------------------------------------------------------- type gString byte func (p gString) Match(src []*types.Token, ctx *Context) (n int, result any, err error) { if len(src) == 0 { return 0, nil, ctx.NewErrorf(ctx.FileEnd, "expect `%s`, but got EOF", stringType(p)) } t := src[0] if t.Tok != token.STRING || t.Lit[0] != byte(p) { return 0, nil, ctx.NewErrorf(t.Pos, "expect `%s`, but got `%v`", stringType(p), t) } return 1, t, nil } func (p gString) First(in []any) (first []any, mayEmpty bool) { return append(in, token.STRING), false } // String returns a matcher that matches a string literal. func String(quoteCh byte) Matcher { return gString(quoteCh) } func stringType(quoteCh gString) string { if quoteCh == '"' { return "QSTRING" } return "RAWSTRING" } // ----------------------------------------------------------------------------- type gToken struct { tok token.Token } func (p *gToken) Match(src []*types.Token, ctx *Context) (n int, result any, err error) { if len(src) == 0 { return 0, nil, ctx.NewErrorf(ctx.FileEnd, "expect `%s`, but got EOF", p.tok) } t := src[0] if t.Tok != p.tok { return 0, nil, ctx.NewErrorf(t.Pos, "expect `%s`, but got `%s`", p.tok, t.Tok) } return 1, t, nil } func (p *gToken) First(in []any) (first []any, mayEmpty bool) { return append(in, p.tok), false } // Token: ADD, SUB, IDENT, INT, FLOAT, CHAR, STRING, etc. func Token(tok token.Token) Matcher { return &gToken{tok} } // ----------------------------------------------------------------------------- type gLiteral MatchToken func (p *gLiteral) Match(src []*types.Token, ctx *Context) (n int, result any, err error) { if len(src) == 0 { return 0, nil, ctx.NewErrorf(ctx.FileEnd, "expect `%s`, but got EOF", p.Lit) } t := src[0] if t.Tok != p.Tok || t.Lit != p.Lit { return 0, nil, ctx.NewErrorf(t.Pos, "expect `%s`, but got `%v`", p.Lit, t) } return 1, t, nil } func (p *gLiteral) First(in []any) (first []any, mayEmpty bool) { return append(in, (*MatchToken)(p)), false } // Literal: "abc", 'a', 123, 1.23, etc. func Literal(tok token.Token, lit string) Matcher { return &gLiteral{tok, lit} } // ----------------------------------------------------------------------------- // Choices represents a choice matcher. type Choices struct { options []Matcher stops []bool } func (p *Choices) CheckConflicts(conflict func(firsts [][]any, i, at int)) { options := p.options n := len(options) firsts := make([][]any, n) for i, g := range options { firsts[i], _ = g.First(nil) } stops := make([]bool, n) for i, me := range firsts { at := conflictWith(me, firsts, i+1) if at >= 0 { conflict(firsts, i, at) } else { stops[i] = true } } p.stops = stops } func (p *Choices) Match(src []*types.Token, ctx *Context) (n int, result any, err error) { var nMax = -1 var errMax error var multiErr = true stops := p.stops // be set by CheckConflicts for i, g := range p.options { if n, result, err = g.Match(src, ctx); err == nil || (n > 0 && stops[i]) { return } if n >= nMax { if n == nMax { multiErr = true } else { nMax, errMax, multiErr = n, err, false } } } if multiErr { errMax = errMultiMismatch } return nMax, nil, errMax } func (p *Choices) First(in []any) (first []any, mayEmpty bool) { for _, g := range p.options { var me bool if in, me = g.First(in); me { mayEmpty = true } } first = in return } // Choice: R1 | R2 | ... | Rn // Should be used with CheckConflicts. func Choice(options ...Matcher) *Choices { return &Choices{options, nil} } // ----------------------------------------------------------------------------- type gSequence struct { items []Matcher } func (p *gSequence) Match(src []*types.Token, ctx *Context) (n int, result any, err error) { nitems := len(p.items) rets := make([]any, nitems) for i, g := range p.items { n1, ret1, err1 := g.Match(src[n:], ctx) if err1 != nil { if isDyn(err1) { err = err1 } else { return n + n1, nil, err1 } } rets[i] = ret1 n += n1 } result = rets return } func (p *gSequence) First(in []any) (first []any, mayEmpty bool) { for _, g := range p.items { if in, mayEmpty = g.First(in); !mayEmpty { break } } first = in return } // Sequence: R1 R2 ... Rn // Should length of items > 0 func Sequence(items ...Matcher) Matcher { return &gSequence{items} } // ----------------------------------------------------------------------------- type gRepeat0 struct { r Matcher } func (p *gRepeat0) Match(src []*types.Token, ctx *Context) (n int, result any, err error) { g := p.r rets := make([]any, 0, 2) for { n1, ret1, err1 := g.Match(src, ctx) if err1 != nil { if isDyn(err1) { err = err1 } else { ctx.SetLastError(len(src)-n1, err1) result = rets return } } rets = append(rets, ret1) n += n1 src = src[n1:] } } func (p *gRepeat0) First(in []any) (first []any, mayEmpty bool) { first, _ = p.r.First(in) mayEmpty = true return } // Repeat0: *R func Repeat0(r Matcher) Matcher { return &gRepeat0{r} } // ----------------------------------------------------------------------------- type gRepeat1 struct { r Matcher } func (p *gRepeat1) Match(src []*types.Token, ctx *Context) (n int, result any, err error) { g := p.r n, ret0, err := g.Match(src, ctx) if err != nil { return } rets := make([]any, 1, 2) rets[0] = ret0 for { n1, ret1, err1 := g.Match(src[n:], ctx) if err1 != nil { if isDyn(err1) { err = err1 } else { ctx.SetLastError(len(src)-n-n1, err1) result = rets return } } rets = append(rets, ret1) n += n1 } } func (p *gRepeat1) First(in []any) (first []any, mayEmpty bool) { return p.r.First(in) } // Repeat1: +R func Repeat1(r Matcher) Matcher { return &gRepeat1{r} } // ----------------------------------------------------------------------------- type gRepeat01 struct { r Matcher } func (p *gRepeat01) Match(src []*types.Token, ctx *Context) (n int, result any, err error) { n, result, err = p.r.Match(src, ctx) if err != nil { return 0, nil, nil } return } func (p *gRepeat01) First(in []any) (first []any, mayEmpty bool) { first, _ = p.r.First(in) mayEmpty = true return } // Repeat01: ?R func Repeat01(r Matcher) Matcher { return &gRepeat01{r} } // ----------------------------------------------------------------------------- // List: R1 % R2 is equivalent to R1 *(R2 R1) func List(a, b Matcher) Matcher { return Sequence(a, Repeat0(Sequence(b, a))) } // ----------------------------------------------------------------------------- type gAdjoin struct { a, b Matcher } func (p *gAdjoin) Match(src []*types.Token, ctx *Context) (n int, result any, err error) { n, ret0, err := p.a.Match(src, ctx) if err != nil { return } if n == 0 { err = errAdjoinEmpty return } n1, ret1, err := p.b.Match(src[n:], ctx) if err != nil && !isDyn(err) { return } if n1 == 0 { err = errAdjoinEmpty return } if src[n-1].End() != src[n].Pos { err = ctx.NewError(src[n].Pos, "not adjoin") return } n += n1 result = []any{ret0, ret1} return } func (p *gAdjoin) First(in []any) (first []any, mayEmpty bool) { first, _ = p.a.First(in) return } // Adjoin: R1 ++ R2 func Adjoin(a, b Matcher) Matcher { return &gAdjoin{a, b} } // ----------------------------------------------------------------------------- type RetProc = func(any) any type ListRetProc = func([]any) any type Var struct { Elem Matcher Name string Pos token.Pos RetProc any } func (p *Var) Match(src []*types.Token, ctx *Context) (n int, result any, err error) { g := p.Elem if g == nil { return 0, nil, ctx.NewErrorf(p.Pos, "variable `%s` not assigned", p.Name) } if enableMatchVar && len(src) > 0 { log.Println("==> Match", p.Name, src[0]) } n, result, err = g.Match(src, ctx) if err == nil { if retProc := p.RetProc; retProc != nil { defer func() { if e := recover(); e != nil { switch e := e.(type) { case *Error: if e.Fset == nil { e.Fset = ctx.Fset } err = e case string: err = &Error{ Fset: ctx.Fset, Pos: src[0].Pos, Msg: e, Dyn: true, } default: err = e.(error) } } }() if listRetPorc, ok := retProc.(ListRetProc); ok { result = listRetPorc(result.([]any)) } else { result = retProc.(RetProc)(result) } } } else if err == errMultiMismatch { var posErr token.Pos var tokErr any if len(src) > 0 { posErr, tokErr = src[0].Pos, src[0] } else { posErr, tokErr = ctx.FileEnd, "EOF" } err = ctx.NewErrorf(posErr, "expect `%s`, but got `%s`", p.Name, tokErr) } return } func (p *Var) First(in []any) (first []any, mayEmpty bool) { elem := p.Elem if elem != nil { p.Elem = nil // to stop recursion first, mayEmpty = elem.First(in) p.Elem = elem } else { panic(RecursiveError{p}) } return } // Assign assigns a value to this variable. func (p *Var) Assign(elem Matcher) error { if p.Elem != nil { return ErrVarAssigned } p.Elem = elem return nil } // NewVar creates a new Var instance. func NewVar(pos token.Pos, name string) *Var { return &Var{Pos: pos, Name: name} } // ----------------------------------------------------------------------------- ================================================ FILE: tpl/parser/_testdata/adjoin/in.xgo ================================================ doc = IDENT ++ RAWSTRING | IDENT SPACE "{" "}" ================================================ FILE: tpl/parser/_testdata/adjoin/out.expect ================================================ ast.Rule: Name: ast.Ident: Name: doc Expr: ast.Choice: Options: ast.BinaryExpr: X: ast.Ident: Name: IDENT Op: ++ Y: ast.Ident: Name: RAWSTRING ast.Sequence: Items: ast.Ident: Name: IDENT ast.Ident: Name: SPACE ast.BasicLit: Kind: STRING Value: "{" ast.BasicLit: Kind: STRING Value: "}" ================================================ FILE: tpl/parser/_testdata/pseudo/in.xgo ================================================ file = stmts => { return &ast.File{ Stmts: this.([]ast.Stmt), } } stmts = *(stmt ";") => { return [n.(ast.Stmt) for n in this] } stmt = varStmt | constStmt | outputStmt | inputStmt | ifStmt | whileStmt | untilStmt | assignStmt varStmt = "DECLARE" namelist ":" typeExpr => { return &ast.DeclareStmt{ Declare: this[0].(*tpl.Token).Pos, Names: this[1].([]*ast.Ident), Type: this[3].(ast.Expr), } } constStmt = "CONSTANT" IDENT "<-" expr => { return &ast.ConstantStmt{ Constant: this[0].(*tpl.Token).Pos, Name: this[1].(*ast.Ident), Value: this[3].(ast.Expr), } } assignStmt = IDENT "<-" expr => { return &ast.AssignStmt{ Name: this[0].(*ast.Ident), Value: this[2].(ast.Expr), } } outputStmt = "OUTPUT" exprlist => { return &ast.OutputStmt{ Output: this[0].(*tpl.Token).Pos, Values: this[1].([]ast.Expr), } } inputStmt = "INPUT" namelist => { return &ast.InputStmt{ Input: this[0].(*tpl.Token).Pos, Names: this[1].([]*ast.Ident), } } ifStmt = "IF" expr "THEN" ";" stmts ?("ELSE" ";" stmts) "ENDIF" => { var elseBody []ast.Stmt if v := this[5]; v != nil { elseStmt := v.([]any) elseBody = elseStmt[2].([]ast.Stmt) } return &ast.IfStmt{ If: this[0].(*tpl.Token).Pos, Cond: this[1].(ast.Expr), Body: this[4].([]ast.Stmt), Else: elseBody, EndIf: this[6].(*tpl.Token).Pos, } } whileStmt = "WHILE" expr "DO" ";" stmts "ENDWHILE" => { return &ast.WhileStmt{ While: this[0].(*tpl.Token).Pos, Cond: this[1].(ast.Expr), Body: this[4].([]ast.Stmt), EndWhile: this[5].(*tpl.Token).Pos, } } untilStmt = "REPEAT" ";" stmts "UNTIL" expr => { return &ast.UntilStmt{ Repeat: this[0].(*tpl.Token).Pos, Body: this[2].([]ast.Stmt), Until: this[3].(*tpl.Token).Pos, Cond: this[4].(ast.Expr), } } typeExpr = "INTEGER" | "REAL" | "STRING" | "BOOLEAN" => { return tpl.ident(this) } expr = binaryExpr2 % ("<" | "<=" | ">" | ">=" | "=" | "<>") => { return tpl.binaryExpr(this) } binaryExpr2 = binaryExpr1 % ("+" | "-") => { return tpl.binaryExpr(this) } binaryExpr1 = operand % ("*" | "/") => { return tpl.binaryExpr(this) } operand = basicLit | ident | parenExpr | unaryExpr unaryExpr = "-" operand => { return tpl.unaryExpr(this) } basicLit = INT | FLOAT | STRING => { return tpl.basicLit(this) } ident = IDENT => { return tpl.ident(this) } parenExpr = "(" expr ")" => { return this[1] } exprlist = expr % "," => { return [v.(ast.Expr) for v in tpl.list(this)] } namelist = IDENT % "," => { return [tpl.ident(v) for v in tpl.list(this)] } ================================================ FILE: tpl/parser/_testdata/pseudo/out.expect ================================================ ast.Rule: Name: ast.Ident: Name: file Expr: ast.Ident: Name: stmts ast.Rule: Name: ast.Ident: Name: stmts Expr: ast.UnaryExpr: Op: * X: ast.Sequence: Items: ast.Ident: Name: stmt ast.BasicLit: Kind: STRING Value: ";" ast.Rule: Name: ast.Ident: Name: stmt Expr: ast.Choice: Options: ast.Ident: Name: varStmt ast.Ident: Name: constStmt ast.Ident: Name: outputStmt ast.Ident: Name: inputStmt ast.Ident: Name: ifStmt ast.Ident: Name: whileStmt ast.Ident: Name: untilStmt ast.Ident: Name: assignStmt ast.Rule: Name: ast.Ident: Name: varStmt Expr: ast.Sequence: Items: ast.BasicLit: Kind: STRING Value: "DECLARE" ast.Ident: Name: namelist ast.BasicLit: Kind: STRING Value: ":" ast.Ident: Name: typeExpr ast.Rule: Name: ast.Ident: Name: constStmt Expr: ast.Sequence: Items: ast.BasicLit: Kind: STRING Value: "CONSTANT" ast.Ident: Name: IDENT ast.BasicLit: Kind: STRING Value: "<-" ast.Ident: Name: expr ast.Rule: Name: ast.Ident: Name: assignStmt Expr: ast.Sequence: Items: ast.Ident: Name: IDENT ast.BasicLit: Kind: STRING Value: "<-" ast.Ident: Name: expr ast.Rule: Name: ast.Ident: Name: outputStmt Expr: ast.Sequence: Items: ast.BasicLit: Kind: STRING Value: "OUTPUT" ast.Ident: Name: exprlist ast.Rule: Name: ast.Ident: Name: inputStmt Expr: ast.Sequence: Items: ast.BasicLit: Kind: STRING Value: "INPUT" ast.Ident: Name: namelist ast.Rule: Name: ast.Ident: Name: ifStmt Expr: ast.Sequence: Items: ast.BasicLit: Kind: STRING Value: "IF" ast.Ident: Name: expr ast.BasicLit: Kind: STRING Value: "THEN" ast.BasicLit: Kind: STRING Value: ";" ast.Ident: Name: stmts ast.UnaryExpr: Op: ? X: ast.Sequence: Items: ast.BasicLit: Kind: STRING Value: "ELSE" ast.BasicLit: Kind: STRING Value: ";" ast.Ident: Name: stmts ast.BasicLit: Kind: STRING Value: "ENDIF" ast.Rule: Name: ast.Ident: Name: whileStmt Expr: ast.Sequence: Items: ast.BasicLit: Kind: STRING Value: "WHILE" ast.Ident: Name: expr ast.BasicLit: Kind: STRING Value: "DO" ast.BasicLit: Kind: STRING Value: ";" ast.Ident: Name: stmts ast.BasicLit: Kind: STRING Value: "ENDWHILE" ast.Rule: Name: ast.Ident: Name: untilStmt Expr: ast.Sequence: Items: ast.BasicLit: Kind: STRING Value: "REPEAT" ast.BasicLit: Kind: STRING Value: ";" ast.Ident: Name: stmts ast.BasicLit: Kind: STRING Value: "UNTIL" ast.Ident: Name: expr ast.Rule: Name: ast.Ident: Name: typeExpr Expr: ast.Choice: Options: ast.BasicLit: Kind: STRING Value: "INTEGER" ast.BasicLit: Kind: STRING Value: "REAL" ast.BasicLit: Kind: STRING Value: "STRING" ast.BasicLit: Kind: STRING Value: "BOOLEAN" ast.Rule: Name: ast.Ident: Name: expr Expr: ast.BinaryExpr: X: ast.Ident: Name: binaryExpr2 Op: % Y: ast.Choice: Options: ast.BasicLit: Kind: STRING Value: "<" ast.BasicLit: Kind: STRING Value: "<=" ast.BasicLit: Kind: STRING Value: ">" ast.BasicLit: Kind: STRING Value: ">=" ast.BasicLit: Kind: STRING Value: "=" ast.BasicLit: Kind: STRING Value: "<>" ast.Rule: Name: ast.Ident: Name: binaryExpr2 Expr: ast.BinaryExpr: X: ast.Ident: Name: binaryExpr1 Op: % Y: ast.Choice: Options: ast.BasicLit: Kind: STRING Value: "+" ast.BasicLit: Kind: STRING Value: "-" ast.Rule: Name: ast.Ident: Name: binaryExpr1 Expr: ast.BinaryExpr: X: ast.Ident: Name: operand Op: % Y: ast.Choice: Options: ast.BasicLit: Kind: STRING Value: "*" ast.BasicLit: Kind: STRING Value: "/" ast.Rule: Name: ast.Ident: Name: operand Expr: ast.Choice: Options: ast.Ident: Name: basicLit ast.Ident: Name: ident ast.Ident: Name: parenExpr ast.Ident: Name: unaryExpr ast.Rule: Name: ast.Ident: Name: unaryExpr Expr: ast.Sequence: Items: ast.BasicLit: Kind: STRING Value: "-" ast.Ident: Name: operand ast.Rule: Name: ast.Ident: Name: basicLit Expr: ast.Choice: Options: ast.Ident: Name: INT ast.Ident: Name: FLOAT ast.Ident: Name: STRING ast.Rule: Name: ast.Ident: Name: ident Expr: ast.Ident: Name: IDENT ast.Rule: Name: ast.Ident: Name: parenExpr Expr: ast.Sequence: Items: ast.BasicLit: Kind: STRING Value: "(" ast.Ident: Name: expr ast.BasicLit: Kind: STRING Value: ")" ast.Rule: Name: ast.Ident: Name: exprlist Expr: ast.BinaryExpr: X: ast.Ident: Name: expr Op: % Y: ast.BasicLit: Kind: STRING Value: "," ast.Rule: Name: ast.Ident: Name: namelist Expr: ast.BinaryExpr: X: ast.Ident: Name: IDENT Op: % Y: ast.BasicLit: Kind: STRING Value: "," ================================================ FILE: tpl/parser/_testdata/simple1/in.xgo ================================================ expr = termExpr | expr ("+" | "-") expr termExpr = unaryExpr | termExpr ("*" | "/") termExpr unaryExpr = operand | "-" unaryExpr operand = INT | FLOAT | "(" expr ")" ================================================ FILE: tpl/parser/_testdata/simple1/out.expect ================================================ ast.Rule: Name: ast.Ident: Name: expr Expr: ast.Choice: Options: ast.Ident: Name: termExpr ast.Sequence: Items: ast.Ident: Name: expr ast.Choice: Options: ast.BasicLit: Kind: STRING Value: "+" ast.BasicLit: Kind: STRING Value: "-" ast.Ident: Name: expr ast.Rule: Name: ast.Ident: Name: termExpr Expr: ast.Choice: Options: ast.Ident: Name: unaryExpr ast.Sequence: Items: ast.Ident: Name: termExpr ast.Choice: Options: ast.BasicLit: Kind: STRING Value: "*" ast.BasicLit: Kind: STRING Value: "/" ast.Ident: Name: termExpr ast.Rule: Name: ast.Ident: Name: unaryExpr Expr: ast.Choice: Options: ast.Ident: Name: operand ast.Sequence: Items: ast.BasicLit: Kind: STRING Value: "-" ast.Ident: Name: unaryExpr ast.Rule: Name: ast.Ident: Name: operand Expr: ast.Choice: Options: ast.Ident: Name: INT ast.Ident: Name: FLOAT ast.Sequence: Items: ast.BasicLit: Kind: STRING Value: "(" ast.Ident: Name: expr ast.BasicLit: Kind: STRING Value: ")" ================================================ FILE: tpl/parser/_testdata/simple2/in.xgo ================================================ expr = termExpr % ("+" | "-") termExpr = unaryExpr % ("*" | "/") unaryExpr = operand | "-" unaryExpr operand = INT | FLOAT | "(" expr ")" ================================================ FILE: tpl/parser/_testdata/simple2/out.expect ================================================ ast.Rule: Name: ast.Ident: Name: expr Expr: ast.BinaryExpr: X: ast.Ident: Name: termExpr Op: % Y: ast.Choice: Options: ast.BasicLit: Kind: STRING Value: "+" ast.BasicLit: Kind: STRING Value: "-" ast.Rule: Name: ast.Ident: Name: termExpr Expr: ast.BinaryExpr: X: ast.Ident: Name: unaryExpr Op: % Y: ast.Choice: Options: ast.BasicLit: Kind: STRING Value: "*" ast.BasicLit: Kind: STRING Value: "/" ast.Rule: Name: ast.Ident: Name: unaryExpr Expr: ast.Choice: Options: ast.Ident: Name: operand ast.Sequence: Items: ast.BasicLit: Kind: STRING Value: "-" ast.Ident: Name: unaryExpr ast.Rule: Name: ast.Ident: Name: operand Expr: ast.Choice: Options: ast.Ident: Name: INT ast.Ident: Name: FLOAT ast.Sequence: Items: ast.BasicLit: Kind: STRING Value: "(" ast.Ident: Name: expr ast.BasicLit: Kind: STRING Value: ")" ================================================ FILE: tpl/parser/parser.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package parser import ( "github.com/goplus/xgo/tpl/ast" "github.com/goplus/xgo/tpl/scanner" "github.com/goplus/xgo/tpl/token" "github.com/qiniu/x/stream" ) // ----------------------------------------------------------------------------- // RetProcParser parses a RetProc. type RetProcParser = func(file *token.File, src []byte, offset int) (ast.Node, scanner.ErrorList) // Config configures the behavior of the parser. type Config struct { ParseRetProc RetProcParser } // ParseFile parses a file and returns the AST. func ParseFile(fset *token.FileSet, filename string, src any, conf *Config) (f *ast.File, err error) { b, err := stream.ReadSourceLocal(filename, src) if err != nil { return nil, err } file := fset.AddFile(filename, -1, len(b)) f, errs := ParseEx(file, b, 0, conf) switch errs.Len() { case 0: case 1: err = errs[0] default: errs.Sort() err = errs } return } // ParseEx parses src[offset:] and returns the AST. func ParseEx(file *token.File, src []byte, offset int, conf *Config) (f *ast.File, errs scanner.ErrorList) { var p parser p.init(file, src, offset) if conf != nil { p.parseRetProc = conf.ParseRetProc } return p.parseFile(), p.errors } // ----------------------------------------------------------------------------- // parser represents a parser. type parser struct { scanner scanner.Scanner file *token.File // Current token pos token.Pos tok token.Token lit string // Callback to parse RetProc. parseRetProc RetProcParser // Error handling errors scanner.ErrorList } func (p *parser) init(file *token.File, src []byte, offset int) { p.file = file eh := func(pos token.Position, msg string) { p.errors.Add(pos, msg) } p.scanner.InitEx(p.file, src, offset, eh, 0) p.next() // initialize first token } // next advances to the next token. func (p *parser) next() { t := p.scanner.Scan() p.pos, p.tok, p.lit = t.Pos, t.Tok, t.Lit } func (p *parser) errorExpected(pos token.Pos, msg string) { msg = "expected " + msg if pos == p.pos { // the error happened at the current position; // make the error message more specific switch { case p.tok == token.SEMICOLON && p.lit == "\n": msg += ", found newline" case len(p.lit) > 0: // print 123 rather than 'INT', etc. msg += ", found " + p.lit default: msg += ", found '" + p.tok.String() + "'" } } p.error(pos, msg) } // expect consumes the current token if it matches the expected token. // If not, it adds an error. func (p *parser) expect(tok token.Token) token.Pos { pos := p.pos if p.tok != tok { p.errorExpected(pos, "'"+tok.String()+"'") } p.next() // make progress return pos } func (p *parser) error(pos token.Pos, msg string) { epos := p.file.Position(pos) p.errors.Add(epos, msg) } // parseFile parses a file and returns the AST. func (p *parser) parseFile() *ast.File { file := &ast.File{ FileStart: p.pos, } for p.tok != token.EOF { rule := p.parseRule() if rule == nil { break } file.Decls = append(file.Decls, rule) } return file } func (p *parser) parseIdent() *ast.Ident { pos := p.pos name := "_" if p.tok == token.IDENT { name = p.lit p.next() } else { p.errorExpected(p.pos, "'IDENT'") } return &ast.Ident{NamePos: pos, Name: name} } // parseRule parses a rule: // // IDENT '=' expr ';' // IDENT '=' expr => { ... } ';' func (p *parser) parseRule() *ast.Rule { if p.tok != token.IDENT { p.errorExpected(p.pos, "'IDENT'") return nil } name := p.parseIdent() tokPos := p.expect(token.ASSIGN) expr := p.parseExpr() if expr == nil { return nil } var retProc ast.Node if p.tok == token.DRARROW { // => { ... } if off, end, ok := p.lambdaExpr(); ok { if p.parseRetProc != nil { file := p.file base := file.Base() src := p.scanner.CodeTo(int(end) - base) expr, err := p.parseRetProc(file, src, int(off)-base) if err == nil { retProc = expr } else { p.errors = append(p.errors, err...) } } } } p.expect(token.SEMICOLON) return &ast.Rule{ Name: name, TokPos: tokPos, Expr: expr, RetProc: retProc, } } func (p *parser) lambdaExpr() (start, end token.Pos, ok bool) { start = p.pos // => { p.next() p.expect(token.LBRACE) level := 1 for { switch p.tok { case token.RBRACE: level-- if level == 0 { // } p.next() end, ok = p.pos, true return } case token.LBRACE: level++ case token.EOF: return } p.next() } } // parseExpr: termList % '|' func (p *parser) parseExpr() ast.Expr { termList := p.parseTermList() for p.tok != token.OR { return termList } options := make([]ast.Expr, 0, 4) options = append(options, termList) for p.tok == token.OR { p.next() termList = p.parseTermList() options = append(options, termList) } return &ast.Choice{Options: options} } // parseTermList: +term func (p *parser) parseTermList() ast.Expr { terms := make([]ast.Expr, 0, 1) for { term, ok := p.parseTerm() if !ok { break } terms = append(terms, term) } switch n := len(terms); n { case 1: return terms[0] case 0: p.error(p.pos, "expected factor") fallthrough // TODO(xsw): BadExpr default: return &ast.Sequence{Items: terms} } } // parseTerm: term2 % '%' func (p *parser) parseTerm() (ast.Expr, bool) { x, ok := p.parseTerm2() if !ok { return nil, false } for p.tok == token.REM { opPos := p.pos p.next() y, ok := p.parseTerm2() if !ok { p.error(p.pos, "expected factor") return x, false } x = &ast.BinaryExpr{ X: x, OpPos: opPos, Op: token.REM, Y: y, } } return x, true } // parseTerm2: factor % "++" func (p *parser) parseTerm2() (ast.Expr, bool) { x, ok := p.parseFactor() if !ok { return nil, false } for p.tok == token.INC { opPos := p.pos p.next() y, ok := p.parseFactor() if !ok { p.error(p.pos, "expected factor") return x, false } x = &ast.BinaryExpr{ X: x, OpPos: opPos, Op: token.INC, Y: y, } } return x, true } // parseFactor: IDENT | CHAR | STRING | ('*' | '+' | '?') factor | '(' expr ')' func (p *parser) parseFactor() (ast.Expr, bool) { switch tok := p.tok; tok { case token.IDENT: ident := &ast.Ident{ NamePos: p.pos, Name: p.lit, } p.next() return ident, true case token.CHAR, token.STRING: lit := &ast.BasicLit{ ValuePos: p.pos, Kind: tok, Value: p.lit, } p.next() return lit, true case token.MUL, token.ADD, token.QUESTION: opPos := p.pos p.next() factor, ok := p.parseFactor() if !ok { p.error(p.pos, "expected factor") } ret := &ast.UnaryExpr{ OpPos: opPos, Op: tok, X: factor, } return ret, true case token.LPAREN: p.next() expr := p.parseExpr() p.expect(token.RPAREN) return expr, true default: return nil, false } } // ----------------------------------------------------------------------------- ================================================ FILE: tpl/parser/parser_test.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package parser_test import ( "log" "os" "path" "reflect" "strings" "testing" gopp "github.com/goplus/xgo/parser" "github.com/goplus/xgo/tpl/ast" "github.com/goplus/xgo/tpl/parser" "github.com/goplus/xgo/tpl/parser/parsertest" "github.com/goplus/xgo/tpl/scanner" "github.com/goplus/xgo/tpl/token" ) func testFrom(t *testing.T, pkgDir, sel string) { if sel != "" && !strings.Contains(pkgDir, sel) { return } t.Helper() log.Println("Parsing", pkgDir) fset := token.NewFileSet() f, err := parser.ParseFile(fset, pkgDir+"/in.xgo", nil, &parser.Config{ ParseRetProc: func(file *token.File, src []byte, offset int) (ast.Node, scanner.ErrorList) { return gopp.ParseExprEx(file, src, offset, 0) }, }) if err != nil { if errs, ok := err.(scanner.ErrorList); ok { for _, e := range errs { t.Log(e) } } t.Fatal("ParseFile failed:", err, reflect.TypeOf(err)) } b, _ := os.ReadFile(pkgDir + "/out.expect") parsertest.Expect(t, pkgDir+"/result.txt", f, b) } func testFromDir(t *testing.T, sel, relDir string) { dir, err := os.Getwd() if err != nil { t.Fatal("Getwd failed:", err) } dir = path.Join(dir, relDir) fis, err := os.ReadDir(dir) if err != nil { t.Fatal("ReadDir failed:", err) } for _, fi := range fis { name := fi.Name() if strings.HasPrefix(name, "_") { continue } t.Run(name, func(t *testing.T) { testFrom(t, dir+"/"+name, sel) }) } } func TestFromTestdata(t *testing.T) { testFromDir(t, "", "./_testdata") } ================================================ FILE: tpl/parser/parsertest/parsertest.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package parsertest import ( "bytes" "fmt" "io" "log" "reflect" "testing" "github.com/goplus/xgo/tpl/ast" "github.com/goplus/xgo/tpl/token" "github.com/qiniu/x/test" ) // ----------------------------------------------------------------------------- var ( tyNode = reflect.TypeOf((*ast.Node)(nil)).Elem() tyString = reflect.TypeOf("") tyToken = reflect.TypeOf(token.Token(0)) ) // FprintNode prints a tpl AST node. func FprintNode(w io.Writer, lead string, v any, prefix, indent string) { val := reflect.ValueOf(v) switch val.Kind() { case reflect.Slice: n := val.Len() if n > 0 && lead != "" { io.WriteString(w, lead) } for i := 0; i < n; i++ { FprintNode(w, "", val.Index(i).Interface(), prefix, indent) } case reflect.Ptr: t := val.Type() if val.IsNil() { return } if t.Implements(tyNode) { if lead != "" { io.WriteString(w, lead) } elem, tyElem := val.Elem(), t.Elem() fmt.Fprintf(w, "%s%v:\n", prefix, tyElem) n := elem.NumField() prefix += indent for i := 0; i < n; i++ { sf := tyElem.Field(i) if sf.Name == "RetProc" { // skip RetProc field, see xgo/tpl/ast.Rule continue } sfv := elem.Field(i).Interface() switch sf.Type { case tyString, tyToken: fmt.Fprintf(w, "%s%v: %v\n", prefix, sf.Name, sfv) default: FprintNode(w, fmt.Sprintf("%s%v:\n", prefix, sf.Name), sfv, prefix+indent, indent) } } } else { log.Panicln("FprintNode unexpected type:", t) } case reflect.Int, reflect.Bool, reflect.Invalid: // skip default: log.Panicln("FprintNode unexpected kind:", val.Kind(), "type:", val.Type()) } } // Fprint prints a tpl ast.File node. func Fprint(w io.Writer, f *ast.File) { FprintNode(w, "", f.Decls, "", " ") } // Expect asserts a tpl AST equals output or not. func Expect(t *testing.T, outfile string, f *ast.File, expected []byte) { b := bytes.NewBuffer(nil) Fprint(b, f) if test.Diff(t, outfile, b.Bytes(), []byte(expected)) { t.Fatal("tpl.Parser: unexpect result") } } // ----------------------------------------------------------------------------- ================================================ FILE: tpl/scanner/_testdata/cstr/go.expect ================================================ 1 IDENT c 2 . 3 IDENT printf 10 IDENT c 11 STRING "Hello" 18 ; 19 IDENT std 22 . 23 IDENT print 29 IDENT py 31 STRING "Hello" 38 ; ================================================ FILE: tpl/scanner/_testdata/cstr/gop.expect ================================================ 1 IDENT c 2 . 3 IDENT printf 10 CSTRING "Hello" 18 ; 19 IDENT std 22 . 23 IDENT print 29 PYSTRING "Hello" 38 ; ================================================ FILE: tpl/scanner/_testdata/cstr/in.xgo ================================================ c.printf c"Hello" std.print py"Hello" ================================================ FILE: tpl/scanner/_testdata/cstr/tpl.expect ================================================ 1 IDENT c 2 . 3 IDENT printf 10 IDENT c 11 STRING "Hello" 18 ; 19 IDENT std 22 . 23 IDENT print 29 IDENT py 31 STRING "Hello" 38 ; ================================================ FILE: tpl/scanner/_testdata/num/go.expect ================================================ 1 IDENT echo 6 INT 1_002 11 ; 12 IDENT echo 17 INT 2 18 + 19 IMAG 3i 21 ; 22 IDENT echo 27 FLOAT 2. 29 IDENT string 35 ; ================================================ FILE: tpl/scanner/_testdata/num/gop.expect ================================================ 1 IDENT echo 6 INT 1_002 11 ; 12 IDENT echo 17 INT 2 18 + 19 IMAG 3i 21 ; 22 IDENT echo 27 FLOAT 2. 29 UNIT string 35 ; ================================================ FILE: tpl/scanner/_testdata/num/in.xgo ================================================ echo 1_002 echo 2+3i echo 2.string ================================================ FILE: tpl/scanner/_testdata/num/tpl.expect ================================================ 1 IDENT echo 6 INT 1_002 11 ; 12 IDENT echo 17 INT 2 18 + 19 IMAG 3i 21 ; 22 IDENT echo 27 FLOAT 2. 29 UNIT string 35 ; ================================================ FILE: tpl/scanner/_testdata/pow/go.expect ================================================ 1 IDENT echo 6 INT 2 7 * 8 * 9 INT 3 10 ; 11 IDENT echo 16 STRING `abc` 21 [ 22 INT 0 23 ] 24 ; ================================================ FILE: tpl/scanner/_testdata/pow/gop.expect ================================================ 1 IDENT echo 6 INT 2 7 * 8 * 9 INT 3 10 ; 11 IDENT echo 16 STRING `abc` 21 [ 22 INT 0 23 ] 24 ; ================================================ FILE: tpl/scanner/_testdata/pow/in.xgo ================================================ echo 2**3 echo `abc`[0] ================================================ FILE: tpl/scanner/_testdata/pow/tpl.expect ================================================ 1 IDENT echo 6 INT 2 7 * 8 * 9 INT 3 10 ; 11 IDENT echo 16 STRING `abc` 21 [ 22 INT 0 23 ] 24 ; ================================================ FILE: tpl/scanner/_testdata/rat/go.expect ================================================ 1 IDENT echo 6 INT 1 7 / 8 INT 3 9 IDENT r 10 ; 11 IDENT echo 16 FLOAT 1.23 20 IDENT r 21 ; ================================================ FILE: tpl/scanner/_testdata/rat/gop.expect ================================================ 1 IDENT echo 6 INT 1 7 / 8 RAT 3r 10 ; 11 IDENT echo 16 RAT 1.23r 21 ; ================================================ FILE: tpl/scanner/_testdata/rat/in.xgo ================================================ echo 1/3r echo 1.23r ================================================ FILE: tpl/scanner/_testdata/rat/tpl.expect ================================================ 1 IDENT echo 6 INT 1 7 / 8 RAT 3r 10 ; 11 IDENT echo 16 RAT 1.23r 21 ; ================================================ FILE: tpl/scanner/_testdata/unit/go.expect ================================================ 1 import import 8 STRING "time" 14 ; 16 IDENT x 18 := 21 STRING "Let's wait for a second" 46 ; 47 IDENT echo 52 IDENT x 53 ; 55 IDENT wait 60 INT 0x10 64 IDENT s 65 ; 66 IDENT wait 71 FLOAT .1 73 IDENT s 74 ; 75 IDENT wait 80 FLOAT 1. 82 IDENT s 83 ; 84 IDENT wait 89 FLOAT 1.e7 93 IDENT s 94 ; 95 IDENT wait 100 FLOAT 1.2 103 IDENT s 104 ; 106 IDENT time 110 . 111 IDENT sleep 117 IMAG 100i 121 IDENT m 122 ; 124 IDENT spend 130 FLOAT 10.0 134 IDENT rmb 137 ; 138 IDENT spend 144 FLOAT 10. 147 IDENT rmb 150 ; 152 IDENT echo 157 STRING "Hello, world" 171 ... ================================================ FILE: tpl/scanner/_testdata/unit/gop.expect ================================================ 1 import import 8 STRING "time" 14 ; 16 IDENT x 18 := 21 STRING "Let's wait for a second" 46 ; 47 IDENT echo 52 IDENT x 53 ; 55 IDENT wait 60 INT 0x10 64 UNIT s 65 ; 66 IDENT wait 71 FLOAT .1 73 UNIT s 74 ; 75 IDENT wait 80 FLOAT 1. 82 UNIT s 83 ; 84 IDENT wait 89 FLOAT 1.e7 93 UNIT s 94 ; 95 IDENT wait 100 FLOAT 1.2 103 UNIT s 104 ; 106 IDENT time 110 . 111 IDENT sleep 117 INT 100 120 UNIT im 122 ; 124 IDENT spend 130 FLOAT 10.0 134 UNIT rmb 137 ; 138 IDENT spend 144 FLOAT 10. 147 UNIT rmb 150 ; 152 IDENT echo 157 STRING "Hello, world" 171 ... 174 ; ================================================ FILE: tpl/scanner/_testdata/unit/in.xgo ================================================ import "time" x := "Let's wait for a second" echo x wait 0x10s wait .1s wait 1.s wait 1.e7s wait 1.2s time.sleep 100im spend 10.0rmb spend 10.rmb echo "Hello, world"... ================================================ FILE: tpl/scanner/_testdata/unit/tpl.expect ================================================ 1 IDENT import 8 STRING "time" 14 ; 16 IDENT x 18 := 21 STRING "Let's wait for a second" 46 ; 47 IDENT echo 52 IDENT x 53 ; 55 IDENT wait 60 INT 0x10 64 UNIT s 65 ; 66 IDENT wait 71 FLOAT .1 73 UNIT s 74 ; 75 IDENT wait 80 FLOAT 1. 82 UNIT s 83 ; 84 IDENT wait 89 FLOAT 1.e7 93 UNIT s 94 ; 95 IDENT wait 100 FLOAT 1.2 103 UNIT s 104 ; 106 IDENT time 110 . 111 IDENT sleep 117 INT 100 120 UNIT im 122 ; 124 IDENT spend 130 FLOAT 10.0 134 UNIT rmb 137 ; 138 IDENT spend 144 FLOAT 10. 147 UNIT rmb 150 ; 152 IDENT echo 157 STRING "Hello, world" 171 ... 174 ; ================================================ FILE: tpl/scanner/error.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package scanner import ( "go/scanner" "io" ) // ----------------------------------------------------------------------------- // Error is an alias of go/scanner.Error type Error = scanner.Error // ErrorList is an alias of go/scanner.ErrorList type ErrorList = scanner.ErrorList // PrintError is a utility function that prints a list of errors to w, // one error per line, if the err parameter is an ErrorList. Otherwise // it prints the err string. func PrintError(w io.Writer, err error) { scanner.PrintError(w, err) } // ----------------------------------------------------------------------------- ================================================ FILE: tpl/scanner/scandir_test.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package scanner_test import ( "bytes" "io" "log" "os" "path" "strings" "testing" "github.com/goplus/xgo/tpl/scanner/scannertest" "github.com/qiniu/x/test" ) func testScan( t *testing.T, pkgDir string, in []byte, expFile string, scan func(w io.Writer, in []byte)) { expect, _ := os.ReadFile(pkgDir + "/" + expFile) var b bytes.Buffer scan(&b, in) out := b.Bytes() if test.Diff(t, pkgDir+"/result.txt", out, expect) { t.Fatal(expFile, ": unexpect result") } } func testFrom(t *testing.T, pkgDir, sel string) { if sel != "" && !strings.Contains(pkgDir, sel) { return } t.Helper() log.Println("Scanning", pkgDir) in, err := os.ReadFile(pkgDir + "/in.xgo") if err != nil { t.Fatal("Scanning", pkgDir, "-", err) } testScan(t, pkgDir, in, "tpl.expect", scannertest.Scan) testScan(t, pkgDir, in, "go.expect", scannertest.GoScan) testScan(t, pkgDir, in, "gop.expect", scannertest.GopScan) } func testFromDir(t *testing.T, sel, relDir string) { dir, err := os.Getwd() if err != nil { t.Fatal("Getwd failed:", err) } dir = path.Join(dir, relDir) fis, err := os.ReadDir(dir) if err != nil { t.Fatal("ReadDir failed:", err) } for _, fi := range fis { name := fi.Name() if strings.HasPrefix(name, "_") { continue } t.Run(name, func(t *testing.T) { pkgDir := dir + "/" + name testFrom(t, pkgDir, sel) }) } } func TestFromTestdata(t *testing.T) { testFromDir(t, "", "./_testdata") } ================================================ FILE: tpl/scanner/scanner.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package scanner import ( "bytes" "fmt" "path/filepath" "strconv" "unicode" "unicode/utf8" "github.com/goplus/xgo/tpl/token" "github.com/goplus/xgo/tpl/types" ) // An ErrorHandler may be provided to Scanner.Init. If a syntax error is // encountered and a handler was installed, the handler is called with a // position and an error message. The position points to the beginning of // the offending token. type ErrorHandler func(pos token.Position, msg string) // A Scanner holds the scanner's internal state while processing // a given text. It can be allocated as part of another data // structure but must be initialized via Init before use. type Scanner struct { // immutable state file *token.File // source file handle dir string // directory portion of file.Name() src []byte // source err ErrorHandler // error reporting; or nil mode Mode // scanning mode // scanning state ch rune // current character offset int // character offset rdOffset int // reading offset (position after current character) lineOffset int // current line offset nParen int unitVal string insertSemi bool // insert a semicolon before next newline // public state - ok to modify ErrorCount int // number of errors encountered } const bom = 0xFEFF // byte order mark, only permitted as very first character // Read the next Unicode char into s.ch. // s.ch < 0 means end-of-file. func (s *Scanner) next() { if s.rdOffset < len(s.src) { s.offset = s.rdOffset if s.ch == '\n' { s.lineOffset = s.offset s.file.AddLine(s.offset) } r, w := rune(s.src[s.rdOffset]), 1 switch { case r == 0: s.error(s.offset, "illegal character NUL") case r >= 0x80: // not ASCII r, w = utf8.DecodeRune(s.src[s.rdOffset:]) if r == utf8.RuneError && w == 1 { s.error(s.offset, "illegal UTF-8 encoding") } else if r == bom && s.offset > 0 { s.error(s.offset, "illegal byte order mark") } } s.rdOffset += w s.ch = r } else { s.offset = len(s.src) if s.ch == '\n' { s.lineOffset = s.offset s.file.AddLine(s.offset) } s.ch = -1 // eof } } // peek returns the byte following the most recently read character without // advancing the scanner. If the scanner is at EOF, peek returns 0. func (s *Scanner) peek() byte { if s.rdOffset < len(s.src) { return s.src[s.rdOffset] } return 0 } // A Mode value is a set of flags (or 0). // They control scanner behavior. type Mode uint const ( // ScanComments means returning comments as COMMENT tokens ScanComments Mode = 1 << iota // NoInsertSemis means don't automatically insert semicolons NoInsertSemis ) // Init prepares the scanner s to tokenize the text src by setting the // scanner at the beginning of src. The scanner uses the file set file // for position information and it adds line information for each line. // It is ok to re-use the same file when re-scanning the same file as // line information which is already present is ignored. Init causes a // panic if the file size does not match the src size. // // Calls to Scan will invoke the error handler err if they encounter a // syntax error and err is not nil. Also, for each error encountered, // the Scanner field ErrorCount is incremented by one. The mode parameter // determines how comments are handled. // // Note that Init may call err if there is an error in the first character // of the file. func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode) { // Explicitly initialize all fields since a scanner may be reused. if file.Size() != len(src) { panic(fmt.Sprintf("file size (%d) does not match src len (%d)", file.Size(), len(src))) } s.InitEx(file, src, 0, err, mode) } // InitEx init the scanner with an offset (this means src[offset:] is all the code to scan). func (s *Scanner) InitEx(file *token.File, src []byte, offset int, err ErrorHandler, mode Mode) { s.file = file s.dir, _ = filepath.Split(file.Name()) s.src = src s.err = err s.mode = mode s.ch = ' ' s.offset = 0 s.rdOffset = offset s.lineOffset = 0 s.insertSemi = false s.ErrorCount = 0 s.next() if s.ch == bom { s.next() // ignore BOM at file beginning } } // CodeTo returns the source code snippet for the given end. func (s *Scanner) CodeTo(end int) []byte { return s.src[:end] } func (s *Scanner) error(offs int, msg string) { if s.err != nil { s.err(s.file.Position(s.file.Pos(offs)), msg) } s.ErrorCount++ } func (s *Scanner) errorf(offs int, format string, args ...any) { s.error(offs, fmt.Sprintf(format, args...)) } var prefix = []byte("//line ") func (s *Scanner) interpretLineComment(text []byte) { if bytes.HasPrefix(text, prefix) { // get filename and line number, if any if i := bytes.LastIndex(text, []byte{':'}); i > 0 { if line, err := strconv.Atoi(string(text[i+1:])); err == nil && line > 0 { // valid //line filename:line comment filename := string(bytes.TrimSpace(text[len(prefix):i])) if filename != "" { filename = filepath.Clean(filename) if !filepath.IsAbs(filename) { // make filename relative to current directory filename = filepath.Join(s.dir, filename) } } // update scanner position s.file.AddLineInfo( s.lineOffset+len(text)+1, filename, line) // +len(text)+1 since comment applies to next line } } } } func (s *Scanner) scanComment() string { // initial '/' already consumed; s.ch == '/' || s.ch == '*' offs := s.offset - 1 // position of initial '/' hasCR := false if s.ch == '/' { //-style comment s.next() for s.ch != '\n' && s.ch >= 0 { if s.ch == '\r' { hasCR = true } s.next() } if offs == s.lineOffset { // comment starts at the beginning of the current line s.interpretLineComment(s.src[offs:s.offset]) } goto exit } /*-style comment */ s.next() for s.ch >= 0 { ch := s.ch if ch == '\r' { hasCR = true } s.next() if ch == '*' && s.ch == '/' { s.next() goto exit } } s.error(offs, "comment not terminated") exit: lit := s.src[offs:s.offset] if hasCR { lit = stripCR(lit) } return string(lit) } func (s *Scanner) findLineEnd() bool { // initial '/' already consumed defer func(offs int) { // reset scanner state to where it was upon calling findLineEnd s.ch = '/' s.offset = offs s.rdOffset = offs + 1 s.next() // consume initial '/' again }(s.offset - 1) // read ahead until a newline, EOF, or non-comment token is found for s.ch == '/' || s.ch == '*' { if s.ch == '/' { //-style comment always contains a newline return true } /*-style comment: look for newline */ s.next() for s.ch >= 0 { ch := s.ch if ch == '\n' { return true } s.next() if ch == '*' && s.ch == '/' { s.next() break } } s.skipWhitespace() // s.insertSemi is set if s.ch < 0 || s.ch == '\n' { return true } if s.ch != '/' { // non-comment token return false } s.next() // consume '/' } return false } func isLetter(ch rune) bool { return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch) } func isDigit(ch rune) bool { return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch) } func (s *Scanner) scanIdentifier() string { offs := s.offset for isLetter(s.ch) || isDigit(s.ch) { s.next() } return string(s.src[offs:s.offset]) } func digitVal(ch rune) int { switch { case '0' <= ch && ch <= '9': return int(ch - '0') case 'a' <= lower(ch) && lower(ch) <= 'f': return int(lower(ch) - 'a' + 10) } return 16 // larger than any legal digit val } func lower(ch rune) rune { return ('a' - 'A') | ch } // returns lower-case ch iff ch is ASCII letter func isDecimal(ch rune) bool { return '0' <= ch && ch <= '9' } func isHex(ch rune) bool { return '0' <= ch && ch <= '9' || 'a' <= lower(ch) && lower(ch) <= 'f' } // digits accepts the sequence { digit | '_' }. // If base <= 10, digits accepts any decimal digit but records // the offset (relative to the source start) of a digit >= base // in *invalid, if *invalid < 0. // digits returns a bitset describing whether the sequence contained // digits (bit 0 is set), or separators '_' (bit 1 is set). func (s *Scanner) digits(base int, invalid *int) (digsep int) { if base <= 10 { max := rune('0' + base) for isDecimal(s.ch) || s.ch == '_' { ds := 1 if s.ch == '_' { ds = 2 } else if s.ch >= max && *invalid < 0 { *invalid = int(s.offset) // record invalid rune offset } digsep |= ds s.next() } } else { for isHex(s.ch) || s.ch == '_' { ds := 1 if s.ch == '_' { ds = 2 } digsep |= ds s.next() } } return } func (s *Scanner) scanNumber() (token.Token, string) { offs := s.offset tok := token.ILLEGAL base := 10 // number base prefix := rune(0) // one of 0 (decimal), '0' (0-octal), 'x', 'o', or 'b' digsep := 0 // bit 0: digit present, bit 1: '_' present invalid := -1 // index of invalid digit in literal, or < 0 // integer part if s.ch != '.' { tok = token.INT if s.ch == '0' { s.next() switch lower(s.ch) { case 'x': s.next() base, prefix = 16, 'x' case 'o': s.next() base, prefix = 8, 'o' case 'b': s.next() base, prefix = 2, 'b' default: base, prefix = 8, '0' digsep = 1 // leading 0 } } digsep |= s.digits(base, &invalid) } // fractional part if s.ch == '.' { tok = token.FLOAT if prefix == 'o' || prefix == 'b' { s.error(s.offset, "invalid radix point in "+litname(prefix)) } s.next() digsep |= s.digits(base, &invalid) } if digsep&1 == 0 { s.error(s.offset, litname(prefix)+" has no digits") } // exponent if e := lower(s.ch); e == 'e' || e == 'p' { switch { case e == 'e' && prefix != 0 && prefix != '0': s.errorf(s.offset, "%q exponent requires decimal mantissa", s.ch) case e == 'p' && prefix != 'x': s.errorf(s.offset, "%q exponent requires hexadecimal mantissa", s.ch) } s.next() tok = token.FLOAT if s.ch == '+' || s.ch == '-' { s.next() } ds := s.digits(10, nil) digsep |= ds if ds&1 == 0 { s.error(s.offset, "exponent has no digits") } } else if prefix == 'x' && tok == token.FLOAT { s.error(s.offset, "hexadecimal mantissa requires a 'p' exponent") } if isLetter(s.ch) { id := s.scanIdentifier() switch id { case "i": tok = token.IMAG case "r": tok = token.RAT default: s.unitVal = id } } lit := string(s.src[offs : s.offset-len(s.unitVal)]) if tok == token.INT && invalid >= 0 { s.errorf(invalid, "invalid digit %q in %s", lit[invalid-offs], litname(prefix)) } if digsep&2 != 0 { if i := invalidSep(lit); i >= 0 { s.error(offs+i, "'_' must separate successive digits") } } return tok, lit } func litname(prefix rune) string { switch prefix { case 'x': return "hexadecimal literal" case 'o', '0': return "octal literal" case 'b': return "binary literal" } return "decimal literal" } // invalidSep returns the index of the first invalid separator in x, or -1. func invalidSep(x string) int { x1 := ' ' // prefix char, we only care if it's 'x' d := '.' // digit, one of '_', '0' (a digit), or '.' (anything else) i := 0 // a prefix counts as a digit if len(x) >= 2 && x[0] == '0' { x1 = lower(rune(x[1])) if x1 == 'x' || x1 == 'o' || x1 == 'b' { d = '0' i = 2 } } // mantissa and exponent for ; i < len(x); i++ { p := d // previous digit d = rune(x[i]) switch { case d == '_': if p != '0' { return i } case isDecimal(d) || x1 == 'x' && isHex(d): d = '0' default: if p == '_' { return i - 1 } d = '.' } } if d == '_' { return len(x) - 1 } return -1 } // scanEscape parses an escape sequence where rune is the accepted // escaped quote. In case of a syntax error, it stops at the offending // character (without consuming it) and returns false. Otherwise // it returns true. func (s *Scanner) scanEscape(quote rune) bool { offs := s.offset var n int var base, max uint32 switch s.ch { case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', quote: s.next() return true case '0', '1', '2', '3', '4', '5', '6', '7': n, base, max = 3, 8, 255 case 'x': s.next() n, base, max = 2, 16, 255 case 'u': s.next() n, base, max = 4, 16, unicode.MaxRune case 'U': s.next() n, base, max = 8, 16, unicode.MaxRune default: msg := "unknown escape sequence" if s.ch < 0 { msg = "escape sequence not terminated" } s.error(offs, msg) return false } var x uint32 for n > 0 { d := uint32(digitVal(s.ch)) if d >= base { msg := fmt.Sprintf("illegal character %#U in escape sequence", s.ch) if s.ch < 0 { msg = "escape sequence not terminated" } s.error(s.offset, msg) return false } x = x*base + d s.next() n-- } if x > max || 0xD800 <= x && x < 0xE000 { s.error(offs, "escape sequence is invalid Unicode code point") return false } return true } func (s *Scanner) scanRune() string { // '\'' opening already consumed offs := s.offset - 1 valid := true n := 0 for { ch := s.ch if ch == '\n' || ch < 0 { // only report error if we don't have one already if valid { s.error(offs, "rune literal not terminated") valid = false } break } s.next() if ch == '\'' { break } n++ if ch == '\\' { if !s.scanEscape('\'') { valid = false } // continue to read to closing quote } } if valid && n != 1 { s.error(offs, "illegal rune literal") } return string(s.src[offs:s.offset]) } func (s *Scanner) scanSharpComment() string { // '#' opening already consumed offs := s.offset - 1 for { ch := s.ch if ch == '\n' || ch < 0 { break } s.next() } return string(s.src[offs:s.offset]) } func (s *Scanner) scanString() string { // '"' opening already consumed offs := s.offset - 1 for { ch := s.ch if ch == '\n' || ch < 0 { s.error(offs, "string literal not terminated") break } s.next() if ch == '"' { break } if ch == '\\' { s.scanEscape('"') } } return string(s.src[offs:s.offset]) } func stripCR(b []byte) []byte { c := make([]byte, len(b)) i := 0 for _, ch := range b { if ch != '\r' { c[i] = ch i++ } } return c[:i] } func (s *Scanner) scanRawString() string { // '`' opening already consumed offs := s.offset - 1 hasCR := false for { ch := s.ch if ch < 0 { s.error(offs, "raw string literal not terminated") break } s.next() if ch == '`' { break } if ch == '\r' { hasCR = true } } lit := s.src[offs:s.offset] if hasCR { lit = stripCR(lit) } return string(lit) } func (s *Scanner) skipWhitespace() { for s.ch == ' ' || s.ch == '\t' || s.ch == '\n' && !s.insertSemi || s.ch == '\r' { s.next() } } // Helper functions for scanning multi-byte tokens such as >> += >>= . // Different routines recognize different length tok_i based on matches // of ch_i. If a token ends in '=', the result is tok1 or tok3 // respectively. Otherwise, the result is tok0 if there was no other // matching character, or tok2 if the matching character was ch2. func (s *Scanner) switch2(tok0, tok1 token.Token) token.Token { if s.ch == '=' { s.next() return tok1 } return tok0 } func (s *Scanner) switch3(tok0, tok1 token.Token, ch2 rune, tok2 token.Token) token.Token { if s.ch == '=' { s.next() return tok1 } if s.ch == ch2 { s.next() return tok2 } return tok0 } func (s *Scanner) switch4(tok0, tok1 token.Token, ch2 rune, tok2, tok3 token.Token) token.Token { if s.ch == '=' { s.next() return tok1 } if s.ch == ch2 { s.next() if s.ch == '=' { s.next() return tok3 } return tok2 } return tok0 } func (s *Scanner) tokSEMICOLON() token.Token { s.nParen = 0 return token.SEMICOLON } // Scan scans the next token and returns the token position, the token, // and its literal string if applicable. The source end is indicated by // token.EOF. // // If the returned token is a literal (IDENT, INT, FLOAT, IMAG, CHAR, STRING) // or COMMENT, the literal string has the corresponding value. // // If the returned token is SEMICOLON, the corresponding // literal string is ";" if the semicolon was present in the source, // and "\n" if the semicolon was inserted because of a newline or // at EOF. // // If the returned token is ILLEGAL, the literal string is the offending // character. // // In all other cases, Scan returns an empty literal string. // // For more tolerant parsing, Scan will return a valid token if // possible even if a syntax error was encountered. Thus, even // if the resulting token sequence contains no illegal tokens, // a client may not assume that no error occurred. Instead it // must check the scanner's ErrorCount or the number of calls // of the error handler, if there was one installed. // // Scan adds line information to the file added to the file // set with Init. Token positions are relative to that file // and thus relative to the file set. func (s *Scanner) Scan() (t types.Token) { scanAgain: s.skipWhitespace() // current token start t.Pos = s.file.Pos(s.offset) // determine token value insertSemi := false if s.unitVal != "" { // number with unit insertSemi = true t.Pos -= token.Pos(len(s.unitVal)) t.Tok, t.Lit = token.UNIT, s.unitVal s.unitVal = "" goto done } switch ch := s.ch; { case isLetter(ch): insertSemi = true t.Lit = s.scanIdentifier() t.Tok = token.IDENT case isDecimal(ch) || ch == '.' && isDecimal(rune(s.peek())): insertSemi = true t.Tok, t.Lit = s.scanNumber() default: s.next() // always make progress switch ch { case -1: if s.insertSemi { s.insertSemi = false // EOF consumed t.Tok, t.Lit = s.tokSEMICOLON(), "\n" return } t.Tok = token.EOF case '\n': // we only reach here if s.insertSemi was // set in the first place and exited early // from s.skipWhitespace() s.insertSemi = false // newline consumed t.Tok, t.Lit = s.tokSEMICOLON(), "\n" return case '"': insertSemi = true t.Tok = token.STRING t.Lit = s.scanString() case '\'': insertSemi = true t.Tok = token.CHAR t.Lit = s.scanRune() case '`': insertSemi = true t.Tok = token.STRING t.Lit = s.scanRawString() case ':': t.Tok = s.switch2(token.COLON, token.DEFINE) case '.': // fractions starting with a '.' are handled by outer switch if s.ch == '.' && s.peek() == '.' { s.next() s.next() // consume last '.' if s.nParen == 0 { insertSemi = true } t.Tok = token.ELLIPSIS } else { t.Tok = token.PERIOD } case ',': t.Tok = token.COMMA case ';': t.Tok = s.tokSEMICOLON() t.Lit = ";" case '(': s.nParen++ t.Tok = token.LPAREN case ')': s.nParen-- insertSemi = true t.Tok = token.RPAREN case '[': t.Tok = token.LBRACK case ']': insertSemi = true t.Tok = token.RBRACK case '{': t.Tok = token.LBRACE case '}': insertSemi = true t.Tok = token.RBRACE case '+': t.Tok = s.switch3(token.ADD, token.ADD_ASSIGN, '+', token.INC) if t.Tok == token.INC { insertSemi = true } case '-': if s.ch == '>' { // -> s.next() t.Tok = token.SRARROW } else { // -- -= t.Tok = s.switch3(token.SUB, token.SUB_ASSIGN, '-', token.DEC) if t.Tok == token.DEC { insertSemi = true } } case '*': t.Tok = s.switch2(token.MUL, token.MUL_ASSIGN) case '/': if s.ch == '/' || s.ch == '*' { // comment if s.insertSemi && s.findLineEnd() { // reset position to the beginning of the comment s.ch = '/' s.offset = s.file.Offset(t.Pos) s.rdOffset = s.offset + 1 s.insertSemi = false // newline consumed t.Tok, t.Lit = s.tokSEMICOLON(), "\n" return } comment := s.scanComment() if s.mode&ScanComments == 0 { // skip comment s.insertSemi = false // newline consumed goto scanAgain } t.Tok = token.COMMENT t.Lit = comment } else { t.Tok = s.switch2(token.QUO, token.QUO_ASSIGN) } case '#': if s.insertSemi { s.ch = '#' s.offset = s.file.Offset(t.Pos) s.rdOffset = s.offset + 1 s.insertSemi = false t.Tok, t.Lit = s.tokSEMICOLON(), "\n" return } comment := s.scanSharpComment() if s.mode&ScanComments == 0 { // skip comment goto scanAgain } t.Tok = token.COMMENT t.Lit = comment case '%': t.Tok = s.switch2(token.REM, token.REM_ASSIGN) case '^': t.Tok = s.switch2(token.XOR, token.XOR_ASSIGN) case '<': switch s.ch { case '-': // <- s.next() t.Tok = token.ARROW case '>': // <> s.next() t.Tok = token.BIDIARROW default: // <= << <<= t.Tok = s.switch4(token.LT, token.LE, '<', token.SHL, token.SHL_ASSIGN) } case '>': t.Tok = s.switch4(token.GT, token.GE, '>', token.SHR, token.SHR_ASSIGN) case '=': t.Tok = s.switch3(token.ASSIGN, token.EQ, '>', token.DRARROW) case '!': t.Tok = s.switch2(token.NOT, token.NE) if t.Tok == token.NOT { insertSemi = true } case '&': if s.ch == '^' { s.next() t.Tok = s.switch2(token.AND_NOT, token.AND_NOT_ASSIGN) } else { t.Tok = s.switch3(token.AND, token.AND_ASSIGN, '&', token.LAND) } case '|': t.Tok = s.switch3(token.OR, token.OR_ASSIGN, '|', token.LOR) case '?': t.Tok = token.QUESTION insertSemi = true case '$': t.Tok = token.ENV case '~': t.Tok = token.TILDE case '@': t.Tok = token.AT default: // next reports unexpected BOMs - don't repeat if ch != bom { s.error(s.file.Offset(t.Pos), fmt.Sprintf("illegal character %#U", ch)) } insertSemi = s.insertSemi // preserve insertSemi info t.Tok = token.ILLEGAL t.Lit = string(ch) } } done: if s.mode&NoInsertSemis == 0 { s.insertSemi = insertSemi } return } ================================================ FILE: tpl/scanner/scanner_test.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package scanner import ( "testing" "github.com/goplus/xgo/tpl/token" "github.com/goplus/xgo/tpl/types" ) type Token = types.Token type tokenTest struct { Pos token.Pos Kind token.Token Literal string } func TestScanner(t *testing.T) { var expected = []tokenTest{ {3, token.IDENT, `term`}, {8, '=', ``}, {10, token.IDENT, `factor`}, {17, '*', ``}, {18, '(', ``}, {19, token.CHAR, `'*'`}, {23, token.IDENT, `factor`}, {29, '/', ``}, {30, token.IDENT, `mul`}, {34, '|', ``}, {36, token.CHAR, `'/'`}, {40, token.IDENT, `factor`}, {46, '/', ``}, {47, token.IDENT, `div`}, {50, ')', ``}, {51, ';', "\n"}, {53, token.IDENT, `expr`}, {58, '=', ``}, {60, token.IDENT, `term`}, {65, '*', ``}, {66, '(', ``}, {67, token.CHAR, `'+'`}, {71, token.IDENT, `term`}, {75, '/', ``}, {76, token.IDENT, `add`}, {80, '|', ``}, {82, token.CHAR, `'-'`}, {86, token.IDENT, `term`}, {90, '/', ``}, {91, token.IDENT, `sub`}, {94, ')', ``}, {95, ';', "\n"}, {97, token.IDENT, `factor`}, {104, '=', ``}, {107, token.IDENT, `FLOAT`}, {112, '/', ``}, {113, token.IDENT, `push`}, {118, '|', ``}, {121, token.CHAR, `'-'`}, {125, token.IDENT, `factor`}, {131, '/', ``}, {132, token.IDENT, `neg`}, {136, '|', ``}, {139, token.CHAR, `'('`}, {143, token.IDENT, `expr`}, {148, token.CHAR, `')'`}, {152, '|', ``}, {155, '(', ``}, {156, token.IDENT, `IDENT`}, {162, token.CHAR, `'('`}, {166, token.IDENT, `expr`}, {171, '%', ``}, {173, token.CHAR, `','`}, {176, '/', ``}, {177, token.IDENT, `arity`}, {183, token.CHAR, `')'`}, {186, ')', ``}, {187, '/', ``}, {188, token.IDENT, `call`}, {193, '|', ``}, {196, token.CHAR, `'+'`}, {200, token.IDENT, `factor`}, {206, ';', "\n"}, {208, token.IDENT, `hello`}, {214, ';', "\n"}, {214, token.COMMENT, "#!/user/bin/env tpl 中文"}, {241, token.IDENT, `hello`}, {247, ';', "\n"}, {247, token.COMMENT, "//!/user/bin/env tpl 中文"}, {275, token.IDENT, `world`}, {281, token.NE, ``}, } const grammar = ` term = factor *('*' factor/mul | '/' factor/div) expr = term *('+' term/add | '-' term/sub) factor = FLOAT/push | '-' factor/neg | '(' expr ')' | (IDENT '(' expr % ','/arity ')')/call | '+' factor hello #!/user/bin/env tpl 中文 hello //!/user/bin/env tpl 中文 world != ` var s Scanner fset := token.NewFileSet() file := fset.AddFile("", -1, len(grammar)) s.Init(file, []byte(grammar), nil /* no error handler */, ScanComments) i := 0 for { c := s.Scan() if c.Tok == token.EOF { break } expect := Token{Tok: expected[i].Kind, Pos: expected[i].Pos, Lit: expected[i].Literal} if c != expect { t.Fatal("Scan failed:", c, expect) } i++ } if len(expected) != i { t.Fatalf("len(expected) != i: %d, %d\n", len(expected), i) } s.Init(file, []byte(grammar), nil, NoInsertSemis) i = 0 for { c := s.Scan() if c.Tok == token.EOF { break } if expected[i].Kind == ';' { i++ } if expected[i].Kind == token.COMMENT { i++ } expect := Token{Tok: expected[i].Kind, Pos: expected[i].Pos, Lit: expected[i].Literal} if c != expect { t.Fatal("Scan failed:", c.Pos, c.Lit, expect) } switch c.Tok.Len() { case 0, 1, 2, 3: default: t.Fatal("TokenLen failed:", c.Tok.Len()) } i++ } if i < len(expected) && expected[i].Kind == ';' { i++ } if len(expected) != i { t.Fatalf("len(expected) != i: %d, %d\n", len(expected), i) } s.Init(file, []byte(grammar), nil, 0) i = 0 for { c := s.Scan() if c.Tok == token.EOF { break } if expected[i].Kind == token.COMMENT { i++ } expect := Token{Tok: expected[i].Kind, Pos: expected[i].Pos, Lit: expected[i].Literal} if c != expect { t.Fatal("Scan failed:", c.Pos, c.Lit, expect) } i++ } if len(expected) != i { t.Fatalf("len(expected) != i: %d, %d\n", len(expected), i) } } ================================================ FILE: tpl/scanner/scannertest/scannertest.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package scannertest import ( "fmt" "io" "github.com/goplus/xgo/tpl/scanner" "github.com/goplus/xgo/tpl/token" goscanner "go/scanner" gotoken "go/token" gopscanner "github.com/goplus/xgo/scanner" goptoken "github.com/goplus/xgo/token" ) // ----------------------------------------------------------------------------- // Scan scans the input and writes the tokens to the writer. func Scan(w io.Writer, in []byte) { var s scanner.Scanner fset := gotoken.NewFileSet() f := fset.AddFile("", -1, len(in)) s.Init(f, in, nil, scanner.ScanComments) for { t := s.Scan() if t.Tok == token.EOF { break } fmt.Fprintln(w, t.Pos, t.Tok, t.Lit) } } // ----------------------------------------------------------------------------- // GopScan scans the input using the XGo standard library scanner and // writes the tokens to the writer. func GopScan(w io.Writer, in []byte) { var s gopscanner.Scanner fset := gotoken.NewFileSet() f := fset.AddFile("", -1, len(in)) s.Init(f, in, nil, gopscanner.ScanComments) for { pos, tok, lit := s.Scan() if tok == goptoken.EOF { break } fmt.Fprintln(w, pos, tok, lit) } } // ----------------------------------------------------------------------------- // GoScan scans the input using the Go standard library scanner and // writes the tokens to the writer. func GoScan(w io.Writer, in []byte) { var s goscanner.Scanner fset := gotoken.NewFileSet() f := fset.AddFile("", -1, len(in)) s.Init(f, in, nil, goscanner.ScanComments) for { pos, tok, lit := s.Scan() if tok == gotoken.EOF { break } fmt.Fprintln(w, pos, tok, lit) } } // ----------------------------------------------------------------------------- ================================================ FILE: tpl/token/token.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package token import ( "strconv" ) // ----------------------------------------------------------------------------- type Token uint func (tok Token) String() (s string) { if tok < Token(len(tokens)) { s = tokens[tok] } if s == "" { s = "token(" + strconv.Itoa(int(tok)) + ")" } return } // Len returns // 1) len of is token literal, if token is an operator. // 2) 0 for else. func (tok Token) Len() int { if tok > ' ' && tok <= Token(len(tokens)) { return len(tokens[tok]) } return 0 } // ----------------------------------------------------------------------------- const ( // Special tokens ILLEGAL Token = iota EOF COMMENT literal_beg // Identifiers and basic type literals // (these tokens stand for classes of literals) IDENT // main INT // 12345 FLOAT // 123.45 IMAG // 123.45i CHAR // 'a' STRING // "abc" RAT // 3r, 3.4r UNIT // 1m, 2.3s, 3ms, 4us, 5ns, 6.5m, 7h, 8d, 9w, 10y literal_end ADD = '+' SUB = '-' MUL = '*' QUO = '/' REM = '%' AND = '&' OR = '|' XOR = '^' LT = '<' GT = '>' ASSIGN = '=' NOT = '!' LPAREN = '(' LBRACK = '[' LBRACE = '{' COMMA = ',' PERIOD = '.' RPAREN = ')' RBRACK = ']' RBRACE = '}' SEMICOLON = ';' COLON = ':' QUESTION = '?' TILDE = '~' AT = '@' ENV = '$' ) const ( operator_beg Token = 0x80 + iota SHL // << SHR // >> AND_NOT // &^ ADD_ASSIGN // += SUB_ASSIGN // -= MUL_ASSIGN // *= QUO_ASSIGN // /= REM_ASSIGN // %= AND_ASSIGN // &= OR_ASSIGN // |= XOR_ASSIGN // ^= SHL_ASSIGN // <<= SHR_ASSIGN // >>= AND_NOT_ASSIGN // &^= LAND // && LOR // || ARROW // <- INC // ++ DEC // -- EQ // == NE // != LE // <= GE // >= DEFINE // := ELLIPSIS // ... DRARROW // => SRARROW // -> BIDIARROW // <> operator_end ) // ----------------------------------------------------------------------------- var tokens = [...]string{ ILLEGAL: "ILLEGAL", EOF: "EOF", COMMENT: "COMMENT", IDENT: "IDENT", INT: "INT", FLOAT: "FLOAT", IMAG: "IMAG", CHAR: "CHAR", STRING: "STRING", RAT: "RAT", UNIT: "UNIT", ADD: "+", SUB: "-", MUL: "*", QUO: "/", REM: "%", AND: "&", OR: "|", XOR: "^", LT: "<", GT: ">", ASSIGN: "=", NOT: "!", LPAREN: "(", LBRACK: "[", LBRACE: "{", COMMA: ",", PERIOD: ".", RPAREN: ")", RBRACK: "]", RBRACE: "}", SEMICOLON: ";", COLON: ":", QUESTION: "?", TILDE: "~", AT: "@", ENV: "$", SHL: "<<", SHR: ">>", AND_NOT: "&^", ADD_ASSIGN: "+=", SUB_ASSIGN: "-=", MUL_ASSIGN: "*=", QUO_ASSIGN: "/=", REM_ASSIGN: "%=", AND_ASSIGN: "&=", OR_ASSIGN: "|=", XOR_ASSIGN: "^=", SHL_ASSIGN: "<<=", SHR_ASSIGN: ">>=", AND_NOT_ASSIGN: "&^=", LAND: "&&", LOR: "||", ARROW: "<-", INC: "++", DEC: "--", EQ: "==", NE: "!=", LE: "<=", GE: ">=", DEFINE: ":=", ELLIPSIS: "...", DRARROW: "=>", SRARROW: "->", BIDIARROW: "<>", } const ( Break = -1 ) // ForEach iterates tokens. func ForEach(from Token, f func(tok Token, lit string) int) { if from == 0 { from = operator_beg + 1 } for from < operator_end { if s := tokens[from]; s != "" { if f(Token(from), s) == Break { break } } from++ } } // ----------------------------------------------------------------------------- ================================================ FILE: tpl/token/token_test.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package token import ( "go/token" "testing" ) func TestToken(t *testing.T) { if literal_beg != 3 { t.Fatal("literal_beg") } for i := Token(0); i < literal_end; i++ { s := i.String() if s == "RAT" || s == "UNIT" || s == "CSTRING" || s == "PYSTRING" { continue } if s != token.Token(i).String() { t.Fatal("String:", i) } } if IDENT.String() != "IDENT" { t.Fatal("IDENT") } if Token(' ').String() != "token(32)" { t.Fatal("token(32)") } if IDENT.Len() != 0 { t.Fatal("TokLen IDENT") } if Token('+').Len() != 1 { t.Fatal("TokLen +") } count := 0 ForEach(0, func(tok Token, name string) int { count++ return 0 }) if count != 28 { t.Fatal("ForEach:", count) } NewFileSet() ForEach(0, func(tok Token, name string) int { if tok != SHL { t.Fatal("ForEach:", tok) } return Break }) } ================================================ FILE: tpl/token/types.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package token import ( "go/token" ) // Pos is a compact encoding of a source position within a file set. // It can be converted into a Position for a more convenient, but much // larger, representation. // // The Pos value for a given file is a number in the range [base, base+size], // where base and size are specified when adding the file to the file set via // AddFile. // // To create the Pos value for a specific source offset (measured in bytes), // first add the respective file to the current file set using FileSet.AddFile // and then call File.Pos(offset) for that file. Given a Pos value p // for a specific file set fset, the corresponding Position value is // obtained by calling fset.Position(p). // // Pos values can be compared directly with the usual comparison operators: // If two Pos values p and q are in the same file, comparing p and q is // equivalent to comparing the respective source file offsets. If p and q // are in different files, p < q is true if the file implied by p was added // to the respective file set before the file implied by q. type Pos = token.Pos const ( // NoPos - The zero value for Pos is NoPos; there is no file and line // information associated with it, and NoPos.IsValid() is false. NoPos // is always smaller than any other Pos value. The corresponding // Position value for NoPos is the zero value for Position. NoPos = token.NoPos ) // Position describes an arbitrary source position // including the file, line, and column location. // A Position is valid if the line number is > 0. type Position = token.Position // A File is a handle for a file belonging to a FileSet. // A File has a name, size, and line offset table. type File = token.File // A FileSet represents a set of source files. Methods of file sets are // synchronized; multiple goroutines may invoke them concurrently. type FileSet = token.FileSet // NewFileSet creates a new file set. func NewFileSet() *FileSet { return token.NewFileSet() } ================================================ FILE: tpl/tpl.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package tpl import ( "fmt" "io" "os" "reflect" "github.com/goplus/xgo/tpl/ast" "github.com/goplus/xgo/tpl/cl" "github.com/goplus/xgo/tpl/matcher" "github.com/goplus/xgo/tpl/parser" "github.com/goplus/xgo/tpl/scanner" "github.com/goplus/xgo/tpl/token" "github.com/goplus/xgo/tpl/types" "github.com/qiniu/x/errors" "github.com/qiniu/x/stream" ) // ----------------------------------------------------------------------------- var showConflict = true // ShowConflict sets the flag to show or hide conflicts. func ShowConflict(f bool) int { showConflict = f return 0 } func onConflictHidden(fset *token.FileSet, c *ast.Choice, firsts [][]any, i, at int) { } // ----------------------------------------------------------------------------- func relocatePos(ePos *token.Position, filename string, line, col int) { ePos.Filename = filename if ePos.Line == line { ePos.Column += col - 1 } else { ePos.Line += line - 1 } } // Relocate relocates the error positions. func Relocate(err error, filename string, line, col int) error { switch e := err.(type) { case *matcher.Error: pos := e.Fset.Position(e.Pos) relocatePos(&pos, filename, line, col) return &scanner.Error{Pos: pos, Msg: e.Msg} case errors.List: for i, ie := range e { e[i] = Relocate(ie, filename, line, col) } case scanner.ErrorList: for _, ie := range e { relocatePos(&ie.Pos, filename, line, col) } case *scanner.Error: relocatePos(&e.Pos, filename, line, col) default: panic("todo: " + reflect.TypeOf(err).String()) } return err } // ----------------------------------------------------------------------------- // Compiler represents a TPL compiler. type Compiler struct { cl.Result } // New creates a new TPL compiler. // params: ruleName1, retProc1, ..., ruleNameN, retProcN func New(src any, params ...any) (ret Compiler, err error) { conf := &cl.Config{ RetProcs: retProcs(params), } if !showConflict { conf.OnConflict = onConflictHidden } return FromFile(nil, "", src, conf) } // NewEx creates a new TPL compiler. // params: ruleName1, retProc1, ..., ruleNameN, retProcN func NewEx(src any, filename string, line, col int, params ...any) (ret Compiler, err error) { conf := &cl.Config{ RetProcs: retProcs(params), } if showConflict { conf.OnConflict = func(fset *token.FileSet, c *ast.Choice, firsts [][]any, i, at int) { if showConflict { pos := fset.Position(c.Options[i].Pos()) relocatePos(&pos, filename, line, col) cl.LogConflict(pos, firsts, i, at) } } } else { conf.OnConflict = onConflictHidden } ret, err = FromFile(nil, "", src, conf) if err != nil { err = Relocate(err, filename, line, col) } return } // FromFile creates a new TPL compiler from a file. // fset can be nil. func FromFile(fset *token.FileSet, filename string, src any, conf *cl.Config) (ret Compiler, err error) { if fset == nil { fset = token.NewFileSet() } f, err := parser.ParseFile(fset, filename, src, nil) if err != nil { return } ret.Result, err = cl.NewEx(conf, fset, f) return } func retProcs(params []any) map[string]any { n := len(params) if n == 0 { return nil } if n&1 != 0 { panic("tpl.New: invalid params. should be in form `ruleName1, retProc1, ..., ruleNameN, retProcN`") } ret := make(map[string]any, n>>1) for i := 0; i < n; i += 2 { ret[params[i].(string)] = params[i+1] } return ret } // ----------------------------------------------------------------------------- // Error represents a matching error. type Error = matcher.Error // A Token is a lexical unit returned by Scan. type Token = types.Token // Scanner represents a TPL scanner. type Scanner interface { Scan() Token Init(file *token.File, src []byte, err scanner.ErrorHandler, mode scanner.Mode) } // Config represents a parsing configuration of [Compiler.Parse]. type Config struct { Scanner Scanner ScanErrorHandler scanner.ErrorHandler ScanMode scanner.Mode Fset *token.FileSet } // ParseExpr parses an expression. func (p *Compiler) ParseExpr(x string, conf *Config) (result any, err error) { return p.ParseExprFrom("", x, conf) } // ParseExprFrom parses an expression from a file. func (p *Compiler) ParseExprFrom(filename string, src any, conf *Config) (result any, err error) { ms, result, err := p.Match(filename, src, conf) if err != nil { return } if len(ms.Toks) == ms.N || isEOL(ms.Toks[ms.N].Tok) { return } t := ms.Next() err = ms.Ctx.NewErrorf(t.Pos, "unexpected token: %v", t) return } // Parse parses a source file. func (p *Compiler) Parse(filename string, src any, conf *Config) (result any, err error) { ms, result, err := p.Match(filename, src, conf) if err != nil { return } if len(ms.Toks) > ms.N { t := ms.Next() err = ms.Ctx.NewErrorf(t.Pos, "unexpected token: %v", t) } return } // MatchState represents a matching state. type MatchState struct { Toks []*Token Ctx *matcher.Context N int } // Next returns the next token. func (p *MatchState) Next() *Token { n := p.Ctx.Left if n > 0 { return p.Toks[len(p.Toks)-n] } return &Token{Tok: token.EOF, Pos: p.Ctx.FileEnd} } // Match matches a source file. func (p *Compiler) Match(filename string, src any, conf *Config) (ms MatchState, result any, err error) { b, err := stream.ReadSourceLocal(filename, src) if err != nil { return } if conf == nil { conf = &Config{} } s := conf.Scanner if s == nil { s = new(scanner.Scanner) } fset := conf.Fset if fset == nil { fset = token.NewFileSet() } f := fset.AddFile(filename, fset.Base(), len(b)) s.Init(f, b, conf.ScanErrorHandler, conf.ScanMode) n := (len(b) >> 3) &^ 7 if n < 8 { n = 8 } toks := make([]*Token, 0, n) for { t := s.Scan() if t.Tok == token.EOF { break } toks = append(toks, &t) } ms.Ctx = matcher.NewContext(fset, token.Pos(f.Base()+len(b)), toks) ms.N, result, err = p.Doc.Match(toks, ms.Ctx) ms.Ctx.SetLastError(len(toks)-ms.N, err) if err != nil { return } ms.Toks = toks return } // ----------------------------------------------------------------------------- func isEOL(tok token.Token) bool { return tok == token.SEMICOLON || tok == token.EOF } func isPlain(result []any) bool { for _, v := range result { if _, ok := scalar(v); !ok { return false } } return true } func scalar(v any) (any, bool) { if v == nil { return nil, true } retry: switch result := v.(type) { case *Token: return v, true case []any: if len(result) == 2 { if isVoid(result[1]) { v = result[0] goto retry } } return v, len(result) == 0 } return v, false } func isVoid(v any) bool { if v == nil { return true } switch v := v.(type) { case *Token: return v.Tok == token.SEMICOLON || v.Tok == token.EOF case []any: return len(v) == 0 } return false } // ----------------------------------------------------------------------------- func Dump(result any, omitSemi ...bool) { Fdump(os.Stdout, result, "", " ", omitSemi != nil && omitSemi[0]) } func Fdump(w io.Writer, ret any, prefix, indent string, omitSemi bool) { retry: switch result := ret.(type) { case *Token: if result.Tok != token.SEMICOLON { fmt.Fprint(w, prefix, result, "\n") } else if !omitSemi { fmt.Fprint(w, prefix, ";\n") } case []any: if len(result) == 2 { if isVoid(result[1]) { ret = result[0] goto retry } } if isPlain(result) { fmt.Print(prefix, "[") for i, v := range result { if i > 0 { fmt.Print(" ") } v, _ = scalar(v) fmt.Fprint(w, v) } fmt.Print("]\n") } else { fmt.Print(prefix, "[\n") for _, v := range result { Fdump(w, v, prefix+indent, indent, omitSemi) } fmt.Print(prefix, "]\n") } case nil: fmt.Fprint(w, prefix, "nil\n") default: panic("unexpected node: " + reflect.TypeOf(ret).String()) } } // ----------------------------------------------------------------------------- // List converts the matching result of (R % ",") to a flat list. // R % "," means R *("," R) func List(in []any) []any { next := in[1].([]any) ret := make([]any, len(next)+1) ret[0] = in[0] for i, v := range next { ret[i+1] = v.([]any)[1] } return ret } // ListOp converts the matching result of (R % ",") to a flat list. // R % "," means R *("," R) func ListOp[T any](in []any, fn func(v any) T) []T { next := in[1].([]any) ret := make([]T, len(next)+1) ret[0] = fn(in[0]) for i, v := range next { ret[i+1] = fn(v.([]any)[1]) } return ret } // RangeOp travels the matching result of (R % ",") and call fn(result of R). // R % "," means R *("," R) func RangeOp(in []any, fn func(v any)) { next := in[1].([]any) fn(in[0]) for _, v := range next { fn(v.([]any)[1]) } } // BinaryExpr converts the matching result of (X % op) to a binary expression. // X % op means X *(op X) func BinaryExpr(recursive bool, in []any) ast.Expr { if recursive { return BinaryExprR(in) } return BinaryExprNR(in) } func BinaryExprR(in []any) ast.Expr { var ret, y ast.Expr switch v := in[0].(type) { case []any: ret = BinaryExprR(v) default: ret = v.(ast.Expr) } for _, v := range in[1].([]any) { next := v.([]any) op := next[0].(*Token) switch v := next[1].(type) { case []any: y = BinaryExprR(v) default: y = v.(ast.Expr) } ret = &ast.BinaryExpr{ X: ret, OpPos: op.Pos, Op: op.Tok, Y: y, } } return ret } func BinaryExprNR(in []any) ast.Expr { ret := in[0].(ast.Expr) for _, v := range in[1].([]any) { next := v.([]any) op := next[0].(*Token) y := next[1].(ast.Expr) ret = &ast.BinaryExpr{ X: ret, OpPos: op.Pos, Op: op.Tok, Y: y, } } return ret } func BinaryOp(recursive bool, in []any, fn func(op *Token, x, y any) any) any { if recursive { return BinaryOpR(in, fn) } return BinaryOpNR(in, fn) } func BinaryOpR(in []any, fn func(op *Token, x, y any) any) any { ret := in[0] if v, ok := ret.([]any); ok { ret = BinaryOpR(v, fn) } for _, v := range in[1].([]any) { next := v.([]any) op := next[0].(*Token) y := next[1] if v, ok := y.([]any); ok { y = BinaryOpR(v, fn) } ret = fncall(fn, op, ret, y) } return ret } func BinaryOpNR(in []any, fn func(op *Token, x, y any) any) any { ret := in[0] for _, v := range in[1].([]any) { next := v.([]any) op := next[0].(*Token) y := next[1] ret = fncall(fn, op, ret, y) } return ret } func fncall(fn func(op *Token, x, y any) any, op *Token, x, y any) any { defer func() { if e := recover(); e != nil { panic(toErr(e, op)) } }() return fn(op, x, y) } func toErr(e any, op *Token) error { switch e := e.(type) { case *matcher.Error: return e case string: return &matcher.Error{ Pos: op.Pos, Msg: e, Dyn: true, } } return e.(error) } // UnaryExpr converts the matching result of (op X) to a unary expression. func UnaryExpr(in []any) ast.Expr { op := in[0].(*Token) return &ast.UnaryExpr{ OpPos: op.Pos, Op: op.Tok, X: in[1].(ast.Expr), } } // Ident converts the matching result of an identifier to an ast.Ident expression. func Ident(this any) *ast.Ident { v := this.(*Token) return &ast.Ident{ NamePos: v.Pos, Name: v.Lit, } } // BasicLit converts the matching result of a basic literal to an ast.BasicLit expression. func BasicLit(this any) *ast.BasicLit { v := this.(*Token) return &ast.BasicLit{ ValuePos: v.Pos, Kind: v.Tok, Value: v.Lit, } } // ----------------------------------------------------------------------------- // Panic panics with a matcher error. func Panic(pos token.Pos, msg string) { err := &matcher.Error{ Pos: pos, Msg: msg, Dyn: true, } panic(err) } // ----------------------------------------------------------------------------- ================================================ FILE: tpl/types/types.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package types import ( "github.com/goplus/xgo/tpl/token" ) // A Token is a lexical unit returned by Scan. type Token struct { Tok token.Token Pos token.Pos Lit string } // String returns the string representation of a token. func (p *Token) String() string { n := len(p.Lit) if n == 0 { return p.Tok.String() } return p.Lit } // End returns end position of this token. func (p *Token) End() token.Pos { n := len(p.Lit) if n == 0 { n = p.Tok.Len() } return p.Pos + token.Pos(n) } ================================================ FILE: tpl/variant/builtin/builtin.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package buitin import ( "reflect" "github.com/goplus/xgo/tpl/variant" ) // ----------------------------------------------------------------------------- // CastInt converts a value to int. func CastInt(args ...any) any { if len(args) != 1 { panic("int: arity mismatch") } switch v := variant.Eval(args[0]).(type) { case float64: return int(v) case int: return v } panic("can't convert to int") } // ----------------------------------------------------------------------------- // Type returns the type of an value. func Type(args ...any) any { if len(args) != 1 { panic("type: arity mismatch") } return reflect.TypeOf(variant.Eval(args[0])) } // ----------------------------------------------------------------------------- func init() { mod := variant.NewModule("builtin") mod.Insert("int", CastInt) mod.Insert("type", Type) } // ----------------------------------------------------------------------------- ================================================ FILE: tpl/variant/delay/delay.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package delay import ( "github.com/goplus/xgo/tpl/token" "github.com/goplus/xgo/tpl/variant" ) // ----------------------------------------------------------------------------- // Value represents a delayed value. type Value = func() any // Eval evaluates a value. func Eval(v any) any { if d, ok := v.(Value); ok { return d() } return v } // EvalOp delays to evaluate a value. func EvalOp(expr any, fn func(v any)) any { return func() any { fn(Eval(expr)) return nil } } // List delays to convert the matching result of (R % ",") to a flat list. // R % "," means R *("," R) func List(in []any, fn func(flat []any)) any { return func() any { fn(variant.List(in)) return nil } } // ListOp delays to convert the matching result of (R % ",") to a flat list. // R % "," means R *("," R) func ListOp[T any](in []any, op func(v any) T, fn func(flat []T)) any { return func() any { fn(variant.ListOp(in, op)) return nil } } // RangeOp delays to travel the matching result of (R % ",") and call fn(result of R). // R % "," means R *("," R) func RangeOp(in []any, fn func(v any)) any { return func() any { variant.RangeOp(in, fn) return nil } } // ----------------------------------------------------------------------------- // Compare delays a compare operation. func Compare(op token.Token, x, y any) any { return func() any { return variant.Compare(op, x, y) } } // MathOp delays a math operation. func MathOp(op token.Token, x, y any) any { return func() any { return variant.MathOp(op, x, y) } } // LogicOp delays a logic operation. func LogicOp(op token.Token, x, y any) any { return func() any { return variant.LogicOp(op, x, y) } } // UnaryOp delays a unary operation. func UnaryOp(op token.Token, x any) any { return func() any { return variant.UnaryOp(op, x) } } // Call delays to call a function. func Call(needList bool, name string, arglist any) any { return func() any { return variant.Call(needList, name, arglist) } } // CallObject delays to call a function object. func CallObject(needList bool, fn any, arglist any) any { return func() any { return variant.CallObject(needList, fn, arglist) } } // ----------------------------------------------------------------------------- // ValueOf delays to get a value. func ValueOf(name string, getval func(name string) (any, bool)) any { return func() any { v, ok := getval(name) if !ok { panic(name + " is undefined") } return v } } // SetValue delays to set a value. func SetValue(name string, setval func(name string, val any), expr any) any { return func() any { setval(name, Eval(expr)) return nil } } // ChgValue delays to change a value. func ChgValue(name string, chgval func(string, func(oldv any) any), chg func(oldv any) any) any { return func() any { chgval(name, chg) return nil } } // ----------------------------------------------------------------------------- // StmtList delays a statement list. func StmtList(stmts []any) any { return func() any { for _, stmt := range stmts { Eval(stmt) } return nil } } // IfElse delays an if-else statement. func IfElse(cond, ifBody, elseStmt any, elseBodyAt int) any { return func() any { if Eval(cond).(bool) { Eval(ifBody) } else if elseStmt != nil { Eval(elseStmt.([]any)[elseBodyAt]) } return nil } } // While delays a while loop. func While(cond, body any) any { return func() any { for Eval(cond).(bool) { Eval(body) } return nil } } // RepeatUntil delays a repeat-until loop. func RepeatUntil(body, cond any) any { return func() any { for { Eval(body) if Eval(cond).(bool) { break } } return nil } } // ----------------------------------------------------------------------------- // InitUniverse initializes the universe module with the specified modules. func InitUniverse(names ...string) { variant.InitUniverse(names...) } // ----------------------------------------------------------------------------- ================================================ FILE: tpl/variant/math/math.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package math import ( "math" "github.com/goplus/xgo/tpl/token" "github.com/goplus/xgo/tpl/variant" ) // ----------------------------------------------------------------------------- type namedFn struct { name string fn func(...any) any } type math1 struct { name string fn func(float64) float64 } type math2 struct { name string fn func(x, y float64) float64 } type mathIntFloat struct { name string fn func(x int, y float64) float64 } func float1(args []any) float64 { if len(args) != 1 { panic("function call: arity mismatch") } return variant.Float(args[0]) } func float2(args []any) (float64, float64) { if len(args) != 2 { panic("function call: arity mismatch") } return variant.Float(args[0]), variant.Float(args[1]) } func intFloat(args []any) (int, float64) { if len(args) != 2 { panic("function call: arity mismatch") } return variant.Int(args[0]), variant.Float(args[1]) } func regMath1(mod *variant.Module, name string, fn func(float64) float64) { mod.Insert(name, func(args ...any) any { return fn(float1(args)) }) } func regMath2(mod *variant.Module, name string, fn func(x, y float64) float64) { mod.Insert(name, func(args ...any) any { return fn(float2(args)) }) } func regMathIntFloat(mod *variant.Module, name string, fn func(x int, y float64) float64) { mod.Insert(name, func(args ...any) any { return fn(intFloat(args)) }) } // ----------------------------------------------------------------------------- func topValue(op token.Token, args []any) any { ret := variant.Eval(args[0]) for _, v := range args[1:] { if variant.Compare(op, v, ret) { ret = v } } return ret } // Max returns the maximum value. func Max(args ...any) any { if len(args) == 0 { panic("max: arity mismatch") } return topValue(token.GT, args) } // Min returns the minimum value. func Min(args ...any) any { if len(args) == 0 { panic("min: arity mismatch") } return topValue(token.LT, args) } // ----------------------------------------------------------------------------- var fnsMath1 = [...]math1{ {"abs", math.Abs}, {"acos", math.Acos}, {"acosh", math.Acosh}, {"asin", math.Asin}, {"asinh", math.Asinh}, {"atan", math.Atan}, {"atanh", math.Atanh}, {"ceil", math.Ceil}, {"cos", math.Cos}, {"cosh", math.Cosh}, {"erf", math.Erf}, {"erfc", math.Erfc}, {"exp", math.Exp}, {"expm1", math.Expm1}, {"floor", math.Floor}, {"gamma", math.Gamma}, {"j0", math.J0}, {"j1", math.J1}, {"log", math.Log}, {"log10", math.Log10}, {"log1p", math.Log1p}, {"log2", math.Log2}, {"round", math.Round}, {"roundToEven", math.RoundToEven}, {"sin", math.Sin}, {"sinh", math.Sinh}, {"sqrt", math.Sqrt}, {"tan", math.Tan}, {"tanh", math.Tanh}, {"trunc", math.Trunc}, {"y0", math.Y0}, {"y1", math.Y1}, } var fnsMath2 = [...]math2{ {"atan2", math.Atan2}, {"copysign", math.Copysign}, {"dim", math.Dim}, {"hypot", math.Hypot}, {"mod", math.Mod}, {"nextafter", math.Nextafter}, {"pow", math.Pow}, {"remainder", math.Remainder}, } var fnsMathIntFloat = [...]mathIntFloat{ {"jn", math.Jn}, {"yn", math.Yn}, } var fnsNamed = [...]namedFn{ {"max", Max}, {"min", Min}, } func init() { mod := variant.NewModule("math") for _, m := range fnsMath1 { regMath1(mod, m.name, m.fn) } for _, m := range fnsMath2 { regMath2(mod, m.name, m.fn) } for _, m := range fnsMathIntFloat { regMathIntFloat(mod, m.name, m.fn) } for _, m := range fnsNamed { mod.Insert(m.name, m.fn) } } // ----------------------------------------------------------------------------- ================================================ FILE: tpl/variant/module.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package variant import ( "github.com/goplus/xgo/tpl" ) // ----------------------------------------------------------------------------- // Module represents a module. type Module struct { Name string objs map[string]any } // NewModule creates a new module. func NewModule(name string) *Module { mod := &Module{ Name: name, objs: make(map[string]any), } universe.Insert(name, mod) return mod } // Lookup looks up an object in the module. func (p *Module) Lookup(name string) (v any, ok bool) { v, ok = p.objs[name] return } // Insert inserts an object into the module. func (p *Module) Insert(name string, v any) { objs := p.objs if _, ok := objs[name]; ok { panic("object exists: " + name) } objs[name] = v } // Merge merges a module into the current module. func (p *Module) Merge(mod *Module) { for name, obj := range mod.objs { p.Insert(name, obj) } } // ----------------------------------------------------------------------------- var ( universe = &Module{ objs: map[string]any{}, } ) // Call calls a function. func Call(needList bool, name string, arglist any) any { if o, ok := universe.objs[name]; ok { if fn, ok := o.(func(...any) any); ok { var args []any if arglist != nil { args = arglist.([]any) if needList { args = tpl.List(args) } } return fn(args...) } panic("not a function: " + name) } panic("function not found: " + name) } // CallObject calls a function object. func CallObject(needList bool, fn any, arglist any) any { if fn, ok := Eval(fn).(func(...any) any); ok { var args []any if arglist != nil { args = arglist.([]any) if needList { args = tpl.List(args) } } return fn(args...) } panic("call of non function") } // ----------------------------------------------------------------------------- // InitUniverse initializes the universe module with the specified modules. func InitUniverse(names ...string) { for _, name := range names { if o, ok := universe.objs[name]; ok { if mod, ok := o.(*Module); ok { universe.Merge(mod) } else { panic("not a module: " + name) } } else { panic("module not found: " + name) } } } // ----------------------------------------------------------------------------- ================================================ FILE: tpl/variant/time/time.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package time import ( "time" "github.com/goplus/xgo/tpl/variant" ) // ----------------------------------------------------------------------------- func now(args ...any) any { if len(args) > 0 { panic("function call: arity mismatch") } return time.Now() } func init() { mod := variant.NewModule("time") mod.Insert("now", now) } // ----------------------------------------------------------------------------- ================================================ FILE: tpl/variant/variant.go ================================================ /* * Copyright (c) 2025 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package variant import ( "github.com/goplus/xgo/tpl/token" ) // ----------------------------------------------------------------------------- // DelayValue represents a delayed value. type DelayValue = func() any // Eval evaluates a value. func Eval(v any) any { if d, ok := v.(DelayValue); ok { return d() } return v } // List converts the matching result of (R % ",") to a flat list. // R % "," means R *("," R) func List(in []any) []any { next := in[1].([]any) ret := make([]any, len(next)+1) ret[0] = Eval(in[0]) for i, v := range next { ret[i+1] = Eval(v.([]any)[1]) } return ret } // ListOp converts the matching result of (R % ",") to a flat list. // R % "," means R *("," R) func ListOp[T any](in []any, fn func(v any) T) []T { next := in[1].([]any) ret := make([]T, len(next)+1) ret[0] = fn(Eval(in[0])) for i, v := range next { ret[i+1] = fn(Eval(v.([]any)[1])) } return ret } // RangeOp travels the matching result of (R % ",") and call fn(result of R). // R % "," means R *("," R) func RangeOp(in []any, fn func(v any)) { next := in[1].([]any) fn(Eval(in[0])) for _, v := range next { fn(Eval(v.([]any)[1])) } } // Float converts a value to float64. func Float(v any) float64 { switch v := Eval(v).(type) { case int: return float64(v) case float64: return v } panic("can't convert to float") } // Int ensures a value is int. // It doesn't convert float to int. func Int(v any) int { if v, ok := Eval(v).(int); ok { return v } panic("not an int") } // ----------------------------------------------------------------------------- func cmpInt(op token.Token, x, y int) bool { switch op { case token.EQ, token.ASSIGN: return x == y case token.NE, token.BIDIARROW: return x != y case token.LT: return x < y case token.LE: return x <= y case token.GT: return x > y case token.GE: return x >= y } panic("invalid int comparison operator: " + op.String()) } func cmpFloat(op token.Token, x, y float64) bool { switch op { case token.EQ, token.ASSIGN: return x == y case token.NE, token.BIDIARROW: return x != y case token.LT: return x < y case token.LE: return x <= y case token.GT: return x > y case token.GE: return x >= y } panic("invalid float comparison operator: " + op.String()) } func cmpString(op token.Token, x, y string) bool { switch op { case token.EQ, token.ASSIGN: return x == y case token.NE, token.BIDIARROW: return x != y case token.LT: return x < y case token.LE: return x <= y case token.GT: return x > y case token.GE: return x >= y } panic("invalid string comparison operator: " + op.String()) } func cmpBool(op token.Token, x, y bool) bool { switch op { case token.EQ, token.ASSIGN: return x == y case token.NE, token.BIDIARROW: return x != y } panic("invalid bool comparison operator: " + op.String()) } // Compare compares two values. func Compare(op token.Token, x, y any) bool { x, y = Eval(x), Eval(y) switch x := x.(type) { case int: switch y := y.(type) { case int: return cmpInt(op, x, y) case float64: return cmpFloat(op, float64(x), y) } case float64: switch y := y.(type) { case int: return cmpFloat(op, x, float64(y)) case float64: return cmpFloat(op, x, y) } case string: if y, ok := y.(string); ok { return cmpString(op, x, y) } case bool: if y, ok := y.(bool); ok { return cmpBool(op, x, y) } } panic("compare: invalid operation") } // ----------------------------------------------------------------------------- func mopInt(op token.Token, x, y int) int { switch op { case token.ADD: return x + y case token.SUB: return x - y case token.MUL: return x * y case token.QUO: return x / y case token.REM: return x % y } panic("invalid int operator: " + op.String()) } func mopFloat(op token.Token, x, y float64) float64 { switch op { case token.ADD: return x + y case token.SUB: return x - y case token.MUL: return x * y case token.QUO: return x / y } panic("invalid float operator: " + op.String()) } func mopString(op token.Token, x, y string) string { switch op { case token.ADD: return x + y } panic("invalid string operator: " + op.String()) } // MathOp does a math operation. func MathOp(op token.Token, x, y any) any { x, y = Eval(x), Eval(y) switch x := x.(type) { case int: switch y := y.(type) { case int: return mopInt(op, x, y) case float64: return mopFloat(op, float64(x), y) } case float64: switch y := y.(type) { case int: return mopFloat(op, x, float64(y)) case float64: return mopFloat(op, x, y) } case string: if y, ok := y.(string); ok { return mopString(op, x, y) } } panic("mathOp: invalid operation") } // ----------------------------------------------------------------------------- // LogicOp does a logic operation. func LogicOp(op token.Token, x, y any) bool { x, y = Eval(x), Eval(y) switch x := x.(type) { case bool: if y, ok := y.(bool); ok { switch op { case token.LAND: return x && y case token.LOR: return x || y } } } panic("logicOp: invalid operation") } // ----------------------------------------------------------------------------- // UnaryOp does a unary operation. func UnaryOp(op token.Token, x any) any { x = Eval(x) switch x := x.(type) { case int: switch op { case token.SUB: return -x case token.ADD: return x } case float64: switch op { case token.SUB: return -x case token.ADD: return x } case bool: if op == token.NOT { return !x } } panic("unaryOp: invalid operation") } // ----------------------------------------------------------------------------- ================================================ FILE: x/build/_testdata/hello/hello.expect ================================================ package main import "fmt" func main() { fmt.Println("XGo") } ================================================ FILE: x/build/_testdata/hello/main.xgo ================================================ println "XGo" ================================================ FILE: x/build/_testdata/multi/Rect.gox ================================================ var ( BaseClass Width, Height float64 *AggClass ) type BaseClass struct { x int y int } type AggClass struct{} func Area() float64 { return Width * Height } ================================================ FILE: x/build/_testdata/multi/main.xgo ================================================ rc := &Rect{Width: 100, Height: 200} println rc ================================================ FILE: x/build/_testdata/multi/multi.expect ================================================ package main import "fmt" type BaseClass struct { x int y int } type AggClass struct { } type Rect struct { BaseClass Width float64 Height float64 *AggClass } func (this *Rect) Area() float64 { return this.Width * this.Height } func main() { rc := &Rect{Width: 100, Height: 200} fmt.Println(rc) } ================================================ FILE: x/build/_testdata/pkg/pkg.expect ================================================ package pkg import "fmt" func Hello() { fmt.Println("XGo") } ================================================ FILE: x/build/_testdata/pkg/pkg.xgo ================================================ package pkg func Hello() { println "XGo" } ================================================ FILE: x/build/build.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package build import ( "bytes" "fmt" goast "go/ast" "go/types" "path/filepath" "github.com/goplus/gogen" "github.com/goplus/gogen/packages" "github.com/goplus/mod/modfile" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/parser/fsx/memfs" "github.com/goplus/xgo/token" ) type Class = cl.Class var ( projects = make(map[string]*cl.Project) ) func RegisterClassFileType(ext string, class string, works []*Class, pkgPaths ...string) { cls := &cl.Project{ Ext: ext, Class: class, Works: works, PkgPaths: pkgPaths, } if ext != "" { projects[ext] = cls } for _, w := range works { projects[w.Ext] = cls } } func init() { RegisterClassFileType(".gmx", "Game", []*Class{{Ext: ".spx", Class: "Sprite"}}, "github.com/goplus/spx", "math") } type Package struct { Fset *token.FileSet Pkg *gogen.Package } func (p *Package) ToSource() ([]byte, error) { var buf bytes.Buffer if err := p.Pkg.WriteTo(&buf); err != nil { return nil, err } return buf.Bytes(), nil } func (p *Package) ToAst() *goast.File { return p.Pkg.ASTFile() } func ClassKind(fname string) (isProj, ok bool) { ext := modfile.ClassExt(fname) switch ext { case ".gmx": return true, true case ".spx": return fname == "main.spx", true default: if c, ok := projects[ext]; ok { for _, w := range c.Works { if w.Ext == ext { if ext != c.Ext || fname != "main"+ext { return false, true } break } } return true, true } } return } type Context struct { impl types.Importer fset *token.FileSet LoadConfig func(*cl.Config) } func Default() *Context { fset := token.NewFileSet() impl := packages.NewImporter(fset) return NewContext(impl, fset) } func NewContext(impl types.Importer, fset *token.FileSet) *Context { if fset == nil { fset = token.NewFileSet() } return &Context{impl: impl, fset: fset} } func (c *Context) Import(path string) (*types.Package, error) { return c.impl.Import(path) } func (c *Context) ParseDir(dir string) (*Package, error) { pkgs, err := parser.ParseDirEx(c.fset, dir, parser.Config{ ClassKind: ClassKind, }) if err != nil { return nil, err } return c.loadPackage(dir, pkgs) } func (c *Context) ParseFSDir(fs parser.FileSystem, dir string) (*Package, error) { pkgs, err := parser.ParseFSDir(c.fset, fs, dir, parser.Config{ ClassKind: ClassKind, }) if err != nil { return nil, err } return c.loadPackage(dir, pkgs) } func (c *Context) ParseFile(file string, src any) (*Package, error) { fs, err := memfs.File(file, src) if err != nil { return nil, err } return c.ParseFSDir(fs, filepath.Dir(file)) } func (c *Context) loadPackage(srcDir string, pkgs map[string]*ast.Package) (*Package, error) { mainPkg, ok := pkgs["main"] if !ok { for _, v := range pkgs { mainPkg = v break } } conf := &cl.Config{Fset: c.fset} conf.Importer = c conf.LookupClass = func(ext string) (c *cl.Project, ok bool) { c, ok = projects[ext] return } if c.LoadConfig != nil { c.LoadConfig(conf) } out, err := cl.NewPackage("", mainPkg, conf) if err != nil { return nil, err } return &Package{c.fset, out}, nil } func (ctx *Context) BuildFile(filename string, src any) (data []byte, err error) { defer func() { r := recover() if r != nil { err = fmt.Errorf("compile %v failed. %v", filename, r) } }() pkg, err := ctx.ParseFile(filename, src) if err != nil { return nil, err } return pkg.ToSource() } func (ctx *Context) BuildFSDir(fs parser.FileSystem, dir string) (data []byte, err error) { defer func() { r := recover() if r != nil { err = fmt.Errorf("compile %v failed. %v", dir, err) } }() pkg, err := ctx.ParseFSDir(fs, dir) if err != nil { return nil, err } return pkg.ToSource() } func (ctx *Context) BuildDir(dir string) (data []byte, err error) { defer func() { r := recover() if r != nil { err = fmt.Errorf("compile %v failed. %v", dir, err) } }() pkg, err := ctx.ParseDir(dir) if err != nil { return nil, err } return pkg.ToSource() } ================================================ FILE: x/build/build_test.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package build_test import ( "bytes" "fmt" "go/printer" "go/types" "os" "path" "path/filepath" "strings" "testing" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/parser/fsx" "github.com/goplus/xgo/x/build" ) var ( ctx = build.Default() ) func init() { cl.SetDebug(cl.FlagNoMarkAutogen) ctx.LoadConfig = func(cfg *cl.Config) { cfg.NoFileLine = true } build.RegisterClassFileType(".tspx", "MyGame", []*build.Class{ {Ext: ".tspx", Class: "Sprite"}, }, "github.com/goplus/xgo/cl/internal/spx") build.RegisterClassFileType("_yap.gox", "App", nil, "github.com/goplus/yap") } func gopClTest(t *testing.T, gopcode any, expected string) { gopClTestEx(t, "main.xgo", gopcode, expected) } func gopClTestEx(t *testing.T, filename string, gopcode any, expected string) { data, err := ctx.BuildFile(filename, gopcode) if err != nil { t.Fatalf("build gop error: %v", err) } if string(data) != expected { fmt.Println("build gop error:") fmt.Println(string(data)) t.Fail() } } func testKind(t *testing.T, name string, proj, class bool) { isProj, ok := build.ClassKind(name) if isProj != proj || ok != class { t.Fatal("check classkind failed", name, isProj, ok) } } func TestKind(t *testing.T) { testKind(t, "Cat.gox", false, false) testKind(t, "Cat.spx", false, true) testKind(t, "main.spx", true, true) testKind(t, "main.gmx", true, true) testKind(t, "Cat.tspx", false, true) testKind(t, "main.tspx", true, true) testKind(t, "blog_yap.gox", true, true) } func TestXGo(t *testing.T) { var src = ` println "XGo" ` var expect = `package main import "fmt" func main() { fmt.Println("XGo") } ` gopClTest(t, src, expect) gopClTest(t, []byte(src), expect) gopClTest(t, bytes.NewBufferString(src), expect) gopClTestEx(t, `./_testdata/hello/main.xgo`, nil, expect) f, err := os.Open("./_testdata/hello/main.xgo") if err != nil { t.Fatal("open failed", err) } defer f.Close() gopClTest(t, f, expect) } func TestGox(t *testing.T) { gopClTestEx(t, "Rect.gox", ` println "XGo" `, `package main import "fmt" type Rect struct { } func (this *Rect) Main() { fmt.Println("XGo") } func main() { new(Rect).Main() } `) gopClTestEx(t, "Rect.gox", ` var ( Buffer v int ) type Buffer struct { buf []byte } println "XGo" `, `package main import "fmt" type Buffer struct { buf []byte } type Rect struct { Buffer v int } func (this *Rect) Main() { fmt.Println("XGo") } func main() { new(Rect).Main() } `) gopClTestEx(t, "Rect.gox", ` var ( *Buffer v int ) type Buffer struct { buf []byte } println "XGo" `, `package main import "fmt" type Buffer struct { buf []byte } type Rect struct { *Buffer v int } func (this *Rect) Main() { fmt.Println("XGo") } func main() { new(Rect).Main() } `) gopClTestEx(t, "Rect.gox", ` import "bytes" var ( *bytes.Buffer v int ) println "XGo" `, `package main import ( "bytes" "fmt" ) type Rect struct { *bytes.Buffer v int } func (this *Rect) Main() { fmt.Println("XGo") } func main() { new(Rect).Main() } `) gopClTestEx(t, "Rect.gox", ` import "bytes" var ( bytes.Buffer v int ) println "XGo" `, `package main import ( "bytes" "fmt" ) type Rect struct { bytes.Buffer v int } func (this *Rect) Main() { fmt.Println("XGo") } func main() { new(Rect).Main() } `) } func TestBig(t *testing.T) { gopClTest(t, ` a := 1/2r println a+1/2r `, `package main import ( "fmt" "github.com/qiniu/x/xgo/ng" "math/big" ) func main() { a := ng.Bigrat_Init__2(big.NewRat(1, 2)) fmt.Println((ng.Bigrat).XGo_Add(a, ng.Bigrat_Init__2(big.NewRat(1, 2)))) } `) } func TestIoxLines(t *testing.T) { gopClTest(t, ` import "io" var r io.Reader for line <- lines(r) { println line } `, `package main import ( "fmt" "github.com/qiniu/x/osx" "io" ) var r io.Reader func main() { for _xgo_it := osx.Lines(r).XGo_Enum(); ; { var _xgo_ok bool line, _xgo_ok := _xgo_it.Next() if !_xgo_ok { break } fmt.Println(line) } } `) } func TestErrorWrap(t *testing.T) { gopClTest(t, ` import ( "strconv" ) func add(x, y string) (int, error) { return strconv.Atoi(x)? + strconv.Atoi(y)?, nil } func addSafe(x, y string) int { return strconv.Atoi(x)?:0 + strconv.Atoi(y)?:0 } println add("100", "23")! sum, err := add("10", "abc") println sum, err println addSafe("10", "abc") `, `package main import ( "fmt" "github.com/qiniu/x/errors" "strconv" ) func add(x string, y string) (int, error) { var _autoGo_1 int { var _xgo_err error _autoGo_1, _xgo_err = strconv.Atoi(x) if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "strconv.Atoi(x)", "main.xgo", 7, "main.add") return 0, _xgo_err } goto _autoGo_2 _autoGo_2: } var _autoGo_3 int { var _xgo_err error _autoGo_3, _xgo_err = strconv.Atoi(y) if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "strconv.Atoi(y)", "main.xgo", 7, "main.add") return 0, _xgo_err } goto _autoGo_4 _autoGo_4: } return _autoGo_1 + _autoGo_3, nil } func addSafe(x string, y string) int { return func() (_xgo_ret int) { var _xgo_err error _xgo_ret, _xgo_err = strconv.Atoi(x) if _xgo_err != nil { return 0 } return }() + func() (_xgo_ret int) { var _xgo_err error _xgo_ret, _xgo_err = strconv.Atoi(y) if _xgo_err != nil { return 0 } return }() } func main() { fmt.Println(func() (_xgo_ret int) { var _xgo_err error _xgo_ret, _xgo_err = add("100", "23") if _xgo_err != nil { _xgo_err = errors.NewFrame(_xgo_err, "add(\"100\", \"23\")", "main.xgo", 14, "main.main") panic(_xgo_err) } return }()) sum, err := add("10", "abc") fmt.Println(sum, err) fmt.Println(addSafe("10", "abc")) } `) } func TestSpx(t *testing.T) { gopClTestEx(t, "main.tspx", `println "hi"`, `package main import ( "fmt" "github.com/goplus/xgo/cl/internal/spx" ) type MyGame struct { spx.MyGame } func (this *MyGame) MainEntry() { fmt.Println("hi") } func (this *MyGame) Main() { spx.Gopt_MyGame_Main(this) } func main() { new(MyGame).Main() } `) gopClTestEx(t, "Cat.tspx", `println "hi"`, `package main import ( "fmt" "github.com/goplus/xgo/cl/internal/spx" ) type Cat struct { spx.Sprite *MyGame } type MyGame struct { spx.MyGame } func (this *MyGame) Main() { spx.Gopt_MyGame_Main(this) } func (this *Cat) Main() { fmt.Println("hi") } func main() { new(MyGame).Main() } `) } func testFromDir(t *testing.T, relDir string) { dir, err := os.Getwd() if err != nil { t.Fatal("Getwd failed:", err) } dir = path.Join(dir, relDir) fis, err := os.ReadDir(dir) if err != nil { t.Fatal("ReadDir failed:", err) } for _, fi := range fis { name := fi.Name() if strings.HasPrefix(name, "_") { continue } t.Run(name, func(t *testing.T) { testFrom(t, name, dir+"/"+name) }) } } func testFrom(t *testing.T, name, dir string) { data, err := ctx.BuildDir(dir) if err != nil { t.Fatal("BuildDir failed:", err) } if chk, err := os.ReadFile(filepath.Join(dir, name+".expect")); err == nil { if !bytes.Equal(data, chk) { t.Fatalf("-- %v output check error --\n%v\n--\n%v", name, string(data), string(chk)) } } } func TestFromTestdata(t *testing.T) { testFromDir(t, "./_testdata") } func TestFS(t *testing.T) { var expect = []byte(`package main import "fmt" func main() { fmt.Println("XGo") } `) data, err := ctx.BuildFSDir(fsx.Local, "./_testdata/hello") if err != nil { t.Fatal("build fs dir failed", err) } if !bytes.Equal(data, expect) { t.Fatal("build fs data failed", string(data)) } } func TestAst(t *testing.T) { var expect = []byte(`package main import "fmt" func main() { fmt.Println("XGo") } `) pkg, err := ctx.ParseFSDir(fsx.Local, "./_testdata/hello") if err != nil { t.Fatal("parser fs dir failed", err) } var buf bytes.Buffer err = printer.Fprint(&buf, pkg.Fset, pkg.ToAst()) if err != nil { t.Fatal("fprint ast error", err) } if !bytes.Equal(buf.Bytes(), expect) { t.Fatal("build ast data failed", buf.String()) } } func TestError(t *testing.T) { _, err := ctx.BuildFile("main.xgo", "bad code") if err == nil { t.Fatal("BuildFile: no error?") } _, err = ctx.BuildDir("./demo/nofound") if err == nil { t.Fatal("BuildDir: no error?") } _, err = ctx.BuildFSDir(fsx.Local, "./demo/nofound") if err == nil { t.Fatal("BuildDir: no error?") } _, err = ctx.BuildFile("main.xgo", "func main()") if err == nil { t.Fatal("BuildFile: no error?") } _, err = ctx.ParseFile("main.xgo", 123) if err == nil { t.Fatal("ParseFile: no error?") } _, err = ctx.ParseFile("./demo/nofound/main.xgo", nil) if err == nil { t.Fatal("ParseFile: no error?") } } type emptyImporter struct { } func (i *emptyImporter) Import(path string) (*types.Package, error) { return nil, fmt.Errorf("not found %v", path) } func TestContext(t *testing.T) { ctx := build.NewContext(&emptyImporter{}, nil) _, err := ctx.BuildFile("main.xgo", `import "fmt"; fmt.Println "XGo"`) if err == nil { t.Fatal("BuildFile: no error?") } } ================================================ FILE: x/fakenet/conn.go ================================================ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package fakenet import ( "io" "net" "sync" "time" ) // NewConn returns a net.Conn built on top of the supplied reader and writer. // It decouples the read and write on the conn from the underlying stream // to enable Close to abort ones that are in progress. // It's primary use is to fake a network connection from stdin and stdout. func NewConn(name string, in io.ReadCloser, out io.WriteCloser) net.Conn { c := &fakeConn{ name: name, reader: newFeeder(in.Read), writer: newFeeder(out.Write), in: in, out: out, } go c.reader.run() go c.writer.run() return c } type fakeConn struct { name string reader *connFeeder writer *connFeeder in io.ReadCloser out io.WriteCloser } type fakeAddr string // connFeeder serializes calls to the source function (io.Reader.Read or // io.Writer.Write) by delegating them to a channel. This also allows calls to // be intercepted when the connection is closed, and cancelled early if the // connection is closed while the calls are still outstanding. type connFeeder struct { source func([]byte) (int, error) input chan []byte result chan feedResult mu sync.Mutex closed bool done chan struct{} } type feedResult struct { n int err error } func (c *fakeConn) Close() error { c.reader.close() c.writer.close() c.in.Close() c.out.Close() return nil } func (c *fakeConn) Read(b []byte) (n int, err error) { return c.reader.do(b) } func (c *fakeConn) Write(b []byte) (n int, err error) { return c.writer.do(b) } func (c *fakeConn) LocalAddr() net.Addr { return fakeAddr(c.name) } func (c *fakeConn) RemoteAddr() net.Addr { return fakeAddr(c.name) } func (c *fakeConn) SetDeadline(t time.Time) error { return nil } func (c *fakeConn) SetReadDeadline(t time.Time) error { return nil } func (c *fakeConn) SetWriteDeadline(t time.Time) error { return nil } func (a fakeAddr) Network() string { return "fake" } func (a fakeAddr) String() string { return string(a) } func newFeeder(source func([]byte) (int, error)) *connFeeder { return &connFeeder{ source: source, input: make(chan []byte), result: make(chan feedResult), done: make(chan struct{}), } } func (f *connFeeder) close() { f.mu.Lock() if !f.closed { f.closed = true close(f.done) } f.mu.Unlock() } func (f *connFeeder) do(b []byte) (n int, err error) { // send the request to the worker select { case f.input <- b: case <-f.done: return 0, io.EOF } // get the result from the worker select { case r := <-f.result: return r.n, r.err case <-f.done: return 0, io.EOF } } func (f *connFeeder) run() { var b []byte for { // wait for an input request select { case b = <-f.input: case <-f.done: return } // invoke the underlying method n, err := f.source(b) // send the result back to the requester select { case f.result <- feedResult{n: n, err: err}: case <-f.done: return } } } ================================================ FILE: x/format/README.md ================================================ XGo Code Style ====== TODO ## Specification ### No package main ```go package main func f() { } ``` will be converted into: ```go func f() { } ``` ### No func main ```go package main func main() { a := 0 } ``` will be converted into: ```go a := 0 ``` ### Replace fmt.Print to builtin ```go import "fmt" n, err := fmt.Println("Hello world") ``` will be converted into: ```go n, err := echo("Hello world") ``` Note: * Convert `fmt.Errorf => errorf` * Convert `fmt.Fprint => fprint` * Convert `fmt.Fprintf => fprintf` * Convert `fmt.Fprintln => fprintln` * Convert `fmt.Print` => `print` * Convert `fmt.Printf` => `printf` * Convert `fmt.Println` => `echo` * Convert `fmt.Sprint => sprint` * Convert `fmt.Sprintf => sprintf` * Convert `fmt.Sprintln => sprintln` ### Command style first ```go import "fmt" fmt.Println() fmt.Println(fmt.Println("Hello world")) ``` will be converted into: ```go echo echo echo("Hello world") ``` Note: * Only the outermost function call statement is converted into command style. So `fmt.Println(fmt.Println("Hello world"))` is converted into `echo echo("Hello world")`, not `echo echo "Hello world"`. ### pkg.Fncall starting with lowercase ```go import "math" echo math.Sin(math.Pi/3) ``` will be converted into: ```go echo math.sin(math.Pi/3) ``` ### Funclit of params convert to lambda in fncall (skip named results) ```go echo(demo(func(n int) int { return n+100 })) echo(demo(func(n int) (v int) { return n+100 })) onStart(func() { echo("start") }) ``` will be converted into: ``` echo demo(n => n + 100) echo demo(func(n int) (v int) { return n + 100 }) onStart => { echo "start" } ``` ================================================ FILE: x/format/_testdata/collection/format.expect ================================================ // We often need our programs to perform operations on // collections of data, like selecting all items that // satisfy a given predicate or mapping all items to a new // collection with a custom function. // In some languages it's idiomatic to use [generic](http://en.wikipedia.org/wiki/Generic_programming) // data structures and algorithms. Go does not support // generics; in Go it's common to provide collection // functions if and when they are specifically needed for // your program and data types. // Here are some example collection functions for slices // of `strings`. You can use these examples to build your // own functions. Note that in some cases it may be // clearest to just inline the collection-manipulating // code directly, instead of creating and calling a // helper function. import ( "strings" ) // Index returns the first index of the target string `t`, or // -1 if no match is found. func Index(vs []string, t string) int { for i, v := range vs { if v == t { return i } } return -1 } // Include returns `true` if the target string t is in the // slice. func Include(vs []string, t string) bool { return Index(vs, t) >= 0 } // Any returns `true` if one of the strings in the slice // satisfies the predicate `f`. func Any(vs []string, f func(string) bool) bool { for _, v := range vs { if f(v) { return true } } return false } // All returns `true` if all of the strings in the slice // satisfy the predicate `f`. func All(vs []string, f func(string) bool) bool { for _, v := range vs { if !f(v) { return false } } return true } // Filter returns a new slice containing all strings in the // slice that satisfy the predicate `f`. func Filter(vs []string, f func(string) bool) []string { vsf := make([]string, 0) for _, v := range vs { if f(v) { vsf = append(vsf, v) } } return vsf } // Map returns a new slice containing the results of applying // the function `f` to each string in the original slice. func Map(vs []string, f func(string) string) []string { vsm := make([]string, len(vs)) for i, v := range vs { vsm[i] = f(v) } return vsm } // Here we try out our various collection functions. var strs = []string{"peach", "apple", "pear", "plum"} echo Index(strs, "pear") echo Include(strs, "grape") echo Any(strs, v => strings.hasPrefix(v, "p")) echo All(strs, v => strings.hasPrefix(v, "p")) echo Filter(strs, v => strings.contains(v, "e")) // The above examples all used anonymous functions, // but you can also use named functions of the correct // type. echo Map(strs, strings.ToUpper) ================================================ FILE: x/format/_testdata/collection/index.xgo ================================================ // We often need our programs to perform operations on // collections of data, like selecting all items that // satisfy a given predicate or mapping all items to a new // collection with a custom function. // In some languages it's idiomatic to use [generic](http://en.wikipedia.org/wiki/Generic_programming) // data structures and algorithms. Go does not support // generics; in Go it's common to provide collection // functions if and when they are specifically needed for // your program and data types. // Here are some example collection functions for slices // of `strings`. You can use these examples to build your // own functions. Note that in some cases it may be // clearest to just inline the collection-manipulating // code directly, instead of creating and calling a // helper function. package main import ( "fmt" "strings" ) // Index returns the first index of the target string `t`, or // -1 if no match is found. func Index(vs []string, t string) int { for i, v := range vs { if v == t { return i } } return -1 } // Include returns `true` if the target string t is in the // slice. func Include(vs []string, t string) bool { return Index(vs, t) >= 0 } // Any returns `true` if one of the strings in the slice // satisfies the predicate `f`. func Any(vs []string, f func(string) bool) bool { for _, v := range vs { if f(v) { return true } } return false } // All returns `true` if all of the strings in the slice // satisfy the predicate `f`. func All(vs []string, f func(string) bool) bool { for _, v := range vs { if !f(v) { return false } } return true } // Filter returns a new slice containing all strings in the // slice that satisfy the predicate `f`. func Filter(vs []string, f func(string) bool) []string { vsf := make([]string, 0) for _, v := range vs { if f(v) { vsf = append(vsf, v) } } return vsf } // Map returns a new slice containing the results of applying // the function `f` to each string in the original slice. func Map(vs []string, f func(string) string) []string { vsm := make([]string, len(vs)) for i, v := range vs { vsm[i] = f(v) } return vsm } func main() { // Here we try out our various collection functions. var strs = []string{"peach", "apple", "pear", "plum"} fmt.Println(Index(strs, "pear")) fmt.Println(Include(strs, "grape")) fmt.Println(Any(strs, func(v string) bool { return strings.HasPrefix(v, "p") })) fmt.Println(All(strs, func(v string) bool { return strings.HasPrefix(v, "p") })) fmt.Println(Filter(strs, func(v string) bool { return strings.Contains(v, "e") })) // The above examples all used anonymous functions, // but you can also use named functions of the correct // type. fmt.Println(Map(strs, strings.ToUpper)) } ================================================ FILE: x/format/_testdata/gopsyntax/format.expect ================================================ a := [1, 2] plot x => x * x onStart x => { println x } for i in :10 { } println [x*x for x in :10] print f()?:10 ================================================ FILE: x/format/_testdata/gopsyntax/index.xgo ================================================ a := [1, 2] plot x => x * x onStart x => { println x } for i <- :10 { } println [x*x for x <- :10] print f()?:10 ================================================ FILE: x/format/_testdata/syntax/format.expect ================================================ func f(...T) { } a++ arr := [...]int{1: 3} m := make(map[string]chan interface{}) for range arr[1:] { foo: *(p) = 1 echo f(arr...) m["a"] <- true } for i := 0; i < 10; i++ { var t *T if false { } else { } select { case <-m["a"]: } switch { case i == 10: } switch t.(type) { case int: defer f() go f().println("Hi") } } ================================================ FILE: x/format/_testdata/syntax/index.xgo ================================================ import "fmt" func f(...T) { } a++ arr := [...]int{1: 3} m := make(map[string]chan interface{}) for range arr[1:] { foo: *(p) = 1 fmt.Println(f(arr...)) m["a"] <- true } for i := 0; i < 10; i++ { var t *T if false { } else { } select { case <-m["a"]: } switch { case i == 10: } switch t.(type) { case int: defer f() go f().Println("Hi") } } ================================================ FILE: x/format/format.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package format import ( "github.com/goplus/xgo/ast" "github.com/goplus/xgo/token" ) // ----------------------------------------------------------------------------- var printFuncs = [][2]string{ {"Errorf", "errorf"}, {"Print", "print"}, {"Printf", "printf"}, {"Println", "println"}, {"Fprint", "fprint"}, {"Fprintf", "fprintf"}, {"Fprintln", "fprintln"}, {"Sprint", "sprint"}, {"Sprintf", "sprintf"}, {"Sprintln", "sprintln"}, } func fmtToBuiltin(ctx *importCtx, sel *ast.Ident, ref *ast.Expr) bool { if ctx.pkgPath == "fmt" { for _, fns := range printFuncs { if fns[0] == sel.Name || fns[1] == sel.Name { name := fns[1] if name == "println" { name = "echo" } *ref = &ast.Ident{NamePos: sel.NamePos, Name: name} return true } } } return false } // ----------------------------------------------------------------------------- func commandStyleFirst(v *ast.CallExpr) { switch v.Fun.(type) { case *ast.Ident, *ast.SelectorExpr: if v.NoParenEnd == token.NoPos { v.NoParenEnd = v.Rparen } } } // ----------------------------------------------------------------------------- func fncallStartingLowerCase(v *ast.CallExpr) { switch fn := v.Fun.(type) { case *ast.SelectorExpr: startWithLowerCase(fn.Sel) } } // ----------------------------------------------------------------------------- func funcLitToLambdaExpr(v *ast.FuncLit, ret *ast.Expr) { nres, named := checkResult(v.Type.Results) if len(named) > 0 { return } var lsh []*ast.Ident for _, p := range v.Type.Params.List { if p.Names == nil { lsh = append(lsh, ast.NewIdent("_")) } else { lsh = append(lsh, p.Names...) } } if len(v.Body.List) == 1 { if stmt, ok := v.Body.List[0].(*ast.ReturnStmt); ok && len(stmt.Results) == nres { *ret = &ast.LambdaExpr{First: v.Pos(), Last: v.Pos(), Lhs: lsh, Rhs: stmt.Results, LhsHasParen: len(lsh) > 1, RhsHasParen: len(stmt.Results) > 1} return } } *ret = &ast.LambdaExpr2{Lhs: lsh, Body: v.Body, LhsHasParen: len(lsh) > 1} } func checkResult(v *ast.FieldList) (nres int, named []*ast.Ident) { if v != nil { for _, f := range v.List { if f.Names == nil { nres++ } else { nres += len(f.Names) named = append(named, f.Names...) } } } return } // ----------------------------------------------------------------------------- ================================================ FILE: x/format/gopstyle.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package format import ( "bytes" "go/types" "path" "strconv" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/format" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/token" ) // ----------------------------------------------------------------------------- func GopstyleSource(src []byte, filename ...string) (ret []byte, err error) { var fname string if filename != nil { fname = filename[0] } fset := token.NewFileSet() var f *ast.File if f, err = parser.ParseFile(fset, fname, src, parser.ParseComments); err == nil { Gopstyle(f) var buf bytes.Buffer if err = format.Node(&buf, fset, f); err == nil { ret = buf.Bytes() } } return } // ----------------------------------------------------------------------------- func Gopstyle(file *ast.File) { if identEqual(file.Name, "main") { file.NoPkgDecl = true } if idx, fn := findFuncDecl(file.Decls, "main"); idx >= 0 { last := len(file.Decls) - 1 if idx == last { file.ShadowEntry = fn // TODO: idx != last: swap main func to last // TODO: should also swap file.Comments /* fn := file.Decls[idx] copy(file.Decls[idx:], file.Decls[idx+1:]) file.Decls[last] = fn */ } } formatFile(file) } func findFuncDecl(decls []ast.Decl, name string) (int, *ast.FuncDecl) { for i, decl := range decls { if fn, ok := decl.(*ast.FuncDecl); ok { if identEqual(fn.Name, name) { return i, fn } } } return -1, nil } func findDecl(decls []ast.Decl, v ast.Decl) int { for i, decl := range decls { if decl == v { return i } } return -1 } func findSpec(specs []ast.Spec, v ast.Spec) int { for i, spec := range specs { if spec == v { return i } } return -1 } func deleteDecl(decls []ast.Decl, v ast.Decl) []ast.Decl { if idx := findDecl(decls, v); idx >= 0 { decls = append(decls[:idx], decls[idx+1:]...) } return decls } func deleteSpec(specs []ast.Spec, v ast.Spec) []ast.Spec { if idx := findSpec(specs, v); idx >= 0 { specs = append(specs[:idx], specs[idx+1:]...) } return specs } func startWithLowerCase(v *ast.Ident) { if c := v.Name[0]; c >= 'A' && c <= 'Z' { v.Name = string(c+('a'-'A')) + v.Name[1:] } } func identEqual(v *ast.Ident, name string) bool { return v != nil && v.Name == name } func toString(l *ast.BasicLit) string { if l.Kind == token.STRING { s, err := strconv.Unquote(l.Value) if err == nil { return s } } panic("TODO: toString - convert ast.BasicLit to string failed") } // ----------------------------------------------------------------------------- type importCtx struct { pkgPath string decl *ast.GenDecl spec *ast.ImportSpec isUsed bool } type formatCtx struct { imports map[string]*importCtx scope *types.Scope } func (ctx *formatCtx) insert(name string) { o := types.NewParam(token.NoPos, nil, name, types.Typ[types.UntypedNil]) ctx.scope.Insert(o) } func (ctx *formatCtx) enterBlock() *types.Scope { old := ctx.scope ctx.scope = types.NewScope(old, token.NoPos, token.NoPos, "") return old } func (ctx *formatCtx) leaveBlock(old *types.Scope) { ctx.scope = old } func formatFile(file *ast.File) { var funcs []*ast.FuncDecl ctx := &formatCtx{ imports: make(map[string]*importCtx), scope: types.NewScope(nil, token.NoPos, token.NoPos, ""), } for _, decl := range file.Decls { switch v := decl.(type) { case *ast.FuncDecl: // delay the process, because package level vars need to be processed first. funcs = append(funcs, v) case *ast.GenDecl: switch v.Tok { case token.IMPORT: for _, item := range v.Specs { var spec = item.(*ast.ImportSpec) var pkgPath = toString(spec.Path) var name string if spec.Name == nil { name = path.Base(pkgPath) // TODO: open pkgPath to get pkgName } else { name = spec.Name.Name if name == "." || name == "_" { continue } } ctx.imports[name] = &importCtx{pkgPath: pkgPath, decl: v, spec: spec} } default: formatGenDecl(ctx, v) } } } for _, fn := range funcs { formatFuncDecl(ctx, fn) } for _, imp := range ctx.imports { if imp.pkgPath == "fmt" && !imp.isUsed { if len(imp.decl.Specs) == 1 { file.Decls = deleteDecl(file.Decls, imp.decl) } else { imp.decl.Specs = deleteSpec(imp.decl.Specs, imp.spec) } } } } func formatGenDecl(ctx *formatCtx, v *ast.GenDecl) { switch v.Tok { case token.VAR, token.CONST: for _, item := range v.Specs { spec := item.(*ast.ValueSpec) formatType(ctx, spec.Type, &spec.Type) formatExprs(ctx, spec.Values) for _, name := range spec.Names { ctx.insert(name.Name) } } case token.TYPE: for _, item := range v.Specs { spec := item.(*ast.TypeSpec) formatType(ctx, spec.Type, &spec.Type) } } } func formatFuncDecl(ctx *formatCtx, v *ast.FuncDecl) { formatFuncType(ctx, v.Type) formatBlockStmt(ctx, v.Body) } /* func fillVarCtx(ctx *formatCtx, spec *ast.ValueSpec) { for _, name := range spec.Names { ctx.scp.addVar(name.Name) } } type scope struct { vars []map[string]bool } func newScope() *scope { return &scope{ vars: []map[string]bool{make(map[string]bool)}, } } func (s *scope) addVar(name string) { s.vars[len(s.vars)-1][name] = true } func (s *scope) containsVar(name string) bool { for _, m := range s.vars { if m[name] { return true } } return false } func (s *scope) enterScope() { s.vars = append(s.vars, make(map[string]bool)) } func (s *scope) exitScope() { s.vars = s.vars[:len(s.vars)-1] } */ // ----------------------------------------------------------------------------- ================================================ FILE: x/format/gopstyle_test.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package format import ( "testing" ) func testFormat(t *testing.T, name string, src, expect string) { t.Run(name, func(t *testing.T) { result, err := GopstyleSource([]byte(src), name) if err != nil { t.Fatal("format.Source failed:", err) } if ret := string(result); ret != expect { t.Fatalf("%s => Expect:\n%s\n=> Got:\n%s\n", name, expect, ret) } }) } // ----------------------------------------------------------------------------- func TestMain(t *testing.T) { testFormat(t, "hello world 1", `package main import "fmt" // this is main func main() { // say hello fmt.Println("Hello world") } `, `// this is main // say hello echo "Hello world" `) testFormat(t, "hello world 2", `package main import "fmt" // this is main func main() { // say hello fmt.Println("Hello world") } func f() { } `, `// this is main func main() { // say hello echo "Hello world" } func f() { } `) testFormat(t, "hello world 3", `package main import "fmt" func f() { } `, `func f() { } `) } // ----------------------------------------------------------------------------- func TestPrint(t *testing.T) { testFormat(t, "print", `package main import "fmt" func f() { fmt.Print("hello") } `, `func f() { print "hello" } `) } func TestPrintf(t *testing.T) { testFormat(t, "print", `package main import "fmt" func f() { fmt.Printf("hello") } `, `func f() { printf "hello" } `) } func TestPrintln(t *testing.T) { testFormat(t, "print", `package main import "fmt" func f() { fmt.Println("hello") } `, `func f() { echo "hello" } `) } func TestPrintlnGroup(t *testing.T) { testFormat(t, "print", `package main import ( "fmt" ) func f() { fmt.Println("hello") } `, `func f() { echo "hello" } `) } func TestPrintlnWithOtherFmtCalls(t *testing.T) { testFormat(t, "print", `package main import . "errors" import "fmt" func f() { fmt.Errorf("%w", New("hello")) fmt.Println("hello") } `, `import . "errors" func f() { errorf "%w", New("hello") echo "hello" } `) } func TestPrintlnWithOtherFmtCallsGroup(t *testing.T) { testFormat(t, "print", `package main import ( "errors" "fmt" ) func f() { fmt.Errorf("%w", errors.New("hello")) fmt.println "hello" } `, `import ( "errors" ) func f() { errorf "%w", errors.new("hello") echo "hello" } `) } func TestPrintlnWithOtherFmtCallsWithAssign(t *testing.T) { testFormat(t, "print", `package main import "errors" import "fmt" func f() { _ = fmt.Errorf("%w", errors.New("hello")) fmt.println("hello") } `, `import "errors" func f() { _ = errorf("%w", errors.new("hello")) echo "hello" } `) } func TestPrintlnWithOtherFmtCallsWithGroupWithAssign(t *testing.T) { testFormat(t, "print", `package main import ( "errors" "fmt" ) func f() { _ = fmt.Errorf("%w", errors.New("hello")) fmt.Println("hello") } `, `import ( "errors" ) func f() { _ = errorf("%w", errors.new("hello")) echo "hello" } `) } func TestPrintlnWithOtherFmtDecls(t *testing.T) { testFormat(t, "print", `package main import "fmt" func f() { _ = fmt.Stringer fmt.Println("hello") } `, `import "fmt" func f() { _ = fmt.Stringer echo "hello" } `) } func TestPrintlnWithOtherFmtVars(t *testing.T) { testFormat(t, "print", `package main import "fmt" func f() { var _ fmt.Stringer fmt.Println("hello") } `, `import "fmt" func f() { var _ fmt.Stringer echo "hello" } `) } func TestPrintlnWithOtherFmtType(t *testing.T) { testFormat(t, "print", `package main import "fmt" func f() { var _ struct { fmt.Stringer fn func() } fmt.Println("hello") } `, `import "fmt" func f() { var _ struct { fmt.Stringer fn func() } echo "hello" } `) } func TestPrintlnImportAlias(t *testing.T) { testFormat(t, "print", `package main import fmt1 "fmt" func f() { fmt1.Println("hello") } `, `func f() { echo "hello" } `) } func TestPrintlnImportMultiAliases(t *testing.T) { testFormat(t, "print", `package main import ( fmt1 "fmt" fmt2 "fmt" ) func f() { fmt1.Println(1) fmt2.Println(2) } `, `func f() { echo 1 echo 2 } `) } func TestPrintlnImportMultiAliasesDifferentGroups(t *testing.T) { testFormat(t, "print", `package main import "fmt" import ( fmt1 "fmt" fmt2 "fmt" ) func f() { var _ fmt.Stringer fmt1.Println(1) fmt2.Println(2) } `, `import "fmt" func f() { var _ fmt.Stringer echo 1 echo 2 } `) } func TestErrorfWithPackageLevelVar(t *testing.T) { testFormat(t, "print", `package main import "errors" import "fmt" var _ = fmt.Errorf("hello %w", errors.New("world")) `, `import "errors" var _ = errorf("hello %w", errors.new("world")) `) } func TestPrintlnWithFmtVarNoImport(t *testing.T) { testFormat(t, "print", `package main func f() { var fmt Foo fmt.Println(1) } `, `func f() { var fmt Foo fmt.println 1 } `) } func TestPrintlnWithFmtVar(t *testing.T) { testFormat(t, "print", `package main import "fmt" var _ fmt.Stringer func f() { var fmt Foo fmt.Println(1) } `, `import "fmt" var _ fmt.Stringer func f() { var fmt Foo fmt.println 1 } `) } // todo: fix inner scope vars // func TestPrintlnWithScopedFmtVar(t *testing.T) { // testFormat(t, "print", `package main // // import "fmt" // // func f() { // { // var fmt Foo // _ = fmt // } // fmt.Println(1) // } // `, `func f() { // { // var fmt Foo // _ = fmt // } // println 1 // } // `) // } func TestPrintlnWithFmtVarAfter(t *testing.T) { testFormat(t, "print", `package main import "fmt" func f() { fmt.Println(1) var fmt Foo _ = fmt } `, `func f() { echo 1 var fmt Foo _ = fmt } `) } func TestPrintlnWithPackageLevelFmtVar(t *testing.T) { testFormat(t, "print", `package main var fmt Foo func f() { fmt.Println(1) } `, `var fmt Foo func f() { fmt.println 1 } `) } func TestPrintlnWithPackageLevelFmtVarAfter(t *testing.T) { testFormat(t, "print", `package main func f() { fmt.Println(1) } var fmt Foo `, `func f() { fmt.println 1 } var fmt Foo `) } func TestPrintlnWithVarFromCall(t *testing.T) { testFormat(t, "print", `package main func f() { var fmt = Foo() fmt.Println(1) } `, `func f() { var fmt = Foo() fmt.println 1 } `) } func TestLambdaFromFuncLit(t *testing.T) { testFormat(t, "funclit to lambda", `package main println(demo(func(n int) int { return n+100 })) println(demo(func(n int) int { return n+100 }),200) demo1(100, func(n int) { println(n) }) demo1(100, func(int) { println(100) }) demo2(300, func(n1, n2 int) int { return(n1 + n2) }) demo2(300, func(int, int) int { return -600 }) demo3(100,func(n1,n2 int)(int) { return n1+n2,n1-n2 },100) demo3(100,func(n1,n2 int)(v int) { return n1+n2,n1-n2 },100) demo4(100,func(n1,n2 int)(a,b int) { println(a,b) return n1+n2,n1-n2 },100) `, `println demo(n => n + 100) println demo(n => n + 100), 200 demo1 100, n => { println n } demo1 100, _ => { println 100 } demo2 300, (n1, n2) => (n1 + n2) demo2 300, (_, _) => -600 demo3 100, (n1, n2) => { return n1 + n2, n1 - n2 }, 100 demo3 100, func(n1, n2 int) (v int) { return n1 + n2, n1 - n2 }, 100 demo4 100, func(n1, n2 int) (a, b int) { println a, b return n1 + n2, n1 - n2 }, 100 `) } ================================================ FILE: x/format/gopstyledir_test.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package format import ( "io/ioutil" "log" "os" "path" "strings" "testing" ) // ----------------------------------------------------------------------------- func TestFromTestdata(t *testing.T) { sel := "" dir, err := os.Getwd() if err != nil { t.Fatal("Getwd failed:", err) } dir = path.Join(dir, "./_testdata") fis, err := ioutil.ReadDir(dir) if err != nil { t.Fatal("ReadDir failed:", err) } for _, fi := range fis { name := fi.Name() if strings.HasPrefix(name, "_") { continue } t.Run(name, func(t *testing.T) { testFrom(t, dir+"/"+name, sel) }) } } func testFrom(t *testing.T, pkgDir, sel string) { if sel != "" && !strings.Contains(pkgDir, sel) { return } log.Println("Formatting", pkgDir) file := pkgDir + "/index.xgo" src, err := os.ReadFile(file) if err != nil { t.Fatal(err) } ret, err := GopstyleSource(src, file) if err != nil { t.Fatal(err) } expect, err := os.ReadFile(pkgDir + "/format.expect") if err != nil { t.Fatal(err) } diffBytes(t, pkgDir+"/format.result", ret, expect) } func diffBytes(t *testing.T, outfile string, dst, src []byte) { line := 1 offs := 0 // line offset for i := 0; i < len(dst) && i < len(src); i++ { d := dst[i] s := src[i] if d != s { os.WriteFile(outfile, dst, 0644) t.Errorf("dst:%d: %s\n", line, dst[offs:]) t.Errorf("src:%d: %s\n", line, src[offs:]) return } if s == '\n' { line++ offs = i + 1 } } if len(dst) != len(src) { t.Errorf("len(dst) = %d, len(src) = %d\ndst = %q\nsrc = %q", len(dst), len(src), dst, src) } } // ----------------------------------------------------------------------------- ================================================ FILE: x/format/stmt_expr_or_type.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package format import ( "go/token" "log" "reflect" "github.com/goplus/xgo/ast" ) // ----------------------------------------------------------------------------- func formatType(ctx *formatCtx, typ ast.Expr, ref *ast.Expr) { switch t := typ.(type) { case *ast.Ident, nil: case *ast.SelectorExpr: formatSelectorExpr(ctx, t, ref) case *ast.StarExpr: formatType(ctx, t.X, &t.X) case *ast.MapType: formatType(ctx, t.Key, &t.Key) formatType(ctx, t.Value, &t.Value) case *ast.StructType: formatFields(ctx, t.Fields) case *ast.ArrayType: formatExpr(ctx, t.Len, &t.Len) formatType(ctx, t.Elt, &t.Elt) case *ast.ChanType: formatType(ctx, t.Value, &t.Value) case *ast.InterfaceType: formatFields(ctx, t.Methods) case *ast.FuncType: formatFuncType(ctx, t) case *ast.Ellipsis: formatType(ctx, t.Elt, &t.Elt) default: log.Panicln("TODO: format -", reflect.TypeOf(typ)) } } func formatFuncType(ctx *formatCtx, t *ast.FuncType) { formatFields(ctx, t.Params) formatFields(ctx, t.Results) } func formatFields(ctx *formatCtx, flds *ast.FieldList) { if flds != nil { for _, fld := range flds.List { formatField(ctx, fld) } } } func formatField(ctx *formatCtx, fld *ast.Field) { formatType(ctx, fld.Type, &fld.Type) } // ----------------------------------------------------------------------------- func formatExprs(ctx *formatCtx, exprs []ast.Expr) { for i, expr := range exprs { formatExpr(ctx, expr, &exprs[i]) } } func formatExpr(ctx *formatCtx, expr ast.Expr, ref *ast.Expr) { switch v := expr.(type) { case *ast.Ident, *ast.BasicLit, *ast.BadExpr, nil: case *ast.BinaryExpr: formatExpr(ctx, v.X, &v.X) formatExpr(ctx, v.Y, &v.Y) case *ast.UnaryExpr: formatExpr(ctx, v.X, &v.X) case *ast.CallExpr: formatCallExpr(ctx, v) case *ast.SelectorExpr: formatSelectorExpr(ctx, v, ref) case *ast.SliceExpr: formatSliceExpr(ctx, v) case *ast.IndexExpr: formatExpr(ctx, v.X, &v.X) formatExpr(ctx, v.Index, &v.Index) case *ast.SliceLit: formatExprs(ctx, v.Elts) case *ast.CompositeLit: formatType(ctx, v.Type, &v.Type) formatExprs(ctx, v.Elts) case *ast.StarExpr: formatExpr(ctx, v.X, &v.X) case *ast.KeyValueExpr: formatExpr(ctx, v.Key, &v.Key) formatExpr(ctx, v.Value, &v.Value) case *ast.FuncLit: formatFuncType(ctx, v.Type) formatBlockStmt(ctx, v.Body) case *ast.TypeAssertExpr: formatExpr(ctx, v.X, &v.X) formatType(ctx, v.Type, &v.Type) case *ast.LambdaExpr: formatExprs(ctx, v.Rhs) case *ast.LambdaExpr2: formatBlockStmt(ctx, v.Body) case *ast.RangeExpr: formatRangeExpr(ctx, v) case *ast.ComprehensionExpr: formatComprehensionExpr(ctx, v) case *ast.ErrWrapExpr: formatExpr(ctx, v.X, &v.X) formatExpr(ctx, v.Default, &v.Default) case *ast.ParenExpr: formatExpr(ctx, v.X, &v.X) case *ast.Ellipsis: formatExpr(ctx, v.Elt, &v.Elt) default: formatType(ctx, expr, ref) } } func formatRangeExpr(ctx *formatCtx, v *ast.RangeExpr) { formatExpr(ctx, v.First, &v.First) formatExpr(ctx, v.Last, &v.Last) formatExpr(ctx, v.Expr3, &v.Expr3) } func formatComprehensionExpr(ctx *formatCtx, v *ast.ComprehensionExpr) { old := ctx.enterBlock() defer ctx.leaveBlock(old) formatForPhrases(ctx, v.Fors) formatExpr(ctx, v.Elt, &v.Elt) } func formatForPhrases(ctx *formatCtx, fors []*ast.ForPhrase) { for _, f := range fors { formatForPhrase(ctx, f) } } func formatForPhrase(ctx *formatCtx, v *ast.ForPhrase) { formatExpr(ctx, v.X, &v.X) formatStmt(ctx, v.Init) formatExpr(ctx, v.Cond, &v.Cond) } func formatSliceExpr(ctx *formatCtx, v *ast.SliceExpr) { formatExpr(ctx, v.X, &v.X) formatExpr(ctx, v.Low, &v.Low) formatExpr(ctx, v.High, &v.High) formatExpr(ctx, v.Max, &v.Max) } func formatCallExpr(ctx *formatCtx, v *ast.CallExpr) { formatExpr(ctx, v.Fun, &v.Fun) fncallStartingLowerCase(v) for i, arg := range v.Args { if fn, ok := arg.(*ast.FuncLit); ok { funcLitToLambdaExpr(fn, &v.Args[i]) } } formatExprs(ctx, v.Args) } func formatSelectorExpr(ctx *formatCtx, v *ast.SelectorExpr, ref *ast.Expr) { switch x := v.X.(type) { case *ast.Ident: if _, o := ctx.scope.LookupParent(x.Name, token.NoPos); o != nil { break } if imp, ok := ctx.imports[x.Name]; ok { if !fmtToBuiltin(imp, v.Sel, ref) { imp.isUsed = true } } default: formatExpr(ctx, x, &v.X) } } // ----------------------------------------------------------------------------- func formatBlockStmt(ctx *formatCtx, stmt *ast.BlockStmt) { if stmt != nil { old := ctx.enterBlock() defer ctx.leaveBlock(old) formatStmts(ctx, stmt.List) } } func formatStmts(ctx *formatCtx, stmts []ast.Stmt) { for _, stmt := range stmts { formatStmt(ctx, stmt) } } func formatStmt(ctx *formatCtx, stmt ast.Stmt) { switch v := stmt.(type) { case *ast.ExprStmt: formatExprStmt(ctx, v) case *ast.AssignStmt: formatAssignStmt(ctx, v) case *ast.IncDecStmt: formatExpr(ctx, v.X, &v.X) case *ast.ForStmt: formatForStmt(ctx, v) case *ast.RangeStmt: formatRangeStmt(ctx, v) case *ast.ForPhraseStmt: formatForPhraseStmt(ctx, v) case *ast.IfStmt: formatIfStmt(ctx, v) case *ast.CaseClause: formatExprs(ctx, v.List) formatStmts(ctx, v.Body) case *ast.SwitchStmt: formatSwitchStmt(ctx, v) case *ast.TypeSwitchStmt: formatTypeSwitchStmt(ctx, v) case *ast.CommClause: formatStmt(ctx, v.Comm) formatStmts(ctx, v.Body) case *ast.SelectStmt: formatBlockStmt(ctx, v.Body) case *ast.DeclStmt: formatDeclStmt(ctx, v) case *ast.ReturnStmt: formatExprs(ctx, v.Results) case *ast.BlockStmt: formatBlockStmt(ctx, v) case *ast.DeferStmt: formatCallExpr(ctx, v.Call) case *ast.GoStmt: formatCallExpr(ctx, v.Call) case *ast.SendStmt: formatExpr(ctx, v.Chan, &v.Chan) for i, val := range v.Values { formatExpr(ctx, val, &v.Values[i]) } case *ast.LabeledStmt: formatStmt(ctx, v.Stmt) case *ast.BranchStmt, *ast.EmptyStmt, nil, *ast.BadStmt: default: log.Panicln("TODO: formatStmt -", reflect.TypeOf(stmt)) } } func formatExprStmt(ctx *formatCtx, v *ast.ExprStmt) { switch x := v.X.(type) { case *ast.CallExpr: commandStyleFirst(x) } formatExpr(ctx, v.X, &v.X) } func formatAssignStmt(ctx *formatCtx, v *ast.AssignStmt) { formatExprs(ctx, v.Lhs) formatExprs(ctx, v.Rhs) } func formatSwitchStmt(ctx *formatCtx, v *ast.SwitchStmt) { old := ctx.enterBlock() defer ctx.leaveBlock(old) formatStmt(ctx, v.Init) formatExpr(ctx, v.Tag, &v.Tag) formatBlockStmt(ctx, v.Body) } func formatTypeSwitchStmt(ctx *formatCtx, v *ast.TypeSwitchStmt) { old := ctx.enterBlock() defer ctx.leaveBlock(old) formatStmt(ctx, v.Init) formatStmt(ctx, v.Assign) formatBlockStmt(ctx, v.Body) } func formatIfStmt(ctx *formatCtx, v *ast.IfStmt) { old := ctx.enterBlock() defer ctx.leaveBlock(old) formatStmt(ctx, v.Init) formatExpr(ctx, v.Cond, &v.Cond) formatBlockStmt(ctx, v.Body) formatStmt(ctx, v.Else) } func formatRangeStmt(ctx *formatCtx, v *ast.RangeStmt) { old := ctx.enterBlock() defer ctx.leaveBlock(old) formatExpr(ctx, v.Key, &v.Key) formatExpr(ctx, v.Value, &v.Value) formatExpr(ctx, v.X, &v.X) formatBlockStmt(ctx, v.Body) } func formatForPhraseStmt(ctx *formatCtx, v *ast.ForPhraseStmt) { old := ctx.enterBlock() defer ctx.leaveBlock(old) formatForPhrase(ctx, v.ForPhrase) formatBlockStmt(ctx, v.Body) } func formatForStmt(ctx *formatCtx, v *ast.ForStmt) { old := ctx.enterBlock() defer ctx.leaveBlock(old) formatStmt(ctx, v.Init) formatExpr(ctx, v.Cond, &v.Cond) formatBlockStmt(ctx, v.Body) } func formatDeclStmt(ctx *formatCtx, v *ast.DeclStmt) { if decl, ok := v.Decl.(*ast.GenDecl); ok { formatGenDecl(ctx, decl) } } // ----------------------------------------------------------------------------- ================================================ FILE: x/fsnotify/fsnotify.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package fsnotify import ( "errors" "io/fs" "log" "os" "path/filepath" "github.com/fsnotify/fsnotify" ) var ( debugEvent bool ) const ( DbgFlagEvent = 1 << iota DbgFlagAll = DbgFlagEvent ) func SetDebug(dbgFlags int) { debugEvent = (dbgFlags & DbgFlagEvent) != 0 } // ----------------------------------------------------------------------------------------- type FSChanged interface { FileChanged(name string) DirAdded(name string) EntryDeleted(name string, isDir bool) } type Ignore = func(name string, isDir bool) bool // ----------------------------------------------------------------------------------------- type Watcher struct { w *fsnotify.Watcher } func New() Watcher { w, err := fsnotify.NewWatcher() if err != nil { log.Panicln("[FATAL] fsnotify.NewWatcher:", err) } return Watcher{w} } func (p Watcher) Run(root string, fc FSChanged, ignore Ignore) error { go p.watchLoop(root, fc, ignore) return watchRecursive(p.w, root) } func (p Watcher) watchLoop(root string, fc FSChanged, ignore Ignore) { const ( eventModify = fsnotify.Write | fsnotify.Create ) for { select { case event, ok := <-p.w.Events: if !ok { // closed return } if debugEvent { log.Println("==> event:", event) } if (event.Op & fsnotify.Remove) != 0 { e := p.w.Remove(event.Name) name, err := filepath.Rel(root, event.Name) if err != nil { log.Println("[ERROR] fsnotify.EntryDeleted filepath.Rel:", err) continue } isDir := (e == nil || !errors.Is(e, fsnotify.ErrNonExistentWatch)) name = filepath.ToSlash(name) if ignore != nil && ignore(name, isDir) { continue } else { fc.EntryDeleted(name, isDir) } } else if (event.Op & eventModify) != 0 { name, err := filepath.Rel(root, event.Name) if err != nil { log.Println("[ERROR] fsnotify.FileChanged filepath.Rel:", err) continue } isDir := isDir(event.Name) name = filepath.ToSlash(name) if ignore != nil && ignore(name, isDir) { continue } else if isDir { if (event.Op & fsnotify.Create) != 0 { watchRecursive(p.w, event.Name) fc.DirAdded(name) } } else { fc.FileChanged(name) } } case err, ok := <-p.w.Errors: if !ok { return } log.Println("[ERROR] fsnotify Errors:", err) } } } func (p *Watcher) Close() error { if w := p.w; w != nil { p.w = nil return w.Close() } return nil } func watchRecursive(w *fsnotify.Watcher, path string) error { err := filepath.WalkDir(path, func(walkPath string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() { if err = w.Add(walkPath); err != nil { return err } } return nil }) return err } func isDir(name string) bool { if fs, err := os.Lstat(name); err == nil { return fs.IsDir() } return false } // ----------------------------------------------------------------------------------------- ================================================ FILE: x/gocmd/build_install.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package gocmd // ----------------------------------------------------------------------------- type InstallConfig = Config func Install(arg string, conf *InstallConfig) (err error) { return doWithArgs("", "install", conf, arg) } func InstallFiles(files []string, conf *InstallConfig) (err error) { return doWithArgs("", "install", conf, files...) } // ----------------------------------------------------------------------------- type BuildConfig = Config func Build(arg string, conf *BuildConfig) (err error) { return doWithArgs("", "build", conf, arg) } func BuildFiles(files []string, conf *BuildConfig) (err error) { return doWithArgs("", "build", conf, files...) } // ----------------------------------------------------------------------------- type TestConfig = Config func Test(arg string, conf *TestConfig) (err error) { return doWithArgs("", "test", conf, arg) } func TestFiles(files []string, conf *TestConfig) (err error) { return doWithArgs("", "test", conf, files...) } // ----------------------------------------------------------------------------- ================================================ FILE: x/gocmd/gocmd.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package gocmd import ( "fmt" "os" "os/exec" "github.com/goplus/mod/env" "github.com/goplus/xgo/x/xgoenv" ) type XGoEnv = env.XGo type Config struct { XGo *XGoEnv GoCmd string Flags []string Run func(cmd *exec.Cmd) error } // ----------------------------------------------------------------------------- func doWithArgs(dir, op string, conf *Config, args ...string) (err error) { if conf == nil { conf = new(Config) } goCmd := conf.GoCmd if goCmd == "" { goCmd = Name() } exargs := make([]string, 1, 16) exargs[0] = op exargs = appendLdflags(exargs, conf.XGo) exargs = append(exargs, conf.Flags...) exargs = append(exargs, args...) cmd := exec.Command(goCmd, exargs...) cmd.Dir = dir run := conf.Run if run == nil { run = runCmd } return run(cmd) } func runCmd(cmd *exec.Cmd) (err error) { cmd.Stdin = os.Stdin cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout return cmd.Run() } // ----------------------------------------------------------------------------- const ( ldFlagVersion = "-X \"github.com/goplus/xgo/env.buildVersion=%s\"" ldFlagBuildDate = "-X \"github.com/goplus/xgo/env.buildDate=%s\"" ldFlagGopRoot = "-X \"github.com/goplus/xgo/env.defaultXGoRoot=%s\"" ) const ( ldFlagAll = ldFlagVersion + " " + ldFlagBuildDate + " " + ldFlagGopRoot ) func loadFlags(env *XGoEnv) string { return fmt.Sprintf(ldFlagAll, env.Version, env.BuildDate, env.Root) } func appendLdflags(exargs []string, env *XGoEnv) []string { if env == nil { env = xgoenv.Get() } return append(exargs, "-ldflags", loadFlags(env)) } // ----------------------------------------------------------------------------- // Name returns name of the go command. // It returns value of environment variable `XGO_GOCMD` if not empty. // If not found, it returns `go`. func Name() string { goCmd := os.Getenv("XGO_GOCMD") if goCmd == "" { goCmd = "go" } return goCmd } // ----------------------------------------------------------------------------- ================================================ FILE: x/gocmd/run.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package gocmd import ( "os" "os/exec" "path/filepath" "strings" "syscall" ) // ----------------------------------------------------------------------------- type RunConfig = Config // RunDir runs a Go project by specified directory. // If buildDir is not empty, it means split `go run` into `go build` // in buildDir and run the built app in current directory. func RunDir(buildDir, dir string, args []string, conf *RunConfig) (err error) { fis, err := os.ReadDir(dir) if err != nil { return } var files []string for _, fi := range fis { if !fi.IsDir() { if fname := fi.Name(); filterRunFname(fname) { files = append(files, filepath.Join(dir, fname)) } } } return RunFiles(buildDir, files, args, conf) } func filterRunFname(fname string) bool { return strings.HasSuffix(fname, ".go") && !(strings.HasSuffix(fname, "_test.go") || strings.HasPrefix(fname, "_")) } // ----------------------------------------------------------------------------- // RunFiles runs a Go project by specified files. // If buildDir is not empty, it means split `go run` into `go build` // in buildDir and run the built app in current directory. func RunFiles(buildDir string, files []string, args []string, conf *RunConfig) (err error) { if len(files) == 0 { return syscall.ENOENT } if buildDir == "" { args = append(files, args...) return doWithArgs("", "run", conf, args...) } absFiles := make([]string, len(files)) for i, file := range files { absFiles[i], _ = filepath.Abs(file) } f, err := os.CreateTemp("", "gobuild") if err != nil { return } tempf := f.Name() f.Close() os.Remove(tempf) defer os.Remove(tempf) buildArgs := append([]string{"-o", tempf}, absFiles...) if err = doWithArgs(buildDir, "build", conf, buildArgs...); err != nil { return } cmd := exec.Command(tempf, args...) return runCmd(cmd) } // ----------------------------------------------------------------------------- ================================================ FILE: x/jsonrpc2/conn.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package jsonrpc2 import ( "context" "encoding/json" "errors" "fmt" "io" "log" "sync" "sync/atomic" "time" ) // Binder builds a connection configuration. // This may be used in servers to generate a new configuration per connection. // ConnectionOptions itself implements Binder returning itself unmodified, to // allow for the simple cases where no per connection information is needed. type Binder interface { // Bind returns the ConnectionOptions to use when establishing the passed-in // Connection. // // The connection is not ready to use when Bind is called, // but Bind may close it without reading or writing to it. Bind(context.Context, *Connection) ConnectionOptions } // A BinderFunc implements the Binder interface for a standalone Bind function. type BinderFunc func(context.Context, *Connection) ConnectionOptions func (f BinderFunc) Bind(ctx context.Context, c *Connection) ConnectionOptions { return f(ctx, c) } var _ Binder = BinderFunc(nil) // ConnectionOptions holds the options for new connections. type ConnectionOptions struct { // Framer allows control over the message framing and encoding. // If nil, HeaderFramer will be used. Framer Framer // Preempter allows registration of a pre-queue message handler. // If nil, no messages will be preempted. Preempter Preempter // Handler is used as the queued message handler for inbound messages. // If nil, all responses will be ErrNotHandled. Handler Handler // OnInternalError, if non-nil, is called with any internal errors that occur // while serving the connection, such as protocol errors or invariant // violations. (If nil, internal errors result in panics.) OnInternalError func(error) } // Connection manages the jsonrpc2 protocol, connecting responses back to their // calls. // Connection is bidirectional; it does not have a designated server or client // end. type Connection struct { seq int64 // must only be accessed using atomic operations stateMu sync.Mutex state inFlightState // accessed only in updateInFlight done chan struct{} // closed (under stateMu) when state.closed is true and all goroutines have completed writer chan Writer // 1-buffered; stores the writer when not in use handler Handler onInternalError func(error) onDone func() } // inFlightState records the state of the incoming and outgoing calls on a // Connection. type inFlightState struct { connClosing bool // true when the Connection's Close method has been called reading bool // true while the readIncoming goroutine is running readErr error // non-nil when the readIncoming goroutine exits (typically io.EOF) writeErr error // non-nil if a call to the Writer has failed with a non-canceled Context // closer shuts down and cleans up the Reader and Writer state, ideally // interrupting any Read or Write call that is currently blocked. It is closed // when the state is idle and one of: connClosing is true, readErr is non-nil, // or writeErr is non-nil. // // After the closer has been invoked, the closer field is set to nil // and the closeErr field is simultaneously set to its result. closer io.Closer closeErr error // error returned from closer.Close outgoingCalls map[ID]*AsyncCall // calls only outgoingNotifications int // # of notifications awaiting "write" // incoming stores the total number of incoming calls and notifications // that have not yet written or processed a result. incoming int incomingByID map[ID]*incomingRequest // calls only // handlerQueue stores the backlog of calls and notifications that were not // already handled by a preempter. // The queue does not include the request currently being handled (if any). handlerQueue []*incomingRequest handlerRunning bool } // updateInFlight locks the state of the connection's in-flight requests, allows // f to mutate that state, and closes the connection if it is idle and either // is closing or has a read or write error. func (c *Connection) updateInFlight(f func(*inFlightState)) { c.stateMu.Lock() defer c.stateMu.Unlock() s := &c.state f(s) select { case <-c.done: // The connection was already completely done at the start of this call to // updateInFlight, so it must remain so. (The call to f should have noticed // that and avoided making any updates that would cause the state to be // non-idle.) if !s.idle() { panic("jsonrpc2_v2: updateInFlight transitioned to non-idle when already done") } return default: } if s.idle() && s.shuttingDown(ErrUnknown) != nil { if Verbose { log.Println("==> Connection.updateInFlight: shuttingDown") } if s.closer != nil { s.closeErr = s.closer.Close() s.closer = nil // prevent duplicate Close calls } if Verbose { log.Println("==> Connection.updateInFlight: s.reading -", s.reading) } if s.reading { // The readIncoming goroutine is still running. Our call to Close should // cause it to exit soon, at which point it will make another call to // updateInFlight, set s.reading to false, and mark the Connection done. } else { // The readIncoming goroutine has exited, or never started to begin with. // Since everything else is idle, we're completely done. if c.onDone != nil { c.onDone() } close(c.done) } } } // idle reports whether the connection is in a state with no pending calls or // notifications. // // If idle returns true, the readIncoming goroutine may still be running, // but no other goroutines are doing work on behalf of the connection. func (s *inFlightState) idle() bool { return len(s.outgoingCalls) == 0 && s.outgoingNotifications == 0 && s.incoming == 0 && !s.handlerRunning } // shuttingDown reports whether the connection is in a state that should // disallow new (incoming and outgoing) calls. It returns either nil or // an error that is or wraps the provided errClosing. func (s *inFlightState) shuttingDown(errClosing error) error { if s.connClosing { // If Close has been called explicitly, it doesn't matter what state the // Reader and Writer are in: we shouldn't be starting new work because the // caller told us not to start new work. return errClosing } if s.readErr != nil { // If the read side of the connection is broken, we cannot read new call // requests, and cannot read responses to our outgoing calls. return fmt.Errorf("%w: %v", errClosing, s.readErr) } if s.writeErr != nil { // If the write side of the connection is broken, we cannot write responses // for incoming calls, and cannot write requests for outgoing calls. return fmt.Errorf("%w: %v", errClosing, s.writeErr) } return nil } // incomingRequest is used to track an incoming request as it is being handled type incomingRequest struct { *Request // the request being processed ctx context.Context cancel context.CancelFunc } // newConnection creates a new connection and runs it. // // This is used by the Dial and Serve functions to build the actual connection. // // The connection is closed automatically (and its resources cleaned up) when // the last request has completed after the underlying ReadWriteCloser breaks, // but it may be stopped earlier by calling Close (for a clean shutdown). func newConnection(bindCtx context.Context, rwc io.ReadWriteCloser, binder Binder, onDone func()) *Connection { // TODO: Should we create a new event span here? // This will propagate cancellation from ctx; should it? ctx := notDone{bindCtx} c := &Connection{ state: inFlightState{closer: rwc}, done: make(chan struct{}), writer: make(chan Writer, 1), onDone: onDone, } // It's tempting to set a finalizer on c to verify that the state has gone // idle when the connection becomes unreachable. Unfortunately, the Binder // interface makes that unsafe: it allows the Handler to close over the // Connection, which could create a reference cycle that would cause the // Connection to become uncollectable. options := binder.Bind(bindCtx, c) framer := options.Framer if framer == nil { framer = HeaderFramer() } c.handler = options.Handler if c.handler == nil { c.handler = defaultHandler{} } c.onInternalError = options.OnInternalError if c.onInternalError == nil { c.onInternalError = defaultHandleError } c.writer <- framer.Writer(rwc) reader := framer.Reader(rwc) c.updateInFlight(func(s *inFlightState) { select { case <-c.done: // Bind already closed the connection; don't start a goroutine to read it. return default: } // The goroutine started here will continue until the underlying stream is closed. // // (If the Binder closed the Connection already, this should error out and // return almost immediately.) s.reading = true go c.readIncoming(ctx, reader, options.Preempter) }) return c } // Notify invokes the target method but does not wait for a response. // The params will be marshaled to JSON before sending over the wire, and will // be handed to the method invoked. func (c *Connection) Notify(ctx context.Context, method string, params any) (err error) { attempted := false defer func() { if attempted { c.updateInFlight(func(s *inFlightState) { s.outgoingNotifications-- }) } }() if debugCall { log.Println("Notify", method, "params:", params) } c.updateInFlight(func(s *inFlightState) { // If the connection is shutting down, allow outgoing notifications only if // there is at least one call still in flight. The number of calls in flight // cannot increase once shutdown begins, and allowing outgoing notifications // may permit notifications that will cancel in-flight calls. if len(s.outgoingCalls) == 0 && len(s.incomingByID) == 0 { err = s.shuttingDown(ErrClientClosing) if err != nil { return } } s.outgoingNotifications++ attempted = true }) if err != nil { return err } notify, err := NewNotification(method, params) if err != nil { return fmt.Errorf("marshaling notify parameters: %v", err) } err = c.write(ctx, notify) if debugCall { log.Println("Connection.write", notify.Method, err) } return } // Call invokes the target method and returns an object that can be used to await the response. // The params will be marshaled to JSON before sending over the wire, and will // be handed to the method invoked. // You do not have to wait for the response, it can just be ignored if not needed. // If sending the call failed, the response will be ready and have the error in it. func (c *Connection) Call(ctx context.Context, method string, params any) *AsyncCall { if debugCall { log.Println("Call", method, "params:", params) } // Generate a new request identifier. id := Int64ID(atomic.AddInt64(&c.seq, 1)) ac := &AsyncCall{ id: id, ready: make(chan struct{}), } // When this method returns, either ac is retired, or the request has been // written successfully and the call is awaiting a response (to be provided by // the readIncoming goroutine). call, err := NewCall(ac.id, method, params) if err != nil { ac.retire(&Response{ID: id, Error: fmt.Errorf("marshaling call parameters: %w", err)}) return ac } c.updateInFlight(func(s *inFlightState) { err = s.shuttingDown(ErrClientClosing) if err != nil { return } if s.outgoingCalls == nil { s.outgoingCalls = make(map[ID]*AsyncCall) } s.outgoingCalls[ac.id] = ac }) if err != nil { ac.retire(&Response{ID: id, Error: err}) return ac } err = c.write(ctx, call) if debugCall { log.Println("Connection.write", call.ID, call.Method, err) } if err != nil { // Sending failed. We will never get a response, so deliver a fake one if it // wasn't already retired by the connection breaking. c.updateInFlight(func(s *inFlightState) { if s.outgoingCalls[ac.id] == ac { delete(s.outgoingCalls, ac.id) ac.retire(&Response{ID: id, Error: err}) } else { // ac was already retired by the readIncoming goroutine: // perhaps our write raced with the Read side of the connection breaking. _ = 0 } }) } return ac } type AsyncCall struct { id ID ready chan struct{} // closed after response has been set response *Response } // ID used for this call. // This can be used to cancel the call if needed. func (ac *AsyncCall) ID() ID { return ac.id } // IsReady can be used to check if the result is already prepared. // This is guaranteed to return true on a result for which Await has already // returned, or a call that failed to send in the first place. func (ac *AsyncCall) IsReady() bool { select { case <-ac.ready: return true default: return false } } // retire processes the response to the call. func (ac *AsyncCall) retire(response *Response) { select { case <-ac.ready: panic(fmt.Sprintf("jsonrpc2: retire called twice for ID %v", ac.id)) default: } ac.response = response close(ac.ready) } // Await waits for (and decodes) the results of a Call. // The response will be unmarshaled from JSON into the result. func (ac *AsyncCall) Await(ctx context.Context, result any) error { if Verbose { log.Println("==> AsyncCall.Await", ac.id) } select { case <-ctx.Done(): return ctx.Err() case <-ac.ready: } if e := ac.response.Error; e != nil { if Verbose { log.Println("==> AsyncCall.Await error:", ac.id, e) } return e } ret := ac.response.Result if Verbose { log.Println("==> AsyncCall.Await ret:", ac.id, string(ret)) } if result == nil { return nil } return json.Unmarshal(ret, result) } // Respond delivers a response to an incoming Call. // // Respond must be called exactly once for any message for which a handler // returns ErrAsyncResponse. It must not be called for any other message. func (c *Connection) Respond(id ID, result any, err error) error { var req *incomingRequest c.updateInFlight(func(s *inFlightState) { req = s.incomingByID[id] }) if req == nil { return c.internalErrorf("Request not found for ID %v", id) } if err == ErrAsyncResponse { // Respond is supposed to supply the asynchronous response, so it would be // confusing to call Respond with an error that promises to call Respond // again. err = c.internalErrorf("Respond called with ErrAsyncResponse for %q", req.Method) } return c.processResult("Respond", req, result, err) } // Cancel cancels the Context passed to the Handle call for the inbound message // with the given ID. // // Cancel will not complain if the ID is not a currently active message, and it // will not cause any messages that have not arrived yet with that ID to be // cancelled. func (c *Connection) Cancel(id ID) { var req *incomingRequest c.updateInFlight(func(s *inFlightState) { req = s.incomingByID[id] }) if req != nil { req.cancel() } } // Wait blocks until the connection is fully closed, but does not close it. func (c *Connection) Wait() error { if Verbose { log.Println("==> Connection.Wait start") } var err error <-c.done c.updateInFlight(func(s *inFlightState) { err = s.closeErr }) if Verbose { log.Println("==> Connection.Wait end:", err) } return err } // Close stops accepting new requests, waits for in-flight requests and enqueued // Handle calls to complete, and then closes the underlying stream. // // After the start of a Close, notification requests (that lack IDs and do not // receive responses) will continue to be passed to the Preempter, but calls // with IDs will receive immediate responses with ErrServerClosing, and no new // requests (not even notifications!) will be enqueued to the Handler. func (c *Connection) Close() error { // Stop handling new requests, and interrupt the reader (by closing the // connection) as soon as the active requests finish. c.updateInFlight(func(s *inFlightState) { s.connClosing = true }) return c.Wait() } // readIncoming collects inbound messages from the reader and delivers them, either responding // to outgoing calls or feeding requests to the queue. func (c *Connection) readIncoming(ctx context.Context, reader Reader, preempter Preempter) { var err error for { var ( msg Message n int64 ) msg, n, err = reader.Read(ctx) if err != nil { break } switch msg := msg.(type) { case *Request: c.acceptRequest(ctx, msg, n, preempter) case *Response: if Verbose { log.Println("==> readIncoming Response:", msg.ID) } c.updateInFlight(func(s *inFlightState) { if ac, ok := s.outgoingCalls[msg.ID]; ok { delete(s.outgoingCalls, msg.ID) ac.retire(msg) } else { // TODO: How should we report unexpected responses? _ = 0 } }) if Verbose { log.Println("==> readIncoming: updateInFlight -", msg.ID) } default: c.internalErrorf("Read returned an unexpected message of type %T", msg) } } c.updateInFlight(func(s *inFlightState) { if Verbose { log.Println("==> readIncoming: updateInFlight s.reading - false") } s.reading = false s.readErr = err // Retire any outgoing requests that were still in flight: with the Reader no // longer being processed, they necessarily cannot receive a response. for id, ac := range s.outgoingCalls { ac.retire(&Response{ID: id, Error: err}) } s.outgoingCalls = nil }) } // acceptRequest either handles msg synchronously or enqueues it to be handled // asynchronously. func (c *Connection) acceptRequest(ctx context.Context, msg *Request, msgBytes int64, preempter Preempter) { // In theory notifications cannot be cancelled, but we build them a cancel // context anyway. ctx, cancel := context.WithCancel(ctx) req := &incomingRequest{ Request: msg, ctx: ctx, cancel: cancel, } // If the request is a call, add it to the incoming map so it can be // cancelled (or responded) by ID. var err error c.updateInFlight(func(s *inFlightState) { s.incoming++ if req.IsCall() { if s.incomingByID[req.ID] != nil { err = fmt.Errorf("%w: request ID %v already in use", ErrInvalidRequest, req.ID) req.ID = ID{} // Don't misattribute this error to the existing request. return } if s.incomingByID == nil { s.incomingByID = make(map[ID]*incomingRequest) } s.incomingByID[req.ID] = req // When shutting down, reject all new Call requests, even if they could // theoretically be handled by the preempter. The preempter could return // ErrAsyncResponse, which would increase the amount of work in flight // when we're trying to ensure that it strictly decreases. err = s.shuttingDown(ErrServerClosing) } }) if err != nil { c.processResult("acceptRequest", req, nil, err) return } if preempter != nil { result, err := preempter.Preempt(req.ctx, req.Request) if req.IsCall() && errors.Is(err, ErrAsyncResponse) { // This request will remain in flight until Respond is called for it. return } if !errors.Is(err, ErrNotHandled) { c.processResult("Preempt", req, result, err) return } } c.updateInFlight(func(s *inFlightState) { // If the connection is shutting down, don't enqueue anything to the // handler — not even notifications. That ensures that if the handler // continues to make progress, it will eventually become idle and // close the connection. err = s.shuttingDown(ErrServerClosing) if err != nil { return } // We enqueue requests that have not been preempted to an unbounded slice. // Unfortunately, we cannot in general limit the size of the handler // queue: we have to read every response that comes in on the wire // (because it may be responding to a request issued by, say, an // asynchronous handler), and in order to get to that response we have // to read all of the requests that came in ahead of it. s.handlerQueue = append(s.handlerQueue, req) if !s.handlerRunning { // We start the handleAsync goroutine when it has work to do, and let it // exit when the queue empties. // // Otherwise, in order to synchronize the handler we would need some other // goroutine (probably readIncoming?) to explicitly wait for handleAsync // to finish, and that would complicate error reporting: either the error // report from the goroutine would be blocked on the handler emptying its // queue (which was tried, and introduced a deadlock detected by // TestCloseCallRace), or the error would need to be reported separately // from synchronizing completion. Allowing the handler goroutine to exit // when idle seems simpler than trying to implement either of those // alternatives correctly. s.handlerRunning = true go c.handleAsync() } }) if err != nil { c.processResult("acceptRequest", req, nil, err) } } // handleAsync invokes the handler on the requests in the handler queue // sequentially until the queue is empty. func (c *Connection) handleAsync() { for { var req *incomingRequest c.updateInFlight(func(s *inFlightState) { if len(s.handlerQueue) > 0 { req, s.handlerQueue = s.handlerQueue[0], s.handlerQueue[1:] } else { s.handlerRunning = false } }) if req == nil { return } // Only deliver to the Handler if not already canceled. if err := req.ctx.Err(); err != nil { c.updateInFlight(func(s *inFlightState) { if s.writeErr != nil { // Assume that req.ctx was canceled due to s.writeErr. // TODO(#51365): use a Context API to plumb this through req.ctx. err = fmt.Errorf("%w: %v", ErrServerClosing, s.writeErr) } }) c.processResult("handleAsync", req, nil, err) continue } if debugCall { req := req.Request id := req.ID if !id.IsValid() { id = StringID("") } log.Println("handleAsync", id, req.Method, string(req.Params)) } result, err := c.handler.Handle(req.ctx, req.Request) c.processResult(c.handler, req, result, err) } } // processResult processes the result of a request and, if appropriate, sends a response. func (c *Connection) processResult(from any, req *incomingRequest, result any, err error) error { switch err { case ErrAsyncResponse: if !req.IsCall() { return c.internalErrorf("%#v returned ErrAsyncResponse for a %q Request without an ID", from, req.Method) } return nil // This request is still in flight, so don't record the result yet. case ErrNotHandled, ErrMethodNotFound: // Add detail describing the unhandled method. err = fmt.Errorf("%w: %q", ErrMethodNotFound, req.Method) } if result != nil && err != nil { c.internalErrorf("%#v returned a non-nil result with a non-nil error for %s:\n%v\n%#v", from, req.Method, err, result) result = nil // Discard the spurious result and respond with err. } if req.IsCall() { response, respErr := NewResponse(req.ID, result, err) if debugCall { log.Println("processResult", response.ID, string(response.Result), response.Error) } // The caller could theoretically reuse the request's ID as soon as we've // sent the response, so ensure that it is removed from the incoming map // before sending. c.updateInFlight(func(s *inFlightState) { delete(s.incomingByID, req.ID) }) if respErr == nil { writeErr := c.write(notDone{req.ctx}, response) if err == nil { err = writeErr } } else { err = c.internalErrorf("%#v returned a malformed result for %q: %w", from, req.Method, respErr) } } else { // req is a notification if result != nil { err = c.internalErrorf("%#v returned a non-nil result for a %q Request without an ID", from, req.Method) } else if err != nil { err = fmt.Errorf("%w: %q notification failed: %v", ErrInternal, req.Method, err) } } _ = err // Cancel the request and finalize the event span to free any associated resources. req.cancel() c.updateInFlight(func(s *inFlightState) { if s.incoming == 0 { panic("jsonrpc2_v2: processResult called when incoming count is already zero") } s.incoming-- }) return nil } // write is used by all things that write outgoing messages, including replies. // it makes sure that writes are atomic func (c *Connection) write(ctx context.Context, msg Message) error { writer := <-c.writer defer func() { c.writer <- writer }() _, err := writer.Write(ctx, msg) if err != nil && ctx.Err() == nil { // The call to Write failed, and since ctx.Err() is nil we can't attribute // the failure (even indirectly) to Context cancellation. The writer appears // to be broken, and future writes are likely to also fail. // // If the read side of the connection is also broken, we might not even be // able to receive cancellation notifications. Since we can't reliably write // the results of incoming calls and can't receive explicit cancellations, // cancel the calls now. c.updateInFlight(func(s *inFlightState) { if s.writeErr == nil { s.writeErr = err for _, r := range s.incomingByID { r.cancel() } } }) } return err } // internalErrorf reports an internal error. By default it panics, but if // c.onInternalError is non-nil it instead calls that and returns an error // wrapping ErrInternal. func (c *Connection) internalErrorf(format string, args ...any) error { err := fmt.Errorf(format, args...) c.onInternalError(err) return fmt.Errorf("%w: %v", ErrInternal, err) } // notDone is a context.Context wrapper that returns a nil Done channel. type notDone struct{ ctx context.Context } func (ic notDone) Value(key any) any { return ic.ctx.Value(key) } func (notDone) Done() <-chan struct{} { return nil } func (notDone) Err() error { return nil } func (notDone) Deadline() (time.Time, bool) { return time.Time{}, false } ================================================ FILE: x/jsonrpc2/frame.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package jsonrpc2 import ( "bufio" "context" "fmt" "io" "strconv" "strings" ) // Reader abstracts the transport mechanics from the JSON RPC protocol. // A Conn reads messages from the reader it was provided on construction, // and assumes that each call to Read fully transfers a single message, // or returns an error. // A reader is not safe for concurrent use, it is expected it will be used by // a single Conn in a safe manner. type Reader interface { // Read gets the next message from the stream. Read(context.Context) (Message, int64, error) } // Writer abstracts the transport mechanics from the JSON RPC protocol. // A Conn writes messages using the writer it was provided on construction, // and assumes that each call to Write fully transfers a single message, // or returns an error. // A writer is not safe for concurrent use, it is expected it will be used by // a single Conn in a safe manner. type Writer interface { // Write sends a message to the stream. Write(context.Context, Message) (int64, error) } // Framer wraps low level byte readers and writers into jsonrpc2 message // readers and writers. // It is responsible for the framing and encoding of messages into wire form. type Framer interface { // Reader wraps a byte reader into a message reader. Reader(rw io.Reader) Reader // Writer wraps a byte writer into a message writer. Writer(rw io.Writer) Writer } // HeaderFramer returns a new Framer. // The messages are sent with HTTP content length and MIME type headers. // This is the format used by LSP and others. func HeaderFramer() Framer { return headerFramer{} } type headerFramer struct{} type headerReader struct{ in *bufio.Reader } type headerWriter struct{ out io.Writer } func (headerFramer) Reader(rw io.Reader) Reader { return &headerReader{in: bufio.NewReader(rw)} } func (headerFramer) Writer(rw io.Writer) Writer { return &headerWriter{out: rw} } func (r *headerReader) Read(ctx context.Context) (Message, int64, error) { select { case <-ctx.Done(): return nil, 0, ctx.Err() default: } var total, length int64 // read the header, stop on the first empty line for { line, err := r.in.ReadString('\n') total += int64(len(line)) if err != nil { if err == io.EOF { if total == 0 { return nil, 0, io.EOF } err = io.ErrUnexpectedEOF } return nil, total, fmt.Errorf("failed reading header line: %w", err) } line = strings.TrimSpace(line) // check we have a header line if line == "" { break } colon := strings.IndexRune(line, ':') if colon < 0 { return nil, total, fmt.Errorf("invalid header line %q", line) } name, value := line[:colon], strings.TrimSpace(line[colon+1:]) switch name { case "Content-Length": if length, err = strconv.ParseInt(value, 10, 32); err != nil { return nil, total, fmt.Errorf("failed parsing Content-Length: %v", value) } if length <= 0 { return nil, total, fmt.Errorf("invalid Content-Length: %v", length) } default: // ignoring unknown headers } } if length == 0 { return nil, total, fmt.Errorf("missing Content-Length header") } data := make([]byte, length) n, err := io.ReadFull(r.in, data) total += int64(n) if err != nil { return nil, total, err } msg, err := DecodeMessage(data) return msg, total, err } func (w *headerWriter) Write(ctx context.Context, msg Message) (int64, error) { select { case <-ctx.Done(): return 0, ctx.Err() default: } data, err := EncodeMessage(msg) if err != nil { return 0, fmt.Errorf("marshaling message: %v", err) } n, err := fmt.Fprintf(w.out, "Content-Length: %v\r\n\r\n", len(data)) total := int64(n) if err == nil { n, err = w.out.Write(data) total += int64(n) } return total, err } ================================================ FILE: x/jsonrpc2/internal/stack/parse.go ================================================ // Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package stack import ( "bufio" "io" "regexp" "strconv" ) var ( reBlank = regexp.MustCompile(`^\s*$`) reGoroutine = regexp.MustCompile(`^\s*goroutine (\d+) \[([^\]]*)\]:\s*$`) reCall = regexp.MustCompile(`^\s*` + `(created by )?` + //marker `(([\w/.]+/)?[\w]+)\.` + //package `(\(([^:.)]*)\)\.)?` + //optional type `([\w\.]+)` + //function `(\(.*\))?` + // args `\s*$`) rePos = regexp.MustCompile(`^\s*(.*):(\d+)( .*)?$`) ) // Scanner splits an input stream into lines in a way that is consumable by // the parser. type Scanner struct { lines *bufio.Scanner done bool } // NewScanner creates a scanner on top of a reader. func NewScanner(r io.Reader) *Scanner { s := &Scanner{ lines: bufio.NewScanner(r), } s.Skip() // prefill return s } // Peek returns the next line without consuming it. func (s *Scanner) Peek() string { if s.done { return "" } return s.lines.Text() } // Skip consumes the next line without looking at it. // Normally used after it has already been looked at using Peek. func (s *Scanner) Skip() { if !s.lines.Scan() { s.done = true } } // Next consumes and returns the next line. func (s *Scanner) Next() string { line := s.Peek() s.Skip() return line } // Done returns true if the scanner has reached the end of the underlying // stream. func (s *Scanner) Done() bool { return s.done } // Err returns true if the scanner has reached the end of the underlying // stream. func (s *Scanner) Err() error { return s.lines.Err() } // Match returns the submatchs of the regular expression against the next line. // If it matched the line is also consumed. func (s *Scanner) Match(re *regexp.Regexp) []string { if s.done { return nil } match := re.FindStringSubmatch(s.Peek()) if match != nil { s.Skip() } return match } // SkipBlank skips any number of pure whitespace lines. func (s *Scanner) SkipBlank() { for !s.done { line := s.Peek() if len(line) != 0 && !reBlank.MatchString(line) { return } s.Skip() } } // Parse the current contiguous block of goroutine stack traces until the // scanned content no longer matches. func Parse(scanner *Scanner) (Dump, error) { dump := Dump{} for { gr, ok := parseGoroutine(scanner) if !ok { return dump, nil } dump = append(dump, gr) } } func parseGoroutine(scanner *Scanner) (Goroutine, bool) { match := scanner.Match(reGoroutine) if match == nil { return Goroutine{}, false } id, _ := strconv.ParseInt(match[1], 0, 32) gr := Goroutine{ ID: int(id), State: match[2], } for { frame, ok := parseFrame(scanner) if !ok { scanner.SkipBlank() return gr, true } if frame.Position.Filename != "" { gr.Stack = append(gr.Stack, frame) } } } func parseFrame(scanner *Scanner) (Frame, bool) { fun, ok := parseFunction(scanner) if !ok { return Frame{}, false } frame := Frame{ Function: fun, } frame.Position, ok = parsePosition(scanner) // if ok is false, then this is a broken state. // we got the func but not the file that must follow // the consumed line can be recovered from the frame //TODO: push back the fun raw return frame, ok } func parseFunction(scanner *Scanner) (Function, bool) { match := scanner.Match(reCall) if match == nil { return Function{}, false } return Function{ Package: match[2], Type: match[5], Name: match[6], }, true } func parsePosition(scanner *Scanner) (Position, bool) { match := scanner.Match(rePos) if match == nil { return Position{}, false } line, _ := strconv.ParseInt(match[2], 0, 32) return Position{Filename: match[1], Line: int(line)}, true } ================================================ FILE: x/jsonrpc2/internal/stack/process.go ================================================ // Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package stack import ( "bytes" "fmt" "io" "runtime" "sort" ) // Capture get the current stack traces from the runtime. func Capture() Dump { buf := make([]byte, 2<<20) buf = buf[:runtime.Stack(buf, true)] scanner := NewScanner(bytes.NewReader(buf)) dump, _ := Parse(scanner) return dump } // Summarize a dump for easier consumption. // This collates goroutines with equivalent stacks. func Summarize(dump Dump) Summary { s := Summary{ Total: len(dump), } for _, gr := range dump { s.addGoroutine(gr) } return s } // Process and input stream to an output stream, summarizing any stacks that // are detected in place. func Process(out io.Writer, in io.Reader) error { scanner := NewScanner(in) for { dump, err := Parse(scanner) summary := Summarize(dump) switch { case len(dump) > 0: fmt.Fprintf(out, "%+v\n\n", summary) case err != nil: return err case scanner.Done(): return scanner.Err() default: // must have been a line that is not part of a dump fmt.Fprintln(out, scanner.Next()) } } } // Diff calculates the delta between two dumps. func Diff(before, after Dump) Delta { result := Delta{} processed := make(map[int]bool) for _, gr := range before { processed[gr.ID] = false } for _, gr := range after { if _, found := processed[gr.ID]; found { result.Shared = append(result.Shared, gr) } else { result.After = append(result.After, gr) } processed[gr.ID] = true } for _, gr := range before { if done := processed[gr.ID]; !done { result.Before = append(result.Before, gr) } } return result } // TODO: do we want to allow contraction of stacks before comparison? func (s *Summary) addGoroutine(gr Goroutine) { index := sort.Search(len(s.Calls), func(i int) bool { return !s.Calls[i].Stack.less(gr.Stack) }) if index >= len(s.Calls) || !s.Calls[index].Stack.equal(gr.Stack) { // insert new stack, first increase the length s.Calls = append(s.Calls, Call{}) // move the top part upward to make space copy(s.Calls[index+1:], s.Calls[index:]) // insert the new call s.Calls[index] = Call{ Stack: gr.Stack, } } // merge the goroutine into the matched call s.Calls[index].merge(gr) } // TODO: do we want other grouping strategies? func (c *Call) merge(gr Goroutine) { for i := range c.Groups { canditate := &c.Groups[i] if canditate.State == gr.State { canditate.Goroutines = append(canditate.Goroutines, gr) return } } c.Groups = append(c.Groups, Group{ State: gr.State, Goroutines: []Goroutine{gr}, }) } ================================================ FILE: x/jsonrpc2/internal/stack/stack.go ================================================ // Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package stack provides support for parsing standard goroutine stack traces. package stack import ( "fmt" "text/tabwriter" ) // Dump is a raw set of goroutines and their stacks. type Dump []Goroutine // Goroutine is a single parsed goroutine dump. type Goroutine struct { State string // state that the goroutine is in. ID int // id of the goroutine. Stack Stack // call frames that make up the stack } // Stack is a set of frames in a callstack. type Stack []Frame // Frame is a point in a call stack. type Frame struct { Function Function Position Position } // Function is the function called at a frame. type Function struct { Package string // package name of function if known Type string // if set function is a method of this type Name string // function name of the frame } // Position is the file position for a frame. type Position struct { Filename string // source filename Line int // line number within file } // Summary is a set of stacks processed and collated into Calls. type Summary struct { Total int // the total count of goroutines in the summary Calls []Call // the collated stack traces } // Call is set of goroutines that all share the same callstack. // They will be grouped by state. type Call struct { Stack Stack // the shared callstack information Groups []Group // the sets of goroutines with the same state } // Group is a set of goroutines with the same stack that are in the same state. type Group struct { State string // the shared state of the goroutines Goroutines []Goroutine // the set of goroutines in this group } // Delta represents the difference between two stack dumps. type Delta struct { Before Dump // The goroutines that were only in the before set. Shared Dump // The goroutines that were in both sets. After Dump // The goroutines that were only in the after set. } func (s Stack) equal(other Stack) bool { if len(s) != len(other) { return false } for i, frame := range s { if !frame.equal(other[i]) { return false } } return true } func (s Stack) less(other Stack) bool { for i, frame := range s { if i >= len(other) { return false } if frame.less(other[i]) { return true } if !frame.equal(other[i]) { return false } } return len(s) < len(other) } func (f Frame) equal(other Frame) bool { return f.Position.equal(other.Position) } func (f Frame) less(other Frame) bool { return f.Position.less(other.Position) } func (p Position) equal(other Position) bool { return p.Filename == other.Filename && p.Line == other.Line } func (p Position) less(other Position) bool { if p.Filename < other.Filename { return true } if p.Filename > other.Filename { return false } return p.Line < other.Line } func (s Summary) Format(w fmt.State, r rune) { tw := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0) for i, c := range s.Calls { if i > 0 { fmt.Fprintf(tw, "\n\n") tw.Flush() } fmt.Fprint(tw, c) } tw.Flush() if s.Total > 0 && w.Flag('+') { fmt.Fprintf(w, "\n\n%d goroutines, %d unique", s.Total, len(s.Calls)) } } func (c Call) Format(w fmt.State, r rune) { for i, g := range c.Groups { if i > 0 { fmt.Fprint(w, " ") } fmt.Fprint(w, g) } for _, f := range c.Stack { fmt.Fprintf(w, "\n%v", f) } } func (g Group) Format(w fmt.State, r rune) { fmt.Fprintf(w, "[%v]: ", g.State) for i, gr := range g.Goroutines { if i > 0 { fmt.Fprint(w, ", ") } fmt.Fprintf(w, "$%d", gr.ID) } } func (f Frame) Format(w fmt.State, c rune) { fmt.Fprintf(w, "%v:\t%v", f.Position, f.Function) } func (f Function) Format(w fmt.State, c rune) { if f.Type != "" { fmt.Fprintf(w, "(%v).", f.Type) } fmt.Fprintf(w, "%v", f.Name) } func (p Position) Format(w fmt.State, c rune) { fmt.Fprintf(w, "%v:%v", p.Filename, p.Line) } ================================================ FILE: x/jsonrpc2/internal/stack/stacktest/stacktest.go ================================================ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package stacktest import ( "testing" "time" "github.com/goplus/xgo/x/jsonrpc2/internal/stack" ) // this is only needed to support pre 1.14 when testing.TB did not have Cleanup type withCleanup interface { Cleanup(func()) } // the maximum amount of time to wait for goroutines to clean themselves up. const maxWait = time.Second // NoLeak checks that a test (or benchmark) does not leak any goroutines. func NoLeak(t testing.TB) { c, ok := t.(withCleanup) if !ok { return } before := stack.Capture() c.Cleanup(func() { var delta stack.Delta start := time.Now() delay := time.Millisecond for { after := stack.Capture() delta = stack.Diff(before, after) if len(delta.After) == 0 { // no leaks return } if time.Since(start) > maxWait { break } time.Sleep(delay) delay *= 2 } // it's been long enough, and leaks are still present summary := stack.Summarize(delta.After) t.Errorf("goroutine leak detected:\n%+v", summary) }) } ================================================ FILE: x/jsonrpc2/jsonrpc2.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package jsonrpc2 is a minimal implementation of the JSON RPC 2 spec. // https://www.jsonrpc.org/specification // It is intended to be compatible with other implementations at the wire level. package jsonrpc2 import ( "context" "errors" ) type dbgFlags int const ( DbgFlagVerbose dbgFlags = 1 << iota DbgFlagCall DbgFlagAll = DbgFlagVerbose | DbgFlagCall ) var ( Verbose bool debugCall bool ) // SetDebug sets debug flags. func SetDebug(flags dbgFlags) { Verbose = (flags & DbgFlagVerbose) != 0 debugCall = (flags & (DbgFlagCall | DbgFlagVerbose)) != 0 } var ( // ErrIdleTimeout is returned when serving timed out waiting for new connections. ErrIdleTimeout = errors.New("timed out waiting for new connections") // ErrNotHandled is returned from a Handler or Preempter to indicate it did // not handle the request. // // If a Handler returns ErrNotHandled, the server replies with // ErrMethodNotFound. ErrNotHandled = errors.New("JSON RPC not handled") // ErrAsyncResponse is returned from a handler to indicate it will generate a // response asynchronously. // // ErrAsyncResponse must not be returned for notifications, // which do not receive responses. ErrAsyncResponse = errors.New("JSON RPC asynchronous response") ) // Preempter handles messages on a connection before they are queued to the main // handler. // Primarily this is used for cancel handlers or notifications for which out of // order processing is not an issue. type Preempter interface { // Preempt is invoked for each incoming request before it is queued for handling. // // If Preempt returns ErrNotHandled, the request will be queued, // and eventually passed to a Handle call. // // Otherwise, the result and error are processed as if returned by Handle. // // Preempt must not block. (The Context passed to it is for Values only.) Preempt(ctx context.Context, req *Request) (result any, err error) } // A PreempterFunc implements the Preempter interface for a standalone Preempt function. type PreempterFunc func(ctx context.Context, req *Request) (any, error) func (f PreempterFunc) Preempt(ctx context.Context, req *Request) (any, error) { return f(ctx, req) } var _ Preempter = PreempterFunc(nil) // Handler handles messages on a connection. type Handler interface { // Handle is invoked sequentially for each incoming request that has not // already been handled by a Preempter. // // If the Request has a nil ID, Handle must return a nil result, // and any error may be logged but will not be reported to the caller. // // If the Request has a non-nil ID, Handle must return either a // non-nil, JSON-marshalable result, or a non-nil error. // // The Context passed to Handle will be canceled if the // connection is broken or the request is canceled or completed. // (If Handle returns ErrAsyncResponse, ctx will remain uncanceled // until either Cancel or Respond is called for the request's ID.) Handle(ctx context.Context, req *Request) (result any, err error) } type defaultHandler struct{} func (defaultHandler) Preempt(context.Context, *Request) (any, error) { return nil, ErrNotHandled } func (defaultHandler) Handle(context.Context, *Request) (any, error) { return nil, ErrNotHandled } // A HandlerFunc implements the Handler interface for a standalone Handle function. type HandlerFunc func(ctx context.Context, req *Request) (any, error) func (f HandlerFunc) Handle(ctx context.Context, req *Request) (any, error) { return f(ctx, req) } var _ Handler = HandlerFunc(nil) func defaultHandleError(err error) { panic("jsonrpc2: " + err.Error()) } // async is a small helper for operations with an asynchronous result that you // can wait for. type async struct { ready chan struct{} // closed when done firstErr chan error // 1-buffered; contains either nil or the first non-nil error } func newAsync() *async { var a async a.ready = make(chan struct{}) a.firstErr = make(chan error, 1) a.firstErr <- nil return &a } func (a *async) done() { close(a.ready) } func (a *async) wait() error { <-a.ready err := <-a.firstErr a.firstErr <- err return err } func (a *async) setError(err error) { storedErr := <-a.firstErr if storedErr == nil { storedErr = err } a.firstErr <- storedErr } ================================================ FILE: x/jsonrpc2/jsonrpc2test/cases/testcase.go ================================================ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package cases import ( "context" "encoding/json" "fmt" "log" "path" "reflect" "sync" "testing" "time" "github.com/goplus/xgo/x/jsonrpc2" "github.com/goplus/xgo/x/jsonrpc2/internal/stack/stacktest" ) var callTests = []invoker{ call{"no_args", nil, true}, call{"one_string", "fish", "got:fish"}, call{"one_number", 10, "got:10"}, call{"join", []string{"a", "b", "c"}, "a/b/c"}, sequence{"notify", []invoker{ notify{"set", 3}, notify{"add", 5}, call{"get", nil, 8}, }}, sequence{"preempt", []invoker{ async{"a", "wait", "a"}, notify{"unblock", "a"}, collect{"a", true, false}, }}, sequence{"basic cancel", []invoker{ async{"b", "wait", "b"}, cancel{"b"}, collect{"b", nil, true}, }}, sequence{"queue", []invoker{ async{"a", "wait", "a"}, notify{"set", 1}, notify{"add", 2}, notify{"add", 3}, notify{"add", 4}, // call{"peek", nil, 0}, // accumulator will not have any adds yet notify{"unblock", "a"}, collect{"a", true, false}, call{"get", nil, 10}, // accumulator now has all the adds }}, sequence{"fork", []invoker{ async{"a", "fork", "a"}, notify{"set", 1}, notify{"add", 2}, notify{"add", 3}, notify{"add", 4}, call{"get", nil, 10}, // fork will not have blocked the adds notify{"unblock", "a"}, collect{"a", true, false}, }}, sequence{"concurrent", []invoker{ async{"a", "fork", "a"}, notify{"unblock", "a"}, async{"b", "fork", "b"}, notify{"unblock", "b"}, collect{"a", true, false}, collect{"b", true, false}, }}, } type binder struct { framer jsonrpc2.Framer runTest func(*handler) } type handler struct { conn *jsonrpc2.Connection accumulator int mutex sync.Mutex waiters map[string]chan struct{} calls map[string]*jsonrpc2.AsyncCall } type invoker interface { Name() string Invoke(t *testing.T, ctx context.Context, h *handler) } type notify struct { method string params any } type call struct { method string params any expect any } type async struct { name string method string params any } type collect struct { name string expect any fails bool } type cancel struct { name string } type sequence struct { name string tests []invoker } type echo call type cancelParams struct{ ID int64 } func Test(t *testing.T, ctx context.Context, listener jsonrpc2.Listener, framer jsonrpc2.Framer, noLeak bool) { if noLeak { stacktest.NoLeak(t) } server := jsonrpc2.NewServer(ctx, listener, binder{framer, nil}) defer func() { listener.Close() if noLeak { server.Wait() } }() for _, test := range callTests { t.Run(test.Name(), func(t *testing.T) { client, err := jsonrpc2.Dial(ctx, listener.Dialer(), binder{framer, func(h *handler) { defer h.conn.Close() ctx := context.Background() test.Invoke(t, ctx, h) if call, ok := test.(*call); ok { // also run all simple call tests in echo mode (*echo)(call).Invoke(t, ctx, h) } }}, nil) if err != nil { t.Fatal(err) } client.Wait() }) } } func (test notify) Name() string { return test.method } func (test notify) Invoke(t *testing.T, ctx context.Context, h *handler) { if err := h.conn.Notify(ctx, test.method, test.params); err != nil { t.Fatalf("%v:Notify failed: %v", test.method, err) } } func (test call) Name() string { return test.method } func (test call) Invoke(t *testing.T, ctx context.Context, h *handler) { results := newResults(test.expect) if err := h.conn.Call(ctx, test.method, test.params).Await(ctx, results); err != nil { t.Fatalf("%v:Call failed: %v", test.method, err) } verifyResults(t, test.method, results, test.expect) } func (test echo) Invoke(t *testing.T, ctx context.Context, h *handler) { results := newResults(test.expect) if err := h.conn.Call(ctx, "echo", []any{test.method, test.params}).Await(ctx, results); err != nil { t.Fatalf("%v:Echo failed: %v", test.method, err) } verifyResults(t, test.method, results, test.expect) } func (test async) Name() string { return test.name } func (test async) Invoke(t *testing.T, ctx context.Context, h *handler) { h.calls[test.name] = h.conn.Call(ctx, test.method, test.params) } func (test collect) Name() string { return test.name } func (test collect) Invoke(t *testing.T, ctx context.Context, h *handler) { o := h.calls[test.name] results := newResults(test.expect) err := o.Await(ctx, results) switch { case test.fails && err == nil: t.Fatalf("%v:Collect was supposed to fail", test.name) case !test.fails && err != nil: t.Fatalf("%v:Collect failed: %v", test.name, err) } verifyResults(t, test.name, results, test.expect) } func (test cancel) Name() string { return test.name } func (test cancel) Invoke(t *testing.T, ctx context.Context, h *handler) { o := h.calls[test.name] if err := h.conn.Notify(ctx, "cancel", &cancelParams{o.ID().Raw().(int64)}); err != nil { t.Fatalf("%v:Collect failed: %v", test.name, err) } } func (test sequence) Name() string { return test.name } func (test sequence) Invoke(t *testing.T, ctx context.Context, h *handler) { for _, child := range test.tests { child.Invoke(t, ctx, h) } } // newResults makes a new empty copy of the expected type to put the results into func newResults(expect any) any { switch e := expect.(type) { case []any: var r []any for _, v := range e { r = append(r, reflect.New(reflect.TypeOf(v)).Interface()) } return r case nil: return nil default: return reflect.New(reflect.TypeOf(expect)).Interface() } } // verifyResults compares the results to the expected values func verifyResults(t *testing.T, method string, results any, expect any) { if expect == nil { if results != nil { t.Errorf("%v:Got results %+v where none expeted", method, expect) } return } val := reflect.Indirect(reflect.ValueOf(results)).Interface() if !reflect.DeepEqual(val, expect) { t.Errorf("%v:Results are incorrect, got %+v expect %+v", method, val, expect) } } func (b binder) Bind(ctx context.Context, conn *jsonrpc2.Connection) jsonrpc2.ConnectionOptions { h := &handler{ conn: conn, waiters: make(map[string]chan struct{}), calls: make(map[string]*jsonrpc2.AsyncCall), } if b.runTest != nil { go b.runTest(h) } return jsonrpc2.ConnectionOptions{ Framer: b.framer, Preempter: h, Handler: h, } } func (h *handler) waiter(name string) chan struct{} { log.Println("waiter:", name) h.mutex.Lock() defer h.mutex.Unlock() waiter := make(chan struct{}) h.waiters[name] = waiter return waiter } func (h *handler) closeWaiter(name string) { log.Println("closeWaiter:", name) for !h.tryCloseWaiter(name) { time.Sleep(time.Millisecond) } } func (h *handler) tryCloseWaiter(name string) (ok bool) { h.mutex.Lock() defer h.mutex.Unlock() waiter, ok := h.waiters[name] if ok { delete(h.waiters, name) close(waiter) } return } func (h *handler) Preempt(ctx context.Context, req *jsonrpc2.Request) (any, error) { switch req.Method { case "unblock": var name string if err := json.Unmarshal(req.Params, &name); err != nil { return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) } h.closeWaiter(name) return nil, nil case "peek": if len(req.Params) > 0 { return nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams) } return h.accumulator, nil case "cancel": var params cancelParams if err := json.Unmarshal(req.Params, ¶ms); err != nil { return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) } h.conn.Cancel(jsonrpc2.Int64ID(params.ID)) return nil, nil default: return nil, jsonrpc2.ErrNotHandled } } func (h *handler) Handle(ctx context.Context, req *jsonrpc2.Request) (any, error) { switch req.Method { case "no_args": if len(req.Params) > 0 { return nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams) } return true, nil case "one_string": var v string if err := json.Unmarshal(req.Params, &v); err != nil { return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) } return "got:" + v, nil case "one_number": var v int if err := json.Unmarshal(req.Params, &v); err != nil { return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) } return fmt.Sprintf("got:%d", v), nil case "set": var v int if err := json.Unmarshal(req.Params, &v); err != nil { return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) } h.accumulator = v return nil, nil case "add": var v int if err := json.Unmarshal(req.Params, &v); err != nil { return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) } h.accumulator += v return nil, nil case "get": if len(req.Params) > 0 { return nil, fmt.Errorf("%w: expected no params", jsonrpc2.ErrInvalidParams) } return h.accumulator, nil case "join": var v []string if err := json.Unmarshal(req.Params, &v); err != nil { return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) } return path.Join(v...), nil case "echo": var v []any if err := json.Unmarshal(req.Params, &v); err != nil { return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) } var result any err := h.conn.Call(ctx, v[0].(string), v[1]).Await(ctx, &result) return result, err case "wait": var name string if err := json.Unmarshal(req.Params, &name); err != nil { return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) } select { case <-h.waiter(name): return true, nil case <-ctx.Done(): return nil, ctx.Err() } case "fork": var name string if err := json.Unmarshal(req.Params, &name); err != nil { return nil, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err) } waitFor := h.waiter(name) go func() { select { case <-waitFor: h.conn.Respond(req.ID, true, nil) case <-ctx.Done(): h.conn.Respond(req.ID, nil, ctx.Err()) } }() return nil, jsonrpc2.ErrAsyncResponse default: return nil, jsonrpc2.ErrNotHandled } } ================================================ FILE: x/jsonrpc2/jsonrpc2test/jsonrpc2_test.go ================================================ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package jsonrpc2test_test import ( "context" "testing" "github.com/goplus/xgo/x/jsonrpc2" "github.com/goplus/xgo/x/jsonrpc2/jsonrpc2test" "github.com/goplus/xgo/x/jsonrpc2/jsonrpc2test/cases" ) func TestNetPipe(t *testing.T) { jsonrpc2.SetDebug(jsonrpc2.DbgFlagCall) ctx := context.Background() listener := jsonrpc2test.NetPipeListener() cases.Test(t, ctx, listener, jsonrpc2.HeaderFramer(), true) } ================================================ FILE: x/jsonrpc2/jsonrpc2test/pipe.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package jsonrpc2test import ( "context" "io" "net" "github.com/goplus/xgo/x/jsonrpc2" ) // NetPipeListener returns a new Listener that listens using net.Pipe. // It is only possibly to connect to it using the Dialer returned by the // Dialer method, each call to that method will generate a new pipe the other // side of which will be returned from the Accept call. func NetPipeListener() jsonrpc2.Listener { return &netPiper{ done: make(chan struct{}), dialed: make(chan io.ReadWriteCloser), } } // netPiper is the implementation of Listener build on top of net.Pipes. type netPiper struct { done chan struct{} dialed chan io.ReadWriteCloser } // Accept blocks waiting for an incoming connection to the listener. func (l *netPiper) Accept(context.Context) (io.ReadWriteCloser, error) { // Block until the pipe is dialed or the listener is closed, // preferring the latter if already closed at the start of Accept. select { case <-l.done: return nil, net.ErrClosed default: } select { case rwc := <-l.dialed: return rwc, nil case <-l.done: return nil, net.ErrClosed } } // Close will cause the listener to stop listening. It will not close any connections that have // already been accepted. func (l *netPiper) Close() error { // unblock any accept calls that are pending close(l.done) return nil } func (l *netPiper) Dialer() jsonrpc2.Dialer { return l } func (l *netPiper) Dial(ctx context.Context) (io.ReadWriteCloser, error) { client, server := net.Pipe() select { case l.dialed <- server: return client, nil case <-l.done: client.Close() server.Close() return nil, net.ErrClosed } } ================================================ FILE: x/jsonrpc2/jsonrpc2test/pipe_test.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jsonrpc2test import ( "context" "io" "net" "testing" ) func TestNetPipeDone(t *testing.T) { np := &netPiper{ done: make(chan struct{}, 1), dialed: make(chan io.ReadWriteCloser), } np.done <- struct{}{} if f, err := np.Accept(context.Background()); f != nil || err != net.ErrClosed { t.Fatal("np.Accept:", f, err) } np.done <- struct{}{} if f, err := np.Dial(context.Background()); f != nil || err != net.ErrClosed { t.Fatal("np.Dial:", f, err) } } ================================================ FILE: x/jsonrpc2/messages.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package jsonrpc2 import ( "encoding/json" "errors" "fmt" ) // ID is a Request identifier. type ID struct { value any } // Message is the interface to all jsonrpc2 message types. // They share no common functionality, but are a closed set of concrete types // that are allowed to implement this interface. The message types are *Request // and *Response. type Message interface { // marshal builds the wire form from the API form. // It is private, which makes the set of Message implementations closed. marshal(to *wireCombined) } // Request is a Message sent to a peer to request behavior. // If it has an ID it is a call, otherwise it is a notification. type Request struct { // ID of this request, used to tie the Response back to the request. // This will be nil for notifications. ID ID // Method is a string containing the method name to invoke. Method string // Params is either a struct or an array with the parameters of the method. Params json.RawMessage } // Response is a Message used as a reply to a call Request. // It will have the same ID as the call it is a response to. type Response struct { // result is the content of the response. Result json.RawMessage // err is set only if the call failed. Error error // id of the request this is a response to. ID ID } // StringID creates a new string request identifier. func StringID(s string) ID { return ID{value: s} } // Int64ID creates a new integer request identifier. func Int64ID(i int64) ID { return ID{value: i} } // IsValid returns true if the ID is a valid identifier. // The default value for ID will return false. func (id ID) IsValid() bool { return id.value != nil } // Raw returns the underlying value of the ID. func (id ID) Raw() any { return id.value } // NewNotification constructs a new Notification message for the supplied // method and parameters. func NewNotification(method string, params any) (*Request, error) { p, merr := marshalToRaw(params) return &Request{Method: method, Params: p}, merr } // NewCall constructs a new Call message for the supplied ID, method and // parameters. func NewCall(id ID, method string, params any) (*Request, error) { p, merr := marshalToRaw(params) return &Request{ID: id, Method: method, Params: p}, merr } func (msg *Request) IsCall() bool { return msg.ID.IsValid() } func (msg *Request) marshal(to *wireCombined) { to.ID = msg.ID.value to.Method = msg.Method to.Params = msg.Params } // NewResponse constructs a new Response message that is a reply to the // supplied. If err is set result may be ignored. func NewResponse(id ID, result any, rerr error) (*Response, error) { r, merr := marshalToRaw(result) return &Response{ID: id, Result: r, Error: rerr}, merr } func (msg *Response) marshal(to *wireCombined) { to.ID = msg.ID.value to.Error = toWireError(msg.Error) to.Result = msg.Result } func toWireError(err error) *wireError { if err == nil { // no error, the response is complete return nil } if err, ok := err.(*wireError); ok { // already a wire error, just use it return err } result := &wireError{Message: err.Error()} var wrapped *wireError if errors.As(err, &wrapped) { // if we wrapped a wire error, keep the code from the wrapped error // but the message from the outer error result.Code = wrapped.Code } return result } func EncodeMessage(msg Message) ([]byte, error) { wire := wireCombined{VersionTag: wireVersion} msg.marshal(&wire) data, err := json.Marshal(&wire) if err != nil { return data, fmt.Errorf("marshaling jsonrpc message: %w", err) } return data, nil } func DecodeMessage(data []byte) (Message, error) { msg := wireCombined{} if err := json.Unmarshal(data, &msg); err != nil { return nil, fmt.Errorf("unmarshaling jsonrpc message: %w", err) } if msg.VersionTag != wireVersion { return nil, fmt.Errorf("invalid message version tag %s expected %s", msg.VersionTag, wireVersion) } id := ID{} switch v := msg.ID.(type) { case nil: case float64: // coerce the id type to int64 if it is float64, the spec does not allow fractional parts id = Int64ID(int64(v)) case int64: id = Int64ID(v) case string: id = StringID(v) default: return nil, fmt.Errorf("invalid message id type <%T>%v", v, v) } if msg.Method != "" { // has a method, must be a call return &Request{ Method: msg.Method, ID: id, Params: msg.Params, }, nil } // no method, should be a response if !id.IsValid() { return nil, ErrInvalidRequest } resp := &Response{ ID: id, Result: msg.Result, } // we have to check if msg.Error is nil to avoid a typed error if msg.Error != nil { resp.Error = msg.Error } return resp, nil } func marshalToRaw(obj any) (json.RawMessage, error) { if obj == nil { return nil, nil } data, err := json.Marshal(obj) if err != nil { return nil, err } return json.RawMessage(data), nil } ================================================ FILE: x/jsonrpc2/serve.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package jsonrpc2 import ( "context" "fmt" "io" "log" "runtime" "sync" "sync/atomic" "time" ) // Listener is implemented by protocols to accept new inbound connections. type Listener interface { // Accept accepts an inbound connection to a server. // It blocks until either an inbound connection is made, or the listener is closed. Accept(context.Context) (io.ReadWriteCloser, error) // Close closes the listener. // Any blocked Accept or Dial operations will unblock and return errors. Close() error // Dialer returns a dialer that can be used to connect to this listener // locally. // If a listener does not implement this it will return nil. Dialer() Dialer } // Dialer is used by clients to dial a server. type Dialer interface { // Dial returns a new communication byte stream to a listening server. Dial(ctx context.Context) (io.ReadWriteCloser, error) } // Server is a running server that is accepting incoming connections. type Server struct { listener Listener binder Binder async *async shutdownOnce sync.Once closing int32 // atomic: set to nonzero when Shutdown is called } // Dial uses the dialer to make a new connection, wraps the returned // reader and writer using the framer to make a stream, and then builds // a connection on top of that stream using the binder. // // The returned Connection will operate independently using the Preempter and/or // Handler provided by the Binder, and will release its own resources when the // connection is broken, but the caller may Close it earlier to stop accepting // (or sending) new requests. func Dial(ctx context.Context, dialer Dialer, binder Binder, onDone func()) (*Connection, error) { // dial a server rwc, err := dialer.Dial(ctx) if err != nil { return nil, err } return newConnection(ctx, rwc, binder, onDone), nil } // NewServer starts a new server listening for incoming connections and returns // it. // This returns a fully running and connected server, it does not block on // the listener. // You can call Wait to block on the server, or Shutdown to get the sever to // terminate gracefully. // To notice incoming connections, use an intercepting Binder. func NewServer(ctx context.Context, listener Listener, binder Binder) *Server { server := &Server{ listener: listener, binder: binder, async: newAsync(), } go server.run(ctx) return server } // Wait returns only when the server has shut down. func (s *Server) Wait() (err error) { if Verbose { log.Println("==> Server.Wait start") } err = s.async.wait() if Verbose { log.Println("==> Server.Wait end:", err) } return } // Shutdown informs the server to stop accepting new connections. func (s *Server) Shutdown() { s.shutdownOnce.Do(func() { atomic.StoreInt32(&s.closing, 1) s.listener.Close() }) } // run accepts incoming connections from the listener, // If IdleTimeout is non-zero, run exits after there are no clients for this // duration, otherwise it exits only on error. func (s *Server) run(ctx context.Context) { defer s.async.done() var activeConns sync.WaitGroup for { rwc, err := s.listener.Accept(ctx) if err != nil { // Only Shutdown closes the listener. If we get an error after Shutdown is // called, assume that was the cause and don't report the error; // otherwise, report the error in case it is unexpected. if atomic.LoadInt32(&s.closing) == 0 { s.async.setError(err) } // We are done generating new connections for good. break } // A new inbound connection. activeConns.Add(1) _ = newConnection(ctx, rwc, s.binder, activeConns.Done) // unregisters itself when done } activeConns.Wait() } // NewIdleListener wraps a listener with an idle timeout. // // When there are no active connections for at least the timeout duration, // calls to Accept will fail with ErrIdleTimeout. // // A connection is considered inactive as soon as its Close method is called. func NewIdleListener(timeout time.Duration, wrap Listener) Listener { l := &idleListener{ wrapped: wrap, timeout: timeout, active: make(chan int, 1), timedOut: make(chan struct{}), idleTimer: make(chan *time.Timer, 1), } l.idleTimer <- time.AfterFunc(l.timeout, l.timerExpired) return l } type idleListener struct { wrapped Listener timeout time.Duration // Only one of these channels is receivable at any given time. active chan int // count of active connections; closed when Close is called if not timed out timedOut chan struct{} // closed when the idle timer expires idleTimer chan *time.Timer // holds the timer only when idle } // Accept accepts an incoming connection. // // If an incoming connection is accepted concurrent to the listener being closed // due to idleness, the new connection is immediately closed. func (l *idleListener) Accept(ctx context.Context) (io.ReadWriteCloser, error) { rwc, err := l.wrapped.Accept(ctx) select { case n, ok := <-l.active: if err != nil { if ok { l.active <- n } return nil, err } if ok { l.active <- n + 1 } else { // l.wrapped.Close Close has been called, but Accept returned a // connection. This race can occur with concurrent Accept and Close calls // with any net.Listener, and it is benign: since the listener was closed // explicitly, it can't have also timed out. _ = 0 } return l.newConn(rwc), nil case <-l.timedOut: if err == nil { // Keeping the connection open would leave the listener simultaneously // active and closed due to idleness, which would be contradictory and // confusing. Close the connection and pretend that it never happened. rwc.Close() } else { // In theory the timeout could have raced with an unrelated error return // from Accept. However, ErrIdleTimeout is arguably still valid (since we // would have closed due to the timeout independent of the error), and the // harm from returning a spurious ErrIdleTimeout is negligible anyway. _ = 0 } return nil, ErrIdleTimeout case timer := <-l.idleTimer: if err != nil { // The idle timer doesn't run until it receives itself from the idleTimer // channel, so it can't have called l.wrapped.Close yet and thus err can't // be ErrIdleTimeout. Leave the idle timer as it was and return whatever // error we got. l.idleTimer <- timer return nil, err } if !timer.Stop() { // Failed to stop the timer — the timer goroutine is in the process of // firing. Send the timer back to the timer goroutine so that it can // safely close the timedOut channel, and then wait for the listener to // actually be closed before we return ErrIdleTimeout. l.idleTimer <- timer rwc.Close() <-l.timedOut return nil, ErrIdleTimeout } l.active <- 1 return l.newConn(rwc), nil } } func (l *idleListener) Close() error { select { case _, ok := <-l.active: if ok { close(l.active) } case <-l.timedOut: // Already closed by the timer; take care not to double-close if the caller // only explicitly invokes this Close method once, since the io.Closer // interface explicitly leaves doubled Close calls undefined. return ErrIdleTimeout case timer := <-l.idleTimer: if !timer.Stop() { // Couldn't stop the timer. It shouldn't take long to run, so just wait // (so that the Listener is guaranteed to be closed before we return) // and pretend that this call happened afterward. // That way we won't leak any timers or goroutines when Close returns. l.idleTimer <- timer <-l.timedOut return ErrIdleTimeout } close(l.active) } return l.wrapped.Close() } func (l *idleListener) Dialer() Dialer { return l.wrapped.Dialer() } func (l *idleListener) timerExpired() { select { case n, ok := <-l.active: if ok { panic(fmt.Sprintf("jsonrpc2: idleListener idle timer fired with %d connections still active", n)) } else { panic("jsonrpc2: Close finished with idle timer still running") } case <-l.timedOut: panic("jsonrpc2: idleListener idle timer fired more than once") case <-l.idleTimer: // The timer for this very call! } // Close the Listener with all channels still blocked to ensure that this call // to l.wrapped.Close doesn't race with the one in l.Close. defer close(l.timedOut) l.wrapped.Close() } func (l *idleListener) connClosed() { select { case n, ok := <-l.active: if !ok { // l is already closed, so it can't close due to idleness, // and we don't need to track the number of active connections any more. return } n-- if n == 0 { l.idleTimer <- time.AfterFunc(l.timeout, l.timerExpired) } else { l.active <- n } case <-l.timedOut: panic("jsonrpc2: idleListener idle timer fired before last active connection was closed") case <-l.idleTimer: panic("jsonrpc2: idleListener idle timer active before last active connection was closed") } } type idleListenerConn struct { wrapped io.ReadWriteCloser l *idleListener closeOnce sync.Once } func (l *idleListener) newConn(rwc io.ReadWriteCloser) *idleListenerConn { c := &idleListenerConn{ wrapped: rwc, l: l, } // A caller that forgets to call Close may disrupt the idleListener's // accounting, even though the file descriptor for the underlying connection // may eventually be garbage-collected anyway. // // Set a (best-effort) finalizer to verify that a Close call always occurs. // (We will clear the finalizer explicitly in Close.) runtime.SetFinalizer(c, func(c *idleListenerConn) { panic("jsonrpc2: IdleListener connection became unreachable without a call to Close") }) return c } func (c *idleListenerConn) Read(p []byte) (int, error) { return c.wrapped.Read(p) } func (c *idleListenerConn) Write(p []byte) (int, error) { return c.wrapped.Write(p) } func (c *idleListenerConn) Close() error { defer c.closeOnce.Do(func() { c.l.connClosed() runtime.SetFinalizer(c, nil) }) return c.wrapped.Close() } ================================================ FILE: x/jsonrpc2/stdio/server.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package stdio import ( "context" "io" "os" "sync/atomic" "github.com/goplus/xgo/x/fakenet" "github.com/goplus/xgo/x/jsonrpc2" "github.com/goplus/xgo/x/jsonrpc2/jsonrpc2test" ) // ----------------------------------------------------------------------------- type listener struct { } func (p *listener) Close() error { return nil } // Accept blocks waiting for an incoming connection to the listener. func (p *listener) Accept(context.Context) (io.ReadWriteCloser, error) { connCnt := &connCnt[server] if atomic.AddInt32(connCnt, 1) != 1 { atomic.AddInt32(connCnt, -1) return nil, ErrTooManyConnections } return fakenet.NewConn("stdio", os.Stdin, os.Stdout), nil } func (p *listener) Dialer() jsonrpc2.Dialer { return nil } // Listener returns a jsonrpc2.Listener based on stdin and stdout. func Listener(allowLocalDail bool) jsonrpc2.Listener { if allowLocalDail { return jsonrpc2test.NetPipeListener() } return &listener{} } // ----------------------------------------------------------------------------- ================================================ FILE: x/jsonrpc2/stdio/stdio.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package stdio import ( "context" "errors" "io" "sync/atomic" "github.com/goplus/xgo/x/fakenet" "github.com/goplus/xgo/x/jsonrpc2" ) var ( ErrTooManyConnections = errors.New("too many connections") ) const ( client = iota server ) var ( connCnt [2]int32 ) // ----------------------------------------------------------------------------- type dialer struct { in io.ReadCloser out io.WriteCloser } // Dial returns a new communication byte stream to a listening server. func (p *dialer) Dial(ctx context.Context) (io.ReadWriteCloser, error) { dailCnt := &connCnt[client] if atomic.AddInt32(dailCnt, 1) != 1 { atomic.AddInt32(dailCnt, -1) return nil, ErrTooManyConnections } return fakenet.NewConn("stdio.dialer", p.in, p.out), nil } // Dialer returns a jsonrpc2.Dialer based on in and out. func Dialer(in io.ReadCloser, out io.WriteCloser) jsonrpc2.Dialer { return &dialer{in: in, out: out} } // Dial makes a new connection based on in and out, wraps the returned // reader and writer using the framer to make a stream, and then builds a // connection on top of that stream using the binder. // // The returned Connection will operate independently using the Preempter and/or // Handler provided by the Binder, and will release its own resources when the // connection is broken, but the caller may Close it earlier to stop accepting // (or sending) new requests. func Dial(in io.ReadCloser, out io.WriteCloser, binder jsonrpc2.Binder, onDone func()) (*jsonrpc2.Connection, error) { return jsonrpc2.Dial(context.Background(), Dialer(in, out), binder, onDone) } // ----------------------------------------------------------------------------- ================================================ FILE: x/jsonrpc2/wire.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package jsonrpc2 import ( "encoding/json" ) // This file contains the go forms of the wire specification. // see http://www.jsonrpc.org/specification for details var ( // ErrParse is used when invalid JSON was received by the server. ErrParse = NewError(-32700, "JSON RPC parse error") // ErrInvalidRequest is used when the JSON sent is not a valid Request object. ErrInvalidRequest = NewError(-32600, "JSON RPC invalid request") // ErrMethodNotFound should be returned by the handler when the method does // not exist / is not available. ErrMethodNotFound = NewError(-32601, "JSON RPC method not found") // ErrInvalidParams should be returned by the handler when method // parameter(s) were invalid. ErrInvalidParams = NewError(-32602, "JSON RPC invalid params") // ErrInternal indicates a failure to process a call correctly ErrInternal = NewError(-32603, "JSON RPC internal error") // The following errors are not part of the json specification, but // compliant extensions specific to this implementation. // ErrServerOverloaded is returned when a message was refused due to a // server being temporarily unable to accept any new messages. ErrServerOverloaded = NewError(-32000, "JSON RPC overloaded") // ErrUnknown should be used for all non coded errors. ErrUnknown = NewError(-32001, "JSON RPC unknown error") // ErrServerClosing is returned for calls that arrive while the server is closing. ErrServerClosing = NewError(-32002, "JSON RPC server is closing") // ErrClientClosing is a dummy error returned for calls initiated while the client is closing. ErrClientClosing = NewError(-32003, "JSON RPC client is closing") ) const wireVersion = "2.0" // wireCombined has all the fields of both Request and Response. // We can decode this and then work out which it is. type wireCombined struct { VersionTag string `json:"jsonrpc"` ID any `json:"id,omitempty"` Method string `json:"method,omitempty"` Params json.RawMessage `json:"params,omitempty"` Result json.RawMessage `json:"result,omitempty"` Error *wireError `json:"error,omitempty"` } // wireError represents a structured error in a Response. type wireError struct { // Code is an error code indicating the type of failure. Code int64 `json:"code"` // Message is a short description of the error. Message string `json:"message"` // Data is optional structured data containing additional information about the error. Data json.RawMessage `json:"data,omitempty"` } // NewError returns an error that will encode on the wire correctly. // The standard codes are made available from this package, this function should // only be used to build errors for application specific codes as allowed by the // specification. func NewError(code int64, message string) error { return &wireError{ Code: code, Message: message, } } func (err *wireError) Error() string { return err.Message } func (err *wireError) Is(other error) bool { w, ok := other.(*wireError) if !ok { return false } return err.Code == w.Code } ================================================ FILE: x/langserver/client.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package langserver import ( "context" "github.com/goplus/xgo/x/jsonrpc2" ) // ----------------------------------------------------------------------------- const ( methodGenGo = "gengo" methodChanged = "changed" ) // ----------------------------------------------------------------------------- // Dialer is used by clients to dial a server. type Dialer = jsonrpc2.Dialer type AsyncCall = jsonrpc2.AsyncCall // Client is a client of the language server. type Client struct { conn *jsonrpc2.Connection } // Open uses the dialer to make a new connection and returns a client of the LangServer // based on the connection. func Open(ctx context.Context, dialer Dialer, onDone func()) (ret Client, err error) { c, err := jsonrpc2.Dial(ctx, dialer, jsonrpc2.BinderFunc( func(ctx context.Context, c *jsonrpc2.Connection) (ret jsonrpc2.ConnectionOptions) { return }), onDone) if err != nil { return } ret = Client{c} return } func (p Client) Close() error { return p.conn.Close() } func (p Client) AsyncGenGo(ctx context.Context, pattern ...string) *AsyncCall { return p.conn.Call(ctx, methodGenGo, pattern) } func (p Client) GenGo(ctx context.Context, pattern ...string) (err error) { return p.AsyncGenGo(ctx, pattern...).Await(ctx, nil) } func (p Client) Changed(ctx context.Context, files ...string) (err error) { return p.conn.Notify(ctx, methodChanged, files) } // ----------------------------------------------------------------------------- ================================================ FILE: x/langserver/serve_dial.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package langserver import ( "context" "io" "io/fs" "log" "os" "os/exec" "strconv" "strings" "time" "github.com/goplus/xgo/x/jsonrpc2/stdio" ) func fatal(err error) { log.Fatalln(err) } // ----------------------------------------------------------------------------- // ServeAndDialConfig represents the configuration of ServeAndDial. type ServeAndDialConfig struct { // OnError is to customize how to process errors (optional). // It should panic in any case. OnError func(err error) } const ( logPrefix = "serve-" logSuffix = ".log" ) func logFileOf(xgoDir string, pid int) string { return xgoDir + logPrefix + strconv.Itoa(pid) + logSuffix } func isLog(fname string) bool { return strings.HasSuffix(fname, logSuffix) && strings.HasPrefix(fname, logPrefix) } func pidByName(fname string) int { pidText := fname[len(logPrefix) : len(fname)-len(logSuffix)] if ret, err := strconv.ParseInt(pidText, 10, 0); err == nil { return int(ret) } return -1 } func killByPid(pid int) { if pid < 0 { return } if proc, err := os.FindProcess(pid); err == nil { proc.Kill() } } func tooOld(d fs.DirEntry) bool { if fi, e := d.Info(); e == nil { lastHour := time.Now().Add(-time.Hour) return fi.ModTime().Before(lastHour) } return false } // ServeAndDial executes a command as a LangServer, makes a new connection to it // and returns a client of the LangServer based on the connection. func ServeAndDial(conf *ServeAndDialConfig, xgoCmd string, args ...string) Client { if conf == nil { conf = new(ServeAndDialConfig) } onErr := conf.OnError if onErr == nil { onErr = fatal } home, err := os.UserHomeDir() if err != nil { onErr(err) } xgoDir := home + "/.xgo/" err = os.MkdirAll(xgoDir, 0755) if err != nil { onErr(err) } // logFile is where the LangServer application log saves to. // default is ~/.xgo/serve-{pid}.log logFile := logFileOf(xgoDir, os.Getpid()) // clean too old logfiles, and kill old LangServer processes go func() { if fis, e := os.ReadDir(xgoDir); e == nil { for _, fi := range fis { if fi.IsDir() { continue } if fname := fi.Name(); isLog(fname) && tooOld(fi) { os.Remove(xgoDir + fname) killByPid(pidByName(fname)) } } } }() in, w := io.Pipe() r, out := io.Pipe() var cmd *exec.Cmd go func() { defer r.Close() defer w.Close() f, err := os.Create(logFile) if err != nil { onErr(err) } defer f.Close() cmd = exec.Command(xgoCmd, args...) cmd.Stdin = r cmd.Stdout = w cmd.Stderr = f err = cmd.Start() if err != nil { onErr(err) } newLogFile := logFileOf(xgoDir, cmd.Process.Pid) os.Rename(logFile, newLogFile) cmd.Wait() }() c, err := Open(context.Background(), stdio.Dialer(in, out), func() { if cmd == nil { return } if proc := cmd.Process; proc != nil { log.Println("==> ServeAndDial: kill", proc.Pid, xgoCmd, args) proc.Kill() } }) if err != nil { onErr(err) } return c } // ----------------------------------------------------------------------------- ================================================ FILE: x/langserver/server.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package langserver import ( "context" "encoding/json" "path/filepath" "sync" "time" "github.com/goplus/xgo/tool" "github.com/goplus/xgo/x/jsonrpc2" "github.com/goplus/xgo/x/xgoprojs" ) // ----------------------------------------------------------------------------- // Listener is implemented by protocols to accept new inbound connections. type Listener = jsonrpc2.Listener // Server is a running server that is accepting incoming connections. type Server = jsonrpc2.Server // Config holds the options for new connections. type Config struct { // Framer allows control over the message framing and encoding. // If nil, HeaderFramer will be used. Framer jsonrpc2.Framer } // NewServer creates a new LangServer and returns it. func NewServer(ctx context.Context, listener Listener, conf *Config) (ret *Server) { h := newHandle() ret = jsonrpc2.NewServer(ctx, listener, jsonrpc2.BinderFunc( func(ctx context.Context, c *jsonrpc2.Connection) (ret jsonrpc2.ConnectionOptions) { if conf != nil { ret.Framer = conf.Framer } ret.Handler = h // ret.OnInternalError = h.OnInternalError return })) h.server = ret go h.runLoop() return } // ----------------------------------------------------------------------------- type none = struct{} type handler struct { mutex sync.Mutex dirty map[string]none server *Server } func newHandle() *handler { return &handler{ dirty: make(map[string]none), } } /* func (p *handler) OnInternalError(err error) { panic("jsonrpc2: " + err.Error()) } */ func (p *handler) runLoop() { const ( duration = time.Second / 100 ) for { var dir string p.mutex.Lock() for dir = range p.dirty { delete(p.dirty, dir) break } p.mutex.Unlock() if dir == "" { time.Sleep(duration) continue } tool.GenGoEx(dir, nil, true, tool.GenFlagPrompt) } } func (p *handler) Changed(files []string) { p.mutex.Lock() defer p.mutex.Unlock() for _, file := range files { dir := filepath.Dir(file) p.dirty[dir] = none{} } } func (p *handler) Handle(ctx context.Context, req *jsonrpc2.Request) (result any, err error) { switch req.Method { case methodChanged: var files []string err = json.Unmarshal(req.Params, &files) if err != nil { return } p.Changed(files) case methodGenGo: var pattern []string err = json.Unmarshal(req.Params, &pattern) if err != nil { return } err = GenGo(pattern...) } return } func GenGo(pattern ...string) (err error) { projs, err := xgoprojs.ParseAll(pattern...) if err != nil { return } conf, _ := tool.NewDefaultConf(".", 0) if conf != nil { defer conf.UpdateCache() } for _, proj := range projs { switch v := proj.(type) { case *xgoprojs.DirProj: tool.GenGoEx(v.Dir, conf, true, 0) case *xgoprojs.PkgPathProj: if v.Path == "builtin" { continue } tool.GenGoPkgPathEx("", v.Path, conf, true, 0) } } return } // ----------------------------------------------------------------------------- ================================================ FILE: x/typesutil/api.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package typesutil import ( "go/types" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/token" ) // A CheckConfig specifies the configuration for type checking. // The zero value for Config is a ready-to-use default configuration. type CheckConfig types.Config // Check type-checks a package and returns the resulting package object and // the first error if any. Additionally, if info != nil, Check populates each // of the non-nil maps in the Info struct. // // The package is marked as complete if no errors occurred, otherwise it is // incomplete. See Config.Error for controlling behavior in the presence of // errors. // // The package is specified by a list of *ast.Files and corresponding // file set, and the package path the package is identified with. // The clean path must not be empty or dot ("."). func (conf *CheckConfig) Check(path string, fset *token.FileSet, files []*ast.File, info *Info) (ret *types.Package, err error) { ret = types.NewPackage(path, "") c := NewChecker((*types.Config)(conf), &Config{Types: ret, Fset: fset}, nil, info) err = c.Files(nil, files) return } ================================================ FILE: x/typesutil/builtin_test.go ================================================ package typesutil import ( "errors" "testing" "github.com/goplus/gogen" ) func TestConvErr(t *testing.T) { e := errors.New("foo") if ret, ok := convErr(nil, &gogen.ImportError{Err: e}); !ok || ret.Msg != "foo" { t.Fatal("convErr:", ret, ok) } if _, ok := convErr(nil, e); ok { t.Fatal("convErr: ok?") } } ================================================ FILE: x/typesutil/check.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package typesutil import ( goast "go/ast" "go/types" "path/filepath" "strings" "github.com/goplus/gogen" "github.com/goplus/mod/xgomod" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/token" "github.com/goplus/xgo/x/typesutil/internal/typesutil" "github.com/qiniu/x/errors" "github.com/qiniu/x/log" ) // ----------------------------------------------------------------------------- type dbgFlags int const ( DbgFlagVerbose dbgFlags = 1 << iota DbgFlagPrintError DbgFlagDisableRecover DbgFlagDefault = DbgFlagVerbose | DbgFlagPrintError DbgFlagAll = DbgFlagDefault | DbgFlagDisableRecover ) var ( debugVerbose bool debugPrintErr bool ) func SetDebug(flags dbgFlags) { debugVerbose = (flags & DbgFlagVerbose) != 0 debugPrintErr = (flags & DbgFlagPrintError) != 0 if (flags & DbgFlagDisableRecover) != 0 { cl.SetDisableRecover(true) } } // ----------------------------------------------------------------------------- type Project = cl.Project type Config struct { // Types provides type information for the package (required). Types *types.Package // Fset provides source position information for syntax trees and types (required). Fset *token.FileSet // WorkingDir is the directory in which to run xgo compiler (optional). // If WorkingDir is not set, os.Getwd() is used. WorkingDir string // Mod represents an XGo module (optional). Mod *xgomod.Module // If IgnoreFuncBodies is set, skip compiling function bodies (optional). IgnoreFuncBodies bool // If UpdateGoTypesOverload is set, update go types overload data (optional). UpdateGoTypesOverload bool } // A Checker maintains the state of the type checker. // It must be created with NewChecker. type Checker struct { conf *types.Config opts *Config goInfo *types.Info xgoInfo *Info } // NewChecker returns a new Checker instance for a given package. // Package files may be added incrementally via checker.Files. func NewChecker(conf *types.Config, opts *Config, goInfo *types.Info, xgoInfo *Info) *Checker { return &Checker{conf, opts, goInfo, xgoInfo} } // Files checks the provided files as part of the checker's package. func (p *Checker) Files(goFiles []*goast.File, xgoFiles []*ast.File) (err error) { opts := p.opts pkgTypes := opts.Types fset := opts.Fset conf := p.conf // Save original Error handler and restore it on function exit origError := conf.Error defer func() { conf.Error = origError }() if len(xgoFiles) == 0 { onErr := conf.Error if onErr != nil { conf.Error = func(err error) { if e, ok := convGoErr(err); ok { onErr(e) } } } checker := types.NewChecker(conf, fset, pkgTypes, p.goInfo) return checker.Files(goFiles) } files := make([]*goast.File, 0, len(goFiles)) gofs := make(map[string]*goast.File) xgofs := make(map[string]*ast.File) for _, goFile := range goFiles { f := fset.File(goFile.Pos()) if f == nil { continue } file := f.Name() fname := filepath.Base(file) if strings.HasPrefix(fname, "xgo_autogen") { continue } gofs[file] = goFile files = append(files, goFile) } for _, xgoFile := range xgoFiles { f := fset.File(xgoFile.Pos()) if f == nil { continue } xgofs[f.Name()] = xgoFile } if debugVerbose { log.Println("typesutil.Check:", pkgTypes.Path(), "xgoFiles =", len(xgofs), "goFiles =", len(gofs)) } pkg := &ast.Package{ Name: pkgTypes.Name(), Files: xgofs, GoFiles: gofs, } mod := opts.Mod if mod == nil { mod = xgomod.Default } _, err = cl.NewPackage(pkgTypes.Path(), pkg, &cl.Config{ Types: pkgTypes, Fset: fset, LookupClass: mod.LookupClass, Importer: conf.Importer, Recorder: NewRecorder(p.xgoInfo), NoFileLine: true, NoAutoGenMain: true, NoSkipConstant: true, Outline: opts.IgnoreFuncBodies, }) if err != nil { if onErr := conf.Error; onErr != nil { if list, ok := err.(errors.List); ok { for _, e := range list { if ce, ok := convErr(fset, e); ok { onErr(ce) } } } else if ce, ok := convErr(fset, err); ok { onErr(ce) } else { onErr(err) } } if debugPrintErr { log.Println("typesutil.Check err:", err) log.SingleStack() } // don't return even if err != nil } if len(files) > 0 { onErr := conf.Error if onErr != nil { conf.Error = func(err error) { if e, ok := convGoErr(err); ok { onErr(e) } } } scope := pkgTypes.Scope() objMap := DeleteObjects(scope, files) checker := types.NewChecker(conf, fset, pkgTypes, p.goInfo) err = checker.Files(files) // TODO(xsw): how to process error? CorrectTypesInfo(scope, objMap, p.xgoInfo.Uses) if opts.UpdateGoTypesOverload { gogen.InitXGoPackage(pkgTypes) } } return } type astIdent interface { comparable ast.Node } type objMapT = map[types.Object]types.Object // CorrectTypesInfo corrects types info to avoid there are two instances for the same Go object. func CorrectTypesInfo[Ident astIdent](scope *types.Scope, objMap objMapT, uses map[Ident]types.Object) { for o := range objMap { objMap[o] = scope.Lookup(o.Name()) } for id, old := range uses { if new := objMap[old]; new != nil { uses[id] = new } } } // DeleteObjects deletes all objects defined in Go files and returns deleted objects. func DeleteObjects(scope *types.Scope, files []*goast.File) objMapT { objMap := make(objMapT) for _, f := range files { for _, decl := range f.Decls { switch v := decl.(type) { case *goast.GenDecl: for _, spec := range v.Specs { switch v := spec.(type) { case *goast.ValueSpec: for _, name := range v.Names { scopeDelete(objMap, scope, name.Name) } case *goast.TypeSpec: scopeDelete(objMap, scope, v.Name.Name) } } case *goast.FuncDecl: if v.Recv == nil { scopeDelete(objMap, scope, v.Name.Name) } } } } return objMap } func convErr(fset *token.FileSet, e error) (ret Error, ok bool) { switch v := e.(type) { case *gogen.CodeError: ret.Pos, ret.End, ret.Msg = v.Pos, v.End, v.Msg case *gogen.MatchError: if v.Src != nil { ret.Pos, ret.End = v.Src.Pos(), v.Src.End() } ret.Msg = v.Message("") case *gogen.ImportError: ret.Pos, ret.End, ret.Msg = v.Pos, v.End, v.Err.Error() case *gogen.BoundTypeError: ret.Pos, ret.End, ret.Msg = v.Pos, v.End, v.Error() default: return } ret.Fset, ok = fset, true return } func convGoErr(e error) (ret Error, ok bool) { if v, ok := e.(types.Error); ok { ret.Fset, ret.Pos, ret.Msg, ret.Soft = v.Fset, v.Pos, v.Msg, v.Soft code, _, end, ok := typesutil.GetErrorGo116(&v) if ok { ret.Code = Code(code) ret.End = end } } return ret, true } func scopeDelete(objMap map[types.Object]types.Object, scope *types.Scope, name string) { if o := typesutil.ScopeDelete(scope, name); o != nil { objMap[o] = nil } } ================================================ FILE: x/typesutil/check_test.go ================================================ package typesutil_test import ( goast "go/ast" "go/importer" goparser "go/parser" "go/types" "log" "os" "path/filepath" "testing" "github.com/goplus/mod/xgomod" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/token" "github.com/goplus/xgo/x/typesutil" "github.com/qiniu/x/errors" ) func init() { if os.Getenv("XGOROOT") == "" { dir, _ := os.Getwd() os.Setenv("XGOROOT", filepath.Clean(filepath.Join(dir, "./../.."))) } typesutil.SetDebug(typesutil.DbgFlagDefault) } func loadFiles(fset *token.FileSet, file string, src any, goxfile string, goxsrc any, gofile string, gosrc any) ([]*ast.File, []*goast.File, error) { var files []*ast.File var gofiles []*goast.File if file != "" { f, err := parser.ParseFile(fset, file, src, 0) if err != nil { return nil, nil, err } files = append(files, f) } if goxfile != "" { f, err := parser.ParseFile(fset, goxfile, goxsrc, parser.ParseXGoClass) if err != nil { return nil, nil, err } files = append(files, f) } if gofile != "" { f, err := goparser.ParseFile(fset, gofile, gosrc, 0) if err != nil { return nil, nil, err } gofiles = append(gofiles, f) } return files, gofiles, nil } func checkFiles(fset *token.FileSet, file string, src any, goxfile string, goxsrc any, gofile string, gosrc any) (*typesutil.Info, *types.Info, error) { files, gofiles, err := loadFiles(fset, file, src, goxfile, goxsrc, gofile, gosrc) if err != nil { return nil, nil, err } return checkInfo(fset, files, gofiles, nil) } func checkFilesWithErrorHandler(fset *token.FileSet, file string, src any, goxfile string, goxsrc any, gofile string, gosrc any, handleErr func(error)) (*typesutil.Info, *types.Info, error) { files, gofiles, err := loadFiles(fset, file, src, goxfile, goxsrc, gofile, gosrc) if err != nil { return nil, nil, err } return checkInfo(fset, files, gofiles, handleErr) } func checkInfo(fset *token.FileSet, files []*ast.File, gofiles []*goast.File, handleErr func(error)) (*typesutil.Info, *types.Info, error) { conf := &types.Config{} conf.Importer = importer.Default() if handleErr == nil { handleErr = func(err error) { log.Println(err) } } conf.Error = handleErr chkOpts := &typesutil.Config{ Types: types.NewPackage("main", "main"), Fset: fset, Mod: xgomod.Default, } info := &typesutil.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), Implicits: make(map[ast.Node]types.Object), Selections: make(map[*ast.SelectorExpr]*types.Selection), Scopes: make(map[ast.Node]*types.Scope), Overloads: make(map[*ast.Ident]types.Object), } ginfo := &types.Info{ Types: make(map[goast.Expr]types.TypeAndValue), Defs: make(map[*goast.Ident]types.Object), Uses: make(map[*goast.Ident]types.Object), Implicits: make(map[goast.Node]types.Object), Selections: make(map[*goast.SelectorExpr]*types.Selection), Scopes: make(map[goast.Node]*types.Scope), } check := typesutil.NewChecker(conf, chkOpts, ginfo, info) err := check.Files(gofiles, files) return info, ginfo, err } func TestCheckFiles(t *testing.T) { fset := token.NewFileSet() info, ginfo, err := checkFiles(fset, "main.xgo", ` import "fmt" type Point struct { x int y int } pt := &Point{} pt.x = 100 pt.y = 200 fmt.Println(pt) gopt := GoPoint{100,200} gopt.Test() gotest() fmt.Println(GoValue) fmt.Println(&Rect{100,200}) `, "Rect.gox", ` var ( x int y int ) `, "util.go", `package main var GoValue string type GoPoint struct { x int y int } func (p *GoPoint) Test() { } func gotest() { } `) if err != nil || info == nil || ginfo == nil { t.Fatalf("check failed: %v", err) } for def, obj := range info.Defs { o := info.ObjectOf(def) if o != obj { t.Fatal("bad obj", o) } } for use, obj := range info.Uses { o := info.ObjectOf(use) if o.String() != obj.String() { t.Fatal("bad obj", o) } typ := info.TypeOf(use) if typ.String() != obj.Type().String() { t.Fatal("bad typ", typ) } } } func TestCheckGoFiles(t *testing.T) { fset := token.NewFileSet() info, ginfo, err := checkFiles(fset, "", "", "", "", "main.go", `package main type GoPoint struct { x int y int } func main() { } `) if err != nil || info == nil || ginfo == nil { t.Fatalf("check failed: %v", err) } } func TestCheckError(t *testing.T) { fset := token.NewFileSet() _, _, err := checkFiles(fset, "main.xgo", ` type Point struct { x int y int } pt := &Point1{} println(pt) `, "", "", "", "") if err == nil { t.Fatal("no error") } _, _, err = checkFiles(fset, "main.xgo", ` var i int = "hello" `, "", "", "", "") if err == nil { t.Fatal("no error") } _, _, err = checkFiles(fset, "main.xgo", ` var nums []int nums = append(nums, "NaN") `, "", "", "", "") if err == nil { t.Fatal("no error") } } func TestBadFile(t *testing.T) { conf := &types.Config{} opt := &typesutil.Config{} opt.Fset = token.NewFileSet() opt.Types = types.NewPackage("", "main") checker := typesutil.NewChecker(conf, opt, nil, nil) _ = checker.Files([]*goast.File{{Name: goast.NewIdent("main")}}, []*ast.File{{Name: ast.NewIdent("main")}}) } func TestCheckOverload(t *testing.T) { fset := token.NewFileSet() info, ginfo, err := checkFiles(fset, "main.xgo", ` type foo struct { } func (a *foo) mulInt(b int) *foo { return a } func (a *foo) mulFoo(b *foo) *foo { return a } func (foo).mul = ( (foo).mulInt (foo).mulFoo ) func addInt0() { } func addInt1(i int) { } func addInt2(i, j int) { } var addInt3 = func(i, j, k int) { } func add = ( addInt0 addInt1 addInt2 addInt3 func(a, b string) string { return a + b } ) var a, b *foo var c = a.mul(100) var d = a.mul(c) func init() { add 100, 200 add 100, 200, 300 add("hello", "world") } `, "", "", "", "") if err != nil || info == nil || ginfo == nil { t.Fatalf("check failed: %v", err) } for use, ovDeclObj := range info.Overloads { o := info.ObjectOf(use) declObj, ovObjs := info.OverloadOf(use) if ovDeclObj != declObj { t.Fatal("bad overload", o) } found := false for _, ovObj := range ovObjs { if o == ovObj { found = true break } } if !found { t.Fatal("bad overload", o) } } for use, o := range info.Uses { declObj, ovObjs := info.OverloadOf(use) if declObj != nil && ovObjs != nil { if info.Overloads[use] == nil { t.Fatal("bad overload", o) } } } } func TestCheckError2(t *testing.T) { var checkerErrs errors.List fset := token.NewFileSet() checkFilesWithErrorHandler(fset, "main.xgo", ` type Foo struct { B bool } bar := 2 gotbar := false boolBar := false gg := &Foo{B: true} if bar == 2 && !gotbar { println("wow!") } else if bar == 1 && gotbar { if !boolBar { boolBar = true for i := 0; i < 20; i++ { if gg.P { println("wow 2!") } } } } `, "", "", "", "", func(err error) { checkerErrs.Add(err) }) if len(checkerErrs) > 1 { t.Fatal("too many errors") } } func TestCheckError3(t *testing.T) { var checkerErrs errors.List fset := token.NewFileSet() checkFilesWithErrorHandler(fset, "main.xgo", ` type Foo struct { B []string } bar := 2 gotbar := false boolBar := false gg := &Foo{B: []string{"hello", "world"}} if bar == 2 && !gotbar { println("Double jump activated!") } else if bar == 1 && gotbar { if !boolBar { boolBar = true for item := range gg.P { println("i am item", item) } } } `, "", "", "", "", func(err error) { checkerErrs.Add(err) }) if len(checkerErrs) > 1 { t.Fatal("too many errors") } } func TestCheckWithGoFiles(t *testing.T) { fset := token.NewFileSet() info, ginfo, err := checkFiles(fset, "main.xgo", ` import "fmt" fmt.Println(add(1, 2)) fmt.Println(gofunc()) `, "", "", "util.go", `package main func add(a, b int) int { return a + b } func gofunc() string { return "hello from go" } `) if err != nil || info == nil || ginfo == nil { t.Fatalf("check failed: %v", err) } // Verify that Go file definitions are accessible from XGo foundAdd := false foundGofunc := false for _, obj := range info.Uses { if obj.Name() == "add" { foundAdd = true } if obj.Name() == "gofunc" { foundGofunc = true } } if !foundAdd || !foundGofunc { t.Fatal("Go file functions not found in XGo uses") } // Verify that Go file definitions are in ginfo foundAddDef := false foundGofuncDef := false for _, obj := range ginfo.Defs { if obj != nil { if obj.Name() == "add" { foundAddDef = true } if obj.Name() == "gofunc" { foundGofuncDef = true } } } if !foundAddDef || !foundGofuncDef { t.Fatal("Go file functions not found in ginfo defs") } } ================================================ FILE: x/typesutil/code_string.go ================================================ // Code generated by "stringer -type Code codes.go"; DO NOT EDIT. package typesutil import "strconv" func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[InvalidSyntaxTree - -1] _ = x[Test-1] _ = x[BlankPkgName-2] _ = x[MismatchedPkgName-3] _ = x[InvalidPkgUse-4] _ = x[BadImportPath-5] _ = x[BrokenImport-6] _ = x[ImportCRenamed-7] _ = x[UnusedImport-8] _ = x[InvalidInitCycle-9] _ = x[DuplicateDecl-10] _ = x[InvalidDeclCycle-11] _ = x[InvalidTypeCycle-12] _ = x[InvalidConstInit-13] _ = x[InvalidConstVal-14] _ = x[InvalidConstType-15] _ = x[UntypedNilUse-16] _ = x[WrongAssignCount-17] _ = x[UnassignableOperand-18] _ = x[NoNewVar-19] _ = x[MultiValAssignOp-20] _ = x[InvalidIfaceAssign-21] _ = x[InvalidChanAssign-22] _ = x[IncompatibleAssign-23] _ = x[UnaddressableFieldAssign-24] _ = x[NotAType-25] _ = x[InvalidArrayLen-26] _ = x[BlankIfaceMethod-27] _ = x[IncomparableMapKey-28] _ = x[InvalidPtrEmbed-30] _ = x[BadRecv-31] _ = x[InvalidRecv-32] _ = x[DuplicateFieldAndMethod-33] _ = x[DuplicateMethod-34] _ = x[InvalidBlank-35] _ = x[InvalidIota-36] _ = x[MissingInitBody-37] _ = x[InvalidInitSig-38] _ = x[InvalidInitDecl-39] _ = x[InvalidMainDecl-40] _ = x[TooManyValues-41] _ = x[NotAnExpr-42] _ = x[TruncatedFloat-43] _ = x[NumericOverflow-44] _ = x[UndefinedOp-45] _ = x[MismatchedTypes-46] _ = x[DivByZero-47] _ = x[NonNumericIncDec-48] _ = x[UnaddressableOperand-49] _ = x[InvalidIndirection-50] _ = x[NonIndexableOperand-51] _ = x[InvalidIndex-52] _ = x[SwappedSliceIndices-53] _ = x[NonSliceableOperand-54] _ = x[InvalidSliceExpr-55] _ = x[InvalidShiftCount-56] _ = x[InvalidShiftOperand-57] _ = x[InvalidReceive-58] _ = x[InvalidSend-59] _ = x[DuplicateLitKey-60] _ = x[MissingLitKey-61] _ = x[InvalidLitIndex-62] _ = x[OversizeArrayLit-63] _ = x[MixedStructLit-64] _ = x[InvalidStructLit-65] _ = x[MissingLitField-66] _ = x[DuplicateLitField-67] _ = x[UnexportedLitField-68] _ = x[InvalidLitField-69] _ = x[UntypedLit-70] _ = x[InvalidLit-71] _ = x[AmbiguousSelector-72] _ = x[UndeclaredImportedName-73] _ = x[UnexportedName-74] _ = x[UndeclaredName-75] _ = x[MissingFieldOrMethod-76] _ = x[BadDotDotDotSyntax-77] _ = x[NonVariadicDotDotDot-78] _ = x[MisplacedDotDotDot-79] _ = x[InvalidDotDotDot-81] _ = x[UncalledBuiltin-82] _ = x[InvalidAppend-83] _ = x[InvalidCap-84] _ = x[InvalidClose-85] _ = x[InvalidCopy-86] _ = x[InvalidComplex-87] _ = x[InvalidDelete-88] _ = x[InvalidImag-89] _ = x[InvalidLen-90] _ = x[SwappedMakeArgs-91] _ = x[InvalidMake-92] _ = x[InvalidReal-93] _ = x[InvalidAssert-94] _ = x[ImpossibleAssert-95] _ = x[InvalidConversion-96] _ = x[InvalidUntypedConversion-97] _ = x[BadOffsetofSyntax-98] _ = x[InvalidOffsetof-99] _ = x[UnusedExpr-100] _ = x[UnusedVar-101] _ = x[MissingReturn-102] _ = x[WrongResultCount-103] _ = x[OutOfScopeResult-104] _ = x[InvalidCond-105] _ = x[InvalidPostDecl-106] _ = x[InvalidIterVar-108] _ = x[InvalidRangeExpr-109] _ = x[MisplacedBreak-110] _ = x[MisplacedContinue-111] _ = x[MisplacedFallthrough-112] _ = x[DuplicateCase-113] _ = x[DuplicateDefault-114] _ = x[BadTypeKeyword-115] _ = x[InvalidTypeSwitch-116] _ = x[InvalidExprSwitch-117] _ = x[InvalidSelectCase-118] _ = x[UndeclaredLabel-119] _ = x[DuplicateLabel-120] _ = x[MisplacedLabel-121] _ = x[UnusedLabel-122] _ = x[JumpOverDecl-123] _ = x[JumpIntoBlock-124] _ = x[InvalidMethodExpr-125] _ = x[WrongArgCount-126] _ = x[InvalidCall-127] _ = x[UnusedResults-128] _ = x[InvalidDefer-129] _ = x[InvalidGo-130] _ = x[BadDecl-131] _ = x[RepeatedDecl-132] _ = x[InvalidUnsafeAdd-133] _ = x[InvalidUnsafeSlice-134] _ = x[UnsupportedFeature-135] _ = x[NotAGenericType-136] _ = x[WrongTypeArgCount-137] _ = x[CannotInferTypeArgs-138] _ = x[InvalidTypeArg-139] _ = x[InvalidInstanceCycle-140] _ = x[InvalidUnion-141] _ = x[MisplacedConstraintIface-142] _ = x[InvalidMethodTypeParams-143] _ = x[MisplacedTypeParam-144] _ = x[InvalidUnsafeSliceData-145] _ = x[InvalidUnsafeString-146] _ = x[InvalidClear-148] _ = x[TypeTooLarge-149] _ = x[InvalidMinMaxOperand-150] _ = x[TooNew-151] } const ( _Code_name_0 = "InvalidSyntaxTree" _Code_name_1 = "TestBlankPkgNameMismatchedPkgNameInvalidPkgUseBadImportPathBrokenImportImportCRenamedUnusedImportInvalidInitCycleDuplicateDeclInvalidDeclCycleInvalidTypeCycleInvalidConstInitInvalidConstValInvalidConstTypeUntypedNilUseWrongAssignCountUnassignableOperandNoNewVarMultiValAssignOpInvalidIfaceAssignInvalidChanAssignIncompatibleAssignUnaddressableFieldAssignNotATypeInvalidArrayLenBlankIfaceMethodIncomparableMapKey" _Code_name_2 = "InvalidPtrEmbedBadRecvInvalidRecvDuplicateFieldAndMethodDuplicateMethodInvalidBlankInvalidIotaMissingInitBodyInvalidInitSigInvalidInitDeclInvalidMainDeclTooManyValuesNotAnExprTruncatedFloatNumericOverflowUndefinedOpMismatchedTypesDivByZeroNonNumericIncDecUnaddressableOperandInvalidIndirectionNonIndexableOperandInvalidIndexSwappedSliceIndicesNonSliceableOperandInvalidSliceExprInvalidShiftCountInvalidShiftOperandInvalidReceiveInvalidSendDuplicateLitKeyMissingLitKeyInvalidLitIndexOversizeArrayLitMixedStructLitInvalidStructLitMissingLitFieldDuplicateLitFieldUnexportedLitFieldInvalidLitFieldUntypedLitInvalidLitAmbiguousSelectorUndeclaredImportedNameUnexportedNameUndeclaredNameMissingFieldOrMethodBadDotDotDotSyntaxNonVariadicDotDotDotMisplacedDotDotDot" _Code_name_3 = "InvalidDotDotDotUncalledBuiltinInvalidAppendInvalidCapInvalidCloseInvalidCopyInvalidComplexInvalidDeleteInvalidImagInvalidLenSwappedMakeArgsInvalidMakeInvalidRealInvalidAssertImpossibleAssertInvalidConversionInvalidUntypedConversionBadOffsetofSyntaxInvalidOffsetofUnusedExprUnusedVarMissingReturnWrongResultCountOutOfScopeResultInvalidCondInvalidPostDecl" _Code_name_4 = "InvalidIterVarInvalidRangeExprMisplacedBreakMisplacedContinueMisplacedFallthroughDuplicateCaseDuplicateDefaultBadTypeKeywordInvalidTypeSwitchInvalidExprSwitchInvalidSelectCaseUndeclaredLabelDuplicateLabelMisplacedLabelUnusedLabelJumpOverDeclJumpIntoBlockInvalidMethodExprWrongArgCountInvalidCallUnusedResultsInvalidDeferInvalidGoBadDeclRepeatedDeclInvalidUnsafeAddInvalidUnsafeSliceUnsupportedFeatureNotAGenericTypeWrongTypeArgCountCannotInferTypeArgsInvalidTypeArgInvalidInstanceCycleInvalidUnionMisplacedConstraintIfaceInvalidMethodTypeParamsMisplacedTypeParamInvalidUnsafeSliceDataInvalidUnsafeString" _Code_name_5 = "InvalidClearTypeTooLargeInvalidMinMaxOperandTooNew" ) var ( _Code_index_1 = [...]uint16{0, 4, 16, 33, 46, 59, 71, 85, 97, 113, 126, 142, 158, 174, 189, 205, 218, 234, 253, 261, 277, 295, 312, 330, 354, 362, 377, 393, 411} _Code_index_2 = [...]uint16{0, 15, 22, 33, 56, 71, 83, 94, 109, 123, 138, 153, 166, 175, 189, 204, 215, 230, 239, 255, 275, 293, 312, 324, 343, 362, 378, 395, 414, 428, 439, 454, 467, 482, 498, 512, 528, 543, 560, 578, 593, 603, 613, 630, 652, 666, 680, 700, 718, 738, 756} _Code_index_3 = [...]uint16{0, 16, 31, 44, 54, 66, 77, 91, 104, 115, 125, 140, 151, 162, 175, 191, 208, 232, 249, 264, 274, 283, 296, 312, 328, 339, 354} _Code_index_4 = [...]uint16{0, 14, 30, 44, 61, 81, 94, 110, 124, 141, 158, 175, 190, 204, 218, 229, 241, 254, 271, 284, 295, 308, 320, 329, 336, 348, 364, 382, 400, 415, 432, 451, 465, 485, 497, 521, 544, 562, 584, 603} _Code_index_5 = [...]uint8{0, 12, 24, 44, 50} ) func (i Code) String() string { switch { case i == -1: return _Code_name_0 case 1 <= i && i <= 28: i -= 1 return _Code_name_1[_Code_index_1[i]:_Code_index_1[i+1]] case 30 <= i && i <= 79: i -= 30 return _Code_name_2[_Code_index_2[i]:_Code_index_2[i+1]] case 81 <= i && i <= 106: i -= 81 return _Code_name_3[_Code_index_3[i]:_Code_index_3[i+1]] case 108 <= i && i <= 146: i -= 108 return _Code_name_4[_Code_index_4[i]:_Code_index_4[i+1]] case 148 <= i && i <= 151: i -= 148 return _Code_name_5[_Code_index_5[i]:_Code_index_5[i+1]] default: return "Code(" + strconv.FormatInt(int64(i), 10) + ")" } } ================================================ FILE: x/typesutil/codes.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package typesutil //go:generate go run golang.org/x/tools/cmd/stringer@latest -type Code codes.go type Code int // This file defines the error codes that can be produced during type-checking. // Collectively, these codes provide an identifier that may be used to // implement special handling for certain types of errors. // // Error code values should not be changed: add new codes at the end. // // Error codes should be fine-grained enough that the exact nature of the error // can be easily determined, but coarse enough that they are not an // implementation detail of the type checking algorithm. As a rule-of-thumb, // errors should be considered equivalent if there is a theoretical refactoring // of the type checker in which they are emitted in exactly one place. For // example, the type checker emits different error messages for "too many // arguments" and "too few arguments", but one can imagine an alternative type // checker where this check instead just emits a single "wrong number of // arguments", so these errors should have the same code. // // Error code names should be as brief as possible while retaining accuracy and // distinctiveness. In most cases names should start with an adjective // describing the nature of the error (e.g. "invalid", "unused", "misplaced"), // and end with a noun identifying the relevant language object. For example, // "_DuplicateDecl" or "_InvalidSliceExpr". For brevity, naming follows the // convention that "bad" implies a problem with syntax, and "invalid" implies a // problem with types. const ( // InvalidSyntaxTree occurs if an invalid syntax tree is provided // to the type checker. It should never happen. InvalidSyntaxTree Code = -1 ) const ( // The zero Code value indicates an unset (invalid) error code. _ Code = iota // Test is reserved for errors that only apply while in self-test mode. Test // BlankPkgName occurs when a package name is the blank identifier "_". // // Per the spec: // "The PackageName must not be the blank identifier." // // Example: // package _ BlankPkgName // MismatchedPkgName occurs when a file's package name doesn't match the // package name already established by other files. MismatchedPkgName // InvalidPkgUse occurs when a package identifier is used outside of a // selector expression. // // Example: // import "fmt" // // var _ = fmt InvalidPkgUse // BadImportPath occurs when an import path is not valid. BadImportPath // BrokenImport occurs when importing a package fails. // // Example: // import "amissingpackage" BrokenImport // ImportCRenamed occurs when the special import "C" is renamed. "C" is a // pseudo-package, and must not be renamed. // // Example: // import _ "C" ImportCRenamed // UnusedImport occurs when an import is unused. // // Example: // import "fmt" // // func main() {} UnusedImport // InvalidInitCycle occurs when an invalid cycle is detected within the // initialization graph. // // Example: // var x int = f() // // func f() int { return x } InvalidInitCycle // DuplicateDecl occurs when an identifier is declared multiple times. // // Example: // var x = 1 // var x = 2 DuplicateDecl // InvalidDeclCycle occurs when a declaration cycle is not valid. // // Example: // type S struct { // S // } // InvalidDeclCycle // InvalidTypeCycle occurs when a cycle in type definitions results in a // type that is not well-defined. // // Example: // import "unsafe" // // type T [unsafe.Sizeof(T{})]int InvalidTypeCycle // InvalidConstInit occurs when a const declaration has a non-constant // initializer. // // Example: // var x int // const _ = x InvalidConstInit // InvalidConstVal occurs when a const value cannot be converted to its // target type. // // TODO(findleyr): this error code and example are not very clear. Consider // removing it. // // Example: // const _ = 1 << "hello" InvalidConstVal // InvalidConstType occurs when the underlying type in a const declaration // is not a valid constant type. // // Example: // const c *int = 4 InvalidConstType // UntypedNilUse occurs when the predeclared (untyped) value nil is used to // initialize a variable declared without an explicit type. // // Example: // var x = nil UntypedNilUse // WrongAssignCount occurs when the number of values on the right-hand side // of an assignment or initialization expression does not match the number // of variables on the left-hand side. // // Example: // var x = 1, 2 WrongAssignCount // UnassignableOperand occurs when the left-hand side of an assignment is // not assignable. // // Example: // func f() { // const c = 1 // c = 2 // } UnassignableOperand // NoNewVar occurs when a short variable declaration (':=') does not declare // new variables. // // Example: // func f() { // x := 1 // x := 2 // } NoNewVar // MultiValAssignOp occurs when an assignment operation (+=, *=, etc) does // not have single-valued left-hand or right-hand side. // // Per the spec: // "In assignment operations, both the left- and right-hand expression lists // must contain exactly one single-valued expression" // // Example: // func f() int { // x, y := 1, 2 // x, y += 1 // return x + y // } MultiValAssignOp // InvalidIfaceAssign occurs when a value of type T is used as an // interface, but T does not implement a method of the expected interface. // // Example: // type I interface { // f() // } // // type T int // // var x I = T(1) InvalidIfaceAssign // InvalidChanAssign occurs when a chan assignment is invalid. // // Per the spec, a value x is assignable to a channel type T if: // "x is a bidirectional channel value, T is a channel type, x's type V and // T have identical element types, and at least one of V or T is not a // defined type." // // Example: // type T1 chan int // type T2 chan int // // var x T1 // // Invalid assignment because both types are named // var _ T2 = x InvalidChanAssign // IncompatibleAssign occurs when the type of the right-hand side expression // in an assignment cannot be assigned to the type of the variable being // assigned. // // Example: // var x []int // var _ int = x IncompatibleAssign // UnaddressableFieldAssign occurs when trying to assign to a struct field // in a map value. // // Example: // func f() { // m := make(map[string]struct{i int}) // m["foo"].i = 42 // } UnaddressableFieldAssign // NotAType occurs when the identifier used as the underlying type in a type // declaration or the right-hand side of a type alias does not denote a type. // // Example: // var S = 2 // // type T S NotAType // InvalidArrayLen occurs when an array length is not a constant value. // // Example: // var n = 3 // var _ = [n]int{} InvalidArrayLen // BlankIfaceMethod occurs when a method name is '_'. // // Per the spec: // "The name of each explicitly specified method must be unique and not // blank." // // Example: // type T interface { // _(int) // } BlankIfaceMethod // IncomparableMapKey occurs when a map key type does not support the == and // != operators. // // Per the spec: // "The comparison operators == and != must be fully defined for operands of // the key type; thus the key type must not be a function, map, or slice." // // Example: // var x map[T]int // // type T []int IncomparableMapKey // InvalidIfaceEmbed occurs when a non-interface type is embedded in an // interface (for go 1.17 or earlier). _ // not used anymore // InvalidPtrEmbed occurs when an embedded field is of the pointer form *T, // and T itself is itself a pointer, an unsafe.Pointer, or an interface. // // Per the spec: // "An embedded field must be specified as a type name T or as a pointer to // a non-interface type name *T, and T itself may not be a pointer type." // // Example: // type T *int // // type S struct { // *T // } InvalidPtrEmbed // BadRecv occurs when a method declaration does not have exactly one // receiver parameter. // // Example: // func () _() {} BadRecv // InvalidRecv occurs when a receiver type expression is not of the form T // or *T, or T is a pointer type. // // Example: // type T struct {} // // func (**T) m() {} InvalidRecv // DuplicateFieldAndMethod occurs when an identifier appears as both a field // and method name. // // Example: // type T struct { // m int // } // // func (T) m() {} DuplicateFieldAndMethod // DuplicateMethod occurs when two methods on the same receiver type have // the same name. // // Example: // type T struct {} // func (T) m() {} // func (T) m(i int) int { return i } DuplicateMethod // InvalidBlank occurs when a blank identifier is used as a value or type. // // Per the spec: // "The blank identifier may appear as an operand only on the left-hand side // of an assignment." // // Example: // var x = _ InvalidBlank // InvalidIota occurs when the predeclared identifier iota is used outside // of a constant declaration. // // Example: // var x = iota InvalidIota // MissingInitBody occurs when an init function is missing its body. // // Example: // func init() MissingInitBody // InvalidInitSig occurs when an init function declares parameters or // results. // // Deprecated: no longer emitted by the type checker. _InvalidInitDecl is // used instead. InvalidInitSig // InvalidInitDecl occurs when init is declared as anything other than a // function. // // Example: // var init = 1 // // Example: // func init() int { return 1 } InvalidInitDecl // InvalidMainDecl occurs when main is declared as anything other than a // function, in a main package. InvalidMainDecl // TooManyValues occurs when a function returns too many values for the // expression context in which it is used. // // Example: // func ReturnTwo() (int, int) { // return 1, 2 // } // // var x = ReturnTwo() TooManyValues // NotAnExpr occurs when a type expression is used where a value expression // is expected. // // Example: // type T struct {} // // func f() { // T // } NotAnExpr // TruncatedFloat occurs when a float constant is truncated to an integer // value. // // Example: // var _ int = 98.6 TruncatedFloat // NumericOverflow occurs when a numeric constant overflows its target type. // // Example: // var x int8 = 1000 NumericOverflow // UndefinedOp occurs when an operator is not defined for the type(s) used // in an operation. // // Example: // var c = "a" - "b" UndefinedOp // MismatchedTypes occurs when operand types are incompatible in a binary // operation. // // Example: // var a = "hello" // var b = 1 // var c = a - b MismatchedTypes // DivByZero occurs when a division operation is provable at compile // time to be a division by zero. // // Example: // const divisor = 0 // var x int = 1/divisor DivByZero // NonNumericIncDec occurs when an increment or decrement operator is // applied to a non-numeric value. // // Example: // func f() { // var c = "c" // c++ // } NonNumericIncDec // UnaddressableOperand occurs when the & operator is applied to an // unaddressable expression. // // Example: // var x = &1 UnaddressableOperand // InvalidIndirection occurs when a non-pointer value is indirected via the // '*' operator. // // Example: // var x int // var y = *x InvalidIndirection // NonIndexableOperand occurs when an index operation is applied to a value // that cannot be indexed. // // Example: // var x = 1 // var y = x[1] NonIndexableOperand // InvalidIndex occurs when an index argument is not of integer type, // negative, or out-of-bounds. // // Example: // var s = [...]int{1,2,3} // var x = s[5] // // Example: // var s = []int{1,2,3} // var _ = s[-1] // // Example: // var s = []int{1,2,3} // var i string // var _ = s[i] InvalidIndex // SwappedSliceIndices occurs when constant indices in a slice expression // are decreasing in value. // // Example: // var _ = []int{1,2,3}[2:1] SwappedSliceIndices // NonSliceableOperand occurs when a slice operation is applied to a value // whose type is not sliceable, or is unaddressable. // // Example: // var x = [...]int{1, 2, 3}[:1] // // Example: // var x = 1 // var y = 1[:1] NonSliceableOperand // InvalidSliceExpr occurs when a three-index slice expression (a[x:y:z]) is // applied to a string. // // Example: // var s = "hello" // var x = s[1:2:3] InvalidSliceExpr // InvalidShiftCount occurs when the right-hand side of a shift operation is // either non-integer, negative, or too large. // // Example: // var ( // x string // y int = 1 << x // ) InvalidShiftCount // InvalidShiftOperand occurs when the shifted operand is not an integer. // // Example: // var s = "hello" // var x = s << 2 InvalidShiftOperand // InvalidReceive occurs when there is a channel receive from a value that // is either not a channel, or is a send-only channel. // // Example: // func f() { // var x = 1 // <-x // } InvalidReceive // InvalidSend occurs when there is a channel send to a value that is not a // channel, or is a receive-only channel. // // Example: // func f() { // var x = 1 // x <- "hello!" // } InvalidSend // DuplicateLitKey occurs when an index is duplicated in a slice, array, or // map literal. // // Example: // var _ = []int{0:1, 0:2} // // Example: // var _ = map[string]int{"a": 1, "a": 2} DuplicateLitKey // MissingLitKey occurs when a map literal is missing a key expression. // // Example: // var _ = map[string]int{1} MissingLitKey // InvalidLitIndex occurs when the key in a key-value element of a slice or // array literal is not an integer constant. // // Example: // var i = 0 // var x = []string{i: "world"} InvalidLitIndex // OversizeArrayLit occurs when an array literal exceeds its length. // // Example: // var _ = [2]int{1,2,3} OversizeArrayLit // MixedStructLit occurs when a struct literal contains a mix of positional // and named elements. // // Example: // var _ = struct{i, j int}{i: 1, 2} MixedStructLit // InvalidStructLit occurs when a positional struct literal has an incorrect // number of values. // // Example: // var _ = struct{i, j int}{1,2,3} InvalidStructLit // MissingLitField occurs when a struct literal refers to a field that does // not exist on the struct type. // // Example: // var _ = struct{i int}{j: 2} MissingLitField // DuplicateLitField occurs when a struct literal contains duplicated // fields. // // Example: // var _ = struct{i int}{i: 1, i: 2} DuplicateLitField // UnexportedLitField occurs when a positional struct literal implicitly // assigns an unexported field of an imported type. UnexportedLitField // InvalidLitField occurs when a field name is not a valid identifier. // // Example: // var _ = struct{i int}{1: 1} InvalidLitField // UntypedLit occurs when a composite literal omits a required type // identifier. // // Example: // type outer struct{ // inner struct { i int } // } // // var _ = outer{inner: {1}} UntypedLit // InvalidLit occurs when a composite literal expression does not match its // type. // // Example: // type P *struct{ // x int // } // var _ = P {} InvalidLit // AmbiguousSelector occurs when a selector is ambiguous. // // Example: // type E1 struct { i int } // type E2 struct { i int } // type T struct { E1; E2 } // // var x T // var _ = x.i AmbiguousSelector // UndeclaredImportedName occurs when a package-qualified identifier is // undeclared by the imported package. // // Example: // import "go/types" // // var _ = types.NotAnActualIdentifier UndeclaredImportedName // UnexportedName occurs when a selector refers to an unexported identifier // of an imported package. // // Example: // import "reflect" // // type _ reflect.flag UnexportedName // UndeclaredName occurs when an identifier is not declared in the current // scope. // // Example: // var x T UndeclaredName // MissingFieldOrMethod occurs when a selector references a field or method // that does not exist. // // Example: // type T struct {} // // var x = T{}.f MissingFieldOrMethod // BadDotDotDotSyntax occurs when a "..." occurs in a context where it is // not valid. // // Example: // var _ = map[int][...]int{0: {}} BadDotDotDotSyntax // NonVariadicDotDotDot occurs when a "..." is used on the final argument to // a non-variadic function. // // Example: // func printArgs(s []string) { // for _, a := range s { // println(a) // } // } // // func f() { // s := []string{"a", "b", "c"} // printArgs(s...) // } NonVariadicDotDotDot // MisplacedDotDotDot occurs when a "..." is used somewhere other than the // final argument in a function declaration. // // Example: // func f(...int, int) MisplacedDotDotDot _ // InvalidDotDotDotOperand was removed. // InvalidDotDotDot occurs when a "..." is used in a non-variadic built-in // function. // // Example: // var s = []int{1, 2, 3} // var l = len(s...) InvalidDotDotDot // UncalledBuiltin occurs when a built-in function is used as a // function-valued expression, instead of being called. // // Per the spec: // "The built-in functions do not have standard Go types, so they can only // appear in call expressions; they cannot be used as function values." // // Example: // var _ = copy UncalledBuiltin // InvalidAppend occurs when append is called with a first argument that is // not a slice. // // Example: // var _ = append(1, 2) InvalidAppend // InvalidCap occurs when an argument to the cap built-in function is not of // supported type. // // See https://golang.org/ref/spec#Length_and_capacity for information on // which underlying types are supported as arguments to cap and len. // // Example: // var s = 2 // var x = cap(s) InvalidCap // InvalidClose occurs when close(...) is called with an argument that is // not of channel type, or that is a receive-only channel. // // Example: // func f() { // var x int // close(x) // } InvalidClose // InvalidCopy occurs when the arguments are not of slice type or do not // have compatible type. // // See https://golang.org/ref/spec#Appending_and_copying_slices for more // information on the type requirements for the copy built-in. // // Example: // func f() { // var x []int // y := []int64{1,2,3} // copy(x, y) // } InvalidCopy // InvalidComplex occurs when the complex built-in function is called with // arguments with incompatible types. // // Example: // var _ = complex(float32(1), float64(2)) InvalidComplex // InvalidDelete occurs when the delete built-in function is called with a // first argument that is not a map. // // Example: // func f() { // m := "hello" // delete(m, "e") // } InvalidDelete // InvalidImag occurs when the imag built-in function is called with an // argument that does not have complex type. // // Example: // var _ = imag(int(1)) InvalidImag // InvalidLen occurs when an argument to the len built-in function is not of // supported type. // // See https://golang.org/ref/spec#Length_and_capacity for information on // which underlying types are supported as arguments to cap and len. // // Example: // var s = 2 // var x = len(s) InvalidLen // SwappedMakeArgs occurs when make is called with three arguments, and its // length argument is larger than its capacity argument. // // Example: // var x = make([]int, 3, 2) SwappedMakeArgs // InvalidMake occurs when make is called with an unsupported type argument. // // See https://golang.org/ref/spec#Making_slices_maps_and_channels for // information on the types that may be created using make. // // Example: // var x = make(int) InvalidMake // InvalidReal occurs when the real built-in function is called with an // argument that does not have complex type. // // Example: // var _ = real(int(1)) InvalidReal // InvalidAssert occurs when a type assertion is applied to a // value that is not of interface type. // // Example: // var x = 1 // var _ = x.(float64) InvalidAssert // ImpossibleAssert occurs for a type assertion x.(T) when the value x of // interface cannot have dynamic type T, due to a missing or mismatching // method on T. // // Example: // type T int // // func (t *T) m() int { return int(*t) } // // type I interface { m() int } // // var x I // var _ = x.(T) ImpossibleAssert // InvalidConversion occurs when the argument type cannot be converted to the // target. // // See https://golang.org/ref/spec#Conversions for the rules of // convertibility. // // Example: // var x float64 // var _ = string(x) InvalidConversion // InvalidUntypedConversion occurs when there is no valid implicit // conversion from an untyped value satisfying the type constraints of the // context in which it is used. // // Example: // var _ = 1 + []int{} InvalidUntypedConversion // BadOffsetofSyntax occurs when unsafe.Offsetof is called with an argument // that is not a selector expression. // // Example: // import "unsafe" // // var x int // var _ = unsafe.Offsetof(x) BadOffsetofSyntax // InvalidOffsetof occurs when unsafe.Offsetof is called with a method // selector, rather than a field selector, or when the field is embedded via // a pointer. // // Per the spec: // // "If f is an embedded field, it must be reachable without pointer // indirections through fields of the struct. " // // Example: // import "unsafe" // // type T struct { f int } // type S struct { *T } // var s S // var _ = unsafe.Offsetof(s.f) // // Example: // import "unsafe" // // type S struct{} // // func (S) m() {} // // var s S // var _ = unsafe.Offsetof(s.m) InvalidOffsetof // UnusedExpr occurs when a side-effect free expression is used as a // statement. Such a statement has no effect. // // Example: // func f(i int) { // i*i // } UnusedExpr // UnusedVar occurs when a variable is declared but unused. // // Example: // func f() { // x := 1 // } UnusedVar // MissingReturn occurs when a function with results is missing a return // statement. // // Example: // func f() int {} MissingReturn // WrongResultCount occurs when a return statement returns an incorrect // number of values. // // Example: // func ReturnOne() int { // return 1, 2 // } WrongResultCount // OutOfScopeResult occurs when the name of a value implicitly returned by // an empty return statement is shadowed in a nested scope. // // Example: // func factor(n int) (i int) { // for i := 2; i < n; i++ { // if n%i == 0 { // return // } // } // return 0 // } OutOfScopeResult // InvalidCond occurs when an if condition is not a boolean expression. // // Example: // func checkReturn(i int) { // if i { // panic("non-zero return") // } // } InvalidCond // InvalidPostDecl occurs when there is a declaration in a for-loop post // statement. // // Example: // func f() { // for i := 0; i < 10; j := 0 {} // } InvalidPostDecl _ // InvalidChanRange was removed. // InvalidIterVar occurs when two iteration variables are used while ranging // over a channel. // // Example: // func f(c chan int) { // for k, v := range c { // println(k, v) // } // } InvalidIterVar // InvalidRangeExpr occurs when the type of a range expression is not // a valid type for use with a range loop. // // Example: // func f(f float64) { // for j := range f { // println(j) // } // } InvalidRangeExpr // MisplacedBreak occurs when a break statement is not within a for, switch, // or select statement of the innermost function definition. // // Example: // func f() { // break // } MisplacedBreak // MisplacedContinue occurs when a continue statement is not within a for // loop of the innermost function definition. // // Example: // func sumeven(n int) int { // proceed := func() { // continue // } // sum := 0 // for i := 1; i <= n; i++ { // if i % 2 != 0 { // proceed() // } // sum += i // } // return sum // } MisplacedContinue // MisplacedFallthrough occurs when a fallthrough statement is not within an // expression switch. // // Example: // func typename(i interface{}) string { // switch i.(type) { // case int64: // fallthrough // case int: // return "int" // } // return "unsupported" // } MisplacedFallthrough // DuplicateCase occurs when a type or expression switch has duplicate // cases. // // Example: // func printInt(i int) { // switch i { // case 1: // println("one") // case 1: // println("One") // } // } DuplicateCase // DuplicateDefault occurs when a type or expression switch has multiple // default clauses. // // Example: // func printInt(i int) { // switch i { // case 1: // println("one") // default: // println("One") // default: // println("1") // } // } DuplicateDefault // BadTypeKeyword occurs when a .(type) expression is used anywhere other // than a type switch. // // Example: // type I interface { // m() // } // var t I // var _ = t.(type) BadTypeKeyword // InvalidTypeSwitch occurs when .(type) is used on an expression that is // not of interface type. // // Example: // func f(i int) { // switch x := i.(type) {} // } InvalidTypeSwitch // InvalidExprSwitch occurs when a switch expression is not comparable. // // Example: // func _() { // var a struct{ _ func() } // switch a /* ERROR cannot switch on a */ { // } // } InvalidExprSwitch // InvalidSelectCase occurs when a select case is not a channel send or // receive. // // Example: // func checkChan(c <-chan int) bool { // select { // case c: // return true // default: // return false // } // } InvalidSelectCase // UndeclaredLabel occurs when an undeclared label is jumped to. // // Example: // func f() { // goto L // } UndeclaredLabel // DuplicateLabel occurs when a label is declared more than once. // // Example: // func f() int { // L: // L: // return 1 // } DuplicateLabel // MisplacedLabel occurs when a break or continue label is not on a for, // switch, or select statement. // // Example: // func f() { // L: // a := []int{1,2,3} // for _, e := range a { // if e > 10 { // break L // } // println(a) // } // } MisplacedLabel // UnusedLabel occurs when a label is declared and not used. // // Example: // func f() { // L: // } UnusedLabel // JumpOverDecl occurs when a label jumps over a variable declaration. // // Example: // func f() int { // goto L // x := 2 // L: // x++ // return x // } JumpOverDecl // JumpIntoBlock occurs when a forward jump goes to a label inside a nested // block. // // Example: // func f(x int) { // goto L // if x > 0 { // L: // print("inside block") // } // } JumpIntoBlock // InvalidMethodExpr occurs when a pointer method is called but the argument // is not addressable. // // Example: // type T struct {} // // func (*T) m() int { return 1 } // // var _ = T.m(T{}) InvalidMethodExpr // WrongArgCount occurs when too few or too many arguments are passed by a // function call. // // Example: // func f(i int) {} // var x = f() WrongArgCount // InvalidCall occurs when an expression is called that is not of function // type. // // Example: // var x = "x" // var y = x() InvalidCall // UnusedResults occurs when a restricted expression-only built-in function // is suspended via go or defer. Such a suspension discards the results of // these side-effect free built-in functions, and therefore is ineffectual. // // Example: // func f(a []int) int { // defer len(a) // return i // } UnusedResults // InvalidDefer occurs when a deferred expression is not a function call, // for example if the expression is a type conversion. // // Example: // func f(i int) int { // defer int32(i) // return i // } InvalidDefer // InvalidGo occurs when a go expression is not a function call, for example // if the expression is a type conversion. // // Example: // func f(i int) int { // go int32(i) // return i // } InvalidGo // All codes below were added in Go 1.17. // BadDecl occurs when a declaration has invalid syntax. BadDecl // RepeatedDecl occurs when an identifier occurs more than once on the left // hand side of a short variable declaration. // // Example: // func _() { // x, y, y := 1, 2, 3 // } RepeatedDecl // InvalidUnsafeAdd occurs when unsafe.Add is called with a // length argument that is not of integer type. // It also occurs if it is used in a package compiled for a // language version before go1.17. // // Example: // import "unsafe" // // var p unsafe.Pointer // var _ = unsafe.Add(p, float64(1)) InvalidUnsafeAdd // InvalidUnsafeSlice occurs when unsafe.Slice is called with a // pointer argument that is not of pointer type or a length argument // that is not of integer type, negative, or out of bounds. // It also occurs if it is used in a package compiled for a language // version before go1.17. // // Example: // import "unsafe" // // var x int // var _ = unsafe.Slice(x, 1) // // Example: // import "unsafe" // // var x int // var _ = unsafe.Slice(&x, float64(1)) // // Example: // import "unsafe" // // var x int // var _ = unsafe.Slice(&x, -1) // // Example: // import "unsafe" // // var x int // var _ = unsafe.Slice(&x, uint64(1) << 63) InvalidUnsafeSlice // All codes below were added in Go 1.18. // UnsupportedFeature occurs when a language feature is used that is not // supported at this Go version. UnsupportedFeature // NotAGenericType occurs when a non-generic type is used where a generic // type is expected: in type or function instantiation. // // Example: // type T int // // var _ T[int] NotAGenericType // WrongTypeArgCount occurs when a type or function is instantiated with an // incorrect number of type arguments, including when a generic type or // function is used without instantiation. // // Errors involving failed type inference are assigned other error codes. // // Example: // type T[p any] int // // var _ T[int, string] // // Example: // func f[T any]() {} // // var x = f WrongTypeArgCount // CannotInferTypeArgs occurs when type or function type argument inference // fails to infer all type arguments. // // Example: // func f[T any]() {} // // func _() { // f() // } CannotInferTypeArgs // InvalidTypeArg occurs when a type argument does not satisfy its // corresponding type parameter constraints. // // Example: // type T[P ~int] struct{} // // var _ T[string] InvalidTypeArg // arguments? InferenceFailed // InvalidInstanceCycle occurs when an invalid cycle is detected // within the instantiation graph. // // Example: // func f[T any]() { f[*T]() } InvalidInstanceCycle // InvalidUnion occurs when an embedded union or approximation element is // not valid. // // Example: // type _ interface { // ~int | interface{ m() } // } InvalidUnion // MisplacedConstraintIface occurs when a constraint-type interface is used // outside of constraint position. // // Example: // type I interface { ~int } // // var _ I MisplacedConstraintIface // InvalidMethodTypeParams occurs when methods have type parameters. // // It cannot be encountered with an AST parsed using go/parser. InvalidMethodTypeParams // MisplacedTypeParam occurs when a type parameter is used in a place where // it is not permitted. // // Example: // type T[P any] P // // Example: // type T[P any] struct{ *P } MisplacedTypeParam // InvalidUnsafeSliceData occurs when unsafe.SliceData is called with // an argument that is not of slice type. It also occurs if it is used // in a package compiled for a language version before go1.20. // // Example: // import "unsafe" // // var x int // var _ = unsafe.SliceData(x) InvalidUnsafeSliceData // InvalidUnsafeString occurs when unsafe.String is called with // a length argument that is not of integer type, negative, or // out of bounds. It also occurs if it is used in a package // compiled for a language version before go1.20. // // Example: // import "unsafe" // // var b [10]byte // var _ = unsafe.String(&b[0], -1) InvalidUnsafeString // InvalidUnsafeStringData occurs if it is used in a package // compiled for a language version before go1.20. _ // not used anymore // InvalidClear occurs when clear is called with an argument // that is not of map or slice type. // // Example: // func _(x int) { // clear(x) // } InvalidClear // TypeTooLarge occurs if unsafe.Sizeof or unsafe.Offsetof is // called with an expression whose type is too large. // // Example: // import "unsafe" // // type E [1 << 31 - 1]int // var a [1 << 31]E // var _ = unsafe.Sizeof(a) // // Example: // import "unsafe" // // type E [1 << 31 - 1]int // var s struct { // _ [1 << 31]E // x int // } // var _ = unsafe.Offsetof(s.x) TypeTooLarge // InvalidMinMaxOperand occurs if min or max is called // with an operand that cannot be ordered because it // does not support the < operator. // // Example: // const _ = min(true) // // Example: // var s, t []byte // var _ = max(s, t) InvalidMinMaxOperand // TooNew indicates that, through build tags or a go.mod file, // a source file requires a version of Go that is newer than // the logic of the type checker. As a consequence, the type // checker may produce spurious errors or fail to report real // errors. The solution is to rebuild the application with a // newer Go release. TooNew ) ================================================ FILE: x/typesutil/eval.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package typesutil import ( "go/types" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/token" ) // CheckExpr type checks the expression expr as if it had appeared at position // pos of package pkg. Type information about the expression is recorded in // info. The expression may be an identifier denoting an uninstantiated generic // function or type. // // If pkg == nil, the Universe scope is used and the provided // position pos is ignored. If pkg != nil, and pos is invalid, // the package scope is used. Otherwise, pos must belong to the // package. // // An error is returned if pos is not within the package or // if the node cannot be type-checked. // // Note: Eval and CheckExpr should not be used instead of running Check // to compute types and values, but in addition to Check, as these // functions ignore the context in which an expression is used (e.g., an // assignment). Thus, top-level untyped constants will return an // untyped type rather than the respective context-specific type. func CheckExpr(fset *token.FileSet, pkg *types.Package, pos token.Pos, expr ast.Expr, info *Info) (err error) { panic("todo") } ================================================ FILE: x/typesutil/exprstring.go ================================================ // Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This file implements printing of expressions. package typesutil import ( "bytes" "fmt" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/x/typesutil/typeparams" ) // ExprString returns the (possibly shortened) string representation for x. // Shortened representations are suitable for user interfaces but may not // necessarily follow Go syntax. func ExprString(x ast.Expr) string { var buf bytes.Buffer WriteExpr(&buf, x) return buf.String() } // WriteExpr writes the (possibly shortened) string representation for x to buf. // Shortened representations are suitable for user interfaces but may not // necessarily follow Go syntax. func WriteExpr(buf *bytes.Buffer, x ast.Expr) { // The AST preserves source-level parentheses so there is // no need to introduce them here to correct for different // operator precedences. (This assumes that the AST was // generated by a Go parser.) switch x := x.(type) { default: fmt.Fprintf(buf, "(ast: %T)", x) // nil, ast.BadExpr, ast.KeyValueExpr case *ast.Ident: buf.WriteString(x.Name) case *ast.Ellipsis: buf.WriteString("...") if x.Elt != nil { WriteExpr(buf, x.Elt) } case *ast.BasicLit: buf.WriteString(x.Value) case *ast.FuncLit: buf.WriteByte('(') WriteExpr(buf, x.Type) buf.WriteString(" literal)") // shortened case *ast.CompositeLit: WriteExpr(buf, x.Type) buf.WriteByte('{') if len(x.Elts) > 0 { buf.WriteString("…") } buf.WriteByte('}') case *ast.ParenExpr: buf.WriteByte('(') WriteExpr(buf, x.X) buf.WriteByte(')') case *ast.SelectorExpr: WriteExpr(buf, x.X) buf.WriteByte('.') buf.WriteString(x.Sel.Name) case *ast.IndexExpr, *ast.IndexListExpr: ix := typeparams.UnpackIndexExpr(x) WriteExpr(buf, ix.X) buf.WriteByte('[') writeExprList(buf, ix.Indices) buf.WriteByte(']') case *ast.SliceExpr: WriteExpr(buf, x.X) buf.WriteByte('[') if x.Low != nil { WriteExpr(buf, x.Low) } buf.WriteByte(':') if x.High != nil { WriteExpr(buf, x.High) } if x.Slice3 { buf.WriteByte(':') if x.Max != nil { WriteExpr(buf, x.Max) } } buf.WriteByte(']') case *ast.TypeAssertExpr: WriteExpr(buf, x.X) buf.WriteString(".(") WriteExpr(buf, x.Type) buf.WriteByte(')') case *ast.CallExpr: WriteExpr(buf, x.Fun) buf.WriteByte('(') writeExprList(buf, x.Args) if x.Ellipsis.IsValid() { buf.WriteString("...") } buf.WriteByte(')') case *ast.StarExpr: buf.WriteByte('*') WriteExpr(buf, x.X) case *ast.UnaryExpr: buf.WriteString(x.Op.String()) WriteExpr(buf, x.X) case *ast.BinaryExpr: WriteExpr(buf, x.X) buf.WriteByte(' ') buf.WriteString(x.Op.String()) buf.WriteByte(' ') WriteExpr(buf, x.Y) case *ast.ArrayType: buf.WriteByte('[') if x.Len != nil { WriteExpr(buf, x.Len) } buf.WriteByte(']') WriteExpr(buf, x.Elt) case *ast.StructType: buf.WriteString("struct{") writeFieldList(buf, x.Fields.List, "; ", false) buf.WriteByte('}') case *ast.FuncType: buf.WriteString("func") writeSigExpr(buf, x) case *ast.InterfaceType: buf.WriteString("interface{") writeFieldList(buf, x.Methods.List, "; ", true) buf.WriteByte('}') case *ast.MapType: buf.WriteString("map[") WriteExpr(buf, x.Key) buf.WriteByte(']') WriteExpr(buf, x.Value) case *ast.ChanType: var s string switch x.Dir { case ast.SEND: s = "chan<- " case ast.RECV: s = "<-chan " default: s = "chan " } buf.WriteString(s) WriteExpr(buf, x.Value) } } func writeSigExpr(buf *bytes.Buffer, sig *ast.FuncType) { buf.WriteByte('(') writeFieldList(buf, sig.Params.List, ", ", false) buf.WriteByte(')') res := sig.Results n := res.NumFields() if n == 0 { // no result return } buf.WriteByte(' ') if n == 1 && len(res.List[0].Names) == 0 { // single unnamed result WriteExpr(buf, res.List[0].Type) return } // multiple or named result(s) buf.WriteByte('(') writeFieldList(buf, res.List, ", ", false) buf.WriteByte(')') } func writeFieldList(buf *bytes.Buffer, list []*ast.Field, sep string, iface bool) { for i, f := range list { if i > 0 { buf.WriteString(sep) } // field list names writeIdentList(buf, f.Names) // types of interface methods consist of signatures only if sig, _ := f.Type.(*ast.FuncType); sig != nil && iface { writeSigExpr(buf, sig) continue } // named fields are separated with a blank from the field type if len(f.Names) > 0 { buf.WriteByte(' ') } WriteExpr(buf, f.Type) // ignore tag } } func writeIdentList(buf *bytes.Buffer, list []*ast.Ident) { for i, x := range list { if i > 0 { buf.WriteString(", ") } buf.WriteString(x.Name) } } func writeExprList(buf *bytes.Buffer, list []ast.Expr) { for i, x := range list { if i > 0 { buf.WriteString(", ") } WriteExpr(buf, x) } } ================================================ FILE: x/typesutil/exprstring_test.go ================================================ // Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package typesutil_test import ( "testing" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/x/typesutil" ) type testEntry struct { src, str string } // dup returns a testEntry where both src and str are the same. func dup(s string) testEntry { return testEntry{s, s} } var testExprs = []testEntry{ // basic type literals dup("x"), dup("true"), dup("42"), dup("3.1415"), dup("2.71828i"), dup(`'a'`), dup(`"foo"`), dup("`bar`"), // func and composite literals {"func(){}", "(func() literal)"}, {"func(x int) complex128 {}", "(func(x int) complex128 literal)"}, {"[]int{1, 2, 3}", "[]int{…}"}, // type expressions dup("[1 << 10]byte"), dup("[]int"), dup("*int"), dup("struct{x int}"), dup("func()"), dup("func(int, float32) string"), dup("interface{m()}"), dup("interface{m() string; n(x int)}"), dup("map[string]int"), dup("chan E"), dup("<-chan E"), dup("chan<- E"), // non-type expressions dup("(x)"), dup("x.f"), dup("a[i]"), dup("s[:]"), dup("s[i:]"), dup("s[:j]"), dup("s[i:j]"), dup("s[:j:k]"), dup("s[i:j:k]"), dup("x.(T)"), dup("x.([10]int)"), dup("x.([...]int)"), dup("x.(struct{})"), dup("x.(struct{x int; y, z float32; E})"), dup("x.(func())"), dup("x.(func(x int))"), dup("x.(func() int)"), dup("x.(func(x, y int, z float32) (r int))"), dup("x.(func(a, b, c int))"), dup("x.(func(x ...T))"), dup("x.(interface{})"), dup("x.(interface{m(); n(x int); E})"), dup("x.(interface{m(); n(x int) T; E; F})"), dup("x.(map[K]V)"), dup("x.(chan E)"), dup("x.(<-chan E)"), dup("x.(chan<- chan int)"), dup("x.(chan<- <-chan int)"), dup("x.(<-chan chan int)"), dup("x.(chan (<-chan int))"), dup("f()"), dup("f(x)"), dup("int(x)"), dup("f(x, x + y)"), dup("f(s...)"), dup("f(a, s...)"), dup("*x"), dup("&x"), dup("x + y"), dup("x + y << (2 * s)"), } func TestExprString(t *testing.T) { for _, test := range testExprs { x, err := parser.ParseExpr(test.src) if err != nil { t.Errorf("%s: %s", test.src, err) continue } if got := typesutil.ExprString(x); got != test.str { t.Errorf("%s: got %s, want %s", test.src, got, test.str) } } } ================================================ FILE: x/typesutil/gopinfo.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package typesutil import ( "fmt" "go/types" "github.com/goplus/gogen" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/cl" "github.com/goplus/xgo/token" "github.com/qiniu/x/log" ) // ----------------------------------------------------------------------------- // Info holds result type information for a type-checked package. // Only the information for which a map is provided is collected. // If the package has type errors, the collected information may // be incomplete. type Info struct { // Types maps expressions to their types, and for constant // expressions, also their values. Invalid expressions are // omitted. // // For (possibly parenthesized) identifiers denoting built-in // functions, the recorded signatures are call-site specific: // if the call result is not a constant, the recorded type is // an argument-specific signature. Otherwise, the recorded type // is invalid. // // The Types map does not record the type of every identifier, // only those that appear where an arbitrary expression is // permitted. For instance, the identifier f in a selector // expression x.f is found only in the Selections map, the // identifier z in a variable declaration 'var z int' is found // only in the Defs map, and identifiers denoting packages in // qualified identifiers are collected in the Uses map. Types map[ast.Expr]types.TypeAndValue // Instances maps identifiers denoting generic types or functions to their // type arguments and instantiated type. // // For example, Instances will map the identifier for 'T' in the type // instantiation T[int, string] to the type arguments [int, string] and // resulting instantiated *Named type. Given a generic function // func F[A any](A), Instances will map the identifier for 'F' in the call // expression F(int(1)) to the inferred type arguments [int], and resulting // instantiated *Signature. // // Invariant: Instantiating Uses[id].Type() with Instances[id].TypeArgs // results in an equivalent of Instances[id].Type. Instances map[*ast.Ident]types.Instance // Defs maps identifiers to the objects they define (including // package names, dots "." of dot-imports, and blank "_" identifiers). // For identifiers that do not denote objects (e.g., the package name // in package clauses, or symbolic variables t in t := x.(type) of // type switch headers), the corresponding objects are nil. // // For an embedded field, Defs returns the field *Var it defines. // // Invariant: Defs[id] == nil || Defs[id].Pos() == id.Pos() Defs map[*ast.Ident]types.Object // Uses maps identifiers to the objects they denote. // // For an embedded field, Uses returns the *TypeName it denotes. // // Invariant: Uses[id].Pos() != id.Pos() Uses map[*ast.Ident]types.Object // Implicits maps nodes to their implicitly declared objects, if any. // The following node and object types may appear: // // node declared object // // *ast.ImportSpec *PkgName for imports without renames // *ast.CaseClause type-specific *Var for each type switch case clause (incl. default) // *ast.Field anonymous parameter *Var (incl. unnamed results) // *ast.FunLit function literal in *ast.OverloadFuncDecl // Implicits map[ast.Node]types.Object // Selections maps selector expressions (excluding qualified identifiers) // to their corresponding selections. Selections map[*ast.SelectorExpr]*types.Selection // Scopes maps ast.Nodes to the scopes they define. Package scopes are not // associated with a specific node but with all files belonging to a package. // Thus, the package scope can be found in the type-checked Package object. // Scopes nest, with the Universe scope being the outermost scope, enclosing // the package scope, which contains (one or more) files scopes, which enclose // function scopes which in turn enclose statement and function literal scopes. // Note that even though package-level functions are declared in the package // scope, the function scopes are embedded in the file scope of the file // containing the function declaration. // // The following node types may appear in Scopes: // // *ast.File // *ast.FuncType // *ast.TypeSpec // *ast.BlockStmt // *ast.IfStmt // *ast.SwitchStmt // *ast.TypeSwitchStmt // *ast.CaseClause // *ast.CommClause // *ast.ForStmt // *ast.RangeStmt // *ast.ForPhraseStmt // *ast.ForPhrase // *ast.LambdaExpr // *ast.LambdaExpr2 // Scopes map[ast.Node]*types.Scope // InitOrder is the list of package-level initializers in the order in which // they must be executed. Initializers referring to variables related by an // initialization dependency appear in topological order, the others appear // in source order. Variables without an initialization expression do not // appear in this list. // InitOrder []*Initializer // Overloads maps identifiers to the overload decl object. Overloads map[*ast.Ident]types.Object } // ObjectOf returns the object denoted by the specified id, // or nil if not found. // // If id is an embedded struct field, ObjectOf returns the field (*Var) // it defines, not the type (*TypeName) it uses. // // Precondition: the Uses and Defs maps are populated. func (info *Info) ObjectOf(id *ast.Ident) types.Object { if obj := info.Defs[id]; obj != nil { return obj } return info.Uses[id] } // TypeOf returns the type of expression e, or nil if not found. // Precondition: the Types, Uses and Defs maps are populated. func (info *Info) TypeOf(e ast.Expr) types.Type { if t, ok := info.Types[e]; ok { return t.Type } if id, _ := e.(*ast.Ident); id != nil { if obj := info.ObjectOf(id); obj != nil { return obj.Type() } } return nil } // Returns the overloaded function declaration corresponding to the ident and its overloaded function members func (info *Info) OverloadOf(id *ast.Ident) (types.Object, []types.Object) { if obj := info.Overloads[id]; obj != nil { if sig, ok := obj.Type().(*types.Signature); ok { if _, objs := gogen.CheckSigFuncExObjects(sig); len(objs) > 1 { return obj, objs } } } return nil, nil } // ----------------------------------------------------------------------------- type xgoRecorder struct { *Info } // NewRecorder creates a new recorder for cl.NewPackage. func NewRecorder(info *Info) cl.Recorder { return xgoRecorder{info} } // Type maps expressions to their types, and for constant // expressions, also their values. Invalid expressions are // omitted. // // For (possibly parenthesized) identifiers denoting built-in // functions, the recorded signatures are call-site specific: // if the call result is not a constant, the recorded type is // an argument-specific signature. Otherwise, the recorded type // is invalid. // // The Types map does not record the type of every identifier, // only those that appear where an arbitrary expression is // permitted. For instance, the identifier f in a selector // expression x.f is found only in the Selections map, the // identifier z in a variable declaration 'var z int' is found // only in the Defs map, and identifiers denoting packages in // qualified identifiers are collected in the Uses map. func (info xgoRecorder) Type(e ast.Expr, tv types.TypeAndValue) { if debugVerbose { log.Println("==> Type:", e, tv.Type) } if info.Types != nil { info.Types[e] = tv } } // Instantiate maps identifiers denoting generic types or functions to their // type arguments and instantiated type. // // For example, Instantiate will map the identifier for 'T' in the type // instantiation T[int, string] to the type arguments [int, string] and // resulting instantiated *Named type. Given a generic function // func F[A any](A), Instances will map the identifier for 'F' in the call // expression F(int(1)) to the inferred type arguments [int], and resulting // instantiated *Signature. // // Invariant: Instantiating Uses[id].Type() with Instances[id].TypeArgs // results in an equivalent of Instances[id].Type. func (info xgoRecorder) Instantiate(id *ast.Ident, inst types.Instance) { if info.Instances != nil { info.Instances[id] = inst } } // Def maps identifiers to the objects they define (including // package names, dots "." of dot-imports, and blank "_" identifiers). // For identifiers that do not denote objects (e.g., the package name // in package clauses, or symbolic variables t in t := x.(type) of // type switch headers), the corresponding objects are nil. // // For an embedded field, Def maps the field *Var it defines. // // Invariant: Defs[id] == nil || Defs[id].Pos() == id.Pos() func (info xgoRecorder) Def(id *ast.Ident, obj types.Object) { if debugVerbose { log.Println("==> Def:", id, obj) } if info.Defs != nil { info.Defs[id] = obj } } // Use maps identifiers to the objects they denote. // // For an embedded field, Use maps the *TypeName it denotes. // // Invariant: Uses[id].Pos() != id.Pos() func (info xgoRecorder) Use(id *ast.Ident, obj types.Object) { if debugVerbose { log.Println("==> Use:", id, obj) } if info.Uses != nil { info.Uses[id] = obj } if info.Overloads != nil { if sig, ok := obj.Type().(*types.Signature); ok { if ext, ok := gogen.CheckSigFuncEx(sig); ok { if debugVerbose { log.Println("==> Overloads:", id, ext) } info.Overloads[id] = obj } } } } // Implicit maps nodes to their implicitly declared objects, if any. // The following node and object types may appear: // // node declared object // // *ast.ImportSpec *PkgName for imports without renames // *ast.CaseClause type-specific *Var for each type switch case clause (incl. default) // *ast.Field anonymous parameter *Var (incl. unnamed results) func (info xgoRecorder) Implicit(node ast.Node, obj types.Object) { if debugVerbose { log.Println("==> Implicit:", obj) } if info.Implicits != nil { info.Implicits[node] = obj } } // Select maps selector expressions (excluding qualified identifiers) // to their corresponding selections. func (info xgoRecorder) Select(e *ast.SelectorExpr, sel *types.Selection) { if info.Selections != nil { info.Selections[e] = sel } } // Scope maps ast.Nodes to the scopes they define. Package scopes are not // associated with a specific node but with all files belonging to a package. // Thus, the package scope can be found in the type-checked Package object. // Scopes nest, with the Universe scope being the outermost scope, enclosing // the package scope, which contains (one or more) files scopes, which enclose // function scopes which in turn enclose statement and function literal scopes. // Note that even though package-level functions are declared in the package // scope, the function scopes are embedded in the file scope of the file // containing the function declaration. // // The following node types may appear in Scopes: // // *ast.File // *ast.FuncType // *ast.TypeSpec // *ast.BlockStmt // *ast.IfStmt // *ast.SwitchStmt // *ast.TypeSwitchStmt // *ast.CaseClause // *ast.CommClause // *ast.ForStmt // *ast.RangeStmt func (info xgoRecorder) Scope(n ast.Node, scope *types.Scope) { if debugVerbose { log.Println("==> Scope:", scope) } if info.Scopes != nil { info.Scopes[n] = scope } } // ----------------------------------------------------------------------------- // An Error describes a type-checking error; it implements the error interface. // A "soft" error is an error that still permits a valid interpretation of a // package (such as "unused variable"); "hard" errors may lead to unpredictable // behavior if ignored. type Error struct { Fset *token.FileSet // file set for interpretation of Pos Pos, End token.Pos // error position Msg string // error message Code Code // error code Soft bool // if set, error is "soft" } // Error returns an error string formatted as follows: // filename:line:column: message func (err Error) Error() string { return fmt.Sprintf("%s: %s", err.Fset.Position(err.Pos), err.Msg) } ================================================ FILE: x/typesutil/info_test.go ================================================ package typesutil_test import ( "fmt" goast "go/ast" goformat "go/format" goparser "go/parser" "go/constant" "go/importer" "go/types" "sort" "strings" "testing" "unsafe" "github.com/goplus/gogen" "github.com/goplus/mod/env" "github.com/goplus/mod/modfile" "github.com/goplus/mod/modload" "github.com/goplus/mod/xgomod" "github.com/goplus/xgo/ast" "github.com/goplus/xgo/format" "github.com/goplus/xgo/parser" "github.com/goplus/xgo/token" "github.com/goplus/xgo/tool" "github.com/goplus/xgo/x/typesutil" ) var spxProject = &modfile.Project{ Ext: ".tgmx", Class: "*MyGame", Works: []*modfile.Class{{Ext: ".tspx", Class: "Sprite"}}, PkgPaths: []string{"github.com/goplus/xgo/cl/internal/spx", "math"}} var spxMod *xgomod.Module func init() { spxMod = xgomod.New(modload.Default) spxMod.Opt.Projects = append(spxMod.Opt.Projects, spxProject) spxMod.ImportClasses() } func lookupClass(ext string) (c *modfile.Project, ok bool) { switch ext { case ".tgmx", ".tspx": return spxProject, true } return } func spxParserConf() parser.Config { return parser.Config{ ClassKind: func(fname string) (isProj bool, ok bool) { ext := modfile.ClassExt(fname) c, ok := lookupClass(ext) if ok { isProj = c.IsProj(ext, fname) } return }, } } func parseMixedSource(mod *xgomod.Module, fset *token.FileSet, name, src string, goname string, gosrc string, parserConf parser.Config, updateGoTypesOverload bool) (*types.Package, *typesutil.Info, *types.Info, error) { f, err := parser.ParseEntry(fset, name, src, parserConf) if err != nil { return nil, nil, nil, err } var gofiles []*goast.File if len(gosrc) > 0 { f, err := goparser.ParseFile(fset, goname, gosrc, goparser.ParseComments) if err != nil { return nil, nil, nil, err } gofiles = append(gofiles, f) } conf := &types.Config{} conf.Importer = tool.NewImporter(nil, &env.XGo{Root: "../..", Version: "1.0"}, fset) chkOpts := &typesutil.Config{ Types: types.NewPackage("main", f.Name.Name), Fset: fset, Mod: mod, UpdateGoTypesOverload: updateGoTypesOverload, } info := &typesutil.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), Implicits: make(map[ast.Node]types.Object), Selections: make(map[*ast.SelectorExpr]*types.Selection), Scopes: make(map[ast.Node]*types.Scope), Overloads: make(map[*ast.Ident]types.Object), } ginfo := &types.Info{ Types: make(map[goast.Expr]types.TypeAndValue), Defs: make(map[*goast.Ident]types.Object), Uses: make(map[*goast.Ident]types.Object), Implicits: make(map[goast.Node]types.Object), Selections: make(map[*goast.SelectorExpr]*types.Selection), Scopes: make(map[goast.Node]*types.Scope), } check := typesutil.NewChecker(conf, chkOpts, ginfo, info) err = check.Files(gofiles, []*ast.File{f}) return chkOpts.Types, info, ginfo, err } func parseSource(fset *token.FileSet, filename string, src any, mode parser.Mode) (*types.Package, *typesutil.Info, error) { f, err := parser.ParseEntry(fset, filename, src, parser.Config{ Mode: mode, }) if err != nil { return nil, nil, err } pkg := types.NewPackage("", f.Name.Name) conf := &types.Config{} xgo := &env.XGo{Version: "1.0"} conf.Importer = tool.NewImporter(nil, xgo, fset) chkOpts := &typesutil.Config{ Types: pkg, Fset: fset, Mod: xgomod.Default, } info := &typesutil.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), Implicits: make(map[ast.Node]types.Object), Selections: make(map[*ast.SelectorExpr]*types.Selection), Scopes: make(map[ast.Node]*types.Scope), Overloads: make(map[*ast.Ident]types.Object), } check := typesutil.NewChecker(conf, chkOpts, nil, info) err = check.Files(nil, []*ast.File{f}) return pkg, info, err } func parseGoSource(fset *token.FileSet, filename string, src any, mode goparser.Mode) (*types.Package, *types.Info, error) { f, err := goparser.ParseFile(fset, filename, src, mode) if err != nil { return nil, nil, err } conf := &types.Config{} conf.Importer = importer.Default() info := &types.Info{ Types: make(map[goast.Expr]types.TypeAndValue), Defs: make(map[*goast.Ident]types.Object), Uses: make(map[*goast.Ident]types.Object), Implicits: make(map[goast.Node]types.Object), Selections: make(map[*goast.SelectorExpr]*types.Selection), Scopes: make(map[goast.Node]*types.Scope), } pkg := types.NewPackage("", f.Name.Name) check := types.NewChecker(conf, fset, pkg, info) err = check.Files([]*goast.File{f}) return pkg, info, err } func testXGoInfo(t *testing.T, src string, gosrc string, expect string) { testXGoInfoEx(t, xgomod.Default, "main.xgo", src, "main.go", gosrc, expect, parser.Config{}) } func testSpxInfo(t *testing.T, name string, src string, expect string) { testXGoInfoEx(t, spxMod, name, src, "main.go", "", expect, spxParserConf()) } func testXGoInfoEx(t *testing.T, mod *xgomod.Module, name string, src string, goname string, gosrc string, expect string, parseConf parser.Config) { fset := token.NewFileSet() _, info, _, err := parseMixedSource(mod, fset, name, src, goname, gosrc, parseConf, false) if err != nil { t.Fatal("parserMixedSource error", err) } var list []string list = append(list, "== types ==") list = append(list, typesList(fset, info.Types, false)...) list = append(list, "== defs ==") list = append(list, defsList(fset, info.Defs, true)...) list = append(list, "== uses ==") list = append(list, usesList(fset, info.Uses)...) if len(info.Overloads) > 0 { list = append(list, "== overloads ==") list = append(list, overloadsList(fset, info.Overloads)...) } result := strings.Join(list, "\n") t.Log(result) if result != expect { t.Fatal("bad expect\n", expect) } } func testInfo(t *testing.T, src any) { fset := token.NewFileSet() _, info, err := parseSource(fset, "main.xgo", src, parser.ParseComments) if err != nil { t.Fatal("parserSource error", err) } _, goinfo, err := parseGoSource(fset, "main.go", src, goparser.ParseComments) if err != nil { t.Fatal("parserGoSource error", err) } testItems(t, "types", typesList(fset, info.Types, true), goTypesList(fset, goinfo.Types, true)) testItems(t, "defs", defsList(fset, info.Defs, true), goDefsList(fset, goinfo.Defs, true)) testItems(t, "uses", usesList(fset, info.Uses), goUsesList(fset, goinfo.Uses)) // TODO check selections //testItems(t, "selections", selectionList(fset, info.Selections), goSelectionList(fset, goinfo.Selections)) } func testItems(t *testing.T, name string, items []string, goitems []string) { text := strings.Join(items, "\n") gotext := strings.Join(goitems, "\n") if len(items) != len(goitems) || text != gotext { t.Errorf(`====== check %v error (XGo count: %v, Go count %v) ====== ------ XGo ------ %v ------ Go ------ %v `, name, len(items), len(goitems), text, gotext) } else { t.Logf(`====== check %v pass (count: %v) ====== %v `, name, len(items), text) } } func sortItems(items []string) []string { sort.Strings(items) for i := 0; i < len(items); i++ { items[i] = fmt.Sprintf("%03v: %v", i, items[i]) } return items } func typesList(fset *token.FileSet, types map[ast.Expr]types.TypeAndValue, skipBasicLit bool) []string { var items []string for expr, tv := range types { var buf strings.Builder posn := fset.Position(expr.Pos()) tvstr := tv.Type.String() if skipBasicLit { if t, ok := expr.(*ast.BasicLit); ok { tvstr = t.Kind.String() } } if tv.Value != nil { tvstr += " = " + tv.Value.String() } // line:col | expr | mode : type = value fmt.Fprintf(&buf, "%2d:%2d | %-19s %-30T | %-7s : %s | %v", posn.Line, posn.Column, exprString(fset, expr), expr, mode(tv), tvstr, (*TypeAndValue)(unsafe.Pointer(&tv)).mode) items = append(items, buf.String()) } return sortItems(items) } func goTypesList(fset *token.FileSet, types map[goast.Expr]types.TypeAndValue, skipBasicLit bool) []string { var items []string for expr, tv := range types { var buf strings.Builder posn := fset.Position(expr.Pos()) tvstr := tv.Type.String() if skipBasicLit { if t, ok := expr.(*goast.BasicLit); ok { tvstr = t.Kind.String() } } if tv.Value != nil { tvstr += " = " + tv.Value.String() } // line:col | expr | mode : type = value fmt.Fprintf(&buf, "%2d:%2d | %-19s %-30T | %-7s : %s | %v", posn.Line, posn.Column, goexprString(fset, expr), expr, mode(tv), tvstr, (*TypeAndValue)(unsafe.Pointer(&tv)).mode) items = append(items, buf.String()) } return sortItems(items) } func defsList(fset *token.FileSet, uses map[*ast.Ident]types.Object, skipNil bool) []string { var items []string for expr, obj := range uses { if skipNil && obj == nil { continue } var buf strings.Builder posn := fset.Position(expr.Pos()) // line:col | expr | mode : type = value fmt.Fprintf(&buf, "%2d:%2d | %-19s | %s", posn.Line, posn.Column, expr, obj) items = append(items, buf.String()) } return sortItems(items) } func goDefsList(fset *token.FileSet, uses map[*goast.Ident]types.Object, skipNil bool) []string { var items []string for expr, obj := range uses { if skipNil && obj == nil { continue // skip nil object } var buf strings.Builder posn := fset.Position(expr.Pos()) // line:col | expr | mode : type = value fmt.Fprintf(&buf, "%2d:%2d | %-19s | %s", posn.Line, posn.Column, expr, obj) items = append(items, buf.String()) } return sortItems(items) } func usesList(fset *token.FileSet, uses map[*ast.Ident]types.Object) []string { var items []string for expr, obj := range uses { var buf strings.Builder posn := fset.Position(expr.Pos()) // line:col | expr | mode : type = value fmt.Fprintf(&buf, "%2d:%2d | %-19s | %s", posn.Line, posn.Column, expr, obj) items = append(items, buf.String()) } return sortItems(items) } func goUsesList(fset *token.FileSet, uses map[*goast.Ident]types.Object) []string { var items []string for expr, obj := range uses { if obj == nil { continue // skip nil object } var buf strings.Builder posn := fset.Position(expr.Pos()) // line:col | expr | mode : type = value fmt.Fprintf(&buf, "%2d:%2d | %-19s | %s", posn.Line, posn.Column, expr, obj) items = append(items, buf.String()) } return sortItems(items) } func overloadsList(fset *token.FileSet, overloads map[*ast.Ident]types.Object) []string { var items []string for expr, obj := range overloads { var buf strings.Builder posn := fset.Position(expr.Pos()) // line:col | expr | mode : type = value fmt.Fprintf(&buf, "%2d:%2d | %-19s | %s", posn.Line, posn.Column, expr, obj) items = append(items, buf.String()) } return sortItems(items) } /* func selectionList(fset *token.FileSet, sels map[*ast.SelectorExpr]*types.Selection) []string { var items []string for expr, sel := range sels { var buf strings.Builder posn := fset.Position(expr.Pos()) // line:col | expr | mode : type = value fmt.Fprintf(&buf, "%2d:%2d | %-19s | %s", posn.Line, posn.Column, exprString(fset, expr), sel) items = append(items, buf.String()) } return sortItems(items) } func goSelectionList(fset *token.FileSet, sels map[*goast.SelectorExpr]*types.Selection) []string { var items []string for expr, sel := range sels { var buf strings.Builder posn := fset.Position(expr.Pos()) // line:col | expr | mode : type = value fmt.Fprintf(&buf, "%2d:%2d | %-19s | %s", posn.Line, posn.Column, goexprString(fset, expr), sel) items = append(items, buf.String()) } return sortItems(items) } */ func mode(tv types.TypeAndValue) string { switch { case tv.IsVoid(): return "void" case tv.IsType(): return "type" case tv.IsBuiltin(): return "builtin" case tv.IsNil(): return "nil" case tv.Assignable(): if tv.Addressable() { return "var" } return "mapindex" case tv.IsValue(): return "value" default: return "unknown" } } func exprString(fset *token.FileSet, expr ast.Expr) string { var buf strings.Builder format.Node(&buf, fset, expr) return buf.String() } func goexprString(fset *token.FileSet, expr goast.Expr) string { var buf strings.Builder goformat.Node(&buf, fset, expr) return buf.String() } type operandMode byte const ( invalid operandMode = iota // operand is invalid novalue // operand represents no value (result of a function call w/o result) builtin // operand is a built-in function typexpr // operand is a type constant_ // operand is a constant; the operand's typ is a Basic type variable // operand is an addressable variable mapindex // operand is a map index expression (acts like a variable on lhs, commaok on rhs of an assignment) value // operand is a computed value commaok // like value, but operand may be used in a comma,ok expression commaerr // like commaok, but second value is error, not boolean cgofunc // operand is a cgo function ) func (v operandMode) String() string { return operandModeString[int(v)] } var operandModeString = [...]string{ invalid: "invalid operand", novalue: "no value", builtin: "built-in", typexpr: "type", constant_: "constant", variable: "variable", mapindex: "map index expression", value: "value", commaok: "comma, ok expression", commaerr: "comma, error expression", cgofunc: "cgo function", } type TypeAndValue struct { mode operandMode Type types.Type Value constant.Value } func TestVarTypes(t *testing.T) { testInfo(t, `package main type T struct { x int y int } var v *int = nil var v1 []int var v2 map[int8]string var v3 struct{} var v4 *T = &T{100,200} var v5 = [6]int{} var v6 = v5[0] var v7 = [6]int{}[0] var m map[int]string func init() { v5[0] = 100 _ = v5[:][0] m[0] = "hello" _ = m[0] _ = map[int]string{}[0] _ = &v3 _ = *(&v3) a := []int{1,2,3,4,5}[0] _ = a } `) } func TestStruct(t *testing.T) { testInfo(t, `package main import "fmt" type Person struct { name string age int8 } func test() { p := Person{ name: "jack", } _ = p.name p.name = "name" fmt.Println(p) } `) } func TestTypeAssert(t *testing.T) { testInfo(t, `package main func test() { var a interface{} = 100 if n, ok := a.(int); ok { _ = n } } `) } func TestChan(t *testing.T) { testInfo(t, `package main func test() { var ch chan int select { case n, ok := <-ch: _ = n _ = ok break } } `) } func TestRange(t *testing.T) { testInfo(t, `package main func test() { a := []int{100,200} for k, v := range a { _ = k _ = v } var m map[int]string for k, v := range m { _ = k _ = v } for v := range m { _ = v } } `) } func TestFuncLit(t *testing.T) { testInfo(t, `package main func test() { add := func(n1 int, n2 int) int { return n1+n2 } _ = add(1,2) go func(n int) { _ = n+100 }(100) } `) } func TestSliceLit(t *testing.T) { testXGoInfo(t, ` a := [100,200] println a `, ``, `== types == 000: 2: 7 | 100 *ast.BasicLit | value : untyped int = 100 | constant 001: 2:11 | 200 *ast.BasicLit | value : untyped int = 200 | constant 002: 3: 1 | println *ast.Ident | value : func(a ...any) (n int, err error) | value 003: 3: 1 | println a *ast.CallExpr | value : (n int, err error) | value 004: 3: 9 | a *ast.Ident | var : []int | variable == defs == 000: 2: 1 | a | var a []int 001: 2: 1 | main | func main.main() == uses == 000: 3: 1 | println | func fmt.Println(a ...any) (n int, err error) 001: 3: 9 | a | var a []int`) } func TestForPhrase1(t *testing.T) { testXGoInfo(t, ` sum := 0 for x <- [1, 3, 5, 7, 11, 13, 17], x > 3 { sum = sum + x } println sum `, ``, `== types == 000: 2: 8 | 0 *ast.BasicLit | value : untyped int = 0 | constant 001: 3:11 | 1 *ast.BasicLit | value : untyped int = 1 | constant 002: 3:14 | 3 *ast.BasicLit | value : untyped int = 3 | constant 003: 3:17 | 5 *ast.BasicLit | value : untyped int = 5 | constant 004: 3:20 | 7 *ast.BasicLit | value : untyped int = 7 | constant 005: 3:23 | 11 *ast.BasicLit | value : untyped int = 11 | constant 006: 3:27 | 13 *ast.BasicLit | value : untyped int = 13 | constant 007: 3:31 | 17 *ast.BasicLit | value : untyped int = 17 | constant 008: 3:36 | x *ast.Ident | var : int | variable 009: 3:36 | x > 3 *ast.BinaryExpr | value : untyped bool | value 010: 3:40 | 3 *ast.BasicLit | value : untyped int = 3 | constant 011: 4: 2 | sum *ast.Ident | var : int | variable 012: 4: 8 | sum *ast.Ident | var : int | variable 013: 4: 8 | sum + x *ast.BinaryExpr | value : int | value 014: 4:14 | x *ast.Ident | var : int | variable 015: 6: 1 | println *ast.Ident | value : func(a ...any) (n int, err error) | value 016: 6: 1 | println sum *ast.CallExpr | value : (n int, err error) | value 017: 6: 9 | sum *ast.Ident | var : int | variable == defs == 000: 2: 1 | main | func main.main() 001: 2: 1 | sum | var sum int 002: 3: 5 | x | var x int == uses == 000: 3:36 | x | var x int 001: 4: 2 | sum | var sum int 002: 4: 8 | sum | var sum int 003: 4:14 | x | var x int 004: 6: 1 | println | func fmt.Println(a ...any) (n int, err error) 005: 6: 9 | sum | var sum int`) } func TestForPhrase2(t *testing.T) { testXGoInfo(t, ` sum := 0 for i, x <- [1, 3, 5, 7, 11, 13, 17], i%2 == 1 && x > 3 { sum = sum + x } println sum `, ``, `== types == 000: 2: 8 | 0 *ast.BasicLit | value : untyped int = 0 | constant 001: 3:14 | 1 *ast.BasicLit | value : untyped int = 1 | constant 002: 3:17 | 3 *ast.BasicLit | value : untyped int = 3 | constant 003: 3:20 | 5 *ast.BasicLit | value : untyped int = 5 | constant 004: 3:23 | 7 *ast.BasicLit | value : untyped int = 7 | constant 005: 3:26 | 11 *ast.BasicLit | value : untyped int = 11 | constant 006: 3:30 | 13 *ast.BasicLit | value : untyped int = 13 | constant 007: 3:34 | 17 *ast.BasicLit | value : untyped int = 17 | constant 008: 3:39 | i *ast.Ident | var : int | variable 009: 3:39 | i % 2 *ast.BinaryExpr | value : int | value 010: 3:39 | i%2 == 1 *ast.BinaryExpr | value : untyped bool | value 011: 3:39 | i%2 == 1 && x > 3 *ast.BinaryExpr | value : untyped bool | value 012: 3:41 | 2 *ast.BasicLit | value : untyped int = 2 | constant 013: 3:46 | 1 *ast.BasicLit | value : untyped int = 1 | constant 014: 3:51 | x *ast.Ident | var : int | variable 015: 3:51 | x > 3 *ast.BinaryExpr | value : untyped bool | value 016: 3:55 | 3 *ast.BasicLit | value : untyped int = 3 | constant 017: 4: 2 | sum *ast.Ident | var : int | variable 018: 4: 8 | sum *ast.Ident | var : int | variable 019: 4: 8 | sum + x *ast.BinaryExpr | value : int | value 020: 4:14 | x *ast.Ident | var : int | variable 021: 6: 1 | println *ast.Ident | value : func(a ...any) (n int, err error) | value 022: 6: 1 | println sum *ast.CallExpr | value : (n int, err error) | value 023: 6: 9 | sum *ast.Ident | var : int | variable == defs == 000: 2: 1 | main | func main.main() 001: 2: 1 | sum | var sum int 002: 3: 5 | i | var i int 003: 3: 8 | x | var x int == uses == 000: 3:39 | i | var i int 001: 3:51 | x | var x int 002: 4: 2 | sum | var sum int 003: 4: 8 | sum | var sum int 004: 4:14 | x | var x int 005: 6: 1 | println | func fmt.Println(a ...any) (n int, err error) 006: 6: 9 | sum | var sum int`) } func TestMapComprehension(t *testing.T) { testXGoInfo(t, ` y := {x: i for i, x <- ["1", "3", "5", "7", "11"]} println y `, ``, `== types == 000: 2: 7 | x *ast.Ident | var : string | variable 001: 2:10 | i *ast.Ident | var : int | variable 002: 2:25 | "1" *ast.BasicLit | value : untyped string = "1" | constant 003: 2:30 | "3" *ast.BasicLit | value : untyped string = "3" | constant 004: 2:35 | "5" *ast.BasicLit | value : untyped string = "5" | constant 005: 2:40 | "7" *ast.BasicLit | value : untyped string = "7" | constant 006: 2:45 | "11" *ast.BasicLit | value : untyped string = "11" | constant 007: 3: 1 | println *ast.Ident | value : func(a ...any) (n int, err error) | value 008: 3: 1 | println y *ast.CallExpr | value : (n int, err error) | value 009: 3: 9 | y *ast.Ident | var : map[string]int | variable == defs == 000: 2: 1 | main | func main.main() 001: 2: 1 | y | var y map[string]int 002: 2:16 | i | var i int 003: 2:19 | x | var x string == uses == 000: 2: 7 | x | var x string 001: 2:10 | i | var i int 002: 3: 1 | println | func fmt.Println(a ...any) (n int, err error) 003: 3: 9 | y | var y map[string]int`) } func TestListComprehension(t *testing.T) { testXGoInfo(t, ` a := [1, 3.4, 5] b := [x*x for x <- a] _ = b `, ``, `== types == 000: 2: 7 | 1 *ast.BasicLit | value : untyped int = 1 | constant 001: 2:10 | 3.4 *ast.BasicLit | value : untyped float = 3.4 | constant 002: 2:15 | 5 *ast.BasicLit | value : untyped int = 5 | constant 003: 3: 7 | x *ast.Ident | var : float64 | variable 004: 3: 7 | x * x *ast.BinaryExpr | value : float64 | value 005: 3: 9 | x *ast.Ident | var : float64 | variable 006: 3:20 | a *ast.Ident | var : []float64 | variable 007: 4: 5 | b *ast.Ident | var : []float64 | variable == defs == 000: 2: 1 | a | var a []float64 001: 2: 1 | main | func main.main() 002: 3: 1 | b | var b []float64 003: 3:15 | x | var x float64 == uses == 000: 3: 7 | x | var x float64 001: 3: 9 | x | var x float64 002: 3:20 | a | var a []float64 003: 4: 5 | b | var b []float64`) } func TestListComprehensionMultiLevel(t *testing.T) { testXGoInfo(t, ` arr := [1, 2, 3, 4.1, 5, 6] x := [[a, b] for a <- arr, a < b for b <- arr, b > 2] println("x:", x) `, ``, `== types == 000: 2: 9 | 1 *ast.BasicLit | value : untyped int = 1 | constant 001: 2:12 | 2 *ast.BasicLit | value : untyped int = 2 | constant 002: 2:15 | 3 *ast.BasicLit | value : untyped int = 3 | constant 003: 2:18 | 4.1 *ast.BasicLit | value : untyped float = 4.1 | constant 004: 2:23 | 5 *ast.BasicLit | value : untyped int = 5 | constant 005: 2:26 | 6 *ast.BasicLit | value : untyped int = 6 | constant 006: 3: 8 | a *ast.Ident | var : float64 | variable 007: 3:11 | b *ast.Ident | var : float64 | variable 008: 3:23 | arr *ast.Ident | var : []float64 | variable 009: 3:28 | a *ast.Ident | var : float64 | variable 010: 3:28 | a < b *ast.BinaryExpr | value : untyped bool | value 011: 3:32 | b *ast.Ident | var : float64 | variable 012: 3:43 | arr *ast.Ident | var : []float64 | variable 013: 3:48 | b *ast.Ident | var : float64 | variable 014: 3:48 | b > 2 *ast.BinaryExpr | value : untyped bool | value 015: 3:52 | 2 *ast.BasicLit | value : untyped int = 2 | constant 016: 4: 1 | println *ast.Ident | value : func(a ...any) (n int, err error) | value 017: 4: 1 | println("x:", x) *ast.CallExpr | value : (n int, err error) | value 018: 4: 9 | "x:" *ast.BasicLit | value : untyped string = "x:" | constant 019: 4:15 | x *ast.Ident | var : [][]float64 | variable == defs == 000: 2: 1 | arr | var arr []float64 001: 2: 1 | main | func main.main() 002: 3: 1 | x | var x [][]float64 003: 3:18 | a | var a float64 004: 3:38 | b | var b float64 == uses == 000: 3: 8 | a | var a float64 001: 3:11 | b | var b float64 002: 3:23 | arr | var arr []float64 003: 3:28 | a | var a float64 004: 3:32 | b | var b float64 005: 3:43 | arr | var arr []float64 006: 3:48 | b | var b float64 007: 4: 1 | println | func fmt.Println(a ...any) (n int, err error) 008: 4:15 | x | var x [][]float64`) } func TestFileEnumLines(t *testing.T) { testXGoInfo(t, ` import "os" for line <- os.Stdin { println line } `, ``, `== types == 000: 4:13 | os.Stdin *ast.SelectorExpr | var : *os.File | variable 001: 5: 2 | println *ast.Ident | value : func(a ...any) (n int, err error) | value 002: 5: 2 | println line *ast.CallExpr | value : (n int, err error) | value 003: 5:10 | line *ast.Ident | var : string | variable == defs == 000: 4: 1 | main | func main.main() 001: 4: 5 | line | var line string == uses == 000: 4:13 | os | package os 001: 4:16 | Stdin | var os.Stdin *os.File 002: 5: 2 | println | func fmt.Println(a ...any) (n int, err error) 003: 5:10 | line | var line string`) } func TestLambdaExpr(t *testing.T) { testXGoInfo(t, `package main func Map(c []float64, t func(float64) float64) { // ... } func Map2(c []float64, t func(float64) (float64, float64)) { // ... } Map([1.2, 3.5, 6], x => x * x) Map2([1.2, 3.5, 6], x => (x * x, x + x)) `, ``, `== types == 000: 2:12 | []float64 *ast.ArrayType | type : []float64 | type 001: 2:14 | float64 *ast.Ident | type : float64 | type 002: 2:25 | func(float64) float64 *ast.FuncType | type : func(float64) float64 | type 003: 2:30 | float64 *ast.Ident | type : float64 | type 004: 2:39 | float64 *ast.Ident | type : float64 | type 005: 6:13 | []float64 *ast.ArrayType | type : []float64 | type 006: 6:15 | float64 *ast.Ident | type : float64 | type 007: 6:26 | func(float64) (float64, float64) *ast.FuncType | type : func(float64) (float64, float64) | type 008: 6:31 | float64 *ast.Ident | type : float64 | type 009: 6:41 | float64 *ast.Ident | type : float64 | type 010: 6:50 | float64 *ast.Ident | type : float64 | type 011: 10: 1 | Map *ast.Ident | value : func(c []float64, t func(float64) float64) | value 012: 10: 1 | Map([1.2, 3.5, 6], x => x * x) *ast.CallExpr | void : () | no value 013: 10: 6 | 1.2 *ast.BasicLit | value : untyped float = 1.2 | constant 014: 10:11 | 3.5 *ast.BasicLit | value : untyped float = 3.5 | constant 015: 10:16 | 6 *ast.BasicLit | value : untyped int = 6 | constant 016: 10:25 | x *ast.Ident | var : float64 | variable 017: 10:25 | x * x *ast.BinaryExpr | value : float64 | value 018: 10:29 | x *ast.Ident | var : float64 | variable 019: 11: 1 | Map2 *ast.Ident | value : func(c []float64, t func(float64) (float64, float64)) | value 020: 11: 1 | Map2([1.2, 3.5, 6], x => (x * x, x + x)) *ast.CallExpr | void : () | no value 021: 11: 7 | 1.2 *ast.BasicLit | value : untyped float = 1.2 | constant 022: 11:12 | 3.5 *ast.BasicLit | value : untyped float = 3.5 | constant 023: 11:17 | 6 *ast.BasicLit | value : untyped int = 6 | constant 024: 11:27 | x *ast.Ident | var : float64 | variable 025: 11:27 | x * x *ast.BinaryExpr | value : float64 | value 026: 11:31 | x *ast.Ident | var : float64 | variable 027: 11:34 | x *ast.Ident | var : float64 | variable 028: 11:34 | x + x *ast.BinaryExpr | value : float64 | value 029: 11:38 | x *ast.Ident | var : float64 | variable == defs == 000: 2: 6 | Map | func main.Map(c []float64, t func(float64) float64) 001: 2:10 | c | var c []float64 002: 2:23 | t | var t func(float64) float64 003: 6: 6 | Map2 | func main.Map2(c []float64, t func(float64) (float64, float64)) 004: 6:11 | c | var c []float64 005: 6:24 | t | var t func(float64) (float64, float64) 006: 10: 1 | main | func main.main() 007: 10:20 | x | var x float64 008: 11:21 | x | var x float64 == uses == 000: 2:14 | float64 | type float64 001: 2:30 | float64 | type float64 002: 2:39 | float64 | type float64 003: 6:15 | float64 | type float64 004: 6:31 | float64 | type float64 005: 6:41 | float64 | type float64 006: 6:50 | float64 | type float64 007: 10: 1 | Map | func main.Map(c []float64, t func(float64) float64) 008: 10:25 | x | var x float64 009: 10:29 | x | var x float64 010: 11: 1 | Map2 | func main.Map2(c []float64, t func(float64) (float64, float64)) 011: 11:27 | x | var x float64 012: 11:31 | x | var x float64 013: 11:34 | x | var x float64 014: 11:38 | x | var x float64`) } func TestLambdaExpr2(t *testing.T) { testXGoInfo(t, `package main func Map(c []float64, t func(float64) float64) { // ... } func Map2(c []float64, t func(float64) (float64, float64)) { // ... } Map([1.2, 3.5, 6], x => { return x * x }) Map2([1.2, 3.5, 6], x => { return x * x, x + x }) `, ``, `== types == 000: 2:12 | []float64 *ast.ArrayType | type : []float64 | type 001: 2:14 | float64 *ast.Ident | type : float64 | type 002: 2:25 | func(float64) float64 *ast.FuncType | type : func(float64) float64 | type 003: 2:30 | float64 *ast.Ident | type : float64 | type 004: 2:39 | float64 *ast.Ident | type : float64 | type 005: 6:13 | []float64 *ast.ArrayType | type : []float64 | type 006: 6:15 | float64 *ast.Ident | type : float64 | type 007: 6:26 | func(float64) (float64, float64) *ast.FuncType | type : func(float64) (float64, float64) | type 008: 6:31 | float64 *ast.Ident | type : float64 | type 009: 6:41 | float64 *ast.Ident | type : float64 | type 010: 6:50 | float64 *ast.Ident | type : float64 | type 011: 10: 1 | Map *ast.Ident | value : func(c []float64, t func(float64) float64) | value 012: 10: 1 | Map([1.2, 3.5, 6], x => { return x * x }) *ast.CallExpr | void : () | no value 013: 10: 6 | 1.2 *ast.BasicLit | value : untyped float = 1.2 | constant 014: 10:11 | 3.5 *ast.BasicLit | value : untyped float = 3.5 | constant 015: 10:16 | 6 *ast.BasicLit | value : untyped int = 6 | constant 016: 11: 9 | x *ast.Ident | var : float64 | variable 017: 11: 9 | x * x *ast.BinaryExpr | value : float64 | value 018: 11:13 | x *ast.Ident | var : float64 | variable 019: 13: 1 | Map2 *ast.Ident | value : func(c []float64, t func(float64) (float64, float64)) | value 020: 13: 1 | Map2([1.2, 3.5, 6], x => { return x * x, x + x }) *ast.CallExpr | void : () | no value 021: 13: 7 | 1.2 *ast.BasicLit | value : untyped float = 1.2 | constant 022: 13:12 | 3.5 *ast.BasicLit | value : untyped float = 3.5 | constant 023: 13:17 | 6 *ast.BasicLit | value : untyped int = 6 | constant 024: 14: 9 | x *ast.Ident | var : float64 | variable 025: 14: 9 | x * x *ast.BinaryExpr | value : float64 | value 026: 14:13 | x *ast.Ident | var : float64 | variable 027: 14:16 | x *ast.Ident | var : float64 | variable 028: 14:16 | x + x *ast.BinaryExpr | value : float64 | value 029: 14:20 | x *ast.Ident | var : float64 | variable == defs == 000: 2: 6 | Map | func main.Map(c []float64, t func(float64) float64) 001: 2:10 | c | var c []float64 002: 2:23 | t | var t func(float64) float64 003: 6: 6 | Map2 | func main.Map2(c []float64, t func(float64) (float64, float64)) 004: 6:11 | c | var c []float64 005: 6:24 | t | var t func(float64) (float64, float64) 006: 10: 1 | main | func main.main() 007: 10:20 | x | var x float64 008: 13:21 | x | var x float64 == uses == 000: 2:14 | float64 | type float64 001: 2:30 | float64 | type float64 002: 2:39 | float64 | type float64 003: 6:15 | float64 | type float64 004: 6:31 | float64 | type float64 005: 6:41 | float64 | type float64 006: 6:50 | float64 | type float64 007: 10: 1 | Map | func main.Map(c []float64, t func(float64) float64) 008: 11: 9 | x | var x float64 009: 11:13 | x | var x float64 010: 13: 1 | Map2 | func main.Map2(c []float64, t func(float64) (float64, float64)) 011: 14: 9 | x | var x float64 012: 14:13 | x | var x float64 013: 14:16 | x | var x float64 014: 14:20 | x | var x float64`) } func TestMixedOverload1(t *testing.T) { testXGoInfo(t, ` type Mesh struct { } func (p *Mesh) Name() string { return "hello" } var ( m1 = &Mesh{} m2 = &Mesh{} ) OnKey "hello", => { } OnKey "hello", key => { } OnKey ["1"], => { } OnKey ["2"], key => { } OnKey [m1, m2], => { } OnKey [m1, m2], key => { } OnKey ["a"], ["b"], key => { } OnKey ["a"], [m1, m2], key => { } OnKey ["a"], nil, key => { } OnKey 100, 200 OnKey "a", "b", x => x * x, x => { return x * 2 } OnKey "a", "b", 1, 2, 3 OnKey("a", "b", [1, 2, 3]...) `, ` package main type Mesher interface { Name() string } type N struct { } func (m *N) OnKey__0(a string, fn func()) { } func (m *N) OnKey__1(a string, fn func(key string)) { } func (m *N) OnKey__2(a []string, fn func()) { } func (m *N) OnKey__3(a []string, fn func(key string)) { } func (m *N) OnKey__4(a []Mesher, fn func()) { } func (m *N) OnKey__5(a []Mesher, fn func(key Mesher)) { } func (m *N) OnKey__6(a []string, b []string, fn func(key string)) { } func (m *N) OnKey__7(a []string, b []Mesher, fn func(key string)) { } func (m *N) OnKey__8(x int, y int) { } func OnKey__0(a string, fn func()) { } func OnKey__1(a string, fn func(key string)) { } func OnKey__2(a []string, fn func()) { } func OnKey__3(a []string, fn func(key string)) { } func OnKey__4(a []Mesher, fn func()) { } func OnKey__5(a []Mesher, fn func(key Mesher)) { } func OnKey__6(a []string, b []string, fn func(key string)) { } func OnKey__7(a []string, b []Mesher, fn func(key string)) { } func OnKey__8(x int, y int) { } func OnKey__9(a, b string, fn ...func(x int) int) { } func OnKey__a(a, b string, v ...int) { } `, `== types == 000: 2:11 | struct { } *ast.StructType | type : struct{} | type 001: 5:10 | Mesh *ast.Ident | type : main.Mesh | type 002: 5:23 | string *ast.Ident | type : string | type 003: 6: 9 | "hello" *ast.BasicLit | value : untyped string = "hello" | constant 004: 10: 7 | &Mesh{} *ast.UnaryExpr | value : *main.Mesh | value 005: 10: 8 | Mesh *ast.Ident | type : main.Mesh | type 006: 10: 8 | Mesh{} *ast.CompositeLit | value : main.Mesh | value 007: 11: 7 | &Mesh{} *ast.UnaryExpr | value : *main.Mesh | value 008: 11: 8 | Mesh *ast.Ident | type : main.Mesh | type 009: 11: 8 | Mesh{} *ast.CompositeLit | value : main.Mesh | value 010: 14: 1 | OnKey *ast.Ident | value : func(a string, fn func()) | value 011: 14: 1 | OnKey "hello", => { } *ast.CallExpr | void : () | no value 012: 14: 7 | "hello" *ast.BasicLit | value : untyped string = "hello" | constant 013: 16: 1 | OnKey *ast.Ident | value : func(a string, fn func(key string)) | value 014: 16: 1 | OnKey "hello", key => { } *ast.CallExpr | void : () | no value 015: 16: 7 | "hello" *ast.BasicLit | value : untyped string = "hello" | constant 016: 18: 1 | OnKey *ast.Ident | value : func(a []string, fn func()) | value 017: 18: 1 | OnKey ["1"], => { } *ast.CallExpr | void : () | no value 018: 18: 8 | "1" *ast.BasicLit | value : untyped string = "1" | constant 019: 20: 1 | OnKey *ast.Ident | value : func(a []string, fn func(key string)) | value 020: 20: 1 | OnKey ["2"], key => { } *ast.CallExpr | void : () | no value 021: 20: 8 | "2" *ast.BasicLit | value : untyped string = "2" | constant 022: 22: 1 | OnKey *ast.Ident | value : func(a []main.Mesher, fn func()) | value 023: 22: 1 | OnKey [m1, m2], => { } *ast.CallExpr | void : () | no value 024: 22: 8 | m1 *ast.Ident | var : *main.Mesh | variable 025: 22:12 | m2 *ast.Ident | var : *main.Mesh | variable 026: 24: 1 | OnKey *ast.Ident | value : func(a []main.Mesher, fn func(key main.Mesher)) | value 027: 24: 1 | OnKey [m1, m2], key => { } *ast.CallExpr | void : () | no value 028: 24: 8 | m1 *ast.Ident | var : *main.Mesh | variable 029: 24:12 | m2 *ast.Ident | var : *main.Mesh | variable 030: 26: 1 | OnKey *ast.Ident | value : func(a []string, b []string, fn func(key string)) | value 031: 26: 1 | OnKey ["a"], ["b"], key => { } *ast.CallExpr | void : () | no value 032: 26: 8 | "a" *ast.BasicLit | value : untyped string = "a" | constant 033: 26:15 | "b" *ast.BasicLit | value : untyped string = "b" | constant 034: 28: 1 | OnKey *ast.Ident | value : func(a []string, b []main.Mesher, fn func(key string)) | value 035: 28: 1 | OnKey ["a"], [m1, m2], key => { } *ast.CallExpr | void : () | no value 036: 28: 8 | "a" *ast.BasicLit | value : untyped string = "a" | constant 037: 28:15 | m1 *ast.Ident | var : *main.Mesh | variable 038: 28:19 | m2 *ast.Ident | var : *main.Mesh | variable 039: 30: 1 | OnKey *ast.Ident | value : func(a []string, b []string, fn func(key string)) | value 040: 30: 1 | OnKey ["a"], nil, key => { } *ast.CallExpr | void : () | no value 041: 30: 8 | "a" *ast.BasicLit | value : untyped string = "a" | constant 042: 30:14 | nil *ast.Ident | nil : untyped nil | value 043: 32: 1 | OnKey *ast.Ident | value : func(x int, y int) | value 044: 32: 1 | OnKey 100, 200 *ast.CallExpr | void : () | no value 045: 32: 7 | 100 *ast.BasicLit | value : untyped int = 100 | constant 046: 32:12 | 200 *ast.BasicLit | value : untyped int = 200 | constant 047: 33: 1 | OnKey *ast.Ident | value : func(a string, b string, fn ...func(x int) int) | value 048: 33: 1 | OnKey "a", "b", x => x * x, x => { return x * 2 } *ast.CallExpr | void : () | no value 049: 33: 7 | "a" *ast.BasicLit | value : untyped string = "a" | constant 050: 33:12 | "b" *ast.BasicLit | value : untyped string = "b" | constant 051: 33:22 | x *ast.Ident | var : int | variable 052: 33:22 | x * x *ast.BinaryExpr | value : int | value 053: 33:26 | x *ast.Ident | var : int | variable 054: 34: 9 | x *ast.Ident | var : int | variable 055: 34: 9 | x * 2 *ast.BinaryExpr | value : int | value 056: 34:13 | 2 *ast.BasicLit | value : untyped int = 2 | constant 057: 36: 1 | OnKey *ast.Ident | value : func(a string, b string, v ...int) | value 058: 36: 1 | OnKey "a", "b", 1, 2, 3 *ast.CallExpr | void : () | no value 059: 36: 7 | "a" *ast.BasicLit | value : untyped string = "a" | constant 060: 36:12 | "b" *ast.BasicLit | value : untyped string = "b" | constant 061: 36:17 | 1 *ast.BasicLit | value : untyped int = 1 | constant 062: 36:20 | 2 *ast.BasicLit | value : untyped int = 2 | constant 063: 36:23 | 3 *ast.BasicLit | value : untyped int = 3 | constant 064: 37: 1 | OnKey *ast.Ident | value : func(a string, b string, v ...int) | value 065: 37: 1 | OnKey("a", "b", [1, 2, 3]...) *ast.CallExpr | void : () | no value 066: 37: 7 | "a" *ast.BasicLit | value : untyped string = "a" | constant 067: 37:12 | "b" *ast.BasicLit | value : untyped string = "b" | constant 068: 37:18 | 1 *ast.BasicLit | value : untyped int = 1 | constant 069: 37:21 | 2 *ast.BasicLit | value : untyped int = 2 | constant 070: 37:24 | 3 *ast.BasicLit | value : untyped int = 3 | constant == defs == 000: 2: 6 | Mesh | type main.Mesh struct{} 001: 5: 7 | p | var p *main.Mesh 002: 5:16 | Name | func (*main.Mesh).Name() string 003: 10: 2 | m1 | var main.m1 *main.Mesh 004: 11: 2 | m2 | var main.m2 *main.Mesh 005: 14: 1 | main | func main.main() 006: 16:16 | key | var key string 007: 20:14 | key | var key string 008: 24:17 | key | var key main.Mesher 009: 26:21 | key | var key string 010: 28:24 | key | var key string 011: 30:19 | key | var key string 012: 33:17 | x | var x int 013: 33:29 | x | var x int == uses == 000: 5:10 | Mesh | type main.Mesh struct{} 001: 5:23 | string | type string 002: 10: 8 | Mesh | type main.Mesh struct{} 003: 11: 8 | Mesh | type main.Mesh struct{} 004: 14: 1 | OnKey | func main.OnKey__0(a string, fn func()) 005: 16: 1 | OnKey | func main.OnKey__1(a string, fn func(key string)) 006: 18: 1 | OnKey | func main.OnKey__2(a []string, fn func()) 007: 20: 1 | OnKey | func main.OnKey__3(a []string, fn func(key string)) 008: 22: 1 | OnKey | func main.OnKey__4(a []main.Mesher, fn func()) 009: 22: 8 | m1 | var main.m1 *main.Mesh 010: 22:12 | m2 | var main.m2 *main.Mesh 011: 24: 1 | OnKey | func main.OnKey__5(a []main.Mesher, fn func(key main.Mesher)) 012: 24: 8 | m1 | var main.m1 *main.Mesh 013: 24:12 | m2 | var main.m2 *main.Mesh 014: 26: 1 | OnKey | func main.OnKey__6(a []string, b []string, fn func(key string)) 015: 28: 1 | OnKey | func main.OnKey__7(a []string, b []main.Mesher, fn func(key string)) 016: 28:15 | m1 | var main.m1 *main.Mesh 017: 28:19 | m2 | var main.m2 *main.Mesh 018: 30: 1 | OnKey | func main.OnKey__6(a []string, b []string, fn func(key string)) 019: 30:14 | nil | nil 020: 32: 1 | OnKey | func main.OnKey__8(x int, y int) 021: 33: 1 | OnKey | func main.OnKey__9(a string, b string, fn ...func(x int) int) 022: 33:22 | x | var x int 023: 33:26 | x | var x int 024: 34: 9 | x | var x int 025: 36: 1 | OnKey | func main.OnKey__a(a string, b string, v ...int) 026: 37: 1 | OnKey | func main.OnKey__a(a string, b string, v ...int) == overloads == 000: 14: 1 | OnKey | func main.OnKey(__xgo_overload_args__ interface{_()}) 001: 16: 1 | OnKey | func main.OnKey(__xgo_overload_args__ interface{_()}) 002: 18: 1 | OnKey | func main.OnKey(__xgo_overload_args__ interface{_()}) 003: 20: 1 | OnKey | func main.OnKey(__xgo_overload_args__ interface{_()}) 004: 22: 1 | OnKey | func main.OnKey(__xgo_overload_args__ interface{_()}) 005: 24: 1 | OnKey | func main.OnKey(__xgo_overload_args__ interface{_()}) 006: 26: 1 | OnKey | func main.OnKey(__xgo_overload_args__ interface{_()}) 007: 28: 1 | OnKey | func main.OnKey(__xgo_overload_args__ interface{_()}) 008: 30: 1 | OnKey | func main.OnKey(__xgo_overload_args__ interface{_()}) 009: 32: 1 | OnKey | func main.OnKey(__xgo_overload_args__ interface{_()}) 010: 33: 1 | OnKey | func main.OnKey(__xgo_overload_args__ interface{_()}) 011: 36: 1 | OnKey | func main.OnKey(__xgo_overload_args__ interface{_()}) 012: 37: 1 | OnKey | func main.OnKey(__xgo_overload_args__ interface{_()})`) } func TestMixedOverload2(t *testing.T) { testXGoInfo(t, ` type Mesh struct { } func (p *Mesh) Name() string { return "hello" } var ( m1 = &Mesh{} m2 = &Mesh{} ) n := &N{} n.onKey "hello", => { } n.onKey "hello", key => { } n.onKey ["1"], => { } n.onKey ["2"], key => { } n.onKey [m1, m2], => { } n.onKey [m1, m2], key => { } n.onKey ["a"], ["b"], key => { } n.onKey ["a"], [m1, m2], key => { } n.onKey ["a"], nil, key => { } n.onKey 100, 200 `, ` package main type Mesher interface { Name() string } type N struct { } func (m *N) OnKey__0(a string, fn func()) { } func (m *N) OnKey__1(a string, fn func(key string)) { } func (m *N) OnKey__2(a []string, fn func()) { } func (m *N) OnKey__3(a []string, fn func(key string)) { } func (m *N) OnKey__4(a []Mesher, fn func()) { } func (m *N) OnKey__5(a []Mesher, fn func(key Mesher)) { } func (m *N) OnKey__6(a []string, b []string, fn func(key string)) { } func (m *N) OnKey__7(a []string, b []Mesher, fn func(key string)) { } func (m *N) OnKey__8(x int, y int) { } func OnKey__0(a string, fn func()) { } func OnKey__1(a string, fn func(key string)) { } func OnKey__2(a []string, fn func()) { } func OnKey__3(a []string, fn func(key string)) { } func OnKey__4(a []Mesher, fn func()) { } func OnKey__5(a []Mesher, fn func(key Mesher)) { } func OnKey__6(a []string, b []string, fn func(key string)) { } func OnKey__7(a []string, b []Mesher, fn func(key string)) { } func OnKey__8(x int, y int) { } func OnKey__9(a, b string, fn ...func(x int) int) { } func OnKey__a(a, b string, v ...int) { } `, `== types == 000: 2:11 | struct { } *ast.StructType | type : struct{} | type 001: 5:10 | Mesh *ast.Ident | type : main.Mesh | type 002: 5:23 | string *ast.Ident | type : string | type 003: 6: 9 | "hello" *ast.BasicLit | value : untyped string = "hello" | constant 004: 10: 7 | &Mesh{} *ast.UnaryExpr | value : *main.Mesh | value 005: 10: 8 | Mesh *ast.Ident | type : main.Mesh | type 006: 10: 8 | Mesh{} *ast.CompositeLit | value : main.Mesh | value 007: 11: 7 | &Mesh{} *ast.UnaryExpr | value : *main.Mesh | value 008: 11: 8 | Mesh *ast.Ident | type : main.Mesh | type 009: 11: 8 | Mesh{} *ast.CompositeLit | value : main.Mesh | value 010: 14: 6 | &N{} *ast.UnaryExpr | value : *main.N | value 011: 14: 7 | N *ast.Ident | type : main.N | type 012: 14: 7 | N{} *ast.CompositeLit | value : main.N | value 013: 15: 1 | n *ast.Ident | var : *main.N | variable 014: 15: 1 | n.onKey *ast.SelectorExpr | value : func(a string, fn func()) | value 015: 15: 1 | n.onKey "hello", => { } *ast.CallExpr | void : () | no value 016: 15: 9 | "hello" *ast.BasicLit | value : untyped string = "hello" | constant 017: 17: 1 | n *ast.Ident | var : *main.N | variable 018: 17: 1 | n.onKey *ast.SelectorExpr | value : func(a string, fn func(key string)) | value 019: 17: 1 | n.onKey "hello", key => { } *ast.CallExpr | void : () | no value 020: 17: 9 | "hello" *ast.BasicLit | value : untyped string = "hello" | constant 021: 19: 1 | n *ast.Ident | var : *main.N | variable 022: 19: 1 | n.onKey *ast.SelectorExpr | value : func(a []string, fn func()) | value 023: 19: 1 | n.onKey ["1"], => { } *ast.CallExpr | void : () | no value 024: 19:10 | "1" *ast.BasicLit | value : untyped string = "1" | constant 025: 21: 1 | n *ast.Ident | var : *main.N | variable 026: 21: 1 | n.onKey *ast.SelectorExpr | value : func(a []string, fn func(key string)) | value 027: 21: 1 | n.onKey ["2"], key => { } *ast.CallExpr | void : () | no value 028: 21:10 | "2" *ast.BasicLit | value : untyped string = "2" | constant 029: 23: 1 | n *ast.Ident | var : *main.N | variable 030: 23: 1 | n.onKey *ast.SelectorExpr | value : func(a []main.Mesher, fn func()) | value 031: 23: 1 | n.onKey [m1, m2], => { } *ast.CallExpr | void : () | no value 032: 23:10 | m1 *ast.Ident | var : *main.Mesh | variable 033: 23:14 | m2 *ast.Ident | var : *main.Mesh | variable 034: 25: 1 | n *ast.Ident | var : *main.N | variable 035: 25: 1 | n.onKey *ast.SelectorExpr | value : func(a []main.Mesher, fn func(key main.Mesher)) | value 036: 25: 1 | n.onKey [m1, m2], key => { } *ast.CallExpr | void : () | no value 037: 25:10 | m1 *ast.Ident | var : *main.Mesh | variable 038: 25:14 | m2 *ast.Ident | var : *main.Mesh | variable 039: 27: 1 | n *ast.Ident | var : *main.N | variable 040: 27: 1 | n.onKey *ast.SelectorExpr | value : func(a []string, b []string, fn func(key string)) | value 041: 27: 1 | n.onKey ["a"], ["b"], key => { } *ast.CallExpr | void : () | no value 042: 27:10 | "a" *ast.BasicLit | value : untyped string = "a" | constant 043: 27:17 | "b" *ast.BasicLit | value : untyped string = "b" | constant 044: 29: 1 | n *ast.Ident | var : *main.N | variable 045: 29: 1 | n.onKey *ast.SelectorExpr | value : func(a []string, b []main.Mesher, fn func(key string)) | value 046: 29: 1 | n.onKey ["a"], [m1, m2], key => { } *ast.CallExpr | void : () | no value 047: 29:10 | "a" *ast.BasicLit | value : untyped string = "a" | constant 048: 29:17 | m1 *ast.Ident | var : *main.Mesh | variable 049: 29:21 | m2 *ast.Ident | var : *main.Mesh | variable 050: 31: 1 | n *ast.Ident | var : *main.N | variable 051: 31: 1 | n.onKey *ast.SelectorExpr | value : func(a []string, b []string, fn func(key string)) | value 052: 31: 1 | n.onKey ["a"], nil, key => { } *ast.CallExpr | void : () | no value 053: 31:10 | "a" *ast.BasicLit | value : untyped string = "a" | constant 054: 31:16 | nil *ast.Ident | nil : untyped nil | value 055: 33: 1 | n *ast.Ident | var : *main.N | variable 056: 33: 1 | n.onKey *ast.SelectorExpr | value : func(x int, y int) | value 057: 33: 1 | n.onKey 100, 200 *ast.CallExpr | void : () | no value 058: 33: 9 | 100 *ast.BasicLit | value : untyped int = 100 | constant 059: 33:14 | 200 *ast.BasicLit | value : untyped int = 200 | constant == defs == 000: 2: 6 | Mesh | type main.Mesh struct{} 001: 5: 7 | p | var p *main.Mesh 002: 5:16 | Name | func (*main.Mesh).Name() string 003: 10: 2 | m1 | var main.m1 *main.Mesh 004: 11: 2 | m2 | var main.m2 *main.Mesh 005: 14: 1 | main | func main.main() 006: 14: 1 | n | var n *main.N 007: 17:18 | key | var key string 008: 21:16 | key | var key string 009: 25:19 | key | var key main.Mesher 010: 27:23 | key | var key string 011: 29:26 | key | var key string 012: 31:21 | key | var key string == uses == 000: 5:10 | Mesh | type main.Mesh struct{} 001: 5:23 | string | type string 002: 10: 8 | Mesh | type main.Mesh struct{} 003: 11: 8 | Mesh | type main.Mesh struct{} 004: 14: 7 | N | type main.N struct{} 005: 15: 1 | n | var n *main.N 006: 15: 3 | onKey | func (*main.N).OnKey__0(a string, fn func()) 007: 17: 1 | n | var n *main.N 008: 17: 3 | onKey | func (*main.N).OnKey__1(a string, fn func(key string)) 009: 19: 1 | n | var n *main.N 010: 19: 3 | onKey | func (*main.N).OnKey__2(a []string, fn func()) 011: 21: 1 | n | var n *main.N 012: 21: 3 | onKey | func (*main.N).OnKey__3(a []string, fn func(key string)) 013: 23: 1 | n | var n *main.N 014: 23: 3 | onKey | func (*main.N).OnKey__4(a []main.Mesher, fn func()) 015: 23:10 | m1 | var main.m1 *main.Mesh 016: 23:14 | m2 | var main.m2 *main.Mesh 017: 25: 1 | n | var n *main.N 018: 25: 3 | onKey | func (*main.N).OnKey__5(a []main.Mesher, fn func(key main.Mesher)) 019: 25:10 | m1 | var main.m1 *main.Mesh 020: 25:14 | m2 | var main.m2 *main.Mesh 021: 27: 1 | n | var n *main.N 022: 27: 3 | onKey | func (*main.N).OnKey__6(a []string, b []string, fn func(key string)) 023: 29: 1 | n | var n *main.N 024: 29: 3 | onKey | func (*main.N).OnKey__7(a []string, b []main.Mesher, fn func(key string)) 025: 29:17 | m1 | var main.m1 *main.Mesh 026: 29:21 | m2 | var main.m2 *main.Mesh 027: 31: 1 | n | var n *main.N 028: 31: 3 | onKey | func (*main.N).OnKey__6(a []string, b []string, fn func(key string)) 029: 31:16 | nil | nil 030: 33: 1 | n | var n *main.N 031: 33: 3 | onKey | func (*main.N).OnKey__8(x int, y int) == overloads == 000: 15: 3 | onKey | func (main.N).OnKey(__xgo_overload_args__ interface{_()}) 001: 17: 3 | onKey | func (main.N).OnKey(__xgo_overload_args__ interface{_()}) 002: 19: 3 | onKey | func (main.N).OnKey(__xgo_overload_args__ interface{_()}) 003: 21: 3 | onKey | func (main.N).OnKey(__xgo_overload_args__ interface{_()}) 004: 23: 3 | onKey | func (main.N).OnKey(__xgo_overload_args__ interface{_()}) 005: 25: 3 | onKey | func (main.N).OnKey(__xgo_overload_args__ interface{_()}) 006: 27: 3 | onKey | func (main.N).OnKey(__xgo_overload_args__ interface{_()}) 007: 29: 3 | onKey | func (main.N).OnKey(__xgo_overload_args__ interface{_()}) 008: 31: 3 | onKey | func (main.N).OnKey(__xgo_overload_args__ interface{_()}) 009: 33: 3 | onKey | func (main.N).OnKey(__xgo_overload_args__ interface{_()})`) } func TestMixedOverload3(t *testing.T) { testXGoInfo(t, ` Test Test 100 var n N n.test n.test 100 `, ` package main func Test__0() { } func Test__1(n int) { } type N struct { } func (p *N) Test__0() { } func (p *N) Test__1(n int) { } `, `== types == 000: 2: 1 | Test *ast.Ident | value : func() | value 001: 3: 1 | Test *ast.Ident | value : func(n int) | value 002: 3: 1 | Test 100 *ast.CallExpr | void : () | no value 003: 3: 6 | 100 *ast.BasicLit | value : untyped int = 100 | constant 004: 4: 7 | N *ast.Ident | type : main.N | type 005: 5: 1 | n *ast.Ident | var : main.N | variable 006: 5: 1 | n.test *ast.SelectorExpr | value : func() | value 007: 6: 1 | n *ast.Ident | var : main.N | variable 008: 6: 1 | n.test *ast.SelectorExpr | value : func(n int) | value 009: 6: 1 | n.test 100 *ast.CallExpr | void : () | no value 010: 6: 8 | 100 *ast.BasicLit | value : untyped int = 100 | constant == defs == 000: 2: 1 | main | func main.main() 001: 4: 5 | n | var n main.N == uses == 000: 2: 1 | Test | func main.Test__0() 001: 3: 1 | Test | func main.Test__1(n int) 002: 4: 7 | N | type main.N struct{} 003: 5: 1 | n | var n main.N 004: 5: 3 | test | func (*main.N).Test__0() 005: 6: 1 | n | var n main.N 006: 6: 3 | test | func (*main.N).Test__1(n int) == overloads == 000: 2: 1 | Test | func main.Test(__xgo_overload_args__ interface{_()}) 001: 3: 1 | Test | func main.Test(__xgo_overload_args__ interface{_()}) 002: 5: 3 | test | func (main.N).Test(__xgo_overload_args__ interface{_()}) 003: 6: 3 | test | func (main.N).Test(__xgo_overload_args__ interface{_()})`) } func TestOverloadNamed(t *testing.T) { testXGoInfo(t, ` import "github.com/goplus/xgo/cl/internal/overload/bar" var a bar.Var[int] var b bar.Var[bar.M] c := bar.Var(string) d := bar.Var(bar.M) `, ``, `== types == 000: 4: 7 | bar.Var *ast.SelectorExpr | type : github.com/goplus/xgo/cl/internal/overload/bar.Var__0[int] | type 001: 4: 7 | bar.Var[int] *ast.IndexExpr | type : github.com/goplus/xgo/cl/internal/overload/bar.Var__0[int] | type 002: 4:15 | int *ast.Ident | type : int | type 003: 5: 7 | bar.Var *ast.SelectorExpr | type : github.com/goplus/xgo/cl/internal/overload/bar.Var__1[github.com/goplus/xgo/cl/internal/overload/bar.M] | type 004: 5: 7 | bar.Var[bar.M] *ast.IndexExpr | type : github.com/goplus/xgo/cl/internal/overload/bar.Var__1[github.com/goplus/xgo/cl/internal/overload/bar.M] | type 005: 5:15 | bar.M *ast.SelectorExpr | type : github.com/goplus/xgo/cl/internal/overload/bar.M | type 006: 6: 6 | bar.Var *ast.SelectorExpr | value : func[T github.com/goplus/xgo/cl/internal/overload/bar.basetype]() *github.com/goplus/xgo/cl/internal/overload/bar.Var__0[T] | value 007: 6: 6 | bar.Var(string) *ast.CallExpr | value : *github.com/goplus/xgo/cl/internal/overload/bar.Var__0[string] | value 008: 6:14 | string *ast.Ident | type : string | type 009: 7: 6 | bar.Var *ast.SelectorExpr | value : func[T map[string]any]() *github.com/goplus/xgo/cl/internal/overload/bar.Var__1[T] | value 010: 7: 6 | bar.Var(bar.M) *ast.CallExpr | value : *github.com/goplus/xgo/cl/internal/overload/bar.Var__1[github.com/goplus/xgo/cl/internal/overload/bar.M] | value 011: 7:14 | bar.M *ast.SelectorExpr | var : github.com/goplus/xgo/cl/internal/overload/bar.M | variable == defs == 000: 4: 5 | a | var main.a github.com/goplus/xgo/cl/internal/overload/bar.Var__0[int] 001: 5: 5 | b | var main.b github.com/goplus/xgo/cl/internal/overload/bar.Var__1[github.com/goplus/xgo/cl/internal/overload/bar.M] 002: 6: 1 | c | var c *github.com/goplus/xgo/cl/internal/overload/bar.Var__0[string] 003: 6: 1 | main | func main.main() 004: 7: 1 | d | var d *github.com/goplus/xgo/cl/internal/overload/bar.Var__1[github.com/goplus/xgo/cl/internal/overload/bar.M] == uses == 000: 4: 7 | bar | package bar ("github.com/goplus/xgo/cl/internal/overload/bar") 001: 4:11 | Var | type github.com/goplus/xgo/cl/internal/overload/bar.Var__0[T github.com/goplus/xgo/cl/internal/overload/bar.basetype] struct{val T} 002: 4:15 | int | type int 003: 5: 7 | bar | package bar ("github.com/goplus/xgo/cl/internal/overload/bar") 004: 5:11 | Var | type github.com/goplus/xgo/cl/internal/overload/bar.Var__1[T map[string]any] struct{val T} 005: 5:15 | bar | package bar ("github.com/goplus/xgo/cl/internal/overload/bar") 006: 5:19 | M | type github.com/goplus/xgo/cl/internal/overload/bar.M = map[string]any 007: 6: 6 | bar | package bar ("github.com/goplus/xgo/cl/internal/overload/bar") 008: 6:10 | Var | func github.com/goplus/xgo/cl/internal/overload/bar.XGox_Var_Cast__0[T github.com/goplus/xgo/cl/internal/overload/bar.basetype]() *github.com/goplus/xgo/cl/internal/overload/bar.Var__0[T] 009: 6:14 | string | type string 010: 7: 6 | bar | package bar ("github.com/goplus/xgo/cl/internal/overload/bar") 011: 7:10 | Var | func github.com/goplus/xgo/cl/internal/overload/bar.XGox_Var_Cast__1[T map[string]any]() *github.com/goplus/xgo/cl/internal/overload/bar.Var__1[T] 012: 7:14 | bar | package bar ("github.com/goplus/xgo/cl/internal/overload/bar") 013: 7:18 | M | type github.com/goplus/xgo/cl/internal/overload/bar.M = map[string]any == overloads == 000: 4:11 | Var | type github.com/goplus/xgo/cl/internal/overload/bar.Var = func(__xgo_overload_args__ interface{_()}) 001: 5:11 | Var | type github.com/goplus/xgo/cl/internal/overload/bar.Var = func(__xgo_overload_args__ interface{_()}) 002: 6:10 | Var | type github.com/goplus/xgo/cl/internal/overload/bar.Var = func(__xgo_overload_args__ interface{_()}) 003: 7:10 | Var | type github.com/goplus/xgo/cl/internal/overload/bar.Var = func(__xgo_overload_args__ interface{_()})`) } func TestMixedOverloadNamed(t *testing.T) { testXGoInfo(t, ` var a Var[int] var b Var[M] c := Var(string) d := Var(M) `, ` package main type M = map[string]any type basetype interface { string | int | bool | float64 } type Var__0[T basetype] struct { val T } type Var__1[T map[string]any] struct { val T } func XGox_Var_Cast__0[T basetype]() *Var__0[T] { return new(Var__0[T]) } func XGox_Var_Cast__1[T map[string]any]() *Var__1[T] { return new(Var__1[T]) } `, `== types == 000: 2: 7 | Var *ast.Ident | type : main.Var__0[int] | type 001: 2: 7 | Var[int] *ast.IndexExpr | type : main.Var__0[int] | type 002: 2:11 | int *ast.Ident | type : int | type 003: 3: 7 | Var *ast.Ident | type : main.Var__1[main.M] | type 004: 3: 7 | Var[M] *ast.IndexExpr | type : main.Var__1[main.M] | type 005: 3:11 | M *ast.Ident | type : main.M | type 006: 4: 6 | Var *ast.Ident | value : func[T main.basetype]() *main.Var__0[T] | value 007: 4: 6 | Var(string) *ast.CallExpr | value : *main.Var__0[string] | value 008: 4:10 | string *ast.Ident | type : string | type 009: 5: 6 | Var *ast.Ident | value : func[T map[string]interface{}]() *main.Var__1[T] | value 010: 5: 6 | Var(M) *ast.CallExpr | value : *main.Var__1[main.M] | value 011: 5:10 | M *ast.Ident | type : main.M | type == defs == 000: 2: 5 | a | var main.a main.Var__0[int] 001: 3: 5 | b | var main.b main.Var__1[main.M] 002: 4: 1 | c | var c *main.Var__0[string] 003: 4: 1 | main | func main.main() 004: 5: 1 | d | var d *main.Var__1[main.M] == uses == 000: 2: 7 | Var | type main.Var__0[T main.basetype] struct{val T} 001: 2:11 | int | type int 002: 3: 7 | Var | type main.Var__1[T map[string]any] struct{val T} 003: 3:11 | M | type main.M = map[string]any 004: 4: 6 | Var | func main.XGox_Var_Cast__0[T main.basetype]() *main.Var__0[T] 005: 4:10 | string | type string 006: 5: 6 | Var | func main.XGox_Var_Cast__1[T map[string]any]() *main.Var__1[T] 007: 5:10 | M | type main.M = map[string]any == overloads == 000: 2: 7 | Var | type main.Var = func(__xgo_overload_args__ interface{_()}) 001: 3: 7 | Var | type main.Var = func(__xgo_overload_args__ interface{_()}) 002: 4: 6 | Var | type main.Var = func(__xgo_overload_args__ interface{_()}) 003: 5: 6 | Var | type main.Var = func(__xgo_overload_args__ interface{_()})`) } func TestMixedRawNamed(t *testing.T) { testXGoInfo(t, ` var a Var__0[int] var b Var__1[M] c := XGox_Var_Cast__0[string] d := XGox_Var_Cast__1[M] `, ` package main type M = map[string]any type basetype interface { string | int | bool | float64 } type Var__0[T basetype] struct { val T } type Var__1[T map[string]any] struct { val T } func XGox_Var_Cast__0[T basetype]() *Var__0[T] { return new(Var__0[T]) } func XGox_Var_Cast__1[T map[string]any]() *Var__1[T] { return new(Var__1[T]) } `, `== types == 000: 2: 7 | Var__0 *ast.Ident | type : main.Var__0[int] | type 001: 2: 7 | Var__0[int] *ast.IndexExpr | type : main.Var__0[int] | type 002: 2:14 | int *ast.Ident | type : int | type 003: 3: 7 | Var__1 *ast.Ident | type : main.Var__1[main.M] | type 004: 3: 7 | Var__1[M] *ast.IndexExpr | type : main.Var__1[main.M] | type 005: 3:14 | M *ast.Ident | type : main.M | type 006: 4: 6 | XGox_Var_Cast__0 *ast.Ident | value : func[T main.basetype]() *main.Var__0[T] | value 007: 4: 6 | XGox_Var_Cast__0[string] *ast.IndexExpr | var : func() *main.Var__0[string] | variable 008: 4:23 | string *ast.Ident | type : string | type 009: 5: 6 | XGox_Var_Cast__1 *ast.Ident | value : func[T map[string]interface{}]() *main.Var__1[T] | value 010: 5: 6 | XGox_Var_Cast__1[M] *ast.IndexExpr | var : func() *main.Var__1[main.M] | variable 011: 5:23 | M *ast.Ident | type : main.M | type == defs == 000: 2: 5 | a | var main.a main.Var__0[int] 001: 3: 5 | b | var main.b main.Var__1[main.M] 002: 4: 1 | c | var c func() *main.Var__0[string] 003: 4: 1 | main | func main.main() 004: 5: 1 | d | var d func() *main.Var__1[main.M] == uses == 000: 2: 7 | Var__0 | type main.Var__0[T main.basetype] struct{val T} 001: 2:14 | int | type int 002: 3: 7 | Var__1 | type main.Var__1[T map[string]any] struct{val T} 003: 3:14 | M | type main.M = map[string]any 004: 4: 6 | XGox_Var_Cast__0 | func main.XGox_Var_Cast__0[T main.basetype]() *main.Var__0[T] 005: 4:23 | string | type string 006: 5: 6 | XGox_Var_Cast__1 | func main.XGox_Var_Cast__1[T map[string]any]() *main.Var__1[T] 007: 5:23 | M | type main.M = map[string]any`) } func TestSpxInfo(t *testing.T) { testSpxInfo(t, "Kai.tspx", ` var ( a int ) type info struct { x int y int } func onInit() { a = 1 clone clone info{1,2} clone &info{1,2} } func onCloned() { say("Hi") } `, `== types == 000: 0: 0 | MyGame *ast.Ident | type : main.MyGame | type 001: 1: 1 | Kai *ast.Ident | type : main.Kai | type 002: 3: 4 | int *ast.Ident | type : int | type 003: 6:11 | struct { x int y int } *ast.StructType | type : struct{x int; y int} | type 004: 7: 4 | int *ast.Ident | type : int | type 005: 8: 4 | int *ast.Ident | type : int | type 006: 12: 2 | a *ast.Ident | var : int | variable 007: 12: 6 | 1 *ast.BasicLit | value : untyped int = 1 | constant 008: 13: 2 | clone *ast.Ident | value : func(sprite any) | value 009: 14: 2 | clone *ast.Ident | value : func(sprite any, data any) | value 010: 14: 2 | clone info{1, 2} *ast.CallExpr | void : () | no value 011: 14: 8 | info *ast.Ident | type : main.info | type 012: 14: 8 | info{1, 2} *ast.CompositeLit | value : main.info | value 013: 14:13 | 1 *ast.BasicLit | value : untyped int = 1 | constant 014: 14:15 | 2 *ast.BasicLit | value : untyped int = 2 | constant 015: 15: 2 | clone *ast.Ident | value : func(sprite any, data any) | value 016: 15: 2 | clone &info{1, 2} *ast.CallExpr | void : () | no value 017: 15: 8 | &info{1, 2} *ast.UnaryExpr | value : *main.info | value 018: 15: 9 | info *ast.Ident | type : main.info | type 019: 15: 9 | info{1, 2} *ast.CompositeLit | value : main.info | value 020: 15:14 | 1 *ast.BasicLit | value : untyped int = 1 | constant 021: 15:16 | 2 *ast.BasicLit | value : untyped int = 2 | constant 022: 19: 2 | say *ast.Ident | value : func(msg string, secs ...float64) | value 023: 19: 2 | say("Hi") *ast.CallExpr | void : () | no value 024: 19: 6 | "Hi" *ast.BasicLit | value : untyped string = "Hi" | constant == defs == 000: 0: 0 | Main | func (*main.Kai).Main() 001: 1: 1 | this | var this *main.Kai 002: 3: 2 | a | field a int 003: 6: 6 | info | type main.info struct{x int; y int} 004: 7: 2 | x | field x int 005: 8: 2 | y | field y int 006: 11: 6 | onInit | func (*main.Kai).onInit() 007: 18: 6 | onCloned | func (*main.Kai).onCloned() == uses == 000: 0: 0 | MyGame | type main.MyGame struct{*github.com/goplus/xgo/cl/internal/spx.MyGame} 001: 1: 1 | Kai | type main.Kai struct{github.com/goplus/xgo/cl/internal/spx.Sprite; *main.MyGame; a int} 002: 3: 4 | int | type int 003: 7: 4 | int | type int 004: 8: 4 | int | type int 005: 12: 2 | a | field a int 006: 13: 2 | clone | func github.com/goplus/xgo/cl/internal/spx.Gopt_Sprite_Clone__0(sprite any) 007: 14: 2 | clone | func github.com/goplus/xgo/cl/internal/spx.Gopt_Sprite_Clone__1(sprite any, data any) 008: 14: 8 | info | type main.info struct{x int; y int} 009: 15: 2 | clone | func github.com/goplus/xgo/cl/internal/spx.Gopt_Sprite_Clone__1(sprite any, data any) 010: 15: 9 | info | type main.info struct{x int; y int} 011: 19: 2 | say | func (*github.com/goplus/xgo/cl/internal/spx.Sprite).Say(msg string, secs ...float64) == overloads == 000: 13: 2 | clone | func (github.com/goplus/xgo/cl/internal/spx.Sprite).Clone(__xgo_overload_args__ interface{_()}) 001: 14: 2 | clone | func (github.com/goplus/xgo/cl/internal/spx.Sprite).Clone(__xgo_overload_args__ interface{_()}) 002: 15: 2 | clone | func (github.com/goplus/xgo/cl/internal/spx.Sprite).Clone(__xgo_overload_args__ interface{_()})`) } func TestScopesInfo(t *testing.T) { var tests = []struct { src string scopes []string // list of scope descriptors of the form kind:varlist }{ {`package p0`, []string{ "file:", }}, {`package p1; import ( "fmt"; m "math"; _ "os" ); var ( _ = fmt.Println; _ = m.Pi )`, []string{ "file:fmt m", }}, {`package p2; func _() {}`, []string{ "file:", "func:", }}, {`package p3; func _(x, y int) {}`, []string{ "file:", "func:x y", }}, {`package p4; func _(x, y int) { x, z := 1, 2; _ = z }`, []string{ "file:", "func:x y z", // redeclaration of x }}, {`package p5; func _(x, y int) (u, _ int) { return }`, []string{ "file:", "func:u x y", }}, {`package p6; func _() { { var x int; _ = x } }`, []string{ "file:", "func:", "block:x", }}, {`package p7; func _() { if true {} }`, []string{ "file:", "func:", "if:", "block:", }}, {`package p8; func _() { if x := 0; x < 0 { y := x; _ = y } }`, []string{ "file:", "func:", "if:x", "block:y", }}, {`package p9; func _() { switch x := 0; x {} }`, []string{ "file:", "func:", "switch:x", }}, {`package p10; func _() { switch x := 0; x { case 1: y := x; _ = y; default: }}`, []string{ "file:", "func:", "switch:x", "case:y", "case:", }}, {`package p11; func _(t interface{}) { switch t.(type) {} }`, []string{ "file:", "func:t", "type switch:", }}, {`package p12; func _(t interface{}) { switch t := t; t.(type) {} }`, []string{ "file:", "func:t", "type switch:t", }}, {`package p13; func _(t interface{}) { switch x := t.(type) { case int: _ = x } }`, []string{ "file:", "func:t", "type switch:", "case:x", // x implicitly declared }}, {`package p14; func _() { select{} }`, []string{ "file:", "func:", }}, {`package p15; func _(c chan int) { select{ case <-c: } }`, []string{ "file:", "func:c", "comm:", }}, {`package p16; func _(c chan int) { select{ case i := <-c: x := i; _ = x} }`, []string{ "file:", "func:c", "comm:i x", }}, {`package p17; func _() { for{} }`, []string{ "file:", "func:", "for:", "block:", }}, {`package p18; func _(n int) { for i := 0; i < n; i++ { _ = i } }`, []string{ "file:", "func:n", "for:i", "block:", }}, {`package p19; func _(a []int) { for i := range a { _ = i} }`, []string{ "file:", "func:a", "range:i", "block:", }}, {`package p20; var s int; func _(a []int) { for i, x := range a { s += x; _ = i } }`, []string{ "file:", "func:a", "range:i x", "block:", }}, {`package p21; var s int; func _(a []int) { for i, x := range a { c := i; println(c) } }`, []string{ "file:", "func:a", "range:i x", "block:c", }}, {`package p22; func _(){ sum := 0; for x <- [1, 3, 5, 7, 11, 13, 17], x > 3 { sum = sum + x; c := sum; _ = c } }`, []string{ "file:", "func:sum", "for phrase:x", "block:c", }}, {`package p23; func _(){ sum := 0; for x <- [1, 3, 5, 7, 11, 13, 17] { sum = sum + x; c := sum; _ = c } }`, []string{ "file:", "func:sum", "for phrase:x", "block:c", }}, {`package p23; func test(fn func(int)int){};func _(){test(func(x int) int { y := x*x; return y } ) }`, []string{ "file:test", "func:fn", "func:", "func:x y", }}, {`package p24; func test(fn func(int)int){};func _(){test( x => x*x );}`, []string{ "file:test", "func:fn", "func:", "lambda:x", }}, {`package p25; func test(fn func(int)int){};func _(){test( x => { y := x*x; return y } ) }`, []string{ "file:test", "func:fn", "func:", "lambda:x y", }}, {`package p26; func _(){ b := {for x <- ["1", "3", "5", "7", "11"], x == "5"}; _ = b }`, []string{ "file:", "func:b", "for phrase:x", }}, {`package p27; func _(){ b, ok := {i for i, x <- ["1", "3", "5", "7", "11"], x == "5"}; _ = b; _ = ok }`, []string{ "file:", "func:b ok", "for phrase:i x", }}, {`package p28; func _(){ a := [x*x for x <- [1, 3.4, 5] if x > 2 ]; _ = a }`, []string{ "file:", "func:a", "for phrase:x", }}, {`package p29; func _(){ arr := [1, 2, 3, 4.1, 5, 6];x := [[a, b] for a <- arr, a < b for b <- arr, b > 2]; _ = x }`, []string{ "file:", "func:arr x", "for phrase:b", "for phrase:a", }}, {`package p30; func _(){ y := {x: i for i, x <- ["1", "3", "5", "7", "11"]}; _ = y }`, []string{ "file:", "func:y", "for phrase:i x", }}, {`package p31; func _(){ z := {v: k for k, v <- {"Hello": 1, "Hi": 3, "xsw": 5, "XGo": 7}, v > 3}; _ = z }`, []string{ "file:", "func:z", "for phrase:k v", }}, } for _, test := range tests { pkg, info, err := parseSource(token.NewFileSet(), "src.xgo", test.src, 0) if err != nil { t.Fatalf("parse source failed: %v", test.src) } name := pkg.Name() // number of scopes must match if len(info.Scopes) != len(test.scopes) { t.Errorf("package %s: got %d scopes; want %d\n%v\n%v", name, len(info.Scopes), len(test.scopes), test.scopes, info.Scopes) } // scope descriptions must match for node, scope := range info.Scopes { kind := "" switch node.(type) { case *ast.File: kind = "file" case *ast.FuncType: kind = "func" case *ast.BlockStmt: kind = "block" case *ast.IfStmt: kind = "if" case *ast.SwitchStmt: kind = "switch" case *ast.TypeSwitchStmt: kind = "type switch" case *ast.CaseClause: kind = "case" case *ast.CommClause: kind = "comm" case *ast.ForStmt: kind = "for" case *ast.RangeStmt: kind = "range" case *ast.ForPhraseStmt: kind = "for phrase" case *ast.LambdaExpr: kind = "lambda" case *ast.LambdaExpr2: kind = "lambda" case *ast.ForPhrase: kind = "for phrase" default: kind = fmt.Sprintf(" %T", node) } // look for matching scope description desc := kind + ":" + strings.Join(scope.Names(), " ") found := false for _, d := range test.scopes { if desc == d { found = true break } } if !found { t.Errorf("package %s: no matching scope found for %s", name, desc) } } } } func TestAddress(t *testing.T) { testInfo(t, `package address type foo struct{ c int; p *int } func (f foo) ptr() *foo { return &f } func (f foo) clone() foo { return f } type nested struct { f foo a [2]foo s []foo m map[int]foo } func _() { getNested := func() nested { return nested{} } getNestedPtr := func() *nested { return &nested{} } _ = getNested().f.c _ = getNested().a[0].c _ = getNested().s[0].c _ = getNested().m[0].c _ = getNested().f.ptr().c _ = getNested().f.clone().c _ = getNested().f.clone().ptr().c _ = getNestedPtr().f.c _ = getNestedPtr().a[0].c _ = getNestedPtr().s[0].c _ = getNestedPtr().m[0].c _ = getNestedPtr().f.ptr().c _ = getNestedPtr().f.clone().c _ = getNestedPtr().f.clone().ptr().c } `) } func TestAddress2(t *testing.T) { testInfo(t, `package load import "os" func _() { _ = os.Stdout } func _() { var a int _ = a } func _() { var p *int _ = *p } func _() { var s []int _ = s[0] } func _() { var s struct{f int} _ = s.f } func _() { var a [10]int _ = a[0] } func _(x int) { _ = x } func _()(x int) { _ = x return } type T int func (x T) _() { _ = x } func _() { var a, b int _ = a + b } func _() { _ = &[]int{1} } func _() { _ = func(){} } func f() { _ = f } func _() { var m map[int]int _ = m[0] _, _ = m[0] } func _() { var ch chan int _ = <-ch _, _ = <-ch } `) } func TestMixedPackage(t *testing.T) { fset := token.NewFileSet() pkg, _, _, err := parseMixedSource(xgomod.Default, fset, "main.xgo", ` Test Test 100 var n N n.test n.test 100 `, "main.go", ` package main func Test__0() { } func Test__1(n int) { } type N struct { } func (p *N) Test__0() { } func (p *N) Test__1(n int) { }`, parser.Config{}, true) if err != nil { t.Fatal(err) } obj := pkg.Scope().Lookup("N") named := obj.Type().(*types.Named) if named.NumMethods() == 2 { t.Fatal("found overload method failed") } ext, ok := gogen.CheckFuncEx(named.Method(2).Type().(*types.Signature)) if !ok { t.Fatal("checkFuncEx failed") } m, ok := ext.(*gogen.TyOverloadMethod) if !ok || len(m.Methods) != 2 { t.Fatal("check TyOverloadMethod failed") } } func TestGopOverloadUses(t *testing.T) { testXGoInfo(t, ` func MulInt(a, b int) int { return a * b } func MulFloat(a, b float64) float64 { return a * b } func Mul = ( MulInt MulFloat func(x, y, z int) int { return x * y * z } ) Mul 100,200 Mul 100,200,300 `, ``, `== types == 000: 0: 0 | "MulInt,MulFloat," *ast.BasicLit | value : untyped string = "MulInt,MulFloat," | constant 001: 2:18 | int *ast.Ident | type : int | type 002: 2:23 | int *ast.Ident | type : int | type 003: 3: 9 | a *ast.Ident | var : int | variable 004: 3: 9 | a * b *ast.BinaryExpr | value : int | value 005: 3:13 | b *ast.Ident | var : int | variable 006: 6:20 | float64 *ast.Ident | type : float64 | type 007: 6:29 | float64 *ast.Ident | type : float64 | type 008: 7: 9 | a *ast.Ident | var : float64 | variable 009: 7: 9 | a * b *ast.BinaryExpr | value : float64 | value 010: 7:13 | b *ast.Ident | var : float64 | variable 011: 13: 2 | func(x, y, z int) int *ast.FuncType | type : func(x int, y int, z int) int | type 012: 13: 2 | func(x, y, z int) int { return x * y * z } *ast.FuncLit | value : func(x int, y int, z int) int | value 013: 13:15 | int *ast.Ident | type : int | type 014: 13:20 | int *ast.Ident | type : int | type 015: 14:10 | x *ast.Ident | var : int | variable 016: 14:10 | x * y *ast.BinaryExpr | value : int | value 017: 14:10 | x * y * z *ast.BinaryExpr | value : int | value 018: 14:14 | y *ast.Ident | var : int | variable 019: 14:18 | z *ast.Ident | var : int | variable 020: 18: 1 | Mul *ast.Ident | value : func(a int, b int) int | value 021: 18: 1 | Mul 100, 200 *ast.CallExpr | value : int | value 022: 18: 5 | 100 *ast.BasicLit | value : untyped int = 100 | constant 023: 18: 9 | 200 *ast.BasicLit | value : untyped int = 200 | constant 024: 19: 1 | Mul *ast.Ident | value : func(x int, y int, z int) int | value 025: 19: 1 | Mul 100, 200, 300 *ast.CallExpr | value : int | value 026: 19: 5 | 100 *ast.BasicLit | value : untyped int = 100 | constant 027: 19: 9 | 200 *ast.BasicLit | value : untyped int = 200 | constant 028: 19:13 | 300 *ast.BasicLit | value : untyped int = 300 | constant == defs == 000: 0: 0 | XGoo_Mul | const main.XGoo_Mul untyped string 001: 2: 6 | MulInt | func main.MulInt(a int, b int) int 002: 2:13 | a | var a int 003: 2:16 | b | var b int 004: 6: 6 | MulFloat | func main.MulFloat(a float64, b float64) float64 005: 6:15 | a | var a float64 006: 6:18 | b | var b float64 007: 10: 6 | Mul | func main.Mul(__xgo_overload_args__ interface{_()}) 008: 13: 2 | Mul__2 | func main.Mul__2(x int, y int, z int) int 009: 13: 7 | x | var x int 010: 13:10 | y | var y int 011: 13:13 | z | var z int 012: 18: 1 | main | func main.main() == uses == 000: 2:18 | int | type int 001: 2:23 | int | type int 002: 3: 9 | a | var a int 003: 3:13 | b | var b int 004: 6:20 | float64 | type float64 005: 6:29 | float64 | type float64 006: 7: 9 | a | var a float64 007: 7:13 | b | var b float64 008: 11: 2 | MulInt | func main.MulInt(a int, b int) int 009: 12: 2 | MulFloat | func main.MulFloat(a float64, b float64) float64 010: 13:15 | int | type int 011: 13:20 | int | type int 012: 14:10 | x | var x int 013: 14:14 | y | var y int 014: 14:18 | z | var z int 015: 18: 1 | Mul | func main.MulInt(a int, b int) int 016: 19: 1 | Mul | func main.Mul__2(x int, y int, z int) int == overloads == 000: 18: 1 | Mul | func main.Mul(__xgo_overload_args__ interface{_()}) 001: 19: 1 | Mul | func main.Mul(__xgo_overload_args__ interface{_()})`) testXGoInfo(t, ` type foo struct { } func (a *foo) mulInt(b int) *foo { return a } func (a *foo) mulFoo(b *foo) *foo { return a } func (foo).mul = ( (foo).mulInt (foo).mulFoo ) var a, b *foo var c = a.mul(100) var d = a.mul(c) `, ``, `== types == 000: 0: 0 | ".mulInt,.mulFoo" *ast.BasicLit | value : untyped string = ".mulInt,.mulFoo" | constant 001: 2:10 | struct { } *ast.StructType | type : struct{} | type 002: 5:10 | foo *ast.Ident | type : main.foo | type 003: 5:24 | int *ast.Ident | type : int | type 004: 5:29 | *foo *ast.StarExpr | type : *main.foo | type 005: 5:30 | foo *ast.Ident | type : main.foo | type 006: 6: 9 | a *ast.Ident | var : *main.foo | variable 007: 9:10 | foo *ast.Ident | type : main.foo | type 008: 9:24 | *foo *ast.StarExpr | type : *main.foo | type 009: 9:25 | foo *ast.Ident | type : main.foo | type 010: 9:30 | *foo *ast.StarExpr | type : *main.foo | type 011: 9:31 | foo *ast.Ident | type : main.foo | type 012: 10: 9 | a *ast.Ident | var : *main.foo | variable 013: 18:10 | *foo *ast.StarExpr | type : *main.foo | type 014: 18:11 | foo *ast.Ident | type : main.foo | type 015: 19: 9 | a *ast.Ident | var : *main.foo | variable 016: 19: 9 | a.mul *ast.SelectorExpr | value : func(b int) *main.foo | value 017: 19: 9 | a.mul(100) *ast.CallExpr | value : *main.foo | value 018: 19:15 | 100 *ast.BasicLit | value : untyped int = 100 | constant 019: 20: 9 | a *ast.Ident | var : *main.foo | variable 020: 20: 9 | a.mul *ast.SelectorExpr | value : func(b *main.foo) *main.foo | value 021: 20: 9 | a.mul(c) *ast.CallExpr | value : *main.foo | value 022: 20:15 | c *ast.Ident | var : *main.foo | variable == defs == 000: 0: 0 | XGoo_foo_mul | const main.XGoo_foo_mul untyped string 001: 2: 6 | foo | type main.foo struct{} 002: 5: 7 | a | var a *main.foo 003: 5:15 | mulInt | func (*main.foo).mulInt(b int) *main.foo 004: 5:22 | b | var b int 005: 9: 7 | a | var a *main.foo 006: 9:15 | mulFoo | func (*main.foo).mulFoo(b *main.foo) *main.foo 007: 9:22 | b | var b *main.foo 008: 13:12 | mul | func (main.foo).mul(__xgo_overload_args__ interface{_()}) 009: 18: 5 | a | var main.a *main.foo 010: 18: 8 | b | var main.b *main.foo 011: 19: 5 | c | var main.c *main.foo 012: 20: 5 | d | var main.d *main.foo == uses == 000: 5:10 | foo | type main.foo struct{} 001: 5:24 | int | type int 002: 5:30 | foo | type main.foo struct{} 003: 6: 9 | a | var a *main.foo 004: 9:10 | foo | type main.foo struct{} 005: 9:25 | foo | type main.foo struct{} 006: 9:31 | foo | type main.foo struct{} 007: 10: 9 | a | var a *main.foo 008: 13: 7 | foo | type main.foo struct{} 009: 14: 3 | foo | type main.foo struct{} 010: 14: 8 | mulInt | func (*main.foo).mulInt(b int) *main.foo 011: 15: 3 | foo | type main.foo struct{} 012: 15: 8 | mulFoo | func (*main.foo).mulFoo(b *main.foo) *main.foo 013: 18:11 | foo | type main.foo struct{} 014: 19: 9 | a | var main.a *main.foo 015: 19:11 | mul | func (*main.foo).mulInt(b int) *main.foo 016: 20: 9 | a | var main.a *main.foo 017: 20:11 | mul | func (*main.foo).mulFoo(b *main.foo) *main.foo 018: 20:15 | c | var main.c *main.foo == overloads == 000: 19:11 | mul | func (main.foo).mul(__xgo_overload_args__ interface{_()}) 001: 20:11 | mul | func (main.foo).mul(__xgo_overload_args__ interface{_()})`) } func TestGopOverloadDecl(t *testing.T) { testXGoInfo(t, ` func addInt0() { } func addInt1(i int) { } func addInt2(i, j int) { } var addInt3 = func(i, j, k int) { } func add = ( addInt0 addInt1 addInt2 addInt3 func(a, b string) string { return a + b } ) func init() { add 100, 200 add 100, 200, 300 add("hello", "world") } `, ``, `== types == 000: 0: 0 | "addInt0,addInt1,addInt2,addInt3," *ast.BasicLit | value : untyped string = "addInt0,addInt1,addInt2,addInt3," | constant 001: 5:16 | int *ast.Ident | type : int | type 002: 8:19 | int *ast.Ident | type : int | type 003: 11:15 | func(i, j, k int) *ast.FuncType | type : func(i int, j int, k int) | type 004: 11:15 | func(i, j, k int) { } *ast.FuncLit | value : func(i int, j int, k int) | value 005: 11:28 | int *ast.Ident | type : int | type 006: 19: 2 | func(a, b string) string *ast.FuncType | type : func(a string, b string) string | type 007: 19: 2 | func(a, b string) string { return a + b } *ast.FuncLit | value : func(a string, b string) string | value 008: 19:12 | string *ast.Ident | type : string | type 009: 19:20 | string *ast.Ident | type : string | type 010: 20:10 | a *ast.Ident | var : string | variable 011: 20:10 | a + b *ast.BinaryExpr | value : string | value 012: 20:14 | b *ast.Ident | var : string | variable 013: 25: 2 | add *ast.Ident | value : func(i int, j int) | value 014: 25: 2 | add 100, 200 *ast.CallExpr | void : () | no value 015: 25: 6 | 100 *ast.BasicLit | value : untyped int = 100 | constant 016: 25:11 | 200 *ast.BasicLit | value : untyped int = 200 | constant 017: 26: 2 | add *ast.Ident | var : func(i int, j int, k int) | variable 018: 26: 2 | add 100, 200, 300 *ast.CallExpr | void : () | no value 019: 26: 6 | 100 *ast.BasicLit | value : untyped int = 100 | constant 020: 26:11 | 200 *ast.BasicLit | value : untyped int = 200 | constant 021: 26:16 | 300 *ast.BasicLit | value : untyped int = 300 | constant 022: 27: 2 | add *ast.Ident | value : func(a string, b string) string | value 023: 27: 2 | add("hello", "world") *ast.CallExpr | value : string | value 024: 27: 6 | "hello" *ast.BasicLit | value : untyped string = "hello" | constant 025: 27:15 | "world" *ast.BasicLit | value : untyped string = "world" | constant == defs == 000: 0: 0 | XGoo_add | const main.XGoo_add untyped string 001: 2: 6 | addInt0 | func main.addInt0() 002: 5: 6 | addInt1 | func main.addInt1(i int) 003: 5:14 | i | var i int 004: 8: 6 | addInt2 | func main.addInt2(i int, j int) 005: 8:14 | i | var i int 006: 8:17 | j | var j int 007: 11: 5 | addInt3 | var main.addInt3 func(i int, j int, k int) 008: 11:20 | i | var i int 009: 11:23 | j | var j int 010: 11:26 | k | var k int 011: 14: 6 | add | func main.add(__xgo_overload_args__ interface{_()}) 012: 19: 2 | add__4 | func main.add__4(a string, b string) string 013: 19: 7 | a | var a string 014: 19:10 | b | var b string 015: 24: 6 | init | func main.init() == uses == 000: 5:16 | int | type int 001: 8:19 | int | type int 002: 11:28 | int | type int 003: 15: 2 | addInt0 | func main.addInt0() 004: 16: 2 | addInt1 | func main.addInt1(i int) 005: 17: 2 | addInt2 | func main.addInt2(i int, j int) 006: 18: 2 | addInt3 | var main.addInt3 func(i int, j int, k int) 007: 19:12 | string | type string 008: 19:20 | string | type string 009: 20:10 | a | var a string 010: 20:14 | b | var b string 011: 25: 2 | add | func main.addInt2(i int, j int) 012: 26: 2 | add | var main.addInt3 func(i int, j int, k int) 013: 27: 2 | add | func main.add__4(a string, b string) string == overloads == 000: 25: 2 | add | func main.add(__xgo_overload_args__ interface{_()}) 001: 26: 2 | add | func main.add(__xgo_overload_args__ interface{_()}) 002: 27: 2 | add | func main.add(__xgo_overload_args__ interface{_()})`) testXGoInfo(t, ` func add = ( func(a, b int) int { return a + b } func(a, b string) string { return a + b } ) func init() { add 100, 200 add "hello", "world" } `, ``, `== types == 000: 3: 2 | func(a, b int) int *ast.FuncType | type : func(a int, b int) int | type 001: 3: 2 | func(a, b int) int { return a + b } *ast.FuncLit | value : func(a int, b int) int | value 002: 3:12 | int *ast.Ident | type : int | type 003: 3:17 | int *ast.Ident | type : int | type 004: 4:10 | a *ast.Ident | var : int | variable 005: 4:10 | a + b *ast.BinaryExpr | value : int | value 006: 4:14 | b *ast.Ident | var : int | variable 007: 6: 2 | func(a, b string) string *ast.FuncType | type : func(a string, b string) string | type 008: 6: 2 | func(a, b string) string { return a + b } *ast.FuncLit | value : func(a string, b string) string | value 009: 6:12 | string *ast.Ident | type : string | type 010: 6:20 | string *ast.Ident | type : string | type 011: 7:10 | a *ast.Ident | var : string | variable 012: 7:10 | a + b *ast.BinaryExpr | value : string | value 013: 7:14 | b *ast.Ident | var : string | variable 014: 12: 2 | add *ast.Ident | value : func(a int, b int) int | value 015: 12: 2 | add 100, 200 *ast.CallExpr | value : int | value 016: 12: 6 | 100 *ast.BasicLit | value : untyped int = 100 | constant 017: 12:11 | 200 *ast.BasicLit | value : untyped int = 200 | constant 018: 13: 2 | add *ast.Ident | value : func(a string, b string) string | value 019: 13: 2 | add "hello", "world" *ast.CallExpr | value : string | value 020: 13: 6 | "hello" *ast.BasicLit | value : untyped string = "hello" | constant 021: 13:15 | "world" *ast.BasicLit | value : untyped string = "world" | constant == defs == 000: 2: 6 | add | func main.add(__xgo_overload_args__ interface{_()}) 001: 3: 2 | add__0 | func main.add__0(a int, b int) int 002: 3: 7 | a | var a int 003: 3:10 | b | var b int 004: 6: 2 | add__1 | func main.add__1(a string, b string) string 005: 6: 7 | a | var a string 006: 6:10 | b | var b string 007: 11: 6 | init | func main.init() == uses == 000: 3:12 | int | type int 001: 3:17 | int | type int 002: 4:10 | a | var a int 003: 4:14 | b | var b int 004: 6:12 | string | type string 005: 6:20 | string | type string 006: 7:10 | a | var a string 007: 7:14 | b | var b string 008: 12: 2 | add | func main.add__0(a int, b int) int 009: 13: 2 | add | func main.add__1(a string, b string) string == overloads == 000: 12: 2 | add | func main.add(__xgo_overload_args__ interface{_()}) 001: 13: 2 | add | func main.add(__xgo_overload_args__ interface{_()})`) } func TestGoxOverloadInfo(t *testing.T) { testSpxInfo(t, "Rect.gox", ` func addInt(a, b int) int { return a + b } func addString(a, b string) string { return a + b } func add = ( addInt func(a, b float64) float64 { return a + b } addString ) `, `== types == 000: 0: 0 | ".addInt,,.addString" *ast.BasicLit | value : untyped string = ".addInt,,.addString" | constant 001: 1: 1 | Rect *ast.Ident | type : main.Rect | type 002: 2:18 | int *ast.Ident | type : int | type 003: 2:23 | int *ast.Ident | type : int | type 004: 3: 9 | a *ast.Ident | var : int | variable 005: 3: 9 | a + b *ast.BinaryExpr | value : int | value 006: 3:13 | b *ast.Ident | var : int | variable 007: 6:21 | string *ast.Ident | type : string | type 008: 6:29 | string *ast.Ident | type : string | type 009: 7: 9 | a *ast.Ident | var : string | variable 010: 7: 9 | a + b *ast.BinaryExpr | value : string | value 011: 7:13 | b *ast.Ident | var : string | variable 012: 12:12 | float64 *ast.Ident | type : float64 | type 013: 12:21 | float64 *ast.Ident | type : float64 | type 014: 13:10 | a *ast.Ident | var : float64 | variable 015: 13:10 | a + b *ast.BinaryExpr | value : float64 | value 016: 13:14 | b *ast.Ident | var : float64 | variable == defs == 000: 0: 0 | XGoo_Rect_add | const main.XGoo_Rect_add untyped string 001: 1: 1 | this | var this *main.Rect 002: 2: 6 | addInt | func (*main.Rect).addInt(a int, b int) int 003: 2:13 | a | var a int 004: 2:16 | b | var b int 005: 6: 6 | addString | func (*main.Rect).addString(a string, b string) string 006: 6:16 | a | var a string 007: 6:19 | b | var b string 008: 10: 6 | add | func (main.Rect).add(__xgo_overload_args__ interface{_()}) 009: 12: 2 | add__1 | func (*main.Rect).add__1(a float64, b float64) float64 010: 12: 7 | a | var a float64 011: 12:10 | b | var b float64 == uses == 000: 1: 1 | Rect | type main.Rect struct{} 001: 2:18 | int | type int 002: 2:23 | int | type int 003: 3: 9 | a | var a int 004: 3:13 | b | var b int 005: 6:21 | string | type string 006: 6:29 | string | type string 007: 7: 9 | a | var a string 008: 7:13 | b | var b string 009: 11: 2 | addInt | func (*main.Rect).addInt(a int, b int) int 010: 12:12 | float64 | type float64 011: 12:21 | float64 | type float64 012: 13:10 | a | var a float64 013: 13:14 | b | var b float64 014: 15: 2 | addString | func (*main.Rect).addString(a string, b string) string`) } func TestTypesAlias(t *testing.T) { testInfo(t, `package main import "fmt" type T = int var v T func demo(v T) { fmt.Println(v) } func main() { demo(100) } `) } ================================================ FILE: x/typesutil/internal/typesutil/types.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package typesutil import ( "go/token" "go/types" "unsafe" ) // ----------------------------------------------------------------------------- // A Scope maintains a set of objects and links to its containing // (parent) and contained (children) scopes. Objects may be inserted // and looked up by name. The zero value for Scope is a ready-to-use // empty scope. type Scope struct { parent *Scope children []*Scope number int // parent.children[number-1] is this scope; 0 if there is no parent elems map[string]types.Object // lazily allocated pos, end token.Pos // scope extent; may be invalid comment string // for debugging only isFunc bool // set if this is a function scope (internal use only) } // ScopeDelete deletes an object from specified scope by its name. func ScopeDelete(s *types.Scope, name string) types.Object { elems := (*Scope)(unsafe.Pointer(s)).elems if o, ok := elems[name]; ok { delete(elems, name) return o } return nil } // ----------------------------------------------------------------------------- // An Error describes a type-checking error; it implements the error interface. // A "soft" error is an error that still permits a valid interpretation of a // package (such as "unused variable"); "hard" errors may lead to unpredictable // behavior if ignored. type Error struct { Fset *token.FileSet // file set for interpretation of Pos Pos token.Pos // error position Msg string // error message Soft bool // if set, error is "soft" // go116code is a future API, unexported as the set of error codes is large // and likely to change significantly during experimentation. Tools wishing // to preview this feature may read go116code using reflection (see // errorcodes_test.go), but beware that there is no guarantee of future // compatibility. go116code int go116start token.Pos go116end token.Pos } func SetErrorGo116(ret *types.Error, code int, start, end token.Pos) { e := (*Error)(unsafe.Pointer(ret)) e.go116code = code e.go116start = start e.go116end = end } // GetErrorGo116 extracts the go116 fields from types.Error func GetErrorGo116(err *types.Error) (code int, start, end token.Pos, ok bool) { defer func() { if recover() != nil { code = 0 start = token.NoPos end = token.NoPos ok = false } }() e := (*Error)(unsafe.Pointer(err)) code = e.go116code start = e.go116start end = e.go116end ok = true return } // ----------------------------------------------------------------------------- func init() { if unsafe.Sizeof(Scope{}) != unsafe.Sizeof(types.Scope{}) { panic("unexpected sizeof types.Scope") } if unsafe.Sizeof(Error{}) != unsafe.Sizeof(types.Error{}) { panic("unexpected sizeof types.Error") } } // ----------------------------------------------------------------------------- ================================================ FILE: x/typesutil/typeparams/typeparams.go ================================================ // Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package typeparams import ( "github.com/goplus/xgo/ast" "github.com/goplus/xgo/token" ) func PackIndexExpr(x ast.Expr, lbrack token.Pos, exprs []ast.Expr, rbrack token.Pos) ast.Expr { switch len(exprs) { case 0: panic("internal error: PackIndexExpr with empty expr slice") case 1: return &ast.IndexExpr{ X: x, Lbrack: lbrack, Index: exprs[0], Rbrack: rbrack, } default: return &ast.IndexListExpr{ X: x, Lbrack: lbrack, Indices: exprs, Rbrack: rbrack, } } } // IndexExpr wraps an ast.IndexExpr or ast.IndexListExpr. // // Orig holds the original ast.Expr from which this IndexExpr was derived. type IndexExpr struct { Orig ast.Expr // the wrapped expr, which may be distinct from the IndexListExpr below. *ast.IndexListExpr } func UnpackIndexExpr(n ast.Node) *IndexExpr { switch e := n.(type) { case *ast.IndexExpr: return &IndexExpr{e, &ast.IndexListExpr{ X: e.X, Lbrack: e.Lbrack, Indices: []ast.Expr{e.Index}, Rbrack: e.Rbrack, }} case *ast.IndexListExpr: return &IndexExpr{e, e} } return nil } ================================================ FILE: x/watcher/changes.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package watcher import ( "io/fs" "log" "path" "path/filepath" "strings" "sync" "github.com/goplus/mod/xgomod" ) // ----------------------------------------------------------------------------------------- type module struct { exts []string } func (p *module) ignore(fname string) bool { ext := path.Ext(fname) for _, v := range p.exts { if ext == v { return false } } return true } // ----------------------------------------------------------------------------------------- type none struct{} type Changes struct { changed map[string]none mods map[string]*module mutex sync.Mutex cond sync.Cond root string } func NewChanges(root string) *Changes { changed := make(map[string]none) mods := make(map[string]*module) root, _ = filepath.Abs(root) c := &Changes{changed: changed, mods: mods, root: root + "/"} c.cond.L = &c.mutex return c } func (p *Changes) doLookupMod(name string) *module { mod, ok := p.mods[name] if !ok { mod = new(module) mod.exts = make([]string, 0, 8) m, e := xgomod.Load(p.root + name) if e == nil { m.ImportClasses(func(c *xgomod.Project) { mod.exts = append(mod.exts, c.Ext) for _, w := range c.Works { if w.Ext != c.Ext { mod.exts = append(mod.exts, w.Ext) } } }) } mod.exts = append(mod.exts, ".xgo", ".gop", ".go", ".gox", ".gmx") p.mods[name] = mod if debugMod { log.Println("Mod:", name, "Exts:", mod.exts) } } return mod } func (p *Changes) lookupMod(name string) *module { name = strings.TrimSuffix(name, "/") p.mutex.Lock() mod := p.doLookupMod(name) p.mutex.Unlock() return mod } func (p *Changes) deleteMod(dir string) { p.mutex.Lock() delete(p.mods, dir) p.mutex.Unlock() } func (p *Changes) Fetch(fullPath bool) (dir string) { p.mutex.Lock() for len(p.changed) == 0 { p.cond.Wait() } for dir = range p.changed { delete(p.changed, dir) break } p.mutex.Unlock() if fullPath { dir = p.root + dir } return } func (p *Changes) Ignore(name string, isDir bool) bool { dir, fname := path.Split(name) if strings.HasPrefix(fname, "_") { return true } return !isDir && (isHiddenTemp(fname) || isAutogen(fname) || p.lookupMod(dir).ignore(fname)) } func (p *Changes) FileChanged(name string) { dir := path.Dir(name) p.mutex.Lock() n := len(p.changed) p.changed[dir] = none{} p.mutex.Unlock() if n == 0 { p.cond.Broadcast() } } func (p *Changes) EntryDeleted(name string, isDir bool) { if isDir { p.deleteMod(name) } else { p.FileChanged(name) } } func (p *Changes) DirAdded(name string) { dir := p.root + name filepath.WalkDir(dir, func(entry string, d fs.DirEntry, err error) error { if err != nil || d.IsDir() { return err } entry, _ = filepath.Rel(dir, entry) entry = filepath.ToSlash(entry) if !p.Ignore(entry, false) { p.FileChanged(entry) } return nil }) } // pattern: xgo_autogen*.go func isAutogen(fname string) bool { return strings.HasPrefix(fname, "xgo_autogen") && strings.HasSuffix(fname, ".go") } // pattern: .* or *~ func isHiddenTemp(fname string) bool { return strings.HasPrefix(fname, ".") || strings.HasSuffix(fname, "~") } // ----------------------------------------------------------------------------------------- ================================================ FILE: x/watcher/watch.go ================================================ /* * Copyright (c) 2023 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Package watcher monitors code changes in an XGo workspace. package watcher import ( "github.com/goplus/xgo/x/fsnotify" ) var ( debugMod bool ) const ( DbgFlagMod = 1 << iota DbgFlagAll = DbgFlagMod ) func SetDebug(dbgFlags int) { debugMod = (dbgFlags & DbgFlagMod) != 0 } // ----------------------------------------------------------------------------------------- type Runner struct { w fsnotify.Watcher c *Changes } func New(root string) Runner { w := fsnotify.New() c := NewChanges(root) return Runner{w: w, c: c} } func (p Runner) Fetch(fullPath bool) (dir string) { return p.c.Fetch(fullPath) } func (p Runner) Run() error { root := p.c.root root = root[:len(root)-1] return p.w.Run(root, p.c, p.c.Ignore) } func (p *Runner) Close() error { return p.w.Close() } // ----------------------------------------------------------------------------------------- ================================================ FILE: x/xgoenv/env.go ================================================ /* * Copyright (c) 2022 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package xgoenv import ( modenv "github.com/goplus/mod/env" "github.com/goplus/xgo/env" ) func Get() *modenv.XGo { return &modenv.XGo{ Version: env.Version(), BuildDate: env.BuildDate(), Root: env.XGOROOT(), } } ================================================ FILE: x/xgoprojs/proj.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package xgoprojs import ( "errors" "os" "path/filepath" "syscall" ) // ----------------------------------------------------------------------------- // Proj is the interface for a project type Proj = interface { projObj() } // FilesProj represents a project with files type FilesProj struct { Files []string } // PkgPathProj represents a project with a package path type PkgPathProj struct { Path string } // DirProj represents a project with a directory type DirProj struct { Dir string } func (p *FilesProj) projObj() {} func (p *PkgPathProj) projObj() {} func (p *DirProj) projObj() {} // ----------------------------------------------------------------------------- // ParseOne parses the first argument and returns a Proj object // If the first argument is a file, it continues to parse subsequent arguments. func ParseOne(args ...string) (proj Proj, next []string, err error) { if len(args) == 0 { return nil, nil, syscall.ENOENT } arg := args[0] if isFile(arg) { n := 1 for n < len(args) && isFile(args[n]) { n++ } return &FilesProj{Files: args[:n]}, args[n:], nil } else if isLocal(arg) { return &DirProj{Dir: arg}, args[1:], nil } return &PkgPathProj{Path: arg}, args[1:], nil } func isFile(fname string) bool { n := len(filepath.Ext(fname)) if n == 0 { return false } if info, err := os.Stat(fname); err != nil || info.IsDir() { return false } return true } func isLocal(ns string) bool { if len(ns) > 0 { switch c := ns[0]; c { case '/', '\\', '.': return true default: return len(ns) >= 2 && ns[1] == ':' && ('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z') } } return false } // ----------------------------------------------------------------------------- // ParseAll parses all arguments and returns a slice of Proj objects func ParseAll(args ...string) (projs []Proj, err error) { var hasFiles, hasNotFiles bool for { proj, next, e := ParseOne(args...) if e != nil { if hasFiles && hasNotFiles { return nil, ErrMixedFilesProj } return } if _, ok := proj.(*FilesProj); ok { hasFiles = true } else { hasNotFiles = true } projs = append(projs, proj) args = next } } var ( // ErrMixedFilesProj is returned when a project contains both files and non-files ErrMixedFilesProj = errors.New("mixed files project") ) // ----------------------------------------------------------------------------- ================================================ FILE: x/xgoprojs/proj_test.go ================================================ /* * Copyright (c) 2021 The XGo Authors (xgo.dev). All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package xgoprojs import ( "testing" ) // ----------------------------------------------------------------------------- func TestIsLocal(t *testing.T) { if !isLocal(".") || !isLocal("/") { t.Fatal(`isLocal(".") || isLocal("/")`) } if !isLocal("c:/foo") { t.Fatal(`isLocal("c:/foo")`) } if !isLocal("C:/foo") { t.Fatal(`isLocal("C:/foo")`) } if isLocal("") { t.Fatal(`isLocal("")`) } } func TestParseOne(t *testing.T) { proj, next, err := ParseOne("proj.go", "proj_test.go", "abc") if err != nil || len(next) != 1 || next[0] != "abc" { t.Fatal("ParseOne failed:", proj, next, err) } } func TestParseAll_wildcard1(t *testing.T) { projs, err := ParseAll("proj_test.go") if err != nil || len(projs) != 1 { t.Fatal("ParseAll failed:", projs, err) } if proj, ok := projs[0].(*FilesProj); !ok || len(proj.Files) != 1 || proj.Files[0] != "proj_test.go" { t.Fatal("ParseAll failed:", projs) } } func TestParseAll_wildcard2(t *testing.T) { projs, err := ParseAll("../xgoenv/env.go") if err != nil || len(projs) != 1 { t.Fatal("ParseAll failed:", projs, err) } if proj, ok := projs[0].(*FilesProj); !ok || len(proj.Files) != 1 || proj.Files[0] != "../xgoenv/env.go" { t.Fatal("ParseAll failed:", projs) } } func TestParseAll_multiFiles(t *testing.T) { projs, err := ParseAll("proj.go", "proj_test.go") if err != nil || len(projs) != 1 { t.Fatal("ParseAll failed:", projs, err) } if proj, ok := projs[0].(*FilesProj); !ok || len(proj.Files) != 2 || proj.Files[0] != "proj.go" { t.Fatal("ParseAll failed:", proj) } projs[0].projObj() } func TestParseAll_multiProjs(t *testing.T) { projs, err := ParseAll("a/...", "./a/...", "/a") if err != nil || len(projs) != 3 { t.Fatal("ParseAll failed:", projs, err) } if proj, ok := projs[0].(*PkgPathProj); !ok || proj.Path != "a/..." { t.Fatal("ParseAll failed:", proj) } if proj, ok := projs[1].(*DirProj); !ok || proj.Dir != "./a/..." { t.Fatal("ParseAll failed:", proj) } if proj, ok := projs[2].(*DirProj); !ok || proj.Dir != "/a" { t.Fatal("ParseAll failed:", proj) } for _, proj := range projs { proj.projObj() } } func TestParseAllErr(t *testing.T) { _, err := ParseAll("a/...", "./a/...", "/a", "proj_test.go") if err != ErrMixedFilesProj { t.Fatal("ParseAll:", err) } } // -----------------------------------------------------------------------------