commit message
This commit is contained in:
parent
24455d9eb8
commit
01679fd252
14 changed files with 410 additions and 1 deletions
23
asmblr/.github/workflows/test.yml
vendored
Normal file
23
asmblr/.github/workflows/test.yml
vendored
Normal 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
4
asmblr/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
*.beam
|
||||
*.ez
|
||||
/build
|
||||
erl_crash.dump
|
||||
24
asmblr/README.md
Normal file
24
asmblr/README.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# asmblr
|
||||
|
||||
[](https://hex.pm/packages/asmblr)
|
||||
[](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
24
asmblr/gleam.toml
Normal 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
20
asmblr/manifest.toml
Normal 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
22
asmblr/src/asmblr.gleam
Normal 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
|
||||
}
|
||||
}
|
||||
67
asmblr/src/asmblr/definitions.gleam
Normal file
67
asmblr/src/asmblr/definitions.gleam
Normal 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"]
|
||||
18
asmblr/src/asmblr/line.gleam
Normal file
18
asmblr/src/asmblr/line.gleam
Normal 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))
|
||||
}
|
||||
11
asmblr/src/asmblr/parser.gleam
Normal file
11
asmblr/src/asmblr/parser.gleam
Normal 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
|
||||
}
|
||||
167
asmblr/src/asmblr/passes.gleam
Normal file
167
asmblr/src/asmblr/passes.gleam
Normal 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>>
|
||||
}
|
||||
14
asmblr/src/asmblr/token.gleam
Normal file
14
asmblr/src/asmblr/token.gleam
Normal 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),
|
||||
)
|
||||
}
|
||||
13
asmblr/test/asmblr_test.gleam
Normal file
13
asmblr/test/asmblr_test.gleam
Normal 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
2
sim/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
build/**
|
||||
build/
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue