├── www ├── .eslintignore ├── .gitignore ├── favicon.png ├── .eslintrc.js ├── package.json ├── webpack.config.js ├── index.html └── main.js ├── rustfmt-wasm ├── .dockerignore ├── .gitignore ├── rust-toolchain ├── Dockerfile ├── Cargo.toml ├── readme.md └── lib.rs ├── gors-cli ├── tests │ ├── files │ │ ├── package.go │ │ ├── unary.go │ │ ├── chan.go │ │ ├── pointer.go │ │ ├── call.go │ │ ├── const.go │ │ ├── goroutine.go │ │ ├── var.go │ │ ├── import.go │ │ ├── primary.go │ │ ├── interface.go │ │ ├── if.go │ │ ├── method.go │ │ ├── slice.go │ │ ├── map.go │ │ ├── array.go │ │ ├── precedence.go │ │ ├── struct.go │ │ ├── for.go │ │ ├── arithmetic.go │ │ ├── defer.go │ │ ├── func.go │ │ ├── type.go │ │ └── comment.go │ ├── go.mod │ ├── programs │ │ ├── hello_world.go │ │ ├── fibonacci_recursive.go │ │ ├── fibonacci_iterative.go │ │ └── fizzbuzz.go │ ├── main.go │ ├── print.go │ └── tests.rs ├── rust-toolchain ├── Cargo.toml └── main.rs ├── fuzz ├── .gitignore ├── rust-toolchain ├── fuzz_targets │ └── scanner.rs ├── Cargo.toml └── Cargo.lock ├── gors ├── rust-toolchain ├── lib.rs ├── ast │ ├── hashable.rs │ ├── printer.rs │ └── mod.rs ├── codegen │ └── mod.rs ├── Cargo.toml ├── compiler │ ├── passes │ │ ├── simplify_return.rs │ │ ├── flatten_block.rs │ │ ├── map_type.rs │ │ ├── hoist_use.rs │ │ ├── type_conversion.rs │ │ ├── inline_fmt.rs │ │ └── mod.rs │ └── mod.rs ├── token │ └── mod.rs └── scanner │ └── mod.rs ├── .cargo └── config ├── gors-wasm ├── package.json ├── readme.md ├── rust-toolchain ├── Cargo.toml └── lib.rs ├── gofmt-wasm ├── go.mod └── lib.go ├── .gitignore ├── Cargo.toml ├── .gitmodules ├── readme.md ├── .github └── workflows │ └── ci.yml └── Cargo.lock /www/.eslintignore: -------------------------------------------------------------------------------- 1 | .gitignore -------------------------------------------------------------------------------- /rustfmt-wasm/.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore -------------------------------------------------------------------------------- /gors-cli/tests/files/package.go: -------------------------------------------------------------------------------- 1 | package main 2 | -------------------------------------------------------------------------------- /rustfmt-wasm/.gitignore: -------------------------------------------------------------------------------- 1 | /pkg/ 2 | /target/ 3 | -------------------------------------------------------------------------------- /www/.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | -------------------------------------------------------------------------------- /gors/rust-toolchain: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | -------------------------------------------------------------------------------- /fuzz/rust-toolchain: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | -------------------------------------------------------------------------------- /gors-cli/rust-toolchain: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | -------------------------------------------------------------------------------- /.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["-C", "target-cpu=native"] 3 | -------------------------------------------------------------------------------- /gors-wasm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gors", 3 | "version": "0.1.0" 4 | } 5 | -------------------------------------------------------------------------------- /gofmt-wasm/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aymericbeaumet/gors/gofmt-wasm 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /www/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aymericbeaumet/gors/HEAD/www/favicon.png -------------------------------------------------------------------------------- /gors-cli/tests/files/unary.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | a := +-!^*&<-0 5 | } 6 | -------------------------------------------------------------------------------- /gors-cli/tests/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aymericbeaumet/gors/gors-cli/tests 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /gors-wasm/readme.md: -------------------------------------------------------------------------------- 1 | # gors-wasm 2 | 3 | ## Compile 4 | 5 | ``` 6 | wasm-pack build 7 | ``` 8 | -------------------------------------------------------------------------------- /gors-wasm/rust-toolchain: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | targets = ["wasm32-unknown-unknown"] 4 | -------------------------------------------------------------------------------- /rustfmt-wasm/rust-toolchain: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2021-03-26" 3 | targets = ["wasm32-unknown-unknown"] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /gors-cli/tests/files/{go,gin,moby,kubernetes,hugo} 2 | /gors-cli/tests/gors-go 3 | /main* 4 | pkg/ 5 | target/ 6 | -------------------------------------------------------------------------------- /gors-cli/tests/programs/hello_world.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("Hello, World!") 7 | } 8 | -------------------------------------------------------------------------------- /gors/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::all, clippy::nursery)] 2 | 3 | pub mod ast; 4 | pub mod codegen; 5 | pub mod compiler; 6 | pub mod parser; 7 | pub mod scanner; 8 | pub mod token; 9 | -------------------------------------------------------------------------------- /gors-cli/tests/files/chan.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | a := make(chan int) 5 | 6 | a <- a 7 | a(<-a) 8 | 9 | a <- <-a 10 | a <- (<-a) 11 | 12 | <-a <- a 13 | <-a(<-a) 14 | } 15 | -------------------------------------------------------------------------------- /gors-cli/tests/files/pointer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var a *int 4 | var b **int 5 | var c ***int 6 | 7 | func main() { 8 | var d *string = nil 9 | var e **string = nil 10 | var f ***string = nil 11 | } 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "gors", 4 | "gors-cli", 5 | "gors-wasm", 6 | ] 7 | 8 | [profile.dev] 9 | opt-level = 0 10 | 11 | [profile.release] 12 | codegen-units = 1 13 | lto = "fat" 14 | opt-level = 3 15 | -------------------------------------------------------------------------------- /gors-cli/tests/files/call.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | hello("World") 7 | } 8 | 9 | func hello(name string) { 10 | fmt.Print("Hello, ") 11 | fmt.Print(name) 12 | fmt.Println("!") 13 | } 14 | -------------------------------------------------------------------------------- /gors-cli/tests/files/const.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const a int = 1 4 | const b, c int = 2, 3 5 | 6 | const () 7 | 8 | const ( 9 | d int = 1 10 | e, f int = 1, 2 11 | ) 12 | 13 | func main() { 14 | const zero = 0 15 | } 16 | -------------------------------------------------------------------------------- /gors-cli/tests/files/goroutine.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | go Server() 5 | 6 | f := func(x, y int) int { return x + y } 7 | go f(0, 1) 8 | 9 | go func(ch chan<- bool) { 10 | sleep(10) 11 | ch <- true 12 | }(c) 13 | } 14 | -------------------------------------------------------------------------------- /gors/ast/hashable.rs: -------------------------------------------------------------------------------- 1 | use crate::ast; 2 | use std::hash::{Hash, Hasher}; 3 | 4 | impl<'a> Hash for &ast::ImportSpec<'a> { 5 | fn hash(&self, state: &mut H) { 6 | std::ptr::hash((*self) as *const ast::ImportSpec, state); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /gors-cli/tests/files/var.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var a int 4 | var b, c int 5 | 6 | var d int = 1 7 | var e, f int = 2, 3 8 | 9 | var () 10 | 11 | var ( 12 | g int = 1 13 | h, i int = 1, 2 14 | ) 15 | 16 | func main() { 17 | var zero = 0 18 | } 19 | -------------------------------------------------------------------------------- /gors-cli/tests/files/import.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "lib/math" 4 | import m "lib/math" 5 | import . "lib/math" 6 | import _ "lib/math" 7 | 8 | import () 9 | 10 | import ( 11 | "lib/math" 12 | . "lib/math" 13 | _ "lib/math" 14 | m "lib/math" 15 | ) 16 | -------------------------------------------------------------------------------- /gors-cli/tests/files/primary.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | x 5 | 2 6 | (s + ".txt") 7 | f(3.1415, true) 8 | //Point{1, 2} 9 | m["foo"] 10 | s[i : j+1] 11 | obj.color 12 | f.p[i].x() 13 | (x).(T) 14 | x.(T) 15 | (x.y).(T) 16 | x.y.(T) 17 | } 18 | -------------------------------------------------------------------------------- /gors-cli/tests/programs/fibonacci_recursive.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println(fibonacci(10)) 7 | } 8 | 9 | func fibonacci(n uint) uint { 10 | if n <= 1 { 11 | return n 12 | } 13 | return fibonacci(n-1) + fibonacci(n-2) 14 | } 15 | -------------------------------------------------------------------------------- /www/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | }, 6 | extends: [ 7 | 'airbnb-base', 8 | ], 9 | parserOptions: { 10 | ecmaVersion: 13, 11 | sourceType: 'module', 12 | }, 13 | rules: { 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /rustfmt-wasm/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.57.0-alpine3.14 2 | 3 | RUN apk add --no-cache libc-dev npm 4 | RUN wget https://rustwasm.github.io/wasm-pack/installer/init.sh -O - | sh 5 | 6 | WORKDIR /root 7 | COPY . . 8 | RUN CFG_RELEASE=nightly wasm-pack build --release 9 | 10 | ENTRYPOINT ["/bin/sh"] 11 | -------------------------------------------------------------------------------- /gors-cli/tests/files/interface.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type i1 interface{} 4 | 5 | type i2 interface { 6 | Read(byte) (int, error) 7 | Write(byte) (int, error) 8 | Close() error 9 | } 10 | 11 | type i3 interface { 12 | i2 13 | } 14 | 15 | type Locker interface { 16 | Lock() 17 | Unlock() 18 | } 19 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/scanner.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | 4 | fuzz_target!(|data: &[u8]| { 5 | if let Ok(buffer) = std::str::from_utf8(data) { 6 | let _ = gors::scanner::Scanner::new(file!(), buffer) 7 | .into_iter() 8 | .collect::>(); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /gors-cli/tests/files/if.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | if x > max { 5 | x = max 6 | } else { 7 | x = 0 8 | } 9 | 10 | if x := f(); x < y { 11 | return x 12 | } else if x > z { 13 | return z 14 | } else { 15 | return y 16 | } 17 | 18 | if m, ok := memoized[n]; ok { 19 | return m 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /gors-cli/tests/files/method.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "math" 4 | 5 | type Point struct { 6 | x, y float64 7 | } 8 | 9 | func (p *Point) Length() float64 { 10 | x2 := p.x * p.x 11 | y2 := p.y * p.y 12 | return math.Sqrt(x2 + y2) 13 | } 14 | 15 | func (p *Point) Scale(factor float64) { 16 | p.x *= factor 17 | p.y *= factor 18 | } 19 | -------------------------------------------------------------------------------- /gors-cli/tests/programs/fibonacci_iterative.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | n := uint(10) 7 | fmt.Println(fibonacci(n)) 8 | } 9 | 10 | func fibonacci(n uint) uint { 11 | a, b := uint(0), uint(1) 12 | 13 | for i, max := uint(1), n; i < max; i++ { 14 | a, b = b, a+b 15 | } 16 | 17 | return b 18 | } 19 | -------------------------------------------------------------------------------- /gors-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gors-wasm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [lib] 8 | name = "gors" 9 | path = "lib.rs" 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | console_error_panic_hook = "0.1.7" 14 | gors = { path = "../gors" } 15 | js-sys = "0.3.55" 16 | wasm-bindgen = "0.2.78" 17 | -------------------------------------------------------------------------------- /gors-cli/tests/files/slice.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var a []string 7 | 8 | primes := []int{2, 3, 5, 7, 11, 13} 9 | 10 | var s []int = primes[1:4] 11 | var s []int = primes[:4] 12 | var s []int = primes[1:] 13 | var s []int = primes[:] 14 | fmt.Println(s) 15 | 16 | t := a[1:3:5] 17 | t := a[:3:5] 18 | } 19 | -------------------------------------------------------------------------------- /gors-cli/tests/programs/fizzbuzz.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fizzbuzz(60) 7 | } 8 | 9 | func fizzbuzz(i int) { 10 | if i%15 == 0 { 11 | fmt.Println("Fizz Buzz") 12 | } else if i%3 == 0 { 13 | fmt.Println("Fizz") 14 | } else if i%5 == 0 { 15 | fmt.Println("Buzz") 16 | } else { 17 | fmt.Println(i) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fuzz" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [[bin]] 11 | name = "scanner" 12 | path = "fuzz_targets/scanner.rs" 13 | test = false 14 | doc = false 15 | 16 | [dependencies] 17 | libfuzzer-sys = "0.4.2" 18 | gors = { path = "../gors" } 19 | 20 | [workspace] 21 | members = ["."] 22 | -------------------------------------------------------------------------------- /gors-cli/tests/files/map.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | var m = map[string]string{} 5 | var n = map[string]int{} 6 | var o = map[string]struct{ x, y float64 }{} 7 | var p = map[string]interface{}{} 8 | 9 | var memoized = map[uint]uint{ 10 | 0: 0, 11 | 1: 1, 12 | } 13 | 14 | var memoized = map[uint]uint{0: 0, 1: 1} 15 | 16 | var memoized = map[uint]uint{} 17 | 18 | a := m[0] 19 | } 20 | -------------------------------------------------------------------------------- /gors-cli/tests/files/array.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | var a [2]string 7 | a[0] = "Hello" 8 | a[1] = "World" 9 | fmt.Println(a[0], a[1]) 10 | fmt.Println(a) 11 | 12 | b := [...]string{} 13 | b := [...]string{"a"} 14 | b := [...]string{"a", "b", "c"} 15 | 16 | primes := [6]int{} 17 | primes := [6]int{2} 18 | primes := [6]int{2, 3, 5, 7, 11, 13} 19 | fmt.Println(primes) 20 | } 21 | -------------------------------------------------------------------------------- /gors-cli/tests/files/precedence.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | a + b*c 5 | a*b + c 6 | 7 | -a*-a/-a%-a<<-a>>-a&-a&^-a+-a-a|-a^-a == -a != -a < -a <= -a > -a >= -a && -a || -a 8 | -b || -b && -b >= -b > -b <= -b < -b != -b == -b^-b|-b-b+-b&^-b&-b>>-b<<-b%-b/-b*-b 9 | 10 | (z + z) * (z + z) 11 | (z == z) | (z == z) 12 | (z && z) == (z && z) 13 | (z || z) && (z || z) 14 | 15 | *p++ 16 | (*p)++ 17 | } 18 | -------------------------------------------------------------------------------- /rustfmt-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustfmt" 3 | version = "0.1.0" 4 | edition = "2018" 5 | publish = false 6 | 7 | [lib] 8 | name = "rustfmt" 9 | path = "lib.rs" 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | rustfmt-nightly = { git = "https://github.com/rust-lang/rustfmt.git", rev = "v1.4.37" } 14 | console_error_panic_hook = "0.1.7" 15 | wasm-bindgen = "0.2.78" 16 | 17 | [workspace] 18 | members = ["."] 19 | -------------------------------------------------------------------------------- /gofmt-wasm/lib.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | gofmt "go/format" 5 | "syscall/js" 6 | ) 7 | 8 | func main() { 9 | c := make(chan struct{}, 0) 10 | js.Global().Set("format", js.FuncOf(format)) 11 | <-c 12 | } 13 | 14 | func format(this js.Value, p []js.Value) interface{} { 15 | result, err := gofmt.Source([]byte(p[0].String())) 16 | if err != nil { 17 | panic(err) 18 | } 19 | return js.ValueOf(string(result)) 20 | } 21 | -------------------------------------------------------------------------------- /gors-wasm/lib.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | 3 | #[wasm_bindgen] 4 | pub fn build(input: String) -> String { 5 | console_error_panic_hook::set_once(); 6 | 7 | let ast = gors::parser::parse_file("main.go", &input).unwrap(); 8 | let compiled = gors::compiler::compile(ast).unwrap(); 9 | 10 | let mut w = vec![]; 11 | gors::codegen::fprint(&mut w, compiled).unwrap(); 12 | 13 | String::from_utf8(w).unwrap() 14 | } 15 | -------------------------------------------------------------------------------- /gors-cli/tests/files/struct.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type s1 struct{} 4 | 5 | type s2 struct { 6 | s1 7 | a bool 8 | b, c bool 9 | } 10 | 11 | type s3 struct { 12 | *s2 13 | a bool `hello:"world"` 14 | b, c bool `hello:"world"` 15 | } 16 | 17 | var v1 struct{} 18 | 19 | var v2 struct { 20 | s1 21 | a bool 22 | b, c bool 23 | } 24 | 25 | var v3 struct { 26 | *s2 27 | a bool `hello:"world"` 28 | b, c bool `hello:"world"` 29 | } 30 | -------------------------------------------------------------------------------- /gors-cli/tests/files/for.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | for a < b { 5 | } 6 | 7 | for i := 0; i < 10; i++ { 8 | } 9 | 10 | for i := 0; i < 10; { 11 | } 12 | 13 | for ; i < 10; i++ { 14 | } 15 | 16 | for i := 0;; { 17 | } 18 | 19 | for ;i < 10; { 20 | } 21 | 22 | for ;;i++ { 23 | } 24 | 25 | for ;; { 26 | } 27 | 28 | for { 29 | } 30 | 31 | for i, _ := range ch { 32 | } 33 | 34 | for w := range ch { 35 | } 36 | 37 | for range ch { 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /gors-cli/tests/files/arithmetic.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | a := 0 5 | 6 | a = a + a 7 | a += a 8 | 9 | a = a - a 10 | a -= a 11 | 12 | a = a * a 13 | a *= a 14 | 15 | a = a / a 16 | a /= a 17 | 18 | a = a % a 19 | a %= a 20 | 21 | a = a & a 22 | a &= a 23 | 24 | a = a | a 25 | a |= a 26 | 27 | a = a ^ a 28 | a ^= a 29 | 30 | a = a &^ a 31 | a &^= a 32 | 33 | a = a << a 34 | a <<= a 35 | 36 | a = a >> a 37 | a >>= a 38 | 39 | a++ 40 | a-- 41 | } 42 | -------------------------------------------------------------------------------- /gors/codegen/mod.rs: -------------------------------------------------------------------------------- 1 | pub fn fprint( 2 | mut w: W, 3 | file: syn::File, 4 | ) -> Result<(), Box> { 5 | let formatted = prettyplease::unparse(&file); 6 | 7 | for (i, line) in formatted.lines().enumerate() { 8 | if i > 0 && (line.starts_with("fn") || line.starts_with("pub fn")) { 9 | w.write_all(b"\n")?; 10 | } 11 | w.write_all(line.as_bytes())?; 12 | w.write_all(b"\n")?; 13 | } 14 | 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /gors-cli/tests/files/defer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | lock(l) 7 | defer unlock(l) // unlocking happens before surrounding function returns 8 | 9 | // prints 3 2 1 0 before surrounding function returns 10 | for i := 0; i <= 3; i++ { 11 | defer fmt.Print(i) 12 | } 13 | } 14 | 15 | // f returns 42 16 | func f() (result int) { 17 | defer func() { 18 | // result is accessed after it was set to 6 by the return statement 19 | result *= 7 20 | }() 21 | return 6 22 | } 23 | -------------------------------------------------------------------------------- /rustfmt-wasm/readme.md: -------------------------------------------------------------------------------- 1 | # rustfmt-wasm 2 | 3 | This crate lives outside of the Cargo workpace because it requires the unstable 4 | Rust toolchain. It is published to npm. 5 | 6 | ## Build 7 | 8 | ``` 9 | docker build --platform=linux/amd64 -t rustfmt-wasm . 10 | ``` 11 | 12 | ## Publish 13 | 14 | ``` 15 | docker run --platform=linux/amd64 --rm -it rustfmt-wasm 16 | $ npm login 17 | $ npm publish --access=public ./pkg 18 | ``` 19 | 20 | ## Development 21 | 22 | ``` 23 | rm -rf ./pkg && docker cp $(docker create --rm rustfmt-wasm):/root/pkg ./pkg 24 | ``` 25 | -------------------------------------------------------------------------------- /gors-cli/tests/files/func.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func f0() {} 4 | 5 | func f1() { 6 | return 7 | } 8 | 9 | func f2(a int, b, c string, d ...bool) bool { 10 | return true 11 | } 12 | 13 | func f3(a int, b, c string, d ...bool) (bool, bool) { 14 | return true, false 15 | } 16 | 17 | func f4(a int, b, c string, d ...bool) (e, f bool, g string) { 18 | return 19 | } 20 | 21 | func main() { 22 | t.Mv(7) 23 | T.Mv(t, 7) 24 | (T).Mv(t, 7) 25 | f1 := T.Mv 26 | f1(t, 7) 27 | f2 := (T).Mv 28 | f2(t, 7) 29 | 30 | s := []string{} 31 | f2(s...) 32 | f2(t, s...) 33 | } 34 | -------------------------------------------------------------------------------- /gors-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gors-cli" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [[bin]] 8 | name = "gors" 9 | path = "main.rs" 10 | 11 | [dependencies] 12 | clap = { version = "3.0.0-rc.0", features = ["derive"] } 13 | gors = { path = "../gors" } 14 | pretty_env_logger = "0.4.0" 15 | serde_json = "1.0.72" 16 | tempdir = "0.3.7" 17 | 18 | [dev-dependencies] 19 | colored = "2.0.0" 20 | console = "0.15.0" 21 | crossbeam = "0.8.1" 22 | glob = "0.3.0" 23 | lazy_static = "1.4.0" 24 | num_cpus = "1.13.0" 25 | phf = { version = "0.10.0", features = ["macros"] } 26 | pretty_assertions = "1.0.0" 27 | similar = { version = "2.1.0", features = ["inline"] } 28 | -------------------------------------------------------------------------------- /gors/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gors" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Experimental go toolchain written in rust (parser, compiler) " 7 | homepage = "https://aymericbeaumet.github.io/gors/" 8 | repository = "https://github.com/aymericbeaumet/gors/" 9 | 10 | [lib] 11 | name = "gors" 12 | path = "lib.rs" 13 | 14 | [dependencies] 15 | log = { version = "0.4.14", features = ["release_max_level_warn"] } 16 | phf = { version = "0.10.1", features = ["macros"] } 17 | prettyplease = "0.1.0" 18 | proc-macro2 = "1.0.34" 19 | quote = "1.0.10" 20 | serde = "1.0.132" 21 | syn = { version = "1.0.82", features = ["full", "visit-mut"] } 22 | unicode-general-category = "0.4.0" 23 | -------------------------------------------------------------------------------- /rustfmt-wasm/lib.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | 3 | #[wasm_bindgen] 4 | pub fn format(input: String) -> String { 5 | console_error_panic_hook::set_once(); 6 | 7 | // 8 | let mut out = vec![]; 9 | 10 | let mut config = rustfmt_nightly::Config::default(); 11 | config.set().emit_mode(rustfmt_nightly::EmitMode::Stdout); 12 | config.set().edition(rustfmt_nightly::Edition::Edition2021); 13 | config.set().verbose(rustfmt_nightly::Verbosity::Quiet); 14 | 15 | { 16 | let mut session = rustfmt_nightly::Session::new(config, Some(&mut out)); 17 | session.format(rustfmt_nightly::Input::Text(input)).unwrap(); 18 | } 19 | 20 | let formatted = String::from_utf8(out).unwrap(); 21 | // 22 | 23 | formatted 24 | } 25 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "gors-cli/tests/files/go"] 2 | path = gors-cli/tests/files/go 3 | url = https://github.com/golang/go.git 4 | shallow = true 5 | 6 | [submodule "gors-cli/tests/files/kubernetes"] 7 | path = gors-cli/tests/files/kubernetes 8 | url = https://github.com/kubernetes/kubernetes.git 9 | shallow = true 10 | 11 | [submodule "gors-cli/tests/files/moby"] 12 | path = gors-cli/tests/files/moby 13 | url = https://github.com/moby/moby.git 14 | shallow = true 15 | 16 | [submodule "gors-cli/tests/files/hugo"] 17 | path = gors-cli/tests/files/hugo 18 | url = https://github.com/gohugoio/hugo.git 19 | shallow = true 20 | 21 | [submodule "gors-cli/tests/files/gin"] 22 | path = gors-cli/tests/files/gin 23 | url = https://github.com/gin-gonic/gin.git 24 | shallow = true 25 | -------------------------------------------------------------------------------- /gors/compiler/passes/simplify_return.rs: -------------------------------------------------------------------------------- 1 | use syn::visit_mut::VisitMut; 2 | 3 | pub fn pass(file: &mut syn::File) { 4 | SimplifyReturn.visit_file_mut(file); 5 | } 6 | 7 | struct SimplifyReturn; 8 | 9 | impl VisitMut for SimplifyReturn { 10 | fn visit_item_fn_mut(&mut self, item_fn: &mut syn::ItemFn) { 11 | if let Some(last) = item_fn.block.stmts.last_mut() { 12 | let expr = match last { 13 | syn::Stmt::Semi(expr, _) => expr, 14 | syn::Stmt::Expr(expr) => expr, 15 | _ => return, 16 | }; 17 | 18 | if let syn::Expr::Return(ret) = expr { 19 | if let Some(expr) = ret.expr.take() { 20 | *last = syn::Stmt::Expr(*expr); 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /www/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "www", 3 | "scripts": { 4 | "start": "webpack serve --mode=development", 5 | "build": "webpack --mode=production", 6 | "lint": "eslint ." 7 | }, 8 | "dependencies": { 9 | "gors": "file:../gors-wasm/pkg", 10 | "monaco-editor": "0.31.1" 11 | }, 12 | "devDependencies": { 13 | "css-loader": "6.5.1", 14 | "eslint": "8.5.0", 15 | "eslint-config-airbnb-base": "15.0.0", 16 | "eslint-plugin-import": "2.25.3", 17 | "favicons": "6.2.2", 18 | "favicons-webpack-plugin": "5.0.2", 19 | "file-loader": "6.2.0", 20 | "html-webpack-plugin": "5.5.0", 21 | "monaco-editor-webpack-plugin": "7.0.1", 22 | "prettier": "2.5.1", 23 | "style-loader": "3.3.1", 24 | "webpack": "5.65.0", 25 | "webpack-cli": "4.9.1", 26 | "webpack-dev-server": "4.6.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gors/compiler/passes/flatten_block.rs: -------------------------------------------------------------------------------- 1 | use syn::visit_mut::{self, VisitMut}; 2 | 3 | pub fn pass(file: &mut syn::File) { 4 | FlattenBlock.visit_file_mut(file); 5 | } 6 | 7 | struct FlattenBlock; 8 | 9 | impl VisitMut for FlattenBlock { 10 | fn visit_block_mut(&mut self, block: &mut syn::Block) { 11 | visit_mut::visit_block_mut(self, block); // depth-first 12 | 13 | for stmt in block.stmts.iter_mut() { 14 | if let syn::Stmt::Expr(syn::Expr::Block(b)) = stmt { 15 | let bstmts = &b.block.stmts; 16 | if bstmts.len() == 1 { 17 | if let syn::Stmt::Expr(expr) = &bstmts[0] { 18 | *stmt = syn::Stmt::Expr(expr.clone()); 19 | continue; 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gors/compiler/passes/map_type.rs: -------------------------------------------------------------------------------- 1 | use syn::visit_mut::{self, VisitMut}; 2 | 3 | pub fn pass(file: &mut syn::File) { 4 | MapType.visit_file_mut(file); 5 | } 6 | 7 | struct MapType; 8 | 9 | impl VisitMut for MapType { 10 | fn visit_ident_mut(&mut self, ident: &mut syn::Ident) { 11 | let name = match ident.to_string().as_str() { 12 | "bool" => "bool", 13 | "rune" => "u32", 14 | "string" => "String", 15 | "float32" => "f32", 16 | "float64" => "f64", 17 | "int" => "isize", 18 | "int8" => "i8", 19 | "int16" => "i16", 20 | "int32" => "i32", 21 | "int64" => "i64", 22 | "uint" => "usize", 23 | "uint8" => "u8", 24 | "uint16" => "u16", 25 | "uint32" => "u32", 26 | "uint64" => "u64", 27 | _ => return, 28 | }; 29 | *ident = quote::format_ident!("{}", name); 30 | 31 | visit_mut::visit_ident_mut(self, ident); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /gors/compiler/passes/hoist_use.rs: -------------------------------------------------------------------------------- 1 | use syn::visit_mut::VisitMut; 2 | 3 | pub fn pass(file: &mut syn::File) { 4 | let mut p = HoistUse::default(); 5 | p.visit_file_mut(file); 6 | 7 | for (i, u) in p.uses.into_values().enumerate() { 8 | file.items.insert(i, u); 9 | } 10 | } 11 | 12 | #[derive(Default)] 13 | struct HoistUse { 14 | uses: std::collections::BTreeMap, 15 | } 16 | 17 | impl VisitMut for HoistUse { 18 | fn visit_path_mut(&mut self, path: &mut syn::Path) { 19 | if path.segments.len() > 1 { 20 | // Save the "use" to hoist it later 21 | self.uses.insert( 22 | (quote::quote! { #path }).to_string(), 23 | syn::parse_quote! { use #path; }, 24 | ); 25 | 26 | // Trim the pass segment to only keep the latest element 27 | let ident = path.segments.last().unwrap(); 28 | path.leading_colon = None; 29 | path.segments = syn::parse_quote! { #ident }; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /www/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const FaviconsWebpackPlugin = require('favicons-webpack-plugin'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); 5 | 6 | module.exports = { 7 | entry: './main.js', 8 | output: { 9 | filename: 'bundle.js', 10 | path: path.resolve(__dirname, 'dist'), 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.css$/i, 16 | use: ['style-loader', 'css-loader'], 17 | }, 18 | { 19 | test: /\.ttf$/, 20 | use: ['file-loader'], 21 | }, 22 | ], 23 | }, 24 | plugins: [ 25 | new FaviconsWebpackPlugin('./favicon.png'), 26 | new HtmlWebpackPlugin({ template: 'index.html' }), 27 | new MonacoWebpackPlugin(), 28 | ], 29 | devServer: { 30 | static: { 31 | directory: path.resolve(__dirname, 'dist'), 32 | }, 33 | compress: true, 34 | port: 8080, 35 | }, 36 | experiments: { 37 | asyncWebAssembly: true, 38 | }, 39 | }; 40 | -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | gors: experimental go compiler 6 | 7 | 42 | 43 | 44 |
45 |

gors is an experimental Go compiler that generates Rust code.

46 | 47 |
48 |
49 |
50 |
51 | 52 |

53 |     
54 | 55 | 56 | -------------------------------------------------------------------------------- /gors-cli/tests/files/type.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type my_int = int 4 | 5 | type () 6 | 7 | type ( 8 | Polar = polar 9 | ) 10 | 11 | type ( 12 | Point struct{ x, y float64 } 13 | polar Point 14 | ) 15 | 16 | type NewMutex Mutex 17 | 18 | type Accounts map[string]string 19 | 20 | type testMemoryManager struct { 21 | description string 22 | machineInfo cadvisorapi.MachineInfo 23 | assignments state.ContainerMemoryAssignments 24 | expectedAssignments state.ContainerMemoryAssignments 25 | machineState state.NUMANodeMap 26 | expectedMachineState state.NUMANodeMap 27 | expectedError error 28 | expectedAllocateError error 29 | expectedAddContainerError error 30 | updateError error 31 | removeContainerID string 32 | nodeAllocatableReservation v1.ResourceList 33 | policyName policyType 34 | affinity topologymanager.Store 35 | systemReservedMemory []kubeletconfig.MemoryReservation 36 | expectedHints map[string][]topologymanager.TopologyHint 37 | expectedReserved systemReservedMemory 38 | reserved systemReservedMemory 39 | podAllocate *v1.Pod 40 | firstPod *v1.Pod 41 | activePods []*v1.Pod 42 | } 43 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # gors [![GitHub Actions](https://github.com/aymericbeaumet/gors/actions/workflows/ci.yml/badge.svg)](https://github.com/aymericbeaumet/gors/actions/workflows/ci.yml) 2 | 3 | [gors](https://github.com/aymericbeaumet/gors) is an experimental go toolchain 4 | written in rust (parser, compiler, codegen). 5 | 6 | ## Install 7 | 8 | ### Using git 9 | 10 | _This method requires the [Rust 11 | toolchain](https://www.rust-lang.org/tools/install) to be installed on your 12 | machine._ 13 | 14 | ``` 15 | git clone -–depth=1 https://github.com/aymericbeaumet/gors.git /tmp/gors 16 | cargo install --path=/tmp/gors/gors-cli 17 | ``` 18 | 19 | ## Development 20 | 21 | ``` 22 | brew install rustup go@1.17 binaryen watchexec 23 | rustup toolchain install stable && rustup toolchain install nightly && rustup default stable 24 | curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 25 | cargo install --force cargo-fuzz 26 | ``` 27 | 28 | ``` 29 | cargo clippy 30 | cargo build 31 | cargo test -- --nocapture --test-threads=1 32 | cargo +nightly fuzz run scanner 33 | cargo doc -p gors --open 34 | ``` 35 | 36 | ``` 37 | RUST_LOG=debug cargo run -- tokens gors-cli/tests/programs/fizzbuzz.go 38 | RUST_LOG=debug cargo run -- ast gors-cli/tests/programs/fizzbuzz.go 39 | RUST_LOG=debug cargo run -- build --emit=rust gors-cli/tests/programs/fizzbuzz.go 40 | RUST_LOG=debug cargo run -- run gors-cli/tests/programs/fizzbuzz.go 41 | ``` 42 | -------------------------------------------------------------------------------- /gors/compiler/passes/type_conversion.rs: -------------------------------------------------------------------------------- 1 | use syn::visit_mut::{self, VisitMut}; 2 | 3 | pub fn pass(file: &mut syn::File) { 4 | TypeConversion.visit_file_mut(file); 5 | } 6 | 7 | struct TypeConversion; 8 | 9 | impl VisitMut for TypeConversion { 10 | fn visit_expr_mut(&mut self, expr: &mut syn::Expr) { 11 | if let syn::Expr::Call(expr_call) = expr { 12 | if let syn::Expr::Path(path) = &*expr_call.func { 13 | let segments = &path.path.segments; 14 | if segments.len() == 1 && is_type(&segments[0].ident) { 15 | *expr = syn::Expr::Cast(syn::ExprCast { 16 | attrs: vec![], 17 | expr: Box::new(expr_call.args[0].clone()), 18 | as_token: ::default(), 19 | ty: Box::new(syn::Type::Path(syn::TypePath { 20 | path: syn::Path { 21 | leading_colon: None, 22 | segments: segments.clone(), 23 | }, 24 | qself: None, 25 | })), 26 | }) 27 | } 28 | } 29 | } 30 | 31 | visit_mut::visit_expr_mut(self, expr); 32 | } 33 | } 34 | 35 | fn is_type(ident: &syn::Ident) -> bool { 36 | let s = ident.to_string(); 37 | matches!( 38 | s.as_str(), 39 | "f32" 40 | | "f64" 41 | | "isize" 42 | | "i8" 43 | | "i16" 44 | | "i32" 45 | | "i64" 46 | | "usize" 47 | | "u8" 48 | | "u32" 49 | | "u16" 50 | | "u64" 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /gors-cli/tests/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "fmt" 7 | "go/parser" 8 | "go/scanner" 9 | "go/token" 10 | "io/ioutil" 11 | "os" 12 | "os/exec" 13 | ) 14 | 15 | func main() { 16 | subcommand := os.Args[1] 17 | filename := os.Args[2] 18 | 19 | w := bufio.NewWriterSize(os.Stdout, 8192) 20 | defer w.Flush() 21 | 22 | switch subcommand { 23 | case "ast": 24 | { 25 | src, err := ioutil.ReadFile(filename) 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | fset := token.NewFileSet() 31 | file, err := parser.ParseFile(fset, filename, src, parser.AllErrors|parser.SkipObjectResolution) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | if err := Fprint(w, fset, file, nil); err != nil { 37 | panic(err) 38 | } 39 | } 40 | 41 | case "run": 42 | { 43 | cmd := exec.Command("go", "run", filename) 44 | cmd.Stdout = w 45 | cmd.Stderr = os.Stderr 46 | 47 | if err := cmd.Run(); err != nil { 48 | panic(err) 49 | } 50 | } 51 | 52 | case "tokens": 53 | { 54 | enc := json.NewEncoder(w) 55 | enc.SetEscapeHTML(false) 56 | 57 | src, err := ioutil.ReadFile(filename) 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | fset := token.NewFileSet() 63 | file := fset.AddFile(filename, fset.Base(), len(src)) 64 | 65 | var s scanner.Scanner 66 | s.Init(file, src, nil, scanner.ScanComments) 67 | 68 | for { 69 | pos, tok, lit := s.Scan() 70 | 71 | if err := enc.Encode([]interface{}{file.Position(pos), tok.String(), lit}); err != nil { 72 | panic(err) 73 | } 74 | 75 | if tok == token.EOF { 76 | break 77 | } 78 | } 79 | 80 | if s.ErrorCount > 0 { 81 | panic(fmt.Errorf("%d error(s) occured while scanning", s.ErrorCount)) 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /gors-cli/tests/files/comment.go: -------------------------------------------------------------------------------- 1 | // alone 2 | 3 | // main is the root package 4 | // lorem ipsum 5 | package main 6 | 7 | // un 8 | 9 | // Type is a type definition 10 | // lorem ipsum 11 | type Type bool 12 | 13 | // deux 14 | 15 | // Var is a global variable 16 | // lorem ipsum 17 | var Var = true 18 | 19 | // trois 20 | 21 | // Const is a global const 22 | // 23 | // Deprecated: lorem ipsum 24 | const Const = true 25 | 26 | // quatre 27 | 28 | // main is also the entry point function of the program 29 | // lorem ipsum 30 | func main() { 31 | // a 32 | 33 | _ = Var // we assign the global variable to nothing 34 | 35 | // b 36 | 37 | _ = (1 /* one */ + /* plus */ 2 /* two */) // eloquent addition 38 | 39 | // c 40 | 41 | /* 42 | GGGGGGGGGGGGG OOOOOOOOO RRRRRRRRRRRRRRRRR SSSSSSSSSSSSSSS 43 | GGG::::::::::::G OO:::::::::OO R::::::::::::::::R SS:::::::::::::::S 44 | GG:::::::::::::::G OO:::::::::::::OO R::::::RRRRRR:::::R S:::::SSSSSS::::::S 45 | G:::::GGGGGGGG::::GO:::::::OOO:::::::ORR:::::R R:::::RS:::::S SSSSSSS 46 | G:::::G GGGGGGO::::::O O::::::O R::::R R:::::RS:::::S 47 | G:::::G O:::::O O:::::O R::::R R:::::RS:::::S 48 | G:::::G O:::::O O:::::O R::::RRRRRR:::::R S::::SSSS 49 | G:::::G GGGGGGGGGGO:::::O O:::::O R:::::::::::::RR SS::::::SSSSS 50 | G:::::G G::::::::GO:::::O O:::::O R::::RRRRRR:::::R SSS::::::::SS 51 | G:::::G GGGGG::::GO:::::O O:::::O R::::R R:::::R SSSSSS::::S 52 | G:::::G G::::GO:::::O O:::::O R::::R R:::::R S:::::S 53 | G:::::G G::::GO::::::O O::::::O R::::R R:::::R S:::::S 54 | G:::::GGGGGGGG::::GO:::::::OOO:::::::ORR:::::R R:::::RSSSSSSS S:::::S 55 | GG:::::::::::::::G OO:::::::::::::OO R::::::R R:::::RS::::::SSSSSS:::::S 56 | GGG::::::GGG:::G OO:::::::::OO R::::::R R:::::RS:::::::::::::::SS 57 | GGGGGG GGGG OOOOOOOOO RRRRRRRR RRRRRRR SSSSSSSSSSSSSSS 58 | */ 59 | 60 | // d 61 | } 62 | 63 | // cinq 64 | // six 65 | -------------------------------------------------------------------------------- /gors/ast/printer.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::hash::{Hash, Hasher}; 3 | use std::io::Write; 4 | 5 | pub struct Printer { 6 | pub w: W, 7 | line: usize, 8 | depth: usize, 9 | lines: HashMap, 10 | } 11 | 12 | impl Printer { 13 | pub fn new(w: W) -> Self { 14 | Self { 15 | w, 16 | line: 0, 17 | depth: 0, 18 | lines: HashMap::default(), 19 | } 20 | } 21 | 22 | pub fn prefix(&mut self) -> std::io::Result<()> { 23 | write!(self.w, "{:6} ", self.line)?; 24 | for _ in 0..self.depth { 25 | self.write(". ")?; 26 | } 27 | Ok(()) 28 | } 29 | 30 | pub fn open_bracket(&mut self) -> std::io::Result<()> { 31 | self.depth += 1; 32 | self.write("{")?; 33 | self.newline() 34 | } 35 | 36 | pub fn close_bracket(&mut self) -> std::io::Result<()> { 37 | self.depth -= 1; 38 | self.prefix()?; 39 | self.write("}")?; 40 | self.newline() 41 | } 42 | 43 | pub fn newline(&mut self) -> std::io::Result<()> { 44 | self.line += 1; 45 | self.write("\n") 46 | } 47 | 48 | pub fn write(&mut self, buf: &str) -> std::io::Result<()> { 49 | self.w.write_all(buf.as_bytes()) 50 | } 51 | 52 | pub fn try_print_line(&mut self, val: T) -> Result> { 53 | // requirement: this hash function should not produce collisions 54 | let mut hasher = std::collections::hash_map::DefaultHasher::new(); 55 | val.hash(&mut hasher); 56 | let h = hasher.finish(); 57 | 58 | if let Some(line) = self.lines.get(&h) { 59 | write!(self.w, "*(obj @ {})", line)?; 60 | self.newline()?; 61 | Ok(true) 62 | } else { 63 | self.lines.insert(h, self.line); 64 | Ok(false) 65 | } 66 | } 67 | } 68 | 69 | pub type PrintResult = Result<(), Box>; 70 | 71 | pub trait Printable { 72 | fn print(&self, _: &mut Printer) -> PrintResult; 73 | } 74 | -------------------------------------------------------------------------------- /www/main.js: -------------------------------------------------------------------------------- 1 | import * as gors from 'gors'; 2 | import * as monaco from 'monaco-editor'; 3 | 4 | function onDOMContentLoaded() { 5 | const input = document.getElementById('input'); 6 | const output = document.getElementById('output'); 7 | const error = document.getElementById('error'); 8 | 9 | const opts = { 10 | cursorSurroundingLines: 5, 11 | folding: false, 12 | fontSize: '13px', 13 | glyphMargin: false, 14 | lineDecorationsWidth: 0, 15 | lineNumbers: 'off', 16 | lineNumbersMinChars: 2, 17 | minimap: { enabled: false }, 18 | occurrencesHighlight: false, 19 | overviewRulerLanes: 0, 20 | renderFinalNewline: false, 21 | renderIndentGuides: false, 22 | renderLineHighlight: 'none', 23 | scrollBeyondLastLine: false, 24 | selectionHighlight: false, 25 | }; 26 | 27 | // setup input editor 28 | 29 | const inputEditor = monaco.editor.create(input, { 30 | ...opts, 31 | language: 'go', 32 | }); 33 | const inputModel = inputEditor.getModel(); 34 | 35 | // setup output editor 36 | 37 | const outputEditor = monaco.editor.create(output, { 38 | ...opts, 39 | language: 'rust', 40 | contextmenu: false, 41 | matchBrackets: 'never', 42 | readOnly: true, 43 | }); 44 | const outputModel = outputEditor.getModel(); 45 | 46 | // register handlers 47 | 48 | inputModel.onDidChangeContent(() => { 49 | try { 50 | const code = inputModel.getValue(); 51 | const built = gors.build(code); 52 | outputModel.setValue(built); 53 | error.innerHTML = ''; 54 | } catch (err) { 55 | error.innerHTML = err; 56 | } 57 | }); 58 | 59 | outputEditor.onKeyDown((event) => { 60 | if (!(event.ctrlKey || event.metaKey)) { 61 | event.preventDefault(); 62 | event.stopPropagation(); 63 | } 64 | }); 65 | 66 | // initialization 67 | 68 | inputEditor.focus(); 69 | inputModel.setValue([ 70 | 'package main', 71 | '', 72 | 'import "fmt"', 73 | '', 74 | 'func main() {', 75 | '\tfmt.Println("Hello, 世界")', 76 | '', 77 | '\t// Start typing and see the changes!', 78 | '\t', 79 | '}', 80 | ].join('\n')); 81 | inputEditor.setPosition({ lineNumber: 9, column: 2 }); 82 | } 83 | 84 | if (document.readyState === 'loading') { 85 | document.addEventListener('DOMContentLoaded', onDOMContentLoaded); 86 | } else { 87 | onDOMContentLoaded(); 88 | } 89 | -------------------------------------------------------------------------------- /gors/compiler/passes/inline_fmt.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Span, TokenStream}; 2 | use quote::quote; 3 | use syn::punctuated::Punctuated; 4 | use syn::token::{Colon2, Comma}; 5 | use syn::visit_mut::{self, VisitMut}; 6 | use syn::{Expr, PathSegment, Token}; 7 | 8 | pub fn pass(file: &mut syn::File) { 9 | InlineFmt.visit_file_mut(file); 10 | } 11 | 12 | struct InlineFmt; 13 | 14 | impl VisitMut for InlineFmt { 15 | fn visit_expr_mut(&mut self, expr: &mut syn::Expr) { 16 | visit_mut::visit_expr_mut(self, expr); // depth-first 17 | 18 | if let syn::Expr::Call(call) = expr { 19 | if let syn::Expr::Path(path) = call.func.as_mut() { 20 | let sgmts = &path.path.segments; 21 | if sgmts.len() == 2 && sgmts[0].ident == "fmt" && sgmts[1].ident == "Println" { 22 | *expr = syn::Expr::Macro(syn::ExprMacro { 23 | attrs: vec![], 24 | mac: syn::Macro { 25 | path: syn::Path { 26 | leading_colon: Some(::default()), 27 | segments: segments(), 28 | }, 29 | bang_token: ::default(), 30 | tokens: tokens(&call.args), 31 | delimiter: syn::MacroDelimiter::Paren(syn::token::Paren { 32 | span: Span::mixed_site(), 33 | }), 34 | }, 35 | }); 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | fn segments() -> Punctuated { 43 | let mut segments = syn::punctuated::Punctuated::new(); 44 | segments.push(syn::PathSegment { 45 | ident: quote::format_ident!("std"), 46 | arguments: syn::PathArguments::None, 47 | }); 48 | segments.push(syn::PathSegment { 49 | ident: quote::format_ident!("println"), 50 | arguments: syn::PathArguments::None, 51 | }); 52 | segments 53 | } 54 | 55 | fn tokens(call_args: &Punctuated) -> TokenStream { 56 | if call_args.len() == 1 { 57 | if let syn::Expr::Lit(expr_lit) = &call_args[0] { 58 | if let syn::Lit::Str(lit_str) = &expr_lit.lit { 59 | return quote! { #lit_str }; 60 | } 61 | } 62 | } 63 | 64 | let mut fmt_str = String::new(); 65 | let mut fmt_args = quote! {}; 66 | for arg in call_args.iter() { 67 | fmt_str.push_str(if fmt_str.is_empty() { "{}" } else { " {}" }); 68 | fmt_args.extend(quote! { , #arg }) 69 | } 70 | quote! { #fmt_str #fmt_args } 71 | } 72 | -------------------------------------------------------------------------------- /gors/compiler/passes/mod.rs: -------------------------------------------------------------------------------- 1 | mod flatten_block; 2 | mod hoist_use; 3 | mod inline_fmt; 4 | mod map_type; 5 | mod simplify_return; 6 | mod type_conversion; 7 | 8 | pub fn pass(file: &mut syn::File) { 9 | inline_fmt::pass(file); 10 | map_type::pass(file); 11 | type_conversion::pass(file); 12 | hoist_use::pass(file); 13 | simplify_return::pass(file); 14 | flatten_block::pass(file); 15 | // TODO: remove useless mut 16 | } 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | //! This module contains the passes tests (the Rust -> Rust step, after the initial Go -> Rust 21 | //! step). 22 | 23 | use super::pass; 24 | use quote::quote; 25 | use syn::parse_quote as rust; 26 | 27 | fn test(mut input: syn::File, expected: syn::File) { 28 | pass(&mut input); // mutates in place, becomes the output 29 | let output = (quote! {#input}).to_string(); 30 | let expected = (quote! {#expected}).to_string(); 31 | if output != expected { 32 | panic!("\n output: {}\n expected: {}\n", output, expected); 33 | } 34 | } 35 | 36 | #[test] 37 | fn it_should_remove_unnecessary_returns() { 38 | test(rust! { fn a() { return 0; } }, rust! { fn a() { 0 } }); 39 | test(rust! { fn a() { return 0 } }, rust! { fn a() { 0 } }); 40 | test( 41 | rust! { fn a() { if true { return 0; } return 2; } }, 42 | rust! { fn a() { if true { return 0; } 2 } }, 43 | ); 44 | } 45 | 46 | #[test] 47 | fn it_should_hoist_use_declarations() { 48 | test( 49 | rust! { fn a() { ::std::println!("hello"); } }, 50 | rust! { use ::std::println; fn a() { println!("hello"); } }, 51 | ); 52 | test( 53 | rust! { pub fn main() { ::std::println!("test"); } }, 54 | rust! { use ::std::println; pub fn main() { println!("test"); } }, 55 | ); 56 | } 57 | 58 | #[test] 59 | fn it_should_only_hoist_duplicates_once() { 60 | test( 61 | rust! { fn a() { ::std::println!("hello"); ::std::println!("world"); } }, 62 | rust! { use ::std::println; fn a() { println!("hello"); println!("world"); } }, 63 | ); 64 | } 65 | 66 | #[test] 67 | fn it_should_flatten_blocks_composed_of_one_expr() { 68 | test(rust! { fn a() { { { a } } } }, rust! { fn a() { a } }); 69 | test( 70 | rust! { fn a() { { loop { { loop {} } } } } }, 71 | rust! { fn a() { loop { loop {} } } }, 72 | ); 73 | test(rust! { fn a() { { { a; } } } }, rust! { fn a() { { a; } } }); 74 | test( 75 | rust! { fn a() { { { return a; } } } }, 76 | rust! { fn a() { { return a; } } }, 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: [push, pull_request] 3 | jobs: 4 | 5 | lint: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | 10 | - name: Install Rust toolchain 11 | uses: actions-rs/toolchain@v1 12 | with: 13 | toolchain: stable 14 | 15 | - name: Run `cargo fmt` 16 | uses: actions-rs/cargo@v1 17 | with: 18 | command: fmt 19 | args: --all -- --check 20 | 21 | - name: Run `cargo clippy` 22 | uses: actions-rs/cargo@v1 23 | with: 24 | command: clippy 25 | args: --workspace --all-features -- -D warnings 26 | 27 | - name: Run `cargo doc` 28 | uses: actions-rs/cargo@v1 29 | with: 30 | command: doc 31 | args: --package=gors 32 | 33 | build: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v2 37 | 38 | - name: Install Rust toolchain 39 | uses: actions-rs/toolchain@v1 40 | with: 41 | toolchain: stable 42 | 43 | - name: Run `cargo build` 44 | uses: actions-rs/cargo@v1 45 | with: 46 | command: build 47 | args: --workspace --all-features --release 48 | 49 | test: 50 | runs-on: ubuntu-latest 51 | steps: 52 | - uses: actions/checkout@v2 53 | with: 54 | submodules: true 55 | 56 | - name: Install Rust toolchain 57 | uses: actions-rs/toolchain@v1 58 | with: 59 | toolchain: stable 60 | 61 | - name: Install Go toolchain 62 | uses: actions/setup-go@v2 63 | with: 64 | go-version: '^1.17' 65 | 66 | - name: Run unit tests 67 | uses: actions-rs/cargo@v1 68 | with: 69 | command: test 70 | args: --workspace --all-features --exclude=gors-cli 71 | 72 | - name: Run integration tests 73 | uses: actions-rs/cargo@v1 74 | with: 75 | command: test 76 | args: --workspace --all-features --package=gors-cli lexer -- --nocapture --test-threads=1 77 | 78 | www: 79 | runs-on: ubuntu-latest 80 | steps: 81 | - uses: actions/checkout@v2 82 | 83 | - name: Install Rust toolchain 84 | uses: actions-rs/toolchain@v1 85 | with: 86 | toolchain: stable 87 | 88 | - name: Install Node.js toolchain 89 | uses: actions/setup-node@v2 90 | with: 91 | node-version: '16' 92 | 93 | - name: Install wasm-pack 94 | run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh 95 | 96 | - name: Build gors-wasm 97 | working-directory: ./gors-wasm 98 | run: wasm-pack build --release 99 | 100 | - name: Install www dependencies 101 | working-directory: ./www 102 | run: npm ci 103 | 104 | - name: Lint www code 105 | working-directory: ./www 106 | run: npm run lint 107 | 108 | - name: Build www code 109 | working-directory: ./www 110 | run: npm run build 111 | 112 | - name: Upload the build artifacts 113 | uses: actions/upload-artifact@v2 114 | with: 115 | name: www-dist 116 | path: ./www/dist 117 | 118 | deploy-www: 119 | runs-on: ubuntu-latest 120 | if: github.ref == 'refs/heads/master' 121 | needs: [lint, build, test, www] 122 | steps: 123 | - uses: actions/checkout@v2 124 | 125 | - name: Download the build artifacts 126 | uses: actions/download-artifact@v2 127 | with: 128 | name: www-dist 129 | path: ./www/dist 130 | 131 | - name: Deploy www 132 | uses: JamesIves/github-pages-deploy-action@4.1.7 133 | with: 134 | branch: gh-pages 135 | folder: ./www/dist 136 | clean: true 137 | -------------------------------------------------------------------------------- /gors-cli/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::all, clippy::nursery)] 2 | 3 | use clap::Parser; 4 | use std::io::Write; 5 | use std::process::Command; 6 | 7 | fn main() -> Result<(), Box> { 8 | pretty_env_logger::init(); 9 | 10 | let opts: Opts = Opts::parse(); 11 | match opts.subcmd { 12 | SubCommand::Ast(cmd) => ast(cmd), 13 | SubCommand::Build(cmd) => build(cmd), 14 | SubCommand::Run(cmd) => run(cmd), 15 | SubCommand::Tokens(cmd) => tokens(cmd), 16 | } 17 | } 18 | 19 | #[derive(Parser)] 20 | #[clap( 21 | version = "1.0", 22 | name = "gors", 23 | author = "Aymeric Beaumet ", 24 | about = "gors is a go toolbelt written in rust; providing a parser and rust transpiler" 25 | )] 26 | struct Opts { 27 | #[clap(subcommand)] 28 | subcmd: SubCommand, 29 | } 30 | 31 | #[derive(Parser)] 32 | enum SubCommand { 33 | #[clap(about = "Parse the named Go file and print the AST")] 34 | Ast(Ast), 35 | #[clap(about = "Compile the named Go file")] 36 | Build(Build), 37 | #[clap(about = "Compile and run the named Go file")] 38 | Run(Run), 39 | #[clap(about = "Scan the named Go file and print the tokens")] 40 | Tokens(Tokens), 41 | } 42 | 43 | #[derive(Parser)] 44 | struct Ast { 45 | #[clap(name = "file", help = "The file to parse")] 46 | file: String, 47 | } 48 | 49 | #[derive(Parser)] 50 | struct Build { 51 | #[clap(name = "file", help = "The file to build")] 52 | file: String, 53 | #[clap( 54 | long, 55 | name = "release", 56 | help = "Build in release mode, with optimizations" 57 | )] 58 | release: bool, 59 | #[clap( 60 | long, 61 | name = "emit", 62 | help = "Type of output for the compiler to emit:\nrust|asm|llvm-bc|llvm-ir|obj|metadata|link|dep-info|mir" 63 | )] 64 | emit: Option, 65 | } 66 | 67 | #[derive(Parser)] 68 | struct Run { 69 | #[clap(name = "file", help = "The file to run")] 70 | file: String, 71 | #[clap( 72 | long, 73 | name = "release", 74 | help = "Build in release mode, with optimizations" 75 | )] 76 | release: bool, 77 | } 78 | 79 | #[derive(Parser)] 80 | struct Tokens { 81 | #[clap(name = "file", help = "The file to lex")] 82 | file: String, 83 | } 84 | 85 | fn ast(cmd: Ast) -> Result<(), Box> { 86 | let stdout = std::io::stdout(); 87 | let mut w = std::io::BufWriter::with_capacity(8192, stdout.lock()); 88 | 89 | let buffer = std::fs::read_to_string(&cmd.file)?; 90 | let ast = gors::parser::parse_file(&cmd.file, &buffer)?; 91 | gors::ast::fprint(&mut w, ast)?; 92 | w.flush()?; 93 | 94 | Ok(()) 95 | } 96 | 97 | fn build(cmd: Build) -> Result<(), Box> { 98 | let buffer = std::fs::read_to_string(&cmd.file)?; 99 | let ast = gors::parser::parse_file(&cmd.file, &buffer)?; 100 | let compiled = gors::compiler::compile(ast)?; 101 | 102 | // shortcut when rust code is to be emitted 103 | if matches!(cmd.emit.as_deref(), Some("rust")) { 104 | let mut w = std::fs::File::create("main.rs")?; 105 | gors::codegen::fprint(&mut w, compiled)?; 106 | return Ok(()); 107 | } 108 | 109 | let tmp_dir = tempdir::TempDir::new("gors")?; 110 | let source_file = tmp_dir.path().join("main.rs"); 111 | let mut w = std::fs::File::create(&source_file)?; 112 | gors::codegen::fprint(&mut w, compiled)?; 113 | w.sync_all()?; 114 | 115 | let rustc = Command::new("rustc") 116 | .args(RustcArgs { 117 | src: source_file.to_str().unwrap(), 118 | out: None, 119 | emit: cmd.emit.as_deref(), 120 | release: cmd.release, 121 | }) 122 | .output()?; 123 | if !rustc.status.success() { 124 | print!("{}", String::from_utf8_lossy(&rustc.stdout)); 125 | eprint!("{}", String::from_utf8_lossy(&rustc.stderr)); 126 | return Ok(()); 127 | } 128 | 129 | Ok(()) 130 | } 131 | 132 | fn run(cmd: Run) -> Result<(), Box> { 133 | let tmp_dir = tempdir::TempDir::new("gors")?; 134 | let out_rust = tmp_dir.path().join("main.rs"); 135 | let out_bin = tmp_dir.path().join("main"); 136 | let mut w = std::fs::File::create(&out_rust)?; 137 | 138 | let buffer = std::fs::read_to_string(&cmd.file)?; 139 | let ast = gors::parser::parse_file(&cmd.file, &buffer)?; 140 | let compiled = gors::compiler::compile(ast)?; 141 | gors::codegen::fprint(&mut w, compiled)?; 142 | w.sync_all()?; 143 | 144 | let rustc = Command::new("rustc") 145 | .args(RustcArgs { 146 | src: out_rust.to_str().unwrap(), 147 | out: Some(out_bin.to_str().unwrap()), 148 | emit: None, 149 | release: cmd.release, 150 | }) 151 | .output()?; 152 | if !rustc.status.success() { 153 | print!("{}", String::from_utf8_lossy(&rustc.stdout)); 154 | eprint!("{}", String::from_utf8_lossy(&rustc.stderr)); 155 | return Ok(()); 156 | } 157 | 158 | let mut cmd = Command::new(&out_bin).spawn()?; 159 | cmd.wait()?; 160 | 161 | Ok(()) 162 | } 163 | 164 | fn tokens(cmd: Tokens) -> Result<(), Box> { 165 | let stdout = std::io::stdout(); 166 | let mut w = std::io::BufWriter::with_capacity(8192, stdout.lock()); 167 | 168 | let buffer = std::fs::read_to_string(&cmd.file)?; 169 | for step in gors::scanner::Scanner::new(&cmd.file, &buffer) { 170 | serde_json::to_writer(&mut w, &step?)?; 171 | w.write_all(b"\n")?; 172 | } 173 | w.flush()?; 174 | 175 | Ok(()) 176 | } 177 | 178 | struct RustcArgs<'a> { 179 | src: &'a str, 180 | out: Option<&'a str>, 181 | emit: Option<&'a str>, 182 | release: bool, 183 | } 184 | 185 | impl<'a> From> for Vec<&'a str> { 186 | fn from(args: RustcArgs<'a>) -> Self { 187 | let mut flags = vec![args.src, "--edition=2021"]; 188 | 189 | if let Some(emit) = args.emit { 190 | flags.extend(["--emit", emit]); 191 | } 192 | 193 | if let Some(out) = args.out { 194 | flags.extend(["-o", out]); 195 | } 196 | 197 | if args.release { 198 | flags.extend([ 199 | "-Ccodegen-units=1", 200 | "-Clto=fat", 201 | "-Copt-level=3", 202 | "-Ctarget-cpu=native", 203 | ]); 204 | } 205 | 206 | flags 207 | } 208 | } 209 | 210 | impl<'a> IntoIterator for RustcArgs<'a> { 211 | type Item = &'a str; 212 | type IntoIter = std::vec::IntoIter; 213 | 214 | fn into_iter(self) -> Self::IntoIter { 215 | Vec::from(self).into_iter() 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /fuzz/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "arbitrary" 7 | version = "1.0.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "510c76ecefdceada737ea728f4f9a84bd2e1ef29f1ba555e560940fe279954de" 10 | 11 | [[package]] 12 | name = "cc" 13 | version = "1.0.72" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" 16 | 17 | [[package]] 18 | name = "cfg-if" 19 | version = "1.0.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 22 | 23 | [[package]] 24 | name = "fuzz" 25 | version = "0.1.0" 26 | dependencies = [ 27 | "gors", 28 | "libfuzzer-sys", 29 | ] 30 | 31 | [[package]] 32 | name = "getrandom" 33 | version = "0.2.3" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 36 | dependencies = [ 37 | "cfg-if", 38 | "libc", 39 | "wasi", 40 | ] 41 | 42 | [[package]] 43 | name = "gors" 44 | version = "0.1.0" 45 | dependencies = [ 46 | "log", 47 | "phf", 48 | "proc-macro2", 49 | "quote", 50 | "serde", 51 | "syn", 52 | "unicode-general-category", 53 | ] 54 | 55 | [[package]] 56 | name = "libc" 57 | version = "0.2.112" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" 60 | 61 | [[package]] 62 | name = "libfuzzer-sys" 63 | version = "0.4.2" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "36a9a84a6e8b55dfefb04235e55edb2b9a2a18488fcae777a6bdaa6f06f1deb3" 66 | dependencies = [ 67 | "arbitrary", 68 | "cc", 69 | "once_cell", 70 | ] 71 | 72 | [[package]] 73 | name = "log" 74 | version = "0.4.14" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 77 | dependencies = [ 78 | "cfg-if", 79 | ] 80 | 81 | [[package]] 82 | name = "once_cell" 83 | version = "1.9.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" 86 | 87 | [[package]] 88 | name = "phf" 89 | version = "0.10.1" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" 92 | dependencies = [ 93 | "phf_macros", 94 | "phf_shared", 95 | "proc-macro-hack", 96 | ] 97 | 98 | [[package]] 99 | name = "phf_generator" 100 | version = "0.10.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" 103 | dependencies = [ 104 | "phf_shared", 105 | "rand", 106 | ] 107 | 108 | [[package]] 109 | name = "phf_macros" 110 | version = "0.10.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" 113 | dependencies = [ 114 | "phf_generator", 115 | "phf_shared", 116 | "proc-macro-hack", 117 | "proc-macro2", 118 | "quote", 119 | "syn", 120 | ] 121 | 122 | [[package]] 123 | name = "phf_shared" 124 | version = "0.10.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 127 | dependencies = [ 128 | "siphasher", 129 | ] 130 | 131 | [[package]] 132 | name = "ppv-lite86" 133 | version = "0.2.15" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" 136 | 137 | [[package]] 138 | name = "proc-macro-hack" 139 | version = "0.5.19" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 142 | 143 | [[package]] 144 | name = "proc-macro2" 145 | version = "1.0.34" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1" 148 | dependencies = [ 149 | "unicode-xid", 150 | ] 151 | 152 | [[package]] 153 | name = "quote" 154 | version = "1.0.10" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 157 | dependencies = [ 158 | "proc-macro2", 159 | ] 160 | 161 | [[package]] 162 | name = "rand" 163 | version = "0.8.4" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 166 | dependencies = [ 167 | "libc", 168 | "rand_chacha", 169 | "rand_core", 170 | "rand_hc", 171 | ] 172 | 173 | [[package]] 174 | name = "rand_chacha" 175 | version = "0.3.1" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 178 | dependencies = [ 179 | "ppv-lite86", 180 | "rand_core", 181 | ] 182 | 183 | [[package]] 184 | name = "rand_core" 185 | version = "0.6.3" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 188 | dependencies = [ 189 | "getrandom", 190 | ] 191 | 192 | [[package]] 193 | name = "rand_hc" 194 | version = "0.3.1" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 197 | dependencies = [ 198 | "rand_core", 199 | ] 200 | 201 | [[package]] 202 | name = "serde" 203 | version = "1.0.132" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008" 206 | 207 | [[package]] 208 | name = "siphasher" 209 | version = "0.3.7" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" 212 | 213 | [[package]] 214 | name = "syn" 215 | version = "1.0.82" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" 218 | dependencies = [ 219 | "proc-macro2", 220 | "quote", 221 | "unicode-xid", 222 | ] 223 | 224 | [[package]] 225 | name = "unicode-general-category" 226 | version = "0.4.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "07547e3ee45e28326cc23faac56d44f58f16ab23e413db526debce3b0bfd2742" 229 | 230 | [[package]] 231 | name = "unicode-xid" 232 | version = "0.2.2" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 235 | 236 | [[package]] 237 | name = "wasi" 238 | version = "0.10.2+wasi-snapshot-preview1" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 241 | -------------------------------------------------------------------------------- /gors-cli/tests/print.go: -------------------------------------------------------------------------------- 1 | /* 2 | * From: https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/go/ast/print.go 3 | * See __FORK__ for the lines impacted by the fork (or diff from the file above). 4 | */ 5 | 6 | // Copyright 2010 The Go Authors. All rights reserved. 7 | // Use of this source code is governed by a BSD-style 8 | // license that can be found in the LICENSE file. 9 | 10 | // This file contains printing support for ASTs. 11 | 12 | package main 13 | 14 | import ( 15 | "fmt" 16 | "go/ast" 17 | "go/token" 18 | "io" 19 | "os" 20 | "reflect" 21 | "sort" 22 | ) 23 | 24 | // A FieldFilter may be provided to Fprint to control the output. 25 | type FieldFilter func(name string, value reflect.Value) bool 26 | 27 | // NotNilFilter returns true for field values that are not nil; 28 | // it returns false otherwise. 29 | func NotNilFilter(_ string, v reflect.Value) bool { 30 | switch v.Kind() { 31 | case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: 32 | return !v.IsNil() 33 | } 34 | return true 35 | } 36 | 37 | // Fprint prints the (sub-)tree starting at AST node x to w. 38 | // If fset != nil, position information is interpreted relative 39 | // to that file set. Otherwise positions are printed as integer 40 | // values (file set specific offsets). 41 | // 42 | // A non-nil FieldFilter f may be provided to control the output: 43 | // struct fields for which f(fieldname, fieldvalue) is true are 44 | // printed; all others are filtered from the output. Unexported 45 | // struct fields are never printed. 46 | func Fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) error { 47 | return fprint(w, fset, x, f) 48 | } 49 | 50 | func fprint(w io.Writer, fset *token.FileSet, x interface{}, f FieldFilter) (err error) { 51 | // setup printer 52 | p := printer{ 53 | output: w, 54 | fset: fset, 55 | filter: f, 56 | ptrmap: make(map[interface{}]int), 57 | last: '\n', // force printing of line number on first line 58 | } 59 | 60 | // install error handler 61 | defer func() { 62 | if e := recover(); e != nil { 63 | err = e.(localError).err // re-panics if it's not a localError 64 | } 65 | }() 66 | 67 | // print x 68 | if x == nil { 69 | p.printf("nil\n") 70 | return 71 | } 72 | p.print(reflect.ValueOf(x)) 73 | p.printf("\n") 74 | 75 | return 76 | } 77 | 78 | // Print prints x to standard output, skipping nil fields. 79 | // Print(fset, x) is the same as Fprint(os.Stdout, fset, x, NotNilFilter). 80 | func Print(fset *token.FileSet, x interface{}) error { 81 | return Fprint(os.Stdout, fset, x, NotNilFilter) 82 | } 83 | 84 | type printer struct { 85 | output io.Writer 86 | fset *token.FileSet 87 | filter FieldFilter 88 | ptrmap map[interface{}]int // *T -> line number 89 | indent int // current indentation level 90 | last byte // the last byte processed by Write 91 | line int // current line number 92 | } 93 | 94 | var indent = []byte(". ") 95 | 96 | func (p *printer) Write(data []byte) (n int, err error) { 97 | var m int 98 | for i, b := range data { 99 | // invariant: data[0:n] has been written 100 | if b == '\n' { 101 | m, err = p.output.Write(data[n : i+1]) 102 | n += m 103 | if err != nil { 104 | return 105 | } 106 | p.line++ 107 | } else if p.last == '\n' { 108 | _, err = fmt.Fprintf(p.output, "%6d ", p.line) 109 | if err != nil { 110 | return 111 | } 112 | for j := p.indent; j > 0; j-- { 113 | _, err = p.output.Write(indent) 114 | if err != nil { 115 | return 116 | } 117 | } 118 | } 119 | p.last = b 120 | } 121 | if len(data) > n { 122 | m, err = p.output.Write(data[n:]) 123 | n += m 124 | } 125 | return 126 | } 127 | 128 | // localError wraps locally caught errors so we can distinguish 129 | // them from genuine panics which we don't want to return as errors. 130 | type localError struct { 131 | err error 132 | } 133 | 134 | // printf is a convenience wrapper that takes care of print errors. 135 | func (p *printer) printf(format string, args ...interface{}) { 136 | if _, err := fmt.Fprintf(p, format, args...); err != nil { 137 | panic(localError{err}) 138 | } 139 | } 140 | 141 | // Implementation note: Print is written for AST nodes but could be 142 | // used to print arbitrary data structures; such a version should 143 | // probably be in a different package. 144 | // 145 | // Note: This code detects (some) cycles created via pointers but 146 | // not cycles that are created via slices or maps containing the 147 | // same slice or map. Code for general data structures probably 148 | // should catch those as well. 149 | 150 | func (p *printer) print(x reflect.Value) { 151 | if !NotNilFilter("", x) { 152 | p.printf("nil") 153 | return 154 | } 155 | 156 | switch x.Kind() { 157 | case reflect.Interface: 158 | p.print(x.Elem()) 159 | 160 | case reflect.Map: 161 | p.printf("%s (len = %d) {", x.Type(), x.Len()) 162 | if x.Len() > 0 { 163 | p.indent++ 164 | p.printf("\n") 165 | keys := x.MapKeys() // __FORK__ 166 | sort.Slice(keys, func(i, j int) bool { return keys[i].String() < keys[j].String() }) // __FORK__ 167 | for _, key := range keys { // __FORK__ 168 | p.print(key) 169 | p.printf(": ") 170 | p.print(x.MapIndex(key)) 171 | p.printf("\n") 172 | } 173 | p.indent-- 174 | } 175 | p.printf("}") 176 | 177 | case reflect.Ptr: 178 | p.printf("*") 179 | // type-checked ASTs may contain cycles - use ptrmap 180 | // to keep track of objects that have been printed 181 | // already and print the respective line number instead 182 | ptr := x.Interface() 183 | if line, exists := p.ptrmap[ptr]; exists { 184 | p.printf("(obj @ %d)", line) 185 | } else { 186 | p.ptrmap[ptr] = p.line 187 | p.print(x.Elem()) 188 | } 189 | 190 | case reflect.Array: 191 | p.printf("%s {", x.Type()) 192 | if x.Len() > 0 { 193 | p.indent++ 194 | p.printf("\n") 195 | for i, n := 0, x.Len(); i < n; i++ { 196 | p.printf("%d: ", i) 197 | p.print(x.Index(i)) 198 | p.printf("\n") 199 | } 200 | p.indent-- 201 | } 202 | p.printf("}") 203 | 204 | case reflect.Slice: 205 | if s, ok := x.Interface().([]byte); ok { 206 | p.printf("%#q", s) 207 | return 208 | } 209 | p.printf("%s (len = %d) {", x.Type(), x.Len()) 210 | if x.Len() > 0 { 211 | p.indent++ 212 | p.printf("\n") 213 | for i, n := 0, x.Len(); i < n; i++ { 214 | p.printf("%d: ", i) 215 | p.print(x.Index(i)) 216 | p.printf("\n") 217 | } 218 | p.indent-- 219 | } 220 | p.printf("}") 221 | 222 | case reflect.Struct: 223 | t := x.Type() 224 | p.printf("%s {", t) 225 | p.indent++ 226 | first := true 227 | for i, n := 0, t.NumField(); i < n; i++ { 228 | // exclude non-exported fields because their 229 | // values cannot be accessed via reflection 230 | if name := t.Field(i).Name; ast.IsExported(name) { // __FORK__: changing IsExported to ast.IsExported 231 | value := x.Field(i) 232 | if p.filter == nil || p.filter(name, value) { 233 | if first { 234 | p.printf("\n") 235 | first = false 236 | } 237 | p.printf("%s: ", name) 238 | p.print(value) 239 | p.printf("\n") 240 | } 241 | } 242 | } 243 | p.indent-- 244 | p.printf("}") 245 | 246 | default: 247 | v := x.Interface() 248 | switch v := v.(type) { 249 | case string: 250 | // print strings in quotes 251 | p.printf("%q", v) 252 | return 253 | case token.Pos: 254 | // position values can be printed nicely if we have a file set 255 | if p.fset != nil { 256 | p.printf("%s", p.fset.Position(v)) 257 | return 258 | } 259 | } 260 | // default 261 | p.printf("%v", v) 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /gors/token/mod.rs: -------------------------------------------------------------------------------- 1 | // https://cs.opensource.google/go/go/+/refs/tags/go1.17.2:src/go/token/token.go 2 | 3 | #![allow(non_camel_case_types)] // For consistency with the Go tokens 4 | 5 | use serde::{ser::SerializeMap, Serialize, Serializer}; 6 | use std::fmt; 7 | 8 | #[derive(Clone, Copy, Debug, Default)] 9 | pub struct Position<'a> { 10 | pub directory: &'a str, 11 | pub file: &'a str, 12 | pub offset: usize, 13 | pub line: usize, 14 | pub column: usize, 15 | } 16 | 17 | impl<'a> fmt::Display for Position<'a> { 18 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 19 | if self.file.is_empty() { 20 | write!(f, ":{}:{}", self.line, self.column) 21 | } else if self.file.starts_with('/') { 22 | write!(f, "{}:{}:{}", self.file, self.line, self.column) 23 | } else { 24 | write!( 25 | f, 26 | "{}/{}:{}:{}", 27 | self.directory, self.file, self.line, self.column 28 | ) 29 | } 30 | } 31 | } 32 | 33 | impl<'a> Serialize for Position<'a> { 34 | fn serialize(&self, serializer: S) -> Result 35 | where 36 | S: Serializer, 37 | { 38 | let mut map = serializer.serialize_map(Some(4))?; 39 | 40 | if self.file.is_empty() { 41 | map.serialize_entry("Filename", "")?; 42 | } else if self.file.starts_with('/') { 43 | map.serialize_entry("Filename", self.file)?; 44 | } else { 45 | // TODO: remove alloc 46 | map.serialize_entry("Filename", &[self.directory, "/", self.file].join(""))?; 47 | } 48 | map.serialize_entry("Offset", &self.offset)?; 49 | map.serialize_entry("Line", &self.line)?; 50 | map.serialize_entry("Column", &self.column)?; 51 | map.end() 52 | } 53 | } 54 | 55 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 56 | pub enum Token { 57 | EOF, 58 | COMMENT, 59 | 60 | IDENT, // main 61 | INT, // 12345 62 | FLOAT, // 123.45 63 | IMAG, // 123.45i 64 | CHAR, // 'a' 65 | STRING, // "abc" 66 | 67 | ADD, // + 68 | SUB, // - 69 | MUL, // * 70 | QUO, // / 71 | REM, // % 72 | 73 | AND, // & 74 | OR, // | 75 | XOR, // ^ 76 | SHL, // << 77 | SHR, // >> 78 | AND_NOT, // &^ 79 | 80 | ADD_ASSIGN, // += 81 | SUB_ASSIGN, // -= 82 | MUL_ASSIGN, // *= 83 | QUO_ASSIGN, // /= 84 | REM_ASSIGN, // %= 85 | 86 | AND_ASSIGN, // &= 87 | OR_ASSIGN, // |= 88 | XOR_ASSIGN, // ^= 89 | SHL_ASSIGN, // <<= 90 | SHR_ASSIGN, // >>= 91 | AND_NOT_ASSIGN, // &^= 92 | 93 | LAND, // && 94 | LOR, // || 95 | ARROW, // <- 96 | INC, // ++ 97 | DEC, // -- 98 | 99 | EQL, // == 100 | LSS, // < 101 | GTR, // > 102 | ASSIGN, // = 103 | NOT, // ! 104 | 105 | NEQ, // != 106 | LEQ, // <= 107 | GEQ, // >= 108 | DEFINE, // := 109 | ELLIPSIS, // ... 110 | 111 | LPAREN, // ( 112 | LBRACK, // [ 113 | LBRACE, // { 114 | COMMA, // , 115 | PERIOD, // . 116 | 117 | RPAREN, // ) 118 | RBRACK, // ] 119 | RBRACE, // } 120 | SEMICOLON, // ; 121 | COLON, // : 122 | 123 | BREAK, 124 | CASE, 125 | CHAN, 126 | CONST, 127 | CONTINUE, 128 | 129 | DEFAULT, 130 | DEFER, 131 | ELSE, 132 | FALLTHROUGH, 133 | FOR, 134 | 135 | FUNC, 136 | GO, 137 | GOTO, 138 | IF, 139 | IMPORT, 140 | 141 | INTERFACE, 142 | MAP, 143 | PACKAGE, 144 | RANGE, 145 | RETURN, 146 | 147 | SELECT, 148 | STRUCT, 149 | SWITCH, 150 | TYPE, 151 | VAR, 152 | } 153 | 154 | impl Token { 155 | pub const fn is_assign_op(&self) -> bool { 156 | use Token::*; 157 | matches!( 158 | self, 159 | ADD_ASSIGN 160 | | SUB_ASSIGN 161 | | MUL_ASSIGN 162 | | QUO_ASSIGN 163 | | REM_ASSIGN 164 | | AND_ASSIGN 165 | | OR_ASSIGN 166 | | XOR_ASSIGN 167 | | SHL_ASSIGN 168 | | SHR_ASSIGN 169 | | AND_NOT_ASSIGN 170 | ) 171 | } 172 | 173 | // https://go.dev/ref/spec#Operator_precedence 174 | pub fn precedence(&self) -> u8 { 175 | use Token::*; 176 | match self { 177 | MUL | QUO | REM | SHL | SHR | AND | AND_NOT => 5, 178 | ADD | SUB | OR | XOR => 4, 179 | EQL | NEQ | LSS | LEQ | GTR | GEQ => 3, 180 | LAND => 2, 181 | LOR => 1, 182 | _ => unreachable!( 183 | "precedence() is only supported for binary operators, called with: {:?}", 184 | self 185 | ), 186 | } 187 | } 188 | 189 | pub const fn lowest_precedence() -> u8 { 190 | 0 191 | } 192 | } 193 | 194 | impl From<&Token> for &'static str { 195 | fn from(token: &Token) -> Self { 196 | use Token::*; 197 | 198 | match token { 199 | EOF => "EOF", 200 | COMMENT => "COMMENT", 201 | 202 | IDENT => "IDENT", 203 | INT => "INT", 204 | FLOAT => "FLOAT", 205 | IMAG => "IMAG", 206 | CHAR => "CHAR", 207 | STRING => "STRING", 208 | 209 | ADD => "+", 210 | SUB => "-", 211 | MUL => "*", 212 | QUO => "/", 213 | REM => "%", 214 | 215 | AND => "&", 216 | OR => "|", 217 | XOR => "^", 218 | SHL => "<<", 219 | SHR => ">>", 220 | AND_NOT => "&^", 221 | 222 | ADD_ASSIGN => "+=", 223 | SUB_ASSIGN => "-=", 224 | MUL_ASSIGN => "*=", 225 | QUO_ASSIGN => "/=", 226 | REM_ASSIGN => "%=", 227 | 228 | AND_ASSIGN => "&=", 229 | OR_ASSIGN => "|=", 230 | XOR_ASSIGN => "^=", 231 | SHL_ASSIGN => "<<=", 232 | SHR_ASSIGN => ">>=", 233 | AND_NOT_ASSIGN => "&^=", 234 | 235 | LAND => "&&", 236 | LOR => "||", 237 | ARROW => "<-", 238 | INC => "++", 239 | DEC => "--", 240 | 241 | EQL => "==", 242 | LSS => "<", 243 | GTR => ">", 244 | ASSIGN => "=", 245 | NOT => "!", 246 | 247 | NEQ => "!=", 248 | LEQ => "<=", 249 | GEQ => ">=", 250 | DEFINE => ":=", 251 | ELLIPSIS => "...", 252 | 253 | LPAREN => "(", 254 | LBRACK => "[", 255 | LBRACE => "{", 256 | COMMA => ",", 257 | PERIOD => ".", 258 | 259 | RPAREN => ")", 260 | RBRACK => "]", 261 | RBRACE => "}", 262 | SEMICOLON => ";", 263 | COLON => ":", 264 | 265 | BREAK => "break", 266 | CASE => "case", 267 | CHAN => "chan", 268 | CONST => "const", 269 | CONTINUE => "continue", 270 | 271 | DEFAULT => "default", 272 | DEFER => "defer", 273 | ELSE => "else", 274 | FALLTHROUGH => "fallthrough", 275 | FOR => "for", 276 | 277 | FUNC => "func", 278 | GO => "go", 279 | GOTO => "goto", 280 | IF => "if", 281 | IMPORT => "import", 282 | 283 | INTERFACE => "interface", 284 | MAP => "map", 285 | PACKAGE => "package", 286 | RANGE => "range", 287 | RETURN => "return", 288 | 289 | SELECT => "select", 290 | STRUCT => "struct", 291 | SWITCH => "switch", 292 | TYPE => "type", 293 | VAR => "var", 294 | } 295 | } 296 | } 297 | 298 | impl Serialize for Token { 299 | fn serialize(&self, serializer: S) -> Result 300 | where 301 | S: Serializer, 302 | { 303 | serializer.serialize_str(self.into()) 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /gors-cli/tests/tests.rs: -------------------------------------------------------------------------------- 1 | use colored::*; 2 | use console::{style, Style}; 3 | use crossbeam::thread; 4 | use glob::glob; 5 | use phf::{phf_set, Set}; 6 | use similar::{ChangeTag, TextDiff}; 7 | use std::fmt; 8 | use std::process::{Command, Output}; 9 | use std::time::Duration; 10 | 11 | lazy_static::lazy_static! { 12 | static ref RUNNER: TestRunner<'static> = TestRunner::new(); 13 | } 14 | 15 | #[test] 16 | fn compiler() { 17 | RUNNER.test("run", &["programs"]); 18 | } 19 | 20 | #[test] 21 | fn lexer() { 22 | RUNNER.test("tokens", &["files", "programs"]); 23 | } 24 | 25 | #[test] 26 | fn parser() { 27 | RUNNER.test("ast", &["files", "programs"]); 28 | } 29 | 30 | #[derive(Debug)] 31 | struct TestRunner<'a> { 32 | gors_bin: &'a str, 33 | gors_go_bin: &'a str, 34 | pattern: &'a str, 35 | thread_count: usize, 36 | } 37 | 38 | impl<'a> TestRunner<'a> { 39 | fn new() -> Self { 40 | let go_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/tests"); 41 | let gors_go_bin = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/gors-go"); 42 | 43 | print!("\n| building gors-go binary..."); 44 | Command::new("go") 45 | .args(["build", "-o", gors_go_bin, "."]) 46 | .current_dir(&go_dir) 47 | .spawn() 48 | .unwrap() 49 | .wait() 50 | .unwrap(); 51 | 52 | let (gors_bin, pattern, thread_count) = match std::option_env!("CI") { 53 | Some("true") => { 54 | println!("\n| building release gors binary..."); 55 | Command::new("cargo") 56 | .args(["build", "--release"]) 57 | .current_dir(&go_dir) 58 | .spawn() 59 | .unwrap() 60 | .wait() 61 | .unwrap(); 62 | 63 | println!("| initializing submodules..."); 64 | Command::new("git") 65 | .args(["submodule", "update", "--init", "--depth=1"]) 66 | .current_dir(concat!(env!("CARGO_MANIFEST_DIR"), "/..")) 67 | .spawn() 68 | .unwrap() 69 | .wait() 70 | .unwrap(); 71 | 72 | ( 73 | concat!(env!("CARGO_MANIFEST_DIR"), "/../target/release/gors"), 74 | "**/*.go", 75 | num_cpus::get(), 76 | ) 77 | } 78 | _ => ( 79 | concat!(env!("CARGO_MANIFEST_DIR"), "/../target/debug/gors"), 80 | "*.go", 81 | 1, 82 | ), 83 | }; 84 | 85 | Self { 86 | gors_bin, 87 | gors_go_bin, 88 | pattern, 89 | thread_count, 90 | } 91 | } 92 | 93 | fn test(&self, command: &str, prefixes: &[&str]) { 94 | let go_files: Vec<_> = prefixes 95 | .iter() 96 | .flat_map(|prefix| glob(&format!("tests/{}/{}", prefix, self.pattern)).unwrap()) 97 | .filter_map(|entry| { 98 | let path = entry.unwrap(); 99 | let path = path.to_str().unwrap(); 100 | if IGNORE_FILES.contains(path) { 101 | None 102 | } else { 103 | Some(path.to_owned()) 104 | } 105 | }) 106 | .collect(); 107 | println!("\n| found {} go files", go_files.len()); 108 | 109 | let (go_elapsed, rust_elapsed) = thread::scope(|scope| { 110 | #[allow(clippy::needless_collect)] // We collect to start the threads in parallel 111 | let handles: Vec<_> = go_files 112 | .chunks((go_files.len() / self.thread_count) + 1) 113 | .enumerate() 114 | .map(|(i, chunk)| { 115 | println!("| starting thread #{} (chunk_len={})", i, chunk.len()); 116 | scope.spawn(|_| { 117 | chunk.iter().fold( 118 | (Duration::new(0, 0), Duration::new(0, 0)), 119 | |acc, go_file| { 120 | let args = &[command, go_file]; 121 | let (go_output, go_elapsed) = exec(self.gors_go_bin, args).unwrap(); 122 | let (rust_output, rust_elapsed) = 123 | exec(self.gors_bin, args).unwrap(); 124 | 125 | if go_output.stdout != rust_output.stdout { 126 | println!("| diff found: {}", go_file); 127 | print_diff( 128 | std::str::from_utf8(&go_output.stdout).unwrap(), 129 | std::str::from_utf8(&rust_output.stdout).unwrap(), 130 | ); 131 | std::process::exit(1); 132 | } 133 | 134 | (acc.0 + go_elapsed, acc.1 + rust_elapsed) 135 | }, 136 | ) 137 | }) 138 | }) 139 | .collect(); 140 | 141 | handles 142 | .into_iter() 143 | .fold((Duration::new(0, 0), Duration::new(0, 0)), |acc, handle| { 144 | let (g, r) = handle.join().unwrap(); 145 | (acc.0 + g, acc.1 + r) 146 | }) 147 | }) 148 | .unwrap(); 149 | 150 | println!("| total elapsed time:"); 151 | println!("| go: {:?}", go_elapsed); 152 | println!( 153 | "| rust: {:?} (go {:+.2}%)", 154 | rust_elapsed, 155 | ((rust_elapsed.as_secs_f64() / go_elapsed.as_secs_f64()) - 1.0) * 100.0 156 | ); 157 | } 158 | } 159 | 160 | fn exec(bin: &str, args: &[&str]) -> Result<(Output, Duration), Box> { 161 | let before = std::time::Instant::now(); 162 | let output = Command::new(bin).args(args).output()?; 163 | let after = std::time::Instant::now(); 164 | 165 | if !output.status.success() { 166 | eprintln!( 167 | "STATUS: {}", 168 | output 169 | .status 170 | .code() 171 | .unwrap_or_else(|| panic!("{:?} {:?}", bin, args)) 172 | ); 173 | eprintln!( 174 | "STDOUT: {}", 175 | std::str::from_utf8(&output.stdout).unwrap().blue(), 176 | ); 177 | eprintln!( 178 | "STDERR: {}", 179 | std::str::from_utf8(&output.stderr).unwrap().blue(), 180 | ); 181 | return Err(format!("{} {:?} failed", bin, args,).into()); 182 | } 183 | 184 | Ok((output, after.checked_duration_since(before).unwrap())) 185 | } 186 | 187 | // Some files cannot successfully be parsed by the Go compiler. So we exclude them from the 188 | // testing/benchmarking for now. 189 | static IGNORE_FILES: Set<&'static str> = phf_set! { 190 | "tests/files/go/src/cmd/api/testdata/src/pkg/p4/p4.go", 191 | "tests/files/go/src/constraints/constraints.go", 192 | "tests/files/go/src/go/doc/testdata/generics.go", 193 | "tests/files/go/src/go/parser/testdata/issue42951/not_a_file.go", 194 | "tests/files/go/test/bombad.go", 195 | "tests/files/go/test/char_lit1.go", 196 | "tests/files/go/test/fixedbugs/bug014.go", 197 | "tests/files/go/test/fixedbugs/bug068.go", 198 | "tests/files/go/test/fixedbugs/bug163.go", 199 | "tests/files/go/test/fixedbugs/bug169.go", 200 | "tests/files/go/test/fixedbugs/issue11359.go", 201 | "tests/files/go/test/fixedbugs/issue11610.go", 202 | "tests/files/go/test/fixedbugs/issue15611.go", 203 | "tests/files/go/test/fixedbugs/issue23587.go", 204 | "tests/files/go/test/fixedbugs/issue30722.go", 205 | "tests/files/go/test/fixedbugs/issue32133.go", 206 | "tests/files/go/test/fixedbugs/issue4405.go", 207 | "tests/files/go/test/fixedbugs/issue9036.go", 208 | "tests/files/go/test/typeparam/absdiff.go", 209 | "tests/files/go/test/typeparam/absdiffimp.dir/a.go", 210 | "tests/files/go/test/typeparam/append.go", 211 | "tests/files/go/test/typeparam/boundmethod.go", 212 | "tests/files/go/test/typeparam/builtins.go", 213 | "tests/files/go/test/typeparam/double.go", 214 | "tests/files/go/test/typeparam/fact.go", 215 | "tests/files/go/test/typeparam/issue39755.go", 216 | "tests/files/go/test/typeparam/issue48137.go", 217 | "tests/files/go/test/typeparam/issue48424.go", 218 | "tests/files/go/test/typeparam/issue48453.go", 219 | "tests/files/go/test/typeparam/issue48538.go", 220 | "tests/files/go/test/typeparam/issue48609.go", 221 | "tests/files/go/test/typeparam/issue48711.go", 222 | "tests/files/go/test/typeparam/issue49295.go", 223 | "tests/files/go/test/typeparam/list.go", 224 | "tests/files/go/test/typeparam/listimp.dir/a.go", 225 | "tests/files/go/test/typeparam/min.go", 226 | "tests/files/go/test/typeparam/minimp.dir/a.go", 227 | "tests/files/go/test/typeparam/nested.go", 228 | "tests/files/go/test/typeparam/ordered.go", 229 | "tests/files/go/test/typeparam/orderedmap.go", 230 | "tests/files/go/test/typeparam/orderedmapsimp.dir/a.go", 231 | "tests/files/go/test/typeparam/settable.go", 232 | "tests/files/go/test/typeparam/sliceimp.dir/a.go", 233 | "tests/files/go/test/typeparam/sliceimp.dir/main.go", 234 | "tests/files/go/test/typeparam/slices.go", 235 | "tests/files/go/test/typeparam/smallest.go", 236 | "tests/files/go/test/typeparam/typelist.go", 237 | }; 238 | 239 | // https://github.com/mitsuhiko/similar/blob/main/examples/terminal-inline.rs 240 | 241 | struct Line(Option); 242 | 243 | impl fmt::Display for Line { 244 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 245 | match self.0 { 246 | None => write!(f, " "), 247 | Some(idx) => write!(f, "{:<4}", idx + 1), 248 | } 249 | } 250 | } 251 | 252 | fn print_diff(expected: &str, got: &str) { 253 | let diff = TextDiff::from_lines(expected, got); 254 | 255 | for (idx, group) in diff.grouped_ops(3).iter().enumerate() { 256 | if idx > 0 { 257 | println!("{:-^1$}", "-", 80); 258 | } 259 | for op in group { 260 | for change in diff.iter_inline_changes(op) { 261 | let (sign, s) = match change.tag() { 262 | ChangeTag::Delete => ("-", Style::new().red()), 263 | ChangeTag::Insert => ("+", Style::new().green()), 264 | ChangeTag::Equal => (" ", Style::new().dim()), 265 | }; 266 | print!( 267 | "{}{} |{}", 268 | style(Line(change.old_index())).dim(), 269 | style(Line(change.new_index())).dim(), 270 | s.apply_to(sign).bold(), 271 | ); 272 | for (emphasized, value) in change.iter_strings_lossy() { 273 | if emphasized { 274 | print!("{}", s.apply_to(value).underlined().on_black()); 275 | } else { 276 | print!("{}", s.apply_to(value)); 277 | } 278 | } 279 | if change.missing_newline() { 280 | println!(); 281 | } 282 | } 283 | } 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /gors/ast/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::large_enum_variant)] // TODO: we allow large enum variant for now, let's profile properly to see if we want to box. 2 | 3 | mod hashable; 4 | mod printable; 5 | mod printer; 6 | 7 | use crate::token::{Position, Token}; 8 | use std::collections::BTreeMap; 9 | 10 | pub fn fprint>( 11 | w: W, 12 | node: T, 13 | ) -> Result<(), Box> { 14 | let mut p = printer::Printer::new(w); 15 | node.print(&mut p) 16 | } 17 | 18 | // https://pkg.go.dev/go/ast#CommentGroup 19 | #[derive(Debug)] 20 | pub struct CommentGroup { 21 | // List []*Comment // len(List) > 0 22 | } 23 | 24 | // https://pkg.go.dev/go/ast#FieldList 25 | #[derive(Debug)] 26 | pub struct FieldList<'a> { 27 | pub opening: Option>, // position of opening parenthesis/brace, if any 28 | pub list: Vec>, // field list; or nil 29 | pub closing: Option>, // position of closing parenthesis/brace, if any 30 | } 31 | 32 | // https://pkg.go.dev/go/ast#Field 33 | #[derive(Debug)] 34 | pub struct Field<'a> { 35 | pub doc: Option, // associated documentation; or nil 36 | pub names: Option>>, // field/method/(type) parameter names, or type "type"; or nil 37 | pub type_: Option>, // field/method/parameter type, type list type; or nil 38 | pub tag: Option>, // field tag; or nil 39 | pub comment: Option, // line comments; or nil 40 | } 41 | 42 | // https://pkg.go.dev/go/ast#File 43 | #[derive(Debug)] 44 | pub struct File<'a> { 45 | pub doc: Option, // associated documentation; or nil 46 | pub package: Position<'a>, // position of "package" keyword 47 | pub name: Ident<'a>, // package name 48 | pub decls: Vec>, // top-level declarations; or nil 49 | pub scope: Option>, // package scope (this file only) 50 | //pub imports: Vec<&'a ImportSpec<'a>>, // imports in this file 51 | pub unresolved: Vec>, // unresolved identifiers in this file 52 | pub comments: Vec, // list of all comments in the source file 53 | } 54 | 55 | impl<'a> File<'a> { 56 | pub fn imports(&self) -> Vec<&ImportSpec<'a>> { 57 | self.decls 58 | .iter() 59 | .filter_map(|decl| { 60 | if let Decl::GenDecl(decl) = decl { 61 | if decl.tok == Token::IMPORT { 62 | return Some(decl.specs.iter()); 63 | } 64 | } 65 | None 66 | }) 67 | .flatten() 68 | .filter_map(|spec| { 69 | if let Spec::ImportSpec(spec) = spec { 70 | return Some(spec); 71 | } 72 | None 73 | }) 74 | .collect() 75 | } 76 | } 77 | 78 | // https://pkg.go.dev/go/ast#FuncDecl 79 | #[derive(Debug)] 80 | pub struct FuncDecl<'a> { 81 | pub doc: Option, // associated documentation; or nil 82 | pub recv: Option>, // receiver (methods); or nil (functions) 83 | pub name: Ident<'a>, // function/method name 84 | pub type_: FuncType<'a>, // function signature: type and value parameters, results, and position of "func" keyword 85 | pub body: Option>, // function body; or nil for external (non-Go) function 86 | } 87 | 88 | // https://pkg.go.dev/go/ast#BlockStmt 89 | #[derive(Debug)] 90 | pub struct BlockStmt<'a> { 91 | pub lbrace: Position<'a>, // position of "{" 92 | pub list: Vec>, 93 | pub rbrace: Position<'a>, // position of "}", if any (may be absent due to syntax error) 94 | } 95 | 96 | // https://pkg.go.dev/go/ast#FuncType 97 | #[derive(Debug)] 98 | pub struct FuncType<'a> { 99 | pub func: Option>, // position of "func" keyword (token.NoPos if there is no "func") 100 | pub params: FieldList<'a>, // (incoming) parameters; non-nil 101 | pub results: Option>, // (outgoing) results; or nil 102 | } 103 | 104 | // https://pkg.go.dev/go/ast#Ident 105 | #[derive(Debug)] 106 | pub struct Ident<'a> { 107 | pub name_pos: Position<'a>, // identifier position 108 | pub name: &'a str, // identifier name 109 | pub obj: Option>>, // denoted object; or nil 110 | } 111 | 112 | // https://pkg.go.dev/go/ast#ImportSpec 113 | #[derive(Debug)] 114 | pub struct ImportSpec<'a> { 115 | pub doc: Option, // associated documentation; or nil 116 | pub name: Option>, // local package name (including "."); or nil 117 | pub path: BasicLit<'a>, // import path 118 | pub comment: Option, // line comments; or nil 119 | //pub end_pos: Position<'a>, // end of spec (overrides Path.Pos if nonzero) 120 | } 121 | 122 | // https://pkg.go.dev/go/ast#ValueSpec 123 | #[derive(Debug)] 124 | pub struct ValueSpec<'a> { 125 | pub doc: Option, // associated documentation; or nil 126 | pub names: Vec>, // value names (len(Names) > 0) 127 | pub type_: Option>, // value type; or nil 128 | pub values: Option>>, // initial values; or nil 129 | pub comment: Option, // line comments; or nil 130 | } 131 | 132 | // https://pkg.go.dev/go/ast#BasicLit 133 | #[derive(Debug)] 134 | pub struct BasicLit<'a> { 135 | pub value_pos: Position<'a>, // literal position 136 | pub kind: Token, // token.INT, token.FLOAT, token.IMAG, token.CHAR, or token.STRING 137 | pub value: &'a str, // literal string; e.g. 42, 0x7f, 3.14, 1e-9, 2.4i, 'a', '\x7f', "foo" or `\m\n\o` 138 | } 139 | 140 | // https://pkg.go.dev/go/ast#Object 141 | #[derive(Debug)] 142 | pub struct Object<'a> { 143 | pub kind: ObjKind, 144 | pub name: &'a str, // declared name 145 | pub decl: Option>, // corresponding Field, XxxSpec, FuncDecl, LabeledStmt, AssignStmt, Scope; or nil 146 | pub data: Option, // object-specific data; or nil 147 | pub type_: Option<()>, // placeholder for type information; may be nil 148 | } 149 | 150 | // https://pkg.go.dev/go/ast#Ellipsis 151 | #[derive(Debug)] 152 | pub struct Ellipsis<'a> { 153 | pub ellipsis: Position<'a>, // position of "..." 154 | pub elt: Option>>, // ellipsis element type (parameter lists only); or nil 155 | } 156 | 157 | // https://pkg.go.dev/go/ast#Ellipsis 158 | #[derive(Debug)] 159 | pub struct TypeAssertExpr<'a> { 160 | pub x: Box>, // expression 161 | pub lparen: Position<'a>, // position of "(" 162 | pub type_: Box>, // asserted type; nil means type switch X.(type) 163 | pub rparen: Position<'a>, // position of ")" 164 | } 165 | 166 | // https://pkg.go.dev/go/ast#SliceExpr 167 | #[derive(Debug)] 168 | pub struct SliceExpr<'a> { 169 | pub x: Box>, // expression 170 | pub lbrack: Position<'a>, // position of "[" 171 | pub low: Option>>, // begin of slice range; or nil 172 | pub high: Option>>, // end of slice range; or nil 173 | pub max: Option>>, // maximum capacity of slice; or nil 174 | pub slice3: bool, // true if 3-index slice (2 colons present) 175 | pub rbrack: Position<'a>, // position of "]" 176 | } 177 | 178 | // https://pkg.go.dev/go/ast#ObjKind 179 | #[derive(Debug)] 180 | pub enum ObjKind { 181 | //Pkg, // package 182 | Con, // constant 183 | //Typ, // type 184 | Var, // variable 185 | Fun, // function or method 186 | //Lbl, // label 187 | } 188 | 189 | #[derive(Debug)] 190 | pub enum ObjDecl<'a> { 191 | FuncDecl(FuncDecl<'a>), 192 | ValueSpec(ValueSpec<'a>), 193 | } 194 | 195 | // https://pkg.go.dev/go/ast#Decl 196 | #[derive(Debug)] 197 | pub enum Decl<'a> { 198 | FuncDecl(FuncDecl<'a>), 199 | GenDecl(GenDecl<'a>), 200 | } 201 | 202 | // https://pkg.go.dev/go/ast#Scope 203 | #[derive(Debug)] 204 | pub struct Scope<'a> { 205 | pub outer: Option>>, 206 | pub objects: BTreeMap<&'a str, Object<'a>>, 207 | } 208 | 209 | // https://pkg.go.dev/go/ast#GenDecl 210 | #[derive(Debug)] 211 | pub struct GenDecl<'a> { 212 | pub doc: Option, // associated documentation; or nil 213 | pub tok_pos: Position<'a>, // position of Tok 214 | pub tok: Token, // IMPORT, CONST, TYPE, or VAR 215 | pub lparen: Option>, // position of '(', if any 216 | pub specs: Vec>, 217 | pub rparen: Option>, // position of ')', if any 218 | } 219 | 220 | // https://pkg.go.dev/go/ast#AssignStmt 221 | #[derive(Debug)] 222 | pub struct AssignStmt<'a> { 223 | pub lhs: Vec>, 224 | pub tok_pos: Position<'a>, // position of Tok 225 | pub tok: Token, // assignment token, DEFINE 226 | pub rhs: Vec>, 227 | } 228 | 229 | // https://pkg.go.dev/go/ast#BinaryExpr 230 | #[derive(Debug)] 231 | pub struct BinaryExpr<'a> { 232 | pub x: Box>, // left operand 233 | pub op_pos: Position<'a>, // position of Op 234 | pub op: Token, // operator 235 | pub y: Box>, // right operand 236 | } 237 | 238 | // https://pkg.go.dev/go/ast#ReturnStmt 239 | #[derive(Debug)] 240 | pub struct ReturnStmt<'a> { 241 | pub return_: Position<'a>, // position of "return" keyword 242 | pub results: Vec>, // result expressions; or nil 243 | } 244 | 245 | // https://pkg.go.dev/go/ast#TypeSpec 246 | #[derive(Debug)] 247 | pub struct TypeSpec<'a> { 248 | pub doc: Option, // associated documentation; or nil 249 | pub name: Option>, // type name 250 | pub assign: Option>, // position of '=', if any 251 | pub type_: Expr<'a>, // *Ident, *ParenExpr, *SelectorExpr, *StarExpr, or any of the *XxxTypes 252 | pub comment: Option, // line comments; or nil 253 | } 254 | 255 | // https://pkg.go.dev/go/ast#StructType 256 | #[derive(Debug)] 257 | pub struct StructType<'a> { 258 | pub struct_: Position<'a>, // position of "struct" keyword 259 | pub fields: Option>, // list of field declarations 260 | pub incomplete: bool, // true if (source) fields are missing in the Fields list 261 | } 262 | 263 | // https://pkg.go.dev/go/ast#StarExpr 264 | #[derive(Debug)] 265 | pub struct StarExpr<'a> { 266 | pub star: Position<'a>, // position of "*" 267 | pub x: Box>, // operand 268 | } 269 | 270 | // https://pkg.go.dev/go/ast#InterfaceType 271 | #[derive(Debug)] 272 | pub struct InterfaceType<'a> { 273 | pub interface: Position<'a>, // position of "interface" keyword 274 | pub methods: Option>, // list of embedded interfaces, methods, or types 275 | pub incomplete: bool, // true if (source) methods or types are missing in the Methods list 276 | } 277 | 278 | // https://pkg.go.dev/go/ast#DeclStmt 279 | #[derive(Debug)] 280 | pub struct DeclStmt<'a> { 281 | pub decl: GenDecl<'a>, // *GenDecl with CONST, TYPE, or VAR token 282 | } 283 | 284 | // https://pkg.go.dev/go/ast#UnaryExpr 285 | #[derive(Debug)] 286 | pub struct UnaryExpr<'a> { 287 | pub op_pos: Position<'a>, // position of Op 288 | pub op: Token, // operator 289 | pub x: Box>, // operand 290 | } 291 | 292 | // https://pkg.go.dev/go/ast#CallExpr 293 | #[derive(Debug)] 294 | pub struct CallExpr<'a> { 295 | pub fun: Box>, // function expression 296 | pub lparen: Position<'a>, // position of "(" 297 | pub args: Option>>, // function arguments; or nil 298 | pub ellipsis: Option>, // position of "..." (token.NoPos if there is no "...") 299 | pub rparen: Position<'a>, // position of ")" 300 | } 301 | 302 | // https://pkg.go.dev/go/ast#SelectorExpr 303 | #[derive(Debug)] 304 | pub struct SelectorExpr<'a> { 305 | pub x: Box>, // expression 306 | pub sel: Ident<'a>, // field selector 307 | } 308 | 309 | // https://pkg.go.dev/go/ast#ExprStmt 310 | #[derive(Debug)] 311 | pub struct ExprStmt<'a> { 312 | pub x: Expr<'a>, // expression 313 | } 314 | 315 | // https://pkg.go.dev/go/ast#SelectorExpr 316 | #[derive(Debug)] 317 | pub struct IfStmt<'a> { 318 | pub if_: Position<'a>, // position of "if" keyword 319 | pub init: Box>>, // initialization statement; or nil 320 | pub cond: Expr<'a>, // condition 321 | pub body: BlockStmt<'a>, 322 | pub else_: Box>>, // else branch; or nil 323 | } 324 | 325 | // https://pkg.go.dev/go/ast#IncDecStmt 326 | #[derive(Debug)] 327 | pub struct IncDecStmt<'a> { 328 | pub x: Expr<'a>, 329 | pub tok_pos: Position<'a>, // position of Tok 330 | pub tok: Token, // INC or DEC 331 | } 332 | 333 | // https://pkg.go.dev/go/ast#ParenExpr 334 | #[derive(Debug)] 335 | pub struct ParenExpr<'a> { 336 | pub lparen: Position<'a>, // position of "(" 337 | pub x: Box>, // parenthesized expression 338 | pub rparen: Position<'a>, // position of ")" 339 | } 340 | 341 | // https://pkg.go.dev/go/ast#GoStmt 342 | #[derive(Debug)] 343 | pub struct GoStmt<'a> { 344 | pub go: Position<'a>, // position of "go" keyword 345 | pub call: CallExpr<'a>, 346 | } 347 | 348 | // https://pkg.go.dev/go/ast#FuncLit 349 | #[derive(Debug)] 350 | pub struct FuncLit<'a> { 351 | pub type_: FuncType<'a>, // function type 352 | pub body: BlockStmt<'a>, // function body 353 | } 354 | 355 | // https://pkg.go.dev/go/ast#ChanType 356 | #[derive(Debug)] 357 | pub struct ChanType<'a> { 358 | pub begin: Position<'a>, // position of "chan" keyword or "<-" (whichever comes first) 359 | pub arrow: Option>, // position of "<-" (token.NoPos if there is no "<-") 360 | pub dir: u8, // channel direction 361 | pub value: Box>, // value type 362 | } 363 | 364 | // https://pkg.go.dev/go/ast#SendStmt 365 | #[derive(Debug)] 366 | pub struct SendStmt<'a> { 367 | pub chan: Expr<'a>, 368 | pub arrow: Position<'a>, // position of "<-" 369 | pub value: Expr<'a>, 370 | } 371 | 372 | // https://pkg.go.dev/go/ast#ForStmt 373 | #[derive(Debug)] 374 | pub struct ForStmt<'a> { 375 | pub for_: Position<'a>, // position of "for" keyword 376 | pub init: Option>>, // initialization statement; or nil 377 | pub cond: Option>, // condition; or nil 378 | pub post: Option>>, // post iteration statement; or nil 379 | pub body: BlockStmt<'a>, 380 | } 381 | 382 | // https://pkg.go.dev/go/ast#RangeStmt 383 | #[derive(Debug)] 384 | pub struct RangeStmt<'a> { 385 | pub for_: Position<'a>, // position of "for" keyword 386 | pub key: Option>, // Key, Value may be nil 387 | pub value: Option>, // Key, Value may be nil 388 | pub tok_pos: Option>, // position of Tok; invalid if Key == nil 389 | pub tok: Option, // ILLEGAL if Key == nil, ASSIGN, DEFINE 390 | pub x: Expr<'a>, // value to range over 391 | pub body: BlockStmt<'a>, 392 | } 393 | 394 | // https://pkg.go.dev/go/ast#EmptyStmt 395 | #[derive(Debug)] 396 | pub struct EmptyStmt<'a> { 397 | pub semicolon: Position<'a>, // position of following ";" 398 | pub implicit: bool, // if set, ";" was omitted in the source 399 | } 400 | 401 | // https://pkg.go.dev/go/ast#IndexExpr 402 | #[derive(Debug)] 403 | pub struct IndexExpr<'a> { 404 | pub x: Box>, // expression 405 | pub lbrack: Position<'a>, // position of "[" 406 | pub index: Box>, // index expression 407 | pub rbrack: Position<'a>, // position of "]" 408 | } 409 | 410 | // https://pkg.go.dev/go/ast#MapType 411 | #[derive(Debug)] 412 | pub struct MapType<'a> { 413 | pub map: Position<'a>, 414 | pub key: Box>, 415 | pub value: Box>, 416 | } 417 | 418 | // https://pkg.go.dev/go/ast#CompositeLit 419 | #[derive(Debug)] 420 | pub struct CompositeLit<'a> { 421 | pub type_: Box>, // literal type; or nil 422 | pub lbrace: Position<'a>, // position of "{" 423 | pub elts: Option>>, // list of composite elements; or nil 424 | pub rbrace: Position<'a>, // position of "}" 425 | pub incomplete: bool, // true if (source) expressions are missing in the Elts list 426 | } 427 | 428 | // https://pkg.go.dev/go/ast#KeyValueExpr 429 | #[derive(Debug)] 430 | pub struct KeyValueExpr<'a> { 431 | pub key: Box>, 432 | pub colon: Position<'a>, // position of ":" 433 | pub value: Box>, 434 | } 435 | 436 | // https://pkg.go.dev/go/ast#ArrayType 437 | #[derive(Debug)] 438 | pub struct ArrayType<'a> { 439 | pub lbrack: Position<'a>, // position of "[" 440 | pub len: Option>>, // Ellipsis node for [...]T array types, nil for slice types 441 | pub elt: Box>, // element type 442 | } 443 | 444 | // https://pkg.go.dev/go/ast#DeferStmt 445 | #[derive(Debug)] 446 | pub struct DeferStmt<'a> { 447 | pub defer: Position<'a>, // position of "defer" keyword 448 | pub call: CallExpr<'a>, 449 | } 450 | 451 | // https://pkg.go.dev/go/ast#ChanDir 452 | #[derive(Debug)] 453 | pub enum ChanDir { 454 | SEND = 1 << 0, 455 | RECV = 1 << 1, 456 | } 457 | 458 | // https://pkg.go.dev/go/ast#Spec 459 | #[derive(Debug)] 460 | pub enum Spec<'a> { 461 | ImportSpec(ImportSpec<'a>), 462 | TypeSpec(TypeSpec<'a>), 463 | ValueSpec(ValueSpec<'a>), 464 | } 465 | 466 | // https://pkg.go.dev/go/ast#Expr 467 | #[derive(Debug)] 468 | pub enum Expr<'a> { 469 | ArrayType(ArrayType<'a>), 470 | BasicLit(BasicLit<'a>), 471 | BinaryExpr(BinaryExpr<'a>), 472 | CallExpr(CallExpr<'a>), 473 | ChanType(ChanType<'a>), 474 | CompositeLit(CompositeLit<'a>), 475 | Ellipsis(Ellipsis<'a>), 476 | FuncLit(FuncLit<'a>), 477 | FuncType(FuncType<'a>), 478 | Ident(Ident<'a>), 479 | IndexExpr(IndexExpr<'a>), 480 | InterfaceType(InterfaceType<'a>), 481 | KeyValueExpr(KeyValueExpr<'a>), 482 | MapType(MapType<'a>), 483 | ParenExpr(ParenExpr<'a>), 484 | SelectorExpr(SelectorExpr<'a>), 485 | SliceExpr(SliceExpr<'a>), 486 | StarExpr(StarExpr<'a>), 487 | StructType(StructType<'a>), 488 | TypeAssertExpr(TypeAssertExpr<'a>), 489 | UnaryExpr(UnaryExpr<'a>), 490 | } 491 | 492 | // https://pkg.go.dev/go/ast#Stmt 493 | #[derive(Debug)] 494 | pub enum Stmt<'a> { 495 | AssignStmt(AssignStmt<'a>), 496 | BlockStmt(BlockStmt<'a>), 497 | DeclStmt(DeclStmt<'a>), 498 | DeferStmt(DeferStmt<'a>), 499 | EmptyStmt(EmptyStmt<'a>), 500 | ExprStmt(ExprStmt<'a>), 501 | ForStmt(ForStmt<'a>), 502 | GoStmt(GoStmt<'a>), 503 | IfStmt(IfStmt<'a>), 504 | IncDecStmt(IncDecStmt<'a>), 505 | RangeStmt(RangeStmt<'a>), 506 | ReturnStmt(ReturnStmt<'a>), 507 | SendStmt(SendStmt<'a>), 508 | } 509 | -------------------------------------------------------------------------------- /gors/compiler/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::fallible_impl_from)] // TODO: switch to TryFrom 2 | 3 | mod passes; 4 | 5 | use crate::{ast, token}; 6 | use proc_macro2::Span; 7 | use syn::Token; 8 | 9 | pub fn compile(file: ast::File) -> Result> { 10 | let mut out = file.into(); 11 | passes::pass(&mut out); 12 | Ok(out) 13 | } 14 | 15 | impl From> for syn::ExprLit { 16 | fn from(basic_lit: ast::BasicLit) -> Self { 17 | Self { 18 | attrs: vec![], 19 | lit: basic_lit.into(), 20 | } 21 | } 22 | } 23 | 24 | impl From> for syn::Lit { 25 | fn from(basic_lit: ast::BasicLit) -> Self { 26 | use token::Token::*; 27 | match basic_lit.kind { 28 | INT => Self::Int(syn::LitInt::new(basic_lit.value, Span::mixed_site())), 29 | STRING => { 30 | let mut value = basic_lit.value.chars(); 31 | value.next(); 32 | value.next_back(); 33 | Self::Str(syn::LitStr::new(value.as_str(), Span::mixed_site())) 34 | } 35 | _ => unimplemented!("{:?}", basic_lit), 36 | } 37 | } 38 | } 39 | 40 | impl From> for syn::ExprBinary { 41 | fn from(binary_expr: ast::BinaryExpr) -> Self { 42 | let (x, op, y) = ( 43 | syn::Expr::from(*binary_expr.x), 44 | syn::BinOp::from(binary_expr.op), 45 | syn::Expr::from(*binary_expr.y), 46 | ); 47 | syn::parse_quote! { #x #op #y } 48 | } 49 | } 50 | 51 | impl From> for syn::Block { 52 | fn from(block_stmt: ast::BlockStmt) -> Self { 53 | Self { 54 | brace_token: syn::token::Brace { 55 | span: Span::mixed_site(), 56 | }, 57 | stmts: block_stmt.list.into_iter().flat_map(Vec::from).collect(), 58 | } 59 | } 60 | } 61 | 62 | impl From> for syn::ExprBlock { 63 | fn from(block_stmt: ast::BlockStmt) -> Self { 64 | Self { 65 | attrs: vec![], 66 | label: None, 67 | block: block_stmt.into(), 68 | } 69 | } 70 | } 71 | 72 | impl From> for syn::ExprCall { 73 | fn from(call_expr: ast::CallExpr) -> Self { 74 | let func = if let ast::Expr::Ident(ident) = *call_expr.fun { 75 | let mut segments = syn::punctuated::Punctuated::new(); 76 | segments.push(syn::PathSegment { 77 | ident: ident.into(), 78 | arguments: syn::PathArguments::None, 79 | }); 80 | 81 | syn::Expr::Path(syn::ExprPath { 82 | attrs: vec![], 83 | qself: None, 84 | path: syn::Path { 85 | segments, 86 | leading_colon: None, 87 | }, 88 | }) 89 | } else { 90 | (*call_expr.fun).into() 91 | }; 92 | 93 | let mut args = syn::punctuated::Punctuated::new(); 94 | if let Some(cargs) = call_expr.args { 95 | for arg in cargs { 96 | args.push(arg.into()) 97 | } 98 | } 99 | 100 | Self { 101 | attrs: vec![], 102 | func: Box::new(func), 103 | paren_token: syn::token::Paren { 104 | span: Span::mixed_site(), 105 | }, 106 | args, 107 | } 108 | } 109 | } 110 | 111 | impl From> for syn::Expr { 112 | fn from(expr: ast::Expr) -> Self { 113 | match expr { 114 | ast::Expr::BasicLit(basic_lit) => Self::Lit(basic_lit.into()), 115 | ast::Expr::BinaryExpr(binary_expr) => Self::Binary(binary_expr.into()), 116 | ast::Expr::CallExpr(call_expr) => Self::Call(call_expr.into()), 117 | ast::Expr::Ident(ident) => Self::Path(ident.into()), 118 | ast::Expr::SelectorExpr(selector_expr) => Self::Path(selector_expr.into()), 119 | _ => unimplemented!("{:?}", expr), 120 | } 121 | } 122 | } 123 | 124 | impl From> for syn::Type { 125 | fn from(expr: ast::Expr) -> Self { 126 | match expr { 127 | ast::Expr::Ident(ident) => { 128 | let mut segments = syn::punctuated::Punctuated::new(); 129 | segments.push(syn::PathSegment { 130 | ident: ident.into(), 131 | arguments: syn::PathArguments::None, 132 | }); 133 | Self::Path(syn::TypePath { 134 | qself: None, 135 | path: syn::Path { 136 | leading_colon: None, 137 | segments, 138 | }, 139 | }) 140 | } 141 | _ => unimplemented!("{:?}", expr), 142 | } 143 | } 144 | } 145 | 146 | impl From> for syn::File { 147 | fn from(file: ast::File) -> Self { 148 | let items = file 149 | .decls 150 | .into_iter() 151 | .filter_map(|decl| match decl { 152 | ast::Decl::FuncDecl(func_decl) => Some(syn::Item::Fn(func_decl.into())), 153 | _ => None, 154 | }) 155 | .collect(); 156 | 157 | Self { 158 | attrs: vec![], 159 | items, 160 | shebang: None, 161 | } 162 | } 163 | } 164 | 165 | impl From> for syn::FnArg { 166 | fn from(field: ast::Field) -> Self { 167 | let name = field.names.unwrap().into_iter().next().unwrap(); 168 | Self::Typed(syn::PatType { 169 | attrs: vec![], 170 | pat: Box::new(syn::Pat::Ident(syn::PatIdent { 171 | attrs: vec![], 172 | by_ref: None, 173 | subpat: None, 174 | mutability: None, 175 | ident: name.into(), 176 | })), 177 | colon_token: ::default(), 178 | ty: Box::new(field.type_.unwrap().into()), 179 | }) 180 | } 181 | } 182 | 183 | impl From> for syn::ItemFn { 184 | fn from(func_decl: ast::FuncDecl) -> Self { 185 | let mut inputs = syn::punctuated::Punctuated::new(); 186 | for param in func_decl.type_.params.list { 187 | inputs.push(param.into()); 188 | } 189 | 190 | let vis = (&func_decl.name).into(); 191 | 192 | let block = 193 | Box::new( 194 | func_decl 195 | .body 196 | .map(|body| body.into()) 197 | .unwrap_or_else(|| syn::Block { 198 | brace_token: syn::token::Brace { 199 | span: Span::mixed_site(), 200 | }, 201 | stmts: vec![], 202 | }), 203 | ); 204 | 205 | let output = if let Some(results) = func_decl.type_.results { 206 | syn::ReturnType::Type( 207 | ]>::default(), 208 | Box::new( 209 | results 210 | .list 211 | .into_iter() 212 | .next() 213 | .unwrap() 214 | .type_ 215 | .unwrap() 216 | .into(), 217 | ), 218 | ) 219 | } else { 220 | syn::ReturnType::Default 221 | }; 222 | 223 | let sig = syn::Signature { 224 | constness: None, 225 | asyncness: None, 226 | unsafety: None, 227 | abi: None, 228 | fn_token: ::default(), 229 | ident: func_decl.name.into(), 230 | generics: syn::Generics { 231 | params: syn::punctuated::Punctuated::new(), 232 | lt_token: None, 233 | gt_token: None, 234 | where_clause: None, 235 | }, 236 | paren_token: syn::token::Paren { 237 | span: Span::mixed_site(), 238 | }, 239 | inputs, 240 | variadic: None, 241 | output, 242 | }; 243 | 244 | Self { 245 | attrs: vec![], 246 | block, 247 | sig, 248 | vis, 249 | } 250 | } 251 | } 252 | 253 | impl From> for syn::ExprPath { 254 | fn from(ident: ast::Ident) -> Self { 255 | let mut segments = syn::punctuated::Punctuated::new(); 256 | segments.push(syn::PathSegment { 257 | ident: ident.into(), 258 | arguments: syn::PathArguments::None, 259 | }); 260 | 261 | Self { 262 | attrs: vec![], 263 | path: syn::Path { 264 | leading_colon: None, 265 | segments, 266 | }, 267 | qself: None, 268 | } 269 | } 270 | } 271 | 272 | impl From<&ast::Ident<'_>> for syn::Visibility { 273 | fn from(name: &ast::Ident) -> Self { 274 | if name.name == "main" || matches!(name.name.chars().next(), Some('A'..='Z')) { 275 | Self::Public(syn::VisPublic { 276 | pub_token: ::default(), 277 | }) 278 | } else { 279 | Self::Inherited 280 | } 281 | } 282 | } 283 | 284 | impl From> for syn::ExprPath { 285 | fn from(selector_expr: ast::SelectorExpr) -> Self { 286 | let x = match *selector_expr.x { 287 | ast::Expr::Ident(ident) => ident, 288 | _ => unimplemented!(), 289 | }; 290 | 291 | let mut segments = syn::punctuated::Punctuated::new(); 292 | segments.push(syn::PathSegment { 293 | ident: x.into(), 294 | arguments: syn::PathArguments::None, 295 | }); 296 | segments.push(syn::PathSegment { 297 | ident: selector_expr.sel.into(), 298 | arguments: syn::PathArguments::None, 299 | }); 300 | 301 | Self { 302 | attrs: vec![], 303 | path: syn::Path { 304 | leading_colon: None, 305 | segments, 306 | }, 307 | qself: None, 308 | } 309 | } 310 | } 311 | 312 | impl From> for syn::Ident { 313 | fn from(ident: ast::Ident) -> Self { 314 | Self::new(ident.name, Span::mixed_site()) 315 | } 316 | } 317 | 318 | impl From> for syn::ExprIf { 319 | fn from(if_stmt: ast::IfStmt) -> Self { 320 | Self { 321 | attrs: vec![], 322 | cond: Box::new(if_stmt.cond.into()), 323 | if_token: ::default(), 324 | then_branch: if_stmt.body.into(), 325 | else_branch: if_stmt.else_.map(|else_| { 326 | ( 327 | ::default(), 328 | Box::new(match else_ { 329 | ast::Stmt::IfStmt(if_stmt) => syn::Expr::If(if_stmt.into()), 330 | ast::Stmt::BlockStmt(block_stmt) => syn::Expr::Block(block_stmt.into()), 331 | _ => unimplemented!(), 332 | }), 333 | ) 334 | }), 335 | } 336 | } 337 | } 338 | 339 | impl From> for Vec { 340 | fn from(stmt: ast::Stmt) -> Self { 341 | let semi = ::default(); 342 | match stmt { 343 | ast::Stmt::AssignStmt(s) => s.into(), 344 | ast::Stmt::DeclStmt(s) => s.into(), 345 | ast::Stmt::ExprStmt(s) => vec![syn::Stmt::Semi(s.x.into(), semi)], 346 | ast::Stmt::ForStmt(s) => vec![syn::Stmt::Expr(s.into())], 347 | ast::Stmt::IfStmt(s) => vec![syn::Stmt::Expr(syn::Expr::If(s.into()))], 348 | ast::Stmt::IncDecStmt(s) => vec![syn::Stmt::Semi(syn::Expr::AssignOp(s.into()), semi)], 349 | ast::Stmt::ReturnStmt(s) => vec![syn::Stmt::Expr(syn::Expr::Return(s.into()))], 350 | _ => unimplemented!("{:?}", stmt), 351 | } 352 | } 353 | } 354 | 355 | impl From> for syn::ExprAssignOp { 356 | fn from(inc_dec_stmt: ast::IncDecStmt) -> Self { 357 | Self { 358 | attrs: vec![], 359 | op: match inc_dec_stmt.tok { 360 | token::Token::INC => syn::BinOp::AddEq(::default()), 361 | token::Token::DEC => syn::BinOp::SubEq(::default()), 362 | _ => unreachable!("implementation error"), 363 | }, 364 | left: Box::new(inc_dec_stmt.x.into()), 365 | right: Box::new(syn::Expr::Lit(syn::ExprLit { 366 | attrs: vec![], 367 | lit: syn::Lit::Int(syn::LitInt::new("1", Span::mixed_site())), 368 | })), 369 | } 370 | } 371 | } 372 | 373 | impl From> for syn::Expr { 374 | fn from(for_stmt: ast::ForStmt) -> Self { 375 | let mut stmts = vec![]; 376 | 377 | if let Some(init) = for_stmt.init { 378 | stmts.extend(Vec::from(*init)); 379 | } 380 | 381 | let mut body: syn::Block = for_stmt.body.into(); 382 | if let Some(post) = for_stmt.post { 383 | body.stmts.extend(Vec::from(*post)); 384 | } 385 | 386 | stmts.push(syn::Stmt::Expr(if let Some(cond) = for_stmt.cond { 387 | Self::While(syn::ExprWhile { 388 | attrs: vec![], 389 | label: None, 390 | cond: Box::new(cond.into()), 391 | body, 392 | while_token: ::default(), 393 | }) 394 | } else { 395 | Self::Loop(syn::ExprLoop { 396 | attrs: vec![], 397 | label: None, 398 | body, 399 | loop_token: ::default(), 400 | }) 401 | })); 402 | 403 | Self::Block(syn::ExprBlock { 404 | attrs: vec![], 405 | label: None, 406 | block: syn::Block { 407 | stmts, 408 | brace_token: syn::token::Brace { 409 | ..Default::default() 410 | }, 411 | }, 412 | }) 413 | } 414 | } 415 | 416 | impl From> for Vec { 417 | fn from(_decl_stmt: ast::DeclStmt) -> Self { 418 | // TODO 419 | vec![] 420 | } 421 | } 422 | 423 | impl From> for Vec { 424 | fn from(assign_stmt: ast::AssignStmt) -> Self { 425 | if assign_stmt.lhs.len() != assign_stmt.rhs.len() { 426 | panic!("different numbers of lhs/rhs in assignment") 427 | } 428 | 429 | if assign_stmt.lhs.is_empty() { 430 | panic!("empty lhs") 431 | } 432 | 433 | // a := 1 434 | // b, c := 2, 3 435 | if assign_stmt.tok == token::Token::DEFINE { 436 | let pat = match assign_stmt.lhs.len() { 437 | 1 => { 438 | if let ast::Expr::Ident(ident) = assign_stmt.lhs.into_iter().next().unwrap() { 439 | syn::Pat::Ident(syn::PatIdent { 440 | attrs: vec![], 441 | ident: ident.into(), 442 | by_ref: None, 443 | subpat: None, 444 | mutability: Some(::default()), 445 | }) 446 | } else { 447 | panic!("expected ident") 448 | } 449 | } 450 | _ => { 451 | let mut elems = syn::punctuated::Punctuated::new(); 452 | for expr in assign_stmt.lhs { 453 | if let ast::Expr::Ident(ident) = expr { 454 | elems.push(syn::Pat::Ident(syn::PatIdent { 455 | attrs: vec![], 456 | ident: ident.into(), 457 | by_ref: None, 458 | subpat: None, 459 | mutability: Some(::default()), 460 | })) 461 | } else { 462 | panic!("expecting ident") 463 | } 464 | } 465 | syn::Pat::Tuple(syn::PatTuple { 466 | attrs: vec![], 467 | paren_token: syn::token::Paren { 468 | ..Default::default() 469 | }, 470 | elems, 471 | }) 472 | } 473 | }; 474 | 475 | let init = match assign_stmt.rhs.len() { 476 | 1 => assign_stmt.rhs.into_iter().next().unwrap().into(), 477 | _ => { 478 | let mut elems = syn::punctuated::Punctuated::new(); 479 | for expr in assign_stmt.rhs { 480 | elems.push(expr.into()) 481 | } 482 | syn::Expr::Tuple(syn::ExprTuple { 483 | attrs: vec![], 484 | elems, 485 | paren_token: syn::token::Paren { 486 | ..Default::default() 487 | }, 488 | }) 489 | } 490 | }; 491 | 492 | return vec![syn::Stmt::Local(syn::Local { 493 | attrs: vec![], 494 | pat, 495 | init: Some((::default(), Box::new(init))), 496 | let_token: ::default(), 497 | semi_token: ::default(), 498 | })]; 499 | } 500 | 501 | // a = 1 502 | // b, c = 2, 3 503 | if assign_stmt.tok == token::Token::ASSIGN { 504 | if assign_stmt.lhs.len() == 1 { 505 | let left: syn::Expr = assign_stmt.lhs.into_iter().next().unwrap().into(); 506 | let right: syn::Expr = assign_stmt.rhs.into_iter().next().unwrap().into(); 507 | return vec![syn::parse_quote! { #left = #right; }]; 508 | } 509 | 510 | let mut out = vec![]; 511 | 512 | let mut idents: Vec = vec![]; 513 | let mut values: Vec = vec![]; 514 | for (lhs, rhs) in assign_stmt.lhs.iter().zip(assign_stmt.rhs.into_iter()) { 515 | if let ast::Expr::Ident(ident) = lhs { 516 | idents.push(quote::format_ident!("{}__", &ident.name)); 517 | values.push(rhs.into()); 518 | } else { 519 | panic!("expecting ident") 520 | } 521 | } 522 | out.push(syn::parse_quote! { let (#(#idents),*) = (#(#values),*); }); 523 | 524 | for lhs in assign_stmt.lhs { 525 | if let ast::Expr::Ident(ident) = &lhs { 526 | let right = quote::format_ident!("{}__", &ident.name); 527 | let left: syn::Expr = lhs.into(); 528 | out.push(syn::parse_quote! { #left = #right; }); 529 | } else { 530 | panic!("expecting ident") 531 | } 532 | } 533 | 534 | return out; 535 | } 536 | 537 | // e += 4 538 | if assign_stmt.tok.is_assign_op() { 539 | if assign_stmt.lhs.len() != 1 { 540 | panic!("only supports a single lhs element") 541 | } 542 | return vec![syn::Stmt::Semi( 543 | syn::Expr::AssignOp(syn::ExprAssignOp { 544 | attrs: vec![], 545 | left: Box::new(assign_stmt.lhs.into_iter().next().unwrap().into()), 546 | op: assign_stmt.tok.into(), 547 | right: Box::new(assign_stmt.rhs.into_iter().next().unwrap().into()), 548 | }), 549 | ::default(), 550 | )]; 551 | } 552 | 553 | unimplemented!( 554 | "implementation error, unexpected token {:?}", 555 | assign_stmt.tok 556 | ) 557 | } 558 | } 559 | 560 | impl From> for syn::ExprReturn { 561 | fn from(return_stmt: ast::ReturnStmt) -> Self { 562 | let expr: syn::Expr = return_stmt.results.into_iter().next().unwrap().into(); 563 | Self { 564 | attrs: vec![], 565 | expr: Some(Box::new(expr)), 566 | return_token: ::default(), 567 | } 568 | } 569 | } 570 | 571 | impl From for syn::BinOp { 572 | fn from(token: token::Token) -> Self { 573 | use token::Token::*; 574 | match token { 575 | ADD => Self::Add(::default()), 576 | SUB => Self::Sub(::default()), 577 | MUL => Self::Mul(::default()), 578 | QUO => Self::Div(::default()), 579 | REM => Self::Rem(::default()), 580 | LAND => Self::And(::default()), 581 | LOR => Self::Or(::default()), 582 | XOR => Self::BitXor(::default()), 583 | AND => Self::BitAnd(::default()), 584 | OR => Self::BitOr(::default()), 585 | SHL => Self::Shl(::default()), 586 | SHR => Self::Shr(>]>::default()), 587 | EQL => Self::Eq(::default()), 588 | LSS => Self::Lt(::default()), 589 | LEQ => Self::Le(::default()), 590 | NEQ => Self::Ne(::default()), 591 | GEQ => Self::Ge(=]>::default()), 592 | GTR => Self::Gt(]>::default()), 593 | // 594 | ADD_ASSIGN => Self::AddEq(::default()), 595 | SUB_ASSIGN => Self::SubEq(::default()), 596 | MUL_ASSIGN => Self::MulEq(::default()), 597 | QUO_ASSIGN => Self::DivEq(::default()), 598 | REM_ASSIGN => Self::RemEq(::default()), 599 | XOR_ASSIGN => Self::BitXorEq(::default()), 600 | AND_ASSIGN => Self::BitAndEq(::default()), 601 | OR_ASSIGN => Self::BitOrEq(::default()), 602 | SHL_ASSIGN => Self::ShlEq(::default()), 603 | SHR_ASSIGN => Self::ShrEq(>=]>::default()), 604 | // 605 | _ => unreachable!("unsupported binary op: {:?}", token), 606 | } 607 | } 608 | } 609 | 610 | #[cfg(test)] 611 | mod tests { 612 | //! This module contains the compiler tests (the initial Go -> Rust step, followed by the 613 | //! compiler passes). 614 | 615 | use super::compile; 616 | use crate::parser::parse_file; 617 | use quote::quote; 618 | use syn::parse_quote as rust; 619 | 620 | fn test(go_input: &str, expected: syn::File) { 621 | let parsed = parse_file("test.go", go_input).unwrap(); 622 | let compiled = compile(parsed).unwrap(); 623 | let output = (quote! {#compiled}).to_string(); 624 | let expected = (quote! {#expected}).to_string(); 625 | if output != expected { 626 | panic!("\n output: {}\n expected: {}\n", output, expected); 627 | } 628 | } 629 | 630 | #[test] 631 | fn it_should_support_binary_operators() { 632 | test( 633 | r#" 634 | package main; 635 | 636 | func main() { 637 | i += 2; 638 | i *= 2; 639 | } 640 | "#, 641 | rust! { 642 | pub fn main() { 643 | i += 2; 644 | i *= 2; 645 | } 646 | }, 647 | ) 648 | } 649 | } 650 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.12.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "atty" 25 | version = "0.2.14" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 28 | dependencies = [ 29 | "hermit-abi", 30 | "libc", 31 | "winapi", 32 | ] 33 | 34 | [[package]] 35 | name = "autocfg" 36 | version = "1.0.1" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 39 | 40 | [[package]] 41 | name = "bitflags" 42 | version = "1.3.2" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 45 | 46 | [[package]] 47 | name = "bumpalo" 48 | version = "3.8.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" 51 | 52 | [[package]] 53 | name = "cfg-if" 54 | version = "1.0.0" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 57 | 58 | [[package]] 59 | name = "clap" 60 | version = "3.0.0-rc.7" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "9468f8012246b0836c6fd11725102b0844254985f2462b6c637d50040ef49df0" 63 | dependencies = [ 64 | "atty", 65 | "bitflags", 66 | "clap_derive", 67 | "indexmap", 68 | "lazy_static", 69 | "os_str_bytes", 70 | "strsim", 71 | "termcolor", 72 | "textwrap", 73 | ] 74 | 75 | [[package]] 76 | name = "clap_derive" 77 | version = "3.0.0-rc.7" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "b72e1af32a4de4d21a43d26de33fe69c00e895371bd8b1523d674f011b610467" 80 | dependencies = [ 81 | "heck", 82 | "proc-macro-error", 83 | "proc-macro2", 84 | "quote", 85 | "syn", 86 | ] 87 | 88 | [[package]] 89 | name = "colored" 90 | version = "2.0.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" 93 | dependencies = [ 94 | "atty", 95 | "lazy_static", 96 | "winapi", 97 | ] 98 | 99 | [[package]] 100 | name = "console" 101 | version = "0.15.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" 104 | dependencies = [ 105 | "encode_unicode", 106 | "libc", 107 | "once_cell", 108 | "regex", 109 | "terminal_size", 110 | "unicode-width", 111 | "winapi", 112 | ] 113 | 114 | [[package]] 115 | name = "console_error_panic_hook" 116 | version = "0.1.7" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" 119 | dependencies = [ 120 | "cfg-if", 121 | "wasm-bindgen", 122 | ] 123 | 124 | [[package]] 125 | name = "crossbeam" 126 | version = "0.8.1" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845" 129 | dependencies = [ 130 | "cfg-if", 131 | "crossbeam-channel", 132 | "crossbeam-deque", 133 | "crossbeam-epoch", 134 | "crossbeam-queue", 135 | "crossbeam-utils", 136 | ] 137 | 138 | [[package]] 139 | name = "crossbeam-channel" 140 | version = "0.5.1" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" 143 | dependencies = [ 144 | "cfg-if", 145 | "crossbeam-utils", 146 | ] 147 | 148 | [[package]] 149 | name = "crossbeam-deque" 150 | version = "0.8.1" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" 153 | dependencies = [ 154 | "cfg-if", 155 | "crossbeam-epoch", 156 | "crossbeam-utils", 157 | ] 158 | 159 | [[package]] 160 | name = "crossbeam-epoch" 161 | version = "0.9.5" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" 164 | dependencies = [ 165 | "cfg-if", 166 | "crossbeam-utils", 167 | "lazy_static", 168 | "memoffset", 169 | "scopeguard", 170 | ] 171 | 172 | [[package]] 173 | name = "crossbeam-queue" 174 | version = "0.3.2" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" 177 | dependencies = [ 178 | "cfg-if", 179 | "crossbeam-utils", 180 | ] 181 | 182 | [[package]] 183 | name = "crossbeam-utils" 184 | version = "0.8.5" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" 187 | dependencies = [ 188 | "cfg-if", 189 | "lazy_static", 190 | ] 191 | 192 | [[package]] 193 | name = "ctor" 194 | version = "0.1.21" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" 197 | dependencies = [ 198 | "quote", 199 | "syn", 200 | ] 201 | 202 | [[package]] 203 | name = "diff" 204 | version = "0.1.12" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" 207 | 208 | [[package]] 209 | name = "encode_unicode" 210 | version = "0.3.6" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 213 | 214 | [[package]] 215 | name = "env_logger" 216 | version = "0.7.1" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 219 | dependencies = [ 220 | "atty", 221 | "humantime", 222 | "log", 223 | "regex", 224 | "termcolor", 225 | ] 226 | 227 | [[package]] 228 | name = "fuchsia-cprng" 229 | version = "0.1.1" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 232 | 233 | [[package]] 234 | name = "getrandom" 235 | version = "0.2.3" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 238 | dependencies = [ 239 | "cfg-if", 240 | "libc", 241 | "wasi", 242 | ] 243 | 244 | [[package]] 245 | name = "glob" 246 | version = "0.3.0" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 249 | 250 | [[package]] 251 | name = "gors" 252 | version = "0.1.0" 253 | dependencies = [ 254 | "log", 255 | "phf", 256 | "prettyplease", 257 | "proc-macro2", 258 | "quote", 259 | "serde", 260 | "syn", 261 | "unicode-general-category", 262 | ] 263 | 264 | [[package]] 265 | name = "gors-cli" 266 | version = "0.1.0" 267 | dependencies = [ 268 | "clap", 269 | "colored", 270 | "console", 271 | "crossbeam", 272 | "glob", 273 | "gors", 274 | "lazy_static", 275 | "num_cpus", 276 | "phf", 277 | "pretty_assertions", 278 | "pretty_env_logger", 279 | "serde_json", 280 | "similar", 281 | "tempdir", 282 | ] 283 | 284 | [[package]] 285 | name = "gors-wasm" 286 | version = "0.1.0" 287 | dependencies = [ 288 | "console_error_panic_hook", 289 | "gors", 290 | "js-sys", 291 | "wasm-bindgen", 292 | ] 293 | 294 | [[package]] 295 | name = "hashbrown" 296 | version = "0.11.2" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 299 | 300 | [[package]] 301 | name = "heck" 302 | version = "0.3.3" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 305 | dependencies = [ 306 | "unicode-segmentation", 307 | ] 308 | 309 | [[package]] 310 | name = "hermit-abi" 311 | version = "0.1.19" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 314 | dependencies = [ 315 | "libc", 316 | ] 317 | 318 | [[package]] 319 | name = "humantime" 320 | version = "1.3.0" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 323 | dependencies = [ 324 | "quick-error", 325 | ] 326 | 327 | [[package]] 328 | name = "indexmap" 329 | version = "1.7.0" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" 332 | dependencies = [ 333 | "autocfg", 334 | "hashbrown", 335 | ] 336 | 337 | [[package]] 338 | name = "itoa" 339 | version = "1.0.1" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 342 | 343 | [[package]] 344 | name = "js-sys" 345 | version = "0.3.55" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" 348 | dependencies = [ 349 | "wasm-bindgen", 350 | ] 351 | 352 | [[package]] 353 | name = "lazy_static" 354 | version = "1.4.0" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 357 | 358 | [[package]] 359 | name = "libc" 360 | version = "0.2.112" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" 363 | 364 | [[package]] 365 | name = "log" 366 | version = "0.4.14" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 369 | dependencies = [ 370 | "cfg-if", 371 | ] 372 | 373 | [[package]] 374 | name = "memchr" 375 | version = "2.4.1" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 378 | 379 | [[package]] 380 | name = "memoffset" 381 | version = "0.6.5" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 384 | dependencies = [ 385 | "autocfg", 386 | ] 387 | 388 | [[package]] 389 | name = "num_cpus" 390 | version = "1.13.1" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 393 | dependencies = [ 394 | "hermit-abi", 395 | "libc", 396 | ] 397 | 398 | [[package]] 399 | name = "once_cell" 400 | version = "1.9.0" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" 403 | 404 | [[package]] 405 | name = "os_str_bytes" 406 | version = "6.0.0" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 409 | dependencies = [ 410 | "memchr", 411 | ] 412 | 413 | [[package]] 414 | name = "output_vt100" 415 | version = "0.1.2" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" 418 | dependencies = [ 419 | "winapi", 420 | ] 421 | 422 | [[package]] 423 | name = "phf" 424 | version = "0.10.1" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" 427 | dependencies = [ 428 | "phf_macros", 429 | "phf_shared", 430 | "proc-macro-hack", 431 | ] 432 | 433 | [[package]] 434 | name = "phf_generator" 435 | version = "0.10.0" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" 438 | dependencies = [ 439 | "phf_shared", 440 | "rand 0.8.4", 441 | ] 442 | 443 | [[package]] 444 | name = "phf_macros" 445 | version = "0.10.0" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" 448 | dependencies = [ 449 | "phf_generator", 450 | "phf_shared", 451 | "proc-macro-hack", 452 | "proc-macro2", 453 | "quote", 454 | "syn", 455 | ] 456 | 457 | [[package]] 458 | name = "phf_shared" 459 | version = "0.10.0" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 462 | dependencies = [ 463 | "siphasher", 464 | ] 465 | 466 | [[package]] 467 | name = "ppv-lite86" 468 | version = "0.2.15" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" 471 | 472 | [[package]] 473 | name = "pretty_assertions" 474 | version = "1.0.0" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "ec0cfe1b2403f172ba0f234e500906ee0a3e493fb81092dac23ebefe129301cc" 477 | dependencies = [ 478 | "ansi_term", 479 | "ctor", 480 | "diff", 481 | "output_vt100", 482 | ] 483 | 484 | [[package]] 485 | name = "pretty_env_logger" 486 | version = "0.4.0" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" 489 | dependencies = [ 490 | "env_logger", 491 | "log", 492 | ] 493 | 494 | [[package]] 495 | name = "prettyplease" 496 | version = "0.1.0" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "3f3ebd569945e276e2c8000677484f50f5c242b3495929ae1c7d42d89778939a" 499 | dependencies = [ 500 | "proc-macro2", 501 | "syn", 502 | ] 503 | 504 | [[package]] 505 | name = "proc-macro-error" 506 | version = "1.0.4" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 509 | dependencies = [ 510 | "proc-macro-error-attr", 511 | "proc-macro2", 512 | "quote", 513 | "syn", 514 | "version_check", 515 | ] 516 | 517 | [[package]] 518 | name = "proc-macro-error-attr" 519 | version = "1.0.4" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 522 | dependencies = [ 523 | "proc-macro2", 524 | "quote", 525 | "version_check", 526 | ] 527 | 528 | [[package]] 529 | name = "proc-macro-hack" 530 | version = "0.5.19" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 533 | 534 | [[package]] 535 | name = "proc-macro2" 536 | version = "1.0.34" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1" 539 | dependencies = [ 540 | "unicode-xid", 541 | ] 542 | 543 | [[package]] 544 | name = "quick-error" 545 | version = "1.2.3" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 548 | 549 | [[package]] 550 | name = "quote" 551 | version = "1.0.10" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 554 | dependencies = [ 555 | "proc-macro2", 556 | ] 557 | 558 | [[package]] 559 | name = "rand" 560 | version = "0.4.6" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 563 | dependencies = [ 564 | "fuchsia-cprng", 565 | "libc", 566 | "rand_core 0.3.1", 567 | "rdrand", 568 | "winapi", 569 | ] 570 | 571 | [[package]] 572 | name = "rand" 573 | version = "0.8.4" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 576 | dependencies = [ 577 | "libc", 578 | "rand_chacha", 579 | "rand_core 0.6.3", 580 | "rand_hc", 581 | ] 582 | 583 | [[package]] 584 | name = "rand_chacha" 585 | version = "0.3.1" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 588 | dependencies = [ 589 | "ppv-lite86", 590 | "rand_core 0.6.3", 591 | ] 592 | 593 | [[package]] 594 | name = "rand_core" 595 | version = "0.3.1" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 598 | dependencies = [ 599 | "rand_core 0.4.2", 600 | ] 601 | 602 | [[package]] 603 | name = "rand_core" 604 | version = "0.4.2" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 607 | 608 | [[package]] 609 | name = "rand_core" 610 | version = "0.6.3" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 613 | dependencies = [ 614 | "getrandom", 615 | ] 616 | 617 | [[package]] 618 | name = "rand_hc" 619 | version = "0.3.1" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 622 | dependencies = [ 623 | "rand_core 0.6.3", 624 | ] 625 | 626 | [[package]] 627 | name = "rdrand" 628 | version = "0.4.0" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 631 | dependencies = [ 632 | "rand_core 0.3.1", 633 | ] 634 | 635 | [[package]] 636 | name = "regex" 637 | version = "1.5.4" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 640 | dependencies = [ 641 | "aho-corasick", 642 | "memchr", 643 | "regex-syntax", 644 | ] 645 | 646 | [[package]] 647 | name = "regex-syntax" 648 | version = "0.6.25" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 651 | 652 | [[package]] 653 | name = "remove_dir_all" 654 | version = "0.5.3" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 657 | dependencies = [ 658 | "winapi", 659 | ] 660 | 661 | [[package]] 662 | name = "ryu" 663 | version = "1.0.9" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 666 | 667 | [[package]] 668 | name = "scopeguard" 669 | version = "1.1.0" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 672 | 673 | [[package]] 674 | name = "serde" 675 | version = "1.0.132" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008" 678 | 679 | [[package]] 680 | name = "serde_json" 681 | version = "1.0.73" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5" 684 | dependencies = [ 685 | "itoa", 686 | "ryu", 687 | "serde", 688 | ] 689 | 690 | [[package]] 691 | name = "similar" 692 | version = "2.1.0" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" 695 | 696 | [[package]] 697 | name = "siphasher" 698 | version = "0.3.7" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" 701 | 702 | [[package]] 703 | name = "strsim" 704 | version = "0.10.0" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 707 | 708 | [[package]] 709 | name = "syn" 710 | version = "1.0.85" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" 713 | dependencies = [ 714 | "proc-macro2", 715 | "quote", 716 | "unicode-xid", 717 | ] 718 | 719 | [[package]] 720 | name = "tempdir" 721 | version = "0.3.7" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" 724 | dependencies = [ 725 | "rand 0.4.6", 726 | "remove_dir_all", 727 | ] 728 | 729 | [[package]] 730 | name = "termcolor" 731 | version = "1.1.2" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 734 | dependencies = [ 735 | "winapi-util", 736 | ] 737 | 738 | [[package]] 739 | name = "terminal_size" 740 | version = "0.1.17" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" 743 | dependencies = [ 744 | "libc", 745 | "winapi", 746 | ] 747 | 748 | [[package]] 749 | name = "textwrap" 750 | version = "0.14.2" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" 753 | 754 | [[package]] 755 | name = "unicode-general-category" 756 | version = "0.4.0" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "07547e3ee45e28326cc23faac56d44f58f16ab23e413db526debce3b0bfd2742" 759 | 760 | [[package]] 761 | name = "unicode-segmentation" 762 | version = "1.8.0" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" 765 | 766 | [[package]] 767 | name = "unicode-width" 768 | version = "0.1.9" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 771 | 772 | [[package]] 773 | name = "unicode-xid" 774 | version = "0.2.2" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 777 | 778 | [[package]] 779 | name = "version_check" 780 | version = "0.9.3" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 783 | 784 | [[package]] 785 | name = "wasi" 786 | version = "0.10.2+wasi-snapshot-preview1" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 789 | 790 | [[package]] 791 | name = "wasm-bindgen" 792 | version = "0.2.78" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" 795 | dependencies = [ 796 | "cfg-if", 797 | "wasm-bindgen-macro", 798 | ] 799 | 800 | [[package]] 801 | name = "wasm-bindgen-backend" 802 | version = "0.2.78" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" 805 | dependencies = [ 806 | "bumpalo", 807 | "lazy_static", 808 | "log", 809 | "proc-macro2", 810 | "quote", 811 | "syn", 812 | "wasm-bindgen-shared", 813 | ] 814 | 815 | [[package]] 816 | name = "wasm-bindgen-macro" 817 | version = "0.2.78" 818 | source = "registry+https://github.com/rust-lang/crates.io-index" 819 | checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" 820 | dependencies = [ 821 | "quote", 822 | "wasm-bindgen-macro-support", 823 | ] 824 | 825 | [[package]] 826 | name = "wasm-bindgen-macro-support" 827 | version = "0.2.78" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" 830 | dependencies = [ 831 | "proc-macro2", 832 | "quote", 833 | "syn", 834 | "wasm-bindgen-backend", 835 | "wasm-bindgen-shared", 836 | ] 837 | 838 | [[package]] 839 | name = "wasm-bindgen-shared" 840 | version = "0.2.78" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" 843 | 844 | [[package]] 845 | name = "winapi" 846 | version = "0.3.9" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 849 | dependencies = [ 850 | "winapi-i686-pc-windows-gnu", 851 | "winapi-x86_64-pc-windows-gnu", 852 | ] 853 | 854 | [[package]] 855 | name = "winapi-i686-pc-windows-gnu" 856 | version = "0.4.0" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 859 | 860 | [[package]] 861 | name = "winapi-util" 862 | version = "0.1.5" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 865 | dependencies = [ 866 | "winapi", 867 | ] 868 | 869 | [[package]] 870 | name = "winapi-x86_64-pc-windows-gnu" 871 | version = "0.4.0" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 874 | -------------------------------------------------------------------------------- /gors/scanner/mod.rs: -------------------------------------------------------------------------------- 1 | // https://golang.org/ref/spec#Lexical_elements 2 | 3 | use crate::token::{Position, Token}; 4 | use phf::{phf_map, Map}; 5 | use std::fmt; 6 | use unicode_general_category::{get_general_category, GeneralCategory}; 7 | 8 | pub type Step<'a> = (Position<'a>, Token, &'a str); 9 | 10 | #[derive(Debug)] 11 | pub enum ScannerError { 12 | HexadecimalNotFound, 13 | OctalNotFound, 14 | UnterminatedComment, 15 | UnterminatedEscapedChar, 16 | UnterminatedRune, 17 | UnterminatedString, 18 | InvalidDirective, 19 | } 20 | 21 | impl std::error::Error for ScannerError {} 22 | 23 | impl fmt::Display for ScannerError { 24 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 | write!(f, "scanner error: {:?}", self) 26 | } 27 | } 28 | 29 | pub type Result = std::result::Result; 30 | 31 | #[derive(Debug)] 32 | pub struct Scanner<'a> { 33 | directory: &'a str, 34 | file: &'a str, 35 | buffer: &'a str, 36 | // 37 | chars: std::iter::Peekable>, 38 | current_char: Option, 39 | current_char_len: usize, 40 | // 41 | offset: usize, 42 | line: usize, 43 | column: usize, 44 | start_offset: usize, 45 | start_line: usize, 46 | start_column: usize, 47 | // 48 | hide_column: bool, 49 | insert_semi: bool, 50 | pending_line_info: Option>, 51 | } 52 | 53 | type LineInfo<'a> = (Option<&'a str>, usize, Option, bool); 54 | 55 | impl<'a> Scanner<'a> { 56 | pub fn new(filename: &'a str, buffer: &'a str) -> Self { 57 | let (directory, file) = filename.rsplit_once('/').unwrap_or(("", filename)); 58 | let mut s = Scanner { 59 | directory, 60 | file, 61 | buffer, 62 | // 63 | chars: buffer.chars().peekable(), 64 | current_char: None, 65 | current_char_len: 0, 66 | // 67 | offset: 0, 68 | line: 1, 69 | column: 1, 70 | start_offset: 0, 71 | start_line: 1, 72 | start_column: 1, 73 | // 74 | hide_column: false, 75 | insert_semi: false, 76 | pending_line_info: None, 77 | }; 78 | s.next(); // read the first character 79 | s 80 | } 81 | 82 | #[allow(clippy::cognitive_complexity)] // Allow complex scan function 83 | pub fn scan(&mut self) -> Result> { 84 | let insert_semi = self.insert_semi; 85 | self.insert_semi = false; 86 | 87 | while let Some(c) = self.current_char { 88 | self.reset_start(); 89 | 90 | match c { 91 | ' ' | '\t' | '\r' => { 92 | self.next(); 93 | } 94 | 95 | '\n' => { 96 | self.next(); 97 | if insert_semi { 98 | return Ok((self.position(), Token::SEMICOLON, "\n")); 99 | } 100 | } 101 | 102 | _ => break, 103 | } 104 | } 105 | 106 | if let Some(c) = self.current_char { 107 | match c { 108 | '+' => { 109 | self.next(); 110 | match self.current_char { 111 | Some('=') => { 112 | self.next(); 113 | return Ok((self.position(), Token::ADD_ASSIGN, "")); 114 | } 115 | Some('+') => { 116 | self.insert_semi = true; 117 | self.next(); 118 | return Ok((self.position(), Token::INC, "")); 119 | } 120 | _ => return Ok((self.position(), Token::ADD, "")), 121 | } 122 | } 123 | 124 | '-' => { 125 | self.next(); 126 | match self.current_char { 127 | Some('=') => { 128 | self.next(); 129 | return Ok((self.position(), Token::SUB_ASSIGN, "")); 130 | } 131 | Some('-') => { 132 | self.insert_semi = true; 133 | self.next(); 134 | return Ok((self.position(), Token::DEC, "")); 135 | } 136 | _ => return Ok((self.position(), Token::SUB, "")), 137 | } 138 | } 139 | 140 | '*' => { 141 | self.next(); 142 | match self.current_char { 143 | Some('=') => { 144 | self.next(); 145 | return Ok((self.position(), Token::MUL_ASSIGN, "")); 146 | } 147 | _ => return Ok((self.position(), Token::MUL, "")), 148 | } 149 | } 150 | 151 | '/' => match self.peek() { 152 | Some('=') => { 153 | self.next(); 154 | self.next(); 155 | return Ok((self.position(), Token::QUO_ASSIGN, "")); 156 | } 157 | Some('/') => { 158 | if insert_semi { 159 | return Ok((self.position(), Token::SEMICOLON, "\n")); 160 | } 161 | return self.scan_line_comment(); 162 | } 163 | Some('*') => { 164 | if insert_semi && self.find_line_end() { 165 | return Ok((self.position(), Token::SEMICOLON, "\n")); 166 | } 167 | return self.scan_general_comment(); 168 | } 169 | _ => { 170 | self.next(); 171 | return Ok((self.position(), Token::QUO, "")); 172 | } 173 | }, 174 | 175 | '%' => { 176 | self.next(); 177 | match self.current_char { 178 | Some('=') => { 179 | self.next(); 180 | return Ok((self.position(), Token::REM_ASSIGN, "")); 181 | } 182 | _ => return Ok((self.position(), Token::REM, "")), 183 | } 184 | } 185 | 186 | '&' => { 187 | self.next(); 188 | match self.current_char { 189 | Some('=') => { 190 | self.next(); 191 | return Ok((self.position(), Token::AND_ASSIGN, "")); 192 | } 193 | Some('&') => { 194 | self.next(); 195 | return Ok((self.position(), Token::LAND, "")); 196 | } 197 | Some('^') => { 198 | self.next(); 199 | match self.current_char { 200 | Some('=') => { 201 | self.next(); 202 | return Ok((self.position(), Token::AND_NOT_ASSIGN, "")); 203 | } 204 | _ => return Ok((self.position(), Token::AND_NOT, "")), 205 | } 206 | } 207 | _ => return Ok((self.position(), Token::AND, "")), 208 | } 209 | } 210 | 211 | '|' => { 212 | self.next(); 213 | match self.current_char { 214 | Some('=') => { 215 | self.next(); 216 | return Ok((self.position(), Token::OR_ASSIGN, "")); 217 | } 218 | Some('|') => { 219 | self.next(); 220 | return Ok((self.position(), Token::LOR, "")); 221 | } 222 | _ => return Ok((self.position(), Token::OR, "")), 223 | } 224 | } 225 | 226 | '^' => { 227 | self.next(); 228 | match self.current_char { 229 | Some('=') => { 230 | self.next(); 231 | return Ok((self.position(), Token::XOR_ASSIGN, "")); 232 | } 233 | _ => return Ok((self.position(), Token::XOR, "")), 234 | } 235 | } 236 | 237 | '<' => { 238 | self.next(); 239 | match self.current_char { 240 | Some('<') => { 241 | self.next(); 242 | match self.current_char { 243 | Some('=') => { 244 | self.next(); 245 | return Ok((self.position(), Token::SHL_ASSIGN, "")); 246 | } 247 | _ => return Ok((self.position(), Token::SHL, "")), 248 | } 249 | } 250 | Some('=') => { 251 | self.next(); 252 | return Ok((self.position(), Token::LEQ, "")); 253 | } 254 | Some('-') => { 255 | self.next(); 256 | return Ok((self.position(), Token::ARROW, "")); 257 | } 258 | _ => return Ok((self.position(), Token::LSS, "")), 259 | } 260 | } 261 | 262 | '>' => { 263 | self.next(); 264 | match self.current_char { 265 | Some('>') => { 266 | self.next(); 267 | match self.current_char { 268 | Some('=') => { 269 | self.next(); 270 | return Ok((self.position(), Token::SHR_ASSIGN, "")); 271 | } 272 | _ => { 273 | return Ok((self.position(), Token::SHR, "")); 274 | } 275 | } 276 | } 277 | Some('=') => { 278 | self.next(); 279 | return Ok((self.position(), Token::GEQ, "")); 280 | } 281 | _ => return Ok((self.position(), Token::GTR, "")), 282 | } 283 | } 284 | 285 | ':' => { 286 | self.next(); 287 | match self.current_char { 288 | Some('=') => { 289 | self.next(); 290 | return Ok((self.position(), Token::DEFINE, "")); 291 | } 292 | _ => return Ok((self.position(), Token::COLON, "")), 293 | } 294 | } 295 | 296 | '!' => { 297 | self.next(); 298 | match self.current_char { 299 | Some('=') => { 300 | self.next(); 301 | return Ok((self.position(), Token::NEQ, "")); 302 | } 303 | _ => return Ok((self.position(), Token::NOT, "")), 304 | } 305 | } 306 | 307 | ',' => { 308 | self.next(); 309 | return Ok((self.position(), Token::COMMA, "")); 310 | } 311 | 312 | '(' => { 313 | self.next(); 314 | return Ok((self.position(), Token::LPAREN, "")); 315 | } 316 | 317 | ')' => { 318 | self.insert_semi = true; 319 | self.next(); 320 | return Ok((self.position(), Token::RPAREN, "")); 321 | } 322 | 323 | '[' => { 324 | self.next(); 325 | return Ok((self.position(), Token::LBRACK, "")); 326 | } 327 | 328 | ']' => { 329 | self.insert_semi = true; 330 | self.next(); 331 | return Ok((self.position(), Token::RBRACK, "")); 332 | } 333 | 334 | '{' => { 335 | self.next(); 336 | return Ok((self.position(), Token::LBRACE, "")); 337 | } 338 | 339 | '}' => { 340 | self.insert_semi = true; 341 | self.next(); 342 | return Ok((self.position(), Token::RBRACE, "")); 343 | } 344 | 345 | ';' => { 346 | self.next(); 347 | return Ok((self.position(), Token::SEMICOLON, ";")); 348 | } 349 | 350 | '.' => { 351 | self.next(); 352 | match self.current_char { 353 | Some('0'..='9') => return self.scan_int_or_float_or_imag(true), 354 | Some('.') => match self.peek() { 355 | Some('.') => { 356 | self.next(); 357 | self.next(); 358 | return Ok((self.position(), Token::ELLIPSIS, "")); 359 | } 360 | _ => return Ok((self.position(), Token::PERIOD, "")), 361 | }, 362 | _ => return Ok((self.position(), Token::PERIOD, "")), 363 | } 364 | } 365 | 366 | '=' => { 367 | self.next(); 368 | match self.current_char { 369 | Some('=') => { 370 | self.next(); 371 | return Ok((self.position(), Token::EQL, "")); 372 | } 373 | _ => return Ok((self.position(), Token::ASSIGN, "")), 374 | } 375 | } 376 | 377 | '0'..='9' => return self.scan_int_or_float_or_imag(false), 378 | '\'' => return self.scan_rune(), 379 | '"' => return self.scan_interpreted_string(), 380 | '`' => return self.scan_raw_string(), 381 | _ => return self.scan_pkg_or_keyword_or_ident(), 382 | }; 383 | } 384 | 385 | self.reset_start(); 386 | if insert_semi { 387 | Ok((self.position(), Token::SEMICOLON, "\n")) 388 | } else { 389 | Ok((self.position(), Token::EOF, "")) 390 | } 391 | } 392 | 393 | // https://golang.org/ref/spec#Keywords 394 | // https://golang.org/ref/spec#Identifiers 395 | fn scan_pkg_or_keyword_or_ident(&mut self) -> Result> { 396 | self.next(); 397 | 398 | while let Some(c) = self.current_char { 399 | if !(is_letter(c) || is_unicode_digit(c)) { 400 | break; 401 | } 402 | self.next() 403 | } 404 | 405 | let pos = self.position(); 406 | let literal = self.literal(); 407 | 408 | if literal.len() > 1 { 409 | if let Some(&token) = KEYWORDS.get(literal) { 410 | self.insert_semi = matches!( 411 | token, 412 | Token::BREAK | Token::CONTINUE | Token::FALLTHROUGH | Token::RETURN 413 | ); 414 | return Ok((pos, token, literal)); 415 | } 416 | } 417 | 418 | self.insert_semi = true; 419 | Ok((pos, Token::IDENT, literal)) 420 | } 421 | 422 | // https://golang.org/ref/spec#Integer_literals 423 | // https://golang.org/ref/spec#Floating-point_literals 424 | // https://golang.org/ref/spec#Imaginary_literals 425 | fn scan_int_or_float_or_imag(&mut self, preceding_dot: bool) -> Result> { 426 | self.insert_semi = true; 427 | 428 | let mut token = Token::INT; 429 | let mut digits = "_0123456789"; 430 | let mut exp = "eE"; 431 | 432 | if !preceding_dot { 433 | if matches!(self.current_char, Some('0')) { 434 | self.next(); 435 | match self.current_char { 436 | Some('b' | 'B') => { 437 | digits = "_01"; 438 | exp = ""; 439 | self.next(); 440 | } 441 | Some('o' | 'O') => { 442 | digits = "_01234567"; 443 | exp = ""; 444 | self.next(); 445 | } 446 | Some('x' | 'X') => { 447 | digits = "_0123456789abcdefABCDEF"; 448 | exp = "pP"; 449 | self.next(); 450 | } 451 | _ => {} 452 | }; 453 | } 454 | 455 | while let Some(c) = self.current_char { 456 | if !digits.contains(c) { 457 | break; 458 | } 459 | self.next(); 460 | } 461 | } 462 | 463 | if preceding_dot || matches!(self.current_char, Some('.')) { 464 | token = Token::FLOAT; 465 | self.next(); 466 | while let Some(c) = self.current_char { 467 | if !digits.contains(c) { 468 | break; 469 | } 470 | self.next(); 471 | } 472 | } 473 | 474 | if !exp.is_empty() { 475 | if let Some(c) = self.current_char { 476 | if exp.contains(c) { 477 | token = Token::FLOAT; 478 | self.next(); 479 | if matches!(self.current_char, Some('-' | '+')) { 480 | self.next(); 481 | } 482 | while let Some(c) = self.current_char { 483 | if !matches!(c, '_' | '0'..='9') { 484 | break; 485 | } 486 | self.next(); 487 | } 488 | } 489 | } 490 | } 491 | 492 | if matches!(self.current_char, Some('i')) { 493 | token = Token::IMAG; 494 | self.next(); 495 | } 496 | 497 | Ok((self.position(), token, self.literal())) 498 | } 499 | 500 | // https://golang.org/ref/spec#Rune_literals 501 | fn scan_rune(&mut self) -> Result> { 502 | self.insert_semi = true; 503 | self.next(); 504 | 505 | match self.current_char { 506 | Some('\\') => self.require_escaped_char::<'\''>()?, 507 | Some(_) => self.next(), 508 | _ => return Err(ScannerError::UnterminatedRune), 509 | } 510 | 511 | if matches!(self.current_char, Some('\'')) { 512 | self.next(); 513 | return Ok((self.position(), Token::CHAR, self.literal())); 514 | } 515 | 516 | Err(ScannerError::UnterminatedRune) 517 | } 518 | 519 | // https://golang.org/ref/spec#String_literals 520 | fn scan_interpreted_string(&mut self) -> Result> { 521 | self.insert_semi = true; 522 | self.next(); 523 | 524 | while let Some(c) = self.current_char { 525 | match c { 526 | '"' => { 527 | self.next(); 528 | return Ok((self.position(), Token::STRING, self.literal())); 529 | } 530 | '\\' => self.require_escaped_char::<'"'>()?, 531 | _ => self.next(), 532 | } 533 | } 534 | 535 | Err(ScannerError::UnterminatedString) 536 | } 537 | 538 | // https://golang.org/ref/spec#String_literals 539 | fn scan_raw_string(&mut self) -> Result> { 540 | self.insert_semi = true; 541 | self.next(); 542 | 543 | while let Some(c) = self.current_char { 544 | match c { 545 | '`' => { 546 | self.next(); 547 | return Ok((self.position(), Token::STRING, self.literal())); 548 | } 549 | _ => self.next(), 550 | } 551 | } 552 | 553 | Err(ScannerError::UnterminatedString) 554 | } 555 | 556 | // https://golang.org/ref/spec#Comments 557 | fn scan_general_comment(&mut self) -> Result> { 558 | self.next(); 559 | self.next(); 560 | 561 | while let Some(c) = self.current_char { 562 | match c { 563 | '*' => { 564 | self.next(); 565 | if matches!(self.current_char, Some('/')) { 566 | self.next(); 567 | 568 | let pos = self.position(); 569 | let lit = self.literal(); 570 | 571 | // look for compiler directives 572 | self.directive(&lit["/*".len()..lit.len() - "*/".len()], true)?; 573 | 574 | return Ok((pos, Token::COMMENT, lit)); 575 | } 576 | } 577 | _ => self.next(), 578 | } 579 | } 580 | 581 | Err(ScannerError::UnterminatedComment) 582 | } 583 | 584 | // https://golang.org/ref/spec#Comments 585 | fn scan_line_comment(&mut self) -> Result> { 586 | self.next(); 587 | self.next(); 588 | 589 | while let Some(c) = self.current_char { 590 | if is_newline(c) { 591 | break; 592 | } 593 | self.next(); 594 | } 595 | 596 | let pos = self.position(); 597 | let lit = self.literal(); 598 | 599 | // look for compiler directives (at the beginning of line) 600 | if self.start_column == 1 { 601 | self.directive(lit["//".len()..].trim_end(), false)?; 602 | } 603 | 604 | Ok((pos, Token::COMMENT, self.literal())) 605 | } 606 | 607 | // https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives 608 | fn directive(&mut self, input: &'a str, immediate: bool) -> Result<()> { 609 | if let Some(line_directive) = input.strip_prefix("line ") { 610 | self.pending_line_info = self.parse_line_directive(line_directive)?; 611 | if immediate { 612 | self.consume_pending_line_info(); 613 | } 614 | } 615 | Ok(()) 616 | } 617 | 618 | fn parse_line_directive(&mut self, line_directive: &'a str) -> Result>> { 619 | if let Some((file, line)) = line_directive.rsplit_once(':') { 620 | let line = line.parse().map_err(|_| ScannerError::InvalidDirective)?; 621 | 622 | if let Some((file, l)) = file.rsplit_once(':') { 623 | if let Ok(l) = l.parse() { 624 | //line :line:col 625 | //line filename:line:col 626 | /*line :line:col*/ 627 | /*line filename:line:col*/ 628 | let file = if !file.is_empty() { Some(file) } else { None }; 629 | let col = Some(line); 630 | let line = l; 631 | let hide_column = false; 632 | return Ok(Some((file, line, col, hide_column))); 633 | } 634 | } 635 | 636 | //line :line 637 | //line filename:line 638 | /*line :line*/ 639 | /*line filename:line*/ 640 | Ok(Some((Some(file), line, None, true))) 641 | } else { 642 | Ok(None) 643 | } 644 | } 645 | 646 | const fn find_line_end(&self) -> bool { 647 | let buffer = self.buffer.as_bytes(); 648 | let mut in_comment = true; 649 | 650 | let mut i = self.offset; 651 | let max = self.buffer.len(); 652 | while i < max { 653 | let c = buffer[i] as char; 654 | 655 | if i < max - 1 { 656 | let n = buffer[i + 1] as char; 657 | 658 | if !in_comment && c == '/' && n == '/' { 659 | return true; 660 | } 661 | 662 | if c == '/' && n == '*' { 663 | i += 2; 664 | in_comment = true; 665 | continue; 666 | } 667 | 668 | if c == '*' && n == '/' { 669 | i += 2; 670 | in_comment = false; 671 | continue; 672 | } 673 | } 674 | 675 | if is_newline(c) { 676 | return true; 677 | } 678 | 679 | if !in_comment && !matches!(c, ' ' | '\t' | '\r') { 680 | return false; 681 | } 682 | 683 | i += 1; 684 | } 685 | 686 | !in_comment 687 | } 688 | 689 | fn consume_pending_line_info(&mut self) { 690 | if let Some(line_info) = self.pending_line_info.take() { 691 | if let Some(file) = line_info.0 { 692 | self.file = file; 693 | } 694 | 695 | self.line = line_info.1; 696 | 697 | if let Some(column) = line_info.2 { 698 | self.column = column; 699 | } 700 | 701 | self.hide_column = line_info.3; 702 | } 703 | } 704 | 705 | fn peek(&mut self) -> Option { 706 | self.chars.peek().copied() 707 | } 708 | 709 | fn next(&mut self) { 710 | self.offset += self.current_char_len; 711 | self.column += self.current_char_len; 712 | let last_char = self.current_char; 713 | 714 | self.current_char = self.chars.next(); 715 | if let Some(c) = self.current_char { 716 | self.current_char_len = c.len_utf8(); 717 | if matches!(last_char, Some('\n')) { 718 | self.line += 1; 719 | self.column = 1; 720 | self.consume_pending_line_info(); 721 | } 722 | } else { 723 | self.current_char_len = 0 724 | } 725 | 726 | log::trace!( 727 | "self.current_char={:?} offset={} line={} column={}", 728 | self.current_char, 729 | self.offset, 730 | self.line, 731 | self.column, 732 | ); 733 | } 734 | 735 | const fn position(&self) -> Position<'a> { 736 | Position { 737 | directory: self.directory, 738 | file: self.file, 739 | offset: self.start_offset, 740 | line: self.start_line, 741 | column: if self.hide_column { 742 | 0 743 | } else { 744 | self.start_column 745 | }, 746 | } 747 | } 748 | 749 | fn reset_start(&mut self) { 750 | self.start_offset = self.offset; 751 | self.start_line = self.line; 752 | self.start_column = self.column; 753 | } 754 | 755 | fn literal(&self) -> &'a str { 756 | &self.buffer[self.start_offset..self.offset] 757 | } 758 | 759 | fn require_escaped_char(&mut self) -> Result<()> { 760 | self.next(); 761 | 762 | let c = self 763 | .current_char 764 | .ok_or(ScannerError::UnterminatedEscapedChar)?; 765 | 766 | // TODO: move this to the match when const generics can be referenced in patterns 767 | if c == DELIM { 768 | self.next(); 769 | return Ok(()); 770 | } 771 | 772 | match c { 773 | 'a' | 'b' | 'f' | 'n' | 'r' | 't' | 'v' | '\\' => self.next(), 774 | 'x' => { 775 | self.next(); 776 | self.require_hex_digits::<2>()? 777 | } 778 | 'u' => { 779 | self.next(); 780 | self.require_hex_digits::<4>()?; 781 | } 782 | 'U' => { 783 | self.next(); 784 | self.require_hex_digits::<8>()?; 785 | } 786 | '0'..='7' => self.require_octal_digits::<3>()?, 787 | _ => return Err(ScannerError::UnterminatedEscapedChar), 788 | } 789 | 790 | Ok(()) 791 | } 792 | 793 | fn require_octal_digits(&mut self) -> Result<()> { 794 | for _ in 0..COUNT { 795 | let c = self.current_char.ok_or(ScannerError::OctalNotFound)?; 796 | 797 | if !is_octal_digit(c) { 798 | return Err(ScannerError::OctalNotFound); 799 | } 800 | 801 | self.next(); 802 | } 803 | 804 | Ok(()) 805 | } 806 | 807 | fn require_hex_digits(&mut self) -> Result<()> { 808 | for _ in 0..COUNT { 809 | let c = self.current_char.ok_or(ScannerError::HexadecimalNotFound)?; 810 | 811 | if !is_hex_digit(c) { 812 | return Err(ScannerError::HexadecimalNotFound); 813 | } 814 | 815 | self.next(); 816 | } 817 | 818 | Ok(()) 819 | } 820 | } 821 | 822 | impl<'a> IntoIterator for Scanner<'a> { 823 | type Item = Result>; 824 | type IntoIter = IntoIter<'a>; 825 | 826 | fn into_iter(self) -> Self::IntoIter { 827 | Self::IntoIter::new(self) 828 | } 829 | } 830 | 831 | pub struct IntoIter<'a> { 832 | scanner: Scanner<'a>, 833 | done: bool, 834 | } 835 | 836 | impl<'a> IntoIter<'a> { 837 | const fn new(scanner: Scanner<'a>) -> Self { 838 | Self { 839 | scanner, 840 | done: false, 841 | } 842 | } 843 | } 844 | 845 | impl<'a> Iterator for IntoIter<'a> { 846 | type Item = Result>; 847 | 848 | fn next(&mut self) -> Option { 849 | if self.done { 850 | return None; 851 | } 852 | 853 | match self.scanner.scan() { 854 | Ok((pos, tok, lit)) => { 855 | if tok == Token::EOF { 856 | self.done = true; 857 | } 858 | Some(Ok((pos, tok, lit))) 859 | } 860 | Err(err) => { 861 | self.done = true; 862 | Some(Err(err)) 863 | } 864 | } 865 | } 866 | } 867 | 868 | // https://golang.org/ref/spec#Letters_and_digits 869 | 870 | fn is_letter(c: char) -> bool { 871 | c == '_' || is_unicode_letter(c) 872 | } 873 | 874 | //const fn is_decimal_digit(c: char) -> bool { 875 | //matches!(c, '0'..='9') 876 | //} 877 | 878 | //const fn is_binary_digit(c: char) -> bool { 879 | //matches!(c, '0'..='1') 880 | //} 881 | 882 | const fn is_octal_digit(c: char) -> bool { 883 | matches!(c, '0'..='7') 884 | } 885 | 886 | const fn is_hex_digit(c: char) -> bool { 887 | matches!(c, '0'..='9' | 'A'..='F' | 'a'..='f') 888 | } 889 | 890 | // https://golang.org/ref/spec#Characters 891 | 892 | const fn is_newline(c: char) -> bool { 893 | c == '\n' 894 | } 895 | 896 | //const fn is_unicode_char(c: char) -> bool { 897 | //c != '\n' 898 | //} 899 | 900 | fn is_unicode_letter(c: char) -> bool { 901 | matches!( 902 | get_general_category(c), 903 | GeneralCategory::UppercaseLetter 904 | | GeneralCategory::LowercaseLetter 905 | | GeneralCategory::TitlecaseLetter 906 | | GeneralCategory::ModifierLetter 907 | | GeneralCategory::OtherLetter 908 | ) 909 | } 910 | 911 | fn is_unicode_digit(c: char) -> bool { 912 | get_general_category(c) == GeneralCategory::DecimalNumber 913 | } 914 | 915 | // https://golang.org/ref/spec#Keywords 916 | 917 | static KEYWORDS: Map<&'static str, Token> = phf_map! { 918 | "break" => Token::BREAK, 919 | "case" => Token::CASE, 920 | "chan" => Token::CHAN, 921 | "const" => Token::CONST, 922 | "continue" => Token::CONTINUE, 923 | 924 | "default" => Token::DEFAULT, 925 | "defer" => Token::DEFER, 926 | "else" => Token::ELSE, 927 | "fallthrough" => Token::FALLTHROUGH, 928 | "for" => Token::FOR, 929 | 930 | "func" => Token::FUNC, 931 | "go" => Token::GO, 932 | "goto" => Token::GOTO, 933 | "if" => Token::IF, 934 | "import" => Token::IMPORT, 935 | 936 | "interface" => Token::INTERFACE, 937 | "map" => Token::MAP, 938 | "package" => Token::PACKAGE, 939 | "range" => Token::RANGE, 940 | "return" => Token::RETURN, 941 | 942 | "select" => Token::SELECT, 943 | "struct" => Token::STRUCT, 944 | "switch" => Token::SWITCH, 945 | "type" => Token::TYPE, 946 | "var" => Token::VAR, 947 | }; 948 | 949 | #[cfg(test)] 950 | mod tests { 951 | use super::Scanner; 952 | 953 | #[test] // fuzz 954 | fn it_should_return_an_error_on_missing_line_number() { 955 | let input = "/*line :*/"; 956 | let mut out: Vec<_> = Scanner::new(file!(), input).into_iter().collect(); 957 | assert!(out.pop().unwrap().is_err()); 958 | } 959 | } 960 | --------------------------------------------------------------------------------