├── rust-toolchain.toml ├── src ├── wasm │ ├── mod.rs │ └── std │ │ └── mod.rs ├── lib.rs ├── parser │ ├── LICENSE │ ├── map.rs │ ├── scope.rs │ ├── errors.rs │ ├── objects.rs │ ├── position.rs │ ├── visitor.rs │ └── token.rs ├── bindings │ └── mod.rs └── main.rs ├── .gitignore ├── Taskfile.yaml ├── examples └── parser_simple.rs ├── Cargo.toml ├── .github └── workflows │ └── test.yml ├── LICENSE ├── go_examples ├── calculator.go ├── strings.go ├── structs.go ├── arrays.go ├── algorithms.go ├── switch.go └── combined.go ├── tests ├── switch_tests.rs ├── string_tests.rs ├── arrays_slices_tests.rs ├── examples_tests.rs └── struct_fields_tests.rs └── README.md /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = [ "rustfmt", "clippy" ] 4 | targets = [ "wasm32-unknown-unknown" ] 5 | -------------------------------------------------------------------------------- /src/wasm/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2025-present Raphael Amorim. All rights reserved. 2 | // Use of this source code is governed by a BSD-3-Clause 3 | // license that can be found in the LICENSE file. 4 | pub mod compiler; 5 | pub mod std; 6 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2025-present Raphael Amorim. All rights reserved. 2 | // Use of this source code is governed by a BSD-3-Clause 3 | // license that can be found in the LICENSE file. 4 | 5 | pub mod bindings; 6 | pub mod parser; 7 | pub mod wasm; 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Test binary, built with `go test -c` 2 | *.test 3 | 4 | # Output of the go coverage tool, specifically when used with LiteIDE 5 | *.out 6 | 7 | # Go workspace file 8 | go.work 9 | go.work.sum 10 | 11 | target 12 | 13 | 14 | # Added by cargo 15 | /target 16 | 17 | # MacOS invisible file 18 | .DS_Store 19 | 20 | # WebAssembly files 21 | *.wasm -------------------------------------------------------------------------------- /Taskfile.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | tasks: 4 | default: 5 | cmds: 6 | - task: lint 7 | - task: test 8 | 9 | run: 10 | cmds: 11 | - cargo run --release 12 | 13 | dev: 14 | cmds: 15 | - cargo run 16 | 17 | lint: 18 | cmds: 19 | - cargo fmt -- --check --color always 20 | - cargo clippy --all-targets --all-features -- -D warnings 21 | 22 | test: 23 | cmds: 24 | - cargo test 25 | - cargo test --test target_wasm_tests 26 | - cargo test --test arrays_slices_tests 27 | -------------------------------------------------------------------------------- /examples/parser_simple.rs: -------------------------------------------------------------------------------- 1 | use goiaba::parser::parse_str; 2 | 3 | fn main() { 4 | let source = r#" 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "strings" 10 | ) 11 | 12 | func main() { 13 | greeting := "Hello, Go!" 14 | fmt.Println(greeting) 15 | 16 | x := 42 17 | if x > 40 { 18 | fmt.Println("Greater than 40") 19 | } else { 20 | fmt.Println("Not greater than 40") 21 | } 22 | 23 | for i := 0; i < 5; i++ { 24 | fmt.Println(i) 25 | } 26 | } 27 | "#; 28 | 29 | match parse_str(source) { 30 | Ok(_f) => println!("Successfully parsed file"), 31 | Err(err) => println!("Error parsing file: {}", err), 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "goiaba" 3 | version = "0.0.5" 4 | edition = "2024" 5 | repository = "https://github.com/raphamorim/goiaba" 6 | license = "BSD-3-Clause" 7 | description = "Experimental Go parser and compiler" 8 | 9 | [[bin]] 10 | name = "goiaba" 11 | path = "src/main.rs" 12 | 13 | [lib] 14 | crate-type = ["cdylib", "rlib"] 15 | name = "goiaba" 16 | path = "src/lib.rs" 17 | 18 | [features] 19 | default = [] 20 | btree_map = [] 21 | 22 | [dependencies] 23 | wasm-encoder = "0.239.0" 24 | clap = { version = "4.4", features = ["derive"] } 25 | 26 | [dev-dependencies] 27 | pretty_assertions = "1.4.1" 28 | wasmtime = "37.0.2" 29 | tempfile = "3.23.0" 30 | wasmparser = "0.220.0" 31 | wat = "1.0" 32 | 33 | [profile.bench] 34 | # It makes little sense to run cross-crate benchmarks w/o lto 35 | # (difference is significant) 36 | lto = true 37 | 38 | [profile.release] 39 | opt-level = 3 40 | lto = "thin" 41 | strip = true 42 | debug = 0 43 | panic = 'abort' 44 | codegen-units = 1 45 | incremental = false 46 | 47 | [profile.dev] 48 | split-debuginfo = "unpacked" 49 | lto = false 50 | incremental = true 51 | opt-level = 0 -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | paths: 6 | - "**/*.rs" 7 | - "**/Cargo.toml" 8 | - "**/Cargo.lock" 9 | - ".github/workflows/test.yml" 10 | pull_request: 11 | paths: 12 | - "**/*.rs" 13 | - "**/Cargo.toml" 14 | - "**/Cargo.lock" 15 | - ".github/workflows/test.yml" 16 | 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 19 | cancel-in-progress: true 20 | 21 | env: 22 | CARGO_TERM_COLOR: always 23 | RUST_BACKTRACE: full 24 | 25 | jobs: 26 | test-native: 27 | strategy: 28 | matrix: 29 | os: [windows-latest, macos-latest, ubuntu-latest] 30 | 31 | runs-on: ${{ matrix.os }} 32 | steps: 33 | - uses: actions/checkout@v3 34 | - uses: Swatinem/rust-cache@v2 35 | - run: rustup toolchain install stable --profile minimal 36 | - run: rustup component add rustfmt clippy 37 | - run: cargo fetch 38 | - run: cargo fmt -- --check --color always 39 | - run: cargo clippy --all-targets --all-features -- --deny=warnings 40 | - run: cargo test 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | Copyright (c) 2025-present Raphael Amorim 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /src/parser/LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | Copyright (c) 2022 Goscript authors 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /go_examples/calculator.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //export add 4 | func add(x int, y int) int { 5 | return x + y 6 | } 7 | 8 | //export multiply 9 | func multiply(a int, b int) int { 10 | return a * b 11 | } 12 | 13 | //export factorial 14 | func factorial(n int) int { 15 | if n <= 1 { 16 | return 1 17 | } 18 | return n * factorial(n-1) 19 | } 20 | 21 | //export max 22 | func max(a int, b int) int { 23 | if a >= b { 24 | return a 25 | } else { 26 | return b 27 | } 28 | } 29 | 30 | //export abs 31 | func abs(x int) int { 32 | if x < 0 { 33 | return -x 34 | } 35 | return x 36 | } 37 | 38 | //export sign 39 | func sign(x int) int { 40 | if x > 0 { 41 | return 1 42 | } else if x < 0 { 43 | return -1 44 | } else { 45 | return 0 46 | } 47 | } 48 | 49 | //export sum_to_n 50 | func sum_to_n(n int) int { 51 | sum := 0 52 | for i := 0; i < n; i++ { 53 | sum = sum + i 54 | } 55 | return sum 56 | } 57 | 58 | //export fibonacci 59 | func fibonacci(n int) int { 60 | if n <= 1 { 61 | return n 62 | } 63 | return fibonacci(n-1) + fibonacci(n-2) 64 | } 65 | 66 | //export power 67 | func power(base int, exp int) int { 68 | if exp == 0 { 69 | return 1 70 | } 71 | if exp == 1 { 72 | return base 73 | } 74 | 75 | result := 1 76 | for i := 0; i < exp; i++ { 77 | result = result * base 78 | } 79 | return result 80 | } 81 | 82 | //export bitwise_and 83 | func bitwise_and(a int, b int) int { 84 | return a & b 85 | } 86 | 87 | //export bitwise_or 88 | func bitwise_or(a int, b int) int { 89 | return a | b 90 | } 91 | 92 | //export bitwise_xor 93 | func bitwise_xor(a int, b int) int { 94 | return a ^ b 95 | } 96 | 97 | //export left_shift 98 | func left_shift(a int, b int) int { 99 | return a << b 100 | } 101 | 102 | //export right_shift 103 | func right_shift(a int, b int) int { 104 | return a >> b 105 | } -------------------------------------------------------------------------------- /src/wasm/std/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2025-present Raphael Amorim. All rights reserved. 2 | // Use of this source code is governed by a BSD-3-Clause 3 | // license that can be found in the LICENSE file. 4 | 5 | use wasm_encoder::ValType; 6 | 7 | /// Represents a stdlib function import 8 | pub struct StdlibImport { 9 | pub module: String, 10 | pub name: String, 11 | pub params: Vec, 12 | pub results: Vec, 13 | } 14 | 15 | /// Check if a function call is a supported stdlib function 16 | /// Returns the import details if supported, None otherwise 17 | pub fn get_stdlib_import(pkg: &str, func: &str) -> Option { 18 | match (pkg, func) { 19 | ("math", "Sqrt") => Some(StdlibImport { 20 | module: "env".to_string(), 21 | name: "sqrt".to_string(), 22 | params: vec![ValType::F64], 23 | results: vec![ValType::F64], 24 | }), 25 | ("strings", "Len") => Some(StdlibImport { 26 | module: "env".to_string(), 27 | name: "strings_len".to_string(), 28 | params: vec![ValType::I32, ValType::I32], // string ptr, len 29 | results: vec![ValType::I32], // length 30 | }), 31 | ("strings", "Join") => Some(StdlibImport { 32 | module: "env".to_string(), 33 | name: "strings_join".to_string(), 34 | params: vec![ValType::I32, ValType::I32, ValType::I32, ValType::I32], // slice ptr, len, sep ptr, len 35 | results: vec![ValType::I32, ValType::I32], // result string ptr, len 36 | }), 37 | ("fmt", "Println") => Some(StdlibImport { 38 | module: "env".to_string(), 39 | name: "print".to_string(), 40 | params: vec![ValType::I32, ValType::I32], // string ptr, len 41 | results: vec![], 42 | }), 43 | // Add more stdlib functions here 44 | _ => None, 45 | } 46 | } 47 | 48 | /// Get the list of all supported stdlib packages 49 | pub fn get_supported_packages() -> Vec<&'static str> { 50 | vec!["math", "strings", "fmt"] 51 | } 52 | 53 | /// Check if a package is a supported stdlib package 54 | pub fn is_supported_package(pkg: &str) -> bool { 55 | get_supported_packages().contains(&pkg) 56 | } 57 | -------------------------------------------------------------------------------- /src/parser/map.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Goscript Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | use std::collections::HashMap; 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct Map { 9 | inner: HashMap, 10 | } 11 | 12 | impl Map 13 | where 14 | K: std::hash::Hash + Eq, 15 | { 16 | pub fn new() -> Map { 17 | Map { 18 | inner: HashMap::new(), 19 | } 20 | } 21 | 22 | pub fn with_capacity(capacity: usize) -> Map { 23 | Map { 24 | inner: HashMap::with_capacity(capacity), 25 | } 26 | } 27 | 28 | pub fn insert(&mut self, key: K, value: V) -> Option { 29 | self.inner.insert(key, value) 30 | } 31 | 32 | pub fn get(&self, key: &K) -> Option<&V> { 33 | self.inner.get(key) 34 | } 35 | 36 | pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { 37 | self.inner.get_mut(key) 38 | } 39 | 40 | pub fn remove(&mut self, key: &K) -> Option { 41 | self.inner.remove(key) 42 | } 43 | 44 | pub fn contains_key(&self, key: &K) -> bool { 45 | self.inner.contains_key(key) 46 | } 47 | 48 | pub fn len(&self) -> usize { 49 | self.inner.len() 50 | } 51 | 52 | pub fn is_empty(&self) -> bool { 53 | self.inner.is_empty() 54 | } 55 | 56 | pub fn clear(&mut self) { 57 | self.inner.clear(); 58 | } 59 | 60 | pub fn iter(&self) -> std::collections::hash_map::Iter<'_, K, V> { 61 | self.inner.iter() 62 | } 63 | 64 | pub fn iter_mut(&mut self) -> std::collections::hash_map::IterMut<'_, K, V> { 65 | self.inner.iter_mut() 66 | } 67 | } 68 | 69 | impl Default for Map 70 | where 71 | K: std::hash::Hash + Eq, 72 | { 73 | fn default() -> Self { 74 | Self::new() 75 | } 76 | } 77 | 78 | impl IntoIterator for Map { 79 | type Item = (K, V); 80 | type IntoIter = std::collections::hash_map::IntoIter; 81 | 82 | fn into_iter(self) -> Self::IntoIter { 83 | self.inner.into_iter() 84 | } 85 | } 86 | 87 | impl std::ops::Index<&K> for Map 88 | where 89 | K: std::hash::Hash + Eq, 90 | { 91 | type Output = V; 92 | 93 | fn index(&self, index: &K) -> &Self::Output { 94 | &self.inner[index] 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /go_examples/strings.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // String manipulation and operations 4 | 5 | //export string_length 6 | func string_length() int { 7 | s := "Hello, World!" 8 | return len(s) 9 | } 10 | 11 | //export empty_string_check 12 | func empty_string_check() int { 13 | s := "" 14 | if len(s) == 0 { 15 | return 1 16 | } 17 | return 0 18 | } 19 | 20 | //export compare_lengths 21 | func compare_lengths() int { 22 | s1 := "Go" 23 | s2 := "WebAssembly" 24 | 25 | len1 := len(s1) 26 | len2 := len(s2) 27 | 28 | if len1 > len2 { 29 | return 1 30 | } else if len1 < len2 { 31 | return -1 32 | } else { 33 | return 0 34 | } 35 | } 36 | 37 | //export total_chars 38 | func total_chars() int { 39 | s1 := "Hello" 40 | s2 := "World" 41 | s3 := "!" 42 | 43 | return len(s1) + len(s2) + len(s3) 44 | } 45 | 46 | //export is_long_string 47 | func is_long_string() int { 48 | s := "This is a long string for testing" 49 | if len(s) > 20 { 50 | return 1 51 | } 52 | return 0 53 | } 54 | 55 | //export count_all_chars 56 | func count_all_chars() int { 57 | messages := []int{ 58 | len("first"), 59 | len("second"), 60 | len("third"), 61 | len("fourth"), 62 | } 63 | 64 | total := 0 65 | for i := 0; i < 4; i++ { 66 | total = total + messages[i] 67 | } 68 | return total 69 | } 70 | 71 | //export validate_input 72 | func validate_input() int { 73 | input := "test123" 74 | minLength := 5 75 | maxLength := 10 76 | 77 | length := len(input) 78 | 79 | if length >= minLength && length <= maxLength { 80 | return 1 81 | } 82 | return 0 83 | } 84 | 85 | //export escape_sequences 86 | func escape_sequences() int { 87 | // Test escape sequence handling 88 | s := "line1\nline2\ttab" 89 | return len(s) 90 | } 91 | 92 | //export string_with_numbers 93 | func string_with_numbers() int { 94 | s := "12345678910" 95 | return len(s) 96 | } 97 | 98 | //export max_string_length 99 | func max_string_length() int { 100 | s1 := "short" 101 | s2 := "medium length" 102 | s3 := "very long string here" 103 | 104 | max := len(s1) 105 | 106 | if len(s2) > max { 107 | max = len(s2) 108 | } 109 | if len(s3) > max { 110 | max = len(s3) 111 | } 112 | 113 | return max 114 | } 115 | -------------------------------------------------------------------------------- /go_examples/structs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Struct examples demonstrating data structures 4 | 5 | type Point struct { 6 | x int 7 | y int 8 | } 9 | 10 | type Rectangle struct { 11 | width int 12 | height int 13 | } 14 | 15 | type Circle struct { 16 | radius int 17 | } 18 | 19 | type Person struct { 20 | age int 21 | height int 22 | } 23 | 24 | //export create_point 25 | func create_point(x int, y int) int { 26 | p := Point{x: x, y: y} 27 | return p.x + p.y 28 | } 29 | 30 | //export point_distance_squared 31 | func point_distance_squared(x1 int, y1 int, x2 int, y2 int) int { 32 | p1 := Point{x: x1, y: y1} 33 | p2 := Point{x: x2, y: y2} 34 | 35 | dx := p1.x - p2.x 36 | dy := p1.y - p2.y 37 | 38 | return dx*dx + dy*dy 39 | } 40 | 41 | //export rectangle_area 42 | func rectangle_area(width int, height int) int { 43 | rect := Rectangle{ 44 | width: width, 45 | height: height, 46 | } 47 | return rect.width * rect.height 48 | } 49 | 50 | //export rectangle_perimeter 51 | func rectangle_perimeter(width int, height int) int { 52 | rect := Rectangle{width: width, height: height} 53 | return 2 * (rect.width + rect.height) 54 | } 55 | 56 | //export circle_area_approx 57 | func circle_area_approx(radius int) int { 58 | c := Circle{radius: radius} 59 | // Approximate pi as 3 for integer math 60 | return 3 * c.radius * c.radius 61 | } 62 | 63 | //export update_point 64 | func update_point(x int, y int, dx int, dy int) int { 65 | p := Point{x: x, y: y} 66 | p.x = p.x + dx 67 | p.y = p.y + dy 68 | return p.x + p.y 69 | } 70 | 71 | //export compare_rectangles 72 | func compare_rectangles(w1 int, h1 int, w2 int, h2 int) int { 73 | rect1 := Rectangle{width: w1, height: h1} 74 | rect2 := Rectangle{width: w2, height: h2} 75 | 76 | area1 := rect1.width * rect1.height 77 | area2 := rect2.width * rect2.height 78 | 79 | if area1 > area2 { 80 | return 1 81 | } else if area1 < area2 { 82 | return -1 83 | } else { 84 | return 0 85 | } 86 | } 87 | 88 | //export point_quadrant 89 | func point_quadrant(x int, y int) int { 90 | p := Point{x: x, y: y} 91 | 92 | if p.x > 0 && p.y > 0 { 93 | return 1 94 | } else if p.x < 0 && p.y > 0 { 95 | return 2 96 | } else if p.x < 0 && p.y < 0 { 97 | return 3 98 | } else if p.x > 0 && p.y < 0 { 99 | return 4 100 | } 101 | return 0 102 | } 103 | 104 | //export scale_rectangle 105 | func scale_rectangle(w int, h int, factor int) int { 106 | rect := Rectangle{width: w, height: h} 107 | rect.width = rect.width * factor 108 | rect.height = rect.height * factor 109 | return rect.width + rect.height 110 | } 111 | 112 | //export person_is_adult 113 | func person_is_adult(age int) int { 114 | p := Person{age: age, height: 170} 115 | if p.age >= 18 { 116 | return 1 117 | } 118 | return 0 119 | } 120 | -------------------------------------------------------------------------------- /go_examples/arrays.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Array manipulation examples demonstrating various operations 4 | 5 | //export array_sum 6 | func array_sum() int { 7 | numbers := []int{10, 20, 30, 40, 50} 8 | total := 0 9 | for i := 0; i < 5; i++ { 10 | total = total + numbers[i] 11 | } 12 | return total 13 | } 14 | 15 | //export find_max 16 | func find_max() int { 17 | arr := []int{3, 7, 2, 9, 1, 12, 5} 18 | max := arr[0] 19 | for i := 1; i < 7; i++ { 20 | if arr[i] > max { 21 | max = arr[i] 22 | } 23 | } 24 | return max 25 | } 26 | 27 | //export find_min 28 | func find_min() int { 29 | arr := []int{42, 17, 99, 3, 56} 30 | min := arr[0] 31 | for i := 1; i < 5; i++ { 32 | if arr[i] < min { 33 | min = arr[i] 34 | } 35 | } 36 | return min 37 | } 38 | 39 | //export reverse_array 40 | func reverse_array() int { 41 | arr := []int{1, 2, 3, 4, 5} 42 | left := 0 43 | right := 4 44 | 45 | for left < right { 46 | temp := arr[left] 47 | arr[left] = arr[right] 48 | arr[right] = temp 49 | left++ 50 | right-- 51 | } 52 | 53 | // Return first element after reversal (should be 5) 54 | return arr[0] 55 | } 56 | 57 | //export linear_search 58 | func linear_search(target int) int { 59 | arr := []int{10, 25, 30, 45, 50, 75, 80} 60 | for i := 0; i < 7; i++ { 61 | if arr[i] == target { 62 | return i 63 | } 64 | } 65 | return -1 66 | } 67 | 68 | //export count_even 69 | func count_even() int { 70 | arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 71 | count := 0 72 | for i := 0; i < 10; i++ { 73 | if arr[i] % 2 == 0 { 74 | count++ 75 | } 76 | } 77 | return count 78 | } 79 | 80 | //export bubble_sort_single_pass 81 | func bubble_sort_single_pass() int { 82 | arr := []int{5, 2, 8, 1, 9} 83 | 84 | // Single pass of bubble sort 85 | for i := 0; i < 4; i++ { 86 | if arr[i] > arr[i+1] { 87 | temp := arr[i] 88 | arr[i] = arr[i+1] 89 | arr[i+1] = temp 90 | } 91 | } 92 | 93 | // Return first element 94 | return arr[0] 95 | } 96 | 97 | //export array_product 98 | func array_product() int { 99 | arr := []int{2, 3, 4, 5} 100 | product := 1 101 | for i := 0; i < 4; i++ { 102 | product = product * arr[i] 103 | } 104 | return product 105 | } 106 | 107 | //export count_positive 108 | func count_positive() int { 109 | arr := []int{-1, 5, -3, 7, 0, 2, -8} 110 | count := 0 111 | for i := 0; i < 7; i++ { 112 | if arr[i] > 0 { 113 | count++ 114 | } 115 | } 116 | return count 117 | } 118 | 119 | //export array_average 120 | func array_average() int { 121 | arr := []int{10, 20, 30, 40, 50} 122 | sum := 0 123 | for i := 0; i < 5; i++ { 124 | sum = sum + arr[i] 125 | } 126 | return sum / 5 127 | } 128 | -------------------------------------------------------------------------------- /src/parser/scope.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Goscript Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // 6 | // This code is adapted from the offical Go code written in Go 7 | // with license as follows: 8 | // Copyright 2013 The Go Authors. All rights reserved. 9 | // Use of this source code is governed by a BSD-style 10 | // license that can be found in the LICENSE file. 11 | 12 | use super::ast::Node; 13 | use super::map::Map; 14 | use super::objects::*; 15 | use super::position; 16 | use std::fmt; 17 | 18 | #[derive(Debug, Clone)] 19 | pub enum EntityKind { 20 | Bad, 21 | Pkg, 22 | Con, 23 | Typ, 24 | Var, 25 | Fun, 26 | Lbl, 27 | } 28 | 29 | impl EntityKind { 30 | pub fn kind_text(&self) -> &str { 31 | match self { 32 | EntityKind::Bad => "bad", 33 | EntityKind::Pkg => "package", 34 | EntityKind::Con => "const", 35 | EntityKind::Typ => "type", 36 | EntityKind::Var => "var", 37 | EntityKind::Fun => "func", 38 | EntityKind::Lbl => "label", 39 | } 40 | } 41 | } 42 | 43 | #[derive(Debug, Clone)] 44 | pub enum DeclObj { 45 | Field(FieldKey), 46 | Spec(SpecKey), 47 | FuncDecl(FuncDeclKey), 48 | LabeledStmt(LabeledStmtKey), 49 | AssignStmt(AssignStmtKey), 50 | NoDecl, 51 | } 52 | 53 | #[derive(Debug, Clone)] 54 | pub enum EntityData { 55 | PkgScope(ScopeKey), 56 | ConIota(isize), 57 | NoData, 58 | } 59 | 60 | // An Entity describes a named language entity such as a package, 61 | // constant, type, variable, function (incl. methods), or label. 62 | #[derive(Debug, Clone)] 63 | pub struct Entity { 64 | pub kind: EntityKind, 65 | pub name: String, 66 | pub decl: DeclObj, 67 | pub data: EntityData, 68 | } 69 | 70 | impl Entity { 71 | pub fn new(kind: EntityKind, name: String, decl: DeclObj, data: EntityData) -> Entity { 72 | Entity { 73 | kind, 74 | name, 75 | decl, 76 | data, 77 | } 78 | } 79 | 80 | pub fn with_no_data(kind: EntityKind, name: String, decl: DeclObj) -> Entity { 81 | Entity::new(kind, name, decl, EntityData::NoData) 82 | } 83 | 84 | pub fn pos(&self, objs: &AstObjects) -> position::Pos { 85 | match &self.decl { 86 | DeclObj::Field(i) => i.pos(objs), 87 | DeclObj::Spec(i) => objs.specs[*i].pos(objs), 88 | DeclObj::FuncDecl(i) => objs.fdecls[*i].pos(objs), 89 | DeclObj::LabeledStmt(i) => objs.l_stmts[*i].pos(objs), 90 | DeclObj::AssignStmt(i) => objs.a_stmts[*i].pos(objs), 91 | DeclObj::NoDecl => 0, 92 | } 93 | } 94 | } 95 | 96 | pub struct Scope { 97 | pub outer: Option, 98 | pub entities: Map, 99 | } 100 | 101 | impl Scope { 102 | pub fn new(outer: Option) -> Scope { 103 | Scope { 104 | outer, 105 | entities: Map::new(), 106 | } 107 | } 108 | 109 | pub fn look_up(&self, name: &String) -> Option<&EntityKey> { 110 | self.entities.get(name) 111 | } 112 | 113 | pub fn insert(&mut self, name: String, entity: EntityKey) -> Option { 114 | self.entities.insert(name, entity) 115 | } 116 | 117 | pub fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 118 | writeln!(f, "scope {:p} {{", self)?; 119 | for (k, _) in self.entities.iter() { 120 | writeln!(f, "\t{}", k)?; 121 | } 122 | writeln!(f, "}}") 123 | } 124 | } 125 | 126 | #[cfg(test)] 127 | mod test {} 128 | -------------------------------------------------------------------------------- /src/parser/errors.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Goscript Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // 6 | // This code is adapted from the offical Go code written in Go 7 | // with license as follows: 8 | // Copyright 2013 The Go Authors. All rights reserved. 9 | // Use of this source code is governed by a BSD-style 10 | // license that can be found in the LICENSE file. 11 | 12 | use super::position::{File, FilePos, Pos}; 13 | use std::cell::{Ref, RefCell}; 14 | use std::fmt; 15 | use std::rc::Rc; 16 | 17 | #[derive(Clone, Debug)] 18 | pub struct Error { 19 | pub pos: FilePos, 20 | pub msg: String, 21 | pub soft: bool, 22 | pub by_parser: bool, // reported by parser (not type checker) 23 | order: usize, // display order 24 | } 25 | 26 | impl fmt::Display for Error { 27 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 28 | let p = if self.by_parser { "[Parser]" } else { "[TC]" }; 29 | writeln!(f, "{} {} {}", p, self.pos, self.msg)?; 30 | Ok(()) 31 | } 32 | } 33 | 34 | impl std::error::Error for Error {} 35 | 36 | #[derive(Clone, Debug, Default)] 37 | pub struct ErrorList { 38 | errors: Rc>>, 39 | } 40 | 41 | impl fmt::Display for ErrorList { 42 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 43 | writeln!(f, "Result: {} errors", self.errors.borrow().len())?; 44 | for e in self.errors.borrow().iter() { 45 | e.fmt(f)?; 46 | } 47 | Ok(()) 48 | } 49 | } 50 | 51 | impl std::error::Error for ErrorList {} 52 | 53 | impl ErrorList { 54 | pub fn new() -> ErrorList { 55 | ErrorList { 56 | errors: Rc::new(RefCell::new(vec![])), 57 | } 58 | } 59 | 60 | pub fn add(&self, p: Option, msg: String, soft: bool, by_parser: bool) { 61 | let fp = p.unwrap_or(FilePos::null()); 62 | let order = if msg.starts_with('\t') { 63 | self.errors 64 | .borrow() 65 | .iter() 66 | .rev() 67 | .find(|x| !x.msg.starts_with('\t')) 68 | .unwrap() 69 | .pos 70 | .offset 71 | } else { 72 | fp.offset 73 | }; 74 | self.errors.borrow_mut().push(Error { 75 | pos: fp, 76 | msg, 77 | soft, 78 | by_parser, 79 | order, 80 | }); 81 | } 82 | 83 | pub fn len(&self) -> usize { 84 | self.errors.borrow().len() 85 | } 86 | 87 | pub fn is_empty(&self) -> bool { 88 | self.errors.borrow().is_empty() 89 | } 90 | 91 | pub fn sort(&self) { 92 | self.errors.borrow_mut().sort_by_key(|e| e.order); 93 | } 94 | 95 | pub fn borrow(&self) -> Ref<'_, Vec> { 96 | self.errors.borrow() 97 | } 98 | } 99 | 100 | #[derive(Clone, Debug)] 101 | pub struct FilePosErrors<'a> { 102 | file: &'a File, 103 | elist: &'a ErrorList, 104 | } 105 | 106 | impl<'a> FilePosErrors<'a> { 107 | pub fn new(file: &'a File, elist: &'a ErrorList) -> FilePosErrors<'a> { 108 | FilePosErrors { file, elist } 109 | } 110 | 111 | pub fn add(&self, pos: Pos, msg: String, soft: bool) { 112 | let p = self.file.position(pos); 113 | self.elist.add(Some(p), msg, soft, false); 114 | } 115 | 116 | pub fn add_str(&self, pos: Pos, s: &str, soft: bool) { 117 | self.add(pos, s.to_string(), soft); 118 | } 119 | 120 | pub fn parser_add(&self, pos: Pos, msg: String) { 121 | let p = self.file.position(pos); 122 | self.elist.add(Some(p), msg, false, true); 123 | } 124 | 125 | pub fn parser_add_str(&self, pos: Pos, s: &str) { 126 | self.parser_add(pos, s.to_string()); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /go_examples/algorithms.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Advanced algorithms and problem-solving examples 4 | 5 | //export is_prime 6 | func is_prime(n int) int { 7 | if n <= 1 { 8 | return 0 9 | } 10 | if n <= 3 { 11 | return 1 12 | } 13 | if n%2 == 0 || n%3 == 0 { 14 | return 0 15 | } 16 | 17 | i := 5 18 | for i*i <= n { 19 | if n%i == 0 || n%(i+2) == 0 { 20 | return 0 21 | } 22 | i = i + 6 23 | } 24 | return 1 25 | } 26 | 27 | //export gcd 28 | func gcd(a int, b int) int { 29 | for b != 0 { 30 | temp := b 31 | b = a % b 32 | a = temp 33 | } 34 | return a 35 | } 36 | 37 | //export lcm 38 | func lcm(a int, b int) int { 39 | if a == 0 || b == 0 { 40 | return 0 41 | } 42 | return (a * b) / gcd(a, b) 43 | } 44 | 45 | //export is_palindrome_number 46 | func is_palindrome_number(n int) int { 47 | if n < 0 { 48 | return 0 49 | } 50 | 51 | original := n 52 | reversed := 0 53 | 54 | for n > 0 { 55 | digit := n % 10 56 | reversed = reversed*10 + digit 57 | n = n / 10 58 | } 59 | 60 | if original == reversed { 61 | return 1 62 | } 63 | return 0 64 | } 65 | 66 | //export sum_of_digits 67 | func sum_of_digits(n int) int { 68 | sum := 0 69 | if n < 0 { 70 | n = -n 71 | } 72 | 73 | for n > 0 { 74 | sum = sum + (n % 10) 75 | n = n / 10 76 | } 77 | return sum 78 | } 79 | 80 | //export count_digits 81 | func count_digits(n int) int { 82 | if n == 0 { 83 | return 1 84 | } 85 | 86 | count := 0 87 | if n < 0 { 88 | n = -n 89 | } 90 | 91 | for n > 0 { 92 | count++ 93 | n = n / 10 94 | } 95 | return count 96 | } 97 | 98 | //export reverse_number 99 | func reverse_number(n int) int { 100 | reversed := 0 101 | negative := 0 102 | 103 | if n < 0 { 104 | negative = 1 105 | n = -n 106 | } 107 | 108 | for n > 0 { 109 | digit := n % 10 110 | reversed = reversed*10 + digit 111 | n = n / 10 112 | } 113 | 114 | if negative == 1 { 115 | reversed = -reversed 116 | } 117 | 118 | return reversed 119 | } 120 | 121 | //export binary_search 122 | func binary_search(target int) int { 123 | arr := []int{1, 3, 5, 7, 9, 11, 13, 15, 17, 19} 124 | left := 0 125 | right := 9 126 | 127 | for left <= right { 128 | mid := (left + right) / 2 129 | 130 | if arr[mid] == target { 131 | return mid 132 | } else if arr[mid] < target { 133 | left = mid + 1 134 | } else { 135 | right = mid - 1 136 | } 137 | } 138 | 139 | return -1 140 | } 141 | 142 | //export nth_triangular 143 | func nth_triangular(n int) int { 144 | return (n * (n + 1)) / 2 145 | } 146 | 147 | //export collatz_steps 148 | func collatz_steps(n int) int { 149 | steps := 0 150 | 151 | for n != 1 { 152 | if n%2 == 0 { 153 | n = n / 2 154 | } else { 155 | n = 3*n + 1 156 | } 157 | steps++ 158 | 159 | if steps > 1000 { 160 | return -1 161 | } 162 | } 163 | 164 | return steps 165 | } 166 | 167 | //export perfect_number_check 168 | func perfect_number_check(n int) int { 169 | if n <= 0 { 170 | return 0 171 | } 172 | 173 | sum := 0 174 | for i := 1; i < n; i++ { 175 | if n%i == 0 { 176 | sum = sum + i 177 | } 178 | } 179 | 180 | if sum == n { 181 | return 1 182 | } 183 | return 0 184 | } 185 | 186 | //export sum_of_squares 187 | func sum_of_squares(n int) int { 188 | sum := 0 189 | for i := 1; i <= n; i++ { 190 | sum = sum + (i * i) 191 | } 192 | return sum 193 | } 194 | -------------------------------------------------------------------------------- /go_examples/switch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Switch statement examples for control flow 4 | 5 | //export day_of_week 6 | func day_of_week(day int) int { 7 | switch day { 8 | case 1: 9 | return 100 // Monday 10 | case 2: 11 | return 200 // Tuesday 12 | case 3: 13 | return 300 // Wednesday 14 | case 4: 15 | return 400 // Thursday 16 | case 5: 17 | return 500 // Friday 18 | case 6: 19 | return 600 // Saturday 20 | case 7: 21 | return 700 // Sunday 22 | default: 23 | return 0 24 | } 25 | } 26 | 27 | //export is_weekday 28 | func is_weekday(day int) int { 29 | switch day { 30 | case 1, 2, 3, 4, 5: 31 | return 1 32 | case 6, 7: 33 | return 0 34 | default: 35 | return -1 36 | } 37 | } 38 | 39 | //export grade_to_gpa 40 | func grade_to_gpa(grade int) int { 41 | switch grade { 42 | case 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100: 43 | return 4 44 | case 80, 81, 82, 83, 84, 85, 86, 87, 88, 89: 45 | return 3 46 | case 70, 71, 72, 73, 74, 75, 76, 77, 78, 79: 47 | return 2 48 | case 60, 61, 62, 63, 64, 65, 66, 67, 68, 69: 49 | return 1 50 | default: 51 | return 0 52 | } 53 | } 54 | 55 | //export season_from_month 56 | func season_from_month(month int) int { 57 | switch month { 58 | case 12, 1, 2: 59 | return 1 // Winter 60 | case 3, 4, 5: 61 | return 2 // Spring 62 | case 6, 7, 8: 63 | return 3 // Summer 64 | case 9, 10, 11: 65 | return 4 // Fall 66 | default: 67 | return 0 68 | } 69 | } 70 | 71 | //export calculator_switch 72 | func calculator_switch(op int, a int, b int) int { 73 | switch op { 74 | case 1: 75 | return a + b 76 | case 2: 77 | return a - b 78 | case 3: 79 | return a * b 80 | case 4: 81 | if b != 0 { 82 | return a / b 83 | } 84 | return 0 85 | default: 86 | return 0 87 | } 88 | } 89 | 90 | //export http_status_category 91 | func http_status_category(status int) int { 92 | switch status { 93 | case 200, 201, 204: 94 | return 1 // Success 95 | case 400, 401, 403, 404: 96 | return 2 // Client error 97 | case 500, 502, 503: 98 | return 3 // Server error 99 | default: 100 | return 0 101 | } 102 | } 103 | 104 | //export color_code 105 | func color_code(color int) int { 106 | switch color { 107 | case 1: 108 | return 255 // Red 109 | case 2: 110 | return 65280 // Green 111 | case 3: 112 | return 255 // Blue 113 | default: 114 | return 0 115 | } 116 | } 117 | 118 | //export priority_level 119 | func priority_level(urgency int, importance int) int { 120 | switch urgency { 121 | case 1: 122 | switch importance { 123 | case 1: 124 | return 4 // Critical 125 | case 2: 126 | return 3 // High 127 | default: 128 | return 2 129 | } 130 | case 2: 131 | switch importance { 132 | case 1: 133 | return 3 134 | case 2: 135 | return 2 136 | default: 137 | return 1 138 | } 139 | default: 140 | return 1 // Low 141 | } 142 | } 143 | 144 | //export traffic_light 145 | func traffic_light(signal int) int { 146 | switch signal { 147 | case 1: 148 | return 10 // Red - stop 149 | case 2: 150 | return 20 // Yellow - slow 151 | case 3: 152 | return 30 // Green - go 153 | default: 154 | return 0 155 | } 156 | } 157 | 158 | //export month_days 159 | func month_days(month int) int { 160 | switch month { 161 | case 1, 3, 5, 7, 8, 10, 12: 162 | return 31 163 | case 4, 6, 9, 11: 164 | return 30 165 | case 2: 166 | return 28 167 | default: 168 | return 0 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /go_examples/combined.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Combined examples demonstrating multiple features together 4 | 5 | type Stats struct { 6 | min int 7 | max int 8 | sum int 9 | avg int 10 | } 11 | 12 | //export analyze_array 13 | func analyze_array() int { 14 | numbers := []int{5, 12, 3, 18, 7, 9} 15 | 16 | stats := Stats{ 17 | min: numbers[0], 18 | max: numbers[0], 19 | sum: 0, 20 | avg: 0, 21 | } 22 | 23 | for i := 0; i < 6; i++ { 24 | if numbers[i] < stats.min { 25 | stats.min = numbers[i] 26 | } 27 | if numbers[i] > stats.max { 28 | stats.max = numbers[i] 29 | } 30 | stats.sum = stats.sum + numbers[i] 31 | } 32 | 33 | stats.avg = stats.sum / 6 34 | 35 | return stats.min + stats.max + stats.avg 36 | } 37 | 38 | //export grade_calculator 39 | func grade_calculator(score int) int { 40 | switch { 41 | case score >= 90: 42 | return 4 // A 43 | case score >= 80: 44 | return 3 // B 45 | case score >= 70: 46 | return 2 // C 47 | case score >= 60: 48 | return 1 // D 49 | default: 50 | return 0 // F 51 | } 52 | } 53 | 54 | //export validate_and_process 55 | func validate_and_process(value int) int { 56 | // Validate input 57 | if value < 0 { 58 | return -1 59 | } 60 | 61 | // Process based on range 62 | switch { 63 | case value < 10: 64 | return value * 10 65 | case value < 100: 66 | return value * 5 67 | case value < 1000: 68 | return value * 2 69 | default: 70 | return value 71 | } 72 | } 73 | 74 | //export filter_and_count 75 | func filter_and_count() int { 76 | data := []int{1, 5, 10, 15, 20, 25, 30, 35, 40} 77 | count := 0 78 | 79 | for i := 0; i < 9; i++ { 80 | switch { 81 | case data[i] < 10: 82 | count = count + 1 83 | case data[i] >= 20 && data[i] < 30: 84 | count = count + 2 85 | case data[i] >= 30: 86 | count = count + 3 87 | } 88 | } 89 | 90 | return count 91 | } 92 | 93 | //export string_category 94 | func string_category() int { 95 | s1 := "short" 96 | s2 := "medium length string" 97 | s3 := "very long string with many characters" 98 | 99 | len1 := len(s1) 100 | len2 := len(s2) 101 | len3 := len(s3) 102 | 103 | category := 0 104 | 105 | if len1 < 10 { 106 | category = category + 1 107 | } 108 | if len2 >= 10 && len2 < 30 { 109 | category = category + 10 110 | } 111 | if len3 >= 30 { 112 | category = category + 100 113 | } 114 | 115 | return category 116 | } 117 | 118 | //export complex_calculation 119 | func complex_calculation(x int, y int, operation int) int { 120 | result := 0 121 | 122 | switch operation { 123 | case 1: 124 | // Sum of squares 125 | result = (x * x) + (y * y) 126 | case 2: 127 | // Product of sum and difference 128 | result = (x + y) * (x - y) 129 | case 3: 130 | // Average of range 131 | if x < y { 132 | sum := 0 133 | for i := x; i <= y; i++ { 134 | sum = sum + i 135 | } 136 | result = sum / (y - x + 1) 137 | } 138 | default: 139 | result = x + y 140 | } 141 | 142 | return result 143 | } 144 | 145 | //export array_transform 146 | func array_transform(mode int) int { 147 | arr := []int{1, 2, 3, 4, 5} 148 | 149 | switch mode { 150 | case 1: 151 | // Double all elements 152 | for i := 0; i < 5; i++ { 153 | arr[i] = arr[i] * 2 154 | } 155 | case 2: 156 | // Square all elements 157 | for i := 0; i < 5; i++ { 158 | arr[i] = arr[i] * arr[i] 159 | } 160 | case 3: 161 | // Increment all elements 162 | for i := 0; i < 5; i++ { 163 | arr[i] = arr[i] + 10 164 | } 165 | } 166 | 167 | // Return sum 168 | sum := 0 169 | for i := 0; i < 5; i++ { 170 | sum = sum + arr[i] 171 | } 172 | return sum 173 | } 174 | 175 | //export point_distance_category 176 | func point_distance_category(x1 int, y1 int, x2 int, y2 int) int { 177 | type Point struct { 178 | x int 179 | y int 180 | } 181 | 182 | p1 := Point{x: x1, y: y1} 183 | p2 := Point{x: x2, y: y2} 184 | 185 | dx := p1.x - p2.x 186 | dy := p1.y - p2.y 187 | distanceSquared := dx*dx + dy*dy 188 | 189 | switch { 190 | case distanceSquared < 25: 191 | return 1 // Very close 192 | case distanceSquared < 100: 193 | return 2 // Close 194 | case distanceSquared < 400: 195 | return 3 // Medium 196 | default: 197 | return 4 // Far 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/parser/objects.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Goscript Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | use super::ast; 6 | use super::scope; 7 | use std::marker::PhantomData; 8 | use std::ops::Index; 9 | use std::ops::IndexMut; 10 | 11 | pub trait PiggyVecKey { 12 | fn as_usize(&self) -> usize; 13 | } 14 | 15 | /// A vec that you can only insert into, so that the index can be used as a key 16 | /// 17 | #[derive(Debug)] 18 | pub struct PiggyVec 19 | where 20 | K: PiggyVecKey + From, 21 | { 22 | vec: Vec, 23 | phantom: PhantomData, 24 | } 25 | 26 | impl PiggyVec 27 | where 28 | K: PiggyVecKey + From, 29 | { 30 | pub fn with_capacity(capacity: usize) -> Self { 31 | Self { 32 | vec: Vec::with_capacity(capacity), 33 | phantom: PhantomData {}, 34 | } 35 | } 36 | 37 | #[inline] 38 | pub fn insert(&mut self, v: V) -> K { 39 | self.vec.push(v); 40 | (self.vec.len() - 1).into() 41 | } 42 | 43 | #[inline] 44 | pub fn vec(&self) -> &Vec { 45 | &self.vec 46 | } 47 | 48 | pub fn iter<'a>(&'a self) -> PiggyVecIter<'a, K, V> { 49 | PiggyVecIter { 50 | vec_iter: self.vec.iter(), 51 | phantom: PhantomData {}, 52 | } 53 | } 54 | } 55 | 56 | impl Index for PiggyVec 57 | where 58 | K: PiggyVecKey + From, 59 | { 60 | type Output = V; 61 | 62 | #[inline] 63 | fn index(&self, index: K) -> &Self::Output { 64 | &self.vec[index.as_usize()] 65 | } 66 | } 67 | 68 | impl IndexMut for PiggyVec 69 | where 70 | K: PiggyVecKey + From, 71 | { 72 | #[inline] 73 | fn index_mut(&mut self, index: K) -> &mut Self::Output { 74 | &mut self.vec[index.as_usize()] 75 | } 76 | } 77 | 78 | impl From> for PiggyVec 79 | where 80 | K: PiggyVecKey + From, 81 | { 82 | #[inline] 83 | fn from(vec: Vec) -> Self { 84 | PiggyVec { 85 | vec, 86 | phantom: PhantomData {}, 87 | } 88 | } 89 | } 90 | 91 | pub struct PiggyVecIter<'a, K, V> 92 | where 93 | K: PiggyVecKey + From, 94 | { 95 | vec_iter: std::slice::Iter<'a, V>, 96 | phantom: PhantomData, 97 | } 98 | 99 | impl<'a, K, V> Iterator for PiggyVecIter<'a, K, V> 100 | where 101 | K: PiggyVecKey + From, 102 | { 103 | type Item = &'a V; 104 | 105 | fn next(&mut self) -> Option { 106 | self.vec_iter.next() 107 | } 108 | } 109 | 110 | #[macro_export] 111 | macro_rules! piggy_key_type { 112 | ( $(#[$outer:meta])* $vis:vis struct $name:ident; $($rest:tt)* ) => { 113 | $(#[$outer])* 114 | #[derive(Copy, Clone, Default, 115 | Eq, PartialEq, Ord, PartialOrd, 116 | Hash, Debug)] 117 | #[repr(transparent)] 118 | $vis struct $name(usize); 119 | 120 | impl $name { 121 | #[inline] 122 | pub fn null() -> Self { 123 | $name(usize::MAX) 124 | } 125 | } 126 | 127 | impl From for $name { 128 | #[inline] 129 | fn from(k: usize) -> Self { 130 | $name(k) 131 | } 132 | } 133 | 134 | impl $crate::parser::PiggyVecKey for $name { 135 | #[inline] 136 | fn as_usize(&self) -> usize { 137 | self.0 138 | } 139 | } 140 | 141 | piggy_key_type!($($rest)*); 142 | }; 143 | 144 | () => {} 145 | } 146 | 147 | piggy_key_type! { 148 | pub struct LabeledStmtKey; 149 | pub struct AssignStmtKey; 150 | pub struct SpecKey; 151 | pub struct FuncDeclKey; 152 | pub struct FuncTypeKey; 153 | pub struct IdentKey; 154 | pub struct FieldKey; 155 | pub struct EntityKey; 156 | pub struct ScopeKey; 157 | } 158 | 159 | pub type LabeledStmts = PiggyVec; 160 | pub type AssignStmts = PiggyVec; 161 | pub type Specs = PiggyVec; 162 | pub type FuncDecls = PiggyVec; 163 | pub type FuncTypes = PiggyVec; 164 | pub type Idents = PiggyVec; 165 | pub type Fields = PiggyVec; 166 | pub type Entitys = PiggyVec; 167 | pub type Scopes = PiggyVec; 168 | 169 | pub struct AstObjects { 170 | pub l_stmts: LabeledStmts, 171 | pub a_stmts: AssignStmts, 172 | pub specs: Specs, 173 | pub fdecls: FuncDecls, 174 | pub ftypes: FuncTypes, 175 | pub idents: Idents, 176 | pub fields: Fields, 177 | pub entities: Entitys, 178 | pub scopes: Scopes, 179 | } 180 | 181 | impl Default for AstObjects { 182 | fn default() -> Self { 183 | Self::new() 184 | } 185 | } 186 | 187 | impl AstObjects { 188 | pub fn new() -> AstObjects { 189 | const CAP: usize = 16; 190 | AstObjects { 191 | l_stmts: PiggyVec::with_capacity(CAP), 192 | a_stmts: PiggyVec::with_capacity(CAP), 193 | specs: PiggyVec::with_capacity(CAP), 194 | fdecls: PiggyVec::with_capacity(CAP), 195 | ftypes: PiggyVec::with_capacity(CAP), 196 | idents: PiggyVec::with_capacity(CAP), 197 | fields: PiggyVec::with_capacity(CAP), 198 | entities: PiggyVec::with_capacity(CAP), 199 | scopes: PiggyVec::with_capacity(CAP), 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/parser/position.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Goscript Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // 6 | // This code is adapted from the offical Go code written in Go 7 | // with license as follows: 8 | // Copyright 2013 The Go Authors. All rights reserved. 9 | // Use of this source code is governed by a BSD-style 10 | // license that can be found in the LICENSE file. 11 | 12 | use std::borrow::Borrow; 13 | use std::fmt; 14 | use std::fmt::Write; 15 | use std::rc::Rc; 16 | 17 | pub type Pos = usize; 18 | 19 | #[derive(Clone, Debug)] 20 | pub struct FilePos { 21 | pub filename: Rc, 22 | pub offset: usize, // offset in utf8 char 23 | pub line: usize, 24 | pub column: usize, 25 | } 26 | 27 | impl FilePos { 28 | pub fn is_valid(&self) -> bool { 29 | self.line > 0 30 | } 31 | 32 | pub fn null() -> FilePos { 33 | FilePos { 34 | filename: Rc::new("[null_file]".to_owned()), 35 | line: 0, 36 | offset: 0, 37 | column: 0, 38 | } 39 | } 40 | } 41 | 42 | impl fmt::Display for FilePos { 43 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 44 | let mut s = String::clone(&*self.filename); 45 | if self.is_valid() { 46 | if !s.is_empty() { 47 | s.push(':'); 48 | } 49 | s.push_str(&self.line.to_string()); 50 | } 51 | if self.column != 0 { 52 | write!(&mut s, ":{}", self.column).unwrap(); 53 | } 54 | if s.is_empty() { 55 | s.push('-'); 56 | } 57 | f.write_str(&s) 58 | } 59 | } 60 | 61 | #[derive(Debug)] 62 | pub struct File { 63 | name: Rc, 64 | base: usize, 65 | size: usize, 66 | lines: Vec, 67 | } 68 | 69 | impl File { 70 | pub fn new(name: String) -> File { 71 | File { 72 | name: Rc::new(name), 73 | base: 0, 74 | size: 0, 75 | lines: vec![0], 76 | } 77 | } 78 | 79 | pub fn name(&self) -> &str { 80 | &self.name 81 | } 82 | 83 | pub fn base(&self) -> usize { 84 | self.base 85 | } 86 | 87 | pub fn size(&self) -> usize { 88 | self.size 89 | } 90 | 91 | pub fn line_count(&self) -> usize { 92 | self.lines.len() 93 | } 94 | 95 | pub fn add_line(&mut self, offset: usize) { 96 | let i = self.line_count(); 97 | if (i == 0 || self.lines[i - 1] < offset) && offset < self.size { 98 | self.lines.push(offset); 99 | } 100 | } 101 | 102 | pub fn merge_line(&mut self, line: usize) { 103 | if line < 1 { 104 | panic!("illegal line number (line numbering starts at 1)"); 105 | } 106 | if line >= self.line_count() { 107 | panic!("illegal line number"); 108 | } 109 | /* 110 | let mut shalf = self.lines.split_off(line); 111 | self.lines.pop().unwrap(); 112 | self.lines.append(&mut shalf); 113 | */ 114 | let lines = &self.lines; 115 | self.lines = lines 116 | .iter() 117 | .enumerate() 118 | .filter(|&(i, _)| i != line) 119 | .map(|(_, l)| *l) 120 | .collect(); 121 | } 122 | 123 | pub fn set_lines(&mut self, lines: Vec) -> bool { 124 | let size = self.size; 125 | for (i, &offset) in self.lines.iter().enumerate() { 126 | if (i == 0 && size <= offset) || offset < lines[i - 1] { 127 | return false; 128 | } 129 | } 130 | self.lines = lines; 131 | true 132 | } 133 | 134 | pub fn set_lines_for_content(&mut self, content: &mut std::str::Chars) { 135 | let (mut new_line, mut line) = (true, 0); 136 | for (offset, b) in content.enumerate() { 137 | if new_line { 138 | self.lines.push(line); 139 | } 140 | new_line = false; 141 | if b == '\n' { 142 | new_line = true; 143 | line = offset + 1; 144 | } 145 | } 146 | } 147 | 148 | pub fn line_start(&self, line: usize) -> usize { 149 | if line < 1 { 150 | panic!("illegal line number (line numbering starts at 1)"); 151 | } 152 | if line >= self.line_count() { 153 | panic!("illegal line number"); 154 | } 155 | self.base + self.lines[line - 1] 156 | } 157 | 158 | pub fn pos(&self, offset: usize) -> Pos { 159 | if offset > self.size() { 160 | panic!("illegal file offset") 161 | } 162 | self.base() + offset 163 | } 164 | 165 | pub fn position(&self, p: Pos) -> FilePos { 166 | if p < self.base || p > self.base + self.size { 167 | panic!("illegal Pos value"); 168 | } 169 | 170 | let line_count = self.line_count(); 171 | let offset = p - self.base; 172 | let line = match self 173 | .lines 174 | .iter() 175 | .enumerate() 176 | .find(|&(_, &line)| line > offset) 177 | { 178 | Some((i, _)) => i, 179 | None => line_count, 180 | }; 181 | let column = offset - self.lines[line - 1] + 1; 182 | 183 | FilePos { 184 | filename: self.name.clone(), 185 | line, 186 | offset, 187 | column, 188 | } 189 | } 190 | } 191 | 192 | #[derive(Debug, Default)] 193 | pub struct FileSet { 194 | base: usize, 195 | files: Vec, 196 | } 197 | 198 | impl FileSet { 199 | pub fn new() -> FileSet { 200 | FileSet { 201 | base: 0, 202 | files: vec![], 203 | } 204 | } 205 | 206 | pub fn base(&self) -> usize { 207 | self.base 208 | } 209 | 210 | pub fn iter(&self) -> FileSetIter<'_> { 211 | FileSetIter { fs: self, cur: 0 } 212 | } 213 | 214 | pub fn file(&self, p: Pos) -> Option<&File> { 215 | self.files 216 | .iter() 217 | .find(|&f| f.base <= p && f.base + f.size >= p) 218 | .map(|v| v as _) 219 | } 220 | 221 | pub fn position(&self, p: Pos) -> Option { 222 | self.file(p).map(|f| f.position(p)) 223 | } 224 | 225 | pub fn index_file(&mut self, i: usize) -> Option<&mut File> { 226 | if i >= self.files.len() { 227 | None 228 | } else { 229 | Some(&mut self.files[i]) 230 | } 231 | } 232 | 233 | pub fn recent_file(&mut self) -> Option<&mut File> { 234 | let c = self.files.len(); 235 | if c == 0 { None } else { self.index_file(c - 1) } 236 | } 237 | 238 | pub fn add_file(&mut self, name: String, base: Option, size: usize) -> &mut File { 239 | let real_base = if let Some(b) = base { b } else { self.base }; 240 | if real_base < self.base { 241 | panic!("illegal base"); 242 | } 243 | 244 | let mut f = File::new(name); 245 | f.base = real_base; 246 | f.size = size; 247 | let set_base = self.base + size + 1; // +1 because EOF also has a position 248 | if set_base < self.base { 249 | panic!("token.Pos offset overflow (> 2G of source code in file set)"); 250 | } 251 | self.base = set_base; 252 | self.files.push(f); 253 | self.recent_file().unwrap() 254 | } 255 | } 256 | 257 | pub struct FileSetIter<'a> { 258 | fs: &'a FileSet, 259 | cur: usize, 260 | } 261 | 262 | impl<'a> Iterator for FileSetIter<'a> { 263 | type Item = &'a File; 264 | 265 | fn next(&mut self) -> Option<&'a File> { 266 | if self.cur < self.fs.files.len() { 267 | self.cur += 1; 268 | Some(self.fs.files[self.cur - 1].borrow()) 269 | } else { 270 | None 271 | } 272 | } 273 | } 274 | 275 | #[cfg(test)] 276 | mod test { 277 | use super::*; 278 | 279 | #[test] 280 | fn test_position() { 281 | let p = FilePos { 282 | filename: Rc::new("test.gs".to_owned()), 283 | offset: 0, 284 | line: 54321, 285 | column: 8, 286 | }; 287 | print!("this is the position: {} ", p); 288 | let mut fs = FileSet::new(); 289 | let mut f = File::new("test.gs".to_owned()); 290 | f.size = 12345; 291 | f.add_line(123); 292 | f.add_line(133); 293 | f.add_line(143); 294 | print!("\nfile: {:?}", f); 295 | f.merge_line(1); 296 | print!("\nfile after merge: {:?}", f); 297 | 298 | { 299 | fs.add_file("testfile1.gs".to_owned(), None, 222); 300 | fs.add_file("testfile2.gs".to_owned(), None, 222); 301 | fs.add_file("testfile3.gs".to_owned(), None, 222); 302 | print!("\nset {:?}", fs); 303 | } 304 | 305 | for f in fs.iter() { 306 | print!("\nfiles in set: {:?}", f); 307 | } 308 | print!("\nfile at 100: {:?}", fs.file(100)) 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /tests/switch_tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2025-present Raphael Amorim. All rights reserved. 2 | // Use of this source code is governed by a BSD-3-Clause 3 | // license that can be found in the LICENSE file. 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use goiaba::wasm::compiler::compile_str; 8 | use wasmtime::{Engine, Instance, Module, Store}; 9 | 10 | #[test] 11 | fn test_simple_switch() { 12 | let go_source = r#" 13 | package main 14 | 15 | //export classify 16 | func classify(x int) int { 17 | switch x { 18 | case 1: 19 | return 10 20 | case 2: 21 | return 20 22 | case 3: 23 | return 30 24 | default: 25 | return 0 26 | } 27 | } 28 | "#; 29 | 30 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 31 | 32 | let engine = Engine::default(); 33 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 34 | let mut store = Store::new(&engine, ()); 35 | let instance = 36 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 37 | 38 | let func = instance 39 | .get_typed_func::(&mut store, "classify") 40 | .expect("Failed to get function"); 41 | 42 | assert_eq!(func.call(&mut store, 1).unwrap(), 10); 43 | assert_eq!(func.call(&mut store, 2).unwrap(), 20); 44 | assert_eq!(func.call(&mut store, 3).unwrap(), 30); 45 | assert_eq!(func.call(&mut store, 99).unwrap(), 0); 46 | } 47 | 48 | #[test] 49 | fn test_switch_with_default() { 50 | let go_source = r#" 51 | package main 52 | 53 | //export get_day_type 54 | func get_day_type(day int) int { 55 | switch day { 56 | case 1, 2, 3, 4, 5: 57 | return 1 // Weekday 58 | case 6, 7: 59 | return 2 // Weekend 60 | default: 61 | return 0 // Invalid 62 | } 63 | } 64 | "#; 65 | 66 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 67 | 68 | let engine = Engine::default(); 69 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 70 | let mut store = Store::new(&engine, ()); 71 | let instance = 72 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 73 | 74 | let func = instance 75 | .get_typed_func::(&mut store, "get_day_type") 76 | .expect("Failed to get function"); 77 | 78 | assert_eq!(func.call(&mut store, 1).unwrap(), 1); 79 | assert_eq!(func.call(&mut store, 3).unwrap(), 1); 80 | assert_eq!(func.call(&mut store, 5).unwrap(), 1); 81 | assert_eq!(func.call(&mut store, 6).unwrap(), 2); 82 | assert_eq!(func.call(&mut store, 7).unwrap(), 2); 83 | assert_eq!(func.call(&mut store, 10).unwrap(), 0); 84 | } 85 | 86 | #[test] 87 | fn test_switch_calculation_in_case() { 88 | let go_source = r#" 89 | package main 90 | 91 | //export compute 92 | func compute(op int, a int, b int) int { 93 | switch op { 94 | case 1: 95 | return a + b 96 | case 2: 97 | return a - b 98 | case 3: 99 | return a * b 100 | case 4: 101 | return a / b 102 | default: 103 | return 0 104 | } 105 | } 106 | "#; 107 | 108 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 109 | 110 | let engine = Engine::default(); 111 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 112 | let mut store = Store::new(&engine, ()); 113 | let instance = 114 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 115 | 116 | let func = instance 117 | .get_typed_func::<(i32, i32, i32), i32>(&mut store, "compute") 118 | .expect("Failed to get function"); 119 | 120 | assert_eq!(func.call(&mut store, (1, 10, 5)).unwrap(), 15); // add 121 | assert_eq!(func.call(&mut store, (2, 10, 5)).unwrap(), 5); // sub 122 | assert_eq!(func.call(&mut store, (3, 10, 5)).unwrap(), 50); // mul 123 | assert_eq!(func.call(&mut store, (4, 10, 5)).unwrap(), 2); // div 124 | assert_eq!(func.call(&mut store, (99, 10, 5)).unwrap(), 0); // default 125 | } 126 | 127 | #[test] 128 | fn test_switch_with_variables() { 129 | let go_source = r#" 130 | package main 131 | 132 | //export evaluate 133 | func evaluate(x int) int { 134 | result := 0 135 | switch x { 136 | case 1: 137 | result = 100 138 | case 2: 139 | result = 200 140 | case 3: 141 | result = 300 142 | } 143 | return result 144 | } 145 | "#; 146 | 147 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 148 | 149 | let engine = Engine::default(); 150 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 151 | let mut store = Store::new(&engine, ()); 152 | let instance = 153 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 154 | 155 | let func = instance 156 | .get_typed_func::(&mut store, "evaluate") 157 | .expect("Failed to get function"); 158 | 159 | assert_eq!(func.call(&mut store, 1).unwrap(), 100); 160 | assert_eq!(func.call(&mut store, 2).unwrap(), 200); 161 | assert_eq!(func.call(&mut store, 3).unwrap(), 300); 162 | assert_eq!(func.call(&mut store, 99).unwrap(), 0); // No default case 163 | } 164 | 165 | #[test] 166 | fn test_nested_switch() { 167 | let go_source = r#" 168 | package main 169 | 170 | //export categorize 171 | func categorize(x int, y int) int { 172 | switch x { 173 | case 1: 174 | switch y { 175 | case 1: 176 | return 11 177 | case 2: 178 | return 12 179 | default: 180 | return 10 181 | } 182 | case 2: 183 | return 20 184 | default: 185 | return 0 186 | } 187 | } 188 | "#; 189 | 190 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 191 | 192 | let engine = Engine::default(); 193 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 194 | let mut store = Store::new(&engine, ()); 195 | let instance = 196 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 197 | 198 | let func = instance 199 | .get_typed_func::<(i32, i32), i32>(&mut store, "categorize") 200 | .expect("Failed to get function"); 201 | 202 | assert_eq!(func.call(&mut store, (1, 1)).unwrap(), 11); 203 | assert_eq!(func.call(&mut store, (1, 2)).unwrap(), 12); 204 | assert_eq!(func.call(&mut store, (1, 3)).unwrap(), 10); 205 | assert_eq!(func.call(&mut store, (2, 1)).unwrap(), 20); 206 | assert_eq!(func.call(&mut store, (3, 1)).unwrap(), 0); 207 | } 208 | 209 | #[test] 210 | fn test_switch_only_default() { 211 | let go_source = r#" 212 | package main 213 | 214 | //export always_42 215 | func always_42(x int) int { 216 | switch x { 217 | default: 218 | return 42 219 | } 220 | } 221 | "#; 222 | 223 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 224 | 225 | let engine = Engine::default(); 226 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 227 | let mut store = Store::new(&engine, ()); 228 | let instance = 229 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 230 | 231 | let func = instance 232 | .get_typed_func::(&mut store, "always_42") 233 | .expect("Failed to get function"); 234 | 235 | assert_eq!(func.call(&mut store, 1).unwrap(), 42); 236 | assert_eq!(func.call(&mut store, 100).unwrap(), 42); 237 | assert_eq!(func.call(&mut store, -5).unwrap(), 42); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/parser/visitor.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Goscript Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | use super::ast::*; 6 | use super::objects::*; 7 | use super::token::Token; 8 | 9 | pub trait ExprVisitor { 10 | type Result; 11 | 12 | fn visit_expr_ident(&mut self, this: &Expr, ident: &IdentKey) -> Self::Result; 13 | 14 | fn visit_expr_ellipsis(&mut self, this: &Expr, els: &Option) -> Self::Result; 15 | 16 | fn visit_expr_basic_lit(&mut self, this: &Expr, blit: &BasicLit) -> Self::Result; 17 | 18 | fn visit_expr_func_lit(&mut self, this: &Expr, flit: &FuncLit) -> Self::Result; 19 | 20 | fn visit_expr_composit_lit(&mut self, this: &Expr, clit: &CompositeLit) -> Self::Result; 21 | 22 | fn visit_expr_paren(&mut self, this: &Expr, expr: &Expr) -> Self::Result; 23 | 24 | fn visit_expr_selector(&mut self, this: &Expr, expr: &Expr, ident: &IdentKey) -> Self::Result; //add: lvalue 25 | 26 | fn visit_expr_index(&mut self, this: &Expr, expr: &Expr, index: &Expr) -> Self::Result; 27 | 28 | fn visit_expr_slice( 29 | &mut self, 30 | this: &Expr, 31 | expr: &Expr, 32 | low: &Option, 33 | high: &Option, 34 | max: &Option, 35 | ) -> Self::Result; 36 | 37 | fn visit_expr_type_assert( 38 | &mut self, 39 | this: &Expr, 40 | expr: &Expr, 41 | typ: &Option, 42 | ) -> Self::Result; 43 | 44 | fn visit_expr_call( 45 | &mut self, 46 | this: &Expr, 47 | func: &Expr, 48 | args: &[Expr], 49 | ellipsis: bool, 50 | ) -> Self::Result; 51 | 52 | fn visit_expr_star(&mut self, this: &Expr, expr: &Expr) -> Self::Result; 53 | 54 | fn visit_expr_unary(&mut self, this: &Expr, expr: &Expr, op: &Token) -> Self::Result; 55 | 56 | fn visit_expr_binary( 57 | &mut self, 58 | this: &Expr, 59 | left: &Expr, 60 | op: &Token, 61 | right: &Expr, 62 | ) -> Self::Result; 63 | 64 | fn visit_expr_key_value(&mut self, this: &Expr, key: &Expr, val: &Expr) -> Self::Result; 65 | 66 | /// codegen needs the unwraped expr 67 | fn visit_expr_array_type( 68 | &mut self, 69 | this: &Expr, 70 | len: &Option, 71 | elm: &Expr, 72 | ) -> Self::Result; 73 | 74 | fn visit_expr_struct_type(&mut self, this: &Expr, s: &StructType) -> Self::Result; 75 | 76 | fn visit_expr_func_type(&mut self, this: &Expr, s: &FuncTypeKey) -> Self::Result; 77 | 78 | fn visit_expr_interface_type(&mut self, this: &Expr, s: &InterfaceType) -> Self::Result; 79 | 80 | /// codegen needs the unwraped expr 81 | fn visit_map_type(&mut self, this: &Expr, key: &Expr, val: &Expr, map: &Expr) -> Self::Result; 82 | 83 | fn visit_chan_type(&mut self, this: &Expr, chan: &Expr, dir: &ChanDir) -> Self::Result; 84 | 85 | fn visit_bad_expr(&mut self, this: &Expr, e: &BadExpr) -> Self::Result; 86 | } 87 | 88 | pub trait StmtVisitor { 89 | type Result; 90 | 91 | fn visit_decl(&mut self, decl: &Decl) -> Self::Result; 92 | 93 | fn visit_stmt_decl_gen(&mut self, gdecl: &GenDecl) -> Self::Result; 94 | 95 | fn visit_stmt_decl_func(&mut self, fdecl: &FuncDeclKey) -> Self::Result; 96 | 97 | fn visit_stmt_labeled(&mut self, lstmt: &LabeledStmtKey) -> Self::Result; 98 | 99 | fn visit_stmt_send(&mut self, sstmt: &SendStmt) -> Self::Result; 100 | 101 | fn visit_stmt_incdec(&mut self, idcstmt: &IncDecStmt) -> Self::Result; 102 | 103 | fn visit_stmt_assign(&mut self, astmt: &AssignStmtKey) -> Self::Result; 104 | 105 | fn visit_stmt_go(&mut self, gostmt: &GoStmt) -> Self::Result; 106 | 107 | fn visit_stmt_defer(&mut self, dstmt: &DeferStmt) -> Self::Result; 108 | 109 | fn visit_stmt_return(&mut self, rstmt: &ReturnStmt) -> Self::Result; 110 | 111 | fn visit_stmt_branch(&mut self, bstmt: &BranchStmt) -> Self::Result; 112 | 113 | fn visit_stmt_block(&mut self, bstmt: &BlockStmt) -> Self::Result; 114 | 115 | fn visit_stmt_if(&mut self, ifstmt: &IfStmt) -> Self::Result; 116 | 117 | fn visit_stmt_case(&mut self, cclause: &CaseClause) -> Self::Result; 118 | 119 | fn visit_stmt_switch(&mut self, sstmt: &SwitchStmt) -> Self::Result; 120 | 121 | fn visit_stmt_type_switch(&mut self, tstmt: &TypeSwitchStmt) -> Self::Result; 122 | 123 | fn visit_stmt_comm(&mut self, cclause: &CommClause) -> Self::Result; 124 | 125 | fn visit_stmt_select(&mut self, sstmt: &SelectStmt) -> Self::Result; 126 | 127 | fn visit_stmt_for(&mut self, fstmt: &ForStmt) -> Self::Result; 128 | 129 | fn visit_stmt_range(&mut self, rstmt: &RangeStmt) -> Self::Result; 130 | 131 | fn visit_expr_stmt(&mut self, stmt: &Expr) -> Self::Result; 132 | 133 | fn visit_empty_stmt(&mut self, e: &EmptyStmt) -> Self::Result; 134 | 135 | fn visit_bad_stmt(&mut self, b: &BadStmt) -> Self::Result; 136 | 137 | fn visit_bad_decl(&mut self, b: &BadDecl) -> Self::Result; 138 | } 139 | 140 | pub fn walk_expr(v: &mut dyn ExprVisitor, expr: &Expr) -> R { 141 | match expr { 142 | Expr::Bad(e) => v.visit_bad_expr(expr, e.as_ref()), 143 | Expr::Ident(e) => v.visit_expr_ident(expr, e), 144 | Expr::Ellipsis(e) => v.visit_expr_ellipsis(expr, &e.as_ref().elt), 145 | Expr::BasicLit(e) => v.visit_expr_basic_lit(expr, e.as_ref()), 146 | Expr::FuncLit(e) => v.visit_expr_func_lit(expr, e.as_ref()), 147 | Expr::CompositeLit(e) => v.visit_expr_composit_lit(expr, e.as_ref()), 148 | Expr::Paren(e) => v.visit_expr_paren(expr, &e.as_ref().expr), 149 | Expr::Selector(e) => { 150 | let selexp = e.as_ref(); 151 | v.visit_expr_selector(expr, &selexp.expr, &selexp.sel) 152 | } 153 | Expr::Index(e) => { 154 | let indexp = e.as_ref(); 155 | v.visit_expr_index(expr, &indexp.expr, &indexp.index) 156 | } 157 | Expr::Slice(e) => { 158 | let slexp = e.as_ref(); 159 | v.visit_expr_slice(expr, &slexp.expr, &slexp.low, &slexp.high, &slexp.max) 160 | } 161 | Expr::TypeAssert(e) => { 162 | let taexp = e.as_ref(); 163 | v.visit_expr_type_assert(expr, &taexp.expr, &taexp.typ) 164 | } 165 | Expr::Call(e) => { 166 | let callexp = e.as_ref(); 167 | v.visit_expr_call( 168 | expr, 169 | &callexp.func, 170 | &callexp.args, 171 | callexp.ellipsis.is_some(), 172 | ) 173 | } 174 | Expr::Star(e) => v.visit_expr_star(expr, &e.as_ref().expr), 175 | Expr::Unary(e) => { 176 | let uexp = e.as_ref(); 177 | v.visit_expr_unary(expr, &uexp.expr, &uexp.op) 178 | } 179 | Expr::Binary(e) => { 180 | let bexp = e.as_ref(); 181 | v.visit_expr_binary(expr, &bexp.expr_a, &bexp.op, &bexp.expr_b) 182 | } 183 | Expr::KeyValue(e) => { 184 | let kvexp = e.as_ref(); 185 | v.visit_expr_key_value(expr, &kvexp.key, &kvexp.val) 186 | } 187 | Expr::Array(e) => v.visit_expr_array_type(expr, &e.as_ref().len, &e.as_ref().elt), 188 | Expr::Struct(e) => v.visit_expr_struct_type(expr, e.as_ref()), 189 | Expr::Func(e) => v.visit_expr_func_type(expr, e), 190 | Expr::Interface(e) => v.visit_expr_interface_type(expr, e.as_ref()), 191 | Expr::Map(e) => { 192 | let mexp = e.as_ref(); 193 | v.visit_map_type(expr, &mexp.key, &mexp.val, expr) 194 | } 195 | Expr::Chan(e) => { 196 | let cexp = e.as_ref(); 197 | v.visit_chan_type(expr, &cexp.val, &cexp.dir) 198 | } 199 | } 200 | } 201 | 202 | pub fn walk_stmt + ExprVisitor, R>( 203 | v: &mut V, 204 | stmt: &Stmt, 205 | ) -> R { 206 | match stmt { 207 | Stmt::Bad(b) => v.visit_bad_stmt(b), 208 | Stmt::Decl(decl) => v.visit_decl(decl), 209 | Stmt::Empty(e) => v.visit_empty_stmt(e), 210 | Stmt::Labeled(lstmt) => v.visit_stmt_labeled(lstmt), 211 | Stmt::Expr(expr) => v.visit_expr_stmt(expr), 212 | Stmt::Send(sstmt) => v.visit_stmt_send(sstmt), 213 | Stmt::IncDec(idstmt) => v.visit_stmt_incdec(idstmt), 214 | Stmt::Assign(astmt) => v.visit_stmt_assign(astmt), 215 | Stmt::Go(gostmt) => v.visit_stmt_go(gostmt), 216 | Stmt::Defer(dstmt) => v.visit_stmt_defer(dstmt), 217 | Stmt::Return(rstmt) => v.visit_stmt_return(rstmt), 218 | Stmt::Branch(bstmt) => v.visit_stmt_branch(bstmt), 219 | Stmt::Block(bstmt) => v.visit_stmt_block(bstmt), 220 | Stmt::If(ifstmt) => v.visit_stmt_if(ifstmt), 221 | Stmt::Case(cclause) => v.visit_stmt_case(cclause), 222 | Stmt::Switch(sstmt) => v.visit_stmt_switch(sstmt), 223 | Stmt::TypeSwitch(tsstmt) => v.visit_stmt_type_switch(tsstmt), 224 | Stmt::Comm(cclause) => v.visit_stmt_comm(cclause), 225 | Stmt::Select(sstmt) => v.visit_stmt_select(sstmt), 226 | Stmt::For(forstmt) => v.visit_stmt_for(forstmt), 227 | Stmt::Range(rstmt) => v.visit_stmt_range(rstmt), 228 | } 229 | } 230 | 231 | pub fn walk_decl(v: &mut dyn StmtVisitor, decl: &Decl) -> R { 232 | match decl { 233 | Decl::Bad(b) => v.visit_bad_decl(b), 234 | Decl::Gen(gdecl) => v.visit_stmt_decl_gen(gdecl), 235 | Decl::Func(fdecl) => v.visit_stmt_decl_func(fdecl), 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /tests/string_tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2025-present Raphael Amorim. All rights reserved. 2 | // Use of this source code is governed by a BSD-3-Clause 3 | // license that can be found in the LICENSE file. 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use goiaba::wasm::compiler::compile_str; 8 | use wasmtime::{Engine, Instance, Module, Store}; 9 | 10 | #[test] 11 | fn test_string_literal_compilation() { 12 | let go_source = r#" 13 | package main 14 | 15 | //export has_string 16 | func has_string() int { 17 | s := "hello" 18 | if s == "hello" { 19 | return 1 20 | } 21 | return 0 22 | } 23 | "#; 24 | 25 | // Just verify it compiles without errors 26 | let result = compile_str(go_source); 27 | assert!(result.is_ok(), "Failed to compile: {:?}", result.err()); 28 | } 29 | 30 | #[test] 31 | fn test_string_length() { 32 | let go_source = r#" 33 | package main 34 | 35 | //export string_len 36 | func string_len() int { 37 | s := "hello" 38 | return len(s) 39 | } 40 | "#; 41 | 42 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 43 | 44 | let engine = Engine::default(); 45 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 46 | let mut store = Store::new(&engine, ()); 47 | let instance = 48 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 49 | 50 | let func = instance 51 | .get_typed_func::<(), i32>(&mut store, "string_len") 52 | .expect("Failed to get function"); 53 | 54 | let result = func.call(&mut store, ()).expect("Failed to call function"); 55 | assert_eq!(result, 5); // "hello" has 5 characters 56 | } 57 | 58 | #[test] 59 | fn test_empty_string_length() { 60 | let go_source = r#" 61 | package main 62 | 63 | //export empty_len 64 | func empty_len() int { 65 | s := "" 66 | return len(s) 67 | } 68 | "#; 69 | 70 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 71 | 72 | let engine = Engine::default(); 73 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 74 | let mut store = Store::new(&engine, ()); 75 | let instance = 76 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 77 | 78 | let func = instance 79 | .get_typed_func::<(), i32>(&mut store, "empty_len") 80 | .expect("Failed to get function"); 81 | 82 | let result = func.call(&mut store, ()).expect("Failed to call function"); 83 | assert_eq!(result, 0); 84 | } 85 | 86 | #[test] 87 | fn test_multiple_string_lengths() { 88 | let go_source = r#" 89 | package main 90 | 91 | //export compare_lengths 92 | func compare_lengths() int { 93 | s1 := "hi" 94 | s2 := "world" 95 | return len(s1) + len(s2) 96 | } 97 | "#; 98 | 99 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 100 | 101 | let engine = Engine::default(); 102 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 103 | let mut store = Store::new(&engine, ()); 104 | let instance = 105 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 106 | 107 | let func = instance 108 | .get_typed_func::<(), i32>(&mut store, "compare_lengths") 109 | .expect("Failed to get function"); 110 | 111 | let result = func.call(&mut store, ()).expect("Failed to call function"); 112 | assert_eq!(result, 7); // "hi" (2) + "world" (5) 113 | } 114 | 115 | #[test] 116 | fn test_string_length_conditional() { 117 | let go_source = r#" 118 | package main 119 | 120 | //export is_long 121 | func is_long() int { 122 | s := "hello world" 123 | if len(s) > 10 { 124 | return 1 125 | } 126 | return 0 127 | } 128 | "#; 129 | 130 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 131 | 132 | let engine = Engine::default(); 133 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 134 | let mut store = Store::new(&engine, ()); 135 | let instance = 136 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 137 | 138 | let func = instance 139 | .get_typed_func::<(), i32>(&mut store, "is_long") 140 | .expect("Failed to get function"); 141 | 142 | let result = func.call(&mut store, ()).expect("Failed to call function"); 143 | assert_eq!(result, 1); 144 | } 145 | 146 | #[test] 147 | fn test_string_special_characters() { 148 | let go_source = r#" 149 | package main 150 | 151 | //export special_chars_len 152 | func special_chars_len() int { 153 | s := "a b\tc" 154 | return len(s) 155 | } 156 | "#; 157 | 158 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 159 | 160 | let engine = Engine::default(); 161 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 162 | let mut store = Store::new(&engine, ()); 163 | let instance = 164 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 165 | 166 | let func = instance 167 | .get_typed_func::<(), i32>(&mut store, "special_chars_len") 168 | .expect("Failed to get function"); 169 | 170 | let result = func.call(&mut store, ()).expect("Failed to call function"); 171 | assert_eq!(result, 5); // "a b\tc" = 5 characters 172 | } 173 | 174 | #[test] 175 | fn test_string_numeric_content() { 176 | let go_source = r#" 177 | package main 178 | 179 | //export numeric_string_len 180 | func numeric_string_len() int { 181 | s := "12345" 182 | return len(s) 183 | } 184 | "#; 185 | 186 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 187 | 188 | let engine = Engine::default(); 189 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 190 | let mut store = Store::new(&engine, ()); 191 | let instance = 192 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 193 | 194 | let func = instance 195 | .get_typed_func::<(), i32>(&mut store, "numeric_string_len") 196 | .expect("Failed to get function"); 197 | 198 | let result = func.call(&mut store, ()).expect("Failed to call function"); 199 | assert_eq!(result, 5); 200 | } 201 | 202 | #[test] 203 | fn test_multiple_string_variables() { 204 | let go_source = r#" 205 | package main 206 | 207 | //export count_chars 208 | func count_chars() int { 209 | s1 := "abc" 210 | s2 := "defgh" 211 | s3 := "ij" 212 | total := len(s1) + len(s2) + len(s3) 213 | return total 214 | } 215 | "#; 216 | 217 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 218 | 219 | let engine = Engine::default(); 220 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 221 | let mut store = Store::new(&engine, ()); 222 | let instance = 223 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 224 | 225 | let func = instance 226 | .get_typed_func::<(), i32>(&mut store, "count_chars") 227 | .expect("Failed to get function"); 228 | 229 | let result = func.call(&mut store, ()).expect("Failed to call function"); 230 | assert_eq!(result, 10); // 3 + 5 + 2 231 | } 232 | 233 | #[test] 234 | fn test_string_in_loop() { 235 | let go_source = r#" 236 | package main 237 | 238 | //export repeat_length 239 | func repeat_length() int { 240 | s := "test" 241 | total := 0 242 | for i := 0; i < 3; i++ { 243 | total = total + len(s) 244 | } 245 | return total 246 | } 247 | "#; 248 | 249 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 250 | 251 | let engine = Engine::default(); 252 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 253 | let mut store = Store::new(&engine, ()); 254 | let instance = 255 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 256 | 257 | let func = instance 258 | .get_typed_func::<(), i32>(&mut store, "repeat_length") 259 | .expect("Failed to get function"); 260 | 261 | let result = func.call(&mut store, ()).expect("Failed to call function"); 262 | assert_eq!(result, 12); // "test" length (4) * 3 iterations 263 | } 264 | 265 | #[test] 266 | fn test_string_length_comparison() { 267 | let go_source = r#" 268 | package main 269 | 270 | //export longer_string 271 | func longer_string() int { 272 | s1 := "short" 273 | s2 := "much longer string" 274 | 275 | if len(s1) > len(s2) { 276 | return 1 277 | } else if len(s1) < len(s2) { 278 | return 2 279 | } else { 280 | return 3 281 | } 282 | } 283 | "#; 284 | 285 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 286 | 287 | let engine = Engine::default(); 288 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 289 | let mut store = Store::new(&engine, ()); 290 | let instance = 291 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 292 | 293 | let func = instance 294 | .get_typed_func::<(), i32>(&mut store, "longer_string") 295 | .expect("Failed to get function"); 296 | 297 | let result = func.call(&mut store, ()).expect("Failed to call function"); 298 | assert_eq!(result, 2); // s2 is longer 299 | } 300 | 301 | #[test] 302 | fn test_string_with_calculation() { 303 | let go_source = r#" 304 | package main 305 | 306 | //export calculate_with_len 307 | func calculate_with_len() int { 308 | s := "golang" 309 | base := 10 310 | multiplier := len(s) 311 | return base * multiplier 312 | } 313 | "#; 314 | 315 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 316 | 317 | let engine = Engine::default(); 318 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 319 | let mut store = Store::new(&engine, ()); 320 | let instance = 321 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 322 | 323 | let func = instance 324 | .get_typed_func::<(), i32>(&mut store, "calculate_with_len") 325 | .expect("Failed to get function"); 326 | 327 | let result = func.call(&mut store, ()).expect("Failed to call function"); 328 | assert_eq!(result, 60); // 10 * 6 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > This is a work in progress! For updates on the ongoing development, follow me on [Twitter](https://x.com/raphamorims) or [Bluesky](https://bsky.app/profile/raphamorim.bsky.social), or watch the repository. 3 | 4 | # Goiaba 5 | 6 | An experimental Go parser and WebAssembly compiler written in Rust. Goiaba translates Go source code into WebAssembly bytecode, enabling Go programs to run in web browsers and other WebAssembly environments. 7 | 8 | Reasons why I am building it: 9 | 10 | - Create a complete bindgen support for JavaScript (web/nodejs/bun/deno) 11 | - Study of [gollvm](https://go.googlesource.com/gollvm/) project 12 | - It will be used by a code editor I am writing in Rust called [Boo](https://raphamorim.io/building-boo-code-editor-1/) 13 | - Well, mostly learn tbh 14 | 15 | ## Features 16 | 17 | - Parse Go source code into an Abstract Syntax Tree (AST) 18 | - Compile Go functions to WebAssembly modules 19 | - Support for fundamental Go language features (functions, control flow, arithmetic) 20 | - Export Go functions for use in JavaScript/WebAssembly environments 21 | - Export Go functions for use in Rust through C ABI 22 | - Export Go functions for use in Zig through C ABI 23 | - Command-line interface for compilation 24 | - Programmatic API for integration into Rust projects 25 | 26 | ## Installation 27 | 28 | ### As a CLI Tool 29 | 30 | ```bash 31 | cargo install goiaba 32 | ``` 33 | 34 | ### As a Library 35 | 36 | Add to your `Cargo.toml`: 37 | 38 | ```toml 39 | [dependencies] 40 | goiaba = "*" 41 | ``` 42 | 43 | ## Usage 44 | 45 | ### Command Line Interface 46 | 47 | Basic compilation: 48 | 49 | ```bash 50 | goiaba main.go -o main.wasm 51 | ``` 52 | 53 | Compile with verbose output: 54 | 55 | ```bash 56 | goiaba input.go --output output.wasm --verbose 57 | ``` 58 | 59 | Compile multiple files: 60 | 61 | ```bash 62 | goiaba main.go utils.go math.go 63 | ``` 64 | 65 | Build all Go files in a directory: 66 | 67 | ```bash 68 | goiaba build . 69 | ``` 70 | 71 | Generate a complete web project with HTML and JavaScript: 72 | 73 | ```bash 74 | goiaba build . --web ./web-project 75 | ``` 76 | 77 | ### Examples 78 | 79 | #### Basic Function Compilation 80 | 81 | Create a simple Go file with an exported function: 82 | 83 | ```go 84 | // add.go 85 | package main 86 | 87 | //export add 88 | func add(x int, y int) int { 89 | return x + y 90 | } 91 | ``` 92 | 93 | Compile it: 94 | 95 | ```bash 96 | goiaba add.go 97 | ``` 98 | 99 | This generates `add.wasm`, that exports add function purely. 100 | 101 | #### Multi-File Compilation 102 | 103 | Compile multiple Go files together (like Go packages): 104 | 105 | ```bash 106 | # Compile specific files 107 | goiaba main.go utils.go math.go 108 | 109 | # Or use the build command to compile all .go files in a directory 110 | goiaba build . 111 | ``` 112 | 113 | Example with multiple files: 114 | 115 | ```go 116 | // main.go 117 | package main 118 | 119 | //export calculate 120 | func calculate(a int, b int) int { 121 | return add(a, b) * multiply(a, b) 122 | } 123 | ``` 124 | 125 | ```go 126 | // math.go 127 | package main 128 | 129 | func add(x int, y int) int { 130 | return x + y 131 | } 132 | 133 | func multiply(x int, y int) int { 134 | return x * y 135 | } 136 | ``` 137 | 138 | #### Complex Data Structures 139 | 140 | Goiaba supports structs, arrays, and slices: 141 | 142 | ```go 143 | package main 144 | 145 | type Person struct { 146 | name string 147 | age int 148 | } 149 | 150 | //export createPerson 151 | func createPerson(name string, age int) Person { 152 | return Person{name: name, age: age} 153 | } 154 | 155 | //export getPersonName 156 | func getPersonName(p Person) string { 157 | return p.name 158 | } 159 | 160 | //export processArray 161 | func processArray(arr []int) int { 162 | sum := 0 163 | for i := 0; i < len(arr); i++ { 164 | sum += arr[i] 165 | } 166 | return sum 167 | } 168 | ``` 169 | 170 | #### Control Flow 171 | 172 | All standard Go control flow constructs are supported: 173 | 174 | ```go 175 | package main 176 | 177 | //export fibonacci 178 | func fibonacci(n int) int { 179 | if n <= 1 { 180 | return n 181 | } 182 | return fibonacci(n-1) + fibonacci(n-2) 183 | } 184 | 185 | //export findMax 186 | func findMax(arr []int) int { 187 | if len(arr) == 0 { 188 | return 0 189 | } 190 | 191 | max := arr[0] 192 | for i := 1; i < len(arr); i++ { 193 | if arr[i] > max { 194 | max = arr[i] 195 | } 196 | } 197 | return max 198 | } 199 | 200 | //export gradeToLetter 201 | func gradeToLetter(score int) string { 202 | switch { 203 | case score >= 90: 204 | return "A" 205 | case score >= 80: 206 | return "B" 207 | case score >= 70: 208 | return "C" 209 | case score >= 60: 210 | return "D" 211 | default: 212 | return "F" 213 | } 214 | } 215 | ``` 216 | 217 | #### Using Standard Library Functions 218 | 219 | ```go 220 | package main 221 | 222 | import "strings" 223 | 224 | //export greet 225 | func greetName(name string) string { 226 | message := strings.Join([]string{"Hello,", name}, " ") 227 | return message 228 | } 229 | 230 | //export stringLength 231 | func stringLength(s string) int { 232 | return len(s) 233 | } 234 | ``` 235 | 236 | ### Library Usage 237 | 238 | #### Basic Compilation 239 | 240 | ```rust 241 | use goiaba::wasm::compiler::compile_str; 242 | 243 | fn main() { 244 | let go_source = r#" 245 | package main 246 | 247 | //export add 248 | func add(x int, y int) int { 249 | return x + y 250 | } 251 | "#; 252 | 253 | let wasm_bytes = compile_str(go_source) 254 | .expect("Failed to compile Go to WASM"); 255 | 256 | // Write to file or use with a WASM runtime 257 | std::fs::write("output.wasm", wasm_bytes) 258 | .expect("Failed to write WASM file"); 259 | } 260 | ``` 261 | 262 | #### Executing Compiled WASM 263 | 264 | ```rust 265 | use goiaba::wasm::compiler::compile_str; 266 | use wasmtime::{Engine, Instance, Module, Store}; 267 | 268 | fn main() { 269 | let go_source = r#" 270 | package main 271 | 272 | //export add 273 | func add(x int, y int) int { 274 | return x + y 275 | } 276 | "#; 277 | 278 | let wasm_bytes = compile_str(go_source) 279 | .expect("Failed to compile Go to WASM"); 280 | 281 | // Create a WASM runtime 282 | let engine = Engine::default(); 283 | let module = Module::from_binary(&engine, &wasm_bytes) 284 | .expect("Failed to load WASM module"); 285 | let mut store = Store::new(&engine, ()); 286 | 287 | // Instantiate the module 288 | let instance = Instance::new(&mut store, &module, &[]) 289 | .expect("Failed to instantiate module"); 290 | 291 | // Get the exported function 292 | let add_func = instance 293 | .get_typed_func::<(i32, i32), i32>(&mut store, "add") 294 | .expect("Failed to get 'add' function"); 295 | 296 | // Call the function 297 | let result = add_func 298 | .call(&mut store, (5, 3)) 299 | .expect("Failed to call 'add' function"); 300 | 301 | assert_eq!(result, 8); 302 | } 303 | ``` 304 | 305 | #### Parsing Go Source Code 306 | 307 | ```rust 308 | use goiaba::parser::parse_str; 309 | 310 | fn main() { 311 | let source = r#" 312 | package main 313 | 314 | func fibonacci(n int) int { 315 | if n <= 1 { 316 | return n 317 | } 318 | return fibonacci(n-1) + fibonacci(n-2) 319 | } 320 | "#; 321 | 322 | match parse_str(source) { 323 | Ok((objects, file)) => { 324 | println!("Successfully parsed Go source code"); 325 | // Access AST nodes through objects and file 326 | } 327 | Err(err) => { 328 | eprintln!("Parse error: {}", err); 329 | } 330 | } 331 | } 332 | ``` 333 | 334 | ## Export Directive 335 | 336 | To make Go functions callable from WebAssembly, use the `//export` directive: 337 | 338 | ```go 339 | //export function_name 340 | func function_name(param1 int, param2 int) int { 341 | return param1 + param2 342 | } 343 | ``` 344 | 345 | The exported name will be used in the WebAssembly module exports. 346 | 347 | ## Import Support 348 | 349 | Goiaba supports importing functions from standard library packages. Currently supported packages include: 350 | 351 | - `math`: Mathematical functions 352 | - `strings`: String manipulation functions 353 | 354 | When you compile this code, Goiaba will generate WASM imports for the stdlib functions used. The resulting WASM module can be instantiated with appropriate import objects that provide the implementations of these functions. 355 | 356 | ### Supported Functions 357 | 358 | - `math.Sqrt(float64) float64` - Square root function 359 | - `strings.Len(string) int` - String length 360 | - `strings.Join([]string, string) string` - Join strings with separator 361 | 362 | Note: The current implementation provides basic support for these functions. Full Go standard library compatibility is planned for future versions. 363 | 364 | ## Development Status 365 | 366 | ### Completed Features 367 | 368 | - [x] Go source code parsing to Abstract Syntax Tree (AST) 369 | - [x] Translation of Go constructs to WebAssembly representations 370 | - [x] WebAssembly bytecode generation 371 | - [x] Function definitions with parameter and return types 372 | - [x] Variable declarations and assignments 373 | - [x] Control flow statements (if/else, for loops) 374 | - [x] Exportable WASM functions 375 | - [x] Arithmetic operations (+, -, *, /, %) 376 | - [x] Comparison operations (<, >, <=, >=, ==, !=) 377 | - [x] Bitwise operations (&, |, ^, <<, >>) 378 | - [x] Logical operations (&&, ||, !) 379 | - [x] Increment/decrement operators (++, --) 380 | - [x] Recursive function calls 381 | - [x] Struct types with field access and assignment 382 | - [x] Command-line interface 383 | - [x] Multi-file compilation support 384 | - [x] Directory-based package building 385 | - [x] Unary operators (negation, logical NOT) 386 | - [x] Arrays and slices 387 | - [x] String literals and operations 388 | - [x] Switch statements 389 | 390 | ### In Development 391 | 392 | - [ ] Pointer dereferencing and operations 393 | - [ ] Methods on types 394 | - [ ] Interfaces 395 | - [ ] Multiple return values 396 | - [ ] Defer statements 397 | - [ ] Panic and recover 398 | - [x] Package imports 399 | - [ ] Standard library functions 400 | - [ ] Floating-point operations 401 | - [ ] Memory management optimizations 402 | 403 | ### Future Plans 404 | 405 | - [ ] Goroutines and channels 406 | - [ ] Complete standard library support 407 | - [ ] Source maps for debugging 408 | - [ ] Optimization passes for generated WASM 409 | - [ ] JavaScript bindings generation (wasm-bindgen) 410 | - [ ] Rust code generation 411 | - [ ] Zig code generation 412 | - [ ] LLVM-IR target compilation 413 | 414 | ## Architecture 415 | 416 | Goiaba consists of several key components: 417 | 418 | 1. **Parser**: Lexical analysis and syntax parsing of Go source code 419 | 2. **AST**: Internal representation of Go program structure 420 | 3. **Translator**: Conversion from Go AST to WebAssembly IR 421 | 4. **Compiler**: Generation of WebAssembly bytecode 422 | 5. **CLI**: Command-line interface for user interaction 423 | 424 | ## Performance Considerations 425 | 426 | The generated WebAssembly code prioritizes correctness over optimization. Future versions will include: 427 | 428 | - Dead code elimination 429 | - Constant folding 430 | - Register allocation improvements 431 | - Memory access optimization 432 | - Function inlining for small functions 433 | 434 | ## Contributing 435 | 436 | Contributions are welcome. Please ensure linting and tests pass before submitting pull requests: 437 | 438 | ```bash 439 | task lint 440 | task test 441 | ``` 442 | 443 | ## Limitations 444 | 445 | Current limitations of the compiler, yet to be added: 446 | 447 | - No garbage collection (manual memory management) 448 | - Limited standard library support 449 | - No concurrency primitives (goroutines, channels) 450 | - No optimizer passes 451 | 452 | ## License 453 | 454 | BSD-3-Clause 455 | 456 | Copyright (c) 2024-present Raphael Amorim 457 | 458 | ## Acknowledgments 459 | 460 | This project builds upon concepts from the Go language specification and WebAssembly standards. Parser implementation is adapted from the Goscript project. 461 | 462 | ## References 463 | 464 | - https://go.dev/blog/ismmkeynote 465 | -------------------------------------------------------------------------------- /src/parser/token.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 The Goscript Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // 6 | // This code is adapted from the offical Go code written in Go 7 | // with license as follows: 8 | // Copyright 2013 The Go Authors. All rights reserved. 9 | // Use of this source code is governed by a BSD-style 10 | // license that can be found in the LICENSE file. 11 | 12 | #![allow(non_camel_case_types)] 13 | use std::fmt; 14 | 15 | pub(crate) const LOWEST_PREC: usize = 0; // non-operators 16 | 17 | //pub(crate) const UNARY_PREC: usize = 6; 18 | //pub(crate) const HIGHEST_PREC: usize = 7; 19 | 20 | #[derive(Hash, Eq, PartialEq, Clone)] 21 | pub enum Token { 22 | // Special tokens 23 | NONE, 24 | ILLEGAL(TokenData), 25 | EOF, 26 | COMMENT(TokenData), 27 | 28 | // Identifiers and basic type literals 29 | IDENT(TokenData), // main 30 | INT(TokenData), // 12345 31 | FLOAT(TokenData), // 123.45 32 | IMAG(TokenData), // 123.45i 33 | CHAR(TokenData), // 'a' 34 | STRING(TokenData), // "abc" 35 | // Operator 36 | ADD, // + 37 | SUB, // - 38 | MUL, // * 39 | QUO, // / 40 | REM, // % 41 | 42 | AND, // & 43 | OR, // | 44 | XOR, // ^ 45 | SHL, // << 46 | SHR, // >> 47 | AND_NOT, // &^ 48 | 49 | ADD_ASSIGN, // += 50 | SUB_ASSIGN, // -= 51 | MUL_ASSIGN, // *= 52 | QUO_ASSIGN, // /= 53 | REM_ASSIGN, // %= 54 | 55 | AND_ASSIGN, // &= 56 | OR_ASSIGN, // |= 57 | XOR_ASSIGN, // ^= 58 | SHL_ASSIGN, // <<= 59 | SHR_ASSIGN, // >>= 60 | AND_NOT_ASSIGN, // &^= 61 | 62 | LAND, // && 63 | LOR, // || 64 | ARROW, // <- 65 | INC, // ++ 66 | DEC, // -- 67 | 68 | EQL, // == 69 | LSS, // < 70 | GTR, // > 71 | ASSIGN, // = 72 | NOT, // ! 73 | 74 | NEQ, // != 75 | LEQ, // <= 76 | GEQ, // >= 77 | DEFINE, // := 78 | ELLIPSIS, // ... 79 | 80 | LPAREN, // ( 81 | LBRACK, // [ 82 | LBRACE, // { 83 | COMMA, // , 84 | PERIOD, // . 85 | 86 | RPAREN, // ) 87 | RBRACK, // ] 88 | RBRACE, // } 89 | SEMICOLON(TokenData), // ; true if SEMICOLON is NOT inserted by scanner 90 | COLON, // : 91 | 92 | // Keywords 93 | BREAK, 94 | CASE, 95 | CHAN, 96 | CONST, 97 | CONTINUE, 98 | 99 | DEFAULT, 100 | DEFER, 101 | ELSE, 102 | FALLTHROUGH, 103 | FOR, 104 | 105 | FUNC, 106 | GO, 107 | GOTO, 108 | IF, 109 | IMPORT, 110 | 111 | INTERFACE, 112 | MAP, 113 | PACKAGE, 114 | RANGE, 115 | RETURN, 116 | 117 | SELECT, 118 | STRUCT, 119 | SWITCH, 120 | TYPE, 121 | VAR, 122 | } 123 | 124 | pub enum TokenType { 125 | Literal, 126 | Operator, 127 | Keyword, 128 | Other, 129 | } 130 | 131 | impl Token { 132 | pub fn token_property(&self) -> (TokenType, &str) { 133 | match self { 134 | Token::NONE => (TokenType::Other, "NONE"), 135 | Token::ILLEGAL(_) => (TokenType::Other, "ILLEGAL"), 136 | Token::EOF => (TokenType::Other, "EOF"), 137 | Token::COMMENT(_) => (TokenType::Other, "COMMENT"), 138 | Token::IDENT(_) => (TokenType::Literal, "IDENT"), 139 | Token::INT(_) => (TokenType::Literal, "INT"), 140 | Token::FLOAT(_) => (TokenType::Literal, "FLOAT"), 141 | Token::IMAG(_) => (TokenType::Literal, "IMAG"), 142 | Token::CHAR(_) => (TokenType::Literal, "CHAR"), 143 | Token::STRING(_) => (TokenType::Literal, "STRING"), 144 | Token::ADD => (TokenType::Operator, "+"), 145 | Token::SUB => (TokenType::Operator, "-"), 146 | Token::MUL => (TokenType::Operator, "*"), 147 | Token::QUO => (TokenType::Operator, "/"), 148 | Token::REM => (TokenType::Operator, "%"), 149 | Token::AND => (TokenType::Operator, "&"), 150 | Token::OR => (TokenType::Operator, "|"), 151 | Token::XOR => (TokenType::Operator, "^"), 152 | Token::SHL => (TokenType::Operator, "<<"), 153 | Token::SHR => (TokenType::Operator, ">>"), 154 | Token::AND_NOT => (TokenType::Operator, "&^"), 155 | Token::ADD_ASSIGN => (TokenType::Operator, "+="), 156 | Token::SUB_ASSIGN => (TokenType::Operator, "-="), 157 | Token::MUL_ASSIGN => (TokenType::Operator, "*="), 158 | Token::QUO_ASSIGN => (TokenType::Operator, "/="), 159 | Token::REM_ASSIGN => (TokenType::Operator, "%="), 160 | Token::AND_ASSIGN => (TokenType::Operator, "&="), 161 | Token::OR_ASSIGN => (TokenType::Operator, "|="), 162 | Token::XOR_ASSIGN => (TokenType::Operator, "^="), 163 | Token::SHL_ASSIGN => (TokenType::Operator, "<<="), 164 | Token::SHR_ASSIGN => (TokenType::Operator, ">>="), 165 | Token::AND_NOT_ASSIGN => (TokenType::Operator, "&^="), 166 | Token::LAND => (TokenType::Operator, "&&"), 167 | Token::LOR => (TokenType::Operator, "||"), 168 | Token::ARROW => (TokenType::Operator, "<-"), 169 | Token::INC => (TokenType::Operator, "++"), 170 | Token::DEC => (TokenType::Operator, "--"), 171 | Token::EQL => (TokenType::Operator, "=="), 172 | Token::LSS => (TokenType::Operator, "<"), 173 | Token::GTR => (TokenType::Operator, ">"), 174 | Token::ASSIGN => (TokenType::Operator, "="), 175 | Token::NOT => (TokenType::Operator, "!"), 176 | Token::NEQ => (TokenType::Operator, "!="), 177 | Token::LEQ => (TokenType::Operator, "<="), 178 | Token::GEQ => (TokenType::Operator, ">="), 179 | Token::DEFINE => (TokenType::Operator, ":="), 180 | Token::ELLIPSIS => (TokenType::Operator, "..."), 181 | Token::LPAREN => (TokenType::Operator, "("), 182 | Token::LBRACK => (TokenType::Operator, "["), 183 | Token::LBRACE => (TokenType::Operator, "{"), 184 | Token::COMMA => (TokenType::Operator, ","), 185 | Token::PERIOD => (TokenType::Operator, "."), 186 | Token::RPAREN => (TokenType::Operator, ")"), 187 | Token::RBRACK => (TokenType::Operator, "]"), 188 | Token::RBRACE => (TokenType::Operator, "}"), 189 | Token::SEMICOLON(_) => (TokenType::Operator, ";"), 190 | Token::COLON => (TokenType::Operator, ":"), 191 | Token::BREAK => (TokenType::Keyword, "break"), 192 | Token::CASE => (TokenType::Keyword, "case"), 193 | Token::CHAN => (TokenType::Keyword, "chan"), 194 | Token::CONST => (TokenType::Keyword, "const"), 195 | Token::CONTINUE => (TokenType::Keyword, "continue"), 196 | Token::DEFAULT => (TokenType::Keyword, "default"), 197 | Token::DEFER => (TokenType::Keyword, "defer"), 198 | Token::ELSE => (TokenType::Keyword, "else"), 199 | Token::FALLTHROUGH => (TokenType::Keyword, "fallthrough"), 200 | Token::FOR => (TokenType::Keyword, "for"), 201 | Token::FUNC => (TokenType::Keyword, "func"), 202 | Token::GO => (TokenType::Keyword, "go"), 203 | Token::GOTO => (TokenType::Keyword, "goto"), 204 | Token::IF => (TokenType::Keyword, "if"), 205 | Token::IMPORT => (TokenType::Keyword, "import"), 206 | Token::INTERFACE => (TokenType::Keyword, "interface"), 207 | Token::MAP => (TokenType::Keyword, "map"), 208 | Token::PACKAGE => (TokenType::Keyword, "package"), 209 | Token::RANGE => (TokenType::Keyword, "range"), 210 | Token::RETURN => (TokenType::Keyword, "return"), 211 | Token::SELECT => (TokenType::Keyword, "select"), 212 | Token::STRUCT => (TokenType::Keyword, "struct"), 213 | Token::SWITCH => (TokenType::Keyword, "switch"), 214 | Token::TYPE => (TokenType::Keyword, "type"), 215 | Token::VAR => (TokenType::Keyword, "var"), 216 | } 217 | } 218 | 219 | pub fn ident_token(ident: String) -> Token { 220 | match ident.as_str() { 221 | "break" => Token::BREAK, 222 | "case" => Token::CASE, 223 | "chan" => Token::CHAN, 224 | "const" => Token::CONST, 225 | "continue" => Token::CONTINUE, 226 | "default" => Token::DEFAULT, 227 | "defer" => Token::DEFER, 228 | "else" => Token::ELSE, 229 | "fallthrough" => Token::FALLTHROUGH, 230 | "for" => Token::FOR, 231 | "func" => Token::FUNC, 232 | "go" => Token::GO, 233 | "goto" => Token::GOTO, 234 | "if" => Token::IF, 235 | "import" => Token::IMPORT, 236 | "interface" => Token::INTERFACE, 237 | "map" => Token::MAP, 238 | "package" => Token::PACKAGE, 239 | "range" => Token::RANGE, 240 | "return" => Token::RETURN, 241 | "select" => Token::SELECT, 242 | "struct" => Token::STRUCT, 243 | "switch" => Token::SWITCH, 244 | "type" => Token::TYPE, 245 | "var" => Token::VAR, 246 | _ => Token::IDENT(ident.into()), 247 | } 248 | } 249 | 250 | pub fn int1() -> Token { 251 | Token::INT("1".to_owned().into()) 252 | } 253 | 254 | pub fn precedence(&self) -> usize { 255 | match self { 256 | Token::LOR => 1, 257 | Token::LAND => 2, 258 | Token::EQL | Token::NEQ | Token::LSS | Token::LEQ | Token::GTR | Token::GEQ => 3, 259 | Token::ADD | Token::SUB | Token::OR | Token::XOR => 4, 260 | Token::MUL 261 | | Token::QUO 262 | | Token::REM 263 | | Token::SHL 264 | | Token::SHR 265 | | Token::AND 266 | | Token::AND_NOT => 5, 267 | _ => LOWEST_PREC, 268 | } 269 | } 270 | 271 | pub fn text(&self) -> &str { 272 | let (_, t) = self.token_property(); 273 | t 274 | } 275 | 276 | pub fn is_literal(&self) -> bool { 277 | matches!(self.token_property().0, TokenType::Literal) 278 | } 279 | 280 | pub fn is_operator(&self) -> bool { 281 | matches!(self.token_property().0, TokenType::Operator) 282 | } 283 | 284 | pub fn is_keyword(&self) -> bool { 285 | matches!(self.token_property().0, TokenType::Keyword) 286 | } 287 | 288 | pub fn get_literal(&self) -> &str { 289 | match self { 290 | Token::INT(l) => l.as_str(), 291 | Token::FLOAT(l) => l.as_str(), 292 | Token::IMAG(l) => l.as_str(), 293 | Token::CHAR(l) => l.as_str(), 294 | Token::STRING(l) => l.as_str(), 295 | _ => "", 296 | } 297 | } 298 | 299 | pub fn is_stmt_start(&self) -> bool { 300 | matches!( 301 | self, 302 | Token::BREAK 303 | | Token::CONST 304 | | Token::CONTINUE 305 | | Token::DEFER 306 | | Token::FALLTHROUGH 307 | | Token::FOR 308 | | Token::GO 309 | | Token::GOTO 310 | | Token::IF 311 | | Token::RETURN 312 | | Token::SELECT 313 | | Token::SWITCH 314 | | Token::TYPE 315 | | Token::VAR 316 | ) 317 | } 318 | 319 | pub fn is_decl_start(&self) -> bool { 320 | matches!(self, Token::CONST | Token::TYPE | Token::VAR) 321 | } 322 | 323 | pub fn is_expr_end(&self) -> bool { 324 | matches!(self, Token::CONST | Token::TYPE | Token::VAR) 325 | } 326 | } 327 | 328 | impl fmt::Display for Token { 329 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 330 | let text = self.text(); 331 | match self { 332 | Token::IDENT(l) 333 | | Token::INT(l) 334 | | Token::FLOAT(l) 335 | | Token::IMAG(l) 336 | | Token::CHAR(l) 337 | | Token::STRING(l) => f.write_str(l.as_str()), 338 | _ => write!(f, "{}", text), 339 | } 340 | } 341 | } 342 | 343 | impl fmt::Debug for Token { 344 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 345 | let text = self.text(); 346 | match self { 347 | Token::IDENT(l) 348 | | Token::INT(l) 349 | | Token::FLOAT(l) 350 | | Token::IMAG(l) 351 | | Token::CHAR(l) 352 | | Token::STRING(l) => write!(f, "{} {}", text, l.as_str()), 353 | Token::SEMICOLON(real) if !*real.as_bool() => write!(f, "\"{}(inserted)\"", text), 354 | token if token.is_operator() || token.is_keyword() => write!(f, "\"{}\"", text), 355 | _ => write!(f, "{}", text), 356 | } 357 | } 358 | } 359 | 360 | #[derive(Hash, Eq, PartialEq, Clone, Debug)] 361 | enum RawTokenData { 362 | Bool(bool), 363 | Str(String), 364 | StrStr(String, String), 365 | StrChar(String, char), 366 | } 367 | 368 | #[derive(Hash, Eq, PartialEq, Clone, Debug)] 369 | pub struct TokenData(Box); 370 | 371 | impl From for TokenData { 372 | fn from(b: bool) -> Self { 373 | TokenData(Box::new(RawTokenData::Bool(b))) 374 | } 375 | } 376 | 377 | impl From for TokenData { 378 | fn from(s: String) -> Self { 379 | TokenData(Box::new(RawTokenData::Str(s))) 380 | } 381 | } 382 | 383 | impl From<(String, String)> for TokenData { 384 | fn from(ss: (String, String)) -> Self { 385 | TokenData(Box::new(RawTokenData::StrStr(ss.0, ss.1))) 386 | } 387 | } 388 | 389 | impl From<(String, char)> for TokenData { 390 | fn from(ss: (String, char)) -> Self { 391 | TokenData(Box::new(RawTokenData::StrChar(ss.0, ss.1))) 392 | } 393 | } 394 | 395 | impl AsRef for TokenData { 396 | fn as_ref(&self) -> &bool { 397 | self.as_bool() 398 | } 399 | } 400 | 401 | impl AsRef for TokenData { 402 | fn as_ref(&self) -> &String { 403 | self.as_str() 404 | } 405 | } 406 | 407 | impl AsMut for TokenData { 408 | fn as_mut(&mut self) -> &mut String { 409 | self.as_str_mut() 410 | } 411 | } 412 | 413 | impl TokenData { 414 | pub fn as_bool(&self) -> &bool { 415 | match self.0.as_ref() { 416 | RawTokenData::Bool(b) => b, 417 | _ => unreachable!(), 418 | } 419 | } 420 | 421 | pub fn as_str(&self) -> &String { 422 | match self.0.as_ref() { 423 | RawTokenData::Str(s) => s, 424 | RawTokenData::StrStr(s, _) => s, 425 | RawTokenData::StrChar(s, _) => s, 426 | _ => unreachable!(), 427 | } 428 | } 429 | 430 | pub fn as_str_mut(&mut self) -> &mut String { 431 | match self.0.as_mut() { 432 | RawTokenData::Str(s) => s, 433 | RawTokenData::StrStr(s, _) => s, 434 | RawTokenData::StrChar(s, _) => s, 435 | _ => unreachable!(), 436 | } 437 | } 438 | 439 | pub fn as_str_str(&self) -> (&String, &String) { 440 | match self.0.as_ref() { 441 | RawTokenData::StrStr(s1, s2) => (s1, s2), 442 | _ => unreachable!(), 443 | } 444 | } 445 | 446 | pub fn as_str_char(&self) -> (&String, &char) { 447 | match self.0.as_ref() { 448 | RawTokenData::StrChar(s, c) => (s, c), 449 | _ => unreachable!(), 450 | } 451 | } 452 | } 453 | 454 | #[cfg(test)] 455 | mod test { 456 | use super::*; 457 | 458 | #[test] 459 | fn token_test() { 460 | print!( 461 | "testxxxxx \n{}\n{}\n{}\n{}\n. ", 462 | Token::ILLEGAL("asd".to_owned().into()), 463 | Token::SWITCH, 464 | Token::IDENT("some_var".to_owned().into()), 465 | Token::FLOAT("3.14".to_owned().into()), 466 | ); 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /src/bindings/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::parser::ast::{Decl, File}; 2 | use crate::parser::objects::AstObjects; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct ExportedFunction { 6 | pub name: String, 7 | pub export_name: String, 8 | pub param_count: usize, 9 | pub param_names: Vec, 10 | pub has_return: bool, 11 | } 12 | 13 | #[derive(Default)] 14 | pub struct JSBindingGenerator { 15 | exported_functions: Vec, 16 | } 17 | 18 | impl JSBindingGenerator { 19 | pub fn new() -> Self { 20 | Self { 21 | exported_functions: Vec::new(), 22 | } 23 | } 24 | 25 | pub fn analyze_go_source(&mut self, source: &str, ast: &File, objs: &AstObjects) { 26 | self.exported_functions.clear(); 27 | 28 | for decl in &ast.decls { 29 | if let Decl::Func(func_key) = decl { 30 | let func_decl = &objs.fdecls[*func_key]; 31 | 32 | if let Some(export_name) = 33 | extract_export_name(source, &objs.idents[func_decl.name].name) 34 | { 35 | let func_type = &objs.ftypes[func_decl.typ]; 36 | 37 | let mut param_names = Vec::new(); 38 | let mut param_count = 0; 39 | 40 | for field_key in &func_type.params.list { 41 | let field = &objs.fields[*field_key]; 42 | for name_key in &field.names { 43 | param_names.push(objs.idents[*name_key].name.clone()); 44 | param_count += 1; 45 | } 46 | } 47 | 48 | let has_return = func_type.results.is_some() 49 | && !func_type.results.as_ref().unwrap().list.is_empty(); 50 | 51 | self.exported_functions.push(ExportedFunction { 52 | name: objs.idents[func_decl.name].name.clone(), 53 | export_name, 54 | param_count, 55 | param_names, 56 | has_return, 57 | }); 58 | } 59 | } 60 | } 61 | } 62 | 63 | pub fn generate_html(&self, _wasm_filename: &str, project_name: &str) -> String { 64 | let function_demos = self.generate_function_demos(); 65 | format!( 66 | r#" 67 | 68 | 69 | 70 | 71 | {} - Go WASM Demo 72 | 149 | 150 | 151 |
152 |

