commit message

This commit is contained in:
jakob 2026-01-15 19:29:35 +01:00
parent 24455d9eb8
commit 01679fd252
14 changed files with 410 additions and 1 deletions

23
asmblr/.github/workflows/test.yml vendored Normal file
View file

@ -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

4
asmblr/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
*.beam
*.ez
/build
erl_crash.dump

24
asmblr/README.md Normal file
View file

@ -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 <https://hexdocs.pm/asmblr>.
## Development
```sh
gleam run # Run the project
gleam test # Run the tests
```

24
asmblr/gleam.toml Normal file
View file

@ -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"

20
asmblr/manifest.toml Normal file
View file

@ -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" }

22
asmblr/src/asmblr.gleam Normal file
View file

@ -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
}
}

View file

@ -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"]

View file

@ -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))
}

View file

@ -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
}

View file

@ -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 -> <<def.opcode:8>>
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)
<<def.opcode:8, arg1:4, arg2:4>>
}
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>>
}

View file

@ -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),
)
}

View file

@ -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!"
}

2
sim/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
build/**
build/

View file

@ -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
}