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

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