From 48817668239911a6949e569a47b8db25678b0feb Mon Sep 17 00:00:00 2001 From: Brendo Costa Date: Sun, 1 Sep 2024 23:44:58 -0300 Subject: [PATCH] Updated project --- README.md | 82 ++- gleam.toml | 3 +- manifest.toml | 3 + src/gwr/binary.gleam | 126 +++++ src/gwr/execution/instance.gleam | 45 ++ src/gwr/execution/logic.gleam | 2 + src/gwr/execution/machine.gleam | 305 +++++++++++ src/gwr/execution/runtime.gleam | 162 ++++++ src/gwr/execution/stack.gleam | 78 +++ src/gwr/module.gleam | 47 -- src/gwr/parser/binary_parser.gleam | 257 +++++++++ src/gwr/parser/binary_reader.gleam | 60 +++ src/gwr/parser/convention_parser.gleam | 36 ++ src/gwr/parser/instruction_parser.gleam | 82 +++ src/gwr/parser/module_parser.gleam | 63 +++ src/gwr/parser/types_parser.gleam | 107 ++++ src/gwr/parser/value_parser.gleam | 60 +++ src/gwr/section.gleam | 162 ------ src/gwr/syntax/convention.gleam | 2 + src/gwr/syntax/index.gleam | 9 + src/gwr/syntax/instruction.gleam | 22 + src/gwr/syntax/module.gleam | 117 ++++ src/gwr/syntax/types.gleam | 102 ++++ src/gwr/syntax/value.gleam | 4 + src/gwr/types/limits.gleam | 81 --- src/gwr/types/name.gleam | 32 -- src/gwr/types/vector.gleam | 32 -- src/gwr/util.gleam | 20 - test/assets/sum.wasm | Bin 0 -> 41 bytes test/assets/sum.wat | 8 + test/gwr/execution/execution_test.gleam | 19 + test/gwr/module_test.gleam | 43 -- test/gwr/parser/binary_parser_test.gleam | 507 ++++++++++++++++++ test/gwr/parser/convention_parser_test.gleam | 28 + test/gwr/parser/instruction_parser_test.gleam | 37 ++ test/gwr/parser/module_parser_test.gleam | 93 ++++ test/gwr/parser/types_parser_test.gleam | 198 +++++++ test/gwr/parser/value_parser_test.gleam | 23 + test/gwr/section_test.gleam | 249 --------- test/gwr/types/limits_test.gleam | 35 -- test/gwr/types/vector_test.gleam | 33 -- 41 files changed, 2638 insertions(+), 736 deletions(-) create mode 100644 src/gwr/binary.gleam create mode 100644 src/gwr/execution/instance.gleam create mode 100644 src/gwr/execution/logic.gleam create mode 100644 src/gwr/execution/machine.gleam create mode 100644 src/gwr/execution/runtime.gleam create mode 100644 src/gwr/execution/stack.gleam delete mode 100644 src/gwr/module.gleam create mode 100644 src/gwr/parser/binary_parser.gleam create mode 100644 src/gwr/parser/binary_reader.gleam create mode 100644 src/gwr/parser/convention_parser.gleam create mode 100644 src/gwr/parser/instruction_parser.gleam create mode 100644 src/gwr/parser/module_parser.gleam create mode 100644 src/gwr/parser/types_parser.gleam create mode 100644 src/gwr/parser/value_parser.gleam delete mode 100644 src/gwr/section.gleam create mode 100644 src/gwr/syntax/convention.gleam create mode 100644 src/gwr/syntax/index.gleam create mode 100644 src/gwr/syntax/instruction.gleam create mode 100644 src/gwr/syntax/module.gleam create mode 100644 src/gwr/syntax/types.gleam create mode 100644 src/gwr/syntax/value.gleam delete mode 100644 src/gwr/types/limits.gleam delete mode 100644 src/gwr/types/name.gleam delete mode 100644 src/gwr/types/vector.gleam delete mode 100644 src/gwr/util.gleam create mode 100644 test/assets/sum.wasm create mode 100644 test/assets/sum.wat create mode 100644 test/gwr/execution/execution_test.gleam delete mode 100644 test/gwr/module_test.gleam create mode 100644 test/gwr/parser/binary_parser_test.gleam create mode 100644 test/gwr/parser/convention_parser_test.gleam create mode 100644 test/gwr/parser/instruction_parser_test.gleam create mode 100644 test/gwr/parser/module_parser_test.gleam create mode 100644 test/gwr/parser/types_parser_test.gleam create mode 100644 test/gwr/parser/value_parser_test.gleam delete mode 100644 test/gwr/section_test.gleam delete mode 100644 test/gwr/types/limits_test.gleam delete mode 100644 test/gwr/types/vector_test.gleam diff --git a/README.md b/README.md index 1d124dd..8a6c620 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,89 @@ An experimental work-in-progress (WIP) WebAssembly runtime written in Gleam. +## Purpose + +Nowadays, many languages ​​support Wasm as a target, from mainstream ones like C++ and Rust, as well as newer ones like Odin and Grain. The purpose of this project is to use WebAssembly to create an alternative interoperability layer to Erlang's virtual machine NIFs. + +## Installation + +```sh +gleam add gwr +``` + ## Usage -TODO. +> [!IMPORTANT] +> Currently the project is in an extremely early stage of development, it is only possible to run a simple sum function. Keep in mind that code and APIs may change dramatically. + +### Step 1 - Build code targeting Wasm + +#### Example - from Rust + +```rust +// sum.rs + +#![no_std] + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! +{ + loop {} +} + +#[no_mangle] +pub extern fn sum(x: i32, y: i32) -> i32 +{ + x + y +} +``` +```sh +rustc --crate-type cdylib --target wasm32-unknown-unknown -C debuginfo=none -C panic=abort -C strip=symbols -C opt-level=3 ./sum.rs -o ./sum.wasm +``` + +#### Example - from Wat + +Using the wat2wasm tool from [wabt](https://github.com/WebAssembly/wabt). + +```wasm +;; sum.wat + +(module + (type $t0 (func (param i32 i32) (result i32))) + (func $sum (export "sum") (type $t0) (param $p0 i32) (param $p1 i32) (result i32) + (i32.add (local.get $p0) (local.get $p1)) + ) +) +``` +```sh +wat2wasm -o ./sum.wasm ./sum.wat +``` + +### Step 2 - Run it from Gleam with GWR + +Using the [simplifile](https://hex.pm/packages/simplifile) package to read the module file. + +```sh +gleam add simplifile +``` + +```gleam +import gwr/execution/instance +import gwr/execution/runtime +import simplifile + +pub fn main() +{ + let assert Ok(module_data) = simplifile.read_bits(from: "sum.wasm") + let assert Ok(instance) = instance.create(from: module_data) + let assert Ok(#(instance, result)) = instance.call(instance, "sum", [runtime.Number(4), runtime.Number(2)]) + let assert [runtime.Number(6)] = result +} +``` + +## Contributing + +Contributions are welcome! Feel free to submit either issues or PRs, but keep in mind that your code needs to be covered by tests. ## License diff --git a/gleam.toml b/gleam.toml index ef28771..ed3de53 100644 --- a/gleam.toml +++ b/gleam.toml @@ -1,5 +1,5 @@ name = "gwr" -version = "0.0.1" +version = "0.0.2" description = "An experimental work-in-progress (WIP) WebAssembly runtime written in Gleam." licences = ["MIT"] repository = { type = "github", user = "BrendoCosta", repo = "gwr" } @@ -10,3 +10,4 @@ gleb128 = ">= 2.0.0 and < 3.0.0" [dev-dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" +simplifile = ">= 2.1.0 and < 3.0.0" diff --git a/manifest.toml b/manifest.toml index 5a49428..d015a71 100644 --- a/manifest.toml +++ b/manifest.toml @@ -2,12 +2,15 @@ # You typically do not need to edit this file packages = [ + { name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" }, { name = "gleam_stdlib", version = "0.39.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "2D7DE885A6EA7F1D5015D1698920C9BAF7241102836CE0C3837A4F160128A9C4" }, { name = "gleb128", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleb128", source = "hex", outer_checksum = "6FBBA4F89A0B515EBC951AAFCD824F54FAD03A81A55C11F4CC9EEC87DE0A2040" }, { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, + { name = "simplifile", version = "2.1.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "BDD04F5D31D6D34E2EDFAEF0B68A6297AEC939888C3BFCE61133DE13857F6DA2" }, ] [requirements] gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } gleb128 = { version = ">= 2.0.0 and < 3.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } +simplifile = { version = ">= 2.1.0 and < 3.0.0"} diff --git a/src/gwr/binary.gleam b/src/gwr/binary.gleam new file mode 100644 index 0000000..e1b50d4 --- /dev/null +++ b/src/gwr/binary.gleam @@ -0,0 +1,126 @@ +import gleam/option.{type Option} + +import gwr/syntax/convention +import gwr/syntax/instruction +import gwr/syntax/types +import gwr/syntax/module + +/// https://webassembly.github.io/spec/core/binary/module.html#sections +pub const custom_section_id = 0x00 +pub const type_section_id = 0x01 +pub const import_section_id = 0x02 +pub const function_section_id = 0x03 +pub const table_section_id = 0x04 +pub const memory_section_id = 0x05 +pub const global_section_id = 0x06 +pub const export_section_id = 0x07 +pub const start_section_id = 0x08 +pub const element_section_id = 0x09 +pub const code_section_id = 0x0a +pub const data_section_id = 0x0b +pub const data_count_section_id = 0x0c + +pub type Binary +{ + Binary + ( + version: Int, + length: Int, + module: module.Module + ) +} + +/// Each section consists of +/// - a one-byte section id, +/// - the u32 size of the contents, in bytes, +/// - the actual contents, whose structure is dependent on the section id. +/// https://webassembly.github.io/spec/core/binary/module.html#sections +pub type Section +{ + Section(id: Int, length: Int, content: Option(SectionContent)) +} + +pub type SectionContent +{ + /// Custom sections have the id 0. They are intended to be used for debugging information + /// or third-party extensions, and are ignored by the WebAssembly semantics. Their contents + /// consist of a name further identifying the custom section, followed by an uninterpreted + /// sequence of bytes for custom use. + /// + /// https://webassembly.github.io/spec/core/binary/module.html#custom-section + CustomSection(name: String, data: Option(BitArray)) + + /// The type section has the id 1. It decodes into a vector of function types that represent + /// the component of a module. + /// + /// https://webassembly.github.io/spec/core/binary/module.html#type-section + TypeSection(function_types: convention.Vector(types.FunctionType)) + + // @TODO + ImportSection + + /// The function section has the id 3. It decodes into a vector of type indices that represent + /// the fields of the functions in the component of a module. The and + /// fields of the respective functions are encoded separately in the code section. + /// + /// https://webassembly.github.io/spec/core/binary/module.html#function-section + FunctionSection(type_indices: convention.Vector(Int)) + + /// The table section has the id 4. It decodes into a vector of tables that represent the + /// component of a module. + /// + /// https://webassembly.github.io/spec/core/binary/modules.html#table-section + // @TODO + TableSection + + /// The memory section has the id 5. It decodes into a vector of memories that represent the + /// component of a module. + /// + /// https://webassembly.github.io/spec/core/binary/modules.html#memory-section + MemorySection(memories: convention.Vector(module.Memory)) + + /// The global section has the id 6. It decodes into a vector of globals that represent the + /// component of a module. + /// + /// https://webassembly.github.io/spec/core/binary/modules.html#global-section + GlobalSection(globals: convention.Vector(module.Global)) + + /// The export section has the id 7. It decodes into a vector of exports that represent the exports + /// component of a module. + /// + /// https://webassembly.github.io/spec/core/binary/module.html#export-section + ExportSection(exports: convention.Vector(module.Export)) + + /// The start section has the id 8. It decodes into an optional start function that represents the + /// component of a module. + /// + /// https://webassembly.github.io/spec/core/binary/modules.html#start-section + StartSection(start_function: module.StartFunction) + + // @TODO + ElementSection + + CodeSection(entries: List(Code)) + + // @TODO + DataSection + + // @TODO + DataCountSection +} + +// https://webassembly.github.io/spec/core/binary/module.html#code-section +pub type Code +{ + Code(size: Int, function_code: FunctionCode) +} + +pub type FunctionCode +{ + FunctionCode(locals: convention.Vector(LocalsDeclaration), body: instruction.Expression) +} + +pub type LocalsDeclaration +{ + LocalsDeclaration(count: Int, type_: types.ValueType) +} \ No newline at end of file diff --git a/src/gwr/execution/instance.gleam b/src/gwr/execution/instance.gleam new file mode 100644 index 0000000..5b03bc4 --- /dev/null +++ b/src/gwr/execution/instance.gleam @@ -0,0 +1,45 @@ +import gleam/list +import gleam/result + +import gwr/binary +import gwr/execution/machine +import gwr/execution/runtime +import gwr/parser/binary_parser +import gwr/parser/binary_reader +import gwr/syntax/module + +pub type WebAssemblyInstance +{ + WebAssemblyInstance + ( + binary: binary.Binary, + machine: machine.Machine + ) +} + +pub fn create(from data: BitArray) -> Result(WebAssemblyInstance, String) +{ + use #(_, binary) <- result.try(binary_parser.parse_binary_module(binary_reader.create(from: data))) + use machine <- result.try(machine.initialize(binary.module)) + Ok(WebAssemblyInstance(binary: binary, machine: machine)) +} + +pub fn call(instance: WebAssemblyInstance, name: String, arguments: List(runtime.Value)) -> Result(#(WebAssemblyInstance, List(runtime.Value)), String) +{ + use function_index <- result.try( + case list.find(in: instance.binary.module.exports, one_that: fn (export) { + case export.name == name, export.descriptor + { + True, module.FunctionExport(_) -> True + _, _ -> False + } + }) + { + Ok(module.Export(name: _, descriptor: module.FunctionExport(index))) -> Ok(index) + _ -> Error("gwr/execution/instance.call: couldn't find an exported function with name \"" <> name <>"\" in the given module") + } + ) + + use #(new_state, results) <- result.try(machine.call(instance.machine.state, function_index, arguments)) + Ok(#(WebAssemblyInstance(..instance, machine: machine.Machine(..instance.machine, state: new_state)), results)) +} \ No newline at end of file diff --git a/src/gwr/execution/logic.gleam b/src/gwr/execution/logic.gleam new file mode 100644 index 0000000..c469e98 --- /dev/null +++ b/src/gwr/execution/logic.gleam @@ -0,0 +1,2 @@ +//pub fn i32_add(configuration: Configuration, stack: Stack) -> Result(#(Configuration, Stack), String) +//pub fn local_get(configuration: Configuration, stack: Stack) -> Result(#(Configuration, Stack), String) \ No newline at end of file diff --git a/src/gwr/execution/machine.gleam b/src/gwr/execution/machine.gleam new file mode 100644 index 0000000..bff7a6a --- /dev/null +++ b/src/gwr/execution/machine.gleam @@ -0,0 +1,305 @@ +import gleam/bool +import gleam/int +import gleam/list +import gleam/result +import gleam/option + +import gwr/execution/runtime +import gwr/execution/stack +import gwr/syntax/index +import gwr/syntax/instruction +import gwr/syntax/module +import gwr/syntax/types + +/// A configuration consists of the current store and an executing thread. +/// +/// https://webassembly.github.io/spec/core/exec/runtime.html#configurations +pub type Configuration +{ + Configuration(store: runtime.Store, thread: Thread) +} + +/// A thread is a computation over instructions that operates relative to the state of +/// a current frame referring to the module instance in which the computation runs, i.e., +/// where the current function originates from. +/// +/// NOTE: The current version of WebAssembly is single-threaded, but configurations with +/// multiple threads may be supported in the future. +/// +/// https://webassembly.github.io/spec/core/exec/runtime.html#configurations +pub type Thread +{ + Thread(framestate: stack.FrameState, instructions: List(instruction.Instruction)) +} + +pub type Machine +{ + Machine(module_instance: runtime.ModuleInstance, state: MachineState) +} + +pub type MachineState +{ + MachineState(configuration: Configuration, stack: stack.Stack) +} + +pub fn initialize(from module: module.Module) -> Result(Machine, String) +{ + let module_instance = runtime.ModuleInstance + ( + types: module.types, + function_addresses: [], + table_addresses: [], + memory_addresses: [], + global_addresses: [], + element_addresses: [], + data_addresses: [], + exports: [], + ) + + let config = Configuration + ( + store: runtime.Store + ( + datas: [], + elements: [], + functions: [], + globals: [], + memories: [], + tables: [], + ), + thread: Thread( + framestate: stack.FrameState( + locals: [], + module_instance: module_instance + ), + instructions: [] + ) + ) + + let stack = stack.create() + + // Allocates web aseembly functions + + use #(store, allocations) <- result.try( + list.fold( + from: Ok(#(config.store, [])), + over: module.functions, + with: fn (state, function) + { + use #(store, allocations) <- result.try(state) + allocate_web_assembly_function(function, store, allocations, module.types) + } + ) + ) + + let module_instance = runtime.ModuleInstance(..module_instance, function_addresses: allocations) + + let results = list.filter_map(store.functions, fn (function) { + case function + { + runtime.WebAssemblyFunctionInstance(type_: type_, module_instance: _, code: code) -> Ok(runtime.WebAssemblyFunctionInstance(type_: type_, module_instance: module_instance, code: code)) + _ -> Error(Nil) + } + }) + + let store = runtime.Store(..store, functions: results) + // We need to update the thread's framestate's module_instance too + let thread = Thread(..config.thread, framestate: stack.FrameState(..config.thread.framestate, module_instance: module_instance)) + + Ok(Machine(state: MachineState(configuration: Configuration(store: store, thread: thread), stack: stack), module_instance: module_instance)) + +} + +// Should be the last to be allocated due to WebAssemblyFunctionInstance requering +// a reference to the full initialized ModuleInstance + +pub fn allocate_web_assembly_function(function: module.Function, store: runtime.Store, addresses: List(runtime.Address), types_list: List(types.FunctionType)) -> Result(#(runtime.Store, List(runtime.Address)), String) +{ + let empty_module_instance = runtime.ModuleInstance + ( + types: [], + function_addresses: [], + table_addresses: [], + memory_addresses: [], + global_addresses: [], + element_addresses: [], + data_addresses: [], + exports: [], + ) + + let function_address = runtime.FunctionAddress(list.length(addresses)) + case types_list |> list.take(up_to: function.type_ + 1) |> list.last + { + Ok(function_type) -> + { + Ok( + #( + runtime.Store(..store, functions: list.append(store.functions, [runtime.WebAssemblyFunctionInstance(type_: function_type, module_instance: empty_module_instance, code: function)])), + list.append(addresses, [function_address]) + ) + ) + } + Error(_) -> Error("gwr/execution/machine.allocate_web_assembly_function: couldn't find the type of the function among module instance's types list") + } +} + +pub fn call(state: MachineState, index: index.FunctionIndex, arguments: List(runtime.Value)) -> Result(#(MachineState, List(runtime.Value)), String) +{ + // We can do this because we are allocating functions in the same order as they appears in the Module's list. + // Maybe we can try using something else e.g. a Dict + let function_address = runtime.FunctionAddress(index) + + use function_instance <- result.try( + case state.configuration.store.functions |> list.take(up_to: address_to_int(function_address) + 1) |> list.last + { + Ok(function_instance) -> Ok(function_instance) + Error(_) -> Error("gwr/execution/machine.call: couldn't find a function instance with the give address \"" <> address_to_string(function_address) <> "\"") + } + ) + + case function_instance + { + runtime.WebAssemblyFunctionInstance(type_: function_type, module_instance: function_module_instance, code: function_code) -> + { + let function_locals = list.fold( + from: arguments, // function's arguments will be joined with function's locals + over: function_code.locals, + with: fn (function_locals, local) { + let value = case local + { + types.Number(types.Integer32) -> runtime.Number(runtime.number_value_default_value) + types.Number(types.Integer64) -> runtime.Number(runtime.number_value_default_value) + types.Number(types.Float32) -> runtime.Number(runtime.number_value_default_value) + types.Number(types.Float64) -> runtime.Number(runtime.number_value_default_value) + types.Vector(types.Vector128) -> runtime.Vector(runtime.vector_value_default_value) + types.Reference(types.FunctionReference) -> runtime.Reference(runtime.Null) + types.Reference(types.ExternReference) -> runtime.Reference(runtime.Null) + } + list.append(function_locals, [value]) + }) + let function_frame = stack.ActivationFrame + ( + arity: list.length(function_type.results), // "[...] Activation frames carry the return arity of the respective function [...]" + framestate: stack.FrameState + ( + locals: function_locals, + module_instance: function_module_instance + ) + ) + let new_state_stack = stack.push(push: stack.ActivationEntry(function_frame), to: state.stack) + let new_state_configuration = Configuration(..state.configuration, thread: Thread(framestate: function_frame.framestate, instructions: function_code.body)) + let new_state = MachineState(configuration: new_state_configuration, stack: new_state_stack) + + use after_state <- result.try(execute(new_state)) + + let #(result_stack, result_values) = stack.pop_repeat(after_state.stack, function_frame.arity) + + use <- bool.guard(when: option.values([stack.peek(result_stack)]) != [stack.ActivationEntry(function_frame)], return: Error("gwr/execution/machine.call: expected the last stack frame to be the calling function frame")) + + let result_values = option.values(result_values) + let result_arity = list.length(result_values) + use <- bool.guard(when: result_arity != function_frame.arity, return: Error("gwr/execution/machine.call: expected " <> int.to_string(function_frame.arity) <> " values but got only " <> int.to_string(result_arity))) + + let results = list.filter_map(result_values, fn (entry) { + case entry + { + stack.ValueEntry(v) -> Ok(v) + _ -> Error(Nil) + } + }) + + Ok(#(MachineState(..after_state, stack: result_stack), results)) + } + runtime.HostFunctionInstance(type_: _, code: _) -> Error("@TODO: call host function") + } +} + +pub fn execute(current_state: MachineState) -> Result(MachineState, String) +{ + use new_state <- result.try( + list.fold( + from: Ok(current_state), + over: current_state.configuration.thread.instructions, + with: fn (current_state, instruction) + { + use current_state <- result.try(current_state) + case instruction + { + instruction.LocalGet(index) -> local_get(current_state, index) + instruction.I32Add -> i32_add(current_state) + instruction.I32Const(value) -> i32_const(current_state, value) + _ -> Ok(current_state) + } + } + ) + ) + + Ok(new_state) +} + +pub fn address_to_int(address: runtime.Address) -> Int +{ + case address + { + runtime.FunctionAddress(addr) -> addr + runtime.TableAddress(addr) -> addr + runtime.MemoryAddress(addr) -> addr + runtime.GlobalAddress(addr) -> addr + runtime.ElementAddress(addr) -> addr + runtime.DataAddress(addr) -> addr + runtime.ExternAddress(addr) -> addr + } +} + +pub fn address_to_string(address: runtime.Address) -> String +{ + int.to_string(address_to_int(address)) +} + +pub fn i32_add(state: MachineState) -> Result(MachineState, String) +{ + let #(stack, values) = stack.pop_repeat(state.stack, 2) + use result <- result.try( + case option.values(values) + { + [stack.ValueEntry(runtime.Number(a)), stack.ValueEntry(runtime.Number(b))] -> Ok(a + b) + [stack.LabelEntry(_), _] | [_, stack.LabelEntry(_)] -> Error("gwr/execution/machine.i32_add: expected a value entry but got a label entry") + [stack.ActivationEntry(_), _] | [_, stack.ActivationEntry(_)] -> Error("gwr/execution/machine.i32_add: expected a value entry but got an activation entry") + [] -> Error("gwr/execution/machine.i32_add: empty operands") + _ -> Error("gwr/execution/machine.i32_add: unknown error") + } + ) + Ok( + MachineState( + ..state, + stack: stack.push(stack, stack.ValueEntry(runtime.Number(result))) + ) + ) +} + +pub fn i32_const(state: MachineState, value: Int) +{ + Ok( + MachineState( + ..state, + stack: stack.push(state.stack, stack.ValueEntry(runtime.Number(value))) + ) + ) +} + +pub fn local_get(state: MachineState, index: index.LocalIndex) -> Result(MachineState, String) +{ + use local <- result.try( + case state.configuration.thread.framestate.locals |> list.take(up_to: index + 1) |> list.last + { + Ok(v) -> Ok(v) + Error(_) -> Error("gwr/execution/machine.local_get: couldn't get the local with index " <> int.to_string(index)) + } + ) + Ok( + MachineState( + ..state, + stack: stack.push(state.stack, stack.ValueEntry(local)) + ) + ) +} \ No newline at end of file diff --git a/src/gwr/execution/runtime.gleam b/src/gwr/execution/runtime.gleam new file mode 100644 index 0000000..a3e2a94 --- /dev/null +++ b/src/gwr/execution/runtime.gleam @@ -0,0 +1,162 @@ +import gleam/dynamic + +import gwr/syntax/module +import gwr/syntax/types +import gwr/syntax/value + +pub type ReferenceValueType +{ + Null + ToFunctionAddress(Address) + ToExternAddress(Address) +} + +/// https://webassembly.github.io/spec/core/exec/runtime.html#values +pub type Value +{ + Number(Int) // @TODO: replace with Number(IntegerNumber(Int)) and add Number(FloatNumber(Float)) + Vector(Int) + Reference(ReferenceValueType) +} + +pub const number_value_default_value = 0 +pub const vector_value_default_value = 0 +pub const reference_value_default_value = Null + +/// A result is the outcome of a computation. It is either a sequence of values or a trap. +/// +/// https://webassembly.github.io/spec/core/exec/runtime.html#results +pub type ExecutionResult +{ + Success(List(Value)) + Trap +} + +/// The store represents all global state that can be manipulated by WebAssembly programs. +/// It consists of the runtime representation of all instances of functions, tables, memories, +/// and globals, element segments, and data segments that have been allocated during the +/// life time of the abstract machine. +/// +/// https://webassembly.github.io/spec/core/exec/runtime.html#store +pub type Store +{ + Store + ( + functions: List(FunctionInstance), + tables: List(TableInstance), + memories: List(MemoryInstance), + globals: List(GlobalInstance), + elements: List(ElementInstance), + datas: List(DataInstance), + ) +} + +/// https://webassembly.github.io/spec/core/exec/runtime.html#addresses +pub type Address +{ + FunctionAddress(Int) + TableAddress(Int) + MemoryAddress(Int) + GlobalAddress(Int) + ElementAddress(Int) + DataAddress(Int) + ExternAddress(Int) +} + +/// A module instance is the runtime representation of a module. It is created by instantiating +/// a module, and collects runtime representations of all entities that are imported, defined, +/// or exported by the module. +/// +/// https://webassembly.github.io/spec/core/exec/runtime.html#module-instances +pub type ModuleInstance +{ + ModuleInstance + ( + types: List(types.FunctionType), + function_addresses: List(Address), + table_addresses: List(Address), + memory_addresses: List(Address), + global_addresses: List(Address), + element_addresses: List(Address), + data_addresses: List(Address), + exports: List(ExportInstance), + ) +} + +/// A function instance is the runtime representation of a function. It effectively is a +/// closure of the original function over the runtime module instance of its originating +/// module. The module instance is used to resolve references to other definitions during +/// execution of the function. +/// +/// https://webassembly.github.io/spec/core/exec/runtime.html#function-instances +pub type FunctionInstance +{ + WebAssemblyFunctionInstance(type_: types.FunctionType, module_instance: ModuleInstance, code: module.Function) + HostFunctionInstance(type_: types.FunctionType, code: fn (List(dynamic.Dynamic)) -> List(dynamic.Dynamic)) +} + +/// A table instance is the runtime representation of a table. It records its type and +/// holds a vector of reference value. +/// +/// https://webassembly.github.io/spec/core/exec/runtime.html#table-instances +pub type TableInstance +{ + TableInstance(type_: types.TableType, elements: List(Value)) +} + +/// A memory instance is the runtime representation of a linear memory. It records its +/// type and holds a vector of bytes. +/// +/// https://webassembly.github.io/spec/core/exec/runtime.html#memory-instances +pub type MemoryInstance +{ + MemoryInstance(type_: types.MemoryType, data: BitArray) +} + +/// A global instance is the runtime representation of a global variable. It records its +/// type and holds an individual value. +/// +/// https://webassembly.github.io/spec/core/exec/runtime.html#global-instances +pub type GlobalInstance +{ + GlobalInstance(type_: types.GlobalType, value: Value) +} + +/// An element instance is the runtime representation of an element segment. It holds a +/// vector of references and their common type. +/// +/// https://webassembly.github.io/spec/core/exec/runtime.html#element-instances +pub type ElementInstance +{ + ElementInstance(type_: types.ReferenceType, element: List(Value)) +} + +/// An data instance is the runtime representation of a data segment. It holds a vector of bytes. +/// +/// https://webassembly.github.io/spec/core/exec/runtime.html#data-instances +pub type DataInstance +{ + DataInstance(data: BitArray) +} + +/// An export instance is the runtime representation of an export. It defines +/// the export’s name and the associated external value. +/// +/// https://webassembly.github.io/spec/core/exec/runtime.html#export-instances +pub type ExportInstance +{ + ExportInstance(name: value.Name, value: ExternalValue) +} + +/// An external value is the runtime representation of an entity that can be imported +/// or exported. It is an address denoting either a function instance, table instance, +/// memory instance, or global instances in the shared store. +/// +/// https://webassembly.github.io/spec/core/exec/runtime.html#external-values +pub type ExternalValue +{ + Function(Address) + Table(Address) + Memory(Address) + Global(Address) +} \ No newline at end of file diff --git a/src/gwr/execution/stack.gleam b/src/gwr/execution/stack.gleam new file mode 100644 index 0000000..fba5981 --- /dev/null +++ b/src/gwr/execution/stack.gleam @@ -0,0 +1,78 @@ +import gleam/option +import gleam/iterator +import gleam/list + +import gwr/execution/runtime +import gwr/syntax/instruction + +/// https://webassembly.github.io/spec/core/exec/runtime.html#stack +pub type Stack +{ + Stack(entries: List(StackEntry)) +} + +/// https://webassembly.github.io/spec/core/exec/runtime.html#stack +pub type StackEntry +{ + ValueEntry(runtime.Value) + LabelEntry(Label) + ActivationEntry(ActivationFrame) +} + +/// Labels carry an argument arity and their associated branch target, which is expressed +/// syntactically as an instruction sequence: +/// +/// https://webassembly.github.io/spec/core/exec/runtime.html#labels +pub type Label +{ + Label(arity: Int, target: List(instruction.Instruction)) +} + +/// Activation frames carry the return arity of the respective function, hold the values of +/// its locals (including arguments) in the order corresponding to their static local indices, and +/// a reference to the function’s own module instance: +/// +/// https://webassembly.github.io/spec/core/exec/runtime.html#activation-frames +pub type ActivationFrame +{ + ActivationFrame(arity: Int, framestate: FrameState) +} + +/// https://webassembly.github.io/spec/core/exec/runtime.html#activation-frames +pub type FrameState +{ + FrameState(locals: List(runtime.Value), module_instance: runtime.ModuleInstance) +} + +pub fn create() -> Stack +{ + Stack(entries: []) +} + +pub fn length(from stack: Stack) -> Int +{ + list.length(stack.entries) +} + +pub fn push(to stack: Stack, push new_entry: StackEntry) -> Stack +{ + Stack(entries: list.append(stack.entries, [new_entry])) +} + +pub fn peek(from stack: Stack) -> option.Option(StackEntry) +{ + option.from_result(list.last(stack.entries)) +} + +pub fn pop(from stack: Stack) -> #(Stack, option.Option(StackEntry)) +{ + #(Stack(entries: list.take(from: stack.entries, up_to: length(stack) - 1)), peek(stack)) +} + +pub fn pop_repeat(from stack: Stack, up_to count: Int) +{ + iterator.fold(from: #(stack, []), over: iterator.range(1, count), with: fn (state, _) { + let #(stack, results) = pop(state.0) + #(stack, list.append(state.1, [results])) + }) +} \ No newline at end of file diff --git a/src/gwr/module.gleam b/src/gwr/module.gleam deleted file mode 100644 index dd85375..0000000 --- a/src/gwr/module.gleam +++ /dev/null @@ -1,47 +0,0 @@ -import gleam/bit_array -import gleam/result - -import gleb128 - -pub type Module -{ - Module - ( - version: Int - ) -} - -pub fn try_detect_signature(raw_data: BitArray) -> Bool -{ - case bit_array.slice(at: 0, from: raw_data, take: 4) - { - Ok(<<0x00, 0x61, 0x73, 0x6d>>) -> True - _ -> False - } -} - -pub fn get_module_version(raw_data: BitArray) -> Result(Int, String) -{ - case bit_array.slice(at: 4, from: raw_data, take: 4) - { - Ok(version_raw_data) -> - { - use version <- result.try(gleb128.decode_unsigned(version_raw_data)) - Ok(version.0) - } - _ -> Error("module::get_module_version: can't get module version raw data") - } -} - -pub fn from_raw_data(raw_data: BitArray) -> Result(Module, String) -{ - case try_detect_signature(raw_data) - { - True -> - { - use version <- result.try(get_module_version(raw_data)) - Ok(Module(version: version)) - } - False -> Error("Can't detect module signature") - } -} \ No newline at end of file diff --git a/src/gwr/parser/binary_parser.gleam b/src/gwr/parser/binary_parser.gleam new file mode 100644 index 0000000..1e8413c --- /dev/null +++ b/src/gwr/parser/binary_parser.gleam @@ -0,0 +1,257 @@ +import gleam/bit_array +import gleam/bool +import gleam/int +import gleam/iterator +import gleam/list +import gleam/option.{Some, None} +import gleam/result + +import gwr/binary +import gwr/parser/convention_parser +import gwr/parser/instruction_parser +import gwr/parser/module_parser +import gwr/parser/types_parser +import gwr/parser/value_parser +import gwr/parser/binary_reader +import gwr/syntax/module + +pub fn parse_locals_declaration(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, binary.LocalsDeclaration), String) +{ + use #(reader, count) <- result.try(value_parser.parse_unsigned_leb128_integer(from: reader)) + use #(reader, value_type) <- result.try(types_parser.parse_value_type(from: reader)) + Ok(#(reader, binary.LocalsDeclaration(count: count, type_: value_type))) +} + +pub fn parse_function_code(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, binary.FunctionCode), String) +{ + use #(reader, locals_declaration) <- result.try(convention_parser.parse_vector(from: reader, with: parse_locals_declaration)) + use #(reader, expression) <- result.try(instruction_parser.parse_expression(from: reader)) + Ok(#(reader, binary.FunctionCode(locals: locals_declaration, body: expression))) +} + +pub fn parse_code(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, binary.Code), String) +{ + use #(reader, size) <- result.try(value_parser.parse_unsigned_leb128_integer(from: reader)) + use #(reader, function_code) <- result.try(parse_function_code(from: reader)) + Ok(#(reader, binary.Code(size: size, function_code: function_code))) +} + +pub fn parse_section(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, binary.Section), String) +{ + use #(reader, section_type_id) <- result.try( + case binary_reader.read(from: reader, take: 1) + { + Ok(#(reader, <>)) -> Ok(#(reader, section_type_id)) + _ -> Error("gwr/parser/binary_parser.parse_section: can't get section type id raw data") + } + ) + + use #(reader, section_length) <- result.try(value_parser.parse_unsigned_leb128_integer(from: reader)) + + use remaining_data <- result.try(binary_reader.get_remaining(from: reader)) + let remaining_data_length = bit_array.byte_size(remaining_data) + + use <- bool.guard( + when: section_length > remaining_data_length, + return: Error("gwr/parser/binary_parser.parse_section: unexpected end of the section's content segment. Expected " <> int.to_string(section_length) <> " bytes but got " <> int.to_string(remaining_data_length) <> " bytes") + ) + + use #(reader, decoded_dection) <- result.try( + case binary_reader.can_read(reader), section_type_id // the reverse order throws a syntax error xD + { + True, id if id == binary.custom_section_id -> + { + use #(reader, custom_section_name) <- result.try(value_parser.parse_name(from: reader)) + use #(reader, custom_section_data) <- result.try(binary_reader.read_remaining(from: reader)) + Ok(#(reader, binary.Section(id: binary.custom_section_id, length: section_length, content: Some(binary.CustomSection(name: custom_section_name, data: Some(custom_section_data)))))) + } + True, id if id == binary.type_section_id -> + { + use #(reader, function_types_vec) <- result.try(convention_parser.parse_vector(from: reader, with: types_parser.parse_function_type)) + Ok(#(reader, binary.Section(id: binary.type_section_id, length: section_length, content: Some(binary.TypeSection(function_types: function_types_vec))))) + } + // @TODO True, id if id == import_section_id -> {} + True, id if id == binary.function_section_id -> + { + use #(reader, indices_vec) <- result.try(convention_parser.parse_vector(from: reader, with: fn (reader) { + use #(reader, index) <- result.try(value_parser.parse_unsigned_leb128_integer(from: reader)) + Ok(#(reader, index)) + })) + + Ok(#(reader, binary.Section(id: binary.function_section_id, length: section_length, content: Some(binary.FunctionSection(type_indices: indices_vec))))) + } + // @TODO True, id if id == table_section_id -> {} + True, id if id == binary.memory_section_id -> + { + use #(reader, mem_vec) <- result.try(convention_parser.parse_vector(from: reader, with: module_parser.parse_memory)) + Ok(#(reader, binary.Section(id: binary.memory_section_id, length: section_length, content: Some(binary.MemorySection(memories: mem_vec))))) + } + True, id if id == binary.global_section_id -> + { + use #(reader, globals_vec) <- result.try(convention_parser.parse_vector(from: reader, with: module_parser.parse_global)) + Ok(#(reader, binary.Section(id: binary.global_section_id, length: section_length, content: Some(binary.GlobalSection(globals: globals_vec))))) + } + True, id if id == binary.export_section_id -> + { + use #(reader, exports_vec) <- result.try(convention_parser.parse_vector(from: reader, with: module_parser.parse_export)) + Ok(#(reader, binary.Section(id: binary.export_section_id, length: section_length, content: Some(binary.ExportSection(exports: exports_vec))))) + } + // @TODO True, id if id == start_section_id -> {} + // @TODO True, id if id == element_section_id -> {} + True, id if id == binary.code_section_id -> + { + use #(reader, codes_vec) <- result.try(convention_parser.parse_vector(from: reader, with: parse_code)) + Ok(#(reader, binary.Section(id: binary.code_section_id, length: section_length, content: Some(binary.CodeSection(entries: codes_vec))))) + } + // @TODO True, id if id == data_section_id -> {} + // @TODO True, id if id == data_count_section_id -> {} + + // Empty sections are allowed + False, id if id == binary.custom_section_id -> Ok(#(reader, binary.Section(id: binary.custom_section_id, length: section_length, content: None))) + False, id if id == binary.type_section_id -> Ok(#(reader, binary.Section(id: binary.type_section_id, length: section_length, content: None))) + // @TODO False, id if id == import_section_id -> {} + False, id if id == binary.function_section_id -> Ok(#(reader, binary.Section(id: binary.function_section_id, length: section_length, content: None))) + // @TODO False, id if id == table_section_id -> {} + False, id if id == binary.memory_section_id -> Ok(#(reader, binary.Section(id: binary.memory_section_id, length: section_length, content: None))) + // @TODO False, id if id == global_section_id -> {} + False, id if id == binary.export_section_id -> Ok(#(reader, binary.Section(id: binary.export_section_id, length: section_length, content: None))) + // @TODO False, id if id == start_section_id -> {} + // @TODO False, id if id == element_section_id -> {} + False, id if id == binary.code_section_id -> Ok(#(reader, binary.Section(id: binary.code_section_id, length: section_length, content: None))) + _, _ -> Error("gwr/parser/binary_parser.parse_section: unknown section type id \"" <> int.to_string(section_type_id) <> "\"") + } + ) + Ok(#(reader, decoded_dection)) +} + +pub fn parse_binary_module(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, binary.Binary), String) +{ + use <- bool.guard(when: binary_reader.is_empty(reader), return: Error("gwr/parser/binary_parser.parse_binary_module: empty data")) + + // https://webassembly.github.io/spec/core/binary/module.html#binary-module + // + // The encoding of a module starts with a preamble containing a 4-byte magic number (the string '\0asm') + // and a version field. The current version of the WebAssembly binary format is 1. + + let #(reader, found_magic_number) = case binary_reader.read(from: reader, take: 4) + { + Ok(#(reader, <<0x00, 0x61, 0x73, 0x6d>>)) -> #(reader, True) + Ok(#(reader, _)) -> #(reader, False) + Error(_) -> #(reader, False) + } + + use <- bool.guard(when: !found_magic_number, return: Error("gwr/parser/binary_parser.parse_binary_module: couldn't find module's magic number")) + + // @TODO: I couldn't figure out the version number binary encoding from the spec (LE32 or LEB128 ?), + // therefore the code below may be fixed. + use #(reader, module_wasm_version) <- result.try( + case binary_reader.read(from: reader, take: 4) + { + Ok(#(reader, <>)) -> Ok(#(reader, version)) + _ -> Error("gwr/parser/binary_parser.parse_binary_module: couldn't find module version") + } + ) + + let empty_module = module.Module + ( + types: [], + functions: [], + tables: [], + memories: [], + globals: [], + elements: [], + datas: [], + start: None, + imports: [], + exports: [] + ) + + use #(reader, filled_module, _) <- result.try( + iterator.fold( + from: Ok(#(reader, empty_module, [])), + over: iterator.range(from: 0x00, to: 0x0c), + with: fn (state, _) + { + use #(reader, module, function_section_type_indices) <- result.try(state) + + use <- bool.guard(when: !binary_reader.can_read(reader), return: state) + use #(reader, section) <- result.try(parse_section(from: reader)) + + use #(module, function_section_type_indices) <- result.try( + case section.content + { + None -> Ok(#(module, function_section_type_indices)) + Some(content) -> + { + case content + { + binary.CustomSection(name: _, data: _) -> Ok(#(module, function_section_type_indices)) + binary.TypeSection(function_types: function_types) -> + { + Ok(#(module.Module(..module, types: function_types), function_section_type_indices)) + } + binary.ImportSection -> Ok(#(module, function_section_type_indices)) + binary.FunctionSection(type_indices: type_indices_list) -> + { + // Here we set function_section_type_indices + Ok(#(module, type_indices_list)) + } + binary.TableSection -> Ok(#(module, function_section_type_indices)) + binary.MemorySection(memories: memories_list) -> + { + Ok(#(module.Module(..module, memories: memories_list), function_section_type_indices)) + } + binary.GlobalSection(globals: globals_list) -> + { + Ok(#(module.Module(..module, globals: globals_list), function_section_type_indices)) + } + binary.ExportSection(exports: exports_list) -> + { + Ok(#(module.Module(..module, exports: exports_list), function_section_type_indices)) + } + binary.StartSection(start_function: start_function) -> + { + Ok(#(module.Module(..module, start: Some(start_function)), function_section_type_indices)) + } + binary.ElementSection -> Ok(#(module, function_section_type_indices)) + binary.CodeSection(entries: code_entries) -> + { + use function_list <- result.try( + list.index_fold( + over: code_entries, + from: Ok([]), + with: fn(function_list, entry, index) + { + use function_list <- result.try(function_list) + case function_section_type_indices |> list.take(up_to: index + 1) |> list.last + { + Ok(function_type_index) -> + { + let function = module.Function( + type_: function_type_index, + locals: list.map(entry.function_code.locals, fn (lc) { lc.type_ }), + body: entry.function_code.body + ) + Ok(list.append(function_list, [function])) + } + Error(_) -> Error("gwr/parser/binary_parser.parse_binary_module: couldn't find type index " <> int.to_string(index)) + } + } + ) + ) + Ok(#(module.Module(..module, functions: function_list), function_section_type_indices)) + } + binary.DataSection -> Ok(#(module, function_section_type_indices)) + binary.DataCountSection -> Ok(#(module, function_section_type_indices)) + } + } + } + ) + + Ok(#(reader, module, function_section_type_indices)) + } + ) + ) + + Ok(#(reader, binary.Binary(version: module_wasm_version, length: binary_reader.bytes_read(from: reader), module: filled_module))) +} \ No newline at end of file diff --git a/src/gwr/parser/binary_reader.gleam b/src/gwr/parser/binary_reader.gleam new file mode 100644 index 0000000..54a968c --- /dev/null +++ b/src/gwr/parser/binary_reader.gleam @@ -0,0 +1,60 @@ +import gleam/bit_array +import gleam/bool + +pub type BinaryReader +{ + BinaryReader + ( + data: BitArray, + current_position: Int, + ) +} + +pub fn create(from data: BitArray) -> BinaryReader +{ + BinaryReader(data: data, current_position: 0) +} + +pub fn bytes_read(from reader: BinaryReader) -> Int +{ + reader.current_position +} + +pub fn is_empty(reader: BinaryReader) -> Bool +{ + reader.data == <<>> +} + +pub fn can_read(reader: BinaryReader) -> Bool +{ + reader.current_position < bit_array.byte_size(reader.data) +} + +pub fn read(from reader: BinaryReader, take count: Int) -> Result(#(BinaryReader, BitArray), String) +{ + use <- bool.guard(when: reader.current_position + count > bit_array.byte_size(reader.data), return: Error("no enough bytes")) + case bit_array.slice(at: reader.current_position, from: reader.data, take: count) + { + Ok(data_read) -> Ok(#(BinaryReader(current_position: reader.current_position + count, data: reader.data), data_read)) + _ -> Error("") + } +} + +pub fn advance(from reader: BinaryReader, up_to count: Int) -> BinaryReader +{ + BinaryReader(..reader, current_position: reader.current_position + count) +} + +pub fn get_remaining(from reader: BinaryReader) -> Result(BitArray, String) +{ + case bit_array.slice(at: reader.current_position, from: reader.data, take: bit_array.byte_size(reader.data) - reader.current_position) + { + Ok(remaining_data) -> Ok(remaining_data) + _ -> Error("") + } +} + +pub fn read_remaining(from reader: BinaryReader) -> Result(#(BinaryReader, BitArray), String) +{ + read(from: reader, take: bit_array.byte_size(reader.data) - reader.current_position) +} \ No newline at end of file diff --git a/src/gwr/parser/convention_parser.gleam b/src/gwr/parser/convention_parser.gleam new file mode 100644 index 0000000..399860f --- /dev/null +++ b/src/gwr/parser/convention_parser.gleam @@ -0,0 +1,36 @@ +import gleam/bool +import gleam/iterator +import gleam/list +import gleam/result + +import gwr/parser/binary_reader +import gwr/parser/value_parser + +import gwr/syntax/convention + +pub fn parse_vector( + from reader: binary_reader.BinaryReader, + with parse_element: fn(binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, a), String) +) -> Result(#(binary_reader.BinaryReader, convention.Vector(a)), String) +{ + use #(reader, vector_length) <- result.try(value_parser.parse_unsigned_leb128_integer(from: reader)) + + // If we got a empty vector, then no parsing should be done at all + use <- bool.guard(when: vector_length == 0, return: Ok(#(reader, []))) + + use #(reader, objects_list) <- result.try( + iterator.try_fold( + over: iterator.range(from: 0, to: vector_length - 1), + from: #(reader, []), + with: fn (state, _index) + { + let #(reader, objects_list) = state + use <- bool.guard(when: !binary_reader.can_read(reader), return: Ok(state)) + use #(reader, object) <- result.try(parse_element(reader)) + Ok(#(reader, list.append(objects_list, [object]))) + } + ) + ) + + Ok(#(reader, objects_list)) +} \ No newline at end of file diff --git a/src/gwr/parser/instruction_parser.gleam b/src/gwr/parser/instruction_parser.gleam new file mode 100644 index 0000000..b17f563 --- /dev/null +++ b/src/gwr/parser/instruction_parser.gleam @@ -0,0 +1,82 @@ +import gleam/bit_array +import gleam/bool +import gleam/int +import gleam/iterator +import gleam/list +import gleam/result + +import gwr/syntax/instruction +import gwr/parser/binary_reader +import gwr/parser/value_parser + +pub fn parse_instruction(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, instruction.Instruction), String) +{ + use #(reader, opcode) <- result.try( + case binary_reader.read(from: reader, take: 1) + { + Ok(#(reader, <>)) -> Ok(#(reader, opcode)) + Error(reason) -> Error("gwr/parser/instruction_parser.parse_instruction: couldn't read opcode: " <> reason) + _ -> Error("gwr/parser/instruction_parser.parse_instruction: unknown error reading opcode") + } + ) + + use #(reader, instruction) <- result.try( + case opcode + { + // Control Instructions + // https://webassembly.github.io/spec/core/binary/instruction.html#control-instructions + 0x00 -> Ok(#(reader, instruction.Unreachable)) + 0x01 -> Ok(#(reader, instruction.NoOp)) + // Variable Instructions + // https://webassembly.github.io/spec/core/binary/instruction.html#variable-instructions + 0x20 -> + { + use #(reader, local_index) <- result.try(value_parser.parse_unsigned_leb128_integer(from: reader)) + Ok(#(reader, instruction.LocalGet(index: local_index))) + } + // Numeric Instructions + // https://webassembly.github.io/spec/core/binary/instruction.html#numeric-instructions + 0x6a -> Ok(#(reader, instruction.I32Add)) + 0x41 -> + { + use #(reader, value) <- result.try(value_parser.parse_uninterpreted_leb128_integer(from: reader)) + Ok(#(reader, instruction.I32Const(value: value))) + } + // End + // https://webassembly.github.io/spec/core/binary/instruction.html#expressions + 0x0b -> Ok(#(reader, instruction.End)) + unknown -> Error("gwr/parser/instruction_parser.parse_instruction: unknown opcode \"0x" <> int.to_base16(unknown) <> "\"") + } + ) + + Ok(#(reader, instruction)) +} + +pub fn parse_expression(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, instruction.Expression), String) +{ + use data <- result.try(binary_reader.get_remaining(from: reader)) + let data_length = bit_array.byte_size(data) + + use #(reader, expression) <- result.try( + iterator.fold( + from: Ok(#(reader, [])), + over: iterator.range(1, data_length), + with: fn (state, _) { + + use #(reader, current_expression) <- result.try(state) + + // If the last instruction was an End instruction then no further processing should be done at all + use <- bool.guard(when: list.last(current_expression) == Ok(instruction.End), return: state) + + // If we reached the end of the data then the last instruction there must be an End instruction; otherwise we got an error + use <- bool.guard(when: !binary_reader.can_read(reader) && list.last(current_expression) != Ok(instruction.End), return: Error("gwr/parser/instruction_parser.parse_expression: an expression must terminate with a End instruction")) + + use #(reader, instruction) <- result.try(parse_instruction(from: reader)) + + Ok(#(reader, list.append(current_expression, [instruction]))) + } + ) + ) + + Ok(#(reader, expression)) +} \ No newline at end of file diff --git a/src/gwr/parser/module_parser.gleam b/src/gwr/parser/module_parser.gleam new file mode 100644 index 0000000..44cd523 --- /dev/null +++ b/src/gwr/parser/module_parser.gleam @@ -0,0 +1,63 @@ +import gleam/int +import gleam/result + +import gwr/parser/binary_reader +import gwr/parser/instruction_parser +import gwr/parser/types_parser +import gwr/parser/value_parser + +import gwr/syntax/module + +pub const function_index_id = 0x00 +pub const table_index_id = 0x01 +pub const memory_index_id = 0x02 +pub const global_index_id = 0x03 + +pub fn parse_export(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, module.Export), String) +{ + use #(reader, export_name) <- result.try(value_parser.parse_name(from: reader)) + + use #(reader, export) <- result.try( + case binary_reader.read(from: reader, take: 1) + { + Ok(#(reader, <>)) if id == function_index_id -> + { + use #(reader, index) <- result.try(value_parser.parse_unsigned_leb128_integer(from: reader)) + Ok(#(reader, module.Export(name: export_name, descriptor: module.FunctionExport(index)))) + } + Ok(#(reader, <>)) if id == table_index_id -> + { + use #(reader, index) <- result.try(value_parser.parse_unsigned_leb128_integer(from: reader)) + Ok(#(reader, module.Export(name: export_name, descriptor: module.TableExport(index)))) + } + Ok(#(reader, <>)) if id == memory_index_id -> + { + use #(reader, index) <- result.try(value_parser.parse_unsigned_leb128_integer(from: reader)) + Ok(#(reader, module.Export(name: export_name, descriptor: module.MemoryExport(index)))) + } + Ok(#(reader, <>)) if id == global_index_id -> + { + use #(reader, index) <- result.try(value_parser.parse_unsigned_leb128_integer(from: reader)) + Ok(#(reader, module.Export(name: export_name, descriptor: module.GlobalExport(index)))) + } + Ok(#(_, <>)) -> Error("gwr/parser/module_parser.parse_export: unexpected export id \"" <> int.to_string(unknown) <> "\"") + Error(reason) -> Error("gwr/parser/module_parser.parse_export: couldn't read export id: " <> reason) + _ -> Error("gwr/parser/module_parser.parse_export: unknown error reading export id") + } + ) + + Ok(#(reader, export)) +} + +pub fn parse_global(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, module.Global), String) +{ + use #(reader, global_type) <- result.try(types_parser.parse_global_type(from: reader)) + use #(reader, expression) <- result.try(instruction_parser.parse_expression(from: reader)) + Ok(#(reader, module.Global(type_: global_type, init: expression))) +} + +pub fn parse_memory(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, module.Memory), String) +{ + use #(reader, limits) <- result.try(types_parser.parse_limits(from: reader)) + Ok(#(reader, module.Memory(type_: limits))) +} \ No newline at end of file diff --git a/src/gwr/parser/types_parser.gleam b/src/gwr/parser/types_parser.gleam new file mode 100644 index 0000000..f3002b4 --- /dev/null +++ b/src/gwr/parser/types_parser.gleam @@ -0,0 +1,107 @@ +import gleam/int +import gleam/option.{None, Some} +import gleam/result + +import gwr/parser/convention_parser +import gwr/parser/binary_reader +import gwr/parser/value_parser + +import gwr/syntax/types + +pub fn parse_value_type(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, types.ValueType), String) +{ + use #(reader, value_type_id) <- result.try( + case binary_reader.read(from: reader, take: 1) + { + Ok(#(reader, <>)) -> Ok(#(reader, value_type_id)) + Error(reason) -> Error("gwr/parser/types_parser.parse_value_type: couldn't read value type id: " <> reason) + _ -> Error("gwr/parser/types_parser.parse_value_type: unknown error reading value type id") + } + ) + + use value_type <- result.try( + case value_type_id + { + 0x7f -> Ok(types.Number(types.Integer32)) + 0x7e -> Ok(types.Number(types.Integer64)) + 0x7d -> Ok(types.Number(types.Float32)) + 0x7c -> Ok(types.Number(types.Float64)) + 0x7b -> Ok(types.Vector(types.Vector128)) + 0x70 -> Ok(types.Reference(types.FunctionReference)) + 0x6f -> Ok(types.Reference(types.ExternReference)) + unknown -> Error("gwr/parser/types_parser.parse_value_type: unknown value type \"" <> int.to_string(unknown) <> "\"") + } + ) + + Ok(#(reader, value_type)) +} + +pub fn parse_limits(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, types.Limits), String) +{ + // From the spec: "limits are encoded with a preceding flag indicating whether a maximum is present." + use #(reader, has_max) <- result.try( + case binary_reader.read(from: reader, take: 1) + { + Ok(#(reader, <<0x00>>)) -> Ok(#(reader, False)) + Ok(#(reader, <<0x01>>)) -> Ok(#(reader, True)) + Ok(#(_, <>)) -> Error("gwr/parser/types_parser.parse_limits: unexpected flag value \"" <> int.to_string(unknown) <> "\"") + Error(reason) -> Error("gwr/parser/types_parser.parse_limits: couldn't read flag value: " <> reason) + _ -> Error("gwr/parser/types_parser.parse_limits: unknown error reading flag value") + } + ) + + use #(reader, min) <- result.try(value_parser.parse_unsigned_leb128_integer(from: reader)) + use #(reader, max) <- result.try( + case has_max + { + True -> + { + use #(reader, max) <- result.try(value_parser.parse_unsigned_leb128_integer(from: reader)) + Ok(#(reader, Some(max))) + } + False -> Ok(#(reader, None)) + } + ) + + Ok(#(reader, types.Limits(min: min, max: max))) +} + +/// Decodes a bit array into a FunctionType. The FunctionType bit array begins with 0x60 byte id +/// and follows with 2 vectors, each containing an arbitrary amount of 1-byte ValueType(s). +/// The first vector represents the list of parameter types of the function, while the second vector represents +/// the list of result types returned by the function. +pub fn parse_function_type(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, types.FunctionType), String) +{ + use #(reader, function_type) <- result.try( + case binary_reader.read(from: reader, take: 1) + { + Ok(#(reader, <<0x60>>)) -> + { + use #(reader, parameters_vec) <- result.try(convention_parser.parse_vector(from: reader, with: parse_value_type)) + use #(reader, results_vec) <- result.try(convention_parser.parse_vector(from: reader, with: parse_value_type)) + Ok(#(reader, types.FunctionType(parameters: parameters_vec, results: results_vec))) + } + Ok(#(_, <>)) -> Error("gwr/parser/types_parser.parse_function_type: unexpected function type id \"" <> int.to_string(unkown) <> "\"") + Error(reason) -> Error("gwr/parser/types_parser.parse_function_type: couldn't read function type id: " <> reason) + _ -> Error("gwr/parser/types_parser.parse_function_type: unknown error reading function type id") + } + ) + + Ok(#(reader, function_type)) +} + +pub fn parse_global_type(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, types.GlobalType), String) +{ + use #(reader, value_type) <- result.try(parse_value_type(from: reader)) + use #(reader, mutability) <- result.try( + case binary_reader.read(from: reader, take: 1) + { + Ok(#(reader, <<0x00>>)) -> Ok(#(reader, types.Constant)) + Ok(#(reader, <<0x01>>)) -> Ok(#(reader, types.Variable)) + Ok(#(_, <>)) -> Error("gwr/parser/types_parser.parse_global_type: unexpected mutability flag value \"" <> int.to_string(unkown) <> "\"") + Error(reason) -> Error("gwr/parser/types_parser.parse_global_type: couldn't read mutability flag value: " <> reason) + _ -> Error("gwr/parser/types_parser.parse_global_type: unknown error reading mutability flag value") + } + ) + Ok(#(reader, types.GlobalType(value_type: value_type, mutability: mutability))) +} \ No newline at end of file diff --git a/src/gwr/parser/value_parser.gleam b/src/gwr/parser/value_parser.gleam new file mode 100644 index 0000000..abbcaa8 --- /dev/null +++ b/src/gwr/parser/value_parser.gleam @@ -0,0 +1,60 @@ +import gleam/bit_array +import gleam/bool +import gleam/int +import gleam/result + +import gwr/parser/binary_reader +import gwr/syntax/value + +import gleb128 + +pub fn parse_unsigned_leb128_integer(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, Int), String) +{ + use remaining_data <- result.try(binary_reader.get_remaining(from: reader)) + use #(result, bytes_read) <- result.try(gleb128.fast_decode_unsigned(remaining_data)) + let reader = binary_reader.advance(reader, bytes_read) + Ok(#(reader, result)) +} + +pub fn parse_signed_leb128_integer(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, Int), String) +{ + use remaining_data <- result.try(binary_reader.get_remaining(from: reader)) + use #(result, bytes_read) <- result.try(gleb128.fast_decode_signed(remaining_data)) + let reader = binary_reader.advance(reader, bytes_read) + Ok(#(reader, result)) +} + +pub fn parse_uninterpreted_leb128_integer(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, Int), String) +{ + parse_signed_leb128_integer(from: reader) +} + +pub fn parse_name(from reader: binary_reader.BinaryReader) -> Result(#(binary_reader.BinaryReader, value.Name), String) +{ + use #(reader, name_length) <- result.try(parse_unsigned_leb128_integer(from: reader)) + + use remaining_data <- result.try(binary_reader.get_remaining(from: reader)) + let remaining_data_length = bit_array.byte_size(remaining_data) + + use <- bool.guard( + when: name_length > remaining_data_length, + return: Error("gwr/parser/value_parser.parse_name: unexpected end of the name's data. Expected = " <> int.to_string(name_length) <> " bytes but got = " <> int.to_string(remaining_data_length) <> " bytes") + ) + + use #(reader, result) <- result.try( + case binary_reader.read(from: reader, take: name_length) + { + Ok(#(reader, name_data)) -> + { + case bit_array.to_string(name_data) + { + Ok(name_string) -> Ok(#(reader, name_string)) + Error(_) -> Error("gwr/parser/value_parser.parse_name: invalid UTF-8 name data") + } + } + Error(reason) -> Error("gwr/parser/value_parser.parse_name: couldn't parse name: " <> reason) + } + ) + + Ok(#(reader, result)) +} \ No newline at end of file diff --git a/src/gwr/section.gleam b/src/gwr/section.gleam deleted file mode 100644 index ecc8e8e..0000000 --- a/src/gwr/section.gleam +++ /dev/null @@ -1,162 +0,0 @@ -import gleam/bit_array -import gleam/bool -import gleam/int -import gleam/option.{type Option, Some, None} -import gleam/result - -import gwr/util -import gwr/types/limits.{type Limits} -import gwr/types/name -import gwr/types/vector - -// Each section consists of -// - a one-byte section id, -// - the u32 size of the contents, in bytes, -// - the actual contents, whose structure is dependent on the section id. - -// https://webassembly.github.io/spec/core/binary/modules.html#sections -pub type RawSection -{ - RawSection(type_id: Int, length: Int, content: Option(BitArray)) -} - -pub type Section -{ - // Custom sections have the id 0. They are intended to be used for debugging information or third-party extensions, and are ignored by the WebAssembly semantics. - // Their contents consist of a name further identifying the custom section, followed by an uninterpreted sequence of bytes for custom use. - Custom(length: Int, name: String, data: Option(BitArray)) - Type(length: Int) - Import(length: Int) - Function(length: Int) - Table(length: Int) - Memory(length: Int, memories: List(Limits)) - Global(length: Int) - Export(length: Int) - Start(length: Int) - Element(length: Int) - Code(length: Int) - Data(length: Int) - DataCount(length: Int) -} - -// https://webassembly.github.io/spec/core/binary/modules.html#sections -pub const custom_section_id = 0x00 -pub const type_section_id = 0x01 -pub const import_section_id = 0x02 -pub const function_section_id = 0x03 -pub const table_section_id = 0x04 -pub const memory_section_id = 0x05 -pub const global_section_id = 0x06 -pub const export_section_id = 0x07 -pub const start_section_id = 0x08 -pub const element_section_id = 0x09 -pub const code_section_id = 0x0a -pub const data_section_id = 0x0b -pub const data_count_section_id = 0x0c - -pub fn parse_raw_section(at position: Int, from raw_data: BitArray) -> Result(RawSection, String) -{ - use section_type_id <- result.try( - case bit_array.slice(at: position, from: raw_data, take: 1) - { - Ok(section_type_id_raw_data) -> case section_type_id_raw_data - { - <> -> Ok(section_type_id_decoded) - _ -> Error("section::parse_raw_section: can't decode section type id raw data into an integer") - } - Error(_) -> Error("section::parse_raw_section: can't get section type id raw data") - } - ) - - use #(section_length, section_length_word_size) <- result.try(util.decode_u32leb128(at: position + 1, from: raw_data)) - - let section_raw_content = case section_length > 0 - { - True -> - { - use <- bool.guard( - when: position + 1 + section_length_word_size + section_length > bit_array.byte_size(raw_data), - return: Error("section::parse_raw_section: unexpected end of the section's content segment") - ) - case bit_array.slice(at: position + 1 + section_length_word_size, from: raw_data, take: section_length) - { - Ok(content) -> Ok(Some(content)) - Error(_) -> Error("section::parse_raw_section: can't get the section's content segment") - } - } - False -> Ok(None) - } - - use section_raw_content <- result.try( - case section_raw_content - { - Ok(v) -> Ok(v) - Error(reason) -> Error(reason) - } - ) - - Ok(RawSection(type_id: section_type_id, length: section_length, content: section_raw_content)) -} - -pub fn decode_section(at position: Int, from raw_data: BitArray) -> Result(Section, String) -{ - use parsed_raw_section <- result.try(parse_raw_section(at: position, from: raw_data)) - case parsed_raw_section.type_id - { - n if n == custom_section_id -> decode_custom_section(parsed_raw_section) - //n if n == type_section_id -> Ok(Type) - //n if n == import_section_id -> Ok(Import) - //n if n == function_section_id -> Ok(Function) - //n if n == table_section_id -> Ok(Table) - n if n == memory_section_id -> decode_memory_section(parsed_raw_section) - //n if n == global_section_id -> Ok(Global) - //n if n == export_section_id -> Ok(Export) - //n if n == start_section_id -> Ok(Start) - //n if n == element_section_id -> Ok(Element) - //n if n == code_section_id -> Ok(Code) - //n if n == data_section_id -> Ok(Data) - //n if n == data_count_section_id -> Ok(DataCount) - _ -> Error("section::decode_section: unknown section type id \"" <> int.to_string(parsed_raw_section.type_id) <> "\"") - } -} - -pub fn decode_custom_section(raw_section: RawSection) -> Result(Section, String) -{ - case raw_section.content - { - Some(content) -> - { - use custom_section_name <- result.try(name.from_raw_data(at: 0, from: content)) - use name_string <- result.try(name.to_string(custom_section_name)) - let name_length = name.length(custom_section_name) - let content_length = bit_array.byte_size(content) - - case bit_array.slice(at: name_length + 1, from: content, take: content_length - name_length - 1) - { - Ok(custom_data) -> Ok(Custom(length: raw_section.length, name: name_string, data: Some(custom_data))) - Error(_) -> Error("section::decode_custom_section: can't get custom data") - } - } - None -> Error("section::decode_custom_section: empty section content") - } -} - -pub fn decode_memory_section(raw_section: RawSection) -> Result(Section, String) -{ - case raw_section.content - { - Some(content) -> - { - use raw_vec <- result.try(vector.from_raw_data(at: 0, from: content)) - use mem_vec <- result.try(limits.from_vector(raw_vec)) - Ok(Memory(length: raw_section.length, memories: mem_vec.0)) - } - None -> Error("section::decode_memory_section: empty section content") - } -} - -pub fn from_raw_data(at position: Int, from raw_data: BitArray) -> Result(Section, String) -{ - use sec <- result.try(decode_section(at: position, from: raw_data)) - Ok(sec) -} \ No newline at end of file diff --git a/src/gwr/syntax/convention.gleam b/src/gwr/syntax/convention.gleam new file mode 100644 index 0000000..b306035 --- /dev/null +++ b/src/gwr/syntax/convention.gleam @@ -0,0 +1,2 @@ +/// https://webassembly.github.io/spec/core/syntax/conventions.html#vectors +pub type Vector(a) = List(a) \ No newline at end of file diff --git a/src/gwr/syntax/index.gleam b/src/gwr/syntax/index.gleam new file mode 100644 index 0000000..69043aa --- /dev/null +++ b/src/gwr/syntax/index.gleam @@ -0,0 +1,9 @@ +pub type TypeIndex = Int +pub type FunctionIndex = Int +pub type TableIndex = Int +pub type MemoryIndex = Int +pub type GlobalIndex = Int +pub type ElementIndex = Int +pub type DataIndex = Int +pub type LocalIndex = Int +pub type LabelIndex = Int \ No newline at end of file diff --git a/src/gwr/syntax/instruction.gleam b/src/gwr/syntax/instruction.gleam new file mode 100644 index 0000000..591206e --- /dev/null +++ b/src/gwr/syntax/instruction.gleam @@ -0,0 +1,22 @@ +import gwr/syntax/index + +pub type Instruction +{ + /// https://webassembly.github.io/spec/core/binary/instructions.html#control-instructions + Unreachable + NoOp + /// https://webassembly.github.io/spec/core/binary/instructions.html#variable-instructions + LocalGet(index: index.LocalIndex) + /// https://webassembly.github.io/spec/core/binary/instructions.html#numeric-instructions + I32Add + I32Const(value: Int) + /// https://webassembly.github.io/spec/core/binary/instructions.html#expressions + End +} + +/// Function bodies, initialization values for globals, elements and offsets +/// of element segments, and offsets of data segments are given as expressions, +/// which are sequences of instructions terminated by an marker. +/// +/// https://webassembly.github.io/spec/core/syntax/instructions.html#expressions +pub type Expression = List(Instruction) \ No newline at end of file diff --git a/src/gwr/syntax/module.gleam b/src/gwr/syntax/module.gleam new file mode 100644 index 0000000..bb50c1b --- /dev/null +++ b/src/gwr/syntax/module.gleam @@ -0,0 +1,117 @@ +import gleam/option.{type Option} + +import gwr/syntax/convention +import gwr/syntax/instruction +import gwr/syntax/index +import gwr/syntax/types +import gwr/syntax/value + +/// WebAssembly programs are organized into modules, which are the unit of deployment, +/// loading, and compilation. A module collects definitions for types, functions, tables, +/// memories, and globals. In addition, it can declare imports and exports and provide +/// initialization in the form of data and element segments, or a start function. +/// +/// https://webassembly.github.io/spec/core/syntax/modules.html#modules +pub type Module +{ + Module + ( + types: convention.Vector(types.FunctionType), + functions: convention.Vector(Function), + tables: convention.Vector(Table), + memories: convention.Vector(Memory), + globals: convention.Vector(Global), + elements: convention.Vector(ElementSegment), + datas: convention.Vector(DataSegment), + start: Option(StartFunction), + imports: convention.Vector(Import), + exports: convention.Vector(Export) + ) +} + +/// https://webassembly.github.io/spec/core/syntax/modules.html#functions +pub type Function +{ + Function(type_: index.TypeIndex, locals: convention.Vector(types.ValueType), body: instruction.Expression) +} + +/// https://webassembly.github.io/spec/core/syntax/modules.html#tables +pub type Table +{ + Table(type_: types.TableType) +} + +/// https://webassembly.github.io/spec/core/syntax/modules.html#memories +pub type Memory +{ + Memory(type_: types.MemoryType) +} + +/// https://webassembly.github.io/spec/core/syntax/modules.html#globals +pub type Global +{ + Global(type_: types.GlobalType, init: instruction.Expression) +} + +/// https://webassembly.github.io/spec/core/syntax/modules.html#element-segments +pub type ElementSegmentMode +{ + PassiveElementSegment + ActiveElementSegment(table: index.TableIndex, offset: instruction.Expression) + DeclarativeElementSegment +} + +/// https://webassembly.github.io/spec/core/syntax/modules.html#element-segments +pub type ElementSegment +{ + ElementSegment(type_: types.ReferenceType, init: convention.Vector(instruction.Expression), mode: ElementSegmentMode) +} + +/// https://webassembly.github.io/spec/core/syntax/modules.html#data-segments +pub type DataSegmentMode +{ + PassiveDataSegment + ActiveDataSegment(memory: index.MemoryIndex, offset: instruction.Expression) +} + +/// https://webassembly.github.io/spec/core/syntax/modules.html#data-segments +pub type DataSegment +{ + DataSegment(init: BitArray, mode: DataSegmentMode) +} + +/// https://webassembly.github.io/spec/core/syntax/modules.html#start-function +pub type StartFunction +{ + StartFunction(function: index.FunctionIndex) +} + +/// https://webassembly.github.io/spec/core/syntax/modules.html#imports +pub type ImportDescriptor +{ + FunctionImport(function: index.FunctionIndex) + TableImport(table: types.TableType) + MemoryImport(memory: types.MemoryType) + GlobalImport(global: types.GlobalType) +} + +/// https://webassembly.github.io/spec/core/syntax/modules.html#imports +pub type Import +{ + Import(module: value.Name, name: value.Name, descriptor: ImportDescriptor) +} + +/// https://webassembly.github.io/spec/core/syntax/modules.html#exports +pub type ExportDescriptor +{ + FunctionExport(index: index.FunctionIndex) + TableExport(index: index.TableIndex) + MemoryExport(index: index.MemoryIndex) + GlobalExport(index: index.GlobalIndex) +} + +/// https://webassembly.github.io/spec/core/syntax/modules.html#exports +pub type Export +{ + Export(name: value.Name, descriptor: ExportDescriptor) +} \ No newline at end of file diff --git a/src/gwr/syntax/types.gleam b/src/gwr/syntax/types.gleam new file mode 100644 index 0000000..9572216 --- /dev/null +++ b/src/gwr/syntax/types.gleam @@ -0,0 +1,102 @@ +import gleam/option.{type Option} + +/// Number types classify numeric values. +/// +/// https://webassembly.github.io/spec/core/syntax/types.html#number-types +pub type NumberType +{ + Integer32 + Integer64 + Float32 + Float64 +} + +/// Vector types classify vectors of numeric values processed by vector instructions (also known as SIMD instructions, single instruction multiple data). +/// +/// https://webassembly.github.io/spec/core/syntax/types.html#vector-types +pub type VectorType +{ + Vector128 +} + +/// Reference types classify first-class references to objects in the runtime store. +/// +/// https://webassembly.github.io/spec/core/syntax/types.html#reference-types +pub type ReferenceType +{ + FunctionReference + ExternReference +} + +/// Value types classify the individual values that WebAssembly code can compute +/// with and the values that a variable accepts. They are either number types, +/// vector types, or reference types. +/// +/// https://webassembly.github.io/spec/core/syntax/types.html#value-types +pub type ValueType +{ + Number(NumberType) + Vector(VectorType) + Reference(ReferenceType) +} + +/// Result types classify the result of executing instructions or functions, +/// which is a sequence of values, written with brackets. +/// +/// https://webassembly.github.io/spec/core/syntax/types.html#result-types +pub type ResultType = List(ValueType) + +/// Function types classify the signature of functions, mapping a vector of parameters +/// to a vector of results. They are also used to classify the inputs and outputs of instructions. +/// +/// https://webassembly.github.io/spec/core/syntax/types.html#function-types +pub type FunctionType +{ + FunctionType(parameters: List(ValueType), results: List(ValueType)) +} + +/// Limits classify the size range of resizeable storage associated with memory types and table types. +/// +/// https://webassembly.github.io/spec/core/syntax/types.html#limits +pub type Limits +{ + Limits(min: Int, max: Option(Int)) +} + +/// Memory types classify linear memories and their size range. +/// +/// https://webassembly.github.io/spec/core/syntax/types.html#memory-types +pub type MemoryType = Limits + +/// Table types classify tables over elements of reference type within a size range. +/// +/// https://webassembly.github.io/spec/core/syntax/types.html#syntax-tabletype +pub type TableType +{ + TableType(limits: Limits, elements: ReferenceType) +} + +pub type Mutability +{ + Constant + Variable +} + +/// Global types classify global variables, which hold a value and can either be mutable or immutable. +/// +/// https://webassembly.github.io/spec/core/syntax/types.html#global-types +pub type GlobalType +{ + GlobalType(mutability: Mutability, value_type: ValueType) +} + +/// External types classify imports and external values with their respective types. +/// +/// https://webassembly.github.io/spec/core/syntax/types.html#external-types +pub type ExternalType +{ + Function(FunctionType) + Table(TableType) + Memory(MemoryType) + Global(GlobalType) +} \ No newline at end of file diff --git a/src/gwr/syntax/value.gleam b/src/gwr/syntax/value.gleam new file mode 100644 index 0000000..cb0ae10 --- /dev/null +++ b/src/gwr/syntax/value.gleam @@ -0,0 +1,4 @@ +/// Names are sequences of characters, which are scalar values as defined by Unicode (Section 2.4). +/// +/// https://webassembly.github.io/spec/core/syntax/values.html#names +pub type Name = String \ No newline at end of file diff --git a/src/gwr/types/limits.gleam b/src/gwr/types/limits.gleam deleted file mode 100644 index 2e7ef7a..0000000 --- a/src/gwr/types/limits.gleam +++ /dev/null @@ -1,81 +0,0 @@ -import gleam/bit_array -import gleam/list -import gleam/option.{type Option, Some, None} -import gleam/result - -import gwr/util -import gwr/types/vector as vector - -// https://webassembly.github.io/spec/core/binary/types.html#limits -pub type Limits -{ - Limits(min: Int, max: Option(Int)) -} - -pub fn from_raw_data(at position: Int, from raw_data: BitArray) -> Result(#(Limits, Int), String) -{ - // From the spec: "limits are encoded with a preceding flag indicating whether a maximum is present." - use has_max <- result.try( - case bit_array.slice(at: position, from: raw_data, take: 1) - { - Ok(<<0x00>>) -> Ok(False) - Ok(<<0x01>>) -> Ok(True) - Ok(_) | Error(_) -> Error("limits::from_raw_data: can't get has_max flag") - } - ) - - use #(min, min_word_size) <- result.try(util.decode_u32leb128(at: position + 1, from: raw_data)) - let bytes_read = 1 + min_word_size - - use maybe_max <- result.try( - case has_max - { - True -> - { - use mx <- result.try(util.decode_u32leb128(at: position + min_word_size + 1, from: raw_data)) - Ok(Some(mx)) - } - False -> - { - Ok(None) - } - } - ) - - let bytes_read = case maybe_max - { - Some(#(_, max_word_size)) -> bytes_read + max_word_size - None -> bytes_read - } - - case maybe_max - { - Some(#(max, _)) -> Ok(#(Limits(min: min, max: Some(max)), bytes_read)) - None -> Ok(#(Limits(min: min, max: None), bytes_read)) - } -} - -pub fn do_from_vector(at position_accumulator: Int, from vec: vector.Vector, to result_accumulator: List(Limits)) -> Result(#(List(Limits), Int), String) -{ - case position_accumulator < vec.length - { - True -> - { - case from_raw_data(at: position_accumulator, from: vec.data) - { - Ok(limit) -> - { - let result_accumulator = list.append(result_accumulator, [limit.0]) - do_from_vector(at: position_accumulator + limit.1, from: vec, to: result_accumulator) - } - Error(reason) -> Error(reason) - } - } - False -> Ok(#(result_accumulator, position_accumulator)) - } -} - -pub fn from_vector(from vec: vector.Vector) -{ - do_from_vector(at: 0, from: vec, to: []) -} \ No newline at end of file diff --git a/src/gwr/types/name.gleam b/src/gwr/types/name.gleam deleted file mode 100644 index 20a0b2c..0000000 --- a/src/gwr/types/name.gleam +++ /dev/null @@ -1,32 +0,0 @@ - -import gleam/bit_array -import gleam/result - -import gwr/types/vector as vector - -// Names are encoded as a vector of bytes containing the Unicode (Section 3.9) UTF-8 encoding of the name’s character sequence. -// https://webassembly.github.io/spec/core/binary/values.html#binary-name -pub type Name -{ - Name(vector: vector.Vector) -} - -pub fn from_raw_data(at position: Int, from raw_data: BitArray) -> Result(Name, String) -{ - use vec <- result.try(vector.from_raw_data(at: position, from: raw_data)) - Ok(Name(vector: vec)) -} - -pub fn to_string(name: Name) -> Result(String, String) -{ - case bit_array.to_string(name.vector.data) - { - Ok(str) -> Ok(str) - Error(_) -> Error("name::show: invalid UTF-8 vector data") - } -} - -pub fn length(of name: Name) -> Int -{ - name.vector.length -} diff --git a/src/gwr/types/vector.gleam b/src/gwr/types/vector.gleam deleted file mode 100644 index bfa8eb3..0000000 --- a/src/gwr/types/vector.gleam +++ /dev/null @@ -1,32 +0,0 @@ -import gleam/bit_array -import gleam/bool -import gleam/int -import gleam/result - -import gwr/util - -// Vectors are encoded with their u32 length followed by the encoding of their element sequence. -// https://webassembly.github.io/spec/core/binary/conventions.html#binary-vec -pub type Vector -{ - Vector(length: Int, data: BitArray) -} - -pub fn from_raw_data(at position: Int, from raw_data: BitArray) -> Result(Vector, String) -{ - use #(vector_length, vector_length_word_size) <- result.try(util.decode_u32leb128(at: position, from: raw_data)) - - use <- bool.guard( - when: position + vector_length_word_size + vector_length > bit_array.byte_size(raw_data), - return: Error("vector::from_raw_data: unexpected end of the vector's data. Expected = " <> int.to_string(vector_length) <> " bytes but got = " <> int.to_string(bit_array.byte_size(raw_data) - vector_length_word_size - position) <> " bytes") - ) - - case bit_array.slice(at: position + vector_length_word_size, from: raw_data, take: vector_length) - { - Ok(vector_data) -> - { - Ok(Vector(length: vector_length, data: vector_data)) - } - Error(_) -> Error("vector::from_raw_data: can't slice vector data") - } -} \ No newline at end of file diff --git a/src/gwr/util.gleam b/src/gwr/util.gleam deleted file mode 100644 index cb49461..0000000 --- a/src/gwr/util.gleam +++ /dev/null @@ -1,20 +0,0 @@ -import gleam/bit_array -import gleam/int -import gleam/result - -import gleb128 - -pub fn decode_u32leb128(at position: Int, from data: BitArray) -> Result(#(Int, Int), String) -{ - let u32leb128_max_length = 5 // ceil(32 / 7) bytes - let bytes_to_take = int.min(u32leb128_max_length, bit_array.byte_size(data) - position) - case bit_array.slice(at: position, from: data, take: bytes_to_take) - { - Ok(u32leb128_raw_data) -> - { - use section_length <- result.try(gleb128.decode_unsigned(u32leb128_raw_data)) - Ok(section_length) - } - Error(_) -> Error("util::decode_u32leb128: can't get section length raw data") - } -} \ No newline at end of file diff --git a/test/assets/sum.wasm b/test/assets/sum.wasm new file mode 100644 index 0000000000000000000000000000000000000000..30eed3fb5bd315ea25210ab498af3a16af50d3ca GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$CF3n|N;NoOtXHZ~JV9eqM0DhYVY5)KL literal 0 HcmV?d00001 diff --git a/test/assets/sum.wat b/test/assets/sum.wat new file mode 100644 index 0000000..10b4e7e --- /dev/null +++ b/test/assets/sum.wat @@ -0,0 +1,8 @@ +;; sum.wat + +(module + (type $t0 (func (param i32 i32) (result i32))) + (func $sum (export "sum") (type $t0) (param $p0 i32) (param $p1 i32) (result i32) + (i32.add (local.get $p0) (local.get $p1)) + ) +) \ No newline at end of file diff --git a/test/gwr/execution/execution_test.gleam b/test/gwr/execution/execution_test.gleam new file mode 100644 index 0000000..baf9741 --- /dev/null +++ b/test/gwr/execution/execution_test.gleam @@ -0,0 +1,19 @@ +import gwr/execution/instance +import gwr/execution/runtime + +import gleeunit +import gleeunit/should +import simplifile + +pub fn main() +{ + gleeunit.main() +} + +pub fn sum_test() +{ + let module_data = simplifile.read_bits(from: "./test/assets/sum.wasm") |> should.be_ok + let instance = instance.create(from: module_data) |> should.be_ok + let #(_, result) = instance.call(instance, "sum", [runtime.Number(4), runtime.Number(2)]) |> should.be_ok + result |> should.equal([runtime.Number(6)]) +} \ No newline at end of file diff --git a/test/gwr/module_test.gleam b/test/gwr/module_test.gleam deleted file mode 100644 index d9585ad..0000000 --- a/test/gwr/module_test.gleam +++ /dev/null @@ -1,43 +0,0 @@ -import gleeunit -import gleeunit/should - -import gwr/module - -pub fn main() -{ - gleeunit.main() -} - -pub fn try_detect_signature_test() -{ - module.try_detect_signature(<<0x00, 0x61, 0x73, 0x6d>>) - |> should.be_true -} - -pub fn get_module_version_test() -{ - module.get_module_version(<<0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00>>) - |> should.be_ok - |> should.equal(1) -} - -pub fn get_module_version_should_fail_parse_module_version_test() -{ - module.get_module_version(<<0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00>>) - |> should.be_error - |> should.equal("module::get_module_version: can't get module version raw data") -} - -pub fn get_module_version_should_fail_invalid_module_test() -{ - module.get_module_version(<<0x00, 0x61, 0x73, 0x6d>>) - |> should.be_error - |> should.equal("module::get_module_version: can't get module version raw data") -} - -pub fn from_raw_data_test() -{ - module.from_raw_data(<<0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00>>) - |> should.be_ok - |> should.equal(module.Module(version: 1)) -} \ No newline at end of file diff --git a/test/gwr/parser/binary_parser_test.gleam b/test/gwr/parser/binary_parser_test.gleam new file mode 100644 index 0000000..270eff5 --- /dev/null +++ b/test/gwr/parser/binary_parser_test.gleam @@ -0,0 +1,507 @@ +import gleam/option.{None, Some} + +import gwr/binary +import gwr/parser/binary_parser +import gwr/parser/binary_reader +import gwr/syntax/instruction +import gwr/syntax/module +import gwr/syntax/types + +import gleeunit +import gleeunit/should + +pub fn main() +{ + gleeunit.main() +} + +pub fn parse_binary_module___empty_module___test() +{ + let reader = binary_reader.create(from: <<0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00>>) + binary_parser.parse_binary_module(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 8), + binary.Binary + ( + version: 1, + length: 8, + module: module.Module + ( + types: [], + functions: [], + tables: [], + memories: [], + globals: [], + elements: [], + datas: [], + start: None, + imports: [], + exports: [] + ) + ) + ) + ) +} + +pub fn parse_binary_module___basic_add___test() +{ + let reader = binary_reader.create(from: << + 0x00, 0x61, 0x73, 0x6d, // Magic number + 0x01, 0x00, 0x00, 0x00, // Version = 1 + 0x01, 0x07, // Type Section with length = 7 bytes + 0x01, // Vector(FunctionType) with 1 items + 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, // [0] = FunctionType(parameters: [I32, I32], results: [I32]) + 0x03, 0x02, // Function Section with length = 2 bytes + 0x01, // Vector(TypeIndex) with 1 items + 0x00, // [0] = 0 + 0x07, 0x0a, // Export Section with length = 10 bytes + 0x01, // Vector(Export) with 1 items + 0x06, 0x61, 0x64, 0x64, 0x54, 0x77, 0x6f, 0x00, 0x00, // [0] = Export(name: "addTwo", descriptor: FunctionExport(index: 0)) + 0x0a, 0x09, // Code Section with length = 9 bytes + 0x01, // Vector(Code) with 1 items + 0x07, // [0] = Code with size = 7 bytes + 0x00, // Vector(LocalsDeclaration) with 0 items + 0x20, 0x00, // LocalGet(index: 0) + 0x20, 0x01, // LocalGet(index: 1) + 0x6a, // I32Add + 0x0b // End + >>) + binary_parser.parse_binary_module(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 44), + binary.Binary + ( + version: 1, + length: 44, + module: module.Module + ( + types: [types.FunctionType([types.Number(types.Integer32), types.Number(types.Integer32)], [types.Number(types.Integer32)])], + functions: [module.Function(0, [], [instruction.LocalGet(0), instruction.LocalGet(1), instruction.I32Add, instruction.End])], + tables: [], + memories: [], + globals: [], + elements: [], + datas: [], + start: None, + imports: [], + exports: [module.Export("addTwo", module.FunctionExport(0))] + ) + ) + ) + ) +} + +pub fn parse_binary_module___empty_data___test() +{ + binary_parser.parse_binary_module(binary_reader.create(from: <<>>)) + |> should.be_error + |> should.equal("gwr/parser/binary_parser.parse_binary_module: empty data") +} + +pub fn parse_binary_module___could_not_find_module_magic_number_1___test() +{ + binary_parser.parse_binary_module(binary_reader.create(from: <<0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00>>)) + |> should.be_error + |> should.equal("gwr/parser/binary_parser.parse_binary_module: couldn't find module's magic number") +} + +pub fn parse_binary_module___could_not_find_module_magic_number_2___test() +{ + binary_parser.parse_binary_module(binary_reader.create(from: <<0x00, 0x63, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00>>)) + |> should.be_error + |> should.equal("gwr/parser/binary_parser.parse_binary_module: couldn't find module's magic number") +} + +pub fn parse_binary_module___could_not_find_module_version_1___test() +{ + binary_parser.parse_binary_module(binary_reader.create(from: <<0x00, 0x61, 0x73, 0x6d>>)) + |> should.be_error + |> should.equal("gwr/parser/binary_parser.parse_binary_module: couldn't find module version") +} + +pub fn parse_binary_module___could_not_find_module_version_2___test() +{ + binary_parser.parse_binary_module(binary_reader.create(from: <<0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00>>)) + |> should.be_error + |> should.equal("gwr/parser/binary_parser.parse_binary_module: couldn't find module version") +} + +pub fn parse_section___no_content___test() +{ + let reader = binary_reader.create(from: << + 0x01, // Type section + 0x00, // U32 LEB128 section length = 0 + >>) + binary_parser.parse_section(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 2), + binary.Section + ( + id: binary.type_section_id, + length: 0, + content: None + ) + ) + ) +} + +pub fn parse_section___unexpected_end___test() +{ + let reader = binary_reader.create(from: << + 0x00, // Section type = "Custom" (0x00) + 0x09, // U32 LEB128 section length = 9 + 0x04, 0x74, 0x65, 0x73, 0x74, // A name with length = 4 and content = "test" + 0x0a, 0x0b, 0x0c // 3 bytes (1 missing) + >>) + binary_parser.parse_section(reader) + |> should.be_error + |> should.equal("gwr/parser/binary_parser.parse_section: unexpected end of the section's content segment. Expected 9 bytes but got 8 bytes") +} + +pub fn parse_memory_section_test() +{ + let reader = binary_reader.create(from: << + 0x05, // Section type = "Memory" (0x05) + 0x07, // U32 LEB128 section length = 7 + 0x02, // A vector with U32 LEB128 length = 2 and content = + 0x00, 0x03, // [0] = Memory(type_: Limits(min: 3, max: None)) + 0x01, 0x20, 0x80, 0x02 // [1] = Memory(type_: Limits(min: 32, max: Some(256)) + >>) + binary_parser.parse_section(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 9), + binary.Section + ( + id: binary.memory_section_id, + length: 7, + content: Some(binary.MemorySection( + memories: [ + module.Memory(type_: types.Limits(min: 3, max: None)), + module.Memory(type_: types.Limits(min: 32, max: Some(256))) + ] + )) + ) + ) + ) +} + +pub fn parse_type_section_test() +{ + let reader = binary_reader.create(from: << + 0x01, // Section type = "Type" (0x01) + 0x17, // U32 LEB128 section length = 22 + 0x04, // A vector with U32 LEB128 length = 4 + 0x60, 0x02, 0x7f, 0x7e, 0x00, // FunctionType(parameters: [I32, I64], results: []) + 0x60, 0x02, 0x7d, 0x7c, 0x01, 0x7b, // FunctionType(parameters: [F32, F64], results: [V128]) + 0x60, 0x03, 0x70, 0x6f, 0x7f, 0x02, 0x6f, 0x7e, // FunctionType(parameters: [FuncRef, ExternRef, I32], results: [ExternRef, I64]) + 0x60, 0x00, 0x00 // FunctionType(parameters: [], results: []) + >>) + binary_parser.parse_section(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 25), + binary.Section + ( + id: binary.type_section_id, + length: 23, + content: Some(binary.TypeSection( + function_types: [ + types.FunctionType(parameters: [types.Number(types.Integer32), types.Number(types.Integer64)], results: []), + types.FunctionType(parameters: [types.Number(types.Float32), types.Number(types.Float64)], results: [types.Vector(types.Vector128)]), + types.FunctionType(parameters: [types.Reference(types.FunctionReference), types.Reference(types.ExternReference), types.Number(types.Integer32)], results: [types.Reference(types.ExternReference), types.Number(types.Integer64)]), + types.FunctionType(parameters: [], results: []) + ] + )) + ) + ) + ) +} + +pub fn parse_export_section_test() +{ + let reader = binary_reader.create(from: << + 0x07, // Section type = "Export" (0x07) + 0x32, // U32 LEB128 section length = 50 + 0x04, // A vector with U32 LEB128 length = 4 + 0x0b, "my_function":utf8, // A name with U32 LEB128 length = 11 and value = "my_function" + 0x00, 0x00, // FunctionExport(index: 0) + 0x08, "my_table":utf8, // A name with U32 LEB128 length = 8 and value = "my_table" + 0x01, 0x01, // TableExport(index: 1) + 0x09, "my_memory":utf8, // A name with U32 LEB128 length = 9 and value = "my_memory" + 0x02, 0x02, // MemoryExport(index: 2) + 0x09, "my_global":utf8, // A name with U32 LEB128 length = 9 and value = "my_global" + 0x03, 0x03, // GlobalExport(index: 3) + >>) + binary_parser.parse_section(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 52), + binary.Section + ( + id: binary.export_section_id, + length: 50, + content: Some(binary.ExportSection( + exports: [ + module.Export(name: "my_function", descriptor: module.FunctionExport(index: 0)), + module.Export(name: "my_table", descriptor: module.TableExport(index: 1)), + module.Export(name: "my_memory", descriptor: module.MemoryExport(index: 2)), + module.Export(name: "my_global", descriptor: module.GlobalExport(index: 3)), + ] + )) + ) + ) + ) +} + +pub fn parse_function_section_test() +{ + let reader = binary_reader.create(from: << + 0x03, // Section type = "Function" (0x03) + 0x0d, // U32 LEB128 section length = 13 + 0x06, // A vector with U32 LEB128 length = 6 + 0x01, // vector[0] = 1 + 0x01, // vector[1] = 1 + 0x02, // vector[2] = 2 + 0x03, // vector[3] = 3 + 0xff, 0xff, 0xff, 0xff, 0x07, // vector[4] = 2147483647 + 0xe5, 0x8e, 0x26 // vector[5] = 624485 + >>) + binary_parser.parse_section(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 15), + binary.Section + ( + id: binary.function_section_id, + length: 13, + content: Some(binary.FunctionSection( + type_indices: [1, 1, 2, 3, 2147483647, 624485] + )) + ) + ) + ) +} + +pub fn parse_code_section_test() +{ + let reader = binary_reader.create(from: << + 0x0a, // Section type = "Code" (0x0a) + 0x0c, // Length = 12 + 0x01, // Vector(Code) with length = 1 + 0x0a, // [0] = Code with size = 10 + 0x01, // Vector(LocalsDeclaration) with length = 1 + 0x02, 0x7f, // [0] = LocalsDeclaration(count: 2, type_: Number(Integer32)) + 0x20, 0x01, // LocalGet(index: 1) + 0x20, 0x02, // LocalGet(index: 2) + 0x6a, // I32Add + 0x01, // NoOp + 0x0b // End + >>) + binary_parser.parse_section(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 14), + binary.Section + ( + id: binary.code_section_id, + length: 12, + content: Some(binary.CodeSection( + entries: [ + binary.Code( + size: 10, + function_code: binary.FunctionCode( + locals: [binary.LocalsDeclaration(count: 2, type_: types.Number(types.Integer32))], + body: [ + instruction.LocalGet(index: 1), + instruction.LocalGet(index: 2), + instruction.I32Add, + instruction.NoOp, + instruction.End + ] + ) + ) + ] + )) + ) + ) + ) +} + +pub fn parse_code_test() +{ + let reader = binary_reader.create(from: << + 0x0a, // Code with size = 10 + 0x01, // Vector(LocalsDeclaration) with length = 1 + 0x02, 0x7f, // [0] = LocalsDeclaration(count: 2, type_: Number(Integer32)) + 0x20, 0x01, // LocalGet(index: 1) + 0x20, 0x02, // LocalGet(index: 2) + 0x6a, // I32Add + 0x01, // NoOp + 0x0b // End + >>) + binary_parser.parse_code(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 11), + binary.Code( + size: 10, + function_code: binary.FunctionCode( + locals: [binary.LocalsDeclaration(count: 2, type_: types.Number(types.Integer32))], + body: [ + instruction.LocalGet(index: 1), + instruction.LocalGet(index: 2), + instruction.I32Add, + instruction.NoOp, + instruction.End + ] + ) + ) + ) + ) +} + +pub fn parse_locals_declaration___3_integer32___test() +{ + let reader = binary_reader.create(from: <<0x03, 0x7f>>) + binary_parser.parse_locals_declaration(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 2), + binary.LocalsDeclaration(count: 3, type_: types.Number(types.Integer32)) + ) + ) +} + +pub fn parse_locals_declaration___2_integer64___test() +{ + let reader = binary_reader.create(from: <<0x02, 0x7e>>) + binary_parser.parse_locals_declaration(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 2), + binary.LocalsDeclaration(count: 2, type_: types.Number(types.Integer64)) + ) + ) +} + +pub fn parse_locals_declaration___128_float32___test() +{ + let reader = binary_reader.create(from: <<0x80, 0x01, 0x7d>>) + binary_parser.parse_locals_declaration(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 3), + binary.LocalsDeclaration(count: 128, type_: types.Number(types.Float32)) + ) + ) +} + +pub fn parse_locals_declaration___123456_float64___test() +{ + let reader = binary_reader.create(from: <<0xc0, 0xc4, 0x07, 0x7c>>) + binary_parser.parse_locals_declaration(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 4), + binary.LocalsDeclaration(count: 123456, type_: types.Number(types.Float64)) + ) + ) +} + +pub fn parse_locals_declaration___255_vector128___test() +{ + let reader = binary_reader.create(from: <<0xff, 0x01, 0x7b>>) + binary_parser.parse_locals_declaration(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 3), + binary.LocalsDeclaration(count: 255, type_: types.Vector(types.Vector128)) + ) + ) +} + +pub fn parse_locals_declaration___2_function_reference___test() +{ + let reader = binary_reader.create(from: <<0x02, 0x70>>) + binary_parser.parse_locals_declaration(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 2), + binary.LocalsDeclaration(count: 2, type_: types.Reference(types.FunctionReference)) + ) + ) +} + +pub fn parse_locals_declaration___1_extern_reference___test() +{ + let reader = binary_reader.create(from: <<0x01, 0x6f>>) + binary_parser.parse_locals_declaration(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 2), + binary.LocalsDeclaration(count: 1, type_: types.Reference(types.ExternReference)) + ) + ) +} + +pub fn parse_function_code_test() +{ + let reader = binary_reader.create(from: << + 0x07, // A vector with 7 LocalsDeclaration + 0x03, 0x7f, // LocalsDeclaration(count: 3, type_: types.Number(types.Integer32)) + 0x02, 0x7e, // LocalsDeclaration(count: 2, type_: types.Number(types.Integer64)) + 0x80, 0x01, 0x7d, // LocalsDeclaration(count: 128, type_: types.Number(types.Float32)) + 0xc0, 0xc4, 0x07, 0x7c, // LocalsDeclaration(count: 123456, type_: types.Number(types.Float64)) + 0xff, 0x01, 0x7b, // LocalsDeclaration(count: 255, type_: types.Vector(types.Vector128)) + 0x02, 0x70, // LocalsDeclaration(count: 2, type_: types.Reference(types.FunctionReference)) + 0x01, 0x6f, // LocalsDeclaration(count: 1, type_: types.Reference(types.ExternReference)) + 0x01, // NoOp <-- the function's body should begin here + 0x01, // NoOp + 0x0b, // End <-- the function's body should terminate here + 0x01, // NoOp + 0x01, // NoOp + >>) + binary_parser.parse_function_code(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 22), + binary.FunctionCode( + locals: [ + binary.LocalsDeclaration(count: 3, type_: types.Number(types.Integer32)), + binary.LocalsDeclaration(count: 2, type_: types.Number(types.Integer64)), + binary.LocalsDeclaration(count: 128, type_: types.Number(types.Float32)), + binary.LocalsDeclaration(count: 123456, type_: types.Number(types.Float64)), + binary.LocalsDeclaration(count: 255, type_: types.Vector(types.Vector128)), + binary.LocalsDeclaration(count: 2, type_: types.Reference(types.FunctionReference)), + binary.LocalsDeclaration(count: 1, type_: types.Reference(types.ExternReference)), + ], + body: [ + instruction.NoOp, + instruction.NoOp, + instruction.End + ] + ) + ) + ) +} \ No newline at end of file diff --git a/test/gwr/parser/convention_parser_test.gleam b/test/gwr/parser/convention_parser_test.gleam new file mode 100644 index 0000000..3a952e3 --- /dev/null +++ b/test/gwr/parser/convention_parser_test.gleam @@ -0,0 +1,28 @@ +import gleam/bit_array + +import gwr/parser/convention_parser +import gwr/parser/binary_reader + +import gleeunit +import gleeunit/should + +pub fn main() +{ + gleeunit.main() +} + +pub fn parse_vector___bytes___test() +{ + let reader = binary_reader.create(from: <<0x01, "Hello World!":utf8>>) + convention_parser.parse_vector(from: reader, with: fn (reader) { + let assert Ok(#(reader, string_data)) = binary_reader.read_remaining(from: reader) + Ok(#(reader, bit_array.to_string(string_data))) + }) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 13), + [Ok("Hello World!")] + ) + ) +} \ No newline at end of file diff --git a/test/gwr/parser/instruction_parser_test.gleam b/test/gwr/parser/instruction_parser_test.gleam new file mode 100644 index 0000000..b733805 --- /dev/null +++ b/test/gwr/parser/instruction_parser_test.gleam @@ -0,0 +1,37 @@ +import gwr/parser/instruction_parser +import gwr/parser/binary_reader +import gwr/syntax/instruction + +import gleeunit +import gleeunit/should + +pub fn main() +{ + gleeunit.main() +} + +pub fn i32_const_test() +{ + let reader = binary_reader.create(from: <<0x41, 0x80, 0x80, 0xc0, 0x00>>) + instruction_parser.parse_instruction(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 5), + instruction.I32Const(value: 1048576) + ) + ) +} + +pub fn local_get_test() +{ + let reader = binary_reader.create(from: <<0x20, 0xff, 0x01>>) + instruction_parser.parse_instruction(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 3), + instruction.LocalGet(index: 255) + ) + ) +} \ No newline at end of file diff --git a/test/gwr/parser/module_parser_test.gleam b/test/gwr/parser/module_parser_test.gleam new file mode 100644 index 0000000..977b470 --- /dev/null +++ b/test/gwr/parser/module_parser_test.gleam @@ -0,0 +1,93 @@ +import gleam/option.{None, Some} + +import gwr/parser/binary_reader +import gwr/parser/module_parser +import gwr/syntax/module +import gwr/syntax/types + +import gleeunit +import gleeunit/should + +pub fn main() +{ + gleeunit.main() +} + +pub fn parse_export___function_export___test() +{ + let reader = binary_reader.create(from: <<0x0b, "my_function":utf8, 0x00, 0x00>>) + module_parser.parse_export(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 14), + module.Export(name: "my_function", descriptor: module.FunctionExport(index: 0)) + ) + ) +} + +pub fn parse_export___table_export___test() +{ + let reader = binary_reader.create(from: <<0x08, "my_table":utf8, 0x01, 0xc0, 0xc4, 0x07>>) + module_parser.parse_export(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 13), + module.Export(name: "my_table", descriptor: module.TableExport(index: 123456)) + ) + ) +} + +pub fn parse_export___memory_export___test() +{ + let reader = binary_reader.create(from: <<0x09, "my_memory":utf8, 0x02, 0xff, 0x01>>) + module_parser.parse_export(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 13), + module.Export(name: "my_memory", descriptor: module.MemoryExport(index: 255)) + ) + ) +} + +pub fn parse_export___global_export___test() +{ + let reader = binary_reader.create(from: <<0x09, "my_global":utf8, 0x03, 0xff, 0xff, 0xff, 0xff, 0x0f>>) + module_parser.parse_export(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 16), + module.Export(name: "my_global", descriptor: module.GlobalExport(index: 4294967295)) + ) + ) +} + +pub fn parse_memory___with_max___test() +{ + let reader = binary_reader.create(from: <<0x00, 0xff, 0xff, 0x03>>) + module_parser.parse_memory(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 4), + module.Memory(type_: types.Limits(min: 65535, max: None)) + ) + ) +} + +pub fn parse_memory___without_max___test() +{ + let reader = binary_reader.create(from: <<0x01, 0x80, 0x08, 0x80, 0x40>>) + module_parser.parse_memory(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 5), + module.Memory(type_: types.Limits(min: 1024, max: Some(8192))) + ) + ) +} + diff --git a/test/gwr/parser/types_parser_test.gleam b/test/gwr/parser/types_parser_test.gleam new file mode 100644 index 0000000..67b64cf --- /dev/null +++ b/test/gwr/parser/types_parser_test.gleam @@ -0,0 +1,198 @@ +import gleam/option.{None, Some} + +import gwr/parser/binary_reader +import gwr/parser/types_parser +import gwr/syntax/types + +import gleeunit +import gleeunit/should + +pub fn main() +{ + gleeunit.main() +} + +pub fn parse_function_type_test() +{ + let reader = binary_reader.create(from: << + 0x60, // Flag + 0x02, 0x7f, 0x7e, // Parameters -> A vector with U32 LEB128 length = 2 and content = [I32, I64] + 0x05, 0x7d, 0x7c, 0x7b, 0x70, 0x6f // Results -> A vector with U32 LEB128 length = 5 and content = [F32, F64, V128, FuncRef, ExternRef] + >>) + types_parser.parse_function_type(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 10), + types.FunctionType( + parameters: [ + types.Number(types.Integer32), + types.Number(types.Integer64) + ], + results: [ + types.Number(types.Float32), + types.Number(types.Float64), + types.Vector(types.Vector128), + types.Reference(types.FunctionReference), + types.Reference(types.ExternReference) + ] + ) + ) + ) +} + +pub fn parse_function_type___empty_vectors___test() +{ + let reader = binary_reader.create(from: <<0x60, 0x00, 0x00>>) + types_parser.parse_function_type(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 3), + types.FunctionType(parameters: [], results: []) + ) + ) +} + +pub fn parse_global_type___constant___test() +{ + let reader = binary_reader.create(from: <<0x7f, 0x00>>) + types_parser.parse_global_type(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 2), + types.GlobalType(value_type: types.Number(types.Integer32), mutability: types.Constant) + ) + ) +} + +pub fn parse_global_type___variable___test() +{ + let reader = binary_reader.create(from: <<0x7e, 0x01>>) + types_parser.parse_global_type(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 2), + types.GlobalType(value_type: types.Number(types.Integer64), mutability: types.Variable) + ) + ) +} + +pub fn parse_limits___no_max___test() +{ + let reader = binary_reader.create(from: <<0x00, 0x03>>) + types_parser.parse_limits(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 2), + types.Limits(min: 3, max: None) + ) + ) +} + +pub fn parse_limits___with_max___test() +{ + let reader = binary_reader.create(from: <<0x01, 0x20, 0x80, 0x02>>) + types_parser.parse_limits(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 4), + types.Limits(min: 32, max: Some(256)), + ) + ) +} + +pub fn parse_value_type___integer_32___test() +{ + let reader = binary_reader.create(from: <<0x7f>>) + types_parser.parse_value_type(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 1), + types.Number(types.Integer32) + ) + ) +} + +pub fn parse_value_type___integer_64___test() +{ + let reader = binary_reader.create(from: <<0x7e>>) + types_parser.parse_value_type(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 1), + types.Number(types.Integer64) + ) + ) +} + +pub fn parse_value_type___float_32___test() +{ + let reader = binary_reader.create(from: <<0x7d>>) + types_parser.parse_value_type(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 1), + types.Number(types.Float32) + ) + ) +} + +pub fn parse_value_type___float_64___test() +{ + let reader = binary_reader.create(from: <<0x7c>>) + types_parser.parse_value_type(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 1), + types.Number(types.Float64) + ) + ) +} + +pub fn parse_value_type___vector_128___test() +{ + let reader = binary_reader.create(from: <<0x7b>>) + types_parser.parse_value_type(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 1), + types.Vector(types.Vector128) + ) + ) +} + +pub fn parse_value_type___function_reference___test() +{ + let reader = binary_reader.create(from: <<0x70>>) + types_parser.parse_value_type(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 1), + types.Reference(types.FunctionReference) + ) + ) +} + +pub fn parse_value_type___extern_reference___test() +{ + let reader = binary_reader.create(from: <<0x6f>>) + types_parser.parse_value_type(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 1), + types.Reference(types.ExternReference) + ) + ) +} \ No newline at end of file diff --git a/test/gwr/parser/value_parser_test.gleam b/test/gwr/parser/value_parser_test.gleam new file mode 100644 index 0000000..29bf9cb --- /dev/null +++ b/test/gwr/parser/value_parser_test.gleam @@ -0,0 +1,23 @@ +import gwr/parser/binary_reader +import gwr/parser/value_parser + +import gleeunit +import gleeunit/should + +pub fn main() +{ + gleeunit.main() +} + +pub fn parse_name_test() +{ + let reader = binary_reader.create(from: <<0x09, "some_name":utf8>>) + value_parser.parse_name(reader) + |> should.be_ok + |> should.equal( + #( + binary_reader.BinaryReader(..reader, current_position: 10), + "some_name" + ) + ) +} \ No newline at end of file diff --git a/test/gwr/section_test.gleam b/test/gwr/section_test.gleam deleted file mode 100644 index 80e5e4c..0000000 --- a/test/gwr/section_test.gleam +++ /dev/null @@ -1,249 +0,0 @@ -import gleeunit -import gleeunit/should - -import gleam/bytes_builder -import gleam/option.{Some, None} - -import gwr/section -import gwr/types/limits.{Limits} - -pub fn main() -{ - gleeunit.main() -} - -pub fn parse_raw_section___custom___test() -{ - section.parse_raw_section( - at: 0, - from: << - 0x00, // Section type = "Custom" (0x00) - 0x03, // U32 LEB128 section length = 3 - 0x0a, 0x0b, 0x0c // Section content - >> - ) - |> should.be_ok - |> should.equal(section.RawSection(type_id: 0x00, length: 3, content: Some(<<0x0a, 0x0b, 0x0c>>))) - - section.parse_raw_section( - at: 0, - from: bytes_builder.from_bit_array( - << - 0x00, // Section type = "Custom" (0x00) - 0xff, 0x01, // U32 LEB128 section length = 255 - >> - ) - |> bytes_builder.append(<<0x00:size(255 * 8)>>) // Section content - |> bytes_builder.to_bit_array - ) - |> should.be_ok - |> should.equal(section.RawSection(type_id: 0x00, length: 255, content: Some(<<0x00:size(255 * 8)>>))) - - section.parse_raw_section( - at: 0, - from: bytes_builder.from_bit_array( - << - 0x01, // Section type = "Type" (0x01) - 0xff, 0x01, // U32 LEB128 section length = 255 - >> - ) - |> bytes_builder.append(<<0x00:size(255 * 8)>>) // Section content - |> bytes_builder.to_bit_array - ) - |> should.be_ok - |> should.equal(section.RawSection(type_id: 0x01, length: 255, content: Some(<<0x00:size(255 * 8)>>))) - - section.parse_raw_section( - at: 0, - from: bytes_builder.from_bit_array( - << - 0x0a, // Section type = "Code" (0x0a) - 0x80, 0x89, 0x7a, // U32 LEB128 section length = 2000000 - >> - ) - |> bytes_builder.append(<<0xff:size(2000000 * 8)>>) // Section content - |> bytes_builder.to_bit_array - ) - |> should.be_ok - |> should.equal(section.RawSection(type_id: 0x0a, length: 2000000, content: Some(<<0xff:size(2000000 * 8)>>))) - - section.parse_raw_section( - at: 0, - from: << - 0x0b, // Section type = "Data" (0x0b) - 0x00, // U32 LEB128 section length = 0 - >> - ) - |> should.be_ok - |> should.equal(section.RawSection(type_id: 0x0b, length: 0, content: None)) -} - -pub fn parse_raw_section___unexpected_end___test() -{ - section.parse_raw_section( - at: 0, - from: << - 0x00, // Section type = "Custom" (0x00) - 0x09, // U32 LEB128 section length = 9 - 0x04, 0x74, 0x65, 0x73, 0x74, // A name with length = 4 and content = "test" - 0x0a, 0x0b, 0x0c // 3 bytes (1 missing) - >> - ) - |> should.be_error - |> should.equal("section::parse_raw_section: unexpected end of the section's content segment") -} - -pub fn decode_section___custom___test() -{ - section.decode_section( - at: 0, - from: << - 0x00, // Section type = "Custom" (0x00) - 0x09, // U32 LEB128 section length = 9 - 0x04, 0x74, 0x65, 0x73, 0x74, // A name with length = 4 and content = "test" - 0x0a, 0x0b, 0x0c, 0x0d - >> - ) - |> should.be_ok - |> should.equal(section.Custom(length: 9, name: "test", data: Some(<<0x0a, 0x0b, 0x0c, 0x0d>>))) -} - -pub fn decode_memory_section_test() -{ - section.decode_section( - at: 0, - from: << - 0x05, // Section type = "Memory" (0x05) - 0x07, // U32 LEB128 section length = 7 - 0x06, // A vector with length = 6 and content = - // [ - 0x00, 0x03, // Limits(min: 3, max: None), - 0x01, 0x20, 0x80, 0x02 // Limits(min: 32, max: Some(256)) - // ] - >> - ) - |> should.be_ok - |> should.equal(section.Memory(length: 7, memories: [Limits(min: 3, max: None), Limits(min: 32, max: Some(256))])) -} - -//pub fn decode_section_type_type_test() -//{ -// section.decode_section_type(<<0x01>>) -// |> should.be_ok -// |> should.equal(section.Type) -//} -// -//pub fn decode_section_type_import_test() -//{ -// section.decode_section_type(<<0x02>>) -// |> should.be_ok -// |> should.equal(section.Import) -//} -// -//pub fn decode_section_type_function_test() -//{ -// section.decode_section_type(<<0x03>>) -// |> should.be_ok -// |> should.equal(section.Function) -//} -// -//pub fn decode_section_type_table_test() -//{ -// section.decode_section_type(<<0x04>>) -// |> should.be_ok -// |> should.equal(section.Table) -//} -// -//pub fn decode_section_type_memory_test() -//{ -// section.decode_section_type(<<0x05>>) -// |> should.be_ok -// |> should.equal(section.Memory) -//} -// -//pub fn decode_section_type_global_test() -//{ -// section.decode_section_type(<<0x06>>) -// |> should.be_ok -// |> should.equal(section.Global) -//} -// -//pub fn decode_section_type_export_test() -//{ -// section.decode_section_type(<<0x07>>) -// |> should.be_ok -// |> should.equal(section.Export) -//} -// -//pub fn decode_section_type_start_test() -//{ -// section.decode_section_type(<<0x08>>) -// |> should.be_ok -// |> should.equal(section.Start) -//} -// -//pub fn decode_section_type_element_test() -//{ -// section.decode_section_type(<<0x09>>) -// |> should.be_ok -// |> should.equal(section.Element) -//} -// -//pub fn decode_section_type_code_test() -//{ -// section.decode_section_type(<<0x0a>>) -// |> should.be_ok -// |> should.equal(section.Code) -//} -// -//pub fn decode_section_type_data_test() -//{ -// section.decode_section_type(<<0x0b>>) -// |> should.be_ok -// |> should.equal(section.Data) -//} -// -//pub fn decode_section_type_datacount_test() -//{ -// section.decode_section_type(<<0x0c>>) -// |> should.be_ok -// |> should.equal(section.DataCount) -//} -// -//pub fn decode_section_type_unknown_test() -//{ -// section.decode_section_type(<<0x0d>>) -// |> should.be_error -// |> should.equal("decode_section_type: unknown section type \"13\"") -//} -// -//pub fn decode_section_type_empty_test() -//{ -// section.decode_section_type(<<>>) -// |> should.be_error -// |> should.equal("decode_section_type: can't get section type raw data") -//} -// -//pub fn from_raw_data_custom_test() -//{ -// section.from_raw_data( -// at: 0, -// from: << -// 0x00, // Section type = "Custom" (0x00) -// 0x80, 0x02, 0x00, 0x00, // U32 LEB128 section length = 256 -// >> -// ) -// |> should.be_ok -// |> should.equal(section.Section(length: 256, inner_type: section.Custom(data: []))) -//} -// -//pub fn from_raw_data_type_test() -//{ -// << -// 0x01, // Section type = "Type" (0x01) -// 0xd8, 0xa0, 0xd0, 0x07, // U32 LEB128 section length = 15994968 -// >> -// |> section.from_raw_data -// |> should.be_ok -// |> should.equal(section.Section(length: 15994968, inner_type: section.Type)) -//} \ No newline at end of file diff --git a/test/gwr/types/limits_test.gleam b/test/gwr/types/limits_test.gleam deleted file mode 100644 index 49fbe31..0000000 --- a/test/gwr/types/limits_test.gleam +++ /dev/null @@ -1,35 +0,0 @@ -import gleeunit -import gleeunit/should -import gleam/option.{Some, None} - -import gwr/types/limits.{Limits} -import gwr/types/vector - -pub fn main() -{ - gleeunit.main() -} - -pub fn from_raw_data___no_max___test() -{ - limits.from_raw_data(at: 0, from: <<0x00, 0x03>>) - |> should.be_ok - |> should.equal(#(Limits(min: 3, max: None), 2)) -} - -pub fn from_raw_data___with_max___test() -{ - limits.from_raw_data(at: 0, from: <<0x01, 0x20, 0x80, 0x02>>) - |> should.be_ok - |> should.equal(#(Limits(min: 32, max: Some(256)), 4)) -} - -pub fn from_vec_test() -{ - vector.from_raw_data(at: 0, from: <<0x06, 0x00, 0x03, 0x01, 0x20, 0x80, 0x02>>) - |> should.be_ok - |> limits.from_vector - |> should.be_ok - |> should.equal(#([Limits(min: 3, max: None), Limits(min: 32, max: Some(256))], 6)) - -} \ No newline at end of file diff --git a/test/gwr/types/vector_test.gleam b/test/gwr/types/vector_test.gleam deleted file mode 100644 index 2b8290c..0000000 --- a/test/gwr/types/vector_test.gleam +++ /dev/null @@ -1,33 +0,0 @@ -import gleeunit -import gleeunit/should - -import gwr/types/vector as vector - -pub fn main() -{ - gleeunit.main() -} - -pub fn from_raw_data_test() -{ - vector.from_raw_data(at: 0, from: <<0x0c, "Hello World!":utf8>>) - |> should.be_ok - |> should.equal(vector.Vector(length: 0x0c, data: <<"Hello World!":utf8>>)) -} - -pub fn from_raw_data___truncated___test() -{ - vector.from_raw_data(at: 0, from: <<0x80, 0x02, 0x00:size(256 * 8)>>) - |> should.be_ok - |> should.equal(vector.Vector(length: 256, data: <<0x00:size(256 * 8)>>)) -} - -pub fn from_raw_data___unexpected_end___test() -{ - vector.from_raw_data(at: 0, from: <<0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00>>) - |> should.be_error - |> should.equal("vector::from_raw_data: unexpected end of the vector's data. Expected = 7 bytes but got = 6 bytes") -} - - -