This commit is contained in:
Dominic Grimm 2022-06-17 13:38:12 +02:00
commit 6ef300f402
25 changed files with 2280 additions and 0 deletions

9
.editorconfig Normal file
View 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
View file

@ -0,0 +1,8 @@
/docs/
/lib/
/bin/
/.shards/
*.dwarf
./examples/*.bin
examples/*.bin
/.vscode/

21
LICENSE Normal file
View 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
View file

@ -0,0 +1,3 @@
# hence
A stack based CPU with microcode and custom firmware support.

44
examples/add.asm Normal file
View 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
View 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
View file

@ -0,0 +1,2 @@
\usepackage{sectsty}
\sectionfont{\underline\clearpage}

19
pdf/pdf.sh Normal file
View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1 @@
require "./hence/*"

83
src/hence/assembler.cr Normal file
View 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
View 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
View 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

View 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._

View 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

View 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

View 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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1,5 @@
require "version_from_shard"
module Hence
VersionFromShard.declare
end