From 01679fd252a01d2166677514221a942e0b74479d Mon Sep 17 00:00:00 2001 From: jakob Date: Thu, 15 Jan 2026 19:29:35 +0100 Subject: [PATCH] commit message --- asmblr/.github/workflows/test.yml | 23 ++++ asmblr/.gitignore | 4 + asmblr/README.md | 24 ++++ asmblr/gleam.toml | 24 ++++ asmblr/manifest.toml | 20 ++++ asmblr/src/asmblr.gleam | 22 ++++ asmblr/src/asmblr/definitions.gleam | 67 +++++++++++ asmblr/src/asmblr/line.gleam | 18 +++ asmblr/src/asmblr/parser.gleam | 11 ++ asmblr/src/asmblr/passes.gleam | 167 ++++++++++++++++++++++++++++ asmblr/src/asmblr/token.gleam | 14 +++ asmblr/test/asmblr_test.gleam | 13 +++ sim/.gitignore | 2 + sim/src/sim.gleam | 2 +- 14 files changed, 410 insertions(+), 1 deletion(-) create mode 100644 asmblr/.github/workflows/test.yml create mode 100644 asmblr/.gitignore create mode 100644 asmblr/README.md create mode 100644 asmblr/gleam.toml create mode 100644 asmblr/manifest.toml create mode 100644 asmblr/src/asmblr.gleam create mode 100644 asmblr/src/asmblr/definitions.gleam create mode 100644 asmblr/src/asmblr/line.gleam create mode 100644 asmblr/src/asmblr/parser.gleam create mode 100644 asmblr/src/asmblr/passes.gleam create mode 100644 asmblr/src/asmblr/token.gleam create mode 100644 asmblr/test/asmblr_test.gleam create mode 100644 sim/.gitignore diff --git a/asmblr/.github/workflows/test.yml b/asmblr/.github/workflows/test.yml new file mode 100644 index 0000000..9821961 --- /dev/null +++ b/asmblr/.github/workflows/test.yml @@ -0,0 +1,23 @@ +name: test + +on: + push: + branches: + - master + - main + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: erlef/setup-beam@v1 + with: + otp-version: "28" + gleam-version: "1.14.0" + rebar3-version: "3" + # elixir-version: "1" + - run: gleam deps download + - run: gleam test + - run: gleam format --check src test diff --git a/asmblr/.gitignore b/asmblr/.gitignore new file mode 100644 index 0000000..599be4e --- /dev/null +++ b/asmblr/.gitignore @@ -0,0 +1,4 @@ +*.beam +*.ez +/build +erl_crash.dump diff --git a/asmblr/README.md b/asmblr/README.md new file mode 100644 index 0000000..d5b3e3e --- /dev/null +++ b/asmblr/README.md @@ -0,0 +1,24 @@ +# asmblr + +[![Package Version](https://img.shields.io/hexpm/v/asmblr)](https://hex.pm/packages/asmblr) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/asmblr/) + +```sh +gleam add asmblr@1 +``` +```gleam +import asmblr + +pub fn main() -> Nil { + // TODO: An example of the project in use +} +``` + +Further documentation can be found at . + +## Development + +```sh +gleam run # Run the project +gleam test # Run the tests +``` diff --git a/asmblr/gleam.toml b/asmblr/gleam.toml new file mode 100644 index 0000000..1404920 --- /dev/null +++ b/asmblr/gleam.toml @@ -0,0 +1,24 @@ +name = "asmblr" +version = "1.0.0" + +# Fill out these fields if you intend to generate HTML documentation or publish +# your project to the Hex package manager. +# +# description = "" +# licences = ["Apache-2.0"] +# repository = { type = "github", user = "", repo = "" } +# links = [{ title = "Website", href = "" }] +# +# For a full reference of all the available options, you can have a look at +# https://gleam.run/writing-gleam/gleam-toml/. + +[dependencies] +file_streams = ">= 1.7.0 and < 2.0.0" +simplifile = ">= 2.3.2 and < 3.0.0" +argv = ">= 1.0.2 and < 2.0.0" +gleam_stdlib = ">= 0.68.1 and < 1.0.0" +gleam_regexp = ">= 1.1.1 and < 2.0.0" + + +[dev-dependencies] +gleeunit = ">= 1.9.0 and < 2.0.0" diff --git a/asmblr/manifest.toml b/asmblr/manifest.toml new file mode 100644 index 0000000..f58f86c --- /dev/null +++ b/asmblr/manifest.toml @@ -0,0 +1,20 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, + { name = "file_streams", version = "1.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "file_streams", source = "hex", outer_checksum = "62757932B5FAC14FC9D0EE69BC4694DC6EA9F0FE6E198F695711C4FCAC1F7A26" }, + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, + { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, + { name = "gleam_stdlib", version = "0.68.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "F7FAEBD8EF260664E86A46C8DBA23508D1D11BB3BCC6EE1B89B3BC3E5C83FF1E" }, + { name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" }, + { name = "simplifile", version = "2.3.2", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "E049B4DACD4D206D87843BCF4C775A50AE0F50A52031A2FFB40C9ED07D6EC70A" }, +] + +[requirements] +argv = { version = ">= 1.0.2 and < 2.0.0" } +file_streams = { version = ">= 1.7.0 and < 2.0.0" } +gleam_regexp = { version = ">= 1.1.1 and < 2.0.0" } +gleam_stdlib = { version = ">= 0.68.1 and < 1.0.0" } +gleeunit = { version = ">= 1.9.0 and < 2.0.0" } +simplifile = { version = ">= 2.3.2 and < 3.0.0" } diff --git a/asmblr/src/asmblr.gleam b/asmblr/src/asmblr.gleam new file mode 100644 index 0000000..0bf1b97 --- /dev/null +++ b/asmblr/src/asmblr.gleam @@ -0,0 +1,22 @@ +import argv +import asmblr/parser +import asmblr/passes +import simplifile + +pub fn main() -> Nil { + let arg = argv.load() + case arg.arguments { + [_program, fname, ..] -> { + let assert Ok(content) = simplifile.read(fname) + parser.parse(content) + |> passes.build_symbol_table() + |> fn(a) { + echo a.symbols + a + } + |> passes.generate_binary + Nil + } + _ -> Nil + } +} diff --git a/asmblr/src/asmblr/definitions.gleam b/asmblr/src/asmblr/definitions.gleam new file mode 100644 index 0000000..a564066 --- /dev/null +++ b/asmblr/src/asmblr/definitions.gleam @@ -0,0 +1,67 @@ +pub type Def { + Def(opcode: Int, size: Int, mnemonic: String) +} + +pub const instructions = [ + Def(6, 3, "ADD"), + Def(22, 3, "ADDF"), + Def(36, 2, "ADDR"), + Def(16, 3, "AND"), + Def(45, 2, "CLEAR"), + Def(10, 3, "COMP"), + Def(34, 3, "COMPF"), + Def(40, 2, "COMPR"), + Def(9, 3, "DIV"), + Def(25, 3, "DIVF"), + Def(39, 2, "DIVR"), + Def(49, 1, "FIX"), + Def(48, 1, "FLOAT"), + Def(61, 1, "HIO"), + Def(15, 3, "J"), + Def(12, 3, "JEQ"), + Def(13, 3, "JGT"), + Def(14, 3, "JLT"), + Def(18, 3, "JSUB"), + Def(0, 3, "LDA"), + Def(26, 3, "LDB"), + Def(20, 3, "LDCH"), + Def(28, 3, "LDF"), + Def(2, 3, "LDL"), + Def(27, 3, "LDS"), + Def(29, 3, "LDT"), + Def(1, 3, "LDX"), + Def(52, 3, "LPS"), + Def(8, 3, "MUL"), + Def(24, 3, "MULF"), + Def(38, 2, "MULR"), + Def(50, 1, "NORM"), + Def(17, 3, "OR"), + Def(54, 3, "RD"), + Def(43, 2, "RMO"), + Def(19, 3, "RSUB"), + Def(41, 2, "SHIFTL"), + Def(42, 2, "SHIFTR"), + Def(60, 1, "SIO"), + Def(59, 3, "SSK"), + Def(3, 3, "STA"), + Def(30, 3, "STB"), + Def(21, 3, "STCH"), + Def(32, 3, "STF"), + Def(53, 3, "STI"), + Def(5, 3, "STL"), + Def(31, 3, "STS"), + Def(58, 3, "STSW"), + Def(33, 3, "STT"), + Def(4, 3, "STX"), + Def(7, 3, "SUB"), + Def(23, 3, "SUBF"), + Def(23, 2, "SUBR"), + Def(23, 2, "SVC"), + Def(23, 3, "TD"), + Def(23, 1, "TIO"), + Def(23, 3, "TIX"), + Def(23, 2, "TIXR"), + Def(23, 3, "WD"), +] + +pub const directives = ["START"] diff --git a/asmblr/src/asmblr/line.gleam b/asmblr/src/asmblr/line.gleam new file mode 100644 index 0000000..b8bde2f --- /dev/null +++ b/asmblr/src/asmblr/line.gleam @@ -0,0 +1,18 @@ +import asmblr/token +import gleam/regexp + +pub type SourceLine { + SourceLine(number: Int, source: String) +} + +pub type Line { + Line(number: Int, tokens: token.Tokens) +} + +pub fn parse_line(line: SourceLine) -> Line { + let assert Ok(re) = + regexp.from_string("^(\\w*)\\s*(\\w*)\\s*([\\w,#@]*)\\s*(.*)$") + let assert [match, ..] = regexp.scan(re, line.source) + let assert [label, operation, arguments, comments] = match.submatches + Line(line.number, token.Tokens(label, operation, arguments, comments)) +} diff --git a/asmblr/src/asmblr/parser.gleam b/asmblr/src/asmblr/parser.gleam new file mode 100644 index 0000000..7c48f04 --- /dev/null +++ b/asmblr/src/asmblr/parser.gleam @@ -0,0 +1,11 @@ +import asmblr/line +import gleam/list +import gleam/string + +pub fn parse(source: String) -> List(line.Line) { + string.split(source, "\n") + |> list.index_map(fn(ln: String, i: Int) -> line.Line { + line.SourceLine(i, ln) |> line.parse_line + }) + |> echo +} diff --git a/asmblr/src/asmblr/passes.gleam b/asmblr/src/asmblr/passes.gleam new file mode 100644 index 0000000..923d807 --- /dev/null +++ b/asmblr/src/asmblr/passes.gleam @@ -0,0 +1,167 @@ +import asmblr/definitions +import asmblr/line +import gleam/dict +import gleam/int +import gleam/list +import gleam/option +import gleam/regexp + +pub type FirstPassOutput { + FirstPassOutput( + code: List(line.Line), + symbols: dict.Dict(String, option.Option(Int)), + ) +} + +type FirstPassState { + FirstPassState(symbols: dict.Dict(String, option.Option(Int)), pointer: Int) +} + +fn handle_label_first_pass( + state: FirstPassState, + code: line.Line, +) -> FirstPassState { + case code.tokens.label { + option.None -> state + option.Some(label) -> { + assert !dict.has_key(state.symbols, label) + FirstPassState( + ..state, + symbols: dict.insert(state.symbols, label, option.Some(state.pointer)), + ) + } + } +} + +fn increment_pointer_first_pass( + state: FirstPassState, + code: line.Line, +) -> FirstPassState { + case code.tokens.mnemonic { + option.None -> state + option.Some("+" <> _) -> { + FirstPassState(..state, pointer: state.pointer + 4) + } + option.Some(mnemonic) -> { + case + list.find(definitions.instructions, fn(inst) { + inst.mnemonic == mnemonic + }) + { + Ok(def) -> { + let size = def.size + FirstPassState(..state, pointer: state.pointer + size) + } + Error(Nil) -> state + } + } + } +} + +fn handle_directives_first_pass( + state: FirstPassState, + code: line.Line, +) -> FirstPassState { + case code.tokens.mnemonic { + option.None -> state + option.Some("START") -> state + option.Some("ORG") -> { + let assert option.Some(org) = code.tokens.arguments + let assert Ok(org) = case org { + "0x" <> num -> int.base_parse(num, 16) + num -> int.base_parse(num, 10) + } + FirstPassState(..state, pointer: org) + } + _ -> state + } +} + +fn parse_line_first_pass( + state: FirstPassState, + code: line.Line, +) -> FirstPassState { + handle_label_first_pass(state, code) + |> increment_pointer_first_pass(code) + |> handle_directives_first_pass(code) +} + +pub fn build_symbol_table(code: List(line.Line)) -> FirstPassOutput { + FirstPassOutput( + code, + list.fold(code, FirstPassState(dict.new(), 0), parse_line_first_pass).symbols, + ) +} + +fn get_register(register: String) -> Result(Int, Nil) { + case register { + "a" | "A" -> Ok(0) + "x" | "X" -> Ok(1) + "l" | "L" -> Ok(2) + "b" | "B" -> Ok(3) + "s" | "S" -> Ok(4) + "t" | "T" -> Ok(5) + _ -> Error(Nil) + } +} + +fn handle_line_second_pass( + code: line.Line, + symbols: dict.Dict(String, option.Option(int)), +) -> BitArray { + case code.tokens.mnemonic { + option.None -> <<>> + option.Some(mnemonic) -> { + case + list.find(definitions.instructions, fn(inst) { + inst.mnemonic == mnemonic + }) + { + Ok(def) -> { + case def.size { + 1 -> <> + 2 -> { + let assert option.Some(args) = code.tokens.arguments + let assert Ok(re) = regexp.from_string("^.*?(\\w),.*?(\\w).*$") + + let assert Ok(regexp.Match( + _, + [option.Some(arg1), option.Some(arg2)], + )) = list.first(regexp.scan(re, args)) + let assert Ok(arg1) = get_register(arg1) + let assert Ok(arg2) = get_register(arg2) + <> + } + 3 -> { + let n = 0 + let i = 0 + let x = 0 + let b = 0 + let p = 0 + let e = 1 + let assert Ok(re) = regexp.from_string("^(\\w*),(\\w)") + let displacement = 1 + << + { def.opcode / 4 }:6, + n:1, + i:1, + x:1, + b:1, + p:1, + e:1, + displacement:12, + >> + } + _ -> panic + } + } + Error(Nil) -> <<>> + } + } + } +} + +pub fn generate_binary(input: FirstPassOutput) -> BitArray { + echo list.map(input.code, fn(l) { handle_line_second_pass(l, input.symbols) }) + <<0>> +} diff --git a/asmblr/src/asmblr/token.gleam b/asmblr/src/asmblr/token.gleam new file mode 100644 index 0000000..936e711 --- /dev/null +++ b/asmblr/src/asmblr/token.gleam @@ -0,0 +1,14 @@ +import gleam/option + +pub type Token { + Token(column: Int, source: String) +} + +pub type Tokens { + Tokens( + label: option.Option(String), + mnemonic: option.Option(String), + arguments: option.Option(String), + comments: option.Option(String), + ) +} diff --git a/asmblr/test/asmblr_test.gleam b/asmblr/test/asmblr_test.gleam new file mode 100644 index 0000000..fba3c88 --- /dev/null +++ b/asmblr/test/asmblr_test.gleam @@ -0,0 +1,13 @@ +import gleeunit + +pub fn main() -> Nil { + gleeunit.main() +} + +// gleeunit test functions end in `_test` +pub fn hello_world_test() { + let name = "Joe" + let greeting = "Hello, " <> name <> "!" + + assert greeting == "Hello, Joe!" +} diff --git a/sim/.gitignore b/sim/.gitignore new file mode 100644 index 0000000..daae80b --- /dev/null +++ b/sim/.gitignore @@ -0,0 +1,2 @@ +build/** +build/ \ No newline at end of file diff --git a/sim/src/sim.gleam b/sim/src/sim.gleam index 382372c..65b5dd0 100644 --- a/sim/src/sim.gleam +++ b/sim/src/sim.gleam @@ -48,6 +48,6 @@ pub fn main() -> Nil { let machine = step(machine, instructions) let machine = step(machine, instructions) let machine = step(machine, instructions) - let machine = run_for(machine, instructions, 16) + let _machine = run_for(machine, instructions, 16) Nil }