Init
This commit is contained in:
commit
6ef300f402
25 changed files with 2280 additions and 0 deletions
9
.editorconfig
Normal file
9
.editorconfig
Normal file
|
@ -0,0 +1,9 @@
|
|||
root = true
|
||||
|
||||
[*.cr]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
/docs/
|
||||
/lib/
|
||||
/bin/
|
||||
/.shards/
|
||||
*.dwarf
|
||||
./examples/*.bin
|
||||
examples/*.bin
|
||||
/.vscode/
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Dominic Grimm <dominic.grimm@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# hence
|
||||
|
||||
A stack based CPU with microcode and custom firmware support.
|
44
examples/add.asm
Normal file
44
examples/add.asm
Normal file
|
@ -0,0 +1,44 @@
|
|||
PROGRAM_SIZE = (32 * 1024)
|
||||
INSTRUCTION_SIZE = 3
|
||||
|
||||
main_jump = (PROGRAM_SIZE - INSTRUCTION_SIZE - INSTRUCTION_SIZE)
|
||||
|
||||
.org 0x0000
|
||||
|
||||
push main_jump
|
||||
jmp
|
||||
|
||||
data:
|
||||
answer:
|
||||
ANSWER_SIZE = 52
|
||||
.bw "The answer to life, the universe, and everything is ", 0, ANSWER_SIZE
|
||||
|
||||
main:
|
||||
push answer
|
||||
loop:
|
||||
dup
|
||||
load
|
||||
emit
|
||||
|
||||
inc
|
||||
dup
|
||||
push (answer + ANSWER_SIZE)
|
||||
neq
|
||||
push loop
|
||||
jif
|
||||
drop
|
||||
|
||||
push 40
|
||||
push 2
|
||||
add
|
||||
print
|
||||
|
||||
push "!"
|
||||
emit
|
||||
push "\n"
|
||||
emit
|
||||
stp
|
||||
|
||||
.org main_jump
|
||||
push main
|
||||
jmp
|
41
examples/hello_world.asm
Normal file
41
examples/hello_world.asm
Normal file
|
@ -0,0 +1,41 @@
|
|||
; Constants
|
||||
PROGRAM_SIZE = (32 * 1024)
|
||||
INSTRUCTION_SIZE = 3
|
||||
|
||||
main_jump = (PROGRAM_SIZE - INSTRUCTION_SIZE - INSTRUCTION_SIZE)
|
||||
|
||||
; Base organisation / offset
|
||||
.org 0x0000
|
||||
|
||||
; Static data
|
||||
push main_jump
|
||||
jmp ; Skip data section as following data is not program code
|
||||
|
||||
data:
|
||||
; Message data
|
||||
msg:
|
||||
MSG_SIZE = 12 ; Define size of message
|
||||
.bw "Hello World!", 0, MSG_SIZE ; Embed byte words in binary
|
||||
|
||||
; Main label
|
||||
main:
|
||||
push msg
|
||||
loop:
|
||||
dup
|
||||
load
|
||||
emit
|
||||
|
||||
inc
|
||||
dup
|
||||
push (msg + MSG_SIZE)
|
||||
neq
|
||||
push loop
|
||||
jif
|
||||
push "\n"
|
||||
emit
|
||||
stp
|
||||
|
||||
; Jump to main label
|
||||
.org main_jump
|
||||
push main
|
||||
jmp
|
2
pdf/chapter_break.tex
Normal file
2
pdf/chapter_break.tex
Normal file
|
@ -0,0 +1,2 @@
|
|||
\usepackage{sectsty}
|
||||
\sectionfont{\underline\clearpage}
|
19
pdf/pdf.sh
Normal file
19
pdf/pdf.sh
Normal file
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env bash
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
pandoc "$1" \
|
||||
-f gfm \
|
||||
-t pdf \
|
||||
-H pdf/chapter_break.tex \
|
||||
--wrap=none \
|
||||
--reference-links \
|
||||
-s \
|
||||
--toc \
|
||||
--toc-depth 4 \
|
||||
-V geometry:a4paper \
|
||||
-V mainfont="Vollkorn" \
|
||||
-V monofont="DejaVu Sans Mono" \
|
||||
-V linkcolor:blue \
|
||||
--highlight-style pdf/pygments.theme \
|
||||
--pdf-engine=xelatex \
|
||||
-o "$2"
|
211
pdf/pygments.theme
Normal file
211
pdf/pygments.theme
Normal file
|
@ -0,0 +1,211 @@
|
|||
{
|
||||
"text-color": null,
|
||||
"background-color": "#f8f8f8",
|
||||
"line-number-color": "#aaaaaa",
|
||||
"line-number-background-color": null,
|
||||
"text-styles": {
|
||||
"Other": {
|
||||
"text-color": "#007020",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"Attribute": {
|
||||
"text-color": "#7d9029",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"SpecialString": {
|
||||
"text-color": "#bb6688",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"Annotation": {
|
||||
"text-color": "#60a0b0",
|
||||
"background-color": null,
|
||||
"bold": true,
|
||||
"italic": true,
|
||||
"underline": false
|
||||
},
|
||||
"Function": {
|
||||
"text-color": "#06287e",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"String": {
|
||||
"text-color": "#4070a0",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"ControlFlow": {
|
||||
"text-color": "#007020",
|
||||
"background-color": null,
|
||||
"bold": true,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"Operator": {
|
||||
"text-color": "#666666",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"Error": {
|
||||
"text-color": "#ff0000",
|
||||
"background-color": null,
|
||||
"bold": true,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"BaseN": {
|
||||
"text-color": "#40a070",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"Alert": {
|
||||
"text-color": "#ff0000",
|
||||
"background-color": null,
|
||||
"bold": true,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"Variable": {
|
||||
"text-color": "#19177c",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"BuiltIn": {
|
||||
"text-color": null,
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"Extension": {
|
||||
"text-color": null,
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"Preprocessor": {
|
||||
"text-color": "#bc7a00",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"Information": {
|
||||
"text-color": "#60a0b0",
|
||||
"background-color": null,
|
||||
"bold": true,
|
||||
"italic": true,
|
||||
"underline": false
|
||||
},
|
||||
"VerbatimString": {
|
||||
"text-color": "#4070a0",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"Warning": {
|
||||
"text-color": "#60a0b0",
|
||||
"background-color": null,
|
||||
"bold": true,
|
||||
"italic": true,
|
||||
"underline": false
|
||||
},
|
||||
"Documentation": {
|
||||
"text-color": "#ba2121",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": true,
|
||||
"underline": false
|
||||
},
|
||||
"Import": {
|
||||
"text-color": null,
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"Char": {
|
||||
"text-color": "#4070a0",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"DataType": {
|
||||
"text-color": "#902000",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"Float": {
|
||||
"text-color": "#40a070",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"Comment": {
|
||||
"text-color": "#60a0b0",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": true,
|
||||
"underline": false
|
||||
},
|
||||
"CommentVar": {
|
||||
"text-color": "#60a0b0",
|
||||
"background-color": null,
|
||||
"bold": true,
|
||||
"italic": true,
|
||||
"underline": false
|
||||
},
|
||||
"Constant": {
|
||||
"text-color": "#880000",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"SpecialChar": {
|
||||
"text-color": "#4070a0",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"DecVal": {
|
||||
"text-color": "#40a070",
|
||||
"background-color": null,
|
||||
"bold": false,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
},
|
||||
"Keyword": {
|
||||
"text-color": "#007020",
|
||||
"background-color": null,
|
||||
"bold": true,
|
||||
"italic": false,
|
||||
"underline": false
|
||||
}
|
||||
}
|
||||
}
|
10
shard.lock
Normal file
10
shard.lock
Normal file
|
@ -0,0 +1,10 @@
|
|||
version: 2.0
|
||||
shards:
|
||||
commander:
|
||||
git: https://github.com/mrrooijen/commander.git
|
||||
version: 0.4.0
|
||||
|
||||
version_from_shard:
|
||||
git: https://github.com/hugopl/version_from_shard.git
|
||||
version: 1.2.5
|
||||
|
19
shard.yml
Normal file
19
shard.yml
Normal file
|
@ -0,0 +1,19 @@
|
|||
name: hence
|
||||
version: 0.1.0
|
||||
|
||||
authors:
|
||||
- Dominic Grimm <dominic.grimm@gmail.com>
|
||||
|
||||
targets:
|
||||
hence:
|
||||
main: src/cli/hence.cr
|
||||
|
||||
crystal: 1.4.1
|
||||
|
||||
license: MIT
|
||||
|
||||
dependencies:
|
||||
commander:
|
||||
github: mrrooijen/commander
|
||||
version_from_shard:
|
||||
github: hugopl/version_from_shard
|
200
src/cli/hence.cr
Normal file
200
src/cli/hence.cr
Normal file
|
@ -0,0 +1,200 @@
|
|||
require "commander"
|
||||
require "../hence"
|
||||
|
||||
CLI = Commander::Command.new do |cmd|
|
||||
cmd.use = "hence"
|
||||
cmd.long = "Hence assembler and emulator"
|
||||
|
||||
cmd.run do
|
||||
puts cmd.help
|
||||
end
|
||||
|
||||
# ameba:disable Lint/ShadowingOuterLocalVar
|
||||
cmd.commands.add do |cmd|
|
||||
cmd.use = "version"
|
||||
cmd.short = "Prins current hence version"
|
||||
cmd.long = cmd.short
|
||||
|
||||
cmd.run do
|
||||
puts Hence::VERSION
|
||||
end
|
||||
end
|
||||
|
||||
# ameba:disable Lint/ShadowingOuterLocalVar
|
||||
cmd.commands.add do |cmd|
|
||||
cmd.use = "firmware"
|
||||
cmd.short = "Firmware utility"
|
||||
cmd.long = cmd.short
|
||||
|
||||
cmd.flags.add do |flag|
|
||||
flag.name = "file"
|
||||
flag.long = "--file"
|
||||
flag.default = false
|
||||
flag.description = "Load firmware from file"
|
||||
flag.persistent = true
|
||||
end
|
||||
|
||||
# ameba:disable Lint/ShadowingOuterLocalVar
|
||||
cmd.commands.add do |cmd|
|
||||
cmd.use = "ls"
|
||||
cmd.short = "Lists available firmwares"
|
||||
cmd.long = cmd.short
|
||||
|
||||
cmd.run do
|
||||
puts Hence::Firmware::FIRMWARES.keys.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
# ameba:disable Lint/ShadowingOuterLocalVar
|
||||
cmd.commands.add do |cmd|
|
||||
cmd.use = "compile <firmware> [out]"
|
||||
cmd.short = "Compiles a firmware"
|
||||
cmd.long = cmd.short
|
||||
|
||||
cmd.flags.add do |flag|
|
||||
flag.name = "dump"
|
||||
flag.long = "--dump"
|
||||
flag.default = false
|
||||
flag.description = "Hexdumps the firmware"
|
||||
end
|
||||
|
||||
cmd.run do |opts, args|
|
||||
bin = if opts.bool["file"]
|
||||
Hence::Firmware.from_yaml(File.read(args[0]))
|
||||
else
|
||||
Hence::Firmware::FIRMWARES[args[0]]
|
||||
end.compile(Hence::Microcode::MICROCODE_NAMES)
|
||||
File.write(args[1], bin) if args[1]?
|
||||
|
||||
puts bin.hexdump if opts.bool["dump"]
|
||||
end
|
||||
end
|
||||
|
||||
# ameba:disable Lint/ShadowingOuterLocalVar
|
||||
cmd.commands.add do |cmd|
|
||||
cmd.use = "doc <firmware> [out]"
|
||||
cmd.short = "Generates documentation for a firmware"
|
||||
cmd.long = cmd.short
|
||||
|
||||
cmd.flags.add do |flag|
|
||||
flag.name = "dump"
|
||||
flag.long = "--dump"
|
||||
flag.default = false
|
||||
flag.description = "Hexdumps the firmware"
|
||||
end
|
||||
|
||||
cmd.run do |opts, args|
|
||||
doc = if opts.bool["file"]
|
||||
Hence::Firmware.from_yaml(File.read(args[0]))
|
||||
else
|
||||
Hence::Firmware::FIRMWARES[args[0]]
|
||||
end.to_s
|
||||
File.write(args[1], doc) if args[1]?
|
||||
|
||||
puts doc if opts.bool["dump"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# ameba:disable Lint/ShadowingOuterLocalVar
|
||||
cmd.commands.add do |cmd|
|
||||
cmd.use = "parse <src>"
|
||||
cmd.short = "Parses source code"
|
||||
cmd.long = cmd.short
|
||||
|
||||
cmd.run do |_opts, args|
|
||||
# ameba:disable Lint/DebugCalls
|
||||
pp Hence::Parser.parse(File.read(args[0]))
|
||||
end
|
||||
end
|
||||
|
||||
# ameba:disable Lint/ShadowingOuterLocalVar
|
||||
cmd.commands.add do |cmd|
|
||||
cmd.use = "assemble <src> [out]"
|
||||
cmd.short = "Compiles assembly code into a binary"
|
||||
cmd.long = cmd.short
|
||||
|
||||
cmd.flags.add do |flag|
|
||||
flag.name = "firmware"
|
||||
flag.short = "-f"
|
||||
flag.long = "--firmware"
|
||||
flag.default = "default"
|
||||
flag.description = "Custom firmware"
|
||||
end
|
||||
|
||||
cmd.flags.add do |flag|
|
||||
flag.name = "file"
|
||||
flag.long = "--file"
|
||||
flag.default = false
|
||||
flag.description = "Load firmware from YAML definition file"
|
||||
end
|
||||
|
||||
cmd.flags.add do |flag|
|
||||
flag.name = "dump"
|
||||
flag.long = "--dump"
|
||||
flag.default = false
|
||||
flag.description = "Hexdumps the binary"
|
||||
end
|
||||
|
||||
cmd.run do |opts, args|
|
||||
bin = Hence::Assembler.assemble(
|
||||
Hence::Parser.parse(File.read(args[0])),
|
||||
if opts.bool["file"]
|
||||
Hence::Firmware.from_yaml(File.read(opts.string["file"]))
|
||||
else
|
||||
Hence::Firmware::FIRMWARES[opts.string["firmware"]]
|
||||
end.build_opcodes[:names]
|
||||
)
|
||||
|
||||
File.write(args[1], bin) if args[1]?
|
||||
puts bin.hexdump if opts.bool["dump"]
|
||||
end
|
||||
end
|
||||
|
||||
# ameba:disable Lint/ShadowingOuterLocalVar
|
||||
cmd.commands.add do |cmd|
|
||||
cmd.use = "run <bin>"
|
||||
cmd.short = "Runs a binary"
|
||||
cmd.long = cmd.short
|
||||
|
||||
cmd.flags.add do |flag|
|
||||
flag.name = "firmware"
|
||||
flag.short = "-f"
|
||||
flag.long = "--firmware"
|
||||
flag.default = "default"
|
||||
flag.description = "Custom firmware"
|
||||
end
|
||||
|
||||
cmd.flags.add do |flag|
|
||||
flag.name = "file"
|
||||
flag.long = "--file"
|
||||
flag.default = false
|
||||
flag.description = "Load firmware from file"
|
||||
end
|
||||
|
||||
cmd.run do |opts, args|
|
||||
bin = Bytes.new({{ 32 * 1024 }})
|
||||
File.open(args[0]) do |file|
|
||||
file.gets_to_end.to_slice.copy_to(bin)
|
||||
end
|
||||
|
||||
Hence::Emulator.emulate(
|
||||
Hence::Emulator::Data.new(
|
||||
bin,
|
||||
if opts.bool["file"]
|
||||
buffer = Bytes.new({{ 32 * 1024 }})
|
||||
File.open(opts.string["firmware"]) do |file|
|
||||
file.gets_to_end.to_slice.copy_to(buffer)
|
||||
end
|
||||
|
||||
buffer
|
||||
else
|
||||
Hence::Firmware::FIRMWARES[opts.string["firmware"]].compile(Hence::Microcode::MICROCODE_NAMES)
|
||||
end
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Commander.run(CLI, ARGV)
|
1
src/hence.cr
Normal file
1
src/hence.cr
Normal file
|
@ -0,0 +1 @@
|
|||
require "./hence/*"
|
83
src/hence/assembler.cr
Normal file
83
src/hence/assembler.cr
Normal file
|
@ -0,0 +1,83 @@
|
|||
module Hence
|
||||
module Assembler
|
||||
extend self
|
||||
|
||||
def assemble(ast : Parser::AST, opcode_names : Hash(String, UInt8), data = Data.new) : Bytes
|
||||
ast.body.each do |node|
|
||||
data.constants["OFFSET"] = Parser::AST::NumberArg.new(data.offset)
|
||||
|
||||
case node
|
||||
when Parser::AST::ConstantDeclarationNode
|
||||
raise "Constants can't be redefined" if data.constants[node.name]?
|
||||
|
||||
data.constants[node.name] = node.value
|
||||
when Parser::AST::LabelNode
|
||||
raise "Labels can't be redefined" if data.constants[node.name]?
|
||||
|
||||
data.constants[node.name] = Parser::AST::NumberArg.new(data.offset)
|
||||
when Parser::AST::MacroCallNode
|
||||
MACROS[node.name].call(data, node.args)
|
||||
when Parser::AST::CallNode
|
||||
data.bin[data.offset] = opcode_names[node.name]
|
||||
arg = node.arg.try(&.resolve(data)) || Bytes.new(2)
|
||||
data.bin[data.offset + 1] = arg[0]
|
||||
data.bin[data.offset + 2] = arg[1]
|
||||
|
||||
data.offset += 3
|
||||
else
|
||||
raise "Unexpected node type: #{typeof(node)}"
|
||||
end
|
||||
|
||||
raise "Offset out of bounds: 0x#{data.offset.to_s(16).rjust(4, '0')} > 0x#{data.bin.size.to_s(16).rjust(4, '0')}" if data.offset > data.bin.size
|
||||
end
|
||||
|
||||
data.bin
|
||||
end
|
||||
|
||||
class Data
|
||||
property bin
|
||||
property offset
|
||||
property constants
|
||||
|
||||
def initialize(
|
||||
@bin = Bytes.new({{ 32 * 1024 }}),
|
||||
@offset = 0_u16,
|
||||
@constants = {} of String => Parser::AST::Arg
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
MACROS = {
|
||||
"org" => ->(data : Data, args : Array(Parser::AST::Arg)) do
|
||||
data.offset = args[0].resolve_as_number(data)
|
||||
end,
|
||||
"org_add" => ->(data : Data, args : Array(Parser::AST::Arg)) do
|
||||
data.offset += args[0].resolve_as_number(data)
|
||||
end,
|
||||
"org_sub" => ->(data : Data, args : Array(Parser::AST::Arg)) do
|
||||
data.offset -= args[0].resolve_as_number(data)
|
||||
end,
|
||||
"bytes" => ->(data : Data, args : Array(Parser::AST::Arg)) do
|
||||
args.each do |a|
|
||||
a.resolve(data).each do |b|
|
||||
data.bin[data.offset] = b
|
||||
data.offset += 1
|
||||
end
|
||||
end
|
||||
end,
|
||||
"bw" => ->(data : Data, args : Array(Parser::AST::Arg)) do
|
||||
string_arg = String.new(args[0].resolve(data)).gsub(/\\n/, '\n').to_slice
|
||||
pos_start = args[1]?.try(&.resolve_as_number(data)) || 0_u16
|
||||
pos_end = args[2]?.try(&.resolve_as_number(data)) || string_arg.size.to_u16
|
||||
|
||||
string_arg[pos_start...pos_end].each do |b|
|
||||
data.bin[data.offset] = b
|
||||
data.offset += 1
|
||||
end
|
||||
end,
|
||||
"include" => ->(_data : Data, _args : Array(Parser::AST::Arg)) do
|
||||
raise "Unimplemented"
|
||||
end,
|
||||
}
|
||||
end
|
||||
end
|
247
src/hence/emulator.cr
Normal file
247
src/hence/emulator.cr
Normal file
|
@ -0,0 +1,247 @@
|
|||
module Hence
|
||||
module Emulator
|
||||
extend self
|
||||
|
||||
def emulate(data : Data) : Data
|
||||
until data.reg_pc == 0xffff_u16
|
||||
data.reg_opc = (data.get_memory(data.reg_pc) & 0xff_u8).to_u8
|
||||
data.reg_pc &+= 1
|
||||
data.reg_arg = data.get_memory(data.reg_pc) << 8
|
||||
data.reg_pc &+= 1
|
||||
data.reg_arg = data.reg_arg | (data.get_memory(data.reg_pc) & 0xff_u8)
|
||||
data.reg_pc &+= 1
|
||||
|
||||
base_mcc = data.reg_opc &* 4
|
||||
data.reg_mccs = data.firmware[base_mcc].to_u16 << 8
|
||||
data.reg_mccs = data.reg_mccs | data.firmware[base_mcc &+ 1]
|
||||
data.reg_mcce = data.firmware[base_mcc &+ 2].to_u16 << 8
|
||||
data.reg_mcce = data.reg_mcce | data.firmware[base_mcc &+ 3]
|
||||
|
||||
until data.reg_mccs &+ data.reg_mccpc >= data.reg_mcce
|
||||
base_mccpc = data.reg_mccs &+ data.reg_mccpc
|
||||
data.reg_mcc = data.firmware[base_mccpc]
|
||||
data.reg_mccarg = data.firmware[base_mccpc &+ 1].to_u16 << 8
|
||||
data.reg_mccarg = data.reg_mccarg | data.firmware[base_mccpc &+ 2]
|
||||
data.reg_mccpc &+= 3
|
||||
|
||||
data.microcodes[data.reg_mcc].run(data)
|
||||
end
|
||||
|
||||
data.reg_mccpc = 0_u16
|
||||
data.reg_tmp = 0_u16
|
||||
data.reg_a = 0_u16
|
||||
data.reg_b = 0_u16
|
||||
data.reg_c = 0_u16
|
||||
data.reg_d = 0_u16
|
||||
end
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
class Data
|
||||
property firmware
|
||||
property microcodes
|
||||
property reg_pc
|
||||
property reg_opc
|
||||
property reg_arg
|
||||
property reg_mccpc
|
||||
property reg_mccs
|
||||
property reg_mcce
|
||||
property reg_mcc
|
||||
property reg_mccarg
|
||||
property reg_tmp
|
||||
property reg_sp
|
||||
property reg_a
|
||||
property reg_b
|
||||
property reg_c
|
||||
property reg_d
|
||||
property reg_inp
|
||||
property reg_out
|
||||
|
||||
def initialize(
|
||||
@program : Bytes,
|
||||
@firmware : Bytes,
|
||||
@microcodes = Microcode::MICROCODES,
|
||||
@reg_pc = 0_u16,
|
||||
@reg_opc = 0_u8,
|
||||
@reg_arg = 0_u16,
|
||||
@reg_mccpc = 0_u16,
|
||||
@reg_mccs = 0_u16,
|
||||
@reg_mcce = 0_u16,
|
||||
@reg_mcc = 0_u8,
|
||||
@reg_mccarg = 0_u16,
|
||||
@reg_tmp = 0_u16,
|
||||
@reg_sp = 0_u16,
|
||||
@reg_a = 0_u16,
|
||||
@reg_b = 0_u16,
|
||||
@reg_c = 0_u16,
|
||||
@reg_d = 0_u16,
|
||||
@reg_inp = 0_u16,
|
||||
@reg_out = 0_u16,
|
||||
@stack = Slice(UInt16).new({{ 8 * 1024 }}),
|
||||
@memory = Slice(UInt16).new({{ 24 * 1024 }})
|
||||
)
|
||||
end
|
||||
|
||||
def get_register(index : UInt8) : UInt16
|
||||
case index
|
||||
when 0x0_u8
|
||||
@reg_pc
|
||||
when 0x1_u8
|
||||
@reg_opc.to_u16
|
||||
when 0x2_u8
|
||||
@reg_arg
|
||||
when 0x3_u8
|
||||
@reg_mccpc
|
||||
when 0x4_u8
|
||||
@reg_mccs
|
||||
when 0x5_u8
|
||||
@reg_mcce
|
||||
when 0x6_u8
|
||||
@reg_mcc.to_u16
|
||||
when 0x7_u8
|
||||
@reg_mccarg
|
||||
when 0x8_u8
|
||||
@reg_tmp
|
||||
when 0x9_u8
|
||||
@reg_sp
|
||||
when 0xa_u8
|
||||
@reg_a
|
||||
when 0xb_u8
|
||||
@reg_b
|
||||
when 0xc_u8
|
||||
@reg_c
|
||||
when 0xd_u8
|
||||
@reg_d
|
||||
when 0xe_u8
|
||||
@reg_inp
|
||||
when 0xf_u8
|
||||
@reg_out
|
||||
else
|
||||
raise "Invalid register index: #{index}"
|
||||
end
|
||||
end
|
||||
|
||||
def set_register(index : UInt8, value : UInt16) : UInt16
|
||||
case index
|
||||
when 0x0_u8
|
||||
@reg_pc = value
|
||||
when 0x1_u8
|
||||
@reg_opc = value
|
||||
when 0x2_u8
|
||||
@reg_arg = value
|
||||
when 0x3_u8
|
||||
@reg_mccpc = value
|
||||
when 0x4_u8
|
||||
@reg_mccs = value
|
||||
when 0x5_u8
|
||||
@reg_mcce = value
|
||||
when 0x6_u8
|
||||
@reg_mcc = value
|
||||
when 0x7_u8
|
||||
@reg_mccarg = value
|
||||
when 0x8_u8
|
||||
@reg_tmp = value
|
||||
when 0x9_u8
|
||||
@reg_sp = value
|
||||
when 0xa_u8
|
||||
@reg_a = value
|
||||
when 0xb_u8
|
||||
@reg_b = value
|
||||
when 0xc_u8
|
||||
@reg_c = value
|
||||
when 0xd_u8
|
||||
@reg_d = value
|
||||
when 0xe_u8
|
||||
@reg_inp = value
|
||||
when 0xf_u8
|
||||
@reg_out = value
|
||||
else
|
||||
raise "Invalid register index: #{index}"
|
||||
end
|
||||
end
|
||||
|
||||
def alu(op : UInt8) : UInt16
|
||||
@reg_tmp =
|
||||
case op
|
||||
when 0x00 # not
|
||||
~@reg_a
|
||||
when 0x01 # and
|
||||
@reg_a & @reg_b
|
||||
when 0x02 # nand
|
||||
~(@reg_a & @reg_b)
|
||||
when 0x03 # or
|
||||
@reg_a | @reg_b
|
||||
when 0x04 # nor
|
||||
~(@reg_a | @reg_b)
|
||||
when 0x05 # xor
|
||||
@reg_a ^ @reg_b
|
||||
when 0x06 # xnor
|
||||
~(@reg_a ^ @reg_b)
|
||||
when 0x07 # shl
|
||||
@reg_a << @reg_b
|
||||
when 0x08 # shr
|
||||
@reg_a >> @reg_b
|
||||
when 0x09 # add
|
||||
@reg_a &+ @reg_b
|
||||
when 0x0a # sub
|
||||
@reg_a &- @reg_b
|
||||
when 0x0b # mul
|
||||
@reg_a &* @reg_b
|
||||
when 0x0c # div
|
||||
@reg_a // @reg_b
|
||||
when 0x0d # mod
|
||||
@reg_a % @reg_b
|
||||
when 0x0e # cmp
|
||||
if @reg_a < @reg_b
|
||||
1_u16
|
||||
elsif @reg_a > @reg_b
|
||||
2_u16
|
||||
else
|
||||
0_u16
|
||||
end
|
||||
when 0x0f # eq
|
||||
@reg_a == @reg_b ? 1_u16 : 0_u16
|
||||
when 0x10 # neq
|
||||
@reg_a != @reg_b ? 1_u16 : 0_u16
|
||||
when 0x11 # lt
|
||||
@reg_a < @reg_b ? 1_u16 : 0_u16
|
||||
when 0x12 # gt
|
||||
@reg_a > @reg_b ? 1_u16 : 0_u16
|
||||
when 0x13 # leq
|
||||
@reg_a <= @reg_b ? 1_u16 : 0_u16
|
||||
when 0x14 # geq
|
||||
@reg_a >= @reg_b ? 1_u16 : 0_u16
|
||||
when 0x15 # bol
|
||||
@reg_a == 1 ? 1_u16 : 0_u16
|
||||
when 0x16 # neg
|
||||
@reg_a == 1 ? 0_u16 : 1_u16
|
||||
else
|
||||
raise "Invalid ALU operation: 0x#{op.to_s(16).rjust(2, '0')}"
|
||||
end
|
||||
end
|
||||
|
||||
def get_memory(address : UInt16) : UInt16
|
||||
if address < {{ 32 * 1024 }}
|
||||
@program[address].to_u16
|
||||
elsif address < {{ 40 * 1024 }}
|
||||
@stack[address - {{ 32 * 1024 }}]
|
||||
else
|
||||
@memory[address - {{ 40 * 1024 }}]
|
||||
end
|
||||
end
|
||||
|
||||
def set_memory(address : UInt16, value : UInt16) : UInt16
|
||||
if address < {{ 32 * 1024 }}
|
||||
0_u16
|
||||
elsif address < {{ 40 * 1024 }}
|
||||
@stack[address - {{ 32 * 1024 }}] = value
|
||||
|
||||
value
|
||||
else
|
||||
@memory[address - {{ 40 * 1024 }}] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
181
src/hence/firmware.cr
Normal file
181
src/hence/firmware.cr
Normal file
|
@ -0,0 +1,181 @@
|
|||
require "yaml"
|
||||
require "ecr"
|
||||
|
||||
require "./firmware/*"
|
||||
|
||||
module Hence
|
||||
class Firmware
|
||||
include YAML::Serializable
|
||||
|
||||
@[YAML::Field]
|
||||
property name
|
||||
|
||||
@[YAML::Field(emit_null: true)]
|
||||
property description
|
||||
|
||||
@[YAML::Field]
|
||||
property authors
|
||||
|
||||
@[YAML::Field]
|
||||
property date
|
||||
|
||||
@[YAML::Field]
|
||||
property version
|
||||
|
||||
@[YAML::Field]
|
||||
property sections
|
||||
|
||||
def initialize(
|
||||
@name : String,
|
||||
@description : String?,
|
||||
@authors : Array(String),
|
||||
@date : Time,
|
||||
@version : String,
|
||||
@sections : Array(Section)
|
||||
)
|
||||
end
|
||||
|
||||
def full_name : String
|
||||
"#{@name}:#{@version}"
|
||||
end
|
||||
|
||||
ECR.def_to_s "#{__DIR__}/firmware/documentation.md.ecr"
|
||||
|
||||
def build_opcodes : {names: Hash(String, UInt8), opcodes: Hash(UInt8, Opcode)}
|
||||
names = {} of String => UInt8
|
||||
opcodes = {} of UInt8 => Opcode
|
||||
i = 0_u8
|
||||
@sections.each do |s|
|
||||
s.opcodes.each do |opc|
|
||||
j = if opc.opcode
|
||||
opc.opcode.not_nil!
|
||||
else
|
||||
x = i
|
||||
i += 1
|
||||
|
||||
x
|
||||
end
|
||||
raise "Duplicate opcode: #{j}" if opcodes.has_key?(j)
|
||||
|
||||
names[opc.name] = j
|
||||
opcodes[j] = opc.to_opcode(opc.opcode ? nil : j)
|
||||
end
|
||||
end
|
||||
|
||||
{names: names, opcodes: opcodes}
|
||||
end
|
||||
|
||||
def compile(microcode_names : Hash(String, UInt8), data = Assembler::Data.new) : Bytes
|
||||
sorted_opcodes = build_opcodes[:opcodes].to_a.sort_by!(&.[0])
|
||||
data.offset = (sorted_opcodes.last[0].to_u16 + 1) * 4
|
||||
sorted_opcodes.each do |opc|
|
||||
end_offset = data.offset + opc[1].microcode.body.size * 3
|
||||
base = opc[0] * 4
|
||||
data.bin[base] = (data.offset >> 8).to_u8
|
||||
data.bin[base + 1] = (data.offset & 0xff).to_u8
|
||||
data.bin[base + 2] = (end_offset >> 8).to_u8
|
||||
data.bin[base + 3] = (end_offset & 0xff).to_u8
|
||||
|
||||
i = 0_u16
|
||||
opc[1].microcode.body.each do |node|
|
||||
j = data.offset + i
|
||||
|
||||
case node
|
||||
when Parser::AST::LabelNode
|
||||
data.constants[node.name] = Parser::AST::NumberArg.new(data.offset &- 3)
|
||||
when Parser::AST::CallNode
|
||||
data.bin[j] = microcode_names[node.name]
|
||||
if node.arg
|
||||
arg = node.arg.not_nil!.resolve(data)
|
||||
|
||||
data.bin[j + 1] = arg.not_nil![0]
|
||||
data.bin[j + 2] = arg.not_nil![1]
|
||||
end
|
||||
|
||||
i += 3
|
||||
else
|
||||
raise "Unexpected node type: #{typeof(node)}"
|
||||
end
|
||||
end
|
||||
|
||||
data.offset = end_offset
|
||||
end
|
||||
|
||||
data.bin
|
||||
end
|
||||
|
||||
class Section
|
||||
include YAML::Serializable
|
||||
|
||||
@[YAML::Field]
|
||||
property name
|
||||
|
||||
@[YAML::Field(emit_null: true)]
|
||||
property description
|
||||
|
||||
@[YAML::Field]
|
||||
property opcodes
|
||||
|
||||
def initialize(@name : String, @description : String?, @opcodes : Array(Opcode))
|
||||
end
|
||||
|
||||
class Opcode
|
||||
include YAML::Serializable
|
||||
|
||||
@[YAML::Field]
|
||||
property name
|
||||
|
||||
@[YAML::Field(emit_null: true)]
|
||||
property description
|
||||
|
||||
@[YAML::Field(emit_null: true)]
|
||||
property example
|
||||
|
||||
@[YAML::Field(emit_null: true)]
|
||||
property opcode
|
||||
|
||||
@[YAML::Field(emit_null: true)]
|
||||
property arg
|
||||
|
||||
@[YAML::Field]
|
||||
property stack
|
||||
|
||||
@[YAML::Field]
|
||||
property microcode
|
||||
|
||||
def initialize(
|
||||
@name : String,
|
||||
@description : String?,
|
||||
@example : String?,
|
||||
@opcode : UInt8?,
|
||||
@arg : String?,
|
||||
@stack : Stack,
|
||||
@microcode : String
|
||||
)
|
||||
end
|
||||
|
||||
def to_opcode(opcode : UInt8? = nil) : ::Hence::Opcode
|
||||
raise "Opcode already defined" if opcode && @opcode
|
||||
|
||||
Hence::Opcode.new(
|
||||
@name,
|
||||
Parser.parse(@microcode)
|
||||
)
|
||||
end
|
||||
|
||||
class Stack
|
||||
include YAML::Serializable
|
||||
|
||||
@[YAML::Field]
|
||||
property input
|
||||
|
||||
@[YAML::Field]
|
||||
property output
|
||||
|
||||
def initialize(@input : Array(String), @output : Array(String))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
56
src/hence/firmware/documentation.md.ecr
Normal file
56
src/hence/firmware/documentation.md.ecr
Normal file
|
@ -0,0 +1,56 @@
|
|||
<%- sorted_opcodes = build_opcodes[:opcodes].to_a.sort_by!(&.[0]) -%>
|
||||
---
|
||||
title: <%=full_name %>
|
||||
description: "<%= @description %>"
|
||||
author: [<%= @authors.map { |a| "\"#{a}\"" }.join(", ") %>]
|
||||
date: <%= @date.to_s("%Y-%m-%d") %>
|
||||
version: "<%= @version %>"
|
||||
---
|
||||
|
||||
# `<%= full_name %>`
|
||||
|
||||
<%= @description %>
|
||||
|
||||
## Opcodes
|
||||
|
||||
Opcode | Name
|
||||
---|---
|
||||
<%- sorted_opcodes.each do |opcode| -%>
|
||||
`0x<%= opcode[0].to_s(16).rjust(2, '0') %>` | [`<%= opcode[1].name %>`](#<%= opcode[1].name %>)
|
||||
<%- end -%>
|
||||
|
||||
## Sections
|
||||
|
||||
<%- @sections.each do |section| -%>
|
||||
### <%= section.name %>
|
||||
|
||||
<%= section.description %>
|
||||
|
||||
<%- section.opcodes.each do |opcode| -%>
|
||||
#### `<%= opcode.name %>`
|
||||
|
||||
<%= opcode.description %>
|
||||
|
||||
<% input = opcode.stack.input.join(", ") -%>
|
||||
<% input += " " unless input.blank? -%>
|
||||
<% output = opcode.stack.output.join(", ") -%>
|
||||
<% output = " " + output unless output.blank? -%>
|
||||
Opcode | Argument | Stack
|
||||
---|---|---
|
||||
`0x<%= sorted_opcodes.find! { |opc| opc[1].name == opcode.name }[0].to_s(16).rjust(2, '0') %>` | <%= opcode.arg && "`#{opcode.arg}`" %> | <%= (opcode.stack.input.size > 0 && opcode.stack.output.size > 0) ? "`#{input}--#{output}`" : nil %>
|
||||
<%- if opcode.example -%>
|
||||
|
||||
```gnuassembler
|
||||
; Example
|
||||
<%= opcode.example -%>
|
||||
```
|
||||
<%- end -%>
|
||||
|
||||
```gnuassembler
|
||||
; Microcode
|
||||
<%= opcode.microcode -%>
|
||||
```
|
||||
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
> _Generated using the [hence](https://git.dergrimm.net/dergrimm/hence) firmware compiler._
|
13
src/hence/firmware/firmwares.cr
Normal file
13
src/hence/firmware/firmwares.cr
Normal file
|
@ -0,0 +1,13 @@
|
|||
module Hence
|
||||
class Firmware
|
||||
{% begin %}
|
||||
{% base_dir = "#{__DIR__}/firmwares" %}
|
||||
|
||||
FIRMWARES = {
|
||||
{% for firmware_name in run("./macros/get_firmwares.cr", base_dir).stringify.lines %}
|
||||
{{ firmware_name }} => Firmware.from_yaml({{ read_file("#{base_dir.id}/#{firmware_name.id}.yml") }}),
|
||||
{% end %}
|
||||
} of String => Firmware
|
||||
{% end %}
|
||||
end
|
||||
end
|
622
src/hence/firmware/firmwares/default.yml
Normal file
622
src/hence/firmware/firmwares/default.yml
Normal file
|
@ -0,0 +1,622 @@
|
|||
---
|
||||
name: default
|
||||
description: Default hence opcode firmware
|
||||
authors:
|
||||
- Dominic Grimm
|
||||
date: 2022-05-14
|
||||
version: 0.1.0
|
||||
|
||||
sections:
|
||||
- name: flow-control
|
||||
description: Flow control
|
||||
opcodes:
|
||||
- name: nop
|
||||
description: No operation
|
||||
example: |
|
||||
nop ; literally nothing but wasting precious cycles
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: []
|
||||
output: []
|
||||
microcode: |
|
||||
nop
|
||||
- name: dbg
|
||||
description: Debugs current stack
|
||||
example: |
|
||||
push 40
|
||||
push 2
|
||||
dbg ; => (2) [40, 2]
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: []
|
||||
output: []
|
||||
microcode: |
|
||||
dbg
|
||||
- name: stp
|
||||
description: Stops execution
|
||||
example: |
|
||||
stp ; program stops execution here. push will not be called
|
||||
push 1
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: []
|
||||
output: []
|
||||
microcode: |
|
||||
tmps 0xfffc
|
||||
tmpl 0x0
|
||||
- name: jmp
|
||||
description: Jumps to address
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [addr]
|
||||
output: []
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0x0
|
||||
- name: jif
|
||||
description: Jumps to address if condition is true (1)
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [cond, addr]
|
||||
output: []
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xc
|
||||
drop
|
||||
tmpl 0xa
|
||||
tmpsr 0xc
|
||||
tmplc 0x0
|
||||
- name: stack-manipulation
|
||||
description: Stack manipulation
|
||||
opcodes:
|
||||
- name: push
|
||||
description: Pushes value to stack
|
||||
opcode: null
|
||||
arg: x
|
||||
stack:
|
||||
input: []
|
||||
output: [x]
|
||||
microcode: |
|
||||
tmpsr 0x2
|
||||
push
|
||||
- name: drop
|
||||
description: Drops top element from stack
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x]
|
||||
output: []
|
||||
microcode: |
|
||||
drop
|
||||
- name: depth
|
||||
description: Returns stack size / depth
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: []
|
||||
output: [depth]
|
||||
microcode: |
|
||||
tmpsr 0x9
|
||||
push
|
||||
- name: pick
|
||||
description: Duplicates top nth element stack value
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: ["n"]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
pick
|
||||
push
|
||||
- name: dup
|
||||
description: Duplicates top element on stack
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x]
|
||||
output: [x, x]
|
||||
microcode: |
|
||||
pick
|
||||
push
|
||||
- name: swap
|
||||
description: Swaps top two elements on stack
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x2, x1]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xa
|
||||
drop
|
||||
tmpl 0xb
|
||||
tmpsr 0xa
|
||||
push
|
||||
tmpsr 0xb
|
||||
push
|
||||
- name: over
|
||||
description: Duplicates second top element onto stack
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x1, x2, x1]
|
||||
microcode: |
|
||||
tmps 1
|
||||
pick
|
||||
push
|
||||
- name: rot
|
||||
description: Rotates top three elements on stack
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2, x3]
|
||||
output: [x2, x3, x1]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xa
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xc
|
||||
tmpsr 0xb
|
||||
push
|
||||
tmpsr 0xc
|
||||
push
|
||||
tmpsr 0xa
|
||||
push
|
||||
- name: nip
|
||||
description: Drops the first item below the top of stack
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x2]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xa
|
||||
drop
|
||||
tmpsr 0xa
|
||||
push
|
||||
- name: tuck
|
||||
description: Copies the top stack item below the second stack item
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x2, x1, x2]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xa
|
||||
drop
|
||||
tmpl 0xb
|
||||
tmpsr 0xa
|
||||
push
|
||||
tmpsr 0xb
|
||||
push
|
||||
tmpsr 0xa
|
||||
push
|
||||
- name: memory-access
|
||||
description: Memory access
|
||||
opcodes:
|
||||
- name: load
|
||||
description: Loads value from memory
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [addr]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
load
|
||||
push
|
||||
- name: store
|
||||
description: Stores value to memory
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x, addr]
|
||||
output: []
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
tmpsr 0xb
|
||||
store
|
||||
- name: io
|
||||
description: IO access
|
||||
opcodes:
|
||||
- name: inp
|
||||
description: Gets input from general IO
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: []
|
||||
output: [x]
|
||||
microcode: |
|
||||
tmpsr 0xe
|
||||
push
|
||||
- name: out
|
||||
description: Outputs value to general IO
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x]
|
||||
output: []
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xf
|
||||
- name: print
|
||||
description: Prints number to STDOUT / LCD display
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x]
|
||||
output: []
|
||||
microcode: |
|
||||
drop
|
||||
print
|
||||
- name: emit
|
||||
description: Prints input as unicode char to STDOUT / LCD display
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x]
|
||||
output: []
|
||||
microcode: |
|
||||
drop
|
||||
emit
|
||||
- name: arithmetic
|
||||
description: Arithmetic
|
||||
opcodes:
|
||||
- name: not
|
||||
description: Logical invert of top stack element
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x00
|
||||
push
|
||||
- name: and
|
||||
description: Logical and of top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x01
|
||||
push
|
||||
- name: nand
|
||||
description: Logical nand of top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x02
|
||||
push
|
||||
- name: or
|
||||
description: Logical or of top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x03
|
||||
push
|
||||
- name: nor
|
||||
description: Logical nor of top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x04
|
||||
push
|
||||
- name: xor
|
||||
description: Logical xor of top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x05
|
||||
push
|
||||
- name: xnor
|
||||
description: Logical xnor of top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x06
|
||||
push
|
||||
- name: shl
|
||||
description: Shifts top stack element left by n bits
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x, n]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x07
|
||||
push
|
||||
- name: shr
|
||||
description: Shifts top stack element right by n bits
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x, n]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x08
|
||||
push
|
||||
- name: add
|
||||
description: Adds top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x09
|
||||
push
|
||||
- name: inc
|
||||
description: Increments top stack element by 1
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x]
|
||||
output: [x]
|
||||
microcode: |
|
||||
tmps 1
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x09
|
||||
push
|
||||
- name: sub
|
||||
description: Subtracts top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x0a
|
||||
push
|
||||
- name: dec
|
||||
description: Decrements top stack element by 1
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x]
|
||||
output: [x]
|
||||
microcode: |
|
||||
tmps 1
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x0a
|
||||
push
|
||||
- name: mul
|
||||
description: Multiplies top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x0b
|
||||
push
|
||||
- name: div
|
||||
description: Divides top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x0c
|
||||
push
|
||||
- name: mod
|
||||
description: Modulo of top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x0d
|
||||
push
|
||||
- name: cmp
|
||||
description: Compares top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x0e
|
||||
push
|
||||
- name: eq
|
||||
description: Logical equality of top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x0f
|
||||
push
|
||||
- name: neq
|
||||
description: Logical inequality of top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x10
|
||||
push
|
||||
- name: lt
|
||||
description: Logical less than of top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x11
|
||||
push
|
||||
- name: gt
|
||||
description: Logical greater than of top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x12
|
||||
push
|
||||
- name: leq
|
||||
description: Logical less than or equal to of top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x13
|
||||
push
|
||||
- name: geq
|
||||
description: Logical greater than or equal to of top two stack elements
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x1, x2]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xb
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x14
|
||||
push
|
||||
- name: bol
|
||||
description: Boolean of top stack element
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x15
|
||||
push
|
||||
- name: neg
|
||||
description: Negates top stack element
|
||||
opcode: null
|
||||
arg: null
|
||||
stack:
|
||||
input: [x]
|
||||
output: [x]
|
||||
microcode: |
|
||||
drop
|
||||
tmpl 0xa
|
||||
alu 0x16
|
||||
push
|
1
src/hence/firmware/macros/get_firmwares.cr
Normal file
1
src/hence/firmware/macros/get_firmwares.cr
Normal file
|
@ -0,0 +1 @@
|
|||
print Dir[Path[ARGV[0], "*.yml"]].map { |f| File.basename(f, ".yml") }.join("\n")
|
98
src/hence/microcode.cr
Normal file
98
src/hence/microcode.cr
Normal file
|
@ -0,0 +1,98 @@
|
|||
require "colorize"
|
||||
require "string_scanner"
|
||||
|
||||
module Hence
|
||||
struct Microcode
|
||||
def initialize(@runner : Emulator::Data ->)
|
||||
end
|
||||
|
||||
def initialize(&@runner : Emulator::Data ->)
|
||||
end
|
||||
|
||||
def run(data : Emulator::Data) : Emulator::Data
|
||||
@runner.call(data)
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
MICROCODES = {
|
||||
0x00_u8 => Microcode.new { }, # nop
|
||||
0x01_u8 => Microcode.new do |d| # dbg
|
||||
stack = if d.reg_sp == 0
|
||||
[] of UInt16
|
||||
else
|
||||
d.reg_sp.times.map_with_index { |_, i| d.get_memory({{ 32_u16 * 1024 }} + i) }
|
||||
end
|
||||
|
||||
puts "#{"[DEBUG]".colorize(:red).mode(:bold)}: (#{d.reg_sp}) #{stack}"
|
||||
print "Press enter to continue execution...".colorize.mode(:dim)
|
||||
gets
|
||||
end,
|
||||
0x02_u8 => Microcode.new do |d| # jmp
|
||||
d.reg_mccpc = d.reg_tmp
|
||||
end,
|
||||
0x03_u8 => Microcode.new do |d| # jif
|
||||
if d.reg_tmp == 1
|
||||
d.reg_mccpc = d.reg_a
|
||||
end
|
||||
end,
|
||||
0x04_u8 => Microcode.new do |d| # tmpl
|
||||
d.set_register((d.reg_mccarg & 0xff).to_u8, d.reg_tmp)
|
||||
end,
|
||||
0x05_u8 => Microcode.new do |d| # tmplc
|
||||
d.set_register((d.reg_mccarg & 0xff).to_u8, d.reg_tmp) if d.reg_a == 1
|
||||
end,
|
||||
0x06_u8 => Microcode.new do |d| # tmps
|
||||
d.reg_tmp = d.reg_mccarg
|
||||
end,
|
||||
0x07_u8 => Microcode.new do |d| # tmpsr
|
||||
d.reg_tmp = d.get_register((d.reg_mccarg & 0xff).to_u8)
|
||||
end,
|
||||
0x08_u8 => Microcode.new do |d| # push
|
||||
d.set_memory({{ 32_u16 * 1024 }} + d.reg_sp, d.reg_tmp)
|
||||
d.reg_sp += 1
|
||||
end,
|
||||
0x09_u8 => Microcode.new do |d| # drop
|
||||
d.reg_tmp = d.get_memory({{ 32_u16 * 1024 }} + d.reg_sp - 1)
|
||||
d.set_memory({{ 32_u16 * 1024 }} + d.reg_sp - 1, 0_u16)
|
||||
d.reg_sp -= 1 if d.reg_sp.positive?
|
||||
end,
|
||||
0x0a_u8 => Microcode.new do |d| # pick
|
||||
d.reg_tmp = d.get_memory({{ 32_u16 * 1024 }} + d.reg_sp - 1 - d.reg_tmp)
|
||||
end,
|
||||
0x0b_u8 => Microcode.new do |d| # alu
|
||||
d.alu((d.reg_mccarg & 0xff).to_u8)
|
||||
end,
|
||||
0x0c_u8 => Microcode.new do |d| # load
|
||||
d.reg_tmp = d.get_memory(d.reg_tmp)
|
||||
end,
|
||||
0x0d_u8 => Microcode.new do |d| # store
|
||||
d.set_memory(d.reg_tmp, d.reg_a)
|
||||
end,
|
||||
0x0e_u8 => Microcode.new do |d| # print
|
||||
print d.reg_tmp
|
||||
end,
|
||||
0x0f_u8 => Microcode.new do |d| # emit
|
||||
print String.new(Bytes[d.reg_tmp])
|
||||
end,
|
||||
} of UInt8 => Microcode
|
||||
MICROCODE_NAMES = {
|
||||
"nop" => 0x00_u8,
|
||||
"dbg" => 0x01_u8,
|
||||
"jmp" => 0x02_u8,
|
||||
"jif" => 0x03_u8,
|
||||
"tmpl" => 0x04_u8,
|
||||
"tmplc" => 0x05_u8,
|
||||
"tmps" => 0x06_u8,
|
||||
"tmpsr" => 0x07_u8,
|
||||
"push" => 0x08_u8,
|
||||
"drop" => 0x09_u8,
|
||||
"pick" => 0x0a_u8,
|
||||
"alu" => 0x0b_u8,
|
||||
"load" => 0x0c_u8,
|
||||
"store" => 0x0d_u8,
|
||||
"print" => 0x0e_u8,
|
||||
"emit" => 0x0f_u8,
|
||||
} of String => UInt8
|
||||
end
|
||||
end
|
9
src/hence/opcode.cr
Normal file
9
src/hence/opcode.cr
Normal file
|
@ -0,0 +1,9 @@
|
|||
module Hence
|
||||
struct Opcode
|
||||
property name
|
||||
property microcode
|
||||
|
||||
def initialize(@name : String, @microcode : Parser::AST)
|
||||
end
|
||||
end
|
||||
end
|
361
src/hence/parser.cr
Normal file
361
src/hence/parser.cr
Normal file
|
@ -0,0 +1,361 @@
|
|||
require "string_scanner"
|
||||
|
||||
module Hence
|
||||
module Parser
|
||||
extend self
|
||||
|
||||
def parse(source : String) : AST
|
||||
s = StringScanner.new(source.gsub(/;.*|@.*|#.*/, nil))
|
||||
body = [] of AST::Node
|
||||
loop do
|
||||
s.skip(/\s*/)
|
||||
break if s.eos?
|
||||
|
||||
body <<
|
||||
if constant_dirty_name = s.scan(/\S+[ \t]*=/)
|
||||
constant_name = constant_dirty_name[..-2].rstrip
|
||||
|
||||
s.skip(/[ \t]*/)
|
||||
|
||||
constant_values = parse_args(s).not_nil!
|
||||
raise "Constant can't have more than one value" if constant_values.size > 1
|
||||
|
||||
AST::ConstantDeclarationNode.new(constant_name, constant_values.first)
|
||||
elsif label_dirty_name = s.scan(/\S+:/)
|
||||
label_name = label_dirty_name.not_nil![..-2]
|
||||
raise "Label name must be lowercase" if label_name != label_name.downcase
|
||||
|
||||
AST::LabelNode.new(label_name)
|
||||
# else
|
||||
# call_name = s.scan(/\S+/).not_nil!
|
||||
|
||||
# s.skip(/[ \t]*/)
|
||||
|
||||
# if call_name.starts_with?('.')
|
||||
# AST::MacroCallNode.new(call_name[1..], self.parse_args(s) || [] of AST::Arg)
|
||||
# else
|
||||
# call_args = self.parse_args(s)
|
||||
# raise "Op code can't be called with more than one argument" if call_args && call_args.size > 1
|
||||
|
||||
# AST::CallNode.new(call_name, call_args.try(&.first?))
|
||||
# end
|
||||
elsif macro_name_raw = s.scan(/\.\S+/)
|
||||
s.skip(/[ \t]*/)
|
||||
|
||||
AST::MacroCallNode.new(macro_name_raw[1..], parse_args(s) || [] of AST::Arg)
|
||||
else
|
||||
call_name = s.scan(/\S+/).not_nil!
|
||||
|
||||
s.skip(/[ \t]*/)
|
||||
|
||||
call_args = parse_args(s) || [] of AST::Arg
|
||||
raise "Op code can't be called with more than one argument" if call_args && call_args.size > 1
|
||||
|
||||
AST::CallNode.new(call_name, call_args.first?)
|
||||
end
|
||||
end
|
||||
|
||||
AST.new(body)
|
||||
end
|
||||
|
||||
def parse_binary_operation(op : String) : AST::BinaryOperation
|
||||
case op
|
||||
when "+"
|
||||
AST::AddBinaryOperation
|
||||
when "-"
|
||||
AST::SubBinaryOperation
|
||||
when "*"
|
||||
AST::MulBinaryOperation
|
||||
when "/"
|
||||
AST::DivBinaryOperation
|
||||
when "**"
|
||||
AST::PowBinaryOperation
|
||||
else
|
||||
raise "Unexpected binary operation: #{op}"
|
||||
end.new
|
||||
end
|
||||
|
||||
def parse_binary_expression_arg(scanner : StringScanner) : AST::BinaryExpressionArg?
|
||||
scanner.skip(/[ \t]*/)
|
||||
return unless scanner.scan(/\(/)
|
||||
|
||||
content = scanner.scan_until(/\)/).not_nil![..-2].split(/\s/, remove_empty: true)
|
||||
raise "Malformed binary expression" if content.size < 3 || content.size % 2 == 0
|
||||
|
||||
first_args = content.shift(3)
|
||||
bin_expr = AST::BinaryExpressionArg.new(
|
||||
parse_args(first_args[0] + "\n").not_nil!.first,
|
||||
parse_args(first_args[2] + "\n").not_nil!.first,
|
||||
parse_binary_operation(first_args[1])
|
||||
)
|
||||
|
||||
until content.empty?
|
||||
args = content.shift(2).not_nil!
|
||||
bin_expr = AST::BinaryExpressionArg.new(
|
||||
bin_expr,
|
||||
parse_args(args[1] + "\n").not_nil!.first,
|
||||
parse_binary_operation(args[0])
|
||||
)
|
||||
end
|
||||
|
||||
bin_expr
|
||||
end
|
||||
|
||||
def parse_binary_expression_arg(string : String) : AST::BinaryExpressionArg?
|
||||
parse_binary_expression_arg(StringScanner.new(string))
|
||||
end
|
||||
|
||||
def parse_args(scanner : StringScanner) : Array(AST::Arg)?
|
||||
args_s = scanner.scan_until(/\n/)
|
||||
|
||||
if args_s.try(&.blank?)
|
||||
[] of AST::Arg
|
||||
else
|
||||
s = StringScanner.new(args_s.not_nil!.strip)
|
||||
args = [] of AST::Arg
|
||||
|
||||
until s.eos?
|
||||
s.skip(/[ \t]*/)
|
||||
raise "Can't seperate arguments because of no comma" if s.skip(/,/).nil? && args.size > 0
|
||||
s.skip(/[ \t]*/)
|
||||
|
||||
args <<
|
||||
if s.check(/\(/)
|
||||
parse_binary_expression_arg(s).not_nil!
|
||||
elsif string_raw = s.scan(/".*"/)
|
||||
AST::StringArg.new(string_raw[1..-2].gsub(/\\n/, '\n'))
|
||||
elsif number = s.scan(/\d[\d\w]*/)
|
||||
AST::NumberArg.new(number.to_u16(prefix: true, underscore: true, strict: true))
|
||||
else
|
||||
AST::ConstantArg.new(s.scan(/\w+/).not_nil!)
|
||||
end
|
||||
end
|
||||
|
||||
args
|
||||
end
|
||||
end
|
||||
|
||||
def parse_args(string : String) : Array(AST::Arg)?
|
||||
parse_args(StringScanner.new(string))
|
||||
end
|
||||
|
||||
struct AST
|
||||
property body
|
||||
|
||||
def initialize(@body : Body)
|
||||
end
|
||||
|
||||
def to_s : String
|
||||
@body.map(&.to_s).join('\n')
|
||||
end
|
||||
|
||||
abstract struct Node
|
||||
abstract def to_s : String
|
||||
end
|
||||
|
||||
alias Body = Array(Node)
|
||||
|
||||
struct LabelNode < Node
|
||||
property name
|
||||
|
||||
def initialize(@name : String)
|
||||
end
|
||||
|
||||
def to_s : String
|
||||
"#{@name}:"
|
||||
end
|
||||
end
|
||||
|
||||
abstract class Arg
|
||||
abstract def resolve(data : Assembler::Data) : Bytes
|
||||
|
||||
abstract def resolve_as_number(data : Assembler::Data) : UInt16
|
||||
|
||||
abstract def to_s : String
|
||||
end
|
||||
|
||||
class NumberArg < Arg
|
||||
property number
|
||||
|
||||
def initialize(@number : UInt16)
|
||||
end
|
||||
|
||||
def resolve(_data : Assembler::Data) : Bytes
|
||||
n = Utils.split_uint16(@number)
|
||||
Bytes[n[0], n[1]]
|
||||
end
|
||||
|
||||
def resolve_as_number(_data : Assembler::Data) : UInt16
|
||||
@number
|
||||
end
|
||||
|
||||
def to_s : String
|
||||
@number.to_s
|
||||
end
|
||||
end
|
||||
|
||||
class ConstantArg < Arg
|
||||
property name
|
||||
|
||||
def initialize(@name : String)
|
||||
end
|
||||
|
||||
def resolve(data : Assembler::Data) : Bytes
|
||||
data.constants[@name].resolve(data)
|
||||
end
|
||||
|
||||
def resolve_as_number(data : Assembler::Data) : UInt16
|
||||
data.constants[@name].resolve_as_number(data)
|
||||
end
|
||||
|
||||
def to_s : String
|
||||
@name
|
||||
end
|
||||
end
|
||||
|
||||
class StringArg < Arg
|
||||
property string
|
||||
|
||||
def initialize(@string : String)
|
||||
end
|
||||
|
||||
def resolve(_data : Assembler::Data) : Bytes
|
||||
if @string.bytesize == 1
|
||||
Bytes[0_u8, @string.bytes[0]]
|
||||
else
|
||||
Bytes.new(@string.bytes.to_unsafe, @string.bytesize)
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_as_number(_data : Assembler::Data) : UInt16
|
||||
slice = resolve(_data)
|
||||
|
||||
Utils.merge_uint16(slice[0], slice[1])
|
||||
end
|
||||
|
||||
def to_s : String
|
||||
"\"#{@string}\""
|
||||
end
|
||||
end
|
||||
|
||||
abstract struct BinaryOperation
|
||||
abstract def resolve(left : Arg, right : Arg, data : Assembler::Data) : Arg
|
||||
|
||||
abstract def to_s : String
|
||||
end
|
||||
|
||||
struct AddBinaryOperation < BinaryOperation
|
||||
def resolve(left : Arg, right : Arg, data : Assembler::Data) : Arg
|
||||
NumberArg.new(left.resolve_as_number(data) &+ right.resolve_as_number(data))
|
||||
end
|
||||
|
||||
def to_s : String
|
||||
"+"
|
||||
end
|
||||
end
|
||||
|
||||
struct SubBinaryOperation < BinaryOperation
|
||||
def resolve(left : Arg, right : Arg, data : Assembler::Data) : Arg
|
||||
NumberArg.new(left.resolve_as_number(data) &- right.resolve_as_number(data))
|
||||
end
|
||||
|
||||
def to_s : String
|
||||
"-"
|
||||
end
|
||||
end
|
||||
|
||||
struct MulBinaryOperation < BinaryOperation
|
||||
def resolve(left : Arg, right : Arg, data : Assembler::Data) : Arg
|
||||
NumberArg.new(left.resolve_as_number(data) &* right.resolve_as_number(data))
|
||||
end
|
||||
|
||||
def to_s : String
|
||||
"*"
|
||||
end
|
||||
end
|
||||
|
||||
struct DivBinaryOperation < BinaryOperation
|
||||
def resolve(left : Arg, right : Arg, data : Assembler::Data) : Arg
|
||||
NumberArg.new(left.resolve_as_number(data) // right.resolve_as_number(data))
|
||||
end
|
||||
|
||||
def to_s : String
|
||||
"/"
|
||||
end
|
||||
end
|
||||
|
||||
struct PowBinaryOperation < BinaryOperation
|
||||
def resolve(left : Arg, right : Arg, data : Assembler::Data) : Arg
|
||||
NumberArg.new(left.resolve_as_number(data) &** right.resolve_as_number(data))
|
||||
end
|
||||
|
||||
def to_s : String
|
||||
"**"
|
||||
end
|
||||
end
|
||||
|
||||
class BinaryExpressionArg < Arg
|
||||
property left
|
||||
property right
|
||||
property op
|
||||
|
||||
def initialize(@left : Arg, @right : Arg, @op : BinaryOperation)
|
||||
end
|
||||
|
||||
def resolve(data : Assembler::Data) : Bytes
|
||||
@op.resolve(@left, @right, data).resolve(data)
|
||||
end
|
||||
|
||||
def resolve_as_number(data : Assembler::Data) : UInt16
|
||||
@op.resolve(@left, @right, data).resolve_as_number(data)
|
||||
end
|
||||
|
||||
def to_s : String
|
||||
# ameba:disable Lint/RedundantStringCoercion
|
||||
"(#{@left.to_s} #{@op.to_s} #{@right.to_s})"
|
||||
end
|
||||
end
|
||||
|
||||
struct ConstantDeclarationNode < Node
|
||||
property name
|
||||
property value
|
||||
|
||||
def initialize(@name : String, @value : Arg)
|
||||
end
|
||||
|
||||
def to_s : String
|
||||
"#{@name}"
|
||||
end
|
||||
end
|
||||
|
||||
struct MacroCallNode < Node
|
||||
property name
|
||||
property args
|
||||
|
||||
def initialize(@name : String, @args : Array(Arg))
|
||||
end
|
||||
|
||||
def to_s : String
|
||||
".#{@name}"
|
||||
end
|
||||
end
|
||||
|
||||
struct CallNode < Node
|
||||
property name
|
||||
property arg
|
||||
|
||||
def initialize(@name : String, @arg : Arg? = nil)
|
||||
end
|
||||
|
||||
def to_s : String
|
||||
if @arg
|
||||
# ameba:disable Lint/RedundantStringCoercion
|
||||
"#{@name} #{@arg.to_s}"
|
||||
else
|
||||
@name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
16
src/hence/utils.cr
Normal file
16
src/hence/utils.cr
Normal file
|
@ -0,0 +1,16 @@
|
|||
module Hence
|
||||
module Utils
|
||||
extend self
|
||||
|
||||
macro split_uint16(x)
|
||||
{
|
||||
({{ x }} >> 8).to_u8,
|
||||
({{ x }} & 0xff_u8).to_u8
|
||||
}
|
||||
end
|
||||
|
||||
macro merge_uint16(x, y)
|
||||
({{ x }}.to_u16 << 8) | {{ y }}
|
||||
end
|
||||
end
|
||||
end
|
5
src/hence/version.cr
Normal file
5
src/hence/version.cr
Normal file
|
@ -0,0 +1,5 @@
|
|||
require "version_from_shard"
|
||||
|
||||
module Hence
|
||||
VersionFromShard.declare
|
||||
end
|
Loading…
Reference in a new issue