{}

153 |

Interactive demo of Go functions compiled to WebAssembly

154 | 155 |
Loading WebAssembly module...
156 | 158 |
159 | 160 | 161 | 162 | "#, 163 | project_name, project_name, function_demos 164 | ) 165 | } 166 | 167 | fn generate_function_demos(&self) -> String { 168 | self.exported_functions.iter().map(|func| { 169 | let inputs = (0..func.param_count) 170 | .map(|i| { 171 | let param_name = if let Some(name) = func.param_names.get(i) { 172 | name.as_str() 173 | } else { 174 | "param" 175 | }; 176 | format!(r#" "#, 177 | func.export_name, i, param_name, if i < 2 { (i + 1) * 5 } else { 1 }) 178 | }) 179 | .collect::>() 180 | .join("\n"); 181 | 182 | format!(r#"
183 |
{export_name}({param_signature})
184 | {inputs} 185 | 186 | 187 |
"#, 188 | export_name = func.export_name, 189 | param_signature = func.param_names.join(", "), 190 | inputs = inputs 191 | ) 192 | }).collect::>().join("\n") 193 | } 194 | 195 | pub fn generate_javascript(&self, wasm_filename: &str) -> String { 196 | let function_implementations = self 197 | .exported_functions 198 | .iter() 199 | .map(|func| { 200 | let param_list = (0..func.param_count) 201 | .map(|i| { 202 | format!( 203 | "parseInt(document.getElementById('{}_param_{}').value)", 204 | func.export_name, i 205 | ) 206 | }) 207 | .collect::>() 208 | .join(", "); 209 | 210 | format!( 211 | r#"window.call_{export_name} = function() {{ 212 | try {{ 213 | const params = [{param_list}]; 214 | 215 | // Validate inputs 216 | for (let i = 0; i < params.length; i++) {{ 217 | if (isNaN(params[i])) {{ 218 | showResult('{export_name}', 'Error: Invalid number in parameter ' + (i + 1), true); 219 | return; 220 | }} 221 | }} 222 | 223 | const result = wasmExports.{export_name}({param_list}); 224 | showResult('{export_name}', `Result: ${{result}}`, false); 225 | }} catch (error) {{ 226 | showResult('{export_name}', `Error: ${{error.message}}`, true); 227 | }} 228 | }};"#, 229 | export_name = func.export_name, 230 | param_list = param_list 231 | ) 232 | }) 233 | .collect::>() 234 | .join("\n\n"); 235 | 236 | format!( 237 | r#"// Generated JavaScript bindings for Go WASM module 238 | let wasmExports = {{}}; 239 | 240 | async function loadWasm() {{ 241 | try {{ 242 | const wasmModule = await WebAssembly.instantiateStreaming(fetch('{wasm_filename}')); 243 | wasmExports = wasmModule.instance.exports; 244 | 245 | console.log('WASM module loaded successfully'); 246 | console.log('Available exports:', Object.keys(wasmExports)); 247 | 248 | // Hide loading message and show content 249 | document.getElementById('loading').style.display = 'none'; 250 | document.getElementById('content').style.display = 'block'; 251 | 252 | }} catch (error) {{ 253 | console.error('Error loading WASM:', error); 254 | document.getElementById('loading').innerHTML = ` 255 |
256 | Error loading WebAssembly module: ${{error.message}} 257 |

258 | Make sure the WASM file '{wasm_filename}' is in the same directory as this HTML file. 259 |
260 | `; 261 | }} 262 | }} 263 | 264 | function showResult(functionName, message, isError) {{ 265 | const resultDiv = document.getElementById(functionName + '_result'); 266 | resultDiv.textContent = message; 267 | resultDiv.className = isError ? 'result error' : 'result'; 268 | resultDiv.style.display = 'block'; 269 | }} 270 | 271 | // Function implementations 272 | {function_implementations} 273 | 274 | // Load WASM when page loads 275 | loadWasm();"#, 276 | wasm_filename = wasm_filename, 277 | function_implementations = function_implementations 278 | ) 279 | } 280 | 281 | pub fn generate_typescript_definitions(&self) -> String { 282 | let function_definitions = self 283 | .exported_functions 284 | .iter() 285 | .map(|func| { 286 | let params = func 287 | .param_names 288 | .iter() 289 | .map(|name| format!("{}: number", name)) 290 | .collect::>() 291 | .join(", "); 292 | 293 | let return_type = if func.has_return { "number" } else { "void" }; 294 | 295 | format!(" {}: ({}) => {};", func.export_name, params, return_type) 296 | }) 297 | .collect::>() 298 | .join("\n"); 299 | 300 | format!( 301 | r#"// TypeScript definitions for Go WASM module 302 | 303 | export interface GoWasmExports {{ 304 | {function_definitions} 305 | }} 306 | 307 | export interface GoWasmModule {{ 308 | instance: {{ 309 | exports: GoWasmExports; 310 | }}; 311 | }} 312 | 313 | declare global {{ 314 | interface Window {{ 315 | {global_functions} }} 316 | }} 317 | 318 | export function loadGoWasm(wasmPath: string): Promise;"#, 319 | function_definitions = function_definitions, 320 | global_functions = self 321 | .exported_functions 322 | .iter() 323 | .map(|func| { format!(" call_{}: () => void;", func.export_name) }) 324 | .collect::>() 325 | .join("\n") 326 | ) 327 | } 328 | 329 | pub fn generate_readme(&self, project_name: &str, wasm_filename: &str) -> String { 330 | let function_list = self 331 | .exported_functions 332 | .iter() 333 | .map(|func| { 334 | format!( 335 | "- `{}({})` - {}", 336 | func.export_name, 337 | func.param_names.join(", "), 338 | if func.has_return { 339 | "returns number" 340 | } else { 341 | "no return value" 342 | } 343 | ) 344 | }) 345 | .collect::>() 346 | .join("\n"); 347 | 348 | let export_names = self 349 | .exported_functions 350 | .iter() 351 | .map(|f| f.export_name.as_str()) 352 | .collect::>() 353 | .join(", "); 354 | 355 | let usage_examples = self 356 | .exported_functions 357 | .iter() 358 | .take(3) 359 | .map(|func| { 360 | let example_params = (0..func.param_count) 361 | .map(|i| (i + 1) * 5) 362 | .collect::>(); 363 | let params_str = example_params 364 | .iter() 365 | .map(|p| p.to_string()) 366 | .collect::>() 367 | .join(", "); 368 | format!("console.log({}({}));", func.export_name, params_str) 369 | }) 370 | .collect::>() 371 | .join("\n"); 372 | 373 | format!( 374 | r#"# {project_name} 375 | 376 | This project contains Go functions compiled to WebAssembly with automatically generated JavaScript bindings. 377 | 378 | ## Files 379 | 380 | - `{wasm_filename}` - Compiled WebAssembly module 381 | - `index.html` - Interactive demo page 382 | - `main.js` - JavaScript bindings and demo logic 383 | - `types.d.ts` - TypeScript definitions 384 | - `README.md` - This file 385 | 386 | ## Available Functions 387 | 388 | {function_list} 389 | 390 | ## Usage 391 | 392 | ### Web Browser 393 | 394 | 1. Serve the files from a web server (required for WASM loading): 395 | ```bash 396 | # Using Python 397 | python -m http.server 8000 398 | 399 | # Using Node.js 400 | npx serve . 401 | 402 | # Using any other static file server 403 | ``` 404 | 405 | 2. Open `http://localhost:8000` in your browser 406 | 407 | 3. Use the interactive demo to test the functions 408 | 409 | ### Programmatic Usage 410 | 411 | ```javascript 412 | // Load the WASM module 413 | const wasmModule = await WebAssembly.instantiateStreaming(fetch('{wasm_filename}')); 414 | const {{ {export_names} }} = wasmModule.instance.exports; 415 | 416 | // Call the functions 417 | {usage_examples} 418 | ``` 419 | 420 | ### TypeScript Support 421 | 422 | Import the type definitions for better development experience: 423 | 424 | ```typescript 425 | import type {{ GoWasmExports }} from './types'; 426 | 427 | const wasmModule = await WebAssembly.instantiateStreaming(fetch('{wasm_filename}')); 428 | const exports: GoWasmExports = wasmModule.instance.exports; 429 | ``` 430 | 431 | ## Building 432 | 433 | This project was generated by the Goiaba Go-to-WASM compiler. 434 | 435 | To rebuild the WASM module: 436 | 437 | ```bash 438 | goiaba source.go -o {wasm_filename} -w . 439 | ``` 440 | 441 | ## Browser Compatibility 442 | 443 | Requires a modern browser with WebAssembly support: 444 | - Chrome 57+ 445 | - Firefox 52+ 446 | - Safari 11+ 447 | - Edge 16+ 448 | "#, 449 | project_name = project_name, 450 | wasm_filename = wasm_filename, 451 | function_list = function_list, 452 | export_names = export_names, 453 | usage_examples = usage_examples 454 | ) 455 | } 456 | } 457 | 458 | fn extract_export_name(source: &str, func_name: &str) -> Option { 459 | let lines: Vec<&str> = source.lines().collect(); 460 | 461 | for (i, line) in lines.iter().enumerate() { 462 | if line.contains(&format!("func {}", func_name)) { 463 | for j in (0..i).rev() { 464 | let check_line = lines[j].trim(); 465 | if check_line.starts_with("//export ") { 466 | let export_name = check_line.strip_prefix("//export ").unwrap().trim(); 467 | if !export_name.is_empty() { 468 | return Some(export_name.to_string()); 469 | } 470 | } 471 | if !check_line.is_empty() && !check_line.starts_with("//") { 472 | break; 473 | } 474 | } 475 | break; 476 | } 477 | } 478 | None 479 | } 480 | -------------------------------------------------------------------------------- /tests/arrays_slices_tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2025-present Raphael Amorim. All rights reserved. 2 | // Use of this source code is governed by a BSD-3-Clause 3 | // license that can be found in the LICENSE file. 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use goiaba::wasm::compiler::compile_str; 8 | use wasmtime::{Engine, Instance, Module, Store}; 9 | 10 | #[test] 11 | fn test_array_literal_creation() { 12 | let go_source = r#" 13 | package main 14 | 15 | //export get_first 16 | func get_first() int { 17 | arr := []int{10, 20, 30} 18 | return arr[0] 19 | } 20 | "#; 21 | 22 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 23 | 24 | let engine = Engine::default(); 25 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 26 | let mut store = Store::new(&engine, ()); 27 | let instance = 28 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 29 | 30 | let func = instance 31 | .get_typed_func::<(), i32>(&mut store, "get_first") 32 | .expect("Failed to get 'get_first' function"); 33 | 34 | let result = func.call(&mut store, ()).expect("Failed to call function"); 35 | assert_eq!(result, 10); 36 | } 37 | 38 | #[test] 39 | fn test_array_index_access() { 40 | let go_source = r#" 41 | package main 42 | 43 | //export get_element 44 | func get_element(index int) int { 45 | arr := []int{5, 10, 15, 20, 25} 46 | return arr[index] 47 | } 48 | "#; 49 | 50 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 51 | 52 | let engine = Engine::default(); 53 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 54 | let mut store = Store::new(&engine, ()); 55 | let instance = 56 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 57 | 58 | let func = instance 59 | .get_typed_func::(&mut store, "get_element") 60 | .expect("Failed to get function"); 61 | 62 | assert_eq!(func.call(&mut store, 0).unwrap(), 5); 63 | assert_eq!(func.call(&mut store, 2).unwrap(), 15); 64 | assert_eq!(func.call(&mut store, 4).unwrap(), 25); 65 | } 66 | 67 | #[test] 68 | fn test_array_index_assignment() { 69 | let go_source = r#" 70 | package main 71 | 72 | //export set_and_get 73 | func set_and_get() int { 74 | arr := []int{1, 2, 3} 75 | arr[1] = 99 76 | return arr[1] 77 | } 78 | "#; 79 | 80 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 81 | 82 | let engine = Engine::default(); 83 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 84 | let mut store = Store::new(&engine, ()); 85 | let instance = 86 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 87 | 88 | let func = instance 89 | .get_typed_func::<(), i32>(&mut store, "set_and_get") 90 | .expect("Failed to get function"); 91 | 92 | let result = func.call(&mut store, ()).expect("Failed to call function"); 93 | assert_eq!(result, 99); 94 | } 95 | 96 | #[test] 97 | fn test_array_sum() { 98 | let go_source = r#" 99 | package main 100 | 101 | //export sum_array 102 | func sum_array() int { 103 | arr := []int{10, 20, 30, 40} 104 | sum := 0 105 | for i := 0; i < 4; i++ { 106 | sum = sum + arr[i] 107 | } 108 | return sum 109 | } 110 | "#; 111 | 112 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 113 | 114 | let engine = Engine::default(); 115 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 116 | let mut store = Store::new(&engine, ()); 117 | let instance = 118 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 119 | 120 | let func = instance 121 | .get_typed_func::<(), i32>(&mut store, "sum_array") 122 | .expect("Failed to get function"); 123 | 124 | let result = func.call(&mut store, ()).expect("Failed to call function"); 125 | assert_eq!(result, 100); // 10 + 20 + 30 + 40 126 | } 127 | 128 | #[test] 129 | fn test_array_modification_loop() { 130 | let go_source = r#" 131 | package main 132 | 133 | //export double_elements 134 | func double_elements() int { 135 | arr := []int{1, 2, 3, 4, 5} 136 | for i := 0; i < 5; i++ { 137 | arr[i] = arr[i] * 2 138 | } 139 | return arr[2] 140 | } 141 | "#; 142 | 143 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 144 | 145 | let engine = Engine::default(); 146 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 147 | let mut store = Store::new(&engine, ()); 148 | let instance = 149 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 150 | 151 | let func = instance 152 | .get_typed_func::<(), i32>(&mut store, "double_elements") 153 | .expect("Failed to get function"); 154 | 155 | let result = func.call(&mut store, ()).expect("Failed to call function"); 156 | assert_eq!(result, 6); // 3 * 2 157 | } 158 | 159 | #[test] 160 | fn test_array_find_max() { 161 | let go_source = r#" 162 | package main 163 | 164 | //export find_max 165 | func find_max() int { 166 | arr := []int{3, 7, 2, 9, 1} 167 | max := arr[0] 168 | for i := 1; i < 5; i++ { 169 | if arr[i] > max { 170 | max = arr[i] 171 | } 172 | } 173 | return max 174 | } 175 | "#; 176 | 177 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 178 | 179 | let engine = Engine::default(); 180 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 181 | let mut store = Store::new(&engine, ()); 182 | let instance = 183 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 184 | 185 | let func = instance 186 | .get_typed_func::<(), i32>(&mut store, "find_max") 187 | .expect("Failed to get function"); 188 | 189 | let result = func.call(&mut store, ()).expect("Failed to call function"); 190 | assert_eq!(result, 9); 191 | } 192 | 193 | #[test] 194 | fn test_array_swap_elements() { 195 | let go_source = r#" 196 | package main 197 | 198 | //export swap_and_return 199 | func swap_and_return() int { 200 | arr := []int{100, 200} 201 | temp := arr[0] 202 | arr[0] = arr[1] 203 | arr[1] = temp 204 | return arr[0] 205 | } 206 | "#; 207 | 208 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 209 | 210 | let engine = Engine::default(); 211 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 212 | let mut store = Store::new(&engine, ()); 213 | let instance = 214 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 215 | 216 | let func = instance 217 | .get_typed_func::<(), i32>(&mut store, "swap_and_return") 218 | .expect("Failed to get function"); 219 | 220 | let result = func.call(&mut store, ()).expect("Failed to call function"); 221 | assert_eq!(result, 200); 222 | } 223 | 224 | #[test] 225 | fn test_array_count_even() { 226 | let go_source = r#" 227 | package main 228 | 229 | //export count_even 230 | func count_even() int { 231 | arr := []int{1, 2, 3, 4, 5, 6} 232 | count := 0 233 | for i := 0; i < 6; i++ { 234 | if arr[i] % 2 == 0 { 235 | count++ 236 | } 237 | } 238 | return count 239 | } 240 | "#; 241 | 242 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 243 | 244 | let engine = Engine::default(); 245 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 246 | let mut store = Store::new(&engine, ()); 247 | let instance = 248 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 249 | 250 | let func = instance 251 | .get_typed_func::<(), i32>(&mut store, "count_even") 252 | .expect("Failed to get function"); 253 | 254 | let result = func.call(&mut store, ()).expect("Failed to call function"); 255 | assert_eq!(result, 3); // 2, 4, 6 256 | } 257 | 258 | #[test] 259 | fn test_array_reverse() { 260 | let go_source = r#" 261 | package main 262 | 263 | //export reverse_array 264 | func reverse_array() int { 265 | arr := []int{1, 2, 3, 4, 5} 266 | left := 0 267 | right := 4 268 | 269 | for left < right { 270 | temp := arr[left] 271 | arr[left] = arr[right] 272 | arr[right] = temp 273 | left++ 274 | right-- 275 | } 276 | 277 | return arr[0] 278 | } 279 | "#; 280 | 281 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 282 | 283 | let engine = Engine::default(); 284 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 285 | let mut store = Store::new(&engine, ()); 286 | let instance = 287 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 288 | 289 | let func = instance 290 | .get_typed_func::<(), i32>(&mut store, "reverse_array") 291 | .expect("Failed to get function"); 292 | 293 | let result = func.call(&mut store, ()).expect("Failed to call function"); 294 | assert_eq!(result, 5); // First element after reversal 295 | } 296 | 297 | #[test] 298 | fn test_array_linear_search() { 299 | let go_source = r#" 300 | package main 301 | 302 | //export search 303 | func search(target int) int { 304 | arr := []int{10, 25, 30, 45, 50} 305 | for i := 0; i < 5; i++ { 306 | if arr[i] == target { 307 | return i 308 | } 309 | } 310 | return -1 311 | } 312 | "#; 313 | 314 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 315 | 316 | let engine = Engine::default(); 317 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 318 | let mut store = Store::new(&engine, ()); 319 | let instance = 320 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 321 | 322 | let func = instance 323 | .get_typed_func::(&mut store, "search") 324 | .expect("Failed to get function"); 325 | 326 | assert_eq!(func.call(&mut store, 30).unwrap(), 2); 327 | assert_eq!(func.call(&mut store, 50).unwrap(), 4); 328 | assert_eq!(func.call(&mut store, 100).unwrap(), -1); 329 | } 330 | 331 | #[test] 332 | fn test_nested_array_operations() { 333 | let go_source = r#" 334 | package main 335 | 336 | //export complex_operation 337 | func complex_operation() int { 338 | arr1 := []int{1, 2, 3} 339 | arr2 := []int{4, 5, 6} 340 | 341 | result := 0 342 | for i := 0; i < 3; i++ { 343 | result = result + (arr1[i] * arr2[i]) 344 | } 345 | 346 | return result 347 | } 348 | "#; 349 | 350 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 351 | 352 | let engine = Engine::default(); 353 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 354 | let mut store = Store::new(&engine, ()); 355 | let instance = 356 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 357 | 358 | let func = instance 359 | .get_typed_func::<(), i32>(&mut store, "complex_operation") 360 | .expect("Failed to get function"); 361 | 362 | let result = func.call(&mut store, ()).expect("Failed to call function"); 363 | assert_eq!(result, 32); // 1*4 + 2*5 + 3*6 = 4 + 10 + 18 364 | } 365 | 366 | #[test] 367 | fn test_array_accumulator() { 368 | let go_source = r#" 369 | package main 370 | 371 | //export accumulate 372 | func accumulate() int { 373 | arr := []int{5, 10, 15} 374 | arr[0] = arr[0] + arr[1] 375 | arr[1] = arr[0] + arr[2] 376 | return arr[1] 377 | } 378 | "#; 379 | 380 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 381 | 382 | let engine = Engine::default(); 383 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 384 | let mut store = Store::new(&engine, ()); 385 | let instance = 386 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 387 | 388 | let func = instance 389 | .get_typed_func::<(), i32>(&mut store, "accumulate") 390 | .expect("Failed to get function"); 391 | 392 | let result = func.call(&mut store, ()).expect("Failed to call function"); 393 | assert_eq!(result, 30); // (5+10) + 15 = 30 394 | } 395 | 396 | #[test] 397 | fn test_array_conditional_update() { 398 | let go_source = r#" 399 | package main 400 | 401 | //export conditional_update 402 | func conditional_update() int { 403 | arr := []int{1, 5, 10, 15, 20} 404 | for i := 0; i < 5; i++ { 405 | if arr[i] >= 10 { 406 | arr[i] = 0 407 | } 408 | } 409 | sum := 0 410 | for i := 0; i < 5; i++ { 411 | sum = sum + arr[i] 412 | } 413 | return sum 414 | } 415 | "#; 416 | 417 | let wasm_bytes = compile_str(go_source).expect("Failed to compile Go to WASM"); 418 | 419 | let engine = Engine::default(); 420 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load WASM module"); 421 | let mut store = Store::new(&engine, ()); 422 | let instance = 423 | Instance::new(&mut store, &module, &[]).expect("Failed to instantiate module"); 424 | 425 | let func = instance 426 | .get_typed_func::<(), i32>(&mut store, "conditional_update") 427 | .expect("Failed to get function"); 428 | 429 | let result = func.call(&mut store, ()).expect("Failed to call function"); 430 | assert_eq!(result, 6); // 1 + 5 + 0 + 0 + 0 431 | } 432 | } 433 | -------------------------------------------------------------------------------- /tests/examples_tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2025-present Raphael Amorim. All rights reserved. 2 | // Use of this source code is governed by a BSD-3-Clause 3 | // license that can be found in the LICENSE file. 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use goiaba::wasm::compiler::compile_str; 8 | use std::fs; 9 | use wasmtime::{Engine, Instance, Module, Store}; 10 | 11 | /// Helper function to compile and validate a Go source file 12 | fn compile_and_validate(filename: &str) -> Vec { 13 | let path = format!("go_examples/{}", filename); 14 | let source = 15 | fs::read_to_string(&path).unwrap_or_else(|_| panic!("Failed to read {}", path)); 16 | 17 | let wasm_bytes = compile_str(&source) 18 | .unwrap_or_else(|e| panic!("Failed to compile {}: {}", filename, e)); 19 | 20 | // Validate the WASM 21 | let engine = Engine::default(); 22 | Module::from_binary(&engine, &wasm_bytes) 23 | .unwrap_or_else(|e| panic!("Invalid WASM generated from {}: {}", filename, e)); 24 | 25 | wasm_bytes 26 | } 27 | 28 | /// Helper to get a typed function from WASM instance 29 | fn get_func( 30 | instance: &Instance, 31 | store: &mut Store<()>, 32 | name: &str, 33 | ) -> wasmtime::TypedFunc 34 | where 35 | Params: wasmtime::WasmParams, 36 | Results: wasmtime::WasmResults, 37 | { 38 | instance 39 | .get_typed_func::(store, name) 40 | .unwrap_or_else(|e| panic!("Failed to get function '{}': {}", name, e)) 41 | } 42 | 43 | #[test] 44 | fn test_calculator_example() { 45 | let wasm_bytes = compile_and_validate("calculator.go"); 46 | 47 | let engine = Engine::default(); 48 | let module = Module::from_binary(&engine, &wasm_bytes).unwrap(); 49 | let mut store = Store::new(&engine, ()); 50 | let instance = Instance::new(&mut store, &module, &[]).unwrap(); 51 | 52 | // Test basic operations 53 | let add = get_func::<(i32, i32), i32>(&instance, &mut store, "add"); 54 | assert_eq!(add.call(&mut store, (5, 3)).unwrap(), 8); 55 | assert_eq!(add.call(&mut store, (100, 200)).unwrap(), 300); 56 | 57 | let multiply = get_func::<(i32, i32), i32>(&instance, &mut store, "multiply"); 58 | assert_eq!(multiply.call(&mut store, (4, 7)).unwrap(), 28); 59 | assert_eq!(multiply.call(&mut store, (10, 10)).unwrap(), 100); 60 | 61 | // Test recursive functions 62 | let factorial = get_func::(&instance, &mut store, "factorial"); 63 | assert_eq!(factorial.call(&mut store, 5).unwrap(), 120); 64 | assert_eq!(factorial.call(&mut store, 0).unwrap(), 1); 65 | assert_eq!(factorial.call(&mut store, 7).unwrap(), 5040); 66 | 67 | let fibonacci = get_func::(&instance, &mut store, "fibonacci"); 68 | assert_eq!(fibonacci.call(&mut store, 0).unwrap(), 0); 69 | assert_eq!(fibonacci.call(&mut store, 1).unwrap(), 1); 70 | assert_eq!(fibonacci.call(&mut store, 10).unwrap(), 55); 71 | 72 | // Test conditional logic 73 | let max = get_func::<(i32, i32), i32>(&instance, &mut store, "max"); 74 | assert_eq!(max.call(&mut store, (10, 5)).unwrap(), 10); 75 | assert_eq!(max.call(&mut store, (3, 8)).unwrap(), 8); 76 | 77 | let abs = get_func::(&instance, &mut store, "abs"); 78 | assert_eq!(abs.call(&mut store, -5).unwrap(), 5); 79 | assert_eq!(abs.call(&mut store, 10).unwrap(), 10); 80 | 81 | let sign = get_func::(&instance, &mut store, "sign"); 82 | assert_eq!(sign.call(&mut store, 42).unwrap(), 1); 83 | assert_eq!(sign.call(&mut store, -42).unwrap(), -1); 84 | assert_eq!(sign.call(&mut store, 0).unwrap(), 0); 85 | 86 | // Test loops 87 | let sum_to_n = get_func::(&instance, &mut store, "sum_to_n"); 88 | assert_eq!(sum_to_n.call(&mut store, 10).unwrap(), 45); 89 | 90 | let power = get_func::<(i32, i32), i32>(&instance, &mut store, "power"); 91 | assert_eq!(power.call(&mut store, (2, 8)).unwrap(), 256); 92 | assert_eq!(power.call(&mut store, (3, 4)).unwrap(), 81); 93 | 94 | // Test bitwise operations 95 | let bitwise_and = get_func::<(i32, i32), i32>(&instance, &mut store, "bitwise_and"); 96 | assert_eq!(bitwise_and.call(&mut store, (12, 10)).unwrap(), 8); 97 | 98 | let bitwise_or = get_func::<(i32, i32), i32>(&instance, &mut store, "bitwise_or"); 99 | assert_eq!(bitwise_or.call(&mut store, (12, 10)).unwrap(), 14); 100 | 101 | let left_shift = get_func::<(i32, i32), i32>(&instance, &mut store, "left_shift"); 102 | assert_eq!(left_shift.call(&mut store, (1, 3)).unwrap(), 8); 103 | } 104 | 105 | #[test] 106 | fn test_arrays_example() { 107 | let wasm_bytes = compile_and_validate("arrays.go"); 108 | 109 | let engine = Engine::default(); 110 | let module = Module::from_binary(&engine, &wasm_bytes).unwrap(); 111 | let mut store = Store::new(&engine, ()); 112 | let instance = Instance::new(&mut store, &module, &[]).unwrap(); 113 | 114 | let array_sum = get_func::<(), i32>(&instance, &mut store, "array_sum"); 115 | assert_eq!(array_sum.call(&mut store, ()).unwrap(), 150); // 10+20+30+40+50 116 | 117 | let find_max = get_func::<(), i32>(&instance, &mut store, "find_max"); 118 | assert_eq!(find_max.call(&mut store, ()).unwrap(), 12); 119 | 120 | let find_min = get_func::<(), i32>(&instance, &mut store, "find_min"); 121 | assert_eq!(find_min.call(&mut store, ()).unwrap(), 3); 122 | 123 | let reverse_array = get_func::<(), i32>(&instance, &mut store, "reverse_array"); 124 | assert_eq!(reverse_array.call(&mut store, ()).unwrap(), 5); 125 | 126 | let linear_search = get_func::(&instance, &mut store, "linear_search"); 127 | assert_eq!(linear_search.call(&mut store, 30).unwrap(), 2); 128 | assert_eq!(linear_search.call(&mut store, 50).unwrap(), 4); 129 | assert_eq!(linear_search.call(&mut store, 99).unwrap(), -1); 130 | 131 | let count_even = get_func::<(), i32>(&instance, &mut store, "count_even"); 132 | assert_eq!(count_even.call(&mut store, ()).unwrap(), 5); 133 | 134 | let array_product = get_func::<(), i32>(&instance, &mut store, "array_product"); 135 | assert_eq!(array_product.call(&mut store, ()).unwrap(), 120); 136 | 137 | let count_positive = get_func::<(), i32>(&instance, &mut store, "count_positive"); 138 | assert_eq!(count_positive.call(&mut store, ()).unwrap(), 3); 139 | 140 | let array_average = get_func::<(), i32>(&instance, &mut store, "array_average"); 141 | assert_eq!(array_average.call(&mut store, ()).unwrap(), 30); 142 | } 143 | 144 | #[test] 145 | fn test_strings_example() { 146 | let wasm_bytes = compile_and_validate("strings.go"); 147 | 148 | let engine = Engine::default(); 149 | let module = Module::from_binary(&engine, &wasm_bytes).unwrap(); 150 | let mut store = Store::new(&engine, ()); 151 | let instance = Instance::new(&mut store, &module, &[]).unwrap(); 152 | 153 | let string_length = get_func::<(), i32>(&instance, &mut store, "string_length"); 154 | assert_eq!(string_length.call(&mut store, ()).unwrap(), 13); 155 | 156 | let empty_string_check = get_func::<(), i32>(&instance, &mut store, "empty_string_check"); 157 | assert_eq!(empty_string_check.call(&mut store, ()).unwrap(), 1); 158 | 159 | let compare_lengths = get_func::<(), i32>(&instance, &mut store, "compare_lengths"); 160 | assert_eq!(compare_lengths.call(&mut store, ()).unwrap(), -1); 161 | 162 | let total_chars = get_func::<(), i32>(&instance, &mut store, "total_chars"); 163 | assert_eq!(total_chars.call(&mut store, ()).unwrap(), 11); 164 | 165 | let is_long_string = get_func::<(), i32>(&instance, &mut store, "is_long_string"); 166 | assert_eq!(is_long_string.call(&mut store, ()).unwrap(), 1); 167 | 168 | let validate_input = get_func::<(), i32>(&instance, &mut store, "validate_input"); 169 | assert_eq!(validate_input.call(&mut store, ()).unwrap(), 1); 170 | 171 | let escape_sequences = get_func::<(), i32>(&instance, &mut store, "escape_sequences"); 172 | assert_eq!(escape_sequences.call(&mut store, ()).unwrap(), 15); 173 | 174 | let string_with_numbers = get_func::<(), i32>(&instance, &mut store, "string_with_numbers"); 175 | assert_eq!(string_with_numbers.call(&mut store, ()).unwrap(), 11); 176 | 177 | let max_string_length = get_func::<(), i32>(&instance, &mut store, "max_string_length"); 178 | assert_eq!(max_string_length.call(&mut store, ()).unwrap(), 21); 179 | } 180 | 181 | #[test] 182 | fn test_structs_example() { 183 | let wasm_bytes = compile_and_validate("structs.go"); 184 | 185 | let engine = Engine::default(); 186 | let module = Module::from_binary(&engine, &wasm_bytes).unwrap(); 187 | let mut store = Store::new(&engine, ()); 188 | let instance = Instance::new(&mut store, &module, &[]).unwrap(); 189 | 190 | let create_point = get_func::<(i32, i32), i32>(&instance, &mut store, "create_point"); 191 | assert_eq!(create_point.call(&mut store, (3, 4)).unwrap(), 7); 192 | 193 | let point_distance_squared = 194 | get_func::<(i32, i32, i32, i32), i32>(&instance, &mut store, "point_distance_squared"); 195 | assert_eq!( 196 | point_distance_squared 197 | .call(&mut store, (0, 0, 3, 4)) 198 | .unwrap(), 199 | 25 200 | ); 201 | 202 | let rectangle_area = get_func::<(i32, i32), i32>(&instance, &mut store, "rectangle_area"); 203 | assert_eq!(rectangle_area.call(&mut store, (5, 10)).unwrap(), 50); 204 | 205 | let rectangle_perimeter = 206 | get_func::<(i32, i32), i32>(&instance, &mut store, "rectangle_perimeter"); 207 | assert_eq!(rectangle_perimeter.call(&mut store, (5, 10)).unwrap(), 30); 208 | 209 | let circle_area_approx = get_func::(&instance, &mut store, "circle_area_approx"); 210 | assert_eq!(circle_area_approx.call(&mut store, 5).unwrap(), 75); 211 | 212 | let update_point = 213 | get_func::<(i32, i32, i32, i32), i32>(&instance, &mut store, "update_point"); 214 | assert_eq!(update_point.call(&mut store, (10, 20, 5, -3)).unwrap(), 32); 215 | 216 | let compare_rectangles = 217 | get_func::<(i32, i32, i32, i32), i32>(&instance, &mut store, "compare_rectangles"); 218 | assert_eq!( 219 | compare_rectangles.call(&mut store, (10, 5, 4, 6)).unwrap(), 220 | 1 221 | ); 222 | 223 | let point_quadrant = get_func::<(i32, i32), i32>(&instance, &mut store, "point_quadrant"); 224 | assert_eq!(point_quadrant.call(&mut store, (5, 5)).unwrap(), 1); 225 | assert_eq!(point_quadrant.call(&mut store, (-5, 5)).unwrap(), 2); 226 | 227 | let person_is_adult = get_func::(&instance, &mut store, "person_is_adult"); 228 | assert_eq!(person_is_adult.call(&mut store, 18).unwrap(), 1); 229 | assert_eq!(person_is_adult.call(&mut store, 17).unwrap(), 0); 230 | } 231 | 232 | #[test] 233 | fn test_switch_example() { 234 | let wasm_bytes = compile_and_validate("switch.go"); 235 | 236 | let engine = Engine::default(); 237 | let module = Module::from_binary(&engine, &wasm_bytes).unwrap(); 238 | let mut store = Store::new(&engine, ()); 239 | let instance = Instance::new(&mut store, &module, &[]).unwrap(); 240 | 241 | let day_of_week = get_func::(&instance, &mut store, "day_of_week"); 242 | assert_eq!(day_of_week.call(&mut store, 1).unwrap(), 100); 243 | assert_eq!(day_of_week.call(&mut store, 7).unwrap(), 700); 244 | 245 | let is_weekday = get_func::(&instance, &mut store, "is_weekday"); 246 | assert_eq!(is_weekday.call(&mut store, 3).unwrap(), 1); 247 | assert_eq!(is_weekday.call(&mut store, 6).unwrap(), 0); 248 | 249 | let season_from_month = get_func::(&instance, &mut store, "season_from_month"); 250 | assert_eq!(season_from_month.call(&mut store, 1).unwrap(), 1); 251 | assert_eq!(season_from_month.call(&mut store, 7).unwrap(), 3); 252 | 253 | let calculator_switch = 254 | get_func::<(i32, i32, i32), i32>(&instance, &mut store, "calculator_switch"); 255 | assert_eq!(calculator_switch.call(&mut store, (1, 10, 5)).unwrap(), 15); 256 | assert_eq!(calculator_switch.call(&mut store, (3, 10, 5)).unwrap(), 50); 257 | 258 | let http_status_category = 259 | get_func::(&instance, &mut store, "http_status_category"); 260 | assert_eq!(http_status_category.call(&mut store, 200).unwrap(), 1); 261 | assert_eq!(http_status_category.call(&mut store, 404).unwrap(), 2); 262 | 263 | let traffic_light = get_func::(&instance, &mut store, "traffic_light"); 264 | assert_eq!(traffic_light.call(&mut store, 1).unwrap(), 10); 265 | assert_eq!(traffic_light.call(&mut store, 3).unwrap(), 30); 266 | 267 | let month_days = get_func::(&instance, &mut store, "month_days"); 268 | assert_eq!(month_days.call(&mut store, 1).unwrap(), 31); 269 | assert_eq!(month_days.call(&mut store, 4).unwrap(), 30); 270 | assert_eq!(month_days.call(&mut store, 2).unwrap(), 28); 271 | 272 | let priority_level = get_func::<(i32, i32), i32>(&instance, &mut store, "priority_level"); 273 | assert_eq!(priority_level.call(&mut store, (1, 1)).unwrap(), 4); 274 | assert_eq!(priority_level.call(&mut store, (2, 2)).unwrap(), 2); 275 | } 276 | 277 | #[test] 278 | fn test_algorithms_example() { 279 | let wasm_bytes = compile_and_validate("algorithms.go"); 280 | 281 | let engine = Engine::default(); 282 | let module = Module::from_binary(&engine, &wasm_bytes).unwrap(); 283 | let mut store = Store::new(&engine, ()); 284 | let instance = Instance::new(&mut store, &module, &[]).unwrap(); 285 | 286 | let is_prime = get_func::(&instance, &mut store, "is_prime"); 287 | assert_eq!(is_prime.call(&mut store, 2).unwrap(), 1); 288 | assert_eq!(is_prime.call(&mut store, 17).unwrap(), 1); 289 | assert_eq!(is_prime.call(&mut store, 4).unwrap(), 0); 290 | 291 | let gcd = get_func::<(i32, i32), i32>(&instance, &mut store, "gcd"); 292 | assert_eq!(gcd.call(&mut store, (48, 18)).unwrap(), 6); 293 | 294 | let lcm = get_func::<(i32, i32), i32>(&instance, &mut store, "lcm"); 295 | assert_eq!(lcm.call(&mut store, (12, 18)).unwrap(), 36); 296 | 297 | let is_palindrome_number = 298 | get_func::(&instance, &mut store, "is_palindrome_number"); 299 | assert_eq!(is_palindrome_number.call(&mut store, 121).unwrap(), 1); 300 | assert_eq!(is_palindrome_number.call(&mut store, 123).unwrap(), 0); 301 | 302 | let sum_of_digits = get_func::(&instance, &mut store, "sum_of_digits"); 303 | assert_eq!(sum_of_digits.call(&mut store, 123).unwrap(), 6); 304 | 305 | let count_digits = get_func::(&instance, &mut store, "count_digits"); 306 | assert_eq!(count_digits.call(&mut store, 12345).unwrap(), 5); 307 | 308 | let reverse_number = get_func::(&instance, &mut store, "reverse_number"); 309 | assert_eq!(reverse_number.call(&mut store, 123).unwrap(), 321); 310 | 311 | let binary_search = get_func::(&instance, &mut store, "binary_search"); 312 | assert_eq!(binary_search.call(&mut store, 7).unwrap(), 3); 313 | assert_eq!(binary_search.call(&mut store, 99).unwrap(), -1); 314 | 315 | let nth_triangular = get_func::(&instance, &mut store, "nth_triangular"); 316 | assert_eq!(nth_triangular.call(&mut store, 5).unwrap(), 15); 317 | 318 | let collatz_steps = get_func::(&instance, &mut store, "collatz_steps"); 319 | assert_eq!(collatz_steps.call(&mut store, 10).unwrap(), 6); 320 | 321 | let sum_of_squares = get_func::(&instance, &mut store, "sum_of_squares"); 322 | assert_eq!(sum_of_squares.call(&mut store, 3).unwrap(), 14); 323 | } 324 | 325 | #[test] 326 | fn test_combined_example() { 327 | let wasm_bytes = compile_and_validate("combined.go"); 328 | 329 | let engine = Engine::default(); 330 | let module = Module::from_binary(&engine, &wasm_bytes).unwrap(); 331 | let mut store = Store::new(&engine, ()); 332 | let instance = Instance::new(&mut store, &module, &[]).unwrap(); 333 | 334 | let analyze_array = get_func::<(), i32>(&instance, &mut store, "analyze_array"); 335 | let result = analyze_array.call(&mut store, ()).unwrap(); 336 | assert!(result > 0); 337 | 338 | let grade_calculator = get_func::(&instance, &mut store, "grade_calculator"); 339 | assert_eq!(grade_calculator.call(&mut store, 95).unwrap(), 4); 340 | assert_eq!(grade_calculator.call(&mut store, 85).unwrap(), 3); 341 | assert_eq!(grade_calculator.call(&mut store, 50).unwrap(), 0); 342 | 343 | let validate_and_process = 344 | get_func::(&instance, &mut store, "validate_and_process"); 345 | assert_eq!(validate_and_process.call(&mut store, -5).unwrap(), -1); 346 | assert_eq!(validate_and_process.call(&mut store, 5).unwrap(), 50); 347 | 348 | let filter_and_count = get_func::<(), i32>(&instance, &mut store, "filter_and_count"); 349 | let count = filter_and_count.call(&mut store, ()).unwrap(); 350 | assert!(count > 0); 351 | 352 | let string_category = get_func::<(), i32>(&instance, &mut store, "string_category"); 353 | assert_eq!(string_category.call(&mut store, ()).unwrap(), 111); 354 | 355 | let complex_calculation = 356 | get_func::<(i32, i32, i32), i32>(&instance, &mut store, "complex_calculation"); 357 | assert_eq!(complex_calculation.call(&mut store, (3, 4, 1)).unwrap(), 25); 358 | 359 | let array_transform = get_func::(&instance, &mut store, "array_transform"); 360 | assert_eq!(array_transform.call(&mut store, 1).unwrap(), 30); 361 | 362 | let point_distance_category = 363 | get_func::<(i32, i32, i32, i32), i32>(&instance, &mut store, "point_distance_category"); 364 | assert_eq!( 365 | point_distance_category 366 | .call(&mut store, (0, 0, 3, 4)) 367 | .unwrap(), 368 | 2 369 | ); 370 | } 371 | 372 | #[test] 373 | fn test_all_examples_compile() { 374 | // Verify all example files compile without errors 375 | let examples = vec![ 376 | "calculator.go", 377 | "arrays.go", 378 | "strings.go", 379 | "structs.go", 380 | "switch.go", 381 | "algorithms.go", 382 | "combined.go", 383 | ]; 384 | 385 | for example in examples { 386 | compile_and_validate(example); 387 | } 388 | } 389 | 390 | #[test] 391 | fn test_examples_generate_valid_wasm() { 392 | // Verify all WASM modules can be instantiated 393 | let examples = vec![ 394 | "calculator.go", 395 | "arrays.go", 396 | "strings.go", 397 | "structs.go", 398 | "switch.go", 399 | "algorithms.go", 400 | "combined.go", 401 | ]; 402 | 403 | let engine = Engine::default(); 404 | 405 | for example in examples { 406 | let wasm_bytes = compile_and_validate(example); 407 | let module = Module::from_binary(&engine, &wasm_bytes) 408 | .unwrap_or_else(|e| panic!("Failed to create module from {}: {}", example, e)); 409 | let mut store = Store::new(&engine, ()); 410 | Instance::new(&mut store, &module, &[]) 411 | .unwrap_or_else(|e| panic!("Failed to instantiate {}: {}", example, e)); 412 | } 413 | } 414 | } 415 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2025-present Raphael Amorim. All rights reserved. 2 | // Use of this source code is governed by a BSD-3-Clause 3 | // license that can be found in the LICENSE file. 4 | 5 | use clap::{Arg, Command}; 6 | use std::fs; 7 | use std::path::{Path, PathBuf}; 8 | use std::process; 9 | 10 | use goiaba::parser::parse_str; 11 | use goiaba::wasm::compiler::compile_str; 12 | 13 | use goiaba::bindings::JSBindingGenerator; 14 | 15 | fn scan_go_files(directory: &str) -> Result, Box> { 16 | let mut go_files = Vec::new(); 17 | 18 | for entry in fs::read_dir(directory)? { 19 | let entry = entry?; 20 | let path = entry.path(); 21 | 22 | if path.is_file() 23 | && let Some(extension) = path.extension() 24 | && extension == "go" 25 | && let Some(path_str) = path.to_str() 26 | { 27 | go_files.push(path_str.to_string()); 28 | } 29 | } 30 | 31 | if go_files.is_empty() { 32 | return Err(format!("No .go files found in directory '{}'", directory).into()); 33 | } 34 | 35 | // Sort for consistent ordering 36 | go_files.sort(); 37 | Ok(go_files) 38 | } 39 | 40 | fn main() -> Result<(), Box> { 41 | let app = Command::new("goiaba") 42 | .version("0.1.0") 43 | .author("Raphael Amorim") 44 | .about("Go to WebAssembly compiler with web project generation") 45 | .subcommand_required(false) 46 | .arg_required_else_help(false) 47 | .arg( 48 | Arg::new("input") 49 | .help("Input Go source files") 50 | .required(false) 51 | .num_args(1..) 52 | .index(1), 53 | ) 54 | .arg( 55 | Arg::new("output") 56 | .help("Output WebAssembly file") 57 | .short('o') 58 | .long("output") 59 | .value_name("FILE") 60 | .required(false), 61 | ) 62 | .arg( 63 | Arg::new("web") 64 | .help("Generate web project with HTML and JS bindings") 65 | .short('w') 66 | .long("web") 67 | .value_name("DIR") 68 | .required(false), 69 | ) 70 | .arg( 71 | Arg::new("verbose") 72 | .help("Enable verbose output") 73 | .short('v') 74 | .long("verbose") 75 | .action(clap::ArgAction::SetTrue), 76 | ) 77 | .subcommand( 78 | Command::new("compile") 79 | .about("Compile Go source files") 80 | .arg( 81 | Arg::new("input") 82 | .help("Input Go source files") 83 | .required(true) 84 | .num_args(1..) 85 | .index(1), 86 | ) 87 | .arg( 88 | Arg::new("output") 89 | .help("Output WebAssembly file") 90 | .short('o') 91 | .long("output") 92 | .value_name("FILE") 93 | .required(false), 94 | ) 95 | .arg( 96 | Arg::new("web") 97 | .help("Generate web project with HTML and JS bindings") 98 | .short('w') 99 | .long("web") 100 | .value_name("DIR") 101 | .required(false), 102 | ) 103 | .arg( 104 | Arg::new("verbose") 105 | .help("Enable verbose output") 106 | .short('v') 107 | .long("verbose") 108 | .action(clap::ArgAction::SetTrue), 109 | ), 110 | ) 111 | .subcommand( 112 | Command::new("build") 113 | .about("Build a Go package from a directory") 114 | .arg( 115 | Arg::new("directory") 116 | .help("Directory containing Go source files") 117 | .required(true) 118 | .index(1), 119 | ) 120 | .arg( 121 | Arg::new("output") 122 | .help("Output WebAssembly file") 123 | .short('o') 124 | .long("output") 125 | .value_name("FILE") 126 | .required(false), 127 | ) 128 | .arg( 129 | Arg::new("web") 130 | .help("Generate web project with HTML and JS bindings") 131 | .short('w') 132 | .long("web") 133 | .value_name("DIR") 134 | .required(false), 135 | ) 136 | .arg( 137 | Arg::new("verbose") 138 | .help("Enable verbose output") 139 | .short('v') 140 | .long("verbose") 141 | .action(clap::ArgAction::SetTrue), 142 | ), 143 | ); 144 | 145 | let matches = app.get_matches(); 146 | 147 | let (input_files, verbose, output_file, web_dir) = if let Some(compile_matches) = 148 | matches.subcommand_matches("compile") 149 | { 150 | let input_files: Vec = compile_matches 151 | .get_many::("input") 152 | .unwrap() 153 | .cloned() 154 | .collect(); 155 | let verbose = compile_matches.get_flag("verbose"); 156 | let output_file = compile_matches.get_one::("output").cloned(); 157 | let web_dir = compile_matches.get_one::("web").cloned(); 158 | (input_files, verbose, output_file, web_dir) 159 | } else if let Some(build_matches) = matches.subcommand_matches("build") { 160 | let directory = build_matches.get_one::("directory").unwrap(); 161 | let verbose = build_matches.get_flag("verbose"); 162 | let output_file = build_matches.get_one::("output").cloned(); 163 | let web_dir = build_matches.get_one::("web").cloned(); 164 | 165 | // Scan directory for .go files 166 | let input_files = scan_go_files(directory)?; 167 | (input_files, verbose, output_file, web_dir) 168 | } else if matches.contains_id("input") { 169 | // Default behavior: treat as compile command (backward compatibility) 170 | let input_files: Vec = matches 171 | .get_many::("input") 172 | .unwrap() 173 | .cloned() 174 | .collect(); 175 | let verbose = matches.get_flag("verbose"); 176 | let output_file = matches.get_one::("output").cloned(); 177 | let web_dir = matches.get_one::("web").cloned(); 178 | (input_files, verbose, output_file, web_dir) 179 | } else { 180 | eprintln!( 181 | "Error: No input files specified. Use 'goiaba compile ' or 'goiaba build '" 182 | ); 183 | process::exit(1); 184 | }; 185 | 186 | if verbose { 187 | println!("Compiling Go files: {}", input_files.join(", ")); 188 | } 189 | 190 | // Read and concatenate all Go source files 191 | let mut go_source = String::new(); 192 | for input_file in &input_files { 193 | let content = match fs::read_to_string(input_file) { 194 | Ok(content) => content, 195 | Err(e) => { 196 | eprintln!("Error reading file '{}': {}", input_file, e); 197 | process::exit(1); 198 | } 199 | }; 200 | go_source.push_str(&content); 201 | go_source.push('\n'); // Add newline between files 202 | } 203 | 204 | // Parse Go source to extract function information 205 | let (ast_objects, go_program) = match parse_str(&go_source) { 206 | Ok((objs, prog)) => (objs, prog), 207 | Err(e) => { 208 | eprintln!("Parse error: {}", e); 209 | process::exit(1); 210 | } 211 | }; 212 | 213 | // Compile Go to WASM 214 | let wasm_bytes = match compile_str(&go_source) { 215 | Ok(bytes) => bytes, 216 | Err(e) => { 217 | eprintln!("Compilation error: {}", e); 218 | process::exit(1); 219 | } 220 | }; 221 | 222 | // Determine output file name 223 | let first_input_file = &input_files[0]; 224 | let input_path = Path::new(first_input_file); 225 | let default_output = input_path.with_extension("wasm"); 226 | let output_file = output_file.unwrap_or_else(|| default_output.to_string_lossy().to_string()); 227 | 228 | // Write WASM file 229 | if let Err(e) = fs::write(&output_file, &wasm_bytes) { 230 | eprintln!("Error writing WASM file '{}': {}", output_file, e); 231 | process::exit(1); 232 | } 233 | 234 | if verbose { 235 | println!( 236 | "Generated WASM: {} ({} bytes)", 237 | output_file, 238 | wasm_bytes.len() 239 | ); 240 | } 241 | 242 | // Check if web project generation is requested 243 | if let Some(ref web_dir_str) = web_dir { 244 | generate_web_project( 245 | web_dir_str, 246 | &output_file, 247 | &go_source, 248 | &go_program, 249 | &ast_objects, 250 | input_path, 251 | verbose, 252 | ); 253 | } else { 254 | println!("Successfully compiled: {}", output_file); 255 | } 256 | 257 | Ok(()) 258 | } 259 | 260 | fn generate_web_project( 261 | web_dir: &str, 262 | wasm_filename: &str, 263 | go_source: &str, 264 | go_program: &goiaba::parser::ast::File, 265 | ast_objects: &goiaba::parser::objects::AstObjects, 266 | input_path: &Path, 267 | verbose: bool, 268 | ) { 269 | let web_path = PathBuf::from(web_dir); 270 | 271 | // Create web directory 272 | if let Err(e) = fs::create_dir_all(&web_path) { 273 | eprintln!("Error creating web directory '{}': {}", web_dir, e); 274 | process::exit(1); 275 | } 276 | 277 | // Initialize JS binding generator 278 | let mut js_gen = JSBindingGenerator::new(); 279 | js_gen.analyze_go_source(go_source, go_program, ast_objects); 280 | 281 | let project_name = input_path 282 | .file_stem() 283 | .unwrap_or_else(|| std::ffi::OsStr::new("go-wasm-project")) 284 | .to_string_lossy() 285 | .to_string(); 286 | 287 | let wasm_file_name = Path::new(wasm_filename) 288 | .file_name() 289 | .unwrap() 290 | .to_string_lossy() 291 | .to_string(); 292 | 293 | // Copy WASM file to web directory 294 | let web_wasm_path = web_path.join(&wasm_file_name); 295 | if let Err(e) = fs::copy(wasm_filename, &web_wasm_path) { 296 | eprintln!("Error copying WASM file: {}", e); 297 | process::exit(1); 298 | } 299 | 300 | // Generate HTML file 301 | let html_content = js_gen.generate_html(&wasm_file_name, &project_name); 302 | let html_path = web_path.join("index.html"); 303 | if let Err(e) = fs::write(&html_path, html_content) { 304 | eprintln!("Error writing HTML file: {}", e); 305 | process::exit(1); 306 | } 307 | 308 | // Generate JavaScript file 309 | let js_content = js_gen.generate_javascript(&wasm_file_name); 310 | let js_path = web_path.join("main.js"); 311 | if let Err(e) = fs::write(&js_path, js_content) { 312 | eprintln!("Error writing JavaScript file: {}", e); 313 | process::exit(1); 314 | } 315 | 316 | // Generate TypeScript definitions 317 | let ts_content = js_gen.generate_typescript_definitions(); 318 | let ts_path = web_path.join("types.d.ts"); 319 | if let Err(e) = fs::write(&ts_path, ts_content) { 320 | eprintln!("Error writing TypeScript definitions: {}", e); 321 | process::exit(1); 322 | } 323 | 324 | // Generate README 325 | let readme_content = js_gen.generate_readme(&project_name, &wasm_file_name); 326 | let readme_path = web_path.join("README.md"); 327 | if let Err(e) = fs::write(&readme_path, readme_content) { 328 | eprintln!("Error writing README file: {}", e); 329 | process::exit(1); 330 | } 331 | 332 | // Generate package.json for npm projects 333 | let package_json = generate_package_json(&project_name); 334 | let package_path = web_path.join("package.json"); 335 | if let Err(e) = fs::write(&package_path, package_json) { 336 | eprintln!("Error writing package.json: {}", e); 337 | process::exit(1); 338 | } 339 | 340 | if verbose { 341 | println!("Generated web project files:"); 342 | println!(" 📁 {}/", web_dir); 343 | println!(" 📄 index.html - Interactive demo page"); 344 | println!(" 📄 main.js - JavaScript bindings"); 345 | println!(" 📄 types.d.ts - TypeScript definitions"); 346 | println!(" 📄 {} - WebAssembly module", wasm_file_name); 347 | println!(" 📄 README.md - Documentation"); 348 | println!(" 📄 package.json - NPM configuration"); 349 | } 350 | 351 | println!("✅ Web project generated in: {}", web_dir); 352 | println!("🚀 To run: cd {} && python -m http.server 8000", web_dir); 353 | println!("🌐 Then open: http://localhost:8000"); 354 | } 355 | 356 | fn generate_package_json(project_name: &str) -> String { 357 | format!( 358 | r#"{{ 359 | "name": "{}", 360 | "version": "1.0.0", 361 | "description": "Go functions compiled to WebAssembly", 362 | "main": "main.js", 363 | "scripts": {{ 364 | "start": "python -m http.server 8000", 365 | "serve": "npx serve .", 366 | "dev": "npx live-server ." 367 | }}, 368 | "keywords": ["webassembly", "wasm", "go", "golang"], 369 | "author": "Generated by Goiaba", 370 | "license": "MIT", 371 | "devDependencies": {{ 372 | "live-server": "^1.2.2", 373 | "serve": "^14.2.1" 374 | }} 375 | }}"#, 376 | project_name.replace(" ", "-").to_lowercase() 377 | ) 378 | } 379 | 380 | #[cfg(test)] 381 | mod tests { 382 | use super::*; 383 | use tempfile::tempdir; 384 | 385 | #[test] 386 | fn test_web_project_generation() { 387 | let temp_dir = tempdir().unwrap(); 388 | let input_file = temp_dir.path().join("test.go"); 389 | let web_dir = temp_dir.path().join("web"); 390 | 391 | let go_code = r#" 392 | package main 393 | 394 | //export add 395 | func add(x int, y int) int { 396 | return x + y 397 | } 398 | 399 | //export multiply 400 | func multiply(a int, b int) int { 401 | return a * b 402 | } 403 | "#; 404 | 405 | fs::write(&input_file, go_code).unwrap(); 406 | 407 | // Parse and compile 408 | let (ast_objects, go_program) = parse_str(go_code).unwrap(); 409 | let wasm_bytes = compile_str(go_code).unwrap(); 410 | let wasm_file = temp_dir.path().join("test.wasm"); 411 | fs::write(&wasm_file, wasm_bytes).unwrap(); 412 | 413 | // Generate web project 414 | generate_web_project( 415 | web_dir.to_str().unwrap(), 416 | wasm_file.to_str().unwrap(), 417 | go_code, 418 | &go_program, 419 | &ast_objects, 420 | &input_file, 421 | false, 422 | ); 423 | 424 | // Verify generated files 425 | assert!(web_dir.join("index.html").exists()); 426 | assert!(web_dir.join("main.js").exists()); 427 | assert!(web_dir.join("types.d.ts").exists()); 428 | assert!(web_dir.join("test.wasm").exists()); 429 | assert!(web_dir.join("README.md").exists()); 430 | assert!(web_dir.join("package.json").exists()); 431 | 432 | // Verify HTML contains function demos 433 | let html_content = fs::read_to_string(web_dir.join("index.html")).unwrap(); 434 | assert!(html_content.contains("add")); 435 | assert!(html_content.contains("multiply")); 436 | assert!(html_content.contains("Interactive demo")); 437 | 438 | // Verify JS contains function bindings 439 | let js_content = fs::read_to_string(web_dir.join("main.js")).unwrap(); 440 | assert!(js_content.contains("call_add")); 441 | assert!(js_content.contains("call_multiply")); 442 | assert!(js_content.contains("loadWasm")); 443 | } 444 | 445 | #[test] 446 | fn test_multi_file_compilation() { 447 | let temp_dir = tempdir().unwrap(); 448 | 449 | // Create first file with package declaration and one function 450 | let file1_path = temp_dir.path().join("file1.go"); 451 | let file1_content = "package main\n\nfunc add(x int, y int) int {\n return x + y\n}\n"; 452 | fs::write(&file1_path, file1_content).unwrap(); 453 | 454 | // Create second file with another function (no package declaration) 455 | let file2_path = temp_dir.path().join("file2.go"); 456 | let file2_content = "func multiply(x int, y int) int {\n return x * y\n}\n"; 457 | fs::write(&file2_path, file2_content).unwrap(); 458 | 459 | // Create third file with main function that uses both (no package declaration) 460 | let file3_path = temp_dir.path().join("file3.go"); 461 | let file3_content = "//export calculate\nfunc calculate(a int, b int) int {\n sum := add(a, b)\n product := multiply(sum, 2)\n return product\n}\n"; 462 | fs::write(&file3_path, file3_content).unwrap(); 463 | 464 | // Test concatenation by reading files manually 465 | let mut concatenated = String::new(); 466 | for file_path in [&file1_path, &file2_path, &file3_path] { 467 | concatenated.push_str(&fs::read_to_string(file_path).unwrap()); 468 | concatenated.push('\n'); 469 | } 470 | 471 | // Compile the concatenated source 472 | let result = compile_str(&concatenated); 473 | assert!( 474 | result.is_ok(), 475 | "Failed to compile multi-file source: {:?}", 476 | result.err() 477 | ); 478 | 479 | let wasm_bytes = result.unwrap(); 480 | assert!(!wasm_bytes.is_empty(), "Generated WASM should not be empty"); 481 | 482 | // Verify that functions from different files are present 483 | // We can't easily inspect the WASM without parsing, but successful compilation 484 | // indicates that all functions were parsed and compiled 485 | } 486 | 487 | #[test] 488 | fn test_single_file_still_works() { 489 | let temp_dir = tempdir().unwrap(); 490 | let single_file_path = temp_dir.path().join("single.go"); 491 | let single_file_content = 492 | "package main\n\n//export test\nfunc test(x int) int {\n return x * 2\n}\n"; 493 | fs::write(&single_file_path, single_file_content).unwrap(); 494 | 495 | let result = compile_str(single_file_content); 496 | assert!( 497 | result.is_ok(), 498 | "Failed to compile single file: {:?}", 499 | result.err() 500 | ); 501 | 502 | let wasm_bytes = result.unwrap(); 503 | assert!(!wasm_bytes.is_empty(), "Generated WASM should not be empty"); 504 | } 505 | 506 | #[test] 507 | fn test_empty_file_handling() { 508 | let temp_dir = tempdir().unwrap(); 509 | 510 | // Create a file with only package declaration 511 | let file1_path = temp_dir.path().join("pkg.go"); 512 | fs::write(&file1_path, "package main\n").unwrap(); 513 | 514 | // Create a file with actual content (no package declaration) 515 | let file2_path = temp_dir.path().join("func.go"); 516 | let file2_content = "//export test\nfunc test() int {\n return 42\n}\n"; 517 | fs::write(&file2_path, file2_content).unwrap(); 518 | 519 | // Concatenate manually 520 | let mut concatenated = String::new(); 521 | concatenated.push_str("package main\n"); 522 | concatenated.push('\n'); 523 | concatenated.push_str(file2_content); 524 | concatenated.push('\n'); 525 | 526 | let result = compile_str(&concatenated); 527 | assert!( 528 | result.is_ok(), 529 | "Failed to compile with empty-ish files: {:?}", 530 | result.err() 531 | ); 532 | 533 | let wasm_bytes = result.unwrap(); 534 | assert!(!wasm_bytes.is_empty(), "Generated WASM should not be empty"); 535 | } 536 | } 537 | -------------------------------------------------------------------------------- /tests/struct_fields_tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2025-present Raphael Amorim. All rights reserved. 2 | // Use of this source code is governed by a BSD-3-Clause 3 | // license that can be found in the LICENSE file. 4 | 5 | use goiaba::parser::parse_str; 6 | use goiaba::wasm::compiler::{GoToWasmTranslator, WasmType, compile_str}; 7 | use wasmtime::{Engine, Instance, Module, Store}; 8 | 9 | #[cfg(test)] 10 | mod tests { 11 | use super::*; 12 | 13 | #[test] 14 | fn test_struct_with_bool_fields() { 15 | let go_source = r#" 16 | package main 17 | 18 | type Flags struct { 19 | isActive bool 20 | isEnabled bool 21 | } 22 | 23 | //export check_flags 24 | func check_flags(active int, enabled int) int { 25 | f := Flags{isActive: active != 0, isEnabled: enabled != 0} 26 | 27 | if f.isActive && f.isEnabled { 28 | return 1 29 | } 30 | return 0 31 | } 32 | "#; 33 | 34 | let wasm_bytes = compile_str(go_source).expect("Failed to compile"); 35 | let engine = Engine::default(); 36 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load module"); 37 | let mut store = Store::new(&engine, ()); 38 | let instance = Instance::new(&mut store, &module, &[]).expect("Failed to instantiate"); 39 | 40 | let check_flags = instance 41 | .get_typed_func::<(i32, i32), i32>(&mut store, "check_flags") 42 | .expect("Failed to get function"); 43 | 44 | assert_eq!(check_flags.call(&mut store, (1, 1)).unwrap(), 1); 45 | assert_eq!(check_flags.call(&mut store, (1, 0)).unwrap(), 0); 46 | assert_eq!(check_flags.call(&mut store, (0, 1)).unwrap(), 0); 47 | } 48 | 49 | #[test] 50 | fn test_struct_with_string_fields() { 51 | let go_source = r#" 52 | package main 53 | 54 | type Person struct { 55 | name string 56 | age int 57 | } 58 | 59 | //export get_age 60 | func get_age() int { 61 | p := Person{name: "Alice", age: 30} 62 | return p.age 63 | } 64 | "#; 65 | 66 | let wasm_bytes = compile_str(go_source).expect("Failed to compile"); 67 | let engine = Engine::default(); 68 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load module"); 69 | let mut store = Store::new(&engine, ()); 70 | let instance = Instance::new(&mut store, &module, &[]).expect("Failed to instantiate"); 71 | 72 | let get_age = instance 73 | .get_typed_func::<(), i32>(&mut store, "get_age") 74 | .expect("Failed to get function"); 75 | 76 | assert_eq!(get_age.call(&mut store, ()).unwrap(), 30); 77 | } 78 | 79 | #[test] 80 | fn test_struct_with_multiple_integer_types() { 81 | let go_source = r#" 82 | package main 83 | 84 | type Numbers struct { 85 | small int8 86 | medium int16 87 | large int64 88 | normal int 89 | } 90 | 91 | //export sum_numbers 92 | func sum_numbers(a int, b int, c int, d int) int { 93 | n := Numbers{small: int8(a), medium: int16(b), large: int64(c), normal: d} 94 | return int(n.small) + int(n.medium) + int(n.large) + n.normal 95 | } 96 | "#; 97 | 98 | let wasm_bytes = compile_str(go_source).expect("Failed to compile"); 99 | let engine = Engine::default(); 100 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load module"); 101 | let mut store = Store::new(&engine, ()); 102 | let instance = Instance::new(&mut store, &module, &[]).expect("Failed to instantiate"); 103 | 104 | let sum_numbers = instance 105 | .get_typed_func::<(i32, i32, i32, i32), i32>(&mut store, "sum_numbers") 106 | .expect("Failed to get function"); 107 | 108 | assert_eq!(sum_numbers.call(&mut store, (1, 2, 3, 4)).unwrap(), 10); 109 | } 110 | 111 | #[test] 112 | fn test_struct_with_unsigned_types() { 113 | let go_source = r#" 114 | package main 115 | 116 | type UnsignedData struct { 117 | byte8 uint8 118 | short16 uint16 119 | int32 uint32 120 | } 121 | 122 | //export process_unsigned 123 | func process_unsigned(a int, b int, c int) int { 124 | data := UnsignedData{byte8: uint8(a), short16: uint16(b), int32: uint32(c)} 125 | return int(data.byte8) + int(data.short16) + int(data.int32) 126 | } 127 | "#; 128 | 129 | let wasm_bytes = compile_str(go_source).expect("Failed to compile"); 130 | let engine = Engine::default(); 131 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load module"); 132 | let mut store = Store::new(&engine, ()); 133 | let instance = Instance::new(&mut store, &module, &[]).expect("Failed to instantiate"); 134 | 135 | let process_unsigned = instance 136 | .get_typed_func::<(i32, i32, i32), i32>(&mut store, "process_unsigned") 137 | .expect("Failed to get function"); 138 | 139 | assert_eq!(process_unsigned.call(&mut store, (10, 20, 30)).unwrap(), 60); 140 | } 141 | 142 | #[test] 143 | fn test_struct_with_mixed_types() { 144 | let go_source = r#" 145 | package main 146 | 147 | type MixedData struct { 148 | count int 149 | isValid bool 150 | id uint32 151 | value int64 152 | } 153 | 154 | //export compute_mixed 155 | func compute_mixed(count int, valid int, id int, value int) int { 156 | data := MixedData{ 157 | count: count, 158 | isValid: valid != 0, 159 | id: uint32(id), 160 | value: int64(value), 161 | } 162 | 163 | result := data.count + int(data.id) + int(data.value) 164 | if data.isValid { 165 | result = result * 2 166 | } 167 | return result 168 | } 169 | "#; 170 | 171 | let wasm_bytes = compile_str(go_source).expect("Failed to compile"); 172 | let engine = Engine::default(); 173 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load module"); 174 | let mut store = Store::new(&engine, ()); 175 | let instance = Instance::new(&mut store, &module, &[]).expect("Failed to instantiate"); 176 | 177 | let compute_mixed = instance 178 | .get_typed_func::<(i32, i32, i32, i32), i32>(&mut store, "compute_mixed") 179 | .expect("Failed to get function"); 180 | 181 | // When valid=1, result should be (5 + 10 + 15) * 2 = 60 182 | assert_eq!(compute_mixed.call(&mut store, (5, 1, 10, 15)).unwrap(), 60); 183 | // When valid=0, result should be (5 + 10 + 15) = 30 184 | assert_eq!(compute_mixed.call(&mut store, (5, 0, 10, 15)).unwrap(), 30); 185 | } 186 | 187 | #[test] 188 | fn test_nested_structs() { 189 | let go_source = r#" 190 | package main 191 | 192 | type Inner struct { 193 | value int 194 | } 195 | 196 | type Outer struct { 197 | data Inner 198 | multiplier int 199 | } 200 | 201 | //export process_nested 202 | func process_nested(val int, mult int) int { 203 | inner := Inner{value: val} 204 | outer := Outer{data: inner, multiplier: mult} 205 | return outer.data.value * outer.multiplier 206 | } 207 | "#; 208 | 209 | let wasm_bytes = compile_str(go_source).expect("Failed to compile"); 210 | let engine = Engine::default(); 211 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load module"); 212 | let mut store = Store::new(&engine, ()); 213 | let instance = Instance::new(&mut store, &module, &[]).expect("Failed to instantiate"); 214 | 215 | let process_nested = instance 216 | .get_typed_func::<(i32, i32), i32>(&mut store, "process_nested") 217 | .expect("Failed to get function"); 218 | 219 | assert_eq!(process_nested.call(&mut store, (7, 3)).unwrap(), 21); 220 | assert_eq!(process_nested.call(&mut store, (10, 5)).unwrap(), 50); 221 | } 222 | 223 | #[test] 224 | fn test_struct_field_assignment_with_different_types() { 225 | let go_source = r#" 226 | package main 227 | 228 | type Counter struct { 229 | current int 230 | maximum int 231 | active bool 232 | } 233 | 234 | //export update_counter 235 | func update_counter(initial int, max int) int { 236 | c := Counter{current: initial, maximum: max, active: true} 237 | 238 | c.current = c.current + 5 239 | 240 | if c.current > c.maximum { 241 | c.active = false 242 | return -1 243 | } 244 | 245 | return c.current 246 | } 247 | "#; 248 | 249 | let wasm_bytes = compile_str(go_source).expect("Failed to compile"); 250 | let engine = Engine::default(); 251 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load module"); 252 | let mut store = Store::new(&engine, ()); 253 | let instance = Instance::new(&mut store, &module, &[]).expect("Failed to instantiate"); 254 | 255 | let update_counter = instance 256 | .get_typed_func::<(i32, i32), i32>(&mut store, "update_counter") 257 | .expect("Failed to get function"); 258 | 259 | assert_eq!(update_counter.call(&mut store, (3, 20)).unwrap(), 8); 260 | assert_eq!(update_counter.call(&mut store, (15, 10)).unwrap(), -1); 261 | } 262 | 263 | #[test] 264 | fn test_struct_with_byte_type() { 265 | let go_source = r#" 266 | package main 267 | 268 | type ByteData struct { 269 | b byte 270 | count int 271 | } 272 | 273 | //export process_byte 274 | func process_byte(byteVal int, count int) int { 275 | data := ByteData{b: byte(byteVal), count: count} 276 | return int(data.b) * data.count 277 | } 278 | "#; 279 | 280 | let wasm_bytes = compile_str(go_source).expect("Failed to compile"); 281 | let engine = Engine::default(); 282 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load module"); 283 | let mut store = Store::new(&engine, ()); 284 | let instance = Instance::new(&mut store, &module, &[]).expect("Failed to instantiate"); 285 | 286 | let process_byte = instance 287 | .get_typed_func::<(i32, i32), i32>(&mut store, "process_byte") 288 | .expect("Failed to get function"); 289 | 290 | assert_eq!(process_byte.call(&mut store, (5, 3)).unwrap(), 15); 291 | assert_eq!(process_byte.call(&mut store, (10, 2)).unwrap(), 20); 292 | } 293 | 294 | #[test] 295 | fn test_multiple_structs_different_types() { 296 | let go_source = r#" 297 | package main 298 | 299 | type TypeA struct { 300 | x int 301 | y int 302 | } 303 | 304 | type TypeB struct { 305 | a bool 306 | b uint32 307 | } 308 | 309 | //export combine_structs 310 | func combine_structs(x int, y int, valid int, id int) int { 311 | ta := TypeA{x: x, y: y} 312 | tb := TypeB{a: valid != 0, b: uint32(id)} 313 | 314 | sum := ta.x + ta.y + int(tb.b) 315 | if tb.a { 316 | sum = sum * 2 317 | } 318 | return sum 319 | } 320 | "#; 321 | 322 | let wasm_bytes = compile_str(go_source).expect("Failed to compile"); 323 | let engine = Engine::default(); 324 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load module"); 325 | let mut store = Store::new(&engine, ()); 326 | let instance = Instance::new(&mut store, &module, &[]).expect("Failed to instantiate"); 327 | 328 | let combine_structs = instance 329 | .get_typed_func::<(i32, i32, i32, i32), i32>(&mut store, "combine_structs") 330 | .expect("Failed to get function"); 331 | 332 | // (10 + 20 + 30) * 2 = 120 when valid=1 333 | assert_eq!( 334 | combine_structs.call(&mut store, (10, 20, 1, 30)).unwrap(), 335 | 120 336 | ); 337 | // (10 + 20 + 30) = 60 when valid=0 338 | assert_eq!( 339 | combine_structs.call(&mut store, (10, 20, 0, 30)).unwrap(), 340 | 60 341 | ); 342 | } 343 | 344 | #[test] 345 | fn test_struct_type_conversions() { 346 | let go_source = r#" 347 | package main 348 | 349 | type ConversionTest struct { 350 | i8 int8 351 | i16 int16 352 | i32 int 353 | i64 int64 354 | } 355 | 356 | //export test_conversions 357 | func test_conversions(val int) int { 358 | ct := ConversionTest{ 359 | i8: int8(val), 360 | i16: int16(val * 2), 361 | i32: val * 3, 362 | i64: int64(val * 4), 363 | } 364 | return int(ct.i8) + int(ct.i16) + ct.i32 + int(ct.i64) 365 | } 366 | "#; 367 | 368 | let wasm_bytes = compile_str(go_source).expect("Failed to compile"); 369 | let engine = Engine::default(); 370 | let module = Module::from_binary(&engine, &wasm_bytes).expect("Failed to load module"); 371 | let mut store = Store::new(&engine, ()); 372 | let instance = Instance::new(&mut store, &module, &[]).expect("Failed to instantiate"); 373 | 374 | let test_conversions = instance 375 | .get_typed_func::(&mut store, "test_conversions") 376 | .expect("Failed to get function"); 377 | 378 | // 5 + 10 + 15 + 20 = 50 379 | assert_eq!(test_conversions.call(&mut store, 5).unwrap(), 50); 380 | // 2 + 4 + 6 + 8 = 20 381 | assert_eq!(test_conversions.call(&mut store, 2).unwrap(), 20); 382 | } 383 | 384 | // Tests to verify WASM type information 385 | mod wasm_type_verification { 386 | use super::*; 387 | 388 | #[test] 389 | fn test_verify_struct_field_types_in_ast() { 390 | let go_source = r#" 391 | package main 392 | 393 | type TypedStruct struct { 394 | b bool 395 | i8 int8 396 | i16 int16 397 | i32 int 398 | i64 int64 399 | u8 uint8 400 | u16 uint16 401 | u32 uint32 402 | u64 uint64 403 | f32 float32 404 | f64 float64 405 | s string 406 | } 407 | 408 | func dummy() int { 409 | return 0 410 | } 411 | "#; 412 | 413 | let (ast_objects, go_program) = parse_str(go_source).expect("Failed to parse"); 414 | let wasm_program = 415 | GoToWasmTranslator::translate_program(&go_program, &ast_objects, go_source); 416 | 417 | // Find the TypedStruct definition 418 | let typed_struct = wasm_program 419 | .structs 420 | .iter() 421 | .find(|s| s.name == "TypedStruct") 422 | .expect("TypedStruct not found"); 423 | 424 | // Verify field types 425 | let field_types: std::collections::HashMap<&str, &WasmType> = typed_struct 426 | .fields 427 | .iter() 428 | .map(|(name, typ)| (name.as_str(), typ)) 429 | .collect(); 430 | 431 | assert_eq!(field_types.get("b"), Some(&&WasmType::Bool)); 432 | assert_eq!(field_types.get("i8"), Some(&&WasmType::Int8)); 433 | assert_eq!(field_types.get("i16"), Some(&&WasmType::Int16)); 434 | assert_eq!(field_types.get("i32"), Some(&&WasmType::Int)); 435 | assert_eq!(field_types.get("i64"), Some(&&WasmType::Int64)); 436 | assert_eq!(field_types.get("u8"), Some(&&WasmType::Uint8)); 437 | assert_eq!(field_types.get("u16"), Some(&&WasmType::Uint16)); 438 | assert_eq!(field_types.get("u32"), Some(&&WasmType::Uint32)); 439 | assert_eq!(field_types.get("u64"), Some(&&WasmType::Uint64)); 440 | assert_eq!(field_types.get("f32"), Some(&&WasmType::Float)); 441 | assert_eq!(field_types.get("f64"), Some(&&WasmType::Float64)); 442 | assert_eq!(field_types.get("s"), Some(&&WasmType::String)); 443 | } 444 | 445 | #[test] 446 | fn test_verify_struct_field_offsets() { 447 | let go_source = r#" 448 | package main 449 | 450 | type OffsetTest struct { 451 | a int8 // offset 0, size 1 452 | b int16 // offset 1, size 2 453 | c int32 // offset 3, size 4 454 | d int64 // offset 7, size 8 455 | } 456 | 457 | func dummy() int { 458 | return 0 459 | } 460 | "#; 461 | 462 | let (ast_objects, go_program) = parse_str(go_source).expect("Failed to parse"); 463 | let wasm_program = 464 | GoToWasmTranslator::translate_program(&go_program, &ast_objects, go_source); 465 | 466 | let offset_test = wasm_program 467 | .structs 468 | .iter() 469 | .find(|s| s.name == "OffsetTest") 470 | .expect("OffsetTest not found"); 471 | 472 | // Verify field offsets 473 | assert_eq!(offset_test.field_offsets.get("a"), Some(&0)); 474 | assert_eq!(offset_test.field_offsets.get("b"), Some(&1)); 475 | assert_eq!(offset_test.field_offsets.get("c"), Some(&3)); 476 | assert_eq!(offset_test.field_offsets.get("d"), Some(&7)); 477 | 478 | // Verify total struct size: 1 + 2 + 4 + 8 = 15 bytes 479 | assert_eq!(offset_test.size, 15); 480 | } 481 | 482 | #[test] 483 | fn test_verify_mixed_size_struct() { 484 | let go_source = r#" 485 | package main 486 | 487 | type MixedSize struct { 488 | small uint8 // 1 byte 489 | medium uint16 // 2 bytes 490 | large uint32 // 4 bytes 491 | huge uint64 // 8 bytes 492 | } 493 | 494 | func dummy() int { 495 | return 0 496 | } 497 | "#; 498 | 499 | let (ast_objects, go_program) = parse_str(go_source).expect("Failed to parse"); 500 | let wasm_program = 501 | GoToWasmTranslator::translate_program(&go_program, &ast_objects, go_source); 502 | 503 | let mixed_size = wasm_program 504 | .structs 505 | .iter() 506 | .find(|s| s.name == "MixedSize") 507 | .expect("MixedSize not found"); 508 | 509 | // Verify offsets: 0, 1, 3, 7 510 | assert_eq!(mixed_size.field_offsets.get("small"), Some(&0)); 511 | assert_eq!(mixed_size.field_offsets.get("medium"), Some(&1)); 512 | assert_eq!(mixed_size.field_offsets.get("large"), Some(&3)); 513 | assert_eq!(mixed_size.field_offsets.get("huge"), Some(&7)); 514 | 515 | // Verify total size: 1 + 2 + 4 + 8 = 15 bytes 516 | assert_eq!(mixed_size.size, 15); 517 | } 518 | 519 | #[test] 520 | fn test_verify_bool_type() { 521 | let go_source = r#" 522 | package main 523 | 524 | type BoolStruct struct { 525 | flag1 bool 526 | flag2 bool 527 | value int 528 | } 529 | 530 | func dummy() int { 531 | return 0 532 | } 533 | "#; 534 | 535 | let (ast_objects, go_program) = parse_str(go_source).expect("Failed to parse"); 536 | let wasm_program = 537 | GoToWasmTranslator::translate_program(&go_program, &ast_objects, go_source); 538 | 539 | let bool_struct = wasm_program 540 | .structs 541 | .iter() 542 | .find(|s| s.name == "BoolStruct") 543 | .expect("BoolStruct not found"); 544 | 545 | // Verify bool fields have Bool type 546 | let field_types: std::collections::HashMap<&str, &WasmType> = bool_struct 547 | .fields 548 | .iter() 549 | .map(|(name, typ)| (name.as_str(), typ)) 550 | .collect(); 551 | 552 | assert_eq!(field_types.get("flag1"), Some(&&WasmType::Bool)); 553 | assert_eq!(field_types.get("flag2"), Some(&&WasmType::Bool)); 554 | assert_eq!(field_types.get("value"), Some(&&WasmType::Int)); 555 | 556 | // Verify offsets: bool is 1 byte 557 | assert_eq!(bool_struct.field_offsets.get("flag1"), Some(&0)); 558 | assert_eq!(bool_struct.field_offsets.get("flag2"), Some(&1)); 559 | assert_eq!(bool_struct.field_offsets.get("value"), Some(&2)); 560 | 561 | // Total size: 1 + 1 + 4 = 6 bytes 562 | assert_eq!(bool_struct.size, 6); 563 | } 564 | 565 | #[test] 566 | fn test_verify_byte_type() { 567 | let go_source = r#" 568 | package main 569 | 570 | type ByteStruct struct { 571 | b byte 572 | count int 573 | } 574 | 575 | func dummy() int { 576 | return 0 577 | } 578 | "#; 579 | 580 | let (ast_objects, go_program) = parse_str(go_source).expect("Failed to parse"); 581 | let wasm_program = 582 | GoToWasmTranslator::translate_program(&go_program, &ast_objects, go_source); 583 | 584 | let byte_struct = wasm_program 585 | .structs 586 | .iter() 587 | .find(|s| s.name == "ByteStruct") 588 | .expect("ByteStruct not found"); 589 | 590 | // Verify byte is treated as Uint8 591 | let field_types: std::collections::HashMap<&str, &WasmType> = byte_struct 592 | .fields 593 | .iter() 594 | .map(|(name, typ)| (name.as_str(), typ)) 595 | .collect(); 596 | 597 | assert_eq!(field_types.get("b"), Some(&&WasmType::Uint8)); 598 | assert_eq!(field_types.get("count"), Some(&&WasmType::Int)); 599 | } 600 | } 601 | } 602 | --------------------------------------------------------------------------------