Compare commits
No commits in common. "master" and "v0.3.1" have entirely different histories.
|
|
@ -1,17 +0,0 @@
|
|||
# 版权 @2022 凹语言 作者。保留所有权利。
|
||||
|
||||
name: Close PRs
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: superbrothers/close-pull-request@v3
|
||||
with:
|
||||
# Optional. Post a issue comment just before closing a pull request.
|
||||
comment: "这里是镜像仓库, 不接受 PR, 请移步 https://gitee.com/wa-lang"
|
||||
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
name: Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
|
||||
- run: make -C internal/app/wawasm
|
||||
- run: make -C waroot/examples/snake publish
|
||||
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
personal_token: ${{ secrets.WA_DEPLOY }}
|
||||
publish_dir: docs
|
||||
publish_branch: gh-pages
|
||||
force_orphan: true
|
||||
user_name: "github-actions[bot]"
|
||||
user_email: "github-actions[bot]@users.noreply.github.com"
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
# https://github.com/goreleaser/goreleaser-action
|
||||
name: Release
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
Test:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{github.repository == 'wa-lang/wa'}}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
|
||||
- run: make ci-test-all
|
||||
|
||||
Publish:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{github.repository == 'wa-lang/wa'}}
|
||||
needs: [Test]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.WA_DEPLOY }}
|
||||
|
|
@ -23,16 +23,14 @@ jobs:
|
|||
- run: go version
|
||||
- run: go env
|
||||
|
||||
- run: make ci-test-all
|
||||
|
||||
- run: go test -v -coverprofile=profile.cov ./...
|
||||
|
||||
- run: go install
|
||||
|
||||
- run: wa -h
|
||||
- run: cd waroot && wa run examples/prime
|
||||
- run: cd waroot && wa run hello.wa
|
||||
- run: cd waroot && wa run -target=chrome hello.wa
|
||||
- run: wa hello.wa
|
||||
- run: wa run _examples/prime
|
||||
- run: wa run hello.wa
|
||||
|
||||
- uses: shogo82148/actions-goveralls@v1
|
||||
with:
|
||||
|
|
@ -52,16 +50,14 @@ jobs:
|
|||
- run: go version
|
||||
- run: go env
|
||||
|
||||
- run: make ci-test-all
|
||||
|
||||
- run: go test -v ./...
|
||||
|
||||
- run: go install
|
||||
|
||||
- run: wa -h
|
||||
- run: cd waroot && wa run examples/prime
|
||||
- run: cd waroot && wa run hello.wa
|
||||
- run: cd waroot && wa run -target=chrome hello.wa
|
||||
- run: wa hello.wa
|
||||
- run: wa run _examples/prime
|
||||
- run: wa run hello.wa
|
||||
|
||||
build-and-test-macos:
|
||||
runs-on: macos-12
|
||||
|
|
@ -77,13 +73,11 @@ jobs:
|
|||
- run: go version
|
||||
- run: go env
|
||||
|
||||
- run: make ci-test-all
|
||||
|
||||
- run: go test -v ./...
|
||||
|
||||
- run: go install
|
||||
|
||||
- run: wa -h
|
||||
- run: cd waroot && wa run examples/prime
|
||||
- run: cd waroot && wa run hello.wa
|
||||
- run: cd waroot && wa run -target=chrome hello.wa
|
||||
- run: wa hello.wa
|
||||
- run: wa run _examples/prime
|
||||
- run: wa run hello.wa
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
/.fleet
|
||||
/.vscode
|
||||
/_build*
|
||||
/hello
|
||||
|
||||
wa
|
||||
wa.exe
|
||||
|
|
@ -16,10 +14,5 @@ wat2wasm.exe
|
|||
*.ll
|
||||
*.s
|
||||
*.out*
|
||||
*.wasm
|
||||
*.wasm.*
|
||||
y.wa.*
|
||||
|
||||
/docs/snake/*
|
||||
|
||||
_target
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
# This is an example .goreleaser.yml file with some sane defaults.
|
||||
# Make sure to check the documentation at http://goreleaser.com
|
||||
before:
|
||||
hooks:
|
||||
- go version
|
||||
builds:
|
||||
- id: default
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm64
|
||||
# 使用 ldflags="-s -w" 去掉符号表和调试信息,以减少发布包的大小
|
||||
ldflags:
|
||||
- -s
|
||||
- -w
|
||||
ignore:
|
||||
- goos: linux
|
||||
goarch: arm64
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
main: .
|
||||
archives:
|
||||
- id: default
|
||||
builds:
|
||||
- default
|
||||
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
changelog:
|
||||
use: github
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
|
||||
release:
|
||||
github:
|
||||
owner: wa-lang
|
||||
name: wa
|
||||
draft: false
|
||||
footer: |
|
||||
Homepage https://wa-lang.org
|
||||
|
||||
brews:
|
||||
- tap:
|
||||
owner: wa-lang
|
||||
name: homebrew-tap
|
||||
url_template: "https://github.com/wa-lang/wa/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
|
||||
download_strategy: CurlDownloadStrategy
|
||||
# Git author used to commit to the repository.
|
||||
# Defaults are shown.
|
||||
commit_author:
|
||||
name: GoReleaser Bot
|
||||
email: goreleaser@carlosbecker.com
|
||||
folder: HomebrewFormula
|
||||
homepage: "https://github.com/wa-lang/wa"
|
||||
description: "The Wa Programming Language"
|
||||
license: "AGPL-3.0 license"
|
||||
skip_upload: false
|
||||
test: |
|
||||
system "#{bin}/wa"
|
||||
|
||||
scoop:
|
||||
url_template: "https://github.com/wa-lang/wa/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
|
||||
bucket:
|
||||
owner: wa-lang
|
||||
name: scoop-bucket
|
||||
branch: master
|
||||
token: "{{ .Env.GITHUB_TOKEN }}"
|
||||
folder: Scoops
|
||||
commit_author:
|
||||
name: goreleaserbot
|
||||
email: bot@goreleaser.com
|
||||
commit_msg_template: "Scoop update for {{ .ProjectName }} version {{ .Tag }}"
|
||||
homepage: "http://github.com/wa-lang/wa"
|
||||
description: "The Wa Programming Language"
|
||||
license: AGPL-3.0 license
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
version: '1.0'
|
||||
name: branch-pipeline
|
||||
displayName: BranchPipeline
|
||||
stages:
|
||||
- stage:
|
||||
name: compile
|
||||
displayName: 编译
|
||||
steps:
|
||||
- step: build@golang
|
||||
name: build_golang
|
||||
displayName: Golang 构建
|
||||
# 支持1.8、1.10、1.11、1.12、1.13、1.14、1.15、1.16八个版本
|
||||
golangVersion: 1.17
|
||||
# 构建命令
|
||||
commands: |
|
||||
go env -w GO111MODULE=on
|
||||
go env -w GOPROXY=https://goproxy.cn,direct
|
||||
|
||||
go test -v ./...
|
||||
|
||||
mkdir -p output/wa-lang-darwin
|
||||
mkdir -p output/wa-lang-linux64
|
||||
mkdir -p output/wa-lang-win64
|
||||
|
||||
GOOS=darwin GOARCH=amd64 go build -o output/wa-lang-darwin/wa main.go
|
||||
GOOS=linux GOARCH=amd64 go build -o output/wa-lang-linux64/wa main.go
|
||||
GOOS=windows GOARCH=amd64 go build -o output/wa-lang-win64/wa.exe main.go
|
||||
|
||||
chmod +X output/wa-lang-linux64/wa
|
||||
./output/wa-lang-linux64/wa
|
||||
./output/wa-lang-linux64/wa -h
|
||||
#./output/wa-lang-linux64/wa waroot/hello.wa
|
||||
#./output/wa-lang-linux64/wa run waroot/examples/prime
|
||||
#./output/wa-lang-linux64/wa run waroot/hello.wa
|
||||
|
||||
# 非必填字段,开启后表示将构建产物暂存,但不会上传到制品库中,7天后自动清除
|
||||
artifacts:
|
||||
# 构建产物名字,作为产物的唯一标识可向下传递,支持自定义,默认为BUILD_ARTIFACT。在下游可以通过${BUILD_ARTIFACT}方式引用来获取构建物地址
|
||||
- name: BUILD_ARTIFACT
|
||||
# 构建产物获取路径,是指代码编译完毕之后构建物的所在路径
|
||||
path:
|
||||
- ./output
|
||||
- step: publish@general_artifacts
|
||||
name: publish_general_artifacts
|
||||
displayName: 上传制品
|
||||
# 上游构建任务定义的产物名,默认BUILD_ARTIFACT
|
||||
dependArtifact: BUILD_ARTIFACT
|
||||
# 上传到制品库时的制品命名,默认output
|
||||
artifactName: output
|
||||
dependsOn: build_golang
|
||||
- stage:
|
||||
name: release
|
||||
displayName: 发布
|
||||
steps:
|
||||
- step: publish@release_artifacts
|
||||
name: publish_release_artifacts
|
||||
displayName: '发布'
|
||||
# 上游上传制品任务的产出
|
||||
dependArtifact: output
|
||||
# 发布制品版本号
|
||||
version: '1.0.0.0'
|
||||
# 是否开启版本号自增,默认开启
|
||||
autoIncrement: true
|
||||
triggers:
|
||||
push:
|
||||
branches:
|
||||
exclude:
|
||||
- master
|
||||
include:
|
||||
- .*
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
version: '1.0'
|
||||
name: master-pipeline
|
||||
displayName: MasterPipeline
|
||||
stages:
|
||||
- stage:
|
||||
name: compile
|
||||
displayName: 编译
|
||||
steps:
|
||||
- step: build@golang
|
||||
name: build_golang
|
||||
displayName: Golang 构建
|
||||
# 支持1.8、1.10、1.11、1.12、1.13、1.14、1.15、1.16八个版本
|
||||
golangVersion: 1.17
|
||||
# 构建命令
|
||||
commands: |
|
||||
go env -w GO111MODULE=on
|
||||
go env -w GOPROXY=https://goproxy.cn,
|
||||
|
||||
go test -v ./...
|
||||
|
||||
mkdir -p output/wa-lang-darwin
|
||||
mkdir -p output/wa-lang-linux64
|
||||
mkdir -p output/wa-lang-win64
|
||||
|
||||
GOOS=darwin GOARCH=amd64 go build -o output/wa-lang-darwin/wa main.go
|
||||
GOOS=linux GOARCH=amd64 go build -o output/wa-lang-linux64/wa main.go
|
||||
GOOS=windows GOARCH=amd64 go build -o output/wa-lang-win64/wa.exe main.go
|
||||
|
||||
chmod +X output/wa-lang-linux64/wa
|
||||
./output/wa-lang-linux64/wa
|
||||
./output/wa-lang-linux64/wa -h
|
||||
#./output/wa-lang-linux64/wa waroot/hello.wa
|
||||
#./output/wa-lang-linux64/wa run waroot/examples/prime
|
||||
#./output/wa-lang-linux64/wa run waroot/hello.wa
|
||||
|
||||
# 非必填字段,开启后表示将构建产物暂存,但不会上传到制品库中,24小时后自动清除
|
||||
artifacts:
|
||||
# 构建产物名字,作为产物的唯一标识可向下传递,支持自定义,默认为BUILD_ARTIFACT。在下游可以通过${BUILD_ARTIFACT}方式引用来获取构建物地址
|
||||
- name: BUILD_ARTIFACT
|
||||
# 构建产物获取路径,是指代码编译完毕之后构建物的所在路径
|
||||
path:
|
||||
- ./output
|
||||
- step: publish@general_artifacts
|
||||
name: publish_general_artifacts
|
||||
displayName: 上传制品
|
||||
# 上游构建任务定义的产物名,默认BUILD_ARTIFACT
|
||||
dependArtifact: BUILD_ARTIFACT
|
||||
# 上传到制品库时的制品命名,默认output
|
||||
artifactName: output
|
||||
dependsOn: build_golang
|
||||
- stage:
|
||||
name: release
|
||||
displayName: 发布
|
||||
steps:
|
||||
- step: publish@release_artifacts
|
||||
name: publish_release_artifacts
|
||||
displayName: '发布'
|
||||
# 上游上传制品任务的产出
|
||||
dependArtifact: output
|
||||
# 发布制品版本号
|
||||
version: '1.0.0.0'
|
||||
# 是否开启版本号自增,默认开启
|
||||
autoIncrement: true
|
||||
triggers:
|
||||
push:
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
version: '1.0'
|
||||
name: pr-pipeline
|
||||
displayName: PRPipeline
|
||||
stages:
|
||||
- stage:
|
||||
name: compile
|
||||
displayName: 编译
|
||||
steps:
|
||||
- step: build@golang
|
||||
name: build_golang
|
||||
displayName: Golang 构建
|
||||
# 支持1.8、1.10、1.11、1.12、1.13、1.14、1.15、1.16八个版本
|
||||
golangVersion: 1.17
|
||||
# 构建命令
|
||||
commands: |
|
||||
go env -w GO111MODULE=on
|
||||
go env -w GOPROXY=https://goproxy.cn,
|
||||
|
||||
go test -v ./...
|
||||
|
||||
mkdir -p output/wa-lang-darwin
|
||||
mkdir -p output/wa-lang-linux64
|
||||
mkdir -p output/wa-lang-win64
|
||||
|
||||
GOOS=darwin GOARCH=amd64 go build -o output/wa-lang-darwin/wa main.go
|
||||
GOOS=linux GOARCH=amd64 go build -o output/wa-lang-linux64/wa main.go
|
||||
GOOS=windows GOARCH=amd64 go build -o output/wa-lang-win64/wa.exe main.go
|
||||
|
||||
chmod +X output/wa-lang-linux64/wa
|
||||
./output/wa-lang-linux64/wa
|
||||
./output/wa-lang-linux64/wa -h
|
||||
#./output/wa-lang-linux64/wa waroot/hello.wa
|
||||
#./output/wa-lang-linux64/wa run waroot/examples/prime
|
||||
#./output/wa-lang-linux64/wa run waroot/hello.wa
|
||||
|
||||
# 非必填字段,开启后表示将构建产物暂存,但不会上传到制品库中,7天后自动清除
|
||||
artifacts:
|
||||
# 构建产物名字,作为产物的唯一标识可向下传递,支持自定义,默认为BUILD_ARTIFACT。在下游可以通过${BUILD_ARTIFACT}方式引用来获取构建物地址
|
||||
- name: BUILD_ARTIFACT
|
||||
# 构建产物获取路径,是指代码编译完毕之后构建物的所在路径
|
||||
path:
|
||||
- ./output
|
||||
- step: publish@general_artifacts
|
||||
name: publish_general_artifacts
|
||||
displayName: 上传制品
|
||||
# 上游构建任务定义的产物名,默认BUILD_ARTIFACT
|
||||
dependArtifact: BUILD_ARTIFACT
|
||||
# 上传到制品库时的制品命名,默认output
|
||||
artifactName: output
|
||||
dependsOn: build_golang
|
||||
triggers:
|
||||
pr:
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
28
Makefile
|
|
@ -1,35 +1,15 @@
|
|||
# 版权 @2019 凹语言 作者。保留所有权利。
|
||||
|
||||
hello:
|
||||
go install
|
||||
cd waroot && go run ../main.go run hello.wa
|
||||
go run main.go run hello.wa
|
||||
|
||||
prime:
|
||||
cd waroot && go run ../main.go run examples/prime
|
||||
go run main.go run _examples/prime
|
||||
|
||||
build-wasm:
|
||||
GOARCH=wasm GOOS=js go build -o wa.out.wasm ./main_wasm.go
|
||||
|
||||
arduino-run:
|
||||
go run main.go -target=arduino arduino.wa
|
||||
|
||||
arduino-build:
|
||||
go run main.go build -target=arduino arduino.wa
|
||||
wat2wasm a.out.wat -o a.out.wasm
|
||||
xxd -i a.out.wasm > app.wasm.h
|
||||
|
||||
ci-test-all:
|
||||
go install
|
||||
go test ./...
|
||||
|
||||
@echo "== std test begin =="
|
||||
go run main.go test std
|
||||
@echo "== std ok =="
|
||||
|
||||
go run main.go run ./waroot/hello.wa
|
||||
cd waroot && go run ../main.go run hello.wa
|
||||
|
||||
make -C ./waroot/examples ci-test-all
|
||||
win-exe-icon:
|
||||
windres -o main_rc_windows.syso main.rc
|
||||
|
||||
clean:
|
||||
-rm a.out*
|
||||
|
|
|
|||
151
README-zh.md
|
|
@ -1,151 +0,0 @@
|
|||
<div align="center">
|
||||
<h1>凹语言</h1>
|
||||
|
||||
[主页](https://wa-lang.org) | [Playground](https://wa-lang.org/playground) | [路线](https://wa-lang.org/smalltalk/st0001.html) | [社区](https://wa-lang.org/community) | [日志](https://wa-lang.org/guide/changelog.html)
|
||||
|
||||
</div>
|
||||
|
||||
凹语言(凹读音“Wā”)是 针对 WASM 平台设计的通用编程语言,同时支持 Linux、macOS 和 Windows 等主流操作系统和 Chrome 等浏览器环境,同时也支持作为独立 Shell 脚本和被嵌入脚本模式执行。
|
||||
|
||||

|
||||
|
||||
- 主页: [https://wa-lang.org](https://wa-lang.org)
|
||||
- 参考手册: [https://wa-lang.org/man/](https://wa-lang.org/man/)
|
||||
- 仓库(Gitee): [https://gitee.com/wa-lang/wa](https://gitee.com/wa-lang/wa)
|
||||
- 仓库(Github): [https://github.com/wa-lang/wa](https://github.com/wa-lang/wa)
|
||||
- Playground: [https://wa-lang.org/playground](https://wa-lang.org/playground)
|
||||
|
||||
> 说明: 除非特别声明,凹语言代码均以 AGPL-v3 开源协议授权, 具体可以参考 LICENSE 文件。
|
||||
|
||||
## 如何参与开发
|
||||
|
||||
项目尚处于原型开源阶段,如果有共建和PR需求请参考 [如何贡献代码](https://wa-lang.org/community/contribute.html)。我们不再接受针对第三方依赖库修改的 PR。
|
||||
|
||||
> 特别注意:向本仓库提交PR视同您认可并接受[凹语言贡献者协议](https://gitee.com/organizations/wa-lang/cla/wca),但在实际签署之前,您的PR不会被评审或接受。
|
||||
|
||||
|
||||
## Playground 在线预览
|
||||
|
||||
[https://wa-lang.org/playground](https://wa-lang.org/playground)
|
||||
|
||||

|
||||
|
||||
## 贪吃蛇游戏
|
||||
|
||||
- [https://wa-lang.org/wa/snake/](https://wa-lang.org/wa/snake/)
|
||||
- [https://wa-lang.org/smalltalk/st0018.html](https://wa-lang.org/smalltalk/st0018.html)
|
||||
|
||||

|
||||
|
||||
## 例子: 凹语言
|
||||
|
||||
打印字符和调用函数:
|
||||
|
||||
```wa
|
||||
import "fmt"
|
||||
|
||||
global year: i32 = 2023
|
||||
|
||||
func main {
|
||||
println("hello, Wa!")
|
||||
println(add(40, 2), year)
|
||||
|
||||
fmt.Println(1+1)
|
||||
}
|
||||
|
||||
func add(a: i32, b: i32) => i32 {
|
||||
return a+b
|
||||
}
|
||||
```
|
||||
|
||||
运行并输出结果:
|
||||
|
||||
```
|
||||
$ wa run hello.wa
|
||||
你好,凹语言!
|
||||
42 2023
|
||||
2
|
||||
```
|
||||
|
||||
## 例子: 打印素数
|
||||
|
||||
打印 30 以内的素数:
|
||||
|
||||
```wa
|
||||
// 版权 @2021 凹语言™ 作者。保留所有权利。
|
||||
|
||||
func main {
|
||||
for n := 2; n <= 30; n = n + 1 {
|
||||
isPrime: int = 1
|
||||
for i := 2; i*i <= n; i = i + 1 {
|
||||
if x := n % i; x == 0 {
|
||||
isPrime = 0
|
||||
}
|
||||
}
|
||||
if isPrime != 0 {
|
||||
println(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
运行并输出结果:
|
||||
|
||||
```
|
||||
$ cd waroot && wa run examples/prime
|
||||
2
|
||||
3
|
||||
5
|
||||
7
|
||||
11
|
||||
13
|
||||
17
|
||||
19
|
||||
23
|
||||
29
|
||||
```
|
||||
|
||||
## 例子:用中文语法打印素数
|
||||
|
||||
```wz
|
||||
引于 "书"
|
||||
|
||||
【启】:
|
||||
// 打印30以内的素数
|
||||
从n=2,到n>30,有n++:
|
||||
设素=1
|
||||
从i=2,到i*i>n,有i++:
|
||||
设x=n%i
|
||||
若x==0则:
|
||||
素=0
|
||||
。
|
||||
。
|
||||
若素!=0则:
|
||||
书·曰:n
|
||||
。
|
||||
。
|
||||
。
|
||||
```
|
||||
|
||||
运行的结果和英文语法的示例相同。
|
||||
|
||||
更多例子 [waroot/examples](waroot/examples)
|
||||
|
||||
## 贡献者名单
|
||||
|
||||
|贡献者|贡献点|
|
||||
| --- | --- |
|
||||
|柴树杉| 35000|
|
||||
|丁尔男| 42500|
|
||||
|史斌 | 29000|
|
||||
|扈梦明| 14000|
|
||||
|赵普明| 17000|
|
||||
|宋汝阳| 2000|
|
||||
|刘云峰| 1000|
|
||||
|王湘南| 1000|
|
||||
|王泽龙| 1000|
|
||||
|吴烜 | 3000|
|
||||
|刘斌 | 2500|
|
||||
|尹贻浩| 2000|
|
||||
|
||||
贡献点变更记录见 [waroot/cplog](waroot/cplog) 目录。
|
||||
133
README.md
|
|
@ -1,82 +1,92 @@
|
|||
<div align="center">
|
||||
<h1>The Wa Programming Language</h1>
|
||||
<h1>🇨🇳 凹语言™ The Wa Programming Language</h1>
|
||||
|
||||
[简体中文](https://github.com/wa-lang/wa/blob/master/README-zh.md) | [English](https://github.com/wa-lang/wa/blob/master/README.md)
|
||||
[主页](https://wa-lang.org) | [Playground](https://wa-lang.org/playground) | [目标](https://wa-lang.org/goals.html) | [路线](https://wa-lang.org/smalltalk/st0002.html) | [社区](https://wa-lang.org/community) | [日志](https://wa-lang.org/changelog.html) | [论坛](https://github.com/wa-lang/wa/discussions)
|
||||
|
||||
[Document](https://wa-lang.org) | [Playground](https://wa-lang.org/playground) | [Goals](https://wa-lang.org/goals.html) | [Roadmap](https://wa-lang.org/smalltalk/st0002.html) | [Community](https://wa-lang.org/community) | [Changelog](https://wa-lang.org/changelog.html) | [Discussions](https://github.com/wa-lang/wa/discussions)
|
||||
|
||||
|
||||
</div>
|
||||
<div align="center">
|
||||
|
||||
[](https://github.com/wa-lang/wa/actions/workflows/wa.yml)
|
||||
[](https://goreportcard.com/report/github.com/wa-lang/wa)
|
||||
[](https://coveralls.io/github/wa-lang/wa)
|
||||
[](https://github.com/wa-lang/wa/releases)
|
||||
[](https://pkg.go.dev/github.com/wa-lang/wa)
|
||||
[](https://github.com/wa-lang/wa/blob/master/LICENSE)
|
||||
|
||||
</div>
|
||||
|
||||
凹语言™(凹读音“Wa”)是 针对 WASM 平台设计的通用编程语言,同时支持 Linux、macOS 和 Windows 等主流操作系统和 Chrome 等浏览器环境,同时也支持作为独立Shell脚本和被嵌入脚本模式执行。
|
||||
|
||||
Wa is a general-purpose programming language designed for developing robustness and maintainability WebAssembly software.
|
||||
Instead of requiring complex toolchains to set up, you can simply go install it - or run it in a browser.
|
||||
|
||||

|
||||

|
||||
|
||||
- Home: [https://wa-lang.github.io/](https://wa-lang.github.io/)
|
||||
- Manual: [https://wa-lang.github.io/man/en/](https://wa-lang.github.io/man/en/)
|
||||
- Github: [https://github.com/wa-lang/wa](https://github.com/wa-lang/wa)
|
||||
- Playground: [https://wa-lang.org/playground](https://wa-lang.org/playground)
|
||||
- 主页 (Homepage): [https://wa-lang.org](https://wa-lang.org)
|
||||
- 代码仓库 (Github): [https://github.com/wa-lang/wa](https://github.com/wa-lang/wa)
|
||||
- 代码仓库 (Gitee): [https://gitee.com/wa-lang/wa](https://gitee.com/wa-lang/wa)
|
||||
- Go 参考文档: [https://pkg.go.dev/github.com/wa-lang/wa](https://pkg.go.dev/github.com/wa-lang/wa)
|
||||
- 开发工具 (Develop Tools): [Playground](https://wa-lang.org/playground), [VSCode 插件](https://marketplace.visualstudio.com/items?itemName=xxxDeveloper.vscode-wa), [Fleet 插件](https://github.com/wa-lang/fleet-wa), [Vim 插件](https://github.com/wa-lang/vim-wa)
|
||||
- 开发组 (Core Team): [柴树杉(chai2010)](https://github.com/chai2010)、[丁尔男(Ending)](https://github.com/3dgen)、[史斌(Benshi)](https://github.com/benshi001)、[扈梦明(xxxDeveloper)](https://github.com/xxxDeveloper)、[刘云峰(leaftree)](https://github.com/leaftree)、[宋汝阳(ShiinaOrez)](https://github.com/ShiinaOrez)
|
||||
|
||||
> Note: Our canonical Git repository is located at https://gitee.com/wa-lang/wa. There is a mirror of the repository at https://github.com/wa-lang/wa. Unless otherwise noted, the Wa source files are distributed under the AGPL-v3 license found in the LICENSE file.
|
||||
|
||||
## Playground
|
||||
## Playground 在线预览
|
||||
|
||||
[https://wa-lang.org/playground](https://wa-lang.org/playground)
|
||||
|
||||

|
||||
](https://wa-lang.org/playground)](https://wa-lang.org/static/images/playground.gif)
|
||||
|
||||
## Snake Game
|
||||
|
||||
- Play: [https://wa-lang.org/wa/snake/](https://wa-lang.org/wa/snake/)
|
||||
- Code: [waroot/examples/snake/README-en.md](waroot/examples/snake/README-en.md)
|
||||
## 本地安装和测试 (Install and Run):
|
||||
|
||||

|
||||
1. `go install github.com/wa-lang/wa@latest`
|
||||
2. `wa init -name=_examples/hi`
|
||||
3. `wa run _examples/hi`
|
||||
|
||||
## Example: Print Wa
|
||||
> 项目尚处于原型开源阶段,如果有共建和PR需求请参考 [如何贡献代码](https://wa-lang.org/community/contribute.html)。
|
||||
|
||||
Print rune and call function:
|
||||
> The Wa project is still in very early stage. If you want to submit PR, please read the [Contribution Guide(Chinese)](https://wa-lang.org/community/contribute.html).
|
||||
|
||||
## 例子: 凹语言 (Example: Print 凹语言)
|
||||
|
||||
打印字符和调用函数(Print rune and call function):
|
||||
|
||||
```wa
|
||||
import "fmt"
|
||||
|
||||
global year: i32 = 2023
|
||||
|
||||
func main {
|
||||
println("hello, Wa!")
|
||||
println(add(40, 2), year)
|
||||
fn main {
|
||||
println("你好,凹语言!")
|
||||
println(add(40, 2))
|
||||
|
||||
fmt.Println(1+1)
|
||||
}
|
||||
|
||||
func add(a: i32, b: i32) => i32 {
|
||||
fn add(a: i32, b: i32) => i32 {
|
||||
return a+b
|
||||
}
|
||||
```
|
||||
|
||||
Execute the program:
|
||||
运行并输出结果 (Execute the program):
|
||||
|
||||
```
|
||||
$ wa run hello.wa
|
||||
hello, Wa!
|
||||
42 2023
|
||||
$ go run main.go hello.wa
|
||||
你好,凹语言!
|
||||
42
|
||||
2
|
||||
```
|
||||
|
||||
## Example: Print Prime
|
||||
## 例子: 打印素数 (Example: Print Prime)
|
||||
|
||||
Print prime numbers up to 30:
|
||||
打印 30 以内的素数 (Print prime numbers up to 30):
|
||||
|
||||
```wa
|
||||
func main {
|
||||
```
|
||||
# 版权 @2021 凹语言™ 作者。保留所有权利。
|
||||
|
||||
fn main {
|
||||
for n := 2; n <= 30; n = n + 1 {
|
||||
isPrime: int = 1
|
||||
var isPrime int = 1
|
||||
for i := 2; i*i <= n; i = i + 1 {
|
||||
if x := n % i; x == 0 {
|
||||
isPrime = 0
|
||||
|
|
@ -89,10 +99,10 @@ func main {
|
|||
}
|
||||
```
|
||||
|
||||
Execute the program:
|
||||
运行并输出结果 (Execute the program):
|
||||
|
||||
```
|
||||
$ cd waroot && wa run examples/prime
|
||||
$ go run main.go run _examples/prime
|
||||
2
|
||||
3
|
||||
5
|
||||
|
|
@ -105,47 +115,28 @@ $ cd waroot && wa run examples/prime
|
|||
29
|
||||
```
|
||||
|
||||
## Example: Print Prime with Chinese syntax
|
||||
更多例子 (More examples) [_examples](_examples)
|
||||
|
||||
Print prime numbers up to 30:
|
||||
## 作为脚本执行 (Execut as a script)
|
||||
|
||||
```wz
|
||||
引于 "书"
|
||||
凹语言本身也可以像 Lua 语言被嵌入 Go 宿主语言环境执行 (The Wa language itself can also be executed like the Lua language embedded in the Go host locale):
|
||||
|
||||
【启】:
|
||||
// 输出30以内的素数
|
||||
从n=2,到n>30,有n++:
|
||||
设素=1
|
||||
从i=2,到i*i>n,有i++:
|
||||
设x=n%i
|
||||
若x==0则:
|
||||
素=0
|
||||
。
|
||||
。
|
||||
若素!=0则:
|
||||
书·曰:n
|
||||
。
|
||||
。
|
||||
。
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/wa-lang/wa/api"
|
||||
)
|
||||
|
||||
func main() {
|
||||
output, err := api.RunCode("hello.wa", "fn main() { println(40+2) }")
|
||||
fmt.Print(string(output), err)
|
||||
}
|
||||
```
|
||||
|
||||
Output is the same as the previous example.
|
||||
注:作为脚本执行目前只支持本地环境。(Note: Executing as a script currently only supports native environments.)
|
||||
|
||||
More examples [waroot/examples](waroot/examples)
|
||||
## 版权(License)
|
||||
|
||||
## Contributors
|
||||
|
||||
|Contributor|Contribution points|
|
||||
| --- | --- |
|
||||
|柴树杉| 35000|
|
||||
|丁尔男| 42500|
|
||||
|史斌 | 29000|
|
||||
|扈梦明| 14000|
|
||||
|赵普明| 17000|
|
||||
|宋汝阳| 2000|
|
||||
|刘云峰| 1000|
|
||||
|王湘南| 1000|
|
||||
|王泽龙| 1000|
|
||||
|吴烜 | 3000|
|
||||
|刘斌 | 2500|
|
||||
|尹贻浩| 2000|
|
||||
版权 @2019-2022 凹语言™ 作者。保留所有权利。(Copyrighe @2019-2022 The Wa author. All rights reserved.)
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
版权 @2022 _examples/hello 作者。保留所有权利。
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Pkg: hello
|
||||
# Pkg: _examples/hello
|
||||
|
||||
欢迎使用 Wa 语言!
|
||||
|
||||
|
|
@ -1,27 +1,19 @@
|
|||
// 版权 @2022 hello 作者。保留所有权利。
|
||||
|
||||
import "fmt"
|
||||
import "3rdparty/pkg"
|
||||
import "myapp/mymath"
|
||||
|
||||
func main {
|
||||
fmt.Println(40 + 2)
|
||||
pkg.Println(100 + 2)
|
||||
println(mymath.I8Max)
|
||||
# 版权 @2022 _examples/hello 作者。保留所有权利。
|
||||
|
||||
fn main() {
|
||||
println(sum(100))
|
||||
heart()
|
||||
}
|
||||
|
||||
func sum(n: int) => int {
|
||||
v: int
|
||||
fn sum(n: int) => int {
|
||||
var v: int
|
||||
for i := 1; i <= n; i++ {
|
||||
v += i
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func heart {
|
||||
fn heart() {
|
||||
a := 0.0
|
||||
for y := 1.5; y > -1.5; y = y - 0.1 {
|
||||
for x := -1.5; x < 1.5; x = x + 0.05 {
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
// Test the llvm backend.
|
||||
// 版权 @2021 凹语言 作者。保留所有权利。
|
||||
|
||||
func main {
|
||||
for n := 2; n <= 30; n = n + 1 {
|
||||
isPrime: int = 1
|
||||
fn PrintPrime(max int) {
|
||||
for n := 2; n <= max; n = n + 1 {
|
||||
isPrime := 1
|
||||
for i := 2; i*i <= n; i = i + 1 {
|
||||
if x := n % i; x == 0 {
|
||||
isPrime = 0
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
// 版权 @2022 _examples/hello 作者。保留所有权利。
|
||||
|
||||
fn Println(x int) {
|
||||
println(x)
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
// 版权 @2022 _examples/hello 作者。保留所有权利。
|
||||
|
||||
fn Println(x int) {
|
||||
println(x)
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "_examples/hello",
|
||||
"pkgpath": "myapp",
|
||||
"version": "0.0.1",
|
||||
"authors": ["author", "author2"],
|
||||
"description": "module description",
|
||||
"readme": "README.md",
|
||||
"homepage": "",
|
||||
"repository": "",
|
||||
"license": "",
|
||||
"license_file": "LICENSE",
|
||||
"keywords": [],
|
||||
"categories": []
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Test the llvm backend.
|
||||
// Test read/write arrays.
|
||||
|
||||
fn main() {
|
||||
test_array(2, 4)
|
||||
test_array(7, 9)
|
||||
}
|
||||
|
||||
fn test_array(a int, b int) {
|
||||
var arr [10][10]int
|
||||
for i := 0; i < 10; i++ {
|
||||
for j := 0; j < 10; j++ {
|
||||
arr[i][j] = i * j
|
||||
}
|
||||
}
|
||||
|
||||
println("arr[", a, "][", b, "] = ", arr[a][b])
|
||||
println("arr[", b, "][", a, "] = ", arr[b][a])
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// Test the llvm backend.
|
||||
// Test passing array type parameters.
|
||||
|
||||
const size = 32
|
||||
|
||||
type fff32 f32
|
||||
|
||||
var arr0 [size]fff32
|
||||
var arr1 [size]fff32
|
||||
|
||||
fn test_init() {
|
||||
for i := 0; i < size; i++ {
|
||||
arr0[i] = fff32(i * i)
|
||||
}
|
||||
}
|
||||
|
||||
fn getarr0a(arr [size]fff32, i int) fff32 {
|
||||
return arr[i]
|
||||
}
|
||||
|
||||
fn getarr0b(arr *[size]fff32, i int) fff32 {
|
||||
return arr[i]
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_init()
|
||||
for i := 6; i < 20; i += 2 {
|
||||
println(getarr0a(arr0, i), ", ", getarr0b(&arr0, i + 1))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// Test the llvm backend.
|
||||
// Test return array type values.
|
||||
|
||||
const size = 32
|
||||
|
||||
type fff32 f32
|
||||
|
||||
var arr1 [size]fff32
|
||||
|
||||
fn makearr0a(i int) [size]fff32 {
|
||||
var r [size]fff32
|
||||
for j := 0; j < size; j++ {
|
||||
r[j] = fff32(i * j)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
fn makearr0b(i int) *[size]fff32 {
|
||||
for j := 0; j < size; j++ {
|
||||
arr1[j] = 2000 - fff32(i * j)
|
||||
}
|
||||
return &arr1
|
||||
}
|
||||
|
||||
fn main() {
|
||||
r0, r1 := makearr0a(10), makearr0b(20)
|
||||
for i := 6; i < 20; i += 2 {
|
||||
println(r0[i], ", ", r1[i])
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Test the llvm backend.
|
||||
// Test comparasions.
|
||||
|
||||
fn main() {
|
||||
test_cmp0(20, 19)
|
||||
test_cmp0(20, 20)
|
||||
test_cmp1(-3, -3)
|
||||
test_cmp1(-3, -4)
|
||||
test_cmp2(10, 10)
|
||||
test_cmp2(10, 10.0001)
|
||||
}
|
||||
|
||||
fn test_cmp0(a u16, b u16) {
|
||||
if a > b {
|
||||
println(a, " > ", b)
|
||||
} else {
|
||||
println(a, " <= ", b)
|
||||
}
|
||||
}
|
||||
|
||||
fn test_cmp1(a i64, b i64) {
|
||||
if a <= b {
|
||||
println(a, " <= ", b)
|
||||
} else {
|
||||
println(a, " > ", b)
|
||||
}
|
||||
}
|
||||
|
||||
fn test_cmp2(a f64, b f64) {
|
||||
if a != b {
|
||||
println(a, " != ", b)
|
||||
} else {
|
||||
println(a, " == ", b)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// Test the llvm backend.
|
||||
// Test integer -> integer convertion.
|
||||
|
||||
fn main() {
|
||||
println(i32(convert_i32_to_i16(65537)))
|
||||
println(u32(convert_i32_to_u16(65537)))
|
||||
println(convert_i16_to_i32(100))
|
||||
println(convert_i16_to_i32(-100))
|
||||
println(convert_u16_to_i32(100))
|
||||
println(convert_u16_to_i32(65530))
|
||||
}
|
||||
|
||||
fn convert_i32_to_i16(a i32) i16 {
|
||||
return i16(a)
|
||||
}
|
||||
|
||||
fn convert_i32_to_u16(a i32) u16 {
|
||||
return u16(a)
|
||||
}
|
||||
|
||||
fn convert_i16_to_i32(a i16) i32 {
|
||||
return i32(a)
|
||||
}
|
||||
|
||||
fn convert_u16_to_i32(a u16) i32 {
|
||||
return i32(a)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// Test the llvm backend.
|
||||
// Test 'floating point' -> 'floating point' convertion.
|
||||
|
||||
fn convert_f64_to_f32(a f64) f32 {
|
||||
return f32(a)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println(convert_f64_to_f32(3.1415926535))
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// Test the llvm backend.
|
||||
// Test integer -> 'floating point' convertion.
|
||||
|
||||
fn main() {
|
||||
println(convert_i16_to_f64(100))
|
||||
println(convert_i16_to_f64(-100))
|
||||
println(convert_u16_to_f64(100))
|
||||
println(convert_u16_to_f64(65530))
|
||||
}
|
||||
|
||||
fn convert_i16_to_f64(a i16) f64 {
|
||||
return f64(a)
|
||||
}
|
||||
|
||||
fn convert_u16_to_f64(a u16) f64 {
|
||||
return f64(a)
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Test the llvm backend.
|
||||
// Test 'floating point' -> integer convertion.
|
||||
|
||||
fn main() {
|
||||
println(convert_f64_to_i32(100.001))
|
||||
println(convert_f64_to_u32(100.001))
|
||||
println(convert_f64_to_i32(99.99))
|
||||
println(convert_f64_to_u32(99.99))
|
||||
println(convert_f64_to_i32(-100.001))
|
||||
println(convert_f64_to_u32(-100.001))
|
||||
}
|
||||
|
||||
fn convert_f64_to_i32(a f64) i32 {
|
||||
return i32(a)
|
||||
}
|
||||
|
||||
fn convert_f64_to_u32(a f64) u32 {
|
||||
return u32(a)
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Test the llvm backend.
|
||||
// Test float32 operations.
|
||||
|
||||
type fff32 f32
|
||||
|
||||
fn getPi() fff32 {
|
||||
return 3.1415926535
|
||||
}
|
||||
|
||||
fn getE() fff32 {
|
||||
return 2.71828
|
||||
}
|
||||
|
||||
fn minusf32(a fff32) fff32 {
|
||||
return 0 - a
|
||||
}
|
||||
|
||||
fn circle_area(r fff32) fff32 {
|
||||
return r * r * 3.1415926
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println(getPi())
|
||||
println(getE())
|
||||
println(minusf32(getPi()))
|
||||
println(minusf32(-1.3))
|
||||
println(circle_area(1.1))
|
||||
println(circle_area(getE()))
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Test the llvm backend.
|
||||
// Test global constants.
|
||||
|
||||
const gbegin = 10
|
||||
const gend = 20
|
||||
|
||||
fn test_global_consts() {
|
||||
for i := gbegin; i < gend; i++ {
|
||||
println(i, " - ", gbegin, " = ", i - gbegin)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_global_consts()
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// Test the llvm backend.
|
||||
// Test read/write global variables.
|
||||
|
||||
type ty0 struct {
|
||||
v0 int
|
||||
v1 float64
|
||||
}
|
||||
|
||||
var gv0 ty0 = ty0{5555, 3.1415926}
|
||||
var gv1 [4]int = [4]int{81, 82, 17, 76}
|
||||
|
||||
fn test_global_variable() {
|
||||
println("gv0: {", gv0.v0, ", ", gv0.v1, "}")
|
||||
println("gv1: {", gv1[0], ", ", gv1[1], ", ", gv1[2], ", ", gv1[3], "}")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_global_variable()
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// Test the llvm backend.
|
||||
// Test read/write global variables.
|
||||
|
||||
type ty0 struct {
|
||||
v0 int
|
||||
v1 float64
|
||||
}
|
||||
|
||||
var gv0 ty0 = ty0{5555, 3.1415926}
|
||||
var gv1 [4]int = [4]int{81, 82, 17, 76}
|
||||
|
||||
fn test_global_variable() {
|
||||
gv0.v0 = 8888
|
||||
gv0.v1 = 2.71828
|
||||
gv1[0] += 2
|
||||
gv1[1] += 3
|
||||
gv1[2] += 4
|
||||
gv1[3] += 5
|
||||
println("gv0: {", gv0.v0, ", ", gv0.v1, "}")
|
||||
println("gv1: {", gv1[0], ", ", gv1[1], ", ", gv1[2], ", ", gv1[3], "}")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_global_variable()
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// Test the llvm backend.
|
||||
|
||||
fn main {
|
||||
a := 0.0
|
||||
for y := 1.5; y > -1.5; y = y - 0.2 {
|
||||
for x := -1.5; x < 1.5; x = x + 0.1 {
|
||||
a = x*x + y*y - 1.0
|
||||
if a*a*a < x*x*y*y*y {
|
||||
print("@")
|
||||
} else {
|
||||
print(" ")
|
||||
}
|
||||
}
|
||||
println()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Test the llvm backend.
|
||||
// Test loops.
|
||||
|
||||
fn main() {
|
||||
test_loop(10)
|
||||
}
|
||||
|
||||
fn test_loop(a i16) {
|
||||
for i := i16(0); i < a; i++ {
|
||||
println(i, " * ", i, " = ", i * i)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// Test the llvm backend.
|
||||
// Test loops.
|
||||
|
||||
fn main() {
|
||||
test_loop(10, 10)
|
||||
}
|
||||
|
||||
fn test_loop(a u64, b u64) {
|
||||
for i := u64(0); i < a; i++ {
|
||||
for j := u64(0); j < b; j++ {
|
||||
println(i, " * ", j, " = ", i * j)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Test the llvm backend.
|
||||
// Test read/write via a pointer.
|
||||
|
||||
var gint0 int = 100
|
||||
var gint1 int = 200
|
||||
|
||||
fn get_int_addr(a int) *int {
|
||||
if a > 0 {
|
||||
return &gint0
|
||||
} else {
|
||||
return &gint1
|
||||
}
|
||||
}
|
||||
|
||||
fn set_int(a *int) {
|
||||
*a += 10
|
||||
}
|
||||
|
||||
fn test_pointer(a int) {
|
||||
println(gint0, ", ", gint1)
|
||||
p := get_int_addr(a)
|
||||
set_int(p)
|
||||
println(gint0, ", ", gint1)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_pointer(1)
|
||||
test_pointer(-1)
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Test the llvm backend.
|
||||
|
||||
fn main() {
|
||||
for n := 2; n <= 30; n = n + 1 {
|
||||
var isPrime int = 1
|
||||
for i := 2; i*i <= n; i = i + 1 {
|
||||
if x := n % i; x == 0 {
|
||||
isPrime = 0
|
||||
}
|
||||
}
|
||||
if isPrime != 0 {
|
||||
println(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
// Test the llvm backend.
|
||||
// Test printing strings.
|
||||
|
||||
fn main() {
|
||||
println("您好,凹语言!\n\"Hello, wa-lang!\"")
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// Test the llvm backend.
|
||||
// Test printing integers.
|
||||
|
||||
fn main() {
|
||||
println(test_calc1(6, 9, 8), ", ", 5, ", ", test_calc5(6, 9, 8))
|
||||
}
|
||||
|
||||
fn test_calc0(a i32, b i32, c i32) i32 {
|
||||
return (-a) * 5 / (b - c + 2)
|
||||
}
|
||||
|
||||
fn test_calc1(a i32, b i32, c i32) i32 {
|
||||
return test_calc0(a, b, c)
|
||||
}
|
||||
|
||||
fn test_calc4(a u16, b u16, c u16) u16 {
|
||||
return (-a) * 5 / (b - c + 2)
|
||||
}
|
||||
|
||||
fn test_calc5(a u16, b u16, c u16) u16 {
|
||||
return test_calc4(-a, b, c)
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// Test the llvm backend.
|
||||
// Test printing float64 values.
|
||||
|
||||
fn main() {
|
||||
println(5.44, ", ", test_calc3(6, 9, 8))
|
||||
}
|
||||
|
||||
fn test_calc2(a f64, b f64, c f64) f64 {
|
||||
return (-a) * 5 / (b - c + 2)
|
||||
}
|
||||
|
||||
fn test_calc3(a f64, b f64, c f64) f64 {
|
||||
return test_calc2(a, b, c)
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// Test the llvm backend.
|
||||
// Test printing small type values.
|
||||
|
||||
fn main() {
|
||||
test_print(-100, 100, -100, 100, 5)
|
||||
}
|
||||
|
||||
fn test_print(a i16, b u16, c i8, d u8, e f32) {
|
||||
println(a, ", ", b, ", ", c, ", ", d, ", ", e/2)
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// Test the llvm backend.
|
||||
// Test read/write structs.
|
||||
|
||||
fn main() {
|
||||
test_struct(10)
|
||||
test_struct(11)
|
||||
test_struct(2)
|
||||
test_struct(13)
|
||||
test_struct(0)
|
||||
}
|
||||
|
||||
type t0 struct {
|
||||
a int64
|
||||
b int32
|
||||
c [4]f64
|
||||
d uint32
|
||||
}
|
||||
|
||||
fn test_struct(a int) {
|
||||
s0 := t0{17, -668, [4]f64{2.71828, 0.717, 3.14159}, 234}
|
||||
switch a {
|
||||
case 10:
|
||||
println(s0.a)
|
||||
case 11:
|
||||
println(s0.b)
|
||||
case 13:
|
||||
println(s0.d)
|
||||
default:
|
||||
println(s0.c[a])
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// Test the llvm backend.
|
||||
// Test read/write structs.
|
||||
|
||||
fn main() {
|
||||
test_struct1(10, 0, 0)
|
||||
test_struct1(11, 1, 0)
|
||||
test_struct1(2, 0, 1)
|
||||
test_struct1(13, 1, 0)
|
||||
test_struct1(2, 1, 0)
|
||||
}
|
||||
|
||||
type t0 struct {
|
||||
a int64
|
||||
b int32
|
||||
c [4]f64
|
||||
d uint32
|
||||
}
|
||||
|
||||
fn test_struct1(a int, b int, c int) {
|
||||
s0 := [2]t0{{11, -12, [4]f64{0.1, -0.2}, 13}, {21, -22, [4]f64{1.1, -1.2}, 24}}
|
||||
switch a {
|
||||
case 10:
|
||||
println(s0[b].a)
|
||||
case 11:
|
||||
println(s0[b].b)
|
||||
case 13:
|
||||
println(s0[b].d)
|
||||
default:
|
||||
println(s0[b].c[c])
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Test the llvm backend.
|
||||
// Test passing struct type parameters.
|
||||
|
||||
type fff32 f32
|
||||
|
||||
const size = 16
|
||||
|
||||
type struct_t0 struct {
|
||||
arr0 [size]int
|
||||
arr1 [size]fff32
|
||||
}
|
||||
|
||||
var st0 struct_t0
|
||||
|
||||
fn test_init() {
|
||||
for i := 0; i < size; i++ {
|
||||
st0.arr0[i] = i * i
|
||||
st0.arr1[i] = fff32(i) * 2.0
|
||||
}
|
||||
}
|
||||
|
||||
fn getst0int(st0 struct_t0, i int) int {
|
||||
return st0.arr0[i]
|
||||
}
|
||||
|
||||
fn getst0f32(st0 *struct_t0, i int) fff32 {
|
||||
return st0.arr1[i]
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_init()
|
||||
for i := 6; i < 14; i += 2 {
|
||||
println(getst0int(st0, i), ", ", getst0f32(&st0, i + 1))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Test the llvm backend.
|
||||
// Test return struct type values.
|
||||
|
||||
const size = 16
|
||||
|
||||
type struct_t0 struct {
|
||||
arr0 [size]int
|
||||
arr1 [size]f32
|
||||
}
|
||||
|
||||
var st1 struct_t0
|
||||
|
||||
fn maket0a(i int) struct_t0 {
|
||||
var r struct_t0
|
||||
for j := 0; j < 16; j++ {
|
||||
r.arr0[j] = 100 - j - i
|
||||
r.arr1[j] = 200.0 - f32(j + i)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
fn maket0b(i int) *struct_t0 {
|
||||
for j := 0; j < 16; j++ {
|
||||
st1.arr0[j] = 100 - j - i
|
||||
st1.arr1[j] = 200.0 - f32(j + i)
|
||||
}
|
||||
return &st1
|
||||
}
|
||||
|
||||
fn main() {
|
||||
r0, r1 := maket0a(20), maket0b(30)
|
||||
for i := 0; i < 16; i++ {
|
||||
println(r0.arr0[i], ", ", r0.arr1[i], " : ", r1.arr0[i], ", ", r1.arr1[i])
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// Test the llvm backend.
|
||||
// Test unconditional jump.
|
||||
|
||||
fn main() {
|
||||
test_jmp(4)
|
||||
}
|
||||
|
||||
fn test_jmp(a i32) {
|
||||
for {
|
||||
if a > 10 {
|
||||
return
|
||||
}
|
||||
println(a)
|
||||
a++
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
// 版权 @2021 凹语言 作者。保留所有权利。
|
||||
|
||||
global I: [8]i32
|
||||
package main
|
||||
|
||||
func Gen_arr(param: [8]i32) => [8]i32 {
|
||||
var I [8]i32
|
||||
|
||||
fn Gen_arr(param: [8]i32) => [8]i32 {
|
||||
param[3] += 2
|
||||
return param
|
||||
}
|
||||
|
||||
func main {
|
||||
j: [8]i32
|
||||
fn main() {
|
||||
var j [8]i32
|
||||
j[3] = 40
|
||||
I = Gen_arr(j)
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# 版权 @2022 凹语言 作者。保留所有权利。
|
||||
|
||||
fn main {
|
||||
test_array(2, 4)
|
||||
test_array(7, 9)
|
||||
}
|
||||
|
||||
fn test_array(a: int, b: int) {
|
||||
var arr [10][10]int
|
||||
for i := 0; i < 10; i++ {
|
||||
for j := 0; j < 10; j++ {
|
||||
arr[i][j] = i * j
|
||||
}
|
||||
}
|
||||
|
||||
println("arr[", a, "][", b, "] = ", arr[a][b])
|
||||
println("arr[", b, "][", a, "] = ", arr[b][a])
|
||||
}
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
// 版权 @2021 凹语言 作者。保留所有权利。
|
||||
|
||||
global counter: i32 = 13
|
||||
global G: *i32
|
||||
package main
|
||||
|
||||
func gen_i() => *i32 {
|
||||
i: i32
|
||||
var counter i32 = 13
|
||||
var G *i32
|
||||
fn gen_i() => *i32{
|
||||
var i i32
|
||||
i = counter
|
||||
counter++
|
||||
return &i
|
||||
}
|
||||
|
||||
func main {
|
||||
fn main() {
|
||||
G = gen_i()
|
||||
println(*G)
|
||||
G = gen_i()
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// Test the llvm backend.
|
||||
# 版权 @2021 凹语言 作者。保留所有权利。
|
||||
|
||||
func main {
|
||||
fn main {
|
||||
a := 0.0
|
||||
for y := 1.5; y > -1.5; y = y - 0.2 {
|
||||
for x := -1.5; x < 1.5; x = x + 0.1 {
|
||||
|
|
@ -0,0 +1,428 @@
|
|||
# 版权 @2022 凹语言 作者。保留所有权利。
|
||||
|
||||
fn main {
|
||||
println("您好,凹语言!\nHello, wa-lang!")
|
||||
println("幸运整数:", test_calc1(6, 9, 8), ", ", 5, ", ", test_calc5(6, 9, 8))
|
||||
println("幸运浮点数:", 5.44, ", ", test_calc3(6, 9, 8))
|
||||
|
||||
test_print(-100, 100, -100, 100, 5)
|
||||
|
||||
test_cmp0(20, 19)
|
||||
test_cmp0(20, 20)
|
||||
test_cmp1(-3, -3)
|
||||
test_cmp1(-3, -4)
|
||||
test_cmp2(10, 10)
|
||||
test_cmp2(10, 10.0001)
|
||||
|
||||
test_jmp(4)
|
||||
test_loop0(10)
|
||||
test_loop1(10, 10)
|
||||
|
||||
test_array(2, 4)
|
||||
test_array(7, 9)
|
||||
|
||||
test_struct0(10)
|
||||
test_struct0(11)
|
||||
test_struct0(2)
|
||||
test_struct0(13)
|
||||
test_struct0(0)
|
||||
|
||||
test_struct1(10, 0, 0)
|
||||
test_struct1(11, 1, 0)
|
||||
test_struct1(2, 0, 1)
|
||||
test_struct1(13, 1, 0)
|
||||
test_struct1(2, 1, 0)
|
||||
|
||||
test_convert0()
|
||||
test_convert1()
|
||||
test_convert2()
|
||||
test_convert3()
|
||||
|
||||
test_global_consts()
|
||||
test_global_variables_0()
|
||||
test_global_variables_1()
|
||||
|
||||
test_pointer(1)
|
||||
test_pointer(-1)
|
||||
|
||||
test_f32()
|
||||
|
||||
test_struct2()
|
||||
test_struct3()
|
||||
test_struct4()
|
||||
|
||||
test_array2()
|
||||
test_array3()
|
||||
test_array4()
|
||||
|
||||
bye()
|
||||
}
|
||||
|
||||
fn bye() {
|
||||
println("再见!\nbye!")
|
||||
}
|
||||
|
||||
fn test_calc0(a i32, b i32, c i32) i32 {
|
||||
return (-a) * 5 / (b - c + 2)
|
||||
}
|
||||
|
||||
fn test_calc1(a i32, b i32, c i32) i32 {
|
||||
return test_calc0(a, b, c)
|
||||
}
|
||||
|
||||
fn test_calc2(a f64, b f64, c f64) f64 {
|
||||
return (-a) * 5 / (b - c + 2)
|
||||
}
|
||||
|
||||
fn test_calc3(a f64, b f64, c f64) f64 {
|
||||
return test_calc2(a, b, c)
|
||||
}
|
||||
|
||||
fn test_calc4(a u16, b u16, c u16) u16 {
|
||||
return (-a) * 5 / (b - c + 2)
|
||||
}
|
||||
|
||||
fn test_calc5(a u16, b u16, c u16) u16 {
|
||||
return test_calc4(-a, b, c)
|
||||
}
|
||||
|
||||
fn test_cmp0(a u16, b u16) {
|
||||
if a > b {
|
||||
println(a, " > ", b)
|
||||
} else {
|
||||
println(a, " <= ", b)
|
||||
}
|
||||
}
|
||||
|
||||
fn test_cmp1(a i64, b i64) {
|
||||
if a <= b {
|
||||
println(a, " <= ", b)
|
||||
} else {
|
||||
println(a, " > ", b)
|
||||
}
|
||||
}
|
||||
|
||||
fn test_cmp2(a f64, b f64) {
|
||||
if a != b {
|
||||
println(a, " != ", b)
|
||||
} else {
|
||||
println(a, " == ", b)
|
||||
}
|
||||
}
|
||||
|
||||
fn test_jmp(a i32) {
|
||||
for {
|
||||
if a > 10 {
|
||||
return
|
||||
}
|
||||
println(a)
|
||||
a++
|
||||
}
|
||||
}
|
||||
|
||||
fn test_loop0(a i16) {
|
||||
for i := i16(0); i < a; i++ {
|
||||
println(i, " * ", i, " = ", i * i)
|
||||
}
|
||||
}
|
||||
|
||||
fn test_loop1(a u64, b u64) {
|
||||
for i := u64(0); i < a; i++ {
|
||||
for j := u64(0); j < b; j++ {
|
||||
println(i, " * ", j, " = ", i * j)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_array(a int, b int) {
|
||||
var arr [10][10]int
|
||||
for i := 0; i < 10; i++ {
|
||||
for j := 0; j < 10; j++ {
|
||||
arr[i][j] = i * j
|
||||
}
|
||||
}
|
||||
|
||||
println("arr[", a, "][", b, "] = ", arr[a][b])
|
||||
println("arr[", b, "][", a, "] = ", arr[b][a])
|
||||
}
|
||||
|
||||
type t0 struct {
|
||||
a int64
|
||||
b int32
|
||||
c [4]f64
|
||||
d uint32
|
||||
}
|
||||
|
||||
fn test_struct0(a int) {
|
||||
s0 := t0{17, -668, [4]f64{2.71828, 0.717, 3.14159}, 234}
|
||||
switch a {
|
||||
case 10:
|
||||
println(s0.a)
|
||||
case 11:
|
||||
println(s0.b)
|
||||
case 13:
|
||||
println(s0.d)
|
||||
default:
|
||||
println(s0.c[a])
|
||||
}
|
||||
}
|
||||
|
||||
fn test_struct1(a int, b int, c int) {
|
||||
s0 := [2]t0{{11, -12, [4]f64{0.1, -0.2}, 13}, {21, -22, [4]f64{1.1, -1.2}, 24}}
|
||||
switch a {
|
||||
case 10:
|
||||
println(s0[b].a)
|
||||
case 11:
|
||||
println(s0[b].b)
|
||||
case 13:
|
||||
println(s0[b].d)
|
||||
default:
|
||||
println(s0[b].c[c])
|
||||
}
|
||||
}
|
||||
|
||||
type struct_t0 struct {
|
||||
arr0 [16]int
|
||||
arr1 [16]f32
|
||||
}
|
||||
var st0 struct_t0
|
||||
var st1 struct_t0
|
||||
|
||||
fn test_struct2() {
|
||||
for i := 0; i < 16; i++ {
|
||||
st0.arr0[i] = i * i
|
||||
st0.arr1[i] = f32(i) * 2.0
|
||||
}
|
||||
}
|
||||
|
||||
fn getst0int(st0 struct_t0, i int) int {
|
||||
return st0.arr0[i]
|
||||
}
|
||||
|
||||
fn getst0f32(st0 *struct_t0, i int) f32 {
|
||||
return st0.arr1[i]
|
||||
}
|
||||
|
||||
fn test_struct3() {
|
||||
for i := 6; i < 14; i += 2 {
|
||||
println(getst0int(st0, i), ", ", getst0f32(&st0, i + 1))
|
||||
}
|
||||
}
|
||||
|
||||
fn maket0a(i int) struct_t0 {
|
||||
var r struct_t0
|
||||
for j := 0; j < 16; j++ {
|
||||
r.arr0[j] = 100 - j - i
|
||||
r.arr1[j] = 200.0 - f32(j + i)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
fn maket0b(i int) *struct_t0 {
|
||||
for j := 0; j < 16; j++ {
|
||||
st1.arr0[j] = 100 - j - i
|
||||
st1.arr1[j] = 200.0 - f32(j + i)
|
||||
}
|
||||
return &st1
|
||||
}
|
||||
|
||||
fn test_struct4() {
|
||||
r0, r1 := maket0a(20), maket0b(30)
|
||||
for i := 0; i < 16; i++ {
|
||||
println(r0.arr0[i], ", ", r0.arr1[i], " : ", r1.arr0[i], ", ", r1.arr1[i])
|
||||
}
|
||||
}
|
||||
|
||||
type fff32 f32
|
||||
var arr0 [32]fff32
|
||||
var arr1 [32]fff32
|
||||
|
||||
fn test_array2() {
|
||||
for i := 0; i < 32; i++ {
|
||||
arr0[i] = fff32(i * i)
|
||||
}
|
||||
}
|
||||
|
||||
fn getarr0a(arr [32]fff32, i int) fff32 {
|
||||
return arr[i]
|
||||
}
|
||||
|
||||
fn getarr0b(arr *[32]fff32, i int) fff32 {
|
||||
return arr[i]
|
||||
}
|
||||
|
||||
fn makearr0a(i int) [32]fff32 {
|
||||
var r [32]fff32
|
||||
for j := 0; j < 32; j++ {
|
||||
r[j] = fff32(i * j)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
fn makearr0b(i int) *[32]fff32 {
|
||||
for j := 0; j < 32; j++ {
|
||||
arr1[j] = 2000 - fff32(i * j)
|
||||
}
|
||||
return &arr1
|
||||
}
|
||||
|
||||
fn test_array3() {
|
||||
for i := 6; i < 20; i += 2 {
|
||||
println(getarr0a(arr0, i), ", ", getarr0b(&arr0, i + 1))
|
||||
}
|
||||
}
|
||||
|
||||
fn test_array4() {
|
||||
r0, r1 := makearr0a(10), makearr0b(20)
|
||||
for i := 6; i < 20; i += 2 {
|
||||
println(r0[i], ", ", r1[i])
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_i32_to_i16(a i32) i16 {
|
||||
return i16(a)
|
||||
}
|
||||
|
||||
fn convert_i32_to_u16(a i32) u16 {
|
||||
return u16(a)
|
||||
}
|
||||
|
||||
fn convert_i16_to_i32(a i16) i32 {
|
||||
return i32(a)
|
||||
}
|
||||
|
||||
fn convert_u16_to_i32(a u16) i32 {
|
||||
return i32(a)
|
||||
}
|
||||
|
||||
fn test_convert0() {
|
||||
println(i32(convert_i32_to_i16(65537)))
|
||||
println(u32(convert_i32_to_u16(65537)))
|
||||
println(convert_i16_to_i32(100))
|
||||
println(convert_i16_to_i32(-100))
|
||||
println(convert_u16_to_i32(100))
|
||||
println(convert_u16_to_i32(65530))
|
||||
}
|
||||
|
||||
fn convert_f64_to_f32(a f64) f32 {
|
||||
return f32(a)
|
||||
}
|
||||
|
||||
fn test_convert1() {
|
||||
println(f64(convert_f64_to_f32(3.1415926535)))
|
||||
}
|
||||
|
||||
fn convert_i16_to_f64(a i16) f64 {
|
||||
return f64(a)
|
||||
}
|
||||
|
||||
fn convert_u16_to_f64(a u16) f64 {
|
||||
return f64(a)
|
||||
}
|
||||
|
||||
fn test_convert2() {
|
||||
println(convert_i16_to_f64(100))
|
||||
println(convert_i16_to_f64(-100))
|
||||
println(convert_u16_to_f64(100))
|
||||
println(convert_u16_to_f64(65530))
|
||||
}
|
||||
|
||||
fn convert_f64_to_i32(a f64) i32 {
|
||||
return i32(a)
|
||||
}
|
||||
|
||||
fn convert_f64_to_u32(a f64) u32 {
|
||||
return u32(a)
|
||||
}
|
||||
|
||||
fn test_convert3() {
|
||||
println(convert_f64_to_i32(100.001))
|
||||
println(convert_f64_to_u32(100.001))
|
||||
println(convert_f64_to_i32(99.99))
|
||||
println(convert_f64_to_u32(99.99))
|
||||
println(convert_f64_to_i32(-100.001))
|
||||
println(convert_f64_to_u32(-100.001))
|
||||
}
|
||||
|
||||
fn test_print(a i16, b u16, c i8, d u8, e f32) {
|
||||
println(a, ", ", b, ", ", c, ", ", d, ", ", e/2)
|
||||
}
|
||||
|
||||
const gbegin = 10
|
||||
const gend = 20
|
||||
|
||||
fn test_global_consts() {
|
||||
for i := gbegin; i < gend; i++ {
|
||||
println(i, " - ", gbegin, " = ", i - gbegin)
|
||||
}
|
||||
}
|
||||
|
||||
type ty0 struct {
|
||||
v0 int
|
||||
v1 float64
|
||||
}
|
||||
var gv0 ty0 = ty0{5555, 3.1415926}
|
||||
var gv1 [4]int = [4]int{81, 82, 17, 76}
|
||||
|
||||
fn test_global_variables_0() {
|
||||
println("gv0: {", gv0.v0, ", ", gv0.v1, "}")
|
||||
println("gv1: {", gv1[0], ", ", gv1[1], ", ", gv1[2], ", ", gv1[3], "}")
|
||||
}
|
||||
|
||||
fn test_global_variables_1() {
|
||||
gv0.v0 = 8888
|
||||
gv0.v1 = 2.71828
|
||||
gv1[0] += 2
|
||||
gv1[1] += 3
|
||||
gv1[2] += 4
|
||||
gv1[3] += 5
|
||||
println("gv0: {", gv0.v0, ", ", gv0.v1, "}")
|
||||
println("gv1: {", gv1[0], ", ", gv1[1], ", ", gv1[2], ", ", gv1[3], "}")
|
||||
}
|
||||
|
||||
var gint0 int = 100
|
||||
var gint1 int = 200
|
||||
|
||||
fn get_int_addr(a int) *int {
|
||||
if a > 0 {
|
||||
return &gint0
|
||||
} else {
|
||||
return &gint1
|
||||
}
|
||||
}
|
||||
|
||||
fn set_int(a *int) {
|
||||
*a += 10
|
||||
}
|
||||
|
||||
fn test_pointer(a int) {
|
||||
println(gint0, ", ", gint1)
|
||||
p := get_int_addr(a)
|
||||
set_int(p)
|
||||
println(gint0, ", ", gint1)
|
||||
}
|
||||
|
||||
fn getPi() f32 {
|
||||
return 3.1415926535
|
||||
}
|
||||
|
||||
fn getE() f32 {
|
||||
return 2.71828
|
||||
}
|
||||
|
||||
fn minusf32(a f32) f32 {
|
||||
return 0 - a
|
||||
}
|
||||
|
||||
fn circle_area(r f32) f32 {
|
||||
return r * r * 3.1415926
|
||||
}
|
||||
|
||||
fn test_f32() {
|
||||
println(getPi())
|
||||
println(getE())
|
||||
println(minusf32(getPi()))
|
||||
println(minusf32(-1.3))
|
||||
println(circle_area(1.1))
|
||||
println(circle_area(getE()))
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
// 版权 @2019 凹语言 作者。保留所有权利。
|
||||
# 版权 @2019 凹语言 作者。保留所有权利。
|
||||
|
||||
func main {
|
||||
fn main() {
|
||||
for n := 2; n <= 30; n = n + 1 {
|
||||
isPrime: int = 1
|
||||
var isPrime int = 1
|
||||
for i := 2; i*i <= n; i = i + 1 {
|
||||
if x := n % i; x == 0 {
|
||||
isPrime = 0
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# 版权 @2021 凹语言 作者。保留所有权利。
|
||||
|
||||
fn main() {
|
||||
p := new_int()
|
||||
println(*p)
|
||||
proc(p)
|
||||
println(*p)
|
||||
}
|
||||
|
||||
fn new_int() => *i32 {
|
||||
var i: i32
|
||||
i = 42
|
||||
return &i
|
||||
}
|
||||
|
||||
fn proc(i: *i32) {
|
||||
*i = 13
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// 版权 @2021 凹语言 作者。保留所有权利。
|
||||
# 版权 @2021 凹语言 作者。保留所有权利。
|
||||
|
||||
func main {
|
||||
fn main() {
|
||||
a := [4]i32{12, 34, 56, 78}
|
||||
s := a[0:1]
|
||||
s[0] = 42
|
||||
|
|
@ -9,7 +9,7 @@ func main {
|
|||
println(a[1]) # 13
|
||||
println(len(s)) # 2
|
||||
|
||||
k: []i32
|
||||
var k: []i32
|
||||
k = append(k, 99, 81, 170)
|
||||
println(k[0]) # 99
|
||||
|
||||
|
|
@ -22,4 +22,4 @@ func main {
|
|||
l[0] = 111
|
||||
println(l[0]) #111
|
||||
println(k[0]) #99
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
func gen_str() => string {
|
||||
|
||||
fn gen_str() => string {
|
||||
return "123456"
|
||||
}
|
||||
|
||||
func main {
|
||||
fn main() {
|
||||
println("你好,凹语言!")
|
||||
|
||||
|
||||
s1 := gen_str()
|
||||
s2 := s1[0:3]
|
||||
s2 = s2 + "abc"
|
||||
println(s1)
|
||||
println(s2)
|
||||
println(s2)
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// 版权 @2021 凹语言 作者。保留所有权利。
|
||||
# 版权 @2021 凹语言 作者。保留所有权利。
|
||||
|
||||
func main {
|
||||
fn main() {
|
||||
i := gen_scref()
|
||||
println(*i.a)
|
||||
println(i.b)
|
||||
|
|
@ -19,18 +19,18 @@ type sc struct {
|
|||
sp
|
||||
}
|
||||
|
||||
func gen_scref() => *sc {
|
||||
v: sc
|
||||
i: i32
|
||||
fn gen_scref() => *sc {
|
||||
var v: sc
|
||||
var i: i32
|
||||
i = 13
|
||||
v.a = &i
|
||||
v.b = 42
|
||||
return &v
|
||||
}
|
||||
|
||||
func gen_sc() => sc {
|
||||
i: i32 = 26
|
||||
v: sc
|
||||
fn gen_sc() => sc {
|
||||
var i: i32 = 26
|
||||
var v: sc
|
||||
v.a = &i
|
||||
v.b = 58
|
||||
return v
|
||||
|
|
@ -0,0 +1 @@
|
|||
版权 @2022 _examples/hello 作者。保留所有权利。
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Pkg: hello
|
||||
# Pkg: _examples/hello
|
||||
|
||||
欢迎使用 Wa 语言!
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// 版权 @2022 hello 作者。保留所有权利。
|
||||
# 版权 @2022 _examples/hello 作者。保留所有权利。
|
||||
|
||||
import (
|
||||
"pkg/mypkg"
|
||||
|
|
@ -9,14 +9,14 @@ type ST struct {
|
|||
B: i32
|
||||
}
|
||||
|
||||
global G: ST
|
||||
var G ST
|
||||
|
||||
func Do {
|
||||
fn Do() {
|
||||
G.A = "13"
|
||||
G.B = 1024
|
||||
}
|
||||
|
||||
func main {
|
||||
fn main() {
|
||||
mypkg.Do(42)
|
||||
println(mypkg.G.A)
|
||||
println(mypkg.G.B)
|
||||
|
|
@ -24,7 +24,7 @@ func main {
|
|||
Do()
|
||||
println(G.A)
|
||||
println(G.B)
|
||||
|
||||
|
||||
println(mypkg.G.A)
|
||||
println(mypkg.G.B)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// 版权 @2022 _examples/hello 作者。保留所有权利。
|
||||
|
||||
type ST struct {
|
||||
A: i32
|
||||
B: string
|
||||
}
|
||||
|
||||
var G ST
|
||||
|
||||
fn Do(x i32) {
|
||||
G.A = x
|
||||
G.B = "Hellow wa"
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
// 版权 @2022 _examples/hello 作者。保留所有权利。
|
||||
|
||||
fn Println(x int) {
|
||||
println(x)
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "_examples/hello",
|
||||
"pkgpath": "pkg",
|
||||
"version": "0.0.1",
|
||||
"authors": ["author", "author2"],
|
||||
"description": "module description",
|
||||
"readme": "README.md",
|
||||
"homepage": "",
|
||||
"repository": "",
|
||||
"license": "",
|
||||
"license_file": "LICENSE",
|
||||
"keywords": [],
|
||||
"categories": []
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fn PrintInt(x int) {
|
||||
print(x, " ")
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// 版权 @2022 凹语言™ 作者。保留所有权利。
|
||||
|
||||
fn isPrime(n int) bool {
|
||||
for i := 2; i*i <= n; i = i + 1 {
|
||||
if n%i == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// 版权 @2022 凹语言™ 作者。保留所有权利。
|
||||
|
||||
import "examples/prime/benpkg"
|
||||
import "joypkg"
|
||||
|
||||
fn main() {
|
||||
for n := 2; n <= 100; n = n + 1 {
|
||||
if isPrime(n) {
|
||||
benpkg.PrintInt(n)
|
||||
}
|
||||
}
|
||||
println("")
|
||||
|
||||
for n := 2; n <= 100; n = n + 1 {
|
||||
if isPrime(n) {
|
||||
joypkg.PrintIntComma(n)
|
||||
}
|
||||
}
|
||||
println("")
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fn PrintIntComma(x int) {
|
||||
print(x, ",")
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "prime",
|
||||
"pkgpath": "examples/prime",
|
||||
"version": "0.0.1",
|
||||
"authors": ["author", "author2"],
|
||||
"description": "module description",
|
||||
"readme": "README.md",
|
||||
"homepage": "",
|
||||
"repository": "",
|
||||
"license": "",
|
||||
"license_file": "LICENSE",
|
||||
"keywords": [],
|
||||
"categories": []
|
||||
}
|
||||
52
api/api.go
|
|
@ -6,12 +6,12 @@ package api
|
|||
import (
|
||||
"io/fs"
|
||||
|
||||
"wa-lang.org/wa/internal/backends/compiler_wat"
|
||||
"wa-lang.org/wa/internal/config"
|
||||
"wa-lang.org/wa/internal/format"
|
||||
"wa-lang.org/wa/internal/loader"
|
||||
"wa-lang.org/wa/internal/logger"
|
||||
"wa-lang.org/wa/internal/wamime"
|
||||
"github.com/wa-lang/wa/internal/backends/compiler_wat"
|
||||
"github.com/wa-lang/wa/internal/backends/target_spec"
|
||||
"github.com/wa-lang/wa/internal/config"
|
||||
"github.com/wa-lang/wa/internal/format"
|
||||
"github.com/wa-lang/wa/internal/loader"
|
||||
"github.com/wa-lang/wa/internal/logger"
|
||||
)
|
||||
|
||||
// 调试参数
|
||||
|
|
@ -42,9 +42,26 @@ type PkgVFS = config.PkgVFS
|
|||
// 指针和整数大小
|
||||
type StdSize = config.StdSizes
|
||||
|
||||
// 默认配置
|
||||
func DefaultConfig() *Config {
|
||||
return config.DefaultConfig()
|
||||
// 目标机器
|
||||
type Machine = target_spec.Machine
|
||||
|
||||
const (
|
||||
Machine_default = Machine_Wasm32_wa // 默认输出的目标类型
|
||||
|
||||
Machine_Wasm32_wa = target_spec.Machine_Wasm32_wa // 凹语言定义的 WASM 规范
|
||||
Machine_Wasm32_wasi = target_spec.Machine_Wasm32_wasi // WASI 定义的 WASM 规范
|
||||
)
|
||||
|
||||
// 解析字符串类型
|
||||
func ParseMachine(s string) (target Machine, ok bool) {
|
||||
switch t := Machine(s); t {
|
||||
case Machine_Wasm32_wa:
|
||||
return t, true
|
||||
case Machine_Wasm32_wasi:
|
||||
return t, true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
// 加载 WaModFile 文件
|
||||
|
|
@ -72,39 +89,36 @@ func LoadProgramVFS(vfs *config.PkgVFS, cfg *config.Config, pkgPath string) (*Pr
|
|||
}
|
||||
|
||||
// 构建 wat 目标
|
||||
func BuildFile(cfg *config.Config, filename string, src interface{}) (wat []byte, err error) {
|
||||
func BuildFile(filename string, src interface{}, target Machine) (wat []byte, err error) {
|
||||
cfg := config.DefaultConfig()
|
||||
prog, err := LoadProgramFile(cfg, filename, src)
|
||||
if err != nil || prog == nil {
|
||||
logger.Tracef(&config.EnableTrace_api, "LoadProgramVFS failed, err = %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
watOut, err := compiler_wat.New().Compile(prog, "main")
|
||||
watOut, err := compiler_wat.New().Compile(prog, target)
|
||||
return []byte(watOut), err
|
||||
}
|
||||
|
||||
// 构建 wat 目标
|
||||
func BuildVFS(cfg *config.Config, vfs *config.PkgVFS, appPkg string) (wat []byte, err error) {
|
||||
func BuildVFS(vfs *config.PkgVFS, appPkg string, target Machine) (wat []byte, err error) {
|
||||
cfg := config.DefaultConfig()
|
||||
prog, err := LoadProgramVFS(vfs, cfg, appPkg)
|
||||
if err != nil || prog == nil {
|
||||
logger.Tracef(&config.EnableTrace_api, "LoadProgramVFS failed, err = %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
watOut, err := compiler_wat.New().Compile(prog, "main")
|
||||
watOut, err := compiler_wat.New().Compile(prog, target)
|
||||
return []byte(watOut), err
|
||||
}
|
||||
|
||||
// 格式化代码
|
||||
func FormatCode(filename, code string) (string, error) {
|
||||
data, _, err := format.File(nil, filename, code)
|
||||
data, err := format.File(nil, filename, code)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// 获取代码语法类型
|
||||
func GetCodeSyntax(filename string, code []byte) string {
|
||||
return wamime.GetCodeMime(filename, code)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,27 +6,26 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"wa-lang.org/wa/internal/config"
|
||||
"wa-lang.org/wa/internal/wabt"
|
||||
"wa-lang.org/wa/internal/wazero"
|
||||
"os"
|
||||
|
||||
"github.com/wa-lang/wa/internal/app/apputil"
|
||||
)
|
||||
|
||||
// 执行凹代码
|
||||
func RunCode(cfg *config.Config, filename, code string, args ...string) (stdoutStderr []byte, err error) {
|
||||
func RunCode(filename, code string) (stdoutStderr []byte, err error) {
|
||||
// 编译为 wat 格式
|
||||
watBytes, err := BuildFile(cfg, filename, code)
|
||||
if err != nil {
|
||||
return
|
||||
wat, err := BuildFile(filename, code, Machine_Wasm32_wa)
|
||||
|
||||
// wat 写到临时文件
|
||||
outfile := "a.out.wat"
|
||||
if !*FlagDebugMode {
|
||||
defer os.Remove(outfile)
|
||||
}
|
||||
if err = os.WriteFile(outfile, []byte(wat), 0666); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// wat 编译为 wasm
|
||||
wasmBytes, err := wabt.Wat2Wasm(watBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// main 执行
|
||||
stdout, stderr, err := wazero.RunWasm(cfg, filename, wasmBytes, args...)
|
||||
stdoutStderr = append(stdout, stderr...)
|
||||
// 执行 wat 文件
|
||||
stdoutStderr, err = apputil.RunWasm(outfile)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,23 +6,23 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"wa-lang.org/wa/api"
|
||||
"github.com/wa-lang/wa/api"
|
||||
)
|
||||
|
||||
func ExampleRunCode() {
|
||||
const code = `
|
||||
global gBase: i32 = 1000
|
||||
var gBase: i32 = 1000
|
||||
|
||||
func main() {
|
||||
fn main() {
|
||||
println(add(40, 2) + gBase)
|
||||
}
|
||||
|
||||
func add(a: i32, b: i32) => i32 {
|
||||
fn add(a: i32, b: i32) => i32 {
|
||||
return a+b
|
||||
}
|
||||
`
|
||||
|
||||
output, err := api.RunCode(api.DefaultConfig(), "hello.wa", code)
|
||||
output, err := api.RunCode("hello.wa", code)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
@ -32,55 +32,3 @@ func ExampleRunCode() {
|
|||
// Output:
|
||||
// 1042
|
||||
}
|
||||
|
||||
func ExampleRunCode_args() {
|
||||
const code = `
|
||||
import "os"
|
||||
|
||||
func main {
|
||||
for i, s := range os.Args[1:] {
|
||||
println(i, ":", s)
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
args := []string{"aa", "bb"}
|
||||
output, err := api.RunCode(api.DefaultConfig(), "hello.wa", code, args...)
|
||||
if err != nil {
|
||||
if len(output) != 0 {
|
||||
log.Println(string(output))
|
||||
}
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Print(string(output))
|
||||
|
||||
// Output:
|
||||
// 0 : aa
|
||||
// 1 : bb
|
||||
}
|
||||
|
||||
func ExampleRunCode_wz() {
|
||||
const code = `
|
||||
#wa:syntax=wz
|
||||
|
||||
引于 "书"
|
||||
|
||||
【启】:
|
||||
书·说:"你好,凹语言中文版!"
|
||||
。
|
||||
`
|
||||
|
||||
output, err := api.RunCode(api.DefaultConfig(), "hello.wa", code)
|
||||
if err != nil {
|
||||
if len(output) != 0 {
|
||||
log.Println(string(output))
|
||||
}
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Print(string(output))
|
||||
|
||||
// Output:
|
||||
// 你好,凹语言中文版!
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,16 +5,16 @@ package api_test
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"wa-lang.org/wa/api"
|
||||
"github.com/wa-lang/wa/api"
|
||||
)
|
||||
|
||||
func ExampleFormatCode() {
|
||||
s, err := api.FormatCode("hello.wa", "func add(a:i32, b:i32)=>i32 {return a+b}")
|
||||
s, err := api.FormatCode("hello.wa", "fn add(a:i32, b:i32)=>i32 {return a+b}")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(s)
|
||||
|
||||
// Output:
|
||||
// func add(a: i32, b: i32) => i32 { return a + b }
|
||||
// fn add(a: i32, b: i32) => i32 { return a + b }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
// 版权 @2023 凹语言 作者。保留所有权利。
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"wa-lang.org/wa/internal/config"
|
||||
)
|
||||
|
||||
// wa 代码:
|
||||
// xxx.wa
|
||||
// xxx_$OS.wa
|
||||
// xxx_$ARCH.wa
|
||||
// xxx_$OS_$ARCH.wa
|
||||
|
||||
// wz 中文代码:
|
||||
// xxx.wz
|
||||
// xxx_$OS.wz
|
||||
// xxx_$ARCH.wz
|
||||
// xxx_$OS_$ARCH.wz
|
||||
|
||||
// ws 汇编代码:
|
||||
// xxx.$BACKEND.ws
|
||||
// xxx_$OS.$BACKEND.ws
|
||||
// xxx_$ARCH.$BACKEND.ws
|
||||
// xxx_$OS_$ARCH.$BACKEND.ws
|
||||
|
||||
// 编译器后端类型
|
||||
const (
|
||||
WaBackend_Default = config.WaBackend_Default // 默认
|
||||
|
||||
WaBackend_clang = config.WaBackend_clang // 输出 c
|
||||
WaBackend_llvm = config.WaBackend_llvm // 输出 llir
|
||||
WaBackend_wat = config.WaBackend_wat // 输出 wat
|
||||
)
|
||||
|
||||
// 目标平台类型, 可管理后缀名
|
||||
const (
|
||||
WaOS_Default = config.WaOS_Default // 默认
|
||||
|
||||
WaOS_arduino = config.WaOS_arduino // Arduino 平台
|
||||
WaOS_chrome = config.WaOS_chrome // Chrome 浏览器
|
||||
WaOS_wasi = config.WaOS_wasi // WASI 接口
|
||||
)
|
||||
|
||||
// 体系结构类型
|
||||
const (
|
||||
WaArch_Default = config.WaArch_Default // 默认
|
||||
|
||||
WaArch_386 = config.WaArch_386 // 386 平台
|
||||
WaArch_amd64 = config.WaArch_amd64 // amd64 平台
|
||||
WaArch_arm64 = config.WaArch_arm64 // arm64 平台
|
||||
WaArch_riscv64 = config.WaArch_riscv64 // riscv64 平台
|
||||
WaArch_wasm = config.WaArch_wasm // wasm 平台
|
||||
)
|
||||
24
api/hello.go
|
|
@ -9,19 +9,11 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
|
||||
"wa-lang.org/wa/api"
|
||||
"github.com/wa-lang/wa/api"
|
||||
)
|
||||
|
||||
func main() {
|
||||
output, err := api.RunCode(api.DefaultConfig(), "hello.wa", code)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Print(string(output))
|
||||
|
||||
output, err = api.RunCode(api.DefaultConfig(), "hello-zh.wz", code_zh)
|
||||
output, err := api.RunCode("hello.wa", code)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
|
@ -31,19 +23,11 @@ func main() {
|
|||
}
|
||||
|
||||
const code = `
|
||||
func main {
|
||||
fn main() {
|
||||
println(add(40, 2))
|
||||
}
|
||||
|
||||
func add(a: i32, b: i32) => i32 {
|
||||
fn add(a: i32, b: i32) => i32 {
|
||||
return a+b
|
||||
}
|
||||
`
|
||||
|
||||
const code_zh = `
|
||||
引于 "书"
|
||||
|
||||
【启】:
|
||||
书·说:"你好,凹语言中文版!"
|
||||
。
|
||||
`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
# 版本日志
|
||||
|
||||
- v0.3.1 (2022-11-16)
|
||||
- 完善 WAT 后端, 支持多包
|
||||
- LLVM 后端支持基本数值运算 (依赖 clang 和 llc)
|
||||
- wazero 更新到 v1.0.0-pre.3 [#37](https://github.com/wa-lang/wa/pull/37)
|
||||
- v0.3.0 (2022-10-28)
|
||||
- 启用 AGPLv3 开源协议
|
||||
- 增加 如何贡献代码 机制
|
||||
- 语言: 实现字符串类型
|
||||
- 启动 LLVM 后端
|
||||
- v0.2.3 (2022-10-18)
|
||||
- 支持 JetBrains Fleet 语法高亮
|
||||
- Wat 后端改善对 Slice 支持
|
||||
- v0.2.2 (2022-10-11)
|
||||
- Windows 增加 exe 图标
|
||||
- Playground 改进异步加载wasm资源, 支持多个例子
|
||||
- Wat 后端改善全局变量支持
|
||||
- 增加 `#wa:xxx` 模式注释, 支持通过凹语言实现底层的 runtime 函数
|
||||
- 实验特性: 简化无参数和返回值函数定义
|
||||
- v0.2.1 (2022-09-16)
|
||||
- 支持纯浏览器编译执行: [https://wa-lang.org/playground](https://wa-lang.org/playground)
|
||||
- 命令行增加打印 logo 子命令
|
||||
- 本地支持被嵌入脚本模式执行
|
||||
- 修复格式化问题
|
||||
- v0.2.0 (2022-09-07)
|
||||
- 全面切换到 WAT 后端, 支持 Linux/macOS/Windows 平台
|
||||
- 简化命令行子命令, 不依赖任何第三方工具
|
||||
- 暂时去掉对字符串和浮点数的例子
|
||||
- v0.1.3 (2022-08-27)
|
||||
- 去掉 CGO 依赖
|
||||
- 去掉 `opa/wasm` 依赖
|
||||
- `#` 改为单行注释
|
||||
- 简化命令行, 命令行支持执行单个文件
|
||||
- v0.1.2 (2022-08-15)
|
||||
- 增加 `#` 作为空格等价字符
|
||||
- v0.1.1 (2022-08-12)
|
||||
- LET 改成 VAR
|
||||
- v0.1.0 (2022-07-24)
|
||||
- 开源, 包含 LLVM 后端, 可运行简单例子
|
||||
- v0.0.0 (2018-00-00)
|
||||
- 凹名字诞生
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
// 版权 @2019 凹语言 作者。保留所有权利。
|
||||
// https://wa-lang.org
|
||||
// https://github.com/wa-lang
|
||||
-->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400" fill="none">
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
<animate attributeName="r" values="0;5;5;0" dur="1s" repeatCount="1" />
|
||||
</circle>
|
||||
|
||||
<path d="M200 230L234 264L268 230M200 230L166 264L132 230" fill="none" stroke="white" stroke-width="8" stroke-linecap="round" stroke-dasharray="0,0,0,192">
|
||||
<path d="M200 230L234 264L268 230M200 230L166 264L132 230" stroke="white" stroke-width="8" stroke-linecap="round" stroke-dasharray="0,0,0,192">
|
||||
<animate attributeType="XML" attributeName="stroke-dasharray" values="0,0,0,192;0,96,96,0; 192,0,0,0" keyTimes="0; 0.5;1" fill="freeze" dur="1.5s" repeatCount="1" />
|
||||
</path>
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
// 版权 @2019 凹语言 作者。保留所有权利。
|
||||
// https://wa-lang.org
|
||||
// https://github.com/wa-lang
|
||||
-->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400" fill="none">
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
<animate attributeName="r" values="0;5;5;0" dur="1s" repeatCount="1" />
|
||||
</circle>
|
||||
|
||||
<path d="M200 230L234 264L268 230M200 230L166 264L132 230" fill="none" stroke="white" stroke-width="8" stroke-linecap="round" stroke-dasharray="0,0,0,192">
|
||||
<path d="M200 230L234 264L268 230M200 230L166 264L132 230" stroke="white" stroke-width="8" stroke-linecap="round" stroke-dasharray="0,0,0,192">
|
||||
<animate attributeType="XML" attributeName="stroke-dasharray" values="0,0,0,192;0,96,96,0; 192,0,0,0" keyTimes="0; 0.5;1" fill="freeze" dur="1.5s" repeatCount="1" />
|
||||
</path>
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
// 版权 @2019 凹语言 作者。保留所有权利。
|
||||
// https://wa-lang.org
|
||||
// https://github.com/wa-lang
|
||||
-->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400" fill="none">
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 970 B |
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
<!--
|
||||
<!--
|
||||
// 版权 @2019 凹语言 作者。保留所有权利。
|
||||
// https://wa-lang.org
|
||||
// https://github.com/wa-lang
|
||||
-->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 628 B After Width: | Height: | Size: 635 B |
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
// 版权 @2019 凹语言 作者。保留所有权利。
|
||||
// https://wa-lang.org
|
||||
// https://github.com/wa-lang
|
||||
-->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 619 B After Width: | Height: | Size: 626 B |
|
|
@ -1,6 +1,6 @@
|
|||
<!--
|
||||
// 版权 @2019 凹语言 作者。保留所有权利。
|
||||
// https://wa-lang.org
|
||||
// https://github.com/wa-lang
|
||||
-->
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 591 B After Width: | Height: | Size: 598 B |
|
|
@ -1,14 +0,0 @@
|
|||
<!doctype html>
|
||||
|
||||
<head>
|
||||
<title>wa-lang/wa</title>
|
||||
|
||||
<!-- // 凹语言 go get 重定向 -->
|
||||
<meta name="go-import" content="wa-lang.org/wa git https://github.com/wa-lang/wa">
|
||||
<meta name="go-source" content="wa-lang.org/wa git https://github.com/wa-lang/wa https://github.com/wa-lang/wa/tree/master{/dir} https://github.com/wa-lang/wa/blob/master{/dir}/{file}#L{line}">
|
||||
</head>
|
||||
|
||||
<div>
|
||||
<h1>wa-lang/wa</h1>
|
||||
<p>hello https://github.com/wa-lang/wa</p>
|
||||
</div>
|
||||
|
|
@ -1,410 +0,0 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Rocher';
|
||||
src: url('./RocherColorGX.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-palette-values --pink {
|
||||
font-family: Rocher;
|
||||
base-palette: 1;
|
||||
}
|
||||
|
||||
@font-palette-values --mint {
|
||||
font-family: Rocher;
|
||||
base-palette: 7;
|
||||
}
|
||||
|
||||
:root {
|
||||
--wa-color: #00A39A;
|
||||
--wa-bg: #2e2e2e;
|
||||
--wa-bg2: #202020;
|
||||
--wa-trans: translate(-50%, -50%);
|
||||
--wa-handle-dir: calc(50% - 30px);
|
||||
--wa-shadow:
|
||||
rgb(45 35 66 / 40%) 0 2px 4px,
|
||||
rgb(45 35 66 / 30%) 0 7px 13px -3px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
position: absolute;
|
||||
top: calc(50% - 355px);
|
||||
left: calc(50% - 392px);
|
||||
color: var(--wa-bg);
|
||||
font-family: Rocher;
|
||||
font-size: 2em;
|
||||
font-palette: --mint;
|
||||
}
|
||||
|
||||
#box {
|
||||
background: var(--wa-color);
|
||||
border-radius: 1em;
|
||||
box-shadow: 0.2em 0.2em 2em rgba(46, 46, 46, 0.5);
|
||||
padding: 0.5em;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: var(--wa-trans)
|
||||
}
|
||||
|
||||
#game {
|
||||
background: var(--wa-color);
|
||||
border-radius: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#game::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-shadow:
|
||||
10px 10px 30px #006E67,
|
||||
-10px -10px 40px #006E67,
|
||||
inset -10px -10px 10px #008880,
|
||||
inset 10px 10px 10px #008880,
|
||||
inset 10px 10px 10px var(--wa-color);
|
||||
filter: blur(3px);
|
||||
}
|
||||
|
||||
.game__title {
|
||||
font-size: 1.2em;
|
||||
text-align: left;
|
||||
padding: 0 0.6em;
|
||||
margin: 0.2em 0;
|
||||
display: block;
|
||||
font-family: 'Rocher';
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.game__title sup {
|
||||
font-size: 0.6em;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#game__screen {
|
||||
background: var(--wa-bg);
|
||||
box-shadow: var(--wa-shadow), var(--wa-bg2) 0 -3px 0 inset;
|
||||
border-radius: 1em;
|
||||
margin: 0 0.3em 0.3em;
|
||||
padding: 0.6em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#game__screen-content {
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
border-radius: 0.5em;
|
||||
overflow: hidden;
|
||||
aspect-ratio: 1;
|
||||
place-content: center;
|
||||
background: #8C957B;
|
||||
}
|
||||
|
||||
#game__operate {
|
||||
flex: 1;
|
||||
padding: 0.3em;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#game__operate-direction,
|
||||
.game__operate-ghost {
|
||||
margin: 0.5em;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#game__operate-direction .direction__item {
|
||||
align-items: center;
|
||||
appearance: none;
|
||||
background: var(--wa-bg);
|
||||
border-radius: 50%;
|
||||
border-width: 0;
|
||||
box-shadow: var(--wa-shadow), var(--wa-bg2) 0 -3px 0 inset;
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
position: absolute;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
touch-action: manipulation;
|
||||
white-space: nowrap;
|
||||
will-change: box-shadow, transform;
|
||||
}
|
||||
|
||||
#game__operate-direction .direction__item::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: -10px;
|
||||
right: -10px;
|
||||
bottom: -10px;
|
||||
}
|
||||
|
||||
#game__operate-direction .direction__item::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0 0.5em 0.866em 0.5em;
|
||||
border-color: transparent transparent #fff transparent;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
#game__operate-direction--up {
|
||||
top: 0;
|
||||
left: var(--wa-handle-dir);
|
||||
}
|
||||
|
||||
#game__operate-direction--up::before {
|
||||
transform: var(--wa-trans)
|
||||
}
|
||||
|
||||
#game__operate-direction--right {
|
||||
top: var(--wa-handle-dir);
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#game__operate-direction--right::before {
|
||||
transform: var(--wa-trans) rotate(90deg);
|
||||
}
|
||||
|
||||
#game__operate-direction--down {
|
||||
bottom: 0;
|
||||
left: var(--wa-handle-dir);
|
||||
}
|
||||
|
||||
#game__operate-direction--down::before {
|
||||
transform: var(--wa-trans) rotate(180deg);
|
||||
}
|
||||
|
||||
#game__operate-direction--left {
|
||||
top: var(--wa-handle-dir);
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#game__operate-direction--left::before {
|
||||
transform: var(--wa-trans) rotate(270deg);
|
||||
}
|
||||
|
||||
.game__operate-ghost {
|
||||
border-radius: 50%;
|
||||
background: rgba(46, 46, 46, 0.8);
|
||||
box-shadow: var(--wa-shadow), var(--wa-bg2) 0 -3px 0 inset;
|
||||
}
|
||||
|
||||
.game__operate-ghost::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
background: var(--wa-bg);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: var(--wa-trans);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.game__operate-ghost span {
|
||||
background: var(--wa-bg);
|
||||
height: 55px;
|
||||
width: 55px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.game__operate-ghost span:nth-child(1) {
|
||||
top: 10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-radius: 10px 10px 0 0;
|
||||
box-shadow: var(--wa-shadow), var(--wa-bg2) -3px 0px 0 inset;
|
||||
}
|
||||
|
||||
.game__operate-ghost span::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0 10px 16px 10px;
|
||||
border-color: transparent transparent var(--wa-bg2) transparent;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.game__operate-ghost span:nth-child(1)::before {
|
||||
transform: var(--wa-trans);
|
||||
}
|
||||
|
||||
.game__operate-ghost span:nth-child(2) {
|
||||
top: 50%;
|
||||
left: 10px;
|
||||
transform: translateY(-50%);
|
||||
border-radius: 10px 0 0 10px;
|
||||
box-shadow: var(--wa-shadow), var(--wa-bg2) 0px -3px 0 inset;
|
||||
}
|
||||
|
||||
.game__operate-ghost span:nth-child(2)::before {
|
||||
transform: var(--wa-trans) rotate(270deg);
|
||||
}
|
||||
|
||||
.game__operate-ghost span:nth-child(3) {
|
||||
top: 50%;
|
||||
right: 10px;
|
||||
transform: translateY(-50%);
|
||||
border-radius: 0 10px 10px 0;
|
||||
box-shadow: var(--wa-shadow), var(--wa-bg2) -3px 0px 0 inset;
|
||||
}
|
||||
|
||||
.game__operate-ghost span:nth-child(3)::before {
|
||||
transform: var(--wa-trans) rotate(90deg);
|
||||
}
|
||||
|
||||
.game__operate-ghost span:nth-child(4) {
|
||||
bottom: 10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-radius: 0 0 10px 10px;
|
||||
box-shadow: var(--wa-shadow), var(--wa-bg2) -3px -3px 0 inset;
|
||||
}
|
||||
|
||||
.game__operate-ghost span:nth-child(4)::before {
|
||||
transform: var(--wa-trans) rotate(180deg);
|
||||
}
|
||||
|
||||
.game__operate-assist {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.game__operate-assist--btns {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 8em;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
.game__operate-assist--btns span:nth-child(1)::before,
|
||||
.game__operate-assist--btns span:nth-child(1)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 3em;
|
||||
height: 2em;
|
||||
align-items: center;
|
||||
appearance: none;
|
||||
background: var(--wa-bg);
|
||||
border-width: 0;
|
||||
box-shadow: var(--wa-shadow), var(--wa-bg2) 0 -3px 0 inset;;
|
||||
box-sizing: border-box;
|
||||
height: 1em;
|
||||
width: 3em;
|
||||
border-radius: 0.5em;
|
||||
}
|
||||
|
||||
.game__operate-assist--btns span:nth-child(1)::before {
|
||||
left: 0.6em;
|
||||
}
|
||||
|
||||
.game__operate-assist--btns span:nth-child(1)::after {
|
||||
left: 4.6em;
|
||||
}
|
||||
|
||||
.game__operate-assist--btns span:nth-child(2)::before,
|
||||
.game__operate-assist--btns span:nth-child(2)::after {
|
||||
position: absolute;
|
||||
width: 3em;
|
||||
height: 2em;
|
||||
color: var(--wa-bg2);
|
||||
font-size: 0.6em;
|
||||
font-family: 'Rocher';
|
||||
font-palette: --pink;
|
||||
}
|
||||
|
||||
.game__operate-assist--btns span:nth-child(2)::before {
|
||||
content: "SELECT";
|
||||
top: 2em;
|
||||
left: 1.4em;
|
||||
}
|
||||
|
||||
.game__operate-assist--btns span:nth-child(2)::after {
|
||||
content: "START";
|
||||
top: 2em;
|
||||
left: 8.4em;
|
||||
}
|
||||
|
||||
.game__operate-assist--audio {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 5em;
|
||||
height: 3em;
|
||||
margin: 0 0 0.6em 0.6em;
|
||||
}
|
||||
|
||||
.game__operate-assist--audio::before {
|
||||
--s: 6px;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background:radial-gradient(var(--wa-bg) 24%,#0000 15%);
|
||||
background-size: var(--s) var(--s);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
#box {
|
||||
width: 768px;
|
||||
max-width: 768px;
|
||||
}
|
||||
|
||||
.game__title {
|
||||
margin: 0.8em 0.2em;
|
||||
}
|
||||
|
||||
.game__operate-assist {
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.tips {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#box {
|
||||
width: 100%;
|
||||
max-width: fit-content;
|
||||
}
|
||||
|
||||
.game__operate-ghost {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#game__operate-direction .direction__item:active {
|
||||
box-shadow: var(--wa-bg2) 0 3px 7px inset;
|
||||
transform: translateY(2px);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
||||
<title>Wa: Snack Game</title>
|
||||
<link rel="stylesheet" href="index.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<span class="tips">Please use the keyboard to control</span>
|
||||
<div id="box">
|
||||
<div id="game">
|
||||
<span class="game__title">
|
||||
Wa Snack Game
|
||||
<sup><em>2022</em></sup>
|
||||
</span>
|
||||
<div id="game__screen">
|
||||
<div id="game__screen-content"></div>
|
||||
</div>
|
||||
<div id="game__operate">
|
||||
<div class="game__operate-ghost"><span></span><span></span><span></span><span></span></div>
|
||||
<div class="game__operate-assist">
|
||||
<span class="game__operate-assist--btns"><span></span><span></span></span>
|
||||
<span class="game__operate-assist--audio"></span>
|
||||
</div>
|
||||
<div id="game__operate-direction">
|
||||
<span id="game__operate-direction--up" class="direction__item"></span>
|
||||
<span id="game__operate-direction--right" class="direction__item"></span>
|
||||
<span id="game__operate-direction--down" class="direction__item"></span>
|
||||
<span id="game__operate-direction--left" class="direction__item"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script type="text/javascript" src="./index.js"></script>
|
||||
<script type="text/javascript" src="./wa_app.js"></script>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
(() => {
|
||||
document.documentElement.addEventListener('touchstart', (event) => {
|
||||
if (event.touches.length > 1) { event.preventDefault(); }
|
||||
}, { passive: false });
|
||||
|
||||
let lastTouchEnd = 0;
|
||||
document.documentElement.addEventListener('touchend', (event) => {
|
||||
let now = Date.now();
|
||||
if (now - lastTouchEnd <= 500) { event.preventDefault() }
|
||||
lastTouchEnd = now;
|
||||
}, { passive: false });
|
||||
|
||||
})()
|
||||
|
||||
const IS_MOBILE = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
|
||||
const DIR_ID_PREFIX = 'game__operate-direction--';
|
||||
const MOBILE_DIR_MAP = [
|
||||
{ id: `${DIR_ID_PREFIX}up`, keyCode: 38 },
|
||||
{ id: `${DIR_ID_PREFIX}down`, keyCode: 40 },
|
||||
{ id: `${DIR_ID_PREFIX}left`, keyCode: 37 },
|
||||
{ id: `${DIR_ID_PREFIX}right`, keyCode: 39 },
|
||||
];
|
||||
|
Before Width: | Height: | Size: 174 KiB |
|
|
@ -1,187 +0,0 @@
|
|||
(() => {
|
||||
class WaApp {
|
||||
constructor() {
|
||||
this._inst = null;
|
||||
this._wa_print_buf = "";
|
||||
}
|
||||
|
||||
init(url) {
|
||||
let app = this;
|
||||
let importsObject = {
|
||||
wasi_snapshot_preview1: new function () {
|
||||
this.args_get = () => { return 0; }
|
||||
this.args_sizes_get = () => { return 0; }
|
||||
|
||||
this.clock_res_get = () => { return 0; }
|
||||
this.clock_time_get = () => { return 0; }
|
||||
this.environ_get = () => { return 0; }
|
||||
this.environ_sizes_get = () => { return 0; }
|
||||
|
||||
this.fd_advise = () => { return 0; }
|
||||
this.fd_allocate = () => { return 0; }
|
||||
this.fd_close = () => { return 0; }
|
||||
this.fd_datasync = () => { return 0; }
|
||||
this.fd_fdstat_get = () => { return 0; }
|
||||
this.fd_fdstat_set_flags = () => { return 0; }
|
||||
this.fd_fdstat_set_rights = () => { return 0; }
|
||||
this.fd_filestat_get = () => { return 0; }
|
||||
this.fd_filestat_set_size = () => { return 0; }
|
||||
this.fd_filestat_set_times = () => { return 0; }
|
||||
this.fd_pread = () => { return 0; }
|
||||
this.fd_prestat_get = () => { return 0; }
|
||||
this.fd_prestat_dir_name = () => { return 0; }
|
||||
this.fd_pwrite = () => { return 0; }
|
||||
this.fd_read = () => { return 0; }
|
||||
this.fd_readdir = () => { return 0; }
|
||||
this.fd_renumber = () => { return 0; }
|
||||
this.fd_seek = () => { return 0; }
|
||||
this.fd_sync = () => { return 0; }
|
||||
this.fd_tell = () => { return 0; }
|
||||
this.fd_write = () => { return 0; }
|
||||
this.path_create_directory = () => { return 0; }
|
||||
this.path_filestat_get = () => { return 0; }
|
||||
this.path_filestat_set_times = () => { return 0; }
|
||||
this.path_link = () => { return 0; }
|
||||
this.path_open = () => { return 0; }
|
||||
this.path_readlink = () => { return 0; }
|
||||
this.path_remove_directory = () => { return 0; }
|
||||
this.path_rename = () => { return 0; }
|
||||
this.path_symlink = () => { return 0; }
|
||||
this.path_unlink_file = () => { return 0; }
|
||||
|
||||
this.poll_oneoff = () => { return 0; }
|
||||
this.proc_exit = () => { return 0; }
|
||||
this.random_get = () => { return 0; }
|
||||
this.sched_yield = () => { return 0; }
|
||||
this.sock_accept = () => { return 0; }
|
||||
this.sock_recv = () => { return 0; }
|
||||
this.sock_send = () => { return 0; }
|
||||
this.sock_shutdown = () => { return 0; }
|
||||
},
|
||||
wa_js_env: new function () {
|
||||
this.waPrintI32 = (i) => {
|
||||
app._wa_print_buf += i
|
||||
}
|
||||
|
||||
this.waPrintRune = (c) => {
|
||||
let ch = String.fromCodePoint(c);
|
||||
if (ch == '\n') {
|
||||
console.log(app._wa_print_buf);
|
||||
app._wa_print_buf = "";
|
||||
}
|
||||
else {
|
||||
app._wa_print_buf += ch
|
||||
}
|
||||
}
|
||||
|
||||
this.waPuts = (prt, len) => {
|
||||
let s = app.getString(prt, len);
|
||||
app._wa_print_buf += s
|
||||
}
|
||||
|
||||
this.rand = (m) => {
|
||||
return parseInt(Math.random() * m)
|
||||
}
|
||||
|
||||
this.newCanvas = (w, h) => {
|
||||
let canvas = document.createElement('canvas');
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
canvas.id = 0; //!!!!!
|
||||
|
||||
const waContent = document.getElementById('game__screen-content');
|
||||
waContent.appendChild(canvas);
|
||||
|
||||
function getPointOnCanvas(x, y) {
|
||||
var bbox = canvas.getBoundingClientRect();
|
||||
return {
|
||||
x: parseInt((x - bbox.left) * (canvas.width / bbox.width)),
|
||||
y: parseInt((y - bbox.top) * (canvas.height / bbox.height))
|
||||
};
|
||||
}
|
||||
|
||||
function onMouseDown(ev) {
|
||||
let pt = getPointOnCanvas(ev.clientX, ev.clientY);
|
||||
app._inst.exports['snake$canvas.OnMouseDown'](canvas.id, pt.x, pt.y);
|
||||
}
|
||||
|
||||
function onMouseUp(ev) {
|
||||
let pt = getPointOnCanvas(ev.clientX, ev.clientY);
|
||||
app._inst.exports['snake$canvas.OnMouseUp'](canvas.id, pt.x, pt.y);
|
||||
}
|
||||
|
||||
function onKeyDown(ev) {
|
||||
app._inst.exports['snake$canvas.OnKeyDown'](canvas.id, ev.keyCode);
|
||||
}
|
||||
|
||||
function onKeyUp(ev) {
|
||||
app._inst.exports['snake$canvas.OnKeyUp'](canvas.id, ev.keyCode);
|
||||
}
|
||||
|
||||
if (IS_MOBILE) {
|
||||
MOBILE_DIR_MAP.forEach((dir) => {
|
||||
const el = document.getElementById(dir.id);
|
||||
el.addEventListener('touchstart', (ev) => onKeyDown({ keyCode: dir.keyCode }));
|
||||
el.addEventListener('touchend', (ev) => onKeyUp({ keyCode: dir.keyCode }));
|
||||
});
|
||||
}
|
||||
|
||||
canvas.addEventListener('mousedown', onMouseDown, true);
|
||||
canvas.addEventListener('mouseup', onMouseUp, true);
|
||||
canvas.addEventListener('keydown', onKeyDown, true);
|
||||
canvas.addEventListener('keyup', onKeyUp, true);
|
||||
canvas.tabIndex = -1; //tabindex
|
||||
canvas.focus();
|
||||
|
||||
this._ctx = canvas.getContext('2d');
|
||||
this._canvas = canvas;
|
||||
return canvas.id;
|
||||
}
|
||||
this.updateCanvas = (id, block, data) => {
|
||||
let img = this._ctx.createImageData(this._canvas.width, this._canvas.height);
|
||||
let buf_len = this._canvas.width * this._canvas.height * 4
|
||||
let buf = app.memUint8Array(data, buf_len);
|
||||
for (var i = 0; i < buf_len; i++) {
|
||||
img.data[i] = buf[i];
|
||||
}
|
||||
this._ctx.putImageData(img, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
WebAssembly.instantiateStreaming(fetch(url), importsObject).then(res => {
|
||||
this._inst = res.instance;
|
||||
this._inst.exports._start();
|
||||
const timer = setInterval(gameLoop, 150);
|
||||
})
|
||||
}
|
||||
|
||||
mem() {
|
||||
return this._inst.exports.memory;
|
||||
}
|
||||
|
||||
memView(addr, len) {
|
||||
return new DataView(this._inst.exports.memory.buffer, addr, len);
|
||||
}
|
||||
|
||||
memUint8Array(addr, len) {
|
||||
return new Uint8Array(this.mem().buffer, addr, len)
|
||||
}
|
||||
|
||||
getString(addr, len) {
|
||||
return new TextDecoder("utf-8").decode(this.memView(addr, len));
|
||||
}
|
||||
|
||||
setString(addr, len, s) {
|
||||
const bytes = new TextEncoder("utf-8").encode(s);
|
||||
if (len > bytes.length) { len = bytes.length; }
|
||||
this.MemUint8Array(addr, len).set(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
function gameLoop() {
|
||||
window['waApp']._inst.exports['snake.Step']();
|
||||
}
|
||||
|
||||
window['waApp'] = new WaApp();
|
||||
window['waApp'].init("./snake.wasm")
|
||||
})()
|
||||
|
|
@ -1,780 +0,0 @@
|
|||
## 凹语言中文语法设计
|
||||
|
||||
## 理念
|
||||
|
||||
凹语言的中文语法(下面简称凹中文版)的设计理念是:
|
||||
|
||||
- 简洁:尽量减少冗余信息。例如:关键字尽量选择单字。
|
||||
- 易读:代码读起来应当尽量贴近自然语言。关键语法信息应当足够醒目。
|
||||
- 灵活:不必拘泥于英文编程语言的传统语法,可以尝试灵活的设计。
|
||||
- 符号:作为中文编程语言,并不排除,而是妙用标点符号和拼音字母。
|
||||
|
||||
凹中文版的语法设计主要受到了[文言](https://wy-lang.org/)编程语言的启发。
|
||||
但与文言编程语言的主要区别,在于上述的第一条理念:简洁。
|
||||
|
||||
我认为文言文相对于白话文,最大的特色就是简洁。
|
||||
而简洁的需求正是由于时代的背景所决定的:当时的书写工具和文字承载工具都非常昂贵,因此惜字如金并不只是比喻。
|
||||
|
||||
因此,为了尽量继承文言文的简洁精神,我在设计凹中语法时,与文言编程语言的风格有了很大的区别。
|
||||
|
||||
凹中文版的语法设计还参考了:
|
||||
|
||||
- 凹英文语法。凹语言的中文和英文语法是相互兼容的,每个语法结构都能找到对应。并且到现在为止,凹中的解析前端还是和凹语言英文语法强耦合的。
|
||||
- Go语言。凹语言初版的实现是用Go写的,且前端代码也是从Go语言的前端移植过来的,因此在设计与实现中会更倾向于贴近Go的风格。
|
||||
- Kotlin和D语言。凹语言的中文语法设计中,也借鉴了一些Kotlin和D的语法设计。
|
||||
|
||||
**提示**:凹中文版语法还处于早期探索阶段,很有可能会发生变化。
|
||||
|
||||
我们计划在完成凹中文前端的重构(即完全脱离现有的Go前端)之时,得到一套稳定的中文语法。
|
||||
|
||||
现在可运行的示例,请参考凹语言工程中的[可执行示例](https://gitee.com/wa-lang/wa/tree/frontend_wz/waroot/examples/wz/)。
|
||||
|
||||
## 简单示例
|
||||
|
||||
下面是Hello World的凹中文版示例:
|
||||
|
||||
```wz
|
||||
引于 "书"
|
||||
【启】:
|
||||
书·说:“你好,凹语言!”
|
||||
。
|
||||
```
|
||||
|
||||
上面的代码中:
|
||||
|
||||
- `引于`(`import`)是关键字。
|
||||
- `【】`是函数定义的标志,相当于`func`。
|
||||
- `书·说:“你好,凹语言”`是函数调用,相当于:`fmt.println(“你好,凹语言!”)`
|
||||
- `:`和`。`是一个程序块的开始和结束符号。相当于`{`和`}`。
|
||||
|
||||
这些设计都是为了简洁和易读原则而做出的选择。
|
||||
|
||||
这段代码用凹英文版写出来就是:
|
||||
|
||||
```wa
|
||||
import "fmt"
|
||||
|
||||
func main {
|
||||
fmt.println("你好,凹语言!")
|
||||
}
|
||||
```
|
||||
|
||||
下面是一个更复杂的示例,展示了其他几个已经实现的基本语法:
|
||||
|
||||
```wz
|
||||
|
||||
引于 "书"
|
||||
|
||||
【启】:
|
||||
// 基本函数调用
|
||||
书·说:"你好,凹语言!"
|
||||
|
||||
// 调用自定义函数
|
||||
书·说:"[自定义函数]:40+2="
|
||||
书·曰:加:40、2
|
||||
|
||||
// 简单的条件判断
|
||||
书·说:"[条件判断]:"
|
||||
设零之数 = 0
|
||||
若零==1则:
|
||||
书·说:"是的,零和1是相等的。"
|
||||
否则:
|
||||
书·说:"错了,零和1是不同的。"
|
||||
。
|
||||
|
||||
// 简单的自定义类型
|
||||
设某=点{横:10, 纵:2}
|
||||
书·说:"[自定义类型]点(10,2)的纵坐标和横坐标之和:"
|
||||
书·曰:某·横 + 某·纵
|
||||
书·说:"[自定义类型]点(10,2)的纵坐标和横坐标之平方和:"
|
||||
书·曰:某·平方距:
|
||||
|
||||
|
||||
// 循环
|
||||
|
||||
// 类似range
|
||||
书·说:"[简单范围] 从0到3:"
|
||||
自0至3,有i:
|
||||
书·曰:i
|
||||
。
|
||||
|
||||
// 类似三段式for循环,注意,由于中英文语义不同,这里的j==8是停止条件,和for循环的“持续条件”正好相反
|
||||
书·说:"[循环语句] 从0到8:"
|
||||
从j=0,到j==8,有j++:
|
||||
书·曰:j
|
||||
。
|
||||
|
||||
书·说:"[循环语句] 从10到0:"
|
||||
设步=1
|
||||
从i=10,到i>=100,有:
|
||||
i+=步
|
||||
书·曰:i
|
||||
。
|
||||
|
||||
// 类似until语句
|
||||
书·说:"[直到语句] 直到5:"
|
||||
设i=0
|
||||
直到i>=5,有:
|
||||
书·曰:i
|
||||
i++
|
||||
。
|
||||
|
||||
// 多路选择
|
||||
书·说:"[多路输出]k=3"
|
||||
设k=3
|
||||
当k:
|
||||
为1,则:书·说:"一"
|
||||
为2,则:书·说:"二"
|
||||
为3,则:书·说:"三"
|
||||
否则:书·说:"不中"
|
||||
。
|
||||
。
|
||||
|
||||
《点》:
|
||||
横之数
|
||||
纵之数
|
||||
。
|
||||
|
||||
【点·平方距】() => 数 :
|
||||
归于:此·纵*此·纵 + 此·横*此·横
|
||||
。
|
||||
|
||||
【加】(甲, 乙之数) => 数 :
|
||||
归于:甲+乙
|
||||
。
|
||||
```
|
||||
|
||||
## 详细介绍
|
||||
|
||||
本节详细介绍上面几种语法的设计,以及为何这样设计的缘由。
|
||||
|
||||
### 函数定义和函数块
|
||||
|
||||
先看Hello World:
|
||||
|
||||
```wz
|
||||
引于 "书"
|
||||
【启】:
|
||||
书·说:“你好,凹语言!”
|
||||
。
|
||||
```
|
||||
|
||||
在凹中文版里,函数的定义用实心方括号`【】`来表达。方括号之中的是函数名称。
|
||||
|
||||
这里的`【启】`函数,是整个程序的启动函数,即我们常说的`主函数`。
|
||||
本来我打算直接用以没有直接选择`【主】`,但写出来之后,这段代码读起来就有“主说:‘要有光’”的味道了,总有点怪怪的感觉。所以只好换了个字。
|
||||
|
||||
其实最开始我的设计几乎相当于对英文版的关键字替换:
|
||||
|
||||
```wz
|
||||
函数 主函数 {
|
||||
打印(“温故而知新,可以为师矣。”)
|
||||
}
|
||||
```
|
||||
|
||||
这里遇到了设计的第一个问题,直译的关键字变成中文之后读起来太过生硬了,而且也不够简洁。
|
||||
|
||||
这种风格的中文编程早就存在了,我相信这也是很多人一听到“中文编程”,就会产生的第一印象。
|
||||
|
||||
我认为这里有历史的原因,但也有习惯的原因。
|
||||
|
||||
”函数“、”打印“这种双字词,最开始翻译时,是用于技术文章,而不是程序的,因此简洁并不是翻译者的第一要务。
|
||||
当时面对这些中文还没有的技术性新概念,采用这种翻译是非常合理的。
|
||||
|
||||
实际上,最早的英文编程语言中,关键字读起来也是冗长而生硬的:"procedure"(后来被简化成proc),”function“(最近才简化成func\fun\fn),
|
||||
可以看出英文关键字的演变也是有一个从陌生到习惯、从冗长明确到简洁的过程的。
|
||||
|
||||
中文没法像英文那么容易缩略,但中文也有优势,我们有非常多的单字词可以选择。
|
||||
因此我当时选择了”方“这个字,表示”方程、配方“的意味,用来替代”函数“;又选择了”曰“这个所有人都认识的单字来替代”打印“:
|
||||
|
||||
```wz
|
||||
方 启 {
|
||||
曰(”温故而知新,可以为师矣。“)
|
||||
}
|
||||
```
|
||||
|
||||
这样已经有一点”文言”编程语言的意味了。上面的代码完全可以用“文言”编程语言的风格来读:
|
||||
|
||||
```
|
||||
吾有一方,其名曰【启】,其方如下:
|
||||
一、曰:“温故而知新,可以为师矣。”
|
||||
方止于此。
|
||||
```
|
||||
|
||||
这个风格其实用来编程已经没有太大问题,既有简洁又清晰,相当于“文言”编程语言的简略版,其效用和英文版几乎一致。
|
||||
|
||||
但我多看了几趟,仍然觉得有点别扭。
|
||||
|
||||
再看看、再瞅瞅,终于发现别扭的地方了:我们没有适应过高度抽象话的中文编程,所以下意识还是会把它当做中文句子来读。
|
||||
那么,不符合中文文本惯例的地方,就会显得有些跳脱,也就有了别扭感。
|
||||
|
||||
而这里最大的别扭感,来自于【空格】的使用。标准的中文文本里,是鲜有空格的。
|
||||
|
||||
我们的标点符号是全角,本身就自带了空白分隔。因此在这里用空格直接连接“方”和“启”字,就会有别扭感。
|
||||
|
||||
空格问题有两个解决办法:
|
||||
|
||||
一是把空格改成文本和标点,回归中文叙述。这也是“文言”编程语言选择的办法。因此即使它用的都是文言文,我们也觉得读起来比较顺畅。
|
||||
但这个办法会增加大量冗余文字,在程序比较简单的时候还行,一旦比较复杂后,多出来的字词读起来就会浪费精力了;
|
||||
另一个问题是这样平铺直述的表达是线性的,用来表示嵌套的逻辑时很难搞清楚层级。
|
||||
|
||||
第二个办法,就是巧妙地利用中文标点符号自带的空白,以他们来顶替空格的作用。
|
||||
|
||||
```wz
|
||||
【启】:
|
||||
曰:“温故而知新,可以为师矣。”
|
||||
。
|
||||
```
|
||||
|
||||
仔细观察,这里不论是实心方括号`【】`,还是中文冒号`:`,都既有充实的间隔感,又自带了空白,而且是符合中文阅读时内心的停顿节奏的空白。
|
||||
因此即使我用了很奇怪的组合`:`和`。`来替代`{}`,都不会显得很别扭。
|
||||
(实际上我最初选的结束符号是办公文本常见的`■`,这个符号本身就是结束符的意思。但由于这个符号远没有句号`。`好打出来,而它在凹中文版中又那么常用,因此我放弃它改用句号了。)
|
||||
|
||||
这个办法还有一个好处,类似`【】`这样的中文标点非常醒目,因此用来定位程序关键要素时很方便。读代码时,目光一扫就能感受到大概有几个函数。
|
||||
|
||||
看看上面的“文言”的例子,最抓眼球的字,是不是就是`【启】`?这也是为什么我选择实心方括号而不是空心的原因。
|
||||
|
||||
另外,由于`【】`的特殊性,我们连`方`这个关键词都可以不用了。
|
||||
|
||||
至此,读者应该还有一个问题,那问什么要把`{}`也换掉呢?
|
||||
|
||||
```wz
|
||||
【启】{
|
||||
曰(”温故而知新,可以为师矣。“)
|
||||
}
|
||||
```
|
||||
|
||||
这确实也是让我头疼的一个选择。'{'的作用本质上是开启新的名字空间。编程语言的名字空间是树状结构,`{}`开辟一个新的子空间,这里头可以继续引用上层的名称,但也可以新建只有自己(以及自己的子孙空间)才能访问的局部名称。要在程序中表达这样一个新的子空间,需要一个开始符号和一个结束符号。
|
||||
|
||||
常见的编程语言有三种方式来表达子空间:
|
||||
|
||||
- 括号。包括C系列语言的花括号`{}`和LISP系的`()`。
|
||||
- 缩进。Python等语言采用的方法。优点是阅读的简洁性更强,更符合自然语言习惯。缺点是层次太多以后容易搞错层级。
|
||||
- 单独的`end`。Lua等语言用这个方法。很多时候开启新的子空间之前都有特殊的程序结构,例如上面的“函数定义”,所以不需要单独指定开启字符。但为了避免混淆,需要指定一个结束字符。`end`就是最常用的结束关键词。这种方式比`{}`更接近自然语言的风格,同时也不用考虑缩进的问题,算是前两种方法的折衷。
|
||||
|
||||
我本来选择的是第三种:
|
||||
|
||||
```wz
|
||||
【启】
|
||||
曰(“温故而知新,可以为师矣。”)
|
||||
。
|
||||
```
|
||||
|
||||
但多看了几遍之后又觉得有点别扭,最后还是把`【启】`后面的冒号加上了:
|
||||
|
||||
```wz
|
||||
【启】:
|
||||
曰(“温故而知新,可以为师矣。”)
|
||||
。
|
||||
```
|
||||
|
||||
这个冒号虽然实际上是冗余的,但在阅读时提供的节奏感,我现在认为是必要的。
|
||||
这样实际上又回归了和`{}`完全等效的局面:`:`对应`{`,`。`对应`}`。还挺合适。
|
||||
|
||||
最后一个问题,问什么用冒号来表示函数调用:
|
||||
|
||||
```wz
|
||||
曰:“温故而知新,可以为师矣。”
|
||||
```
|
||||
|
||||
```wz
|
||||
曰(“温故而知新,可以为师矣。”)
|
||||
```
|
||||
|
||||
再对比一下,其实没有本质的区别。非要说的话,怪我选的这个例子吧。实在是和《论语》 原文太像了:
|
||||
|
||||
```
|
||||
子曰:“温故而知新,可以为师矣。”
|
||||
```
|
||||
|
||||
这种平铺直叙的命令式,实在是太符合自然语言习惯了。
|
||||
|
||||
那么冒号会不会遇到问题呢?多个参数时怎么办?参数又是函数调用的嵌套时,又该怎么办?这个问题比较复杂,我会在后面函数相关的话题里专门描述。总之,一旦嵌套了,还是得有括号帮忙。
|
||||
|
||||
至此我们完成了HelloWorld的对比,也大致了解了凹中文版在语法设计时所衡量的因素。接下来,我们看看更复杂的情况。
|
||||
|
||||
|
||||
### 变量
|
||||
|
||||
凹英文版变量的定义方式如下:
|
||||
|
||||
```wa
|
||||
// 使用关键字var
|
||||
var a: int = 1
|
||||
a = a + 1
|
||||
|
||||
// 快速定义语法
|
||||
b := 2
|
||||
```
|
||||
|
||||
凹语言的变量定义和Go语言基本一致,主要的区别在于变量和类型之间用`:`分隔,更接近Kotlin等语言的风格。
|
||||
|
||||
在凹中文版中,用关键字“**设**”来表示变量的定义:
|
||||
|
||||
```wz
|
||||
设甲=1
|
||||
甲=甲+1
|
||||
```
|
||||
|
||||
而用来赋值的操作符是`=`和英文版一致。
|
||||
|
||||
当然,类似abc这样的拼音符号,我认为在中文编程中并不需要避讳。因此上面的代码也可以写成:
|
||||
|
||||
```wz
|
||||
设a=1
|
||||
a=a+1
|
||||
```
|
||||
|
||||
要指明变量的类型,如果按照英文版的方式来的话,会是这样:
|
||||
|
||||
```wz
|
||||
设a:数=1
|
||||
a=a+1
|
||||
```
|
||||
|
||||
但我感觉这样读起来会有些卡顿感。由于中文缺乏空格,这里的冒号就显得太突兀了。况且冒号已经用在代码块和函数调用上了。
|
||||
|
||||
解决办法有三种。
|
||||
|
||||
一种是变量和类型一起用括号包裹起来:
|
||||
|
||||
```wz
|
||||
设(甲:数)=1
|
||||
```
|
||||
|
||||
或者类似于函数定义,用独立的符号把变量名包裹起来,而不是包裹类型:
|
||||
|
||||
```wz
|
||||
设「甲」数=1
|
||||
```
|
||||
|
||||
第三种是直接把“**之**”字做成关键字,用来替代冒号:
|
||||
|
||||
```wz
|
||||
设甲之数=1
|
||||
```
|
||||
|
||||
现在暂时没有找到最佳的方案,我决定暂时用“**之**”关键字分隔的方式。等有了更多的代码体验之后,再确定正式方案。
|
||||
|
||||
|
||||
### 数据类型
|
||||
|
||||
凹中文版最初版本只支持两个基本类型:《数》和《文》。它们分别对应与Go语言的`int`和`string`类型。更多的类型支持,留待整个编译器雏形搭建好之后再慢慢扩充。
|
||||
|
||||
复合类型中最常见的是数组(array)和映射(map)。
|
||||
|
||||
```wz
|
||||
【启】:
|
||||
// 数组
|
||||
设a=[1,2,3]
|
||||
|
||||
// 映射
|
||||
设b={1:2, 3:4, 5:6}
|
||||
。
|
||||
```
|
||||
|
||||
如果需要指明类型,则这么定义:
|
||||
|
||||
```wz
|
||||
【启】:
|
||||
// 数组
|
||||
设a之[数]=[1,2,3]
|
||||
|
||||
// 映射
|
||||
设b之{数:数}={1:2, 3:4, 5:6}
|
||||
。
|
||||
```
|
||||
|
||||
这里数组和映射的类型名称语法和英文版不一样。英文版沿用了Go的写法:
|
||||
|
||||
- 数组:`[]int`
|
||||
- 映射:`map[int]int`
|
||||
|
||||
而中文版则选择了和字面量几乎一致的形式。
|
||||
|
||||
- 数组:`[数]`
|
||||
- 映射:`{数:数}`
|
||||
|
||||
这也得益于中文版并没有采用`{}`来表示代码块,因此可以把`{}`留给映射的类型标记用。
|
||||
|
||||
TODO: 这个功能暂时还没实现。
|
||||
|
||||
### 自定义函数
|
||||
|
||||
凹英文版定义函数的语法如下:
|
||||
|
||||
```wa
|
||||
func add(a: int, b: int) => int {
|
||||
return a+b
|
||||
}
|
||||
|
||||
func main {
|
||||
println(add(1, 2))
|
||||
}
|
||||
```
|
||||
|
||||
和Go语言类似,但有两点区别:
|
||||
|
||||
- 参数列表和返回类型之间有个`=>`符号分隔,这样让函数定义在整个文件中更醒目。当前版本这个`=>`是可以省略的。
|
||||
- 如果函数参数为空,则可以把括号省略掉,比如这里的`func main`就直接接`{`了。
|
||||
|
||||
凹中文版的形式如下:
|
||||
|
||||
```wz
|
||||
【和】(a之数、b之数)=> 数:
|
||||
归于:a+b
|
||||
。
|
||||
|
||||
【启】:
|
||||
曰:和:1、2
|
||||
。
|
||||
```
|
||||
|
||||
这里:
|
||||
- 【和】是函数名,`【】`表示定义一个新函数,相当于`func`。
|
||||
- 参数列表和英文版基本一致,只不过参数的分隔由逗号`,`变成了顿号`、`。
|
||||
- `归于:`是关键字,和`return`一样。
|
||||
|
||||
这里为什么选择和英文版几乎一致的形式?是因为我做过几个其他尝试之后,并没有找到更清晰且可读的方法。
|
||||
|
||||
因此在初始版本里,这么写已经足够好了。并且也符合凹中文版的设计原则:简洁、清晰可读且妙用了符号。因此我就不去特意发明新符号去替代了。
|
||||
|
||||
这里只有一个小遗憾,`=>`这个箭头在中文里是没有的,用英文版的话,也没有足够好的空间,必须在右侧加一个空格。我现在用的字体会自动把`=>`两个字符转换成`⇒`这一个字符的样子,因此看起来还比较和谐。但由于`⇒`这个字符没有办法直接输入,只能战术放弃。
|
||||
|
||||
### 函数的调用
|
||||
|
||||
函数的调用采用的是“:”加参数列表的格式,而不是传统语言中双括号的格式。
|
||||
|
||||
```
|
||||
和:1、2
|
||||
```
|
||||
|
||||
相当于`add(1, 2)`。
|
||||
|
||||
如果需要嵌套,可以用括号表示优先级:
|
||||
|
||||
```
|
||||
积:5、(和:2、3)
|
||||
```
|
||||
|
||||
相当于:`mul(5, add(2, 3))`
|
||||
|
||||
可以看到,凹中文版的函数调用语法的实际效用,和英文版并没有本质差别。
|
||||
|
||||
用`:`的主要好处是增加了简单调用的可读性,让普通代码中的一行行函数调用看起来更像是对话。但缺点是遇到复杂的调用组合,可能可读性不如英文版。我觉得这里也可能有习惯性的问题,所以打算先试用一段时间,看看是否真的有这个问题。
|
||||
|
||||
另外,函数调用的冒号和代码块的冒号是有冲突的,这一点需要再仔细验证一下。如果不行,可能考虑回归传统的`()`调用。
|
||||
|
||||
还有一个问题,即如果没有参数,该如何表示?
|
||||
|
||||
我暂时的设计是如果没有参数,还是回归`()`
|
||||
|
||||
```wz
|
||||
【感叹】:
|
||||
书·曰:“呜呼!”
|
||||
。
|
||||
|
||||
【启】:
|
||||
感叹()
|
||||
。
|
||||
```
|
||||
|
||||
总之,这里的设计还是不太成熟,需要再探讨探讨。
|
||||
|
||||
|
||||
### 类型
|
||||
|
||||
用户自定义类型在高级编程语言中是非常重要的设计。很多设计模式都是依托于类型系统。
|
||||
|
||||
凹中文版的类型系统基本继承自Go语言,即不支持继承等传统OOP,而支持面向数据的类型体系,以及基于组装(composition)和接口模式的类型体系。
|
||||
|
||||
先看最基本的类型定义。让我们定义一个“点”类型(即Point),它的有两个成员,一个表示横坐标(x),另一个表示纵坐标(y),类型为整数(int)。
|
||||
|
||||
凹英文版:
|
||||
|
||||
```wa
|
||||
type Point struct {
|
||||
x: int
|
||||
y: int
|
||||
}
|
||||
|
||||
func main {
|
||||
p := Point{x:1, y:2}
|
||||
println(p.x+p.y)
|
||||
}
|
||||
```
|
||||
|
||||
凹中文版:
|
||||
|
||||
```wz
|
||||
《点》:
|
||||
纵之数
|
||||
横之数
|
||||
。
|
||||
|
||||
【启】:
|
||||
设p=点{横:1,纵:2}
|
||||
曰:p·x+p·y
|
||||
。
|
||||
```
|
||||
|
||||
这里和英文版唯一的区别就是用`《》`来表示类型定义,替代英文版的`type <name> struct`。
|
||||
在struct中,成员的定义和英文版一致,只是用`之`来代替英文版的`:`,用于分隔成员名称和类型。
|
||||
|
||||
类型的组合模式还没有仔细研究,初步设想如下:
|
||||
|
||||
```wz
|
||||
《三维点》:
|
||||
有:点
|
||||
深之数
|
||||
。
|
||||
```
|
||||
|
||||
这里的关键字“**有**”表示`has-a`关系,即《三维点》中包含《点》的成员。这样实际上和英文版的组合模式是一样的。
|
||||
|
||||
### 方法
|
||||
|
||||
方法是与类型绑定的函数。本质上它的运行和普通函数是一样的,但与类型绑定之后,我们可以非常自然地使用“主谓宾”的语法结构,而不是传统函数的“谓主宾”结构。
|
||||
|
||||
方法还有其他好处,比如可以和接口模式或组合模式结合起来,实现更复杂的类型系统。
|
||||
|
||||
凹英文版的方法定义如下:
|
||||
|
||||
```wa
|
||||
func Point.Length() => int {
|
||||
return math.sqrt(this.x*this.x + this.y*this.y)
|
||||
}
|
||||
|
||||
func main {
|
||||
p := Point{x:1, y:2}
|
||||
println(p.Length())
|
||||
}
|
||||
```
|
||||
|
||||
和Go不同之处在于,凹语言的方法定义之比普通函数在名称前多了一个前缀。`Point.Length`,表示`Length`方法是属于`Point`类型的。
|
||||
这种方式和Scala、Kotlin等语言风格类似。
|
||||
|
||||
在方法之内,用`this`关键字表示`Point`类型的实例;即,在`p.Length()`中,`this`其实就是`p`。
|
||||
|
||||
|
||||
凹中文版也沿用了这种方法,只是类型和方法名之间的间隔改为更适应中文的`·`,而`this`改为`此`:
|
||||
|
||||
```wz
|
||||
【点·长度】=> 数:
|
||||
归于:开方:此·纵*此·纵+此·横*此·横
|
||||
。
|
||||
|
||||
【启】:
|
||||
设p为点{横:1,纵:2}
|
||||
曰:p·长度()
|
||||
。
|
||||
```
|
||||
|
||||
### 控制流
|
||||
|
||||
#### if-else语句
|
||||
|
||||
凹英文版的`if-else`语句如下:
|
||||
|
||||
```go
|
||||
a := 1
|
||||
b := 2
|
||||
if a>b {
|
||||
println("bigger")
|
||||
} else if a==b {
|
||||
println("equals")
|
||||
} else {
|
||||
println("smaller")
|
||||
}
|
||||
```
|
||||
|
||||
凹中文版则是:
|
||||
|
||||
```wz
|
||||
若1>0则:
|
||||
曰:“1>0”
|
||||
又若1=0则:
|
||||
曰:“1==0”
|
||||
否则:
|
||||
曰:“1<=0”
|
||||
。
|
||||
```
|
||||
|
||||
这里的关键字“**若**”表示`if`,关键字“**又若**”表示`else if`,关键字“**否则**”表示`else`,每个条件之后加了`则`关键字,以增加可读性。
|
||||
|
||||
#### 循环
|
||||
|
||||
凹英文版的循环有三种形式:
|
||||
|
||||
1. 三段式for循环
|
||||
|
||||
```go
|
||||
sum := 0
|
||||
for i:=0;i<10;i++ {
|
||||
sum += i
|
||||
}
|
||||
println(sum)
|
||||
```
|
||||
|
||||
2. while循环
|
||||
|
||||
```go
|
||||
i := 1
|
||||
for i <= 10 {
|
||||
println(i)
|
||||
i++
|
||||
}
|
||||
```
|
||||
|
||||
3. 无限循环
|
||||
|
||||
```go
|
||||
for {
|
||||
println("looping...")
|
||||
if condition() {
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
第一种三段式循环,`for <初始化>;<条件>;<递进>`,三段操作分别是初始化循环变量、判断循环结束条件、以及每次执行完循环体之后做的递进更新。
|
||||
第二种和第三种形式其实是这三段操作的省略而已。
|
||||
|
||||
|
||||
凹中文版这样支持三段式:`从<初始化>,到<结束条件>,有<递进>:<循环体>`。例如:
|
||||
|
||||
```wz
|
||||
设和=0
|
||||
|
||||
从i=0,到i==10,有i++:
|
||||
和+=i
|
||||
。
|
||||
```
|
||||
|
||||
这个语法还是比较清晰的。
|
||||
|
||||
注意,我没有找到一个关键字可以表示“只要条件成立,就继续执行”的意思,所以只能用`到`这样的字表示“只要条件城里,就结束循环”。正好和for循环的条件是反的。
|
||||
|
||||
没办法,这就是中英文的习惯不同。
|
||||
|
||||
非要和`for`循环一致的话,大概只能:
|
||||
|
||||
```wz
|
||||
设和=0
|
||||
|
||||
从i=0,若i<10,则i++:
|
||||
和+=i
|
||||
。
|
||||
```
|
||||
|
||||
但只读这句话,总感觉想问“这个到哪里才结束啊?”。总之还是别扭。
|
||||
|
||||
为了支持`for`循环的习惯用户,我还是支持了这种写法。
|
||||
|
||||
另外,由于用关键字替代了符号,在省略时就不如`for`循环好看了:
|
||||
|
||||
```wz
|
||||
设i=0
|
||||
设和=0
|
||||
|
||||
从,到i==0,有:
|
||||
和+=i
|
||||
i++
|
||||
。
|
||||
```
|
||||
|
||||
这显然好看,所以我加了一个新关键字“直到“,可以这么写:
|
||||
|
||||
```wz
|
||||
设i=0
|
||||
设和=0
|
||||
|
||||
直到i==0,有:
|
||||
和+=i
|
||||
i++
|
||||
。
|
||||
```
|
||||
|
||||
这就和`while`差不多了。或者更确切的说,相当于`until`语句。
|
||||
|
||||
至于全部省略的无限循环,只好用类似于“循环”这样的关键字了。
|
||||
|
||||
```wz
|
||||
循环:
|
||||
若xx则:
|
||||
停止
|
||||
。
|
||||
。
|
||||
```
|
||||
|
||||
这显然不太雅观。还需要再改进。
|
||||
|
||||
#### switch语句
|
||||
|
||||
凹英文版的switch语句:
|
||||
|
||||
```go
|
||||
a := 2
|
||||
|
||||
switch i {
|
||||
case 1:
|
||||
println("one")
|
||||
case 2:
|
||||
println("two")
|
||||
case 3, 4:
|
||||
println("three and four")
|
||||
case i < 10:
|
||||
println("smaller than ten")
|
||||
default:
|
||||
println("nah")
|
||||
}
|
||||
```
|
||||
|
||||
中文版初步的想法是模仿Kotlin的`when`语句:
|
||||
|
||||
```wz
|
||||
当i:
|
||||
为1,则:曰:“一”
|
||||
为2,则:曰:“二”
|
||||
为3,则:曰:“三”
|
||||
否则:曰:“不中”
|
||||
。
|
||||
```
|
||||
|
||||
关键字`当`、`为`、`则`、`否则`分别表示`switch`、`case`、`:`和`default`。
|
||||
这个语法和`when`其实也一致,因此未来可以扩充到更复杂的语句。
|
||||
|
||||
### 接口
|
||||
|
||||
凹英文版的接口:
|
||||
|
||||
```wa
|
||||
type duck interface {
|
||||
quack()
|
||||
}
|
||||
```
|
||||
|
||||
这样,包含`quack()`方法的类型都可以当做`duck`来看待。
|
||||
|
||||
在凹中文版中,类型定义是双书名号`《》`,因此接口的定义自然用单书名号`〈〉`,所以:
|
||||
|
||||
```wz
|
||||
〈鸭子〉:
|
||||
【嘎嘎】
|
||||
。
|
||||
```
|
||||
|
||||
意思就是“鸭子”这个接口,包含一个“嘎嘎”方法。这样,任何包含“嘎嘎”方法的类型都可以当做“鸭子”来看待。
|
||||
|
||||
注:这个特性现在还没实现。
|
||||
|
||||
|
||||
## 展望
|
||||
|
||||
到现在(2023年4月初)为止,上述的中文语法几乎都实现了,但是还有一些细节需要完善。
|
||||
|
||||
凹中文版2023年的目标就是完全重写前端解析模块,完善所有语言特性的语法,与英文版做到100%对应。并作出正式的凹中文版标准语法。
|
||||
|
||||
届时就可以考虑给中文语法扩充更贴近中文用户使用习惯的特殊新语法了。
|
||||
|
||||
凹中文版的目标,不是让人人都用中文版语法、抛弃英文版语法,而是想抛砖引玉,启发所有人都来设计与开发更适合中国人的编程语言。
|
||||
605
docs/wz/spec.md
|
|
@ -1,605 +0,0 @@
|
|||
# 语言规范
|
||||
|
||||
## 1 EBNF 记法
|
||||
|
||||
凹语言中文语法使用扩展的巴克斯-诺尔范式(EBNF)定义:
|
||||
|
||||
```
|
||||
Production = production_name "=" [ Expression ] "." .
|
||||
Expression = Alternative { "|" Alternative } .
|
||||
Alternative = Term { Term } .
|
||||
Term = production_name | token [ "…" token ] | Group | Option | Repetition .
|
||||
Group = "(" Expression ")" .
|
||||
Option = "[" Expression "]" .
|
||||
Repetition = "{" Expression "}" .
|
||||
```
|
||||
|
||||
其中生成式由表达式构造,表达式通过术和以下操作符构造,自上而下优先级递增(低到高):
|
||||
|
||||
```
|
||||
| 选择
|
||||
() 分组
|
||||
[] 可选(0 或 1 次)
|
||||
{} 重复(0 到 n 次)
|
||||
```
|
||||
|
||||
用小写生成式名用于标识词法标记。使用非最终(Non-terminals)词法标记使用驼峰记法(CamelCase)。而位于双引号 "" 内的即为词法标记。形式 a … b 表示把从 a 到 b 的字符集作为选择项。横向省略号 … 也在本文档中非正式地表示各种列举或简略的代码片断。单个字符 …(不同于三个字符 ...)并非凹语言本身的标记。
|
||||
|
||||
## 2 源文件表示
|
||||
|
||||
为了更好支持中文和其他语言地区的编程,凹语言源码为采用 UTF-8 编码的 Unicode 文本表示。每个码点都是不同的,例如,大写与小写的字母就是不同的字符。同时为了兼容 C 语言字符串的习惯,编译器会阻止字符 NUL(U+0000)出现在源码文本中。此外,如果源文件开头有 BOM 头部,则编译器应该忽略它。为了遵循流行编程语言的习惯,下划线字符被作为字母对待。
|
||||
|
||||
## 3 词法元素
|
||||
|
||||
### 3.1 注释
|
||||
|
||||
凹语言默认采用行注释,以 `//` 开头的表示行注释。注释不能从字符或字符串字面中开始,也不能在其它注释中。行注释可以视做一个换行符。
|
||||
|
||||
### 3.2 词符类型(Token)
|
||||
|
||||
构成凹语言的词符类型有:
|
||||
|
||||
- 关键字,如`若`。
|
||||
- 标识符,如变量名称。
|
||||
- 面值常量,如整数`123`、字符串`"abc"`、
|
||||
- 运算操作符,如`+`、`-`。
|
||||
- 分隔符,如`【】`、`:`。空白字符` `可用于分隔关键字和标识符。
|
||||
|
||||
凹语言程序源码首先会通过词法解析,转化为有效的词符序列,便于后续的语法解析工作。
|
||||
|
||||
### 3.2 基本字符
|
||||
|
||||
凹语言中文版的基本字符包括:
|
||||
|
||||
```
|
||||
数字 = "0" … "9" .
|
||||
16进制数字 = "0" … "9" | "a" … "f" | "A" … "F" .
|
||||
字母 = "a" … "z" | "A" … "Z" | "_" | Unicode字母
|
||||
Unicode字母 = /* 类别为"Letter"的Unicode字符 */ .
|
||||
```
|
||||
|
||||
注:Unicode字符的“Letter”类别是Unicode标准定义的类别。凹语言根据Unicode标准来判定一个字符是否符合“Letter”类别。
|
||||
|
||||
|
||||
### 3.3 标识符(Identifier)
|
||||
|
||||
标识符用做函数、变量、类型等程序实体的名称。一个标识符由一个或多个字母和数字组成。标识符的第一个字符必须是字母,以便于和数字面值区分。
|
||||
|
||||
```
|
||||
标识符 = 字母 { 字母 | 数字 } .
|
||||
```
|
||||
|
||||
标识符的示例:
|
||||
|
||||
```
|
||||
甲
|
||||
a
|
||||
实例
|
||||
_obj
|
||||
z123
|
||||
这是_一个_很长_的_变量
|
||||
```
|
||||
|
||||
### 3.5 关键字
|
||||
|
||||
关键字也是由字母组成的词符,但与标识符不同的是,它们是语言预先定义好的保留词,有特殊的语法意义,不能用于变量名称等。
|
||||
|
||||
凹中文版目前的关键字有:
|
||||
|
||||
```
|
||||
引于 归于 设 之
|
||||
若 则 又若 否则
|
||||
从 到 有 直到
|
||||
自 至 当 为
|
||||
且......或
|
||||
停之 续之
|
||||
```
|
||||
|
||||
凹中文版还处于早期探索阶段,后续可能添加或修改新的关键字。
|
||||
|
||||
### 3.6 运算符
|
||||
|
||||
以下是凹中文版的运算符和与标点:
|
||||
|
||||
```
|
||||
+ - * /
|
||||
+= -= *= /=
|
||||
++ -- ! &
|
||||
【 】 ( )
|
||||
《 》 〈 〉
|
||||
[ ] { }
|
||||
: 。 =>
|
||||
、 ,
|
||||
```
|
||||
|
||||
运算符用于组成表达式,标点用于组成或分隔语句。
|
||||
|
||||
### 3.7 字面值
|
||||
|
||||
字面值是在凹语言代码中明确写出的值,有布尔值、整数、浮点数、字符串、字符几种类型。其中布尔值有`真`、`假`;整数字面值有二进制、八进制、十进制、十六进制几种表示格式;浮点数字面值有小数、科学计数法和十六进制浮点数几种形式;字符串有普通字符串和支持多行的原生字符串几种形式;字符则是有单引号包含表示单个字符。所有的字面值都是无类型的常量。
|
||||
|
||||
普通字符串和字符面值支持的转义符号:
|
||||
|
||||
```
|
||||
\a U+0007 警报或铃声
|
||||
\b U+0008 退格
|
||||
\f U+000C 换页
|
||||
\n U+000A 换行
|
||||
\r U+000D 回车
|
||||
\t U+0009 横向制表符
|
||||
\v U+000b 纵向制表符
|
||||
\\ U+005c 反斜杠
|
||||
\' U+0027 单引号(仅在符文字面中有效)
|
||||
\" U+0022 双引号(仅在字符串字面中有效)
|
||||
```
|
||||
|
||||
以下是常见的字面值常量:
|
||||
|
||||
```
|
||||
# 布尔字面值
|
||||
真
|
||||
假
|
||||
|
||||
# 整数
|
||||
42
|
||||
4_2
|
||||
0600
|
||||
0_600
|
||||
-42
|
||||
|
||||
# 浮点数
|
||||
0.
|
||||
72.40
|
||||
3.1415926
|
||||
1_5. # == 15.0
|
||||
0.15e+0_2 # == 15.0
|
||||
0x1p-2 # == 0.25
|
||||
0x2.p10 # == 2048.0
|
||||
|
||||
# 字符串
|
||||
"凹语言" # UTF-8 输入的文本
|
||||
`凹语言` # UTF-8 输入的原始字面文本
|
||||
"\u65e5\u672c\u8a9e" # 显式的 Unicode 码点
|
||||
"\U000065e5\U0000672c\U00008a9e" # 显式的 Unicode 码点
|
||||
"\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" # 显式的 UTF-8 字节
|
||||
|
||||
# 字符
|
||||
'凹'
|
||||
'a'
|
||||
'ä'
|
||||
'\t'
|
||||
'\000'
|
||||
'\007'
|
||||
'\xff'
|
||||
'\u12e4'
|
||||
'\U00101234'
|
||||
```
|
||||
|
||||
如果源码将两个码点表示为一个字符,例如包含着重号和字母的结合形式,凹语言会当作两个字面处理(因此不能作为字符面值)。
|
||||
|
||||
## 4 常量
|
||||
|
||||
常量由字面值或常量表达式产生,包含布尔常量、字符常量、整数常量、浮点数常量和字符串常量几种。其中字符、整数、浮点数常量统称为数值常量。
|
||||
|
||||
数值类型常量可表示任意精度的精确值而不受到逻辑现在,因此也就没有整数溢出和 IEEE-754 浮点数的一些限制。常量可以是有类型化的或无类型化的。字面常量、真、假某些只包含无类型化操作数的常量表达式产生的新常量结构也是无类型化的。
|
||||
|
||||
无类型化的常量有一个默认类型,也就是当上下文中需要类型化常量时通过隐式转换出来的类型, 例如在 `设i=0` 这种无显示类型的短变量声明中会被作为`数`(int)类型。
|
||||
|
||||
语言提提供了几个默认类型:
|
||||
|
||||
- `布尔`,即`真`或`假`,对应英文版的`bool`
|
||||
- `字`,即单个字符,如`a`、`甲`等,对应英文版的`rune`
|
||||
- `数`,整数,对应英文版的`int`,整数类型还对应了几个不同位宽的类型,如`数8`表示用8为字节存储的整数。
|
||||
- `小数`,浮点数,对应英文版的`float`
|
||||
- `文`,文本,即字符串,对应英文版的`string`
|
||||
|
||||
尽管数值型常量值域无逻辑限制,但是实现时要求编译器至少有 256bit 表示整数常量和浮点数常量,如果发生溢出则需要在编译阶段给出一个错误。
|
||||
|
||||
## 5 变量
|
||||
|
||||
凹语言中变量是内存用用于保存值的存储位置,或者叫可被取地址的值。变量允许值的集合由其类型确定,比如 `数8` 类型表示 -128 到 127 的整数集合、`布尔`表示 `真`或 `假`的布尔值集合。
|
||||
|
||||
变量又分为具名和匿名两种:具名变量通过标识符引用、而匿名变量通过指针来引用。变量的静态类型在声明时确定,可以明确指定或者由初始化的值类型推导。变量的值可通过在表达式中引用变量取得。如果变量尚未赋值,其值即为其类型的零值。
|
||||
|
||||
零值从直观上表示内存中都是0的状态:布尔类型对应 `假`、数值类型对应 0、字符串对应空字符串`""`、指针和切片对应 `空`。如果是复合类型则其组成的元素均是零值。
|
||||
|
||||
```
|
||||
设甲之布尔 # `甲`是布尔类型
|
||||
设x # x值为空,类型为`万物`(即interface{})
|
||||
设v之*T # v值为空,类型为*T
|
||||
```
|
||||
|
||||
全部的变量组成了程序的状态,也就是所谓的上下文信息。
|
||||
|
||||
## 6 类型
|
||||
|
||||
类型不仅仅决定了值的集合,同时还定义了与该类型值特定的操作集合。已命名类型可通过类型名指定。未命名类型可通过类型字面指定,它将根据既有的类型组成新的类型。
|
||||
|
||||
类型的 EBNF 语法如下:
|
||||
|
||||
```
|
||||
类型定义 = "《" 类型名称 "》" ( "=" 类型面值 | ":" 类型成员列表 "。" ) .
|
||||
类型名称 = 标识符 .
|
||||
类型面值 = 数组类型 | 结构类型 | 指针类型 | 函数类型 | 接口类型 | 切片类型 | 映射类型 .
|
||||
类型成员列表 = 类型成员 { "," 类型成员 } .
|
||||
类型成员 = 成员名称 [ "之" 类型 ] .
|
||||
成员名称 = 标识符 .
|
||||
```
|
||||
|
||||
类型的定义有两种形式,一种是类型别名,用`=`表示,另一种是结构性类型,用`:`后接成员列表。
|
||||
|
||||
布尔值、数值与字符串类型的实例的命名是预声明的`布尔`、`数`、`文`等。 数组、结构体、指针、函数、接口、切片和映射这些复合类型可由类型字面再进行构造。
|
||||
|
||||
下面是定义类型的例子:
|
||||
|
||||
```
|
||||
// 定义一个新的类型别名,名字叫“字节”,它实际上是“正8”整数类型的别名
|
||||
《字节》 = 正8
|
||||
// 定义一个新类型别名,名称叫“缓冲区”,它实际上是一个“字类型”的定长数组,即英文版的`[1024]byte`
|
||||
《缓冲区》 = [字:1024]
|
||||
// 定义一个新的类型别名,名字叫“字典”,它实际上是一个“文”类型的映射,即英文版的`map[string]string`
|
||||
《字典》 = {文:文}
|
||||
|
||||
|
||||
// 定义一个结构性类型,名字叫“点”,它有“横”与“纵”两个成员,分别代表横坐标和纵坐标
|
||||
《点》:
|
||||
横之数
|
||||
纵之数
|
||||
。
|
||||
```
|
||||
|
||||
其中 `字节`、`缓冲区`和`字典`都是类型的别名(即不产生新类型对象),而`点`则产生新的类型。
|
||||
|
||||
### 5.1 布尔类型
|
||||
|
||||
预声明的布尔类型为 bool。布尔类型表示由预声明常量 true 和 false 所代表的布尔值的集。
|
||||
|
||||
### 5.2 数值类型
|
||||
|
||||
数值类型表示整数值和浮点数值的集合。预声明和架构无关的预声明数值类型:
|
||||
|
||||
```
|
||||
u8 所有无符号 8 位整数集(0 到 255)
|
||||
u16 所有无符号 16 位整数集(0 到 65535)
|
||||
u32 所有无符号 32 位整数集(0 到 4294967295)
|
||||
u64 所有无符号 64 位整数集(0 到 18446744073709551615)
|
||||
|
||||
i8 所有带符号 8 位整数集(-128 到 127)
|
||||
i16 所有带符号 16 位整数集(-32768 到 32767)
|
||||
i32 所有带符号 32 位整数集(-2147483648 到 2147483647)
|
||||
i64 所有带符号 64 位整数集(-9223372036854775808 到 9223372036854775807)
|
||||
|
||||
f32 所有 IEEE-754 32 位浮点数集
|
||||
f64 所有 IEEE-754 64 位浮点数集
|
||||
|
||||
byte u8 的别名
|
||||
rune i32 的别名
|
||||
```
|
||||
|
||||
比如 i32 表示用 32bit 二进制补码表示的整数值集合。
|
||||
|
||||
此外还有大小取决于具体实现的预声明数值类型:
|
||||
|
||||
```
|
||||
uint 32 或 64 位
|
||||
int 大小与 uint 相同
|
||||
uintptr 大到足以存储指针值无解释位的无符号整数
|
||||
```
|
||||
|
||||
但是对应凹语言来说,目前 uint/int/uintptr 在 WebAssembly 平台都是用 32bit 表示。
|
||||
|
||||
### 5.3 字符串类型
|
||||
|
||||
字符串类型为 string,表示字符串值的集,字符串的值为字节序列。字符串值是只读不可修改的(不是绑定到某个字符串值的变量):一旦被创建,字符串的内容就不能更改。
|
||||
|
||||
可使用内建函数 `len` 获取字符串 `s` 的长度。若该字符串为常量,则其长度即为编译时常量。 字符串的字节可通过整数 `0` 至 `len(s)-1` 访问。获取这样一个元素的地址是非法的:比如通过 `&s[i]` 访问字符串的第 `i` 个字节的地址是禁止的。
|
||||
|
||||
### 5.4 数组类型
|
||||
|
||||
数组是单一类型元素的序列,其中长度也是数组类型的一部分。数组的 EBNF 语法如下:
|
||||
|
||||
```
|
||||
ArrayType = "[" ArrayLength "]" ElementType .
|
||||
ArrayLength = Expression .
|
||||
ElementType = Type .
|
||||
```
|
||||
|
||||
可使用内建函数 `len` 获取数组的长度,返回的结果一个常量。可通过整数下标 `0` 到 `len(a)-1` 访问数组的元素。数组类型总是一维的,但可组合构成多维的类型。
|
||||
|
||||
```
|
||||
[32]byte
|
||||
[2*N] struct { x, y :i32 }
|
||||
[9527]*f64
|
||||
[3][5]int
|
||||
[2][2][2]f64 // 等价于[2]([2]([2]f64))
|
||||
```
|
||||
|
||||
数组的长度可以使用常量表达式、或者是由初始化面值推导,数组的元素可以是数组从而可以构成多维数组。
|
||||
|
||||
### 5.5 切片类型
|
||||
|
||||
切片类型是在数组类型基础上抽象的类型,切片类型不包含底层数组的长度信息。切片类型底层是对某个数组的引用,未初始化切片的值为 nil。
|
||||
|
||||
可使用内建函数 `len` 获取切片的长度,返回的结果一个变量。可使用内建函数 `cap` 获取切片的容量,返回的结果一个变量。可通过整数下标 `0` 到 `len(a)-1` 访问切片的元素。切片一旦初始化,就总是引用一个包含其元素的底层数组。 因此,切片与引用同一个数组的其他切片共享存储;与此相反,不同的数组总是表示其不同的存储。
|
||||
|
||||
可以通过 make 内置函数创建切片(容量参赛是可选的):
|
||||
|
||||
```
|
||||
make([]T, length)
|
||||
make([]T, length, capacity)
|
||||
```
|
||||
|
||||
类似于数组,切片总是一维的,但可组合构造多维切片对象。因为多维切片和多维数组的底层数据布局差异,其内部的切片必须单独进行初始化,但是带来的一个灵活性是不同子切片大小可以不同。
|
||||
|
||||
### 5.6 结构体类型
|
||||
|
||||
结构体和数组类似,数组采用下标来组织相同类型的值,而结构体定义命名的不同类型的元素序列。结构体类型中每一个元素都有一个名字和类型。字段名可显示地指定或通过匿名嵌入字段隐式地指定。在结构体中,字段名必须是唯一的。
|
||||
|
||||
```
|
||||
StructType = "struct" "{" { FieldDecl ";" } "}" .
|
||||
FieldDecl = (IdentifierList ":" Type | EmbeddedField) [ Tag ] .
|
||||
EmbeddedField = [ "*" ] TypeName .
|
||||
Tag = string_lit .
|
||||
```
|
||||
|
||||
下面是结构体的例子:
|
||||
|
||||
```
|
||||
# 空结构体
|
||||
struct {}
|
||||
|
||||
# 带 6 个字段的结构体
|
||||
struct {
|
||||
x, y :int
|
||||
u :f32
|
||||
_ :f32 # 填充
|
||||
A :*[]int
|
||||
F :func()
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
通过有类型而无显式字段名声明的字段为匿名字段,亦称为嵌入式字段或该结构体中此种类型的嵌入。 这种字段类型必须作为一个类型名 T 或一个非接口类型名的指针 *T来实现, 且 T 本身不能为指针类型。未限定类型名的行为类似于字段名。
|
||||
|
||||
```
|
||||
# 匿名字段的结构体类型
|
||||
struct {
|
||||
T1 # 字段名为 T1
|
||||
*T2 # 字段名为 T2
|
||||
P.T3 # 字段名为 T3
|
||||
*P.T4 # 字段名为 T4
|
||||
x, y :i32 # 字段名为 x 和 y
|
||||
}
|
||||
```
|
||||
|
||||
字段声明可后跟一个可选的字符串字面值标注:
|
||||
|
||||
```
|
||||
struct {
|
||||
x, y : f64 "" # 空标注字符串相当于没有标注
|
||||
name : string "任何字符串都允许作为标注"
|
||||
_ : [4]byte "这不是一个结构体字段"
|
||||
}
|
||||
```
|
||||
|
||||
标注是结构体的类型标识的一部分,未来可通过反射机制获得。
|
||||
|
||||
### 5.7 指针类型
|
||||
|
||||
指针类型表示一个指向所有给定类型变量的指针的集合,其中指向的类型称为指针的基础类型。未初始化的指针的值为 nil。
|
||||
|
||||
```
|
||||
PointerType = "*" BaseType .
|
||||
BaseType = Type .
|
||||
```
|
||||
|
||||
下面是指针类型的例子:
|
||||
|
||||
```
|
||||
*Point
|
||||
*[4]int
|
||||
```
|
||||
|
||||
凹语言指针和传统的 C/C++ 指针并不完全相同,目前的实现更偏向引用。
|
||||
|
||||
### 5.8 函数类型
|
||||
|
||||
凹语言函数类型表示所有带相同形参和返回类型的集合。未初始化的函数类型变量的值为 nil。
|
||||
|
||||
```
|
||||
FunctionType = "func" Signature .
|
||||
Signature = Parameters [ "=>" Result ] .
|
||||
Result = Parameters | ":" Type .
|
||||
Parameters = "(" [ ParameterList [ "," ] ] ")" .
|
||||
ParameterList = ParameterDecl { "," ParameterDecl } .
|
||||
ParameterDecl = [ IdentifierList ] ":" [ "..." ] Type .
|
||||
```
|
||||
|
||||
在形参或结果的列表中,其名称必须都存在或都不存在。 若存在,则每个名称代表一个指定类型的项,所有在签名中的非空白名称必须是唯一的。 若不存在,则每个类型代表一个此类型的项。若恰好有一个未命名的值,它可能写作一个不加括号的类型,除此之外,形参和结果的列表总是在括号中。
|
||||
|
||||
函数签名中加入的最后一个形参可能有一个带 `...` 前缀的类型。 带这样形参的函数被称为变参函数,它可接受零个或多个实参。
|
||||
|
||||
```
|
||||
func()
|
||||
func(x :int) => int
|
||||
func(a, _ :int, z :f32) => bool
|
||||
func(a, b :int, z :f32) => (bool)
|
||||
func(prefix :string, values :...int)
|
||||
func(a, b :int, z :f64, opt :...any) => (success bool)
|
||||
func(int, int, f64) => (f64, *[]int)
|
||||
func(n :int) => func(p :*T)
|
||||
```
|
||||
|
||||
函数的类型可以用于全局函数、闭包函数、方法值等地方。
|
||||
|
||||
### 5.9 接口类型
|
||||
|
||||
接口类型定义满足方法列表的类型结合。目前只有结构体才能定义方法。未初始化的接口类型变量的值为 nil。
|
||||
|
||||
```
|
||||
InterfaceType = "interface" "{" { ( MethodSpec | InterfaceTypeName ) ";" } "}" .
|
||||
MethodSpec = MethodName Signature .
|
||||
MethodName = identifier .
|
||||
InterfaceTypeName = TypeName .
|
||||
```
|
||||
|
||||
下面是一个接口类型定义的方法集合:
|
||||
|
||||
```
|
||||
# 一个简单的 File 接口
|
||||
interface {
|
||||
Read(b :[]byte) => (n :int, err :error)
|
||||
Write(b :[]byte) => (n :int, err :error)
|
||||
Close()
|
||||
}
|
||||
```
|
||||
|
||||
如果 T 类型有着接口定义的方法,那么就表示实现了接口:
|
||||
|
||||
```
|
||||
func T.Read(p :[]byte) => (n :int, err :error)
|
||||
func T.Write(p :[]byte) => (n :int, err :error)
|
||||
func T.Close() error
|
||||
```
|
||||
|
||||
其中没有定义方法集的 `interface{}` 接口可以包含任何类型的值,同时它有一个 `any` 别名。
|
||||
|
||||
### 5.10 map类型
|
||||
|
||||
map 是一个同种类型元素的无序组,该类型称为元素类型 映射通过另一类型唯一的键集索引,该类型称为键类型。未初始化的映射值为 nil。
|
||||
|
||||
```
|
||||
MapType = "map" "[" KeyType "]" ElementType .
|
||||
KeyType = Type .
|
||||
```
|
||||
|
||||
比较操作符 `==`和 `!=` 必须由键类型的操作数完全定义;因此键类型不能是函数、映射或切片。若该键类型为接口类型,这些比较运算符必须由动态键值定义;失败将导致一个异常.
|
||||
|
||||
同样通过 make 内置函数创建一个 map:
|
||||
|
||||
```
|
||||
make(map[string]int)
|
||||
make(map[string]int, 100)
|
||||
```
|
||||
|
||||
make创建map时,第二个是一个可选的容量估计参数。
|
||||
|
||||
## 6 类型与值的性质
|
||||
|
||||
## 6.1 类型标识
|
||||
|
||||
若两个已命名类型的类型名源自相同的类型实现,它们就是相同的。一个已命名类型和一个未命名类型总不相同。若两个未命名类型其相应的类型字面相同,那么它们的类型相同,即它们的字面结构是否相同且其相应的组件类型是否相同。细节详述:
|
||||
|
||||
- 若两个数组类型其元素类型相同且长度相同,那么它们的类型相同。
|
||||
- 若两个切片类型其元素类型相同,那么它们的类型相同。
|
||||
- 若两个结构体类型其字段序列相同、相应字段名相同、类型相同、标注相同,那么它们的类型相同。两个匿名字段根据其名字被判定是否相同。
|
||||
- 若两个指针类型其基础类型相同,那么它们的类型相同。
|
||||
- 若两个函数类型其形参个数相同、返回值相同、相应形参类型相同、返回值类型相同,那么它们的类型相同。形参和返回值名无需匹配。
|
||||
- 若两个接口类型其方法集相同、名字相同、函数类型相同,那么它们的类型相同。出自不同包的小写方法名总不相同。两接口类型是否相同与方法的次序无关。
|
||||
- 若两个map类型其键值类型相同,那么它们的类型相同。
|
||||
|
||||
若两个类型非相同即为不同。
|
||||
|
||||
|
||||
## 6.2 可赋值性
|
||||
|
||||
在下列情况下,值 x 可赋予类型为 T 的变量:
|
||||
|
||||
- 当 x 的类型和 T 相同时。
|
||||
- 当 x 的类型 V 和 T 有相同的 底层类型 且在 V 或 T 中至少有一个不是已命名类型时。
|
||||
- 当 T 为接口类型且 x 实现了 T 时。
|
||||
- 当 x 为预声明标识符 nil 且 T 为指针、函数、切片、映射、通道或接口类型时。
|
||||
- 当 x 为无类型化,可通过类型 T 的值来表示的 常量时。
|
||||
|
||||
任何类型都可赋予下划线表示的空白标识符.
|
||||
|
||||
## 6.3 可表示性
|
||||
|
||||
若满足以下条件,则常量 x 可表示为 T 类型的值:
|
||||
|
||||
- x 在由 T 所确定的值的集合中。
|
||||
- T 为浮点类型且 x 可被舍入到 T 的精度而不会溢出。
|
||||
|
||||
下面是一个例子:
|
||||
|
||||
```
|
||||
x T x 可表示为一个 T 类型的值,因为
|
||||
|
||||
'a' byte 97 属于 byte 值的集合
|
||||
97 rune rune 是 int32 的别名,且 97 属于 32 位整数值的集合
|
||||
"foo" string "foo" 属于 string 值的集合
|
||||
1024 int16 1024 属于 16 位整数的集合
|
||||
42.0 byte 42 属于无符号 8 位整数的集合
|
||||
1e10 u64 10000000000 属于 64 位无符号整数的集合
|
||||
2.718281828459045 f32 2.718281828459045 舍入到 2.7182817,它属于 f32 值的集合
|
||||
-1e-1000 f64 -1e-1000 舍入到 IEEE -0.0,它进一步简化为 0.0
|
||||
0i int 0 是一个整数值
|
||||
|
||||
x T x 无法表示为一个 T 类型的值,因为
|
||||
|
||||
0 bool 0 不属于布尔值的集合
|
||||
'a' string 'a' 是一个 rune,它不属于 string 值的集合
|
||||
1024 byte 1024 不属于无符号 8 位整数的集合
|
||||
-1 uint16 -1 不属于 16 位无符号整数的集合
|
||||
1.1 int 1.1 不是一个整数值
|
||||
1e1000 f64 1e1000 舍入后溢出到 IEEE +Inf
|
||||
```
|
||||
|
||||
## 7 代码块
|
||||
|
||||
凹语言代码块为一对大括号括住的,可能为空的一系列声明和语句。
|
||||
|
||||
```
|
||||
Block = "{" StatementList "}" .
|
||||
StatementList = { Statement ";" } .
|
||||
```
|
||||
|
||||
除显式源码块外,还有一些隐式块:
|
||||
|
||||
- 全域块包含所有的 凹语言 源码文本。
|
||||
- 每个包都有包含其所有 凹语言 源码文本的包块。
|
||||
- 每个文件都有包含其所有 凹语言 源码文本的文件块。
|
||||
- 每个 if、for 和 switch 语句都被视为处于其自身的隐式块中。
|
||||
- 每个 switch 语句中的子句其行为如同隐式块。
|
||||
|
||||
块可嵌套并会影响作用域。
|
||||
|
||||
## 8 声明与作用域
|
||||
|
||||
凹语言中声明可将非空白标识符绑定到一个常量、类型、变量、函数、标签或包。在程序中,每个标识符都必须被声明才能被使用。同一标识符不能在同一代码块中声明两次,且在文件与包代码块中不能同时声明。
|
||||
|
||||
下划线表示的空白标识符可像其它标识符一样在声明中使用,但它不会引入绑定被视作未声明的。在包块中,标识符 init 仅能用作 init 函数声明,且与空白标识符一样不会引入新的绑定。
|
||||
|
||||
```
|
||||
Declaration = ConstDecl | TypeDecl | VarDecl .
|
||||
TopLevelDecl = Declaration | FunctionDecl | MethodDecl .
|
||||
```
|
||||
|
||||
已声明标识符的作用域即为该标识符所表示的具名常量、类型、变量、函数、标签或包在源文本中的作用范围。
|
||||
|
||||
凹语言 使用块表示词法作用域:
|
||||
|
||||
1. 预声明标识符的作用域为全域块。
|
||||
1. 即在任何函数之外的顶级声明的表示常量、类型、变量或函数的标识符其作用域为该包块。
|
||||
1. 已导入包的包名作用域为仅包含该导入声明的文件块。
|
||||
1. 表示方法接收器 this、函数形参或返回值变量的标识符,其作用域为该函数体。
|
||||
1. 在函数中声明为常量或变量的标识符,其作用域始于该函数中具体常量实现或变量实现的结尾,止于最内部包含块的结尾。
|
||||
1. 在函数中声明为类型的标识符,其作用域始于该函数中具体类型实现的标识符,止于最内部包含块的结尾。
|
||||
|
||||
在块中短声明的标识符可在其内部块中重新声明。
|
||||
|
||||
## 8.1 标签作用域
|
||||
|
||||
## 8.2 空白标识符
|
||||
|
||||
|
||||
## 9 表达式
|
||||
|
||||
## 10 语句
|
||||
|
||||
## 11 包结构
|
||||
|
||||
## 12 初始化和执行顺序
|
||||
|
||||
|
||||
|
||||
7
go.mod
|
|
@ -1,11 +1,10 @@
|
|||
// 版权 @2019 凹语言 作者。保留所有权利。
|
||||
|
||||
module wa-lang.org/wa
|
||||
module github.com/wa-lang/wa
|
||||
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.3.2
|
||||
wa-lang.org/wabt-go v1.3.0
|
||||
wa-lang.org/wazero v1.0.1
|
||||
github.com/tetratelabs/wazero v1.0.0-pre.3
|
||||
github.com/wa-lang/wabt-go v1.1.0
|
||||
)
|
||||
|
|
|
|||
14
go.sum
|
|
@ -1,10 +1,4 @@
|
|||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/tetratelabs/wazero v1.0.0-pre.4 h1:RBJQT5OzmORkSp6MmZDWoFEr0zXjk4pmvMKAdeUnsaI=
|
||||
github.com/tetratelabs/wazero v1.0.0-pre.4/go.mod h1:u8wrFmpdrykiFK0DFPiFm5a4+0RzsdmXYVtijBKqUVo=
|
||||
wa-lang.org/wabt-go v1.2.3 h1:xwGW3+IrCMAPDx+ngQFx7vl0xBc1vuooRNql7i2yCiA=
|
||||
wa-lang.org/wabt-go v1.2.3/go.mod h1:v4fKY/p4oMiKwQ2S2aC/wOXvJ2bk2MgtLmwK0TKTNx0=
|
||||
wa-lang.org/wabt-go v1.3.0 h1:7oSrr80hGlf9G6wwf2pQQ1NXbeqBxEk3D3GDWwbYmVA=
|
||||
wa-lang.org/wabt-go v1.3.0/go.mod h1:v4fKY/p4oMiKwQ2S2aC/wOXvJ2bk2MgtLmwK0TKTNx0=
|
||||
wa-lang.org/wazero v1.0.1 h1:6o6M2o6TP0c6b1+jJtfFaAJQGBIk8HrDegIGzrtwmGc=
|
||||
wa-lang.org/wazero v1.0.1/go.mod h1:r1lGhhlgAnh5eZyNHe/dslT0LF7aY3qZGJWCbxRqf3E=
|
||||
github.com/tetratelabs/wazero v1.0.0-pre.3 h1:Z5fbogMUGcERzaQb9mQU8+yJSy0bVvv2ce3dfR4wcZg=
|
||||
github.com/tetratelabs/wazero v1.0.0-pre.3/go.mod h1:M8UDNECGm/HVjOfq0EOe4QfCY9Les1eq54IChMLETbc=
|
||||
github.com/wa-lang/wabt-go v1.1.0 h1:0ois7H69M+6lJhjqU/3c5HxTGeQjwTW0Ncj+SLEW96o=
|
||||
github.com/wa-lang/wabt-go v1.1.0/go.mod h1:I/qIi3rHMN4ECgmZ8FzYwpNr2KfLs4TiXuTi7Ql7H3g=
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
# 版权 @2019 凹语言 作者。保留所有权利。
|
||||
|
||||
import "fmt"
|
||||
|
||||
fn main {
|
||||
println("你好,凹语言!")
|
||||
println(add(40, 2))
|
||||
|
||||
fmt.Println(1+1)
|
||||
}
|
||||
|
||||
fn add(a: i32, b: i32) => i32 {
|
||||
return a+b
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
Copyright (c) 2016 Sourcegraph Inc
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
# jsonrpc2: JSON-RPC 2.0 implementation for Go [](https://travis-ci.org/sourcegraph/jsonrpc2) [](https://sourcegraph.com/github.com/sourcegraph/jsonrpc2?badge) [](https://godoc.org/github.com/sourcegraph/jsonrpc2)
|
||||
|
||||
|
||||
Package jsonrpc2 provides a [Go](https://golang.org) implementation of [JSON-RPC 2.0](http://www.jsonrpc.org/specification).
|
||||
|
||||
This package is **experimental** until further notice.
|
||||
|
||||
[**Open the code in Sourcegraph**](https://sourcegraph.com/github.com/sourcegraph/jsonrpc2)
|
||||
|
||||
## Known issues
|
||||
|
||||
* Batch requests and responses are not yet supported.
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package jsonrpc2
|
||||
|
||||
import "context"
|
||||
|
||||
// AsyncHandler wraps a Handler such that each request is handled in its own
|
||||
// goroutine. It is a convenience wrapper.
|
||||
func AsyncHandler(h Handler) Handler {
|
||||
return asyncHandler{h}
|
||||
}
|
||||
|
||||
type asyncHandler struct {
|
||||
Handler
|
||||
}
|
||||
|
||||
func (h asyncHandler) Handle(ctx context.Context, conn *Conn, req *Request) {
|
||||
go h.Handler.Handle(ctx, conn, req)
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
package jsonrpc2
|
||||
|
||||
// CallOption is an option that can be provided to (*Conn).Call to
|
||||
// configure custom behavior. See Meta.
|
||||
type CallOption interface {
|
||||
apply(r *Request) error
|
||||
}
|
||||
|
||||
type callOptionFunc func(r *Request) error
|
||||
|
||||
func (c callOptionFunc) apply(r *Request) error { return c(r) }
|
||||
|
||||
// Meta returns a call option which attaches the given meta object to
|
||||
// the JSON-RPC 2.0 request (this is a Sourcegraph extension to JSON
|
||||
// RPC 2.0 for carrying metadata).
|
||||
func Meta(meta interface{}) CallOption {
|
||||
return callOptionFunc(func(r *Request) error {
|
||||
return r.SetMeta(meta)
|
||||
})
|
||||
}
|
||||
|
||||
// ExtraField returns a call option which attaches the given name/value pair to
|
||||
// the JSON-RPC 2.0 request. This can be used to add arbitrary extensions to
|
||||
// JSON RPC 2.0.
|
||||
func ExtraField(name string, value interface{}) CallOption {
|
||||
return callOptionFunc(func(r *Request) error {
|
||||
return r.SetExtraField(name, value)
|
||||
})
|
||||
}
|
||||
|
||||
// PickID returns a call option which sets the ID on a request. Care must be
|
||||
// taken to ensure there are no conflicts with any previously picked ID, nor
|
||||
// with the default sequence ID.
|
||||
func PickID(id ID) CallOption {
|
||||
return callOptionFunc(func(r *Request) error {
|
||||
r.ID = id
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// StringID returns a call option that instructs the request ID to be set as a
|
||||
// string.
|
||||
func StringID() CallOption {
|
||||
return callOptionFunc(func(r *Request) error {
|
||||
r.ID.IsString = true
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
@ -1,471 +0,0 @@
|
|||
package jsonrpc2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Conn is a JSON-RPC client/server connection. The JSON-RPC protocol
|
||||
// is symmetric, so a Conn runs on both ends of a client-server
|
||||
// connection.
|
||||
type Conn struct {
|
||||
stream ObjectStream
|
||||
|
||||
h Handler
|
||||
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
seq uint64
|
||||
pending map[ID]*call
|
||||
|
||||
sending sync.Mutex
|
||||
|
||||
disconnect chan struct{}
|
||||
|
||||
logger Logger
|
||||
|
||||
// Set by ConnOpt funcs.
|
||||
onRecv []func(*Request, *Response)
|
||||
onSend []func(*Request, *Response)
|
||||
}
|
||||
|
||||
var _ JSONRPC2 = (*Conn)(nil)
|
||||
|
||||
// NewConn creates a new JSON-RPC client/server connection using the
|
||||
// given ReadWriteCloser (typically a TCP connection or stdio). The
|
||||
// JSON-RPC protocol is symmetric, so a Conn runs on both ends of a
|
||||
// client-server connection.
|
||||
//
|
||||
// NewClient consumes conn, so you should call Close on the returned
|
||||
// client not on the given conn.
|
||||
func NewConn(ctx context.Context, stream ObjectStream, h Handler, opts ...ConnOpt) *Conn {
|
||||
c := &Conn{
|
||||
stream: stream,
|
||||
h: h,
|
||||
pending: map[ID]*call{},
|
||||
disconnect: make(chan struct{}),
|
||||
logger: log.New(os.Stderr, "", log.LstdFlags),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
if opt == nil {
|
||||
continue
|
||||
}
|
||||
opt(c)
|
||||
}
|
||||
go c.readMessages(ctx)
|
||||
return c
|
||||
}
|
||||
|
||||
// Close closes the JSON-RPC connection. The connection may not be
|
||||
// used after it has been closed.
|
||||
func (c *Conn) Close() error {
|
||||
return c.close(nil)
|
||||
}
|
||||
|
||||
// Call initiates a JSON-RPC call using the specified method and params, and
|
||||
// waits for the response. If the response is successful, its result is stored
|
||||
// in result (a pointer to a value that can be JSON-unmarshaled into);
|
||||
// otherwise, a non-nil error is returned. See DispatchCall for more details.
|
||||
func (c *Conn) Call(ctx context.Context, method string, params, result interface{}, opts ...CallOption) error {
|
||||
call, err := c.DispatchCall(ctx, method, params, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return call.Wait(ctx, result)
|
||||
}
|
||||
|
||||
// DisconnectNotify returns a channel that is closed when the
|
||||
// underlying connection is disconnected.
|
||||
func (c *Conn) DisconnectNotify() <-chan struct{} {
|
||||
return c.disconnect
|
||||
}
|
||||
|
||||
// DispatchCall dispatches a JSON-RPC call using the specified method and
|
||||
// params, and returns a call proxy or an error. Call Wait() on the returned
|
||||
// proxy to receive the response. Only use this function if you need to do work
|
||||
// after dispatching the request, otherwise use Call.
|
||||
//
|
||||
// The params member is omitted from the JSON-RPC request if the given params is
|
||||
// nil. Use json.RawMessage("null") to send a JSON-RPC request with its params
|
||||
// member set to null.
|
||||
func (c *Conn) DispatchCall(ctx context.Context, method string, params interface{}, opts ...CallOption) (Waiter, error) {
|
||||
req := &Request{Method: method}
|
||||
for _, opt := range opts {
|
||||
if opt == nil {
|
||||
continue
|
||||
}
|
||||
if err := opt.apply(req); err != nil {
|
||||
return Waiter{}, err
|
||||
}
|
||||
}
|
||||
if params != nil {
|
||||
if err := req.SetParams(params); err != nil {
|
||||
return Waiter{}, err
|
||||
}
|
||||
}
|
||||
call, err := c.send(ctx, &anyMessage{request: req}, true)
|
||||
if err != nil {
|
||||
return Waiter{}, err
|
||||
}
|
||||
return Waiter{call: call}, nil
|
||||
}
|
||||
|
||||
// Notify is like Call, but it returns when the notification request is sent
|
||||
// (without waiting for a response, because JSON-RPC notifications do not have
|
||||
// responses).
|
||||
//
|
||||
// The params member is omitted from the JSON-RPC request if the given params is
|
||||
// nil. Use json.RawMessage("null") to send a JSON-RPC request with its params
|
||||
// member set to null.
|
||||
func (c *Conn) Notify(ctx context.Context, method string, params interface{}, opts ...CallOption) error {
|
||||
req := &Request{Method: method, Notif: true}
|
||||
for _, opt := range opts {
|
||||
if opt == nil {
|
||||
continue
|
||||
}
|
||||
if err := opt.apply(req); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if params != nil {
|
||||
if err := req.SetParams(params); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := c.send(ctx, &anyMessage{request: req}, false)
|
||||
return err
|
||||
}
|
||||
|
||||
// Reply sends a successful response with a result.
|
||||
func (c *Conn) Reply(ctx context.Context, id ID, result interface{}) error {
|
||||
resp := &Response{ID: id}
|
||||
if err := resp.SetResult(result); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := c.send(ctx, &anyMessage{response: resp}, false)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReplyWithError sends a response with an error.
|
||||
func (c *Conn) ReplyWithError(ctx context.Context, id ID, respErr *Error) error {
|
||||
_, err := c.send(ctx, &anyMessage{response: &Response{ID: id, Error: respErr}}, false)
|
||||
return err
|
||||
}
|
||||
|
||||
// SendResponse sends resp to the peer. It is lower level than (*Conn).Reply.
|
||||
func (c *Conn) SendResponse(ctx context.Context, resp *Response) error {
|
||||
_, err := c.send(ctx, &anyMessage{response: resp}, false)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Conn) close(cause error) error {
|
||||
c.sending.Lock()
|
||||
c.mu.Lock()
|
||||
defer c.sending.Unlock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.closed {
|
||||
return ErrClosed
|
||||
}
|
||||
|
||||
for _, call := range c.pending {
|
||||
close(call.done)
|
||||
}
|
||||
|
||||
if cause != nil && cause != io.EOF && cause != io.ErrUnexpectedEOF {
|
||||
c.logger.Printf("jsonrpc2: protocol error: %v\n", cause)
|
||||
}
|
||||
|
||||
close(c.disconnect)
|
||||
c.closed = true
|
||||
return c.stream.Close()
|
||||
}
|
||||
|
||||
func (c *Conn) readMessages(ctx context.Context) {
|
||||
var err error
|
||||
for err == nil {
|
||||
var m anyMessage
|
||||
err = c.stream.ReadObject(&m)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
switch {
|
||||
case m.request != nil:
|
||||
for _, onRecv := range c.onRecv {
|
||||
onRecv(m.request, nil)
|
||||
}
|
||||
c.h.Handle(ctx, c, m.request)
|
||||
|
||||
case m.response != nil:
|
||||
resp := m.response
|
||||
if resp != nil {
|
||||
id := resp.ID
|
||||
c.mu.Lock()
|
||||
call := c.pending[id]
|
||||
delete(c.pending, id)
|
||||
c.mu.Unlock()
|
||||
|
||||
if call != nil {
|
||||
call.response = resp
|
||||
}
|
||||
|
||||
if len(c.onRecv) > 0 {
|
||||
var req *Request
|
||||
if call != nil {
|
||||
req = call.request
|
||||
}
|
||||
for _, onRecv := range c.onRecv {
|
||||
onRecv(req, resp)
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case call == nil:
|
||||
c.logger.Printf("jsonrpc2: ignoring response #%s with no corresponding request\n", id)
|
||||
|
||||
case resp.Error != nil:
|
||||
call.done <- resp.Error
|
||||
close(call.done)
|
||||
|
||||
default:
|
||||
call.done <- nil
|
||||
close(call.done)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
c.close(err)
|
||||
}
|
||||
|
||||
func (c *Conn) send(_ context.Context, m *anyMessage, wait bool) (cc *call, err error) {
|
||||
c.sending.Lock()
|
||||
defer c.sending.Unlock()
|
||||
|
||||
// m.request.ID could be changed, so we store a copy to correctly
|
||||
// clean up pending
|
||||
var id ID
|
||||
|
||||
c.mu.Lock()
|
||||
if c.closed {
|
||||
c.mu.Unlock()
|
||||
return nil, ErrClosed
|
||||
}
|
||||
|
||||
// Assign a default id if not set
|
||||
if m.request != nil && wait {
|
||||
cc = &call{request: m.request, seq: c.seq, done: make(chan error, 1)}
|
||||
|
||||
isIDUnset := len(m.request.ID.Str) == 0 && m.request.ID.Num == 0
|
||||
if isIDUnset {
|
||||
if m.request.ID.IsString {
|
||||
m.request.ID.Str = strconv.FormatUint(c.seq, 10)
|
||||
} else {
|
||||
m.request.ID.Num = c.seq
|
||||
}
|
||||
}
|
||||
c.seq++
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
if len(c.onSend) > 0 {
|
||||
var (
|
||||
req *Request
|
||||
resp *Response
|
||||
)
|
||||
switch {
|
||||
case m.request != nil:
|
||||
req = m.request
|
||||
case m.response != nil:
|
||||
resp = m.response
|
||||
}
|
||||
for _, onSend := range c.onSend {
|
||||
onSend(req, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// Store requests so we can later associate them with incoming
|
||||
// responses.
|
||||
if m.request != nil && wait {
|
||||
c.mu.Lock()
|
||||
id = m.request.ID
|
||||
c.pending[id] = cc
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// From here on, if we fail to send this, then we need to remove
|
||||
// this from the pending map so we don't block on it or pile up
|
||||
// pending entries for unsent messages.
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if cc != nil {
|
||||
c.mu.Lock()
|
||||
delete(c.pending, id)
|
||||
c.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := c.stream.WriteObject(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// Waiter proxies an ongoing JSON-RPC call.
|
||||
type Waiter struct {
|
||||
*call
|
||||
}
|
||||
|
||||
// Wait for the result of an ongoing JSON-RPC call. If the response
|
||||
// is successful, its result is stored in result (a pointer to a
|
||||
// value that can be JSON-unmarshaled into); otherwise, a non-nil
|
||||
// error is returned.
|
||||
func (w Waiter) Wait(ctx context.Context, result interface{}) error {
|
||||
select {
|
||||
case err, ok := <-w.call.done:
|
||||
if !ok {
|
||||
err = ErrClosed
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result != nil {
|
||||
if w.call.response.Result == nil {
|
||||
w.call.response.Result = &jsonNull
|
||||
}
|
||||
if err := json.Unmarshal(*w.call.response.Result, result); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// call represents a JSON-RPC call over its entire lifecycle.
|
||||
type call struct {
|
||||
request *Request
|
||||
response *Response
|
||||
seq uint64 // the seq of the request
|
||||
done chan error
|
||||
}
|
||||
|
||||
// anyMessage represents either a JSON Request or Response.
|
||||
type anyMessage struct {
|
||||
request *Request
|
||||
response *Response
|
||||
}
|
||||
|
||||
func (m anyMessage) MarshalJSON() ([]byte, error) {
|
||||
var v interface{}
|
||||
switch {
|
||||
case m.request != nil && m.response == nil:
|
||||
v = m.request
|
||||
case m.request == nil && m.response != nil:
|
||||
v = m.response
|
||||
}
|
||||
if v != nil {
|
||||
return json.Marshal(v)
|
||||
}
|
||||
return nil, errors.New("jsonrpc2: message must have exactly one of the request or response fields set")
|
||||
}
|
||||
|
||||
func (m *anyMessage) UnmarshalJSON(data []byte) error {
|
||||
// The presence of these fields distinguishes between the 2
|
||||
// message types.
|
||||
type msg struct {
|
||||
ID interface{} `json:"id"`
|
||||
Method *string `json:"method"`
|
||||
Result anyValueWithExplicitNull `json:"result"`
|
||||
Error interface{} `json:"error"`
|
||||
}
|
||||
|
||||
var isRequest, isResponse bool
|
||||
checkType := func(m *msg) error {
|
||||
mIsRequest := m.Method != nil
|
||||
mIsResponse := m.Result.null || m.Result.value != nil || m.Error != nil
|
||||
if (!mIsRequest && !mIsResponse) || (mIsRequest && mIsResponse) {
|
||||
return errors.New("jsonrpc2: unable to determine message type (request or response)")
|
||||
}
|
||||
if (mIsRequest && isResponse) || (mIsResponse && isRequest) {
|
||||
return errors.New("jsonrpc2: batch message type mismatch (must be all requests or all responses)")
|
||||
}
|
||||
isRequest = mIsRequest
|
||||
isResponse = mIsResponse
|
||||
return nil
|
||||
}
|
||||
|
||||
if isArray := len(data) > 0 && data[0] == '['; isArray {
|
||||
var msgs []msg
|
||||
if err := json.Unmarshal(data, &msgs); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(msgs) == 0 {
|
||||
return errors.New("jsonrpc2: invalid empty batch")
|
||||
}
|
||||
for i := range msgs {
|
||||
if err := checkType(&msg{
|
||||
ID: msgs[i].ID,
|
||||
Method: msgs[i].Method,
|
||||
Result: msgs[i].Result,
|
||||
Error: msgs[i].Error,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var m msg
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkType(&m); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var v interface{}
|
||||
switch {
|
||||
case isRequest && !isResponse:
|
||||
v = &m.request
|
||||
case !isRequest && isResponse:
|
||||
v = &m.response
|
||||
}
|
||||
if err := json.Unmarshal(data, v); err != nil {
|
||||
return err
|
||||
}
|
||||
if !isRequest && isResponse && m.response.Error == nil && m.response.Result == nil {
|
||||
m.response.Result = &jsonNull
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// anyValueWithExplicitNull is used to distinguish {} from
|
||||
// {"result":null} by anyMessage's JSON unmarshaler.
|
||||
type anyValueWithExplicitNull struct {
|
||||
null bool // JSON "null"
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func (v anyValueWithExplicitNull) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.value)
|
||||
}
|
||||
|
||||
func (v *anyValueWithExplicitNull) UnmarshalJSON(data []byte) error {
|
||||
data = bytes.TrimSpace(data)
|
||||
if string(data) == "null" {
|
||||
*v = anyValueWithExplicitNull{null: true}
|
||||
return nil
|
||||
}
|
||||
*v = anyValueWithExplicitNull{}
|
||||
return json.Unmarshal(data, &v.value)
|
||||
}
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
package jsonrpc2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Logger interface implements one method - Printf.
|
||||
// You can use the stdlib logger *log.Logger
|
||||
type Logger interface {
|
||||
Printf(format string, v ...interface{})
|
||||
}
|
||||
|
||||
// ConnOpt is the type of function that can be passed to NewConn to
|
||||
// customize the Conn before it is created.
|
||||
type ConnOpt func(*Conn)
|
||||
|
||||
// OnRecv causes all requests received on conn to invoke f(req, nil)
|
||||
// and all responses to invoke f(req, resp),
|
||||
func OnRecv(f func(*Request, *Response)) ConnOpt {
|
||||
return func(c *Conn) { c.onRecv = append(c.onRecv, f) }
|
||||
}
|
||||
|
||||
// OnSend causes all requests sent on conn to invoke f(req, nil) and
|
||||
// all responses to invoke f(nil, resp),
|
||||
func OnSend(f func(*Request, *Response)) ConnOpt {
|
||||
return func(c *Conn) { c.onSend = append(c.onSend, f) }
|
||||
}
|
||||
|
||||
// LogMessages causes all messages sent and received on conn to be
|
||||
// logged using the provided logger.
|
||||
func LogMessages(logger Logger) ConnOpt {
|
||||
return func(c *Conn) {
|
||||
// Remember reqs we have received so we can helpfully show the
|
||||
// request method in OnSend for responses.
|
||||
var (
|
||||
mu sync.Mutex
|
||||
reqMethods = map[ID]string{}
|
||||
)
|
||||
|
||||
// Set custom logger from provided input
|
||||
c.logger = logger
|
||||
|
||||
OnRecv(func(req *Request, resp *Response) {
|
||||
switch {
|
||||
case req != nil:
|
||||
mu.Lock()
|
||||
reqMethods[req.ID] = req.Method
|
||||
mu.Unlock()
|
||||
|
||||
params, _ := json.Marshal(req.Params)
|
||||
if req.Notif {
|
||||
logger.Printf("jsonrpc2: --> notif: %s: %s\n", req.Method, params)
|
||||
} else {
|
||||
logger.Printf("jsonrpc2: --> request #%s: %s: %s\n", req.ID, req.Method, params)
|
||||
}
|
||||
|
||||
case resp != nil:
|
||||
var method string
|
||||
if req != nil {
|
||||
method = req.Method
|
||||
} else {
|
||||
method = "(no matching request)"
|
||||
}
|
||||
switch {
|
||||
case resp.Result != nil:
|
||||
result, _ := json.Marshal(resp.Result)
|
||||
logger.Printf("jsonrpc2: --> result #%s: %s: %s\n", resp.ID, method, result)
|
||||
case resp.Error != nil:
|
||||
err, _ := json.Marshal(resp.Error)
|
||||
logger.Printf("jsonrpc2: --> error #%s: %s: %s\n", resp.ID, method, err)
|
||||
}
|
||||
}
|
||||
})(c)
|
||||
OnSend(func(req *Request, resp *Response) {
|
||||
switch {
|
||||
case req != nil:
|
||||
params, _ := json.Marshal(req.Params)
|
||||
if req.Notif {
|
||||
logger.Printf("jsonrpc2: <-- notif: %s: %s\n", req.Method, params)
|
||||
} else {
|
||||
logger.Printf("jsonrpc2: <-- request #%s: %s: %s\n", req.ID, req.Method, params)
|
||||
}
|
||||
|
||||
case resp != nil:
|
||||
mu.Lock()
|
||||
method := reqMethods[resp.ID]
|
||||
delete(reqMethods, resp.ID)
|
||||
mu.Unlock()
|
||||
if method == "" {
|
||||
method = "(no previous request)"
|
||||
}
|
||||
|
||||
if resp.Result != nil {
|
||||
result, _ := json.Marshal(resp.Result)
|
||||
logger.Printf("jsonrpc2: <-- result #%s: %s: %s\n", resp.ID, method, result)
|
||||
} else {
|
||||
err, _ := json.Marshal(resp.Error)
|
||||
logger.Printf("jsonrpc2: <-- error #%s: %s: %s\n", resp.ID, method, err)
|
||||
}
|
||||
}
|
||||
})(c)
|
||||
}
|
||||
}
|
||||
|
||||
// SetLogger sets the logger for the connection.
|
||||
func SetLogger(logger Logger) ConnOpt {
|
||||
return func(c *Conn) {
|
||||
c.logger = logger
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
package jsonrpc2
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// HandlerWithError implements Handler by calling the func for each
|
||||
// request and handling returned errors and results.
|
||||
func HandlerWithError(handleFunc func(context.Context, *Conn, *Request) (result interface{}, err error)) *HandlerWithErrorConfigurer {
|
||||
return &HandlerWithErrorConfigurer{handleFunc: handleFunc}
|
||||
}
|
||||
|
||||
// HandlerWithErrorConfigurer is a handler created by HandlerWithError.
|
||||
type HandlerWithErrorConfigurer struct {
|
||||
handleFunc func(context.Context, *Conn, *Request) (result interface{}, err error)
|
||||
suppressErrClosed bool
|
||||
}
|
||||
|
||||
// Handle implements Handler.
|
||||
func (h *HandlerWithErrorConfigurer) Handle(ctx context.Context, conn *Conn, req *Request) {
|
||||
result, err := h.handleFunc(ctx, conn, req)
|
||||
if req.Notif {
|
||||
if err != nil {
|
||||
conn.logger.Printf("jsonrpc2 handler: notification %q handling error: %v\n", req.Method, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
resp := &Response{ID: req.ID}
|
||||
if err == nil {
|
||||
err = resp.SetResult(result)
|
||||
}
|
||||
if err != nil {
|
||||
if e, ok := err.(*Error); ok {
|
||||
resp.Error = e
|
||||
} else {
|
||||
resp.Error = &Error{Message: err.Error()}
|
||||
}
|
||||
}
|
||||
|
||||
if !req.Notif {
|
||||
if err := conn.SendResponse(ctx, resp); err != nil {
|
||||
if err != ErrClosed || !h.suppressErrClosed {
|
||||
conn.logger.Printf("jsonrpc2 handler: sending response %s: %v\n", resp.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SuppressErrClosed makes the handler suppress jsonrpc2.ErrClosed errors from
|
||||
// being logged. The original handler `h` is returned.
|
||||
//
|
||||
// This is optional because only in some cases is this behavior desired. For
|
||||
// example, a handler that serves end-user connections may not want to log
|
||||
// ErrClosed because it just indicates the end-user connection has gone away
|
||||
// for any reason (they could have lost wifi connection, are no longer
|
||||
// interested in the request and closed the connection, etc) and as such it
|
||||
// would be log spam, whereas a handler that serves internal connections would
|
||||
// never expect connections to go away unexpectedly (which could indicate
|
||||
// service degradation, etc) and as such ErrClosed should always be logged.
|
||||
func (h *HandlerWithErrorConfigurer) SuppressErrClosed() Handler {
|
||||
h.suppressErrClosed = true
|
||||
return h
|
||||
}
|
||||