Initial commit (from Logisim-Assembler)
This commit is contained in:
139
.clang-format
Executable file
139
.clang-format
Executable file
@ -0,0 +1,139 @@
|
||||
---
|
||||
BasedOnStyle: LLVM
|
||||
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
|
||||
# Don't vertically align too much to not fuck with VC
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments: None
|
||||
AlignConsecutiveBitFields: None
|
||||
AlignConsecutiveDeclarations: None
|
||||
AlignConsecutiveMacros: None
|
||||
AlignEscapedNewlines: DontAlign
|
||||
|
||||
AlignOperands: AlignAfterOperator
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
|
||||
# Allow single line stuff
|
||||
AllowShortBlocksOnASingleLine: Always
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
|
||||
# Don't force single line for each
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
|
||||
BitFieldColonSpacing: Both
|
||||
BreakBeforeBinaryOperators: None
|
||||
|
||||
# Braces completely attached, should be the same as BreakBeforeBraces: Attach
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: false
|
||||
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakStringLiterals: false
|
||||
ColumnLimit: 0
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerIndentWidth: 2
|
||||
ContinuationIndentWidth: 2
|
||||
Cpp11BracedListStyle: true
|
||||
|
||||
# Force my style onto everything :)
|
||||
DeriveLineEnding: false
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: Always
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
IncludeBlocks: Merge
|
||||
IndentAccessModifiers: false # don't indent, use AccessModifierOffset instead
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: false
|
||||
IndentExternBlock: NoIndent
|
||||
IndentGotoLabels: false
|
||||
IndentPPDirectives: None
|
||||
IndentRequiresClause: false
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertBraces: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
LambdaBodyIndentation: Signature
|
||||
Language: Cpp
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
PackConstructorInitializers: BinPack
|
||||
PointerAlignment: Right
|
||||
PPIndentWidth: -1 # use IndentWidth
|
||||
QualifierAlignment: Leave # Don't mess with const int const *ptr;
|
||||
ReferenceAlignment: Right
|
||||
ReflowComments: false
|
||||
# TODO: RemoveBracesLLVM: false
|
||||
RequiresClausePosition: OwnLine
|
||||
SeparateDefinitionBlocks: Always
|
||||
ShortNamespaceLines: 0
|
||||
SortIncludes: CaseInsensitive
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: Never
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: false
|
||||
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: 1
|
||||
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: c++20
|
||||
TabWidth: 4
|
||||
UseCRLF: false
|
||||
UseTab: Never
|
||||
|
||||
...
|
2
.clang-tidy
Normal file
2
.clang-tidy
Normal file
@ -0,0 +1,2 @@
|
||||
Checks: "-*,bugprone-*,clang-analyzer-*,concurrency-*,cppcoreguidelines-*,misc-*,modernize-*,performance-*,portability-*,readability-*"
|
||||
FormatStyle: file
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.direnv
|
||||
cmake-build-debug
|
28
CMakeLists.txt
Normal file
28
CMakeLists.txt
Normal file
@ -0,0 +1,28 @@
|
||||
cmake_minimum_required(VERSION 3.25)
|
||||
project(LogisimAssembler)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
find_package(Boost 1.81 COMPONENTS program_options REQUIRED)
|
||||
|
||||
add_executable(lasm
|
||||
src/main.cpp
|
||||
src/lexer/Token.cpp
|
||||
src/lexer/Lexer.cpp
|
||||
src/parser/Parser.cpp
|
||||
|
||||
src/ast/Node.cpp
|
||||
src/ast/nodes/RootNode.cpp
|
||||
src/ast/nodes/ConstNode.cpp
|
||||
src/ast/nodes/MovNode.cpp
|
||||
src/ast/nodes/AluNode.cpp
|
||||
src/ast/nodes/JumpNode.cpp
|
||||
|
||||
src/ast/Observer.cpp
|
||||
src/ast/PrefixObserver.cpp
|
||||
src/ast/PostfixObserver.cpp
|
||||
src/codegen/PrintObserver.cpp
|
||||
src/codegen/CodegenObserver.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(lasm Boost::program_options)
|
30
README.md
Normal file
30
README.md
Normal file
@ -0,0 +1,30 @@
|
||||
# README
|
||||
|
||||
Small assembler that generates SystemVerilog ROM modules for [this](https://gitlab.com/ChUrl/quartus-8-bit-cpu).
|
||||
|
||||
## Usage
|
||||
|
||||
`svrasm -i <inputfile> -o <outputfile>`
|
||||
|
||||
## Instructionset
|
||||
|
||||
| Instruction | 1. Operand | 2. Operand | Note |
|
||||
|-------------|-----------------------------|-----------------|----------------------------------------|
|
||||
| MOV | Constant or Source Register | Target Register | |
|
||||
| AND | NONE | NONE | Works on reg1 and reg2, result in reg3 |
|
||||
| OR | NONE | NONE | Works on reg1 and reg2, result in reg3 |
|
||||
| NAND | NONE | NONE | Works on reg1 and reg2, result in reg3 |
|
||||
| NOR | NONE | NONE | Works on reg1 and reg2, result in reg3 |
|
||||
| ADD | NONE | NONE | Works on reg1 and reg2, result in reg3 |
|
||||
| SUB | NONE | NONE | Works on reg1 and reg2, result in reg3 |
|
||||
| JMP | NONE | NONE | Works on reg3 |
|
||||
| JEQ | NONE | NONE | Works on reg3 |
|
||||
| JLE | NONE | NONE | Works on reg3 |
|
||||
| JLEQ | NONE | NONE | Works on reg3 |
|
||||
| NOP | NONE | NONE | Works on reg3 |
|
||||
| JNEQ | NONE | NONE | Works on reg3 |
|
||||
| JGR | NONE | NONE | Works on reg3 |
|
||||
| JGEQ | NONE | NONE | Works on reg3 |
|
||||
|
||||
Line comments are recognized, indicated by `#`.
|
||||
|
94
flake.lock
generated
Normal file
94
flake.lock
generated
Normal file
@ -0,0 +1,94 @@
|
||||
{
|
||||
"nodes": {
|
||||
"devshell": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1678957337,
|
||||
"narHash": "sha256-Gw4nVbuKRdTwPngeOZQOzH/IFowmz4LryMPDiJN/ah4=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "3e0e60ab37cd0bf7ab59888f5c32499d851edb47",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1642700792,
|
||||
"narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "846b2ae0fc4cc943637d3d1def4454213e203cba",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"locked": {
|
||||
"lastModified": 1678901627,
|
||||
"narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1677383253,
|
||||
"narHash": "sha256-UfpzWfSxkfXHnb4boXZNaKsAcUrZT9Hw+tao1oZxd08=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9952d6bc395f5841262b006fbace8dd7e143b634",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1679281263,
|
||||
"narHash": "sha256-neMref1GTruSLt1jBgAw+lvGsZj8arQYfdxvSi5yp4Q=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "8276a165b9fa3db1a7a4f29ee29b680e0799b9dc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devshell": "devshell",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
117
flake.nix
Normal file
117
flake.nix
Normal file
@ -0,0 +1,117 @@
|
||||
{
|
||||
description = "Logisim Assembler Development Environment";
|
||||
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
inputs.devshell.url = "github:numtide/devshell";
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
flake-utils,
|
||||
devshell,
|
||||
}:
|
||||
flake-utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
config.allowUnfree = true; # For clion
|
||||
overlays = [devshell.overlays.default];
|
||||
};
|
||||
|
||||
# NOTE: Usual 64 bit compilers that don't collide
|
||||
bintools = pkgs.wrapBintoolsWith {
|
||||
bintools = pkgs.bintools.bintools;
|
||||
libc = pkgs.glibc;
|
||||
};
|
||||
gcc12 = pkgs.hiPrio (pkgs.wrapCCWith {
|
||||
cc = pkgs.gcc12.cc;
|
||||
libc = pkgs.glibc;
|
||||
bintools = bintools;
|
||||
});
|
||||
clang15 = pkgs.wrapCCWith {
|
||||
cc = pkgs.clang_15.cc;
|
||||
libc = pkgs.glibc;
|
||||
bintools = bintools;
|
||||
};
|
||||
|
||||
# NOTE: Multilib compilers that don't collide
|
||||
bintools_multi = pkgs.wrapBintoolsWith {
|
||||
bintools = pkgs.bintools.bintools; # Get the unwrapped bintools from the wrapper
|
||||
libc = pkgs.glibc_multi;
|
||||
};
|
||||
gcc12_multi = pkgs.hiPrio (pkgs.wrapCCWith {
|
||||
cc = pkgs.gcc12.cc; # Get the unwrapped gcc from the wrapper
|
||||
libc = pkgs.glibc_multi;
|
||||
bintools = bintools_multi;
|
||||
});
|
||||
clang15_multi = pkgs.wrapCCWith {
|
||||
cc = pkgs.clang_15.cc;
|
||||
libc = pkgs.glibc_multi;
|
||||
bintools = bintools_multi;
|
||||
};
|
||||
in {
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
# Compilers
|
||||
bintools
|
||||
gcc12
|
||||
clang15
|
||||
# bintools_multi
|
||||
# gcc12_multi
|
||||
# clang14_multi
|
||||
|
||||
# Libraries
|
||||
boost181
|
||||
|
||||
# Native buildinputs
|
||||
gnumake
|
||||
cmake
|
||||
# nasm
|
||||
|
||||
# Development
|
||||
# bear # To generate compilation database
|
||||
gdb
|
||||
cling # To try out my bullshit implementations
|
||||
# doxygen # Generate docs + graphs
|
||||
];
|
||||
};
|
||||
|
||||
# TODO: DevShell doesn't propagate buildinputs, so its difficult to find e.g. Boost...
|
||||
# # devShell = pkgs.devshell.mkShell ...
|
||||
# devShell = pkgs.devshell.mkShell {
|
||||
# name = "Logisim Assembler Development Environment";
|
||||
|
||||
# packages = with pkgs; [
|
||||
# # Compilers
|
||||
# bintools
|
||||
# gcc12
|
||||
# clang15
|
||||
# # bintools_multi
|
||||
# # gcc12_multi
|
||||
# # clang14_multi
|
||||
|
||||
# # Libraries
|
||||
# boost181
|
||||
|
||||
# # Native buildinputs
|
||||
# gnumake
|
||||
# cmake
|
||||
# # nasm
|
||||
|
||||
# # Development
|
||||
# # bear # To generate compilation database
|
||||
# gdb
|
||||
# cling # To try out my bullshit implementations
|
||||
# # doxygen # Generate docs + graphs
|
||||
# ];
|
||||
|
||||
# commands = [
|
||||
# {
|
||||
# name = "ide";
|
||||
# help = "Run clion for project";
|
||||
# command = "clion &>/dev/null ./ &";
|
||||
# }
|
||||
# ];
|
||||
# };
|
||||
});
|
||||
}
|
17
programs/add_and_jump.cpu8_v1
Normal file
17
programs/add_and_jump.cpu8_v1
Normal file
@ -0,0 +1,17 @@
|
||||
v3.0 hex words addressed
|
||||
00: 05 81 0a 82 44 99 0f 82 45 00 c1 00 00 00 00 00
|
||||
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
13
programs/add_and_jump.lasm8_v1
Normal file
13
programs/add_and_jump.lasm8_v1
Normal file
@ -0,0 +1,13 @@
|
||||
# Add 5 + 10
|
||||
MOV 5, reg1
|
||||
MOV 10, reg2
|
||||
ADD
|
||||
|
||||
# Subtract result - 15
|
||||
MOV reg3, reg1
|
||||
MOV 15, reg2
|
||||
SUB
|
||||
|
||||
# Jump to 0 if result == 15
|
||||
MOV 0, reg0
|
||||
JEQ
|
17
programs/counting.cpu8_v1
Normal file
17
programs/counting.cpu8_v1
Normal file
@ -0,0 +1,17 @@
|
||||
v3.0 hex words addressed
|
||||
00: 30 86 31 86 32 86 33 86 34 86 35 86 36 86 37 86
|
||||
10: 38 86 39 86 00 c4 00 00 00 00 00 00 00 00 00 00
|
||||
20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
13
programs/counting.lasm8_v1
Normal file
13
programs/counting.lasm8_v1
Normal file
@ -0,0 +1,13 @@
|
||||
# Write "0123456789..." in ASCII to the output
|
||||
MOV 48, output
|
||||
MOV 49, output
|
||||
MOV 50, output
|
||||
MOV 51, output
|
||||
MOV 52, output
|
||||
MOV 53, output
|
||||
MOV 54, output
|
||||
MOV 55, output
|
||||
MOV 56, output
|
||||
MOV 57, output
|
||||
MOV 0, reg0
|
||||
JMP
|
17
programs/input_output.cpu8_v1
Normal file
17
programs/input_output.cpu8_v1
Normal file
@ -0,0 +1,17 @@
|
||||
v3.0 hex words addressed
|
||||
00: b1 0a 82 44 9e 00 00 00 00 00 00 00 00 00 00 00
|
||||
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
5
programs/input_output.lasm8_v1
Normal file
5
programs/input_output.lasm8_v1
Normal file
@ -0,0 +1,5 @@
|
||||
# Add 10 to the input number, then output the result
|
||||
MOV input, reg1
|
||||
MOV 10, reg2
|
||||
ADD
|
||||
MOV reg3, output
|
17
programs/nop_and_jump.cpu8_v1
Normal file
17
programs/nop_and_jump.cpu8_v1
Normal file
@ -0,0 +1,17 @@
|
||||
v3.0 hex words addressed
|
||||
00: c0 00 c4 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
4
programs/nop_and_jump.lasm8_v1
Normal file
4
programs/nop_and_jump.lasm8_v1
Normal file
@ -0,0 +1,4 @@
|
||||
# Endless NOP loop
|
||||
NOP
|
||||
MOV 0, reg0
|
||||
JMP
|
13
src/ast/Node.cpp
Normal file
13
src/ast/Node.cpp
Normal file
@ -0,0 +1,13 @@
|
||||
//
|
||||
// Created by christoph on 20.03.23.
|
||||
//
|
||||
|
||||
#include "Node.h"
|
||||
|
||||
void Node::addChild(std::unique_ptr<Node> child) {
|
||||
children.push_back(std::move(child));
|
||||
}
|
||||
|
||||
auto Node::getChildren() const -> const std::vector<std::unique_ptr<Node>> & {
|
||||
return children;
|
||||
}
|
49
src/ast/Node.h
Normal file
49
src/ast/Node.h
Normal file
@ -0,0 +1,49 @@
|
||||
//
|
||||
// Created by christoph on 20.03.23.
|
||||
//
|
||||
|
||||
#ifndef LOGISIMASSEMBLER_NODE_H
|
||||
#define LOGISIMASSEMBLER_NODE_H
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "../lexer/Token.h"
|
||||
|
||||
class Node {
|
||||
public:
|
||||
Node() = default;
|
||||
|
||||
Node(const Node ©) = default;
|
||||
|
||||
auto operator=(const Node ©) -> Node & = default;
|
||||
|
||||
Node(Node &&move) = default;
|
||||
|
||||
auto operator=(Node &&move) -> Node & = default;
|
||||
|
||||
virtual ~Node() = default;
|
||||
|
||||
void addChild(std::unique_ptr<Node> child);
|
||||
|
||||
// TODO: For more complex instructions, compile needs to return a vector<uint8_t>
|
||||
// TODO: In this case, the Observer may not traverse all nodes...
|
||||
// The Observer is the wrong choice for compilation.
|
||||
// I can just call compile on the root, and the root compiles its children.
|
||||
[[nodiscard]] virtual auto compile() const -> uint8_t = 0;
|
||||
|
||||
[[nodiscard]] auto getChildren() const -> const std::vector<std::unique_ptr<Node>> &;
|
||||
|
||||
protected:
|
||||
enum Operation : uint8_t {
|
||||
CONSTANT,
|
||||
ALU,
|
||||
COPY,
|
||||
CONDITION
|
||||
};
|
||||
|
||||
protected:
|
||||
// TODO: Currently the AST degrades to a list, but someday we'll need a real tree
|
||||
std::vector<std::unique_ptr<Node>> children;
|
||||
};
|
||||
|
||||
#endif //LOGISIMASSEMBLER_NODE_H
|
11
src/ast/Observer.cpp
Normal file
11
src/ast/Observer.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
//
|
||||
// Created by christoph on 21.03.23.
|
||||
//
|
||||
|
||||
#include "Observer.h"
|
||||
|
||||
Observer::Observer(const Node &root) : root(root) {}
|
||||
|
||||
void Observer::traverse() {
|
||||
traverse(root);
|
||||
}
|
28
src/ast/Observer.h
Normal file
28
src/ast/Observer.h
Normal file
@ -0,0 +1,28 @@
|
||||
//
|
||||
// Created by christoph on 21.03.23.
|
||||
//
|
||||
|
||||
#ifndef LOGISIMASSEMBLER_OBSERVER_H
|
||||
#define LOGISIMASSEMBLER_OBSERVER_H
|
||||
|
||||
#include "Node.h"
|
||||
#include <memory>
|
||||
|
||||
class Observer {
|
||||
public:
|
||||
Observer(const Node &root);
|
||||
|
||||
virtual ~Observer() = default;
|
||||
|
||||
void traverse();
|
||||
|
||||
protected:
|
||||
virtual void traverse(const Node &node) = 0;
|
||||
|
||||
virtual void action(const Node &node) = 0;
|
||||
|
||||
private:
|
||||
const Node &root;
|
||||
};
|
||||
|
||||
#endif //LOGISIMASSEMBLER_OBSERVER_H
|
18
src/ast/PostfixObserver.cpp
Normal file
18
src/ast/PostfixObserver.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
//
|
||||
// Created by christoph on 20.03.23.
|
||||
//
|
||||
|
||||
#include "PostfixObserver.h"
|
||||
|
||||
PostfixObserver::PostfixObserver(const Node &root) : Observer(root) {}
|
||||
|
||||
// TODO: Shouldn't be recursive
|
||||
void PostfixObserver::traverse(const Node &node) {
|
||||
for (const auto &child : node.getChildren()) {
|
||||
depth++;
|
||||
traverse(*child);
|
||||
depth--;
|
||||
}
|
||||
|
||||
action(node);
|
||||
}
|
23
src/ast/PostfixObserver.h
Normal file
23
src/ast/PostfixObserver.h
Normal file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// Created by christoph on 20.03.23.
|
||||
//
|
||||
|
||||
#ifndef LOGISIMASSEMBLER_POSTFIXOBSERVER_H
|
||||
#define LOGISIMASSEMBLER_POSTFIXOBSERVER_H
|
||||
|
||||
#include "Observer.h"
|
||||
|
||||
class PostfixObserver : public Observer {
|
||||
public:
|
||||
PostfixObserver(const Node &root);
|
||||
|
||||
~PostfixObserver() override = default;
|
||||
|
||||
protected:
|
||||
void traverse(const Node &node) override;
|
||||
|
||||
protected:
|
||||
uint32_t depth = 0;
|
||||
};
|
||||
|
||||
#endif //LOGISIMASSEMBLER_PREFIXOBSERVER_H
|
18
src/ast/PrefixObserver.cpp
Normal file
18
src/ast/PrefixObserver.cpp
Normal file
@ -0,0 +1,18 @@
|
||||
//
|
||||
// Created by christoph on 20.03.23.
|
||||
//
|
||||
|
||||
#include "PrefixObserver.h"
|
||||
|
||||
PrefixObserver::PrefixObserver(const Node &root) : Observer(root) {}
|
||||
|
||||
// TODO: Shouldn't be recursive
|
||||
void PrefixObserver::traverse(const Node &node) {
|
||||
action(node);
|
||||
|
||||
for (const auto &child : node.getChildren()) {
|
||||
depth++;
|
||||
traverse(*child);
|
||||
depth--;
|
||||
}
|
||||
}
|
23
src/ast/PrefixObserver.h
Normal file
23
src/ast/PrefixObserver.h
Normal file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// Created by christoph on 20.03.23.
|
||||
//
|
||||
|
||||
#ifndef LOGISIMASSEMBLER_PREFIXOBSERVER_H
|
||||
#define LOGISIMASSEMBLER_PREFIXOBSERVER_H
|
||||
|
||||
#include "Observer.h"
|
||||
|
||||
class PrefixObserver : public Observer {
|
||||
public:
|
||||
PrefixObserver(const Node &root);
|
||||
|
||||
~PrefixObserver() override = default;
|
||||
|
||||
protected:
|
||||
void traverse(const Node &node) override;
|
||||
|
||||
protected:
|
||||
uint32_t depth = 0;
|
||||
};
|
||||
|
||||
#endif //LOGISIMASSEMBLER_PREFIXOBSERVER_H
|
15
src/ast/nodes/AluNode.cpp
Normal file
15
src/ast/nodes/AluNode.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
//
|
||||
// Created by christoph on 21.03.23.
|
||||
//
|
||||
|
||||
#include "AluNode.h"
|
||||
|
||||
AluNode::AluNode(AluNode::AluOperation operation) : operation(operation) {}
|
||||
|
||||
auto AluNode::compile() const -> uint8_t {
|
||||
if (operation > SUB) {
|
||||
throw "Compile Error: Invalid ALU Operation!";
|
||||
}
|
||||
|
||||
return (ALU & 0b11) << 6 | (operation & 0b111);
|
||||
}
|
32
src/ast/nodes/AluNode.h
Normal file
32
src/ast/nodes/AluNode.h
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// Created by christoph on 21.03.23.
|
||||
//
|
||||
|
||||
#ifndef LOGISIMASSEMBLER_ALUNODE_H
|
||||
#define LOGISIMASSEMBLER_ALUNODE_H
|
||||
|
||||
#include "../Node.h"
|
||||
|
||||
class AluNode : public Node {
|
||||
public:
|
||||
enum AluOperation : uint8_t {
|
||||
AND,
|
||||
OR,
|
||||
NAND,
|
||||
NOR,
|
||||
ADD,
|
||||
SUB
|
||||
};
|
||||
|
||||
public:
|
||||
AluNode(AluOperation operation);
|
||||
|
||||
~AluNode() override = default;
|
||||
|
||||
[[nodiscard]] auto compile() const -> uint8_t override;
|
||||
|
||||
private:
|
||||
AluOperation operation;
|
||||
};
|
||||
|
||||
#endif //LOGISIMASSEMBLER_ALUNODE_H
|
15
src/ast/nodes/ConstNode.cpp
Normal file
15
src/ast/nodes/ConstNode.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
//
|
||||
// Created by christoph on 21.03.23.
|
||||
//
|
||||
|
||||
#include "ConstNode.h"
|
||||
|
||||
ConstNode::ConstNode(uint8_t value) : value(value) {}
|
||||
|
||||
auto ConstNode::compile() const -> uint8_t {
|
||||
if (value > 0b00111111) {
|
||||
throw "Compile Error: Constant too large!";
|
||||
}
|
||||
|
||||
return (CONSTANT & 0b11) << 6 | (value & 0b111111);
|
||||
}
|
22
src/ast/nodes/ConstNode.h
Normal file
22
src/ast/nodes/ConstNode.h
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// Created by christoph on 21.03.23.
|
||||
//
|
||||
|
||||
#ifndef LOGISIMASSEMBLER_CONSTNODE_H
|
||||
#define LOGISIMASSEMBLER_CONSTNODE_H
|
||||
|
||||
#include "../Node.h"
|
||||
|
||||
class ConstNode : public Node {
|
||||
public:
|
||||
ConstNode(uint8_t value);
|
||||
|
||||
~ConstNode() override = default;
|
||||
|
||||
[[nodiscard]] auto compile() const -> uint8_t override;
|
||||
|
||||
private:
|
||||
uint8_t value;
|
||||
};
|
||||
|
||||
#endif //LOGISIMASSEMBLER_CONSTNODE_H
|
11
src/ast/nodes/JumpNode.cpp
Normal file
11
src/ast/nodes/JumpNode.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
//
|
||||
// Created by christoph on 21.03.23.
|
||||
//
|
||||
|
||||
#include "JumpNode.h"
|
||||
|
||||
JumpNode::JumpNode(JumpNode::JumpOperation operation) : operation(operation) {}
|
||||
|
||||
uint8_t JumpNode::compile() const {
|
||||
return (CONDITION & 0b11) << 6 | (operation & 0b111);
|
||||
}
|
34
src/ast/nodes/JumpNode.h
Normal file
34
src/ast/nodes/JumpNode.h
Normal file
@ -0,0 +1,34 @@
|
||||
//
|
||||
// Created by christoph on 21.03.23.
|
||||
//
|
||||
|
||||
#ifndef LOGISIMASSEMBLER_JUMPNODE_H
|
||||
#define LOGISIMASSEMBLER_JUMPNODE_H
|
||||
|
||||
#include "../Node.h"
|
||||
|
||||
class JumpNode : public Node {
|
||||
public:
|
||||
enum JumpOperation : uint8_t {
|
||||
NEVER,
|
||||
EQUAL_ZERO,
|
||||
LESS_ZERO,
|
||||
LESS_EQUAL_ZERO,
|
||||
ALWAYS,
|
||||
NOT_ZERO,
|
||||
GREATER_ZERO,
|
||||
GREATER_EQUAL_ZERO
|
||||
};
|
||||
|
||||
public:
|
||||
JumpNode(JumpOperation operation);
|
||||
|
||||
~JumpNode() override = default;
|
||||
|
||||
[[nodiscard]] auto compile() const -> uint8_t override;
|
||||
|
||||
private:
|
||||
JumpOperation operation;
|
||||
};
|
||||
|
||||
#endif //LOGISIMASSEMBLER_JUMPNODE_H
|
11
src/ast/nodes/MovNode.cpp
Normal file
11
src/ast/nodes/MovNode.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
//
|
||||
// Created by christoph on 21.03.23.
|
||||
//
|
||||
|
||||
#include "MovNode.h"
|
||||
|
||||
MovNode::MovNode(uint8_t source, uint8_t target) : source(source), target(target) {}
|
||||
|
||||
auto MovNode::compile() const -> uint8_t {
|
||||
return (COPY & 0b11) << 6 | (source & 0b111) << 3 | (target & 0b111);
|
||||
}
|
23
src/ast/nodes/MovNode.h
Normal file
23
src/ast/nodes/MovNode.h
Normal file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// Created by christoph on 21.03.23.
|
||||
//
|
||||
|
||||
#ifndef LOGISIMASSEMBLER_MOVNODE_H
|
||||
#define LOGISIMASSEMBLER_MOVNODE_H
|
||||
|
||||
#include "../Node.h"
|
||||
|
||||
class MovNode : public Node {
|
||||
public:
|
||||
MovNode(uint8_t source, uint8_t target);
|
||||
|
||||
~MovNode() override = default;
|
||||
|
||||
[[nodiscard]] auto compile() const -> uint8_t override;
|
||||
|
||||
private:
|
||||
uint8_t source;
|
||||
uint8_t target;
|
||||
};
|
||||
|
||||
#endif //LOGISIMASSEMBLER_MOVNODE_H
|
9
src/ast/nodes/RootNode.cpp
Normal file
9
src/ast/nodes/RootNode.cpp
Normal file
@ -0,0 +1,9 @@
|
||||
//
|
||||
// Created by christoph on 21.03.23.
|
||||
//
|
||||
|
||||
#include "RootNode.h"
|
||||
|
||||
auto RootNode::compile() const -> uint8_t {
|
||||
return -1;
|
||||
}
|
19
src/ast/nodes/RootNode.h
Normal file
19
src/ast/nodes/RootNode.h
Normal file
@ -0,0 +1,19 @@
|
||||
//
|
||||
// Created by christoph on 21.03.23.
|
||||
//
|
||||
|
||||
#ifndef LOGISIMASSEMBLER_ROOTNODE_H
|
||||
#define LOGISIMASSEMBLER_ROOTNODE_H
|
||||
|
||||
#include "../Node.h"
|
||||
|
||||
class RootNode : public Node {
|
||||
public:
|
||||
RootNode() = default;
|
||||
|
||||
~RootNode() override = default;
|
||||
|
||||
[[nodiscard]] auto compile() const -> uint8_t override;
|
||||
};
|
||||
|
||||
#endif //LOGISIMASSEMBLER_ROOTNODE_H
|
24
src/codegen/CodegenObserver.cpp
Normal file
24
src/codegen/CodegenObserver.cpp
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// Created by christoph on 21.03.23.
|
||||
//
|
||||
|
||||
#include "CodegenObserver.h"
|
||||
#include <boost/format.hpp>
|
||||
#include <iostream>
|
||||
|
||||
CodegenObserver::CodegenObserver(const Node &node, std::vector<std::string> &output_string)
|
||||
: PostfixObserver(node), output_string(output_string) {}
|
||||
|
||||
void CodegenObserver::action(const Node &node) {
|
||||
const uint8_t dec = node.compile();
|
||||
const uint8_t INVALID = -1;
|
||||
|
||||
if (dec != INVALID) {
|
||||
// uint8_t is always interpreted as char, so cast to uint32_t
|
||||
const std::string hex = (boost::format("%x") % static_cast<uint32_t>(dec)).str();
|
||||
if (hex.empty() || hex.size() > 2) {
|
||||
throw "Compile Error: Resulting instruction has invalid size!";
|
||||
}
|
||||
output_string.push_back(hex.length() == 2 ? hex : "0" + hex);
|
||||
}
|
||||
}
|
23
src/codegen/CodegenObserver.h
Normal file
23
src/codegen/CodegenObserver.h
Normal file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// Created by christoph on 21.03.23.
|
||||
//
|
||||
|
||||
#ifndef LOGISIMASSEMBLER_CODEGENOBSERVER_H
|
||||
#define LOGISIMASSEMBLER_CODEGENOBSERVER_H
|
||||
|
||||
#include "../ast/PostfixObserver.h"
|
||||
|
||||
class CodegenObserver : public PostfixObserver {
|
||||
public:
|
||||
CodegenObserver(const Node &node, std::vector<std::string> &output_string);
|
||||
|
||||
~CodegenObserver() override = default;
|
||||
|
||||
protected:
|
||||
void action(const Node &node) override;
|
||||
|
||||
private:
|
||||
std::vector<std::string> &output_string;
|
||||
};
|
||||
|
||||
#endif //LOGISIMASSEMBLER_CODEGENOBSERVER_H
|
23
src/codegen/PrintObserver.cpp
Normal file
23
src/codegen/PrintObserver.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// Created by christoph on 21.03.23.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include "PrintObserver.h"
|
||||
|
||||
PrintObserver::PrintObserver(const Node &node) : PrefixObserver(node) {}
|
||||
|
||||
void PrintObserver::action(const Node &node) {
|
||||
// Print a simple indent guide
|
||||
std::string depth_padding(depth * 2, '|');
|
||||
if (depth > 0) {
|
||||
for (uint32_t i = 0; i < depth_padding.length(); ++i) {
|
||||
if (i % 2 == 1) {
|
||||
depth_padding[i] = ' ';
|
||||
}
|
||||
}
|
||||
depth_padding[(depth * 2) - 1] = '-';
|
||||
}
|
||||
|
||||
std::cout << depth_padding << typeid(node).name() << std::endl;
|
||||
}
|
20
src/codegen/PrintObserver.h
Normal file
20
src/codegen/PrintObserver.h
Normal file
@ -0,0 +1,20 @@
|
||||
//
|
||||
// Created by christoph on 21.03.23.
|
||||
//
|
||||
|
||||
#ifndef LOGISIMASSEMBLER_PRINTOBSERVER_H
|
||||
#define LOGISIMASSEMBLER_PRINTOBSERVER_H
|
||||
|
||||
#include "../ast/PrefixObserver.h"
|
||||
|
||||
class PrintObserver : public PrefixObserver {
|
||||
public:
|
||||
PrintObserver(const Node &node);
|
||||
|
||||
~PrintObserver() override = default;
|
||||
|
||||
protected:
|
||||
void action(const Node &node) override;
|
||||
};
|
||||
|
||||
#endif //LOGISIMASSEMBLER_PRINTOBSERVER_H
|
149
src/lexer/Lexer.cpp
Normal file
149
src/lexer/Lexer.cpp
Normal file
@ -0,0 +1,149 @@
|
||||
//
|
||||
// Created by christoph on 20.03.23.
|
||||
//
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include "Lexer.h"
|
||||
|
||||
// ! Helper Functions
|
||||
|
||||
auto is_whitespace(const char character) -> bool {
|
||||
const auto ascii_value = static_cast<uint8_t>(character);
|
||||
const uint8_t ascii_tab = 9;
|
||||
const uint8_t ascii_cr = 13;
|
||||
const uint8_t ascii_space = 32;
|
||||
|
||||
return (ascii_value >= ascii_tab && ascii_value <= ascii_cr)
|
||||
|| ascii_value == ascii_space;
|
||||
}
|
||||
|
||||
auto is_ignored(const char character) -> bool {
|
||||
// TODO: Any other ignored characters that could happen in the program?
|
||||
return character == ',';
|
||||
}
|
||||
|
||||
auto is_numeric(const char character) -> bool {
|
||||
const auto ascii_value = static_cast<uint8_t>(character);
|
||||
const uint8_t ascii_zero = 48;
|
||||
const uint8_t ascii_nine = 57;
|
||||
|
||||
return ascii_value >= ascii_zero && ascii_value <= ascii_nine;
|
||||
}
|
||||
|
||||
auto is_alphabetical(const char character) -> bool {
|
||||
const auto ascii_value = static_cast<uint8_t>(character);
|
||||
const uint8_t ascii_a = 97;
|
||||
const uint8_t ascii_A = 65;
|
||||
const uint8_t ascii_z = 122;
|
||||
const uint8_t ascii_Z = 90;
|
||||
const uint8_t ascii_underscore = 95;
|
||||
|
||||
return (ascii_value >= ascii_a && ascii_value <= ascii_z)
|
||||
|| (ascii_value >= ascii_A && ascii_value <= ascii_Z)
|
||||
|| ascii_value == ascii_underscore;
|
||||
}
|
||||
|
||||
auto is_mnemonic(const Token &token) -> bool {
|
||||
// TODO: Move this to a separate header
|
||||
const std::vector<std::string> mnemonics = {"MOV",
|
||||
"AND", "OR", "NAND", "NOR", "ADD", "SUB",
|
||||
"JMP", "JEQ", "JLE", "JLEQ", "NOP", "JNEQ", "JGR", "JGEQ"};
|
||||
|
||||
return std::find(mnemonics.begin(), mnemonics.end(), static_cast<std::string_view>(token))
|
||||
!= mnemonics.end();
|
||||
}
|
||||
|
||||
// ! Public Functions
|
||||
|
||||
Lexer::Lexer(std::string_view input_string)
|
||||
: input_string(input_string), position(input_string.begin()) {}
|
||||
|
||||
auto Lexer::next() -> Token {
|
||||
// Skip past everything that doesn't contain program information
|
||||
while (is_whitespace(peek()) || peek() == ',' || peek() == '#') {
|
||||
if (peek() == '#') {
|
||||
// Eat whole comment, we can't decide if sth is a comment after eating #
|
||||
comment();
|
||||
} else {
|
||||
get();
|
||||
}
|
||||
}
|
||||
|
||||
if (position >= input_string.end()) {
|
||||
return static_cast<Token>(Token::END);
|
||||
}
|
||||
|
||||
if (is_numeric(peek())) {
|
||||
return number();
|
||||
}
|
||||
|
||||
if (peek() == '[') {
|
||||
return address();
|
||||
}
|
||||
|
||||
if (is_alphabetical(peek())) {
|
||||
const Token token = identifier_or_mnemonic();
|
||||
if (is_mnemonic(token)) {
|
||||
return {Token::MNEMONIC, static_cast<std::string_view>(token)};
|
||||
}
|
||||
return {Token::IDENTIFIER, static_cast<std::string_view>(token)};
|
||||
}
|
||||
|
||||
return {Token::UNEXPECTED, std::string_view(position, position + 1)};
|
||||
}
|
||||
|
||||
// ! Private Functions
|
||||
|
||||
auto Lexer::peek() const -> char {
|
||||
return *position;
|
||||
}
|
||||
|
||||
auto Lexer::get() -> char {
|
||||
return *(position++);
|
||||
}
|
||||
|
||||
auto Lexer::identifier_or_mnemonic() -> Token {
|
||||
const std::string_view::const_iterator begin = position;
|
||||
while (peek() != ' ' && (is_alphabetical(peek()) || is_numeric(peek()))) {
|
||||
get();
|
||||
}
|
||||
const std::string_view::const_iterator end = position;
|
||||
|
||||
// We don't know the type yet, so use UNEXPECTED
|
||||
return {Token::UNEXPECTED, std::string_view(begin, end)};
|
||||
}
|
||||
|
||||
auto Lexer::number() -> Token {
|
||||
const std::string_view::const_iterator begin = position;
|
||||
while (is_numeric(peek())) {
|
||||
get();
|
||||
}
|
||||
const std::string_view::const_iterator end = position;
|
||||
|
||||
return {Token::NUMBER, std::string_view(begin, end)};
|
||||
}
|
||||
|
||||
auto Lexer::address() -> Token {
|
||||
get(); // Eat '['
|
||||
|
||||
const std::string_view::const_iterator begin = position;
|
||||
while (is_numeric(peek())) {
|
||||
get(); // Eat the address number
|
||||
}
|
||||
const std::string_view::const_iterator end = position;
|
||||
|
||||
if (peek() != ']') {
|
||||
throw "Lexer Error: Expected ']'!";
|
||||
}
|
||||
get(); // Eat ']'
|
||||
|
||||
return {Token::ADDRESS, std::string_view(begin, end)};
|
||||
}
|
||||
|
||||
void Lexer::comment() {
|
||||
// Eat the whole line
|
||||
while (peek() != '\n') {
|
||||
get();
|
||||
}
|
||||
}
|
46
src/lexer/Lexer.h
Normal file
46
src/lexer/Lexer.h
Normal file
@ -0,0 +1,46 @@
|
||||
//
|
||||
// Created by christoph on 20.03.23.
|
||||
//
|
||||
|
||||
#ifndef LOGISIMASSEMBLER_LEXER_H
|
||||
#define LOGISIMASSEMBLER_LEXER_H
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include "Token.h"
|
||||
|
||||
class Lexer {
|
||||
public:
|
||||
explicit Lexer(std::string_view input_string);
|
||||
|
||||
Lexer(const Lexer ©) = delete;
|
||||
|
||||
auto operator=(const Lexer ©) -> Lexer & = delete;
|
||||
|
||||
Lexer(Lexer &&move) = delete;
|
||||
|
||||
auto operator=(Lexer &&move) -> Lexer & = delete;
|
||||
|
||||
~Lexer() = default;
|
||||
|
||||
auto next() -> Token;
|
||||
|
||||
private:
|
||||
[[nodiscard]] auto peek() const -> char;
|
||||
|
||||
auto get() -> char;
|
||||
|
||||
auto identifier_or_mnemonic() -> Token;
|
||||
|
||||
auto number() -> Token;
|
||||
|
||||
auto address() -> Token;
|
||||
|
||||
void comment();
|
||||
|
||||
private:
|
||||
std::string_view input_string;
|
||||
std::string_view::const_iterator position;
|
||||
};
|
||||
|
||||
#endif //LOGISIMASSEMBLER_LEXER_H
|
48
src/lexer/Token.cpp
Normal file
48
src/lexer/Token.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
//
|
||||
// Created by christoph on 20.03.23.
|
||||
//
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <charconv>
|
||||
#include "Token.h"
|
||||
|
||||
Token::Token(Token::Type type) : type(type) {}
|
||||
|
||||
Token::Token(Token::Type type, std::string_view lexeme) : type(type), lexeme(lexeme) {}
|
||||
|
||||
auto Token::getType() const -> Token::Type {
|
||||
return type;
|
||||
}
|
||||
|
||||
auto Token::getTypeName() const -> std::string {
|
||||
return std::array<std::string, 6> {"MNEMONIC",
|
||||
"IDENTIFIER",
|
||||
"NUMBER",
|
||||
"ADDRESS",
|
||||
"END",
|
||||
"UNEXPECTED"}[type];
|
||||
}
|
||||
|
||||
auto Token::subtoken(uint8_t pos, uint8_t len) const -> Token {
|
||||
return std::move(Token(type, lexeme.substr(pos, len)));
|
||||
}
|
||||
|
||||
Token::operator std::string_view() const {
|
||||
return lexeme;
|
||||
}
|
||||
|
||||
Token::operator std::string() const {
|
||||
return lexeme;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/56634507/safely-convert-stdstring-view-to-int-like-stoi-or-atoi#answer-65675200
|
||||
Token::operator uint8_t() const {
|
||||
uint8_t out;
|
||||
const std::from_chars_result result = std::from_chars(lexeme.data(), lexeme.data() + lexeme.size(), out);
|
||||
if (result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range)
|
||||
{
|
||||
throw "Conversion Error: Can't convert Token to uint8_t!";
|
||||
}
|
||||
return out;
|
||||
}
|
61
src/lexer/Token.h
Normal file
61
src/lexer/Token.h
Normal file
@ -0,0 +1,61 @@
|
||||
//
|
||||
// Created by christoph on 20.03.23.
|
||||
//
|
||||
|
||||
#ifndef LOGISIMASSEMBLER_TOKEN_H
|
||||
#define LOGISIMASSEMBLER_TOKEN_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
class Token {
|
||||
public:
|
||||
enum Type : uint8_t {
|
||||
MNEMONIC,
|
||||
IDENTIFIER,
|
||||
NUMBER,
|
||||
ADDRESS, // Using []
|
||||
|
||||
// TODO: Inline calculations
|
||||
// PLUS,
|
||||
// MINUS,
|
||||
// ASTERISK,
|
||||
// SLASH,
|
||||
|
||||
END,
|
||||
UNEXPECTED
|
||||
};
|
||||
|
||||
public:
|
||||
explicit Token(Type type);
|
||||
|
||||
Token(Type type, std::string_view lexeme);
|
||||
|
||||
Token(const Token ©) = default;
|
||||
|
||||
auto operator=(const Token ©) -> Token & = default;
|
||||
|
||||
Token(Token &&move) noexcept = default;
|
||||
|
||||
auto operator=(Token &&move) noexcept -> Token & = default;
|
||||
|
||||
~Token() = default;
|
||||
|
||||
[[nodiscard]] auto getType() const -> Type;
|
||||
|
||||
[[nodiscard]] auto getTypeName() const -> std::string;
|
||||
|
||||
[[nodiscard]] auto subtoken(uint8_t pos, uint8_t len) const -> Token;
|
||||
|
||||
explicit operator std::string_view() const;
|
||||
|
||||
explicit operator std::string() const;
|
||||
|
||||
explicit operator uint8_t() const;
|
||||
|
||||
private:
|
||||
Type type;
|
||||
std::string lexeme;
|
||||
};
|
||||
|
||||
#endif //LOGISIMASSEMBLER_TOKEN_H
|
154
src/main.cpp
Normal file
154
src/main.cpp
Normal file
@ -0,0 +1,154 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <iomanip>
|
||||
#include "lexer/Lexer.h"
|
||||
#include "ast/Node.h"
|
||||
#include "parser/Parser.h"
|
||||
#include "codegen/PrintObserver.h"
|
||||
#include "codegen/CodegenObserver.h"
|
||||
|
||||
namespace po = boost::program_options;
|
||||
|
||||
auto read(const std::string& input_file, std::string &input_string) -> bool {
|
||||
std::ifstream ifs;
|
||||
ifs.open(input_file, std::ios_base::in);
|
||||
if (!ifs) {
|
||||
std::cout << "Failed to open input stream!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
while (!ifs.eof()) {
|
||||
std::string line;
|
||||
getline(ifs, line);
|
||||
input_string += line + '\n';
|
||||
}
|
||||
|
||||
ifs.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto lex(std::string_view input_string, std::vector<Token> &tokens) -> bool {
|
||||
Lexer lexer(input_string);
|
||||
while (true) {
|
||||
Token token = lexer.next();
|
||||
std::cout << std::setw(10) << token.getTypeName() << ": " << static_cast<std::string_view>(token) << std::endl;
|
||||
|
||||
if (token.getType() == Token::UNEXPECTED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tokens.push_back(std::move(token));
|
||||
|
||||
if (tokens.back().getType() == Token::END) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto parse(std::vector<Token> &tokens) -> std::unique_ptr<Node> {
|
||||
Parser parser(tokens);
|
||||
return std::move(parser.parse());
|
||||
}
|
||||
|
||||
auto write(const std::string &output_file, const std::vector<std::string> &output_string) -> bool {
|
||||
if (output_string.size() > 255) {
|
||||
std::cout << "Program too large!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream ofs;
|
||||
ofs.open(output_file, std::ios_base::out);
|
||||
if (!ofs) {
|
||||
std::cout << "Failed to open output stream!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
ofs << "v3.0 hex words addressed";
|
||||
for (uint32_t i = 0; i <= 255; ++i) {
|
||||
if (i % 16 == 0) {
|
||||
ofs << std::endl;
|
||||
// Print Address
|
||||
std::string address = (boost::format("%x") % i).str();
|
||||
ofs << (address == "0" ? "00" : address) << ": ";
|
||||
}
|
||||
|
||||
if (i < output_string.size()) {
|
||||
ofs << output_string[i] << " ";
|
||||
} else {
|
||||
ofs << "00 ";
|
||||
}
|
||||
}
|
||||
|
||||
ofs.flush();
|
||||
ofs.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto main(int argc, char **argv) -> int {
|
||||
// Argument parsing straight from the Boost manual: https://www.boost.org/doc/libs/1_60_0/doc/html/program_options/tutorial.html
|
||||
po::options_description desc("Allowed options");
|
||||
desc.add_options()
|
||||
("help,h", "Show this help message")
|
||||
("input,i", po::value<std::string>(), "Input file")
|
||||
("output,o", po::value<std::string>(), "Output file");
|
||||
|
||||
po::variables_map vm;
|
||||
po::store(po::parse_command_line(argc, argv, desc), vm);
|
||||
po::notify(vm);
|
||||
|
||||
if (vm.count("help")) {
|
||||
std::cout << desc << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (!vm.count("input")) {
|
||||
std::cout << "Did not provide input file!" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
if (!vm.count("output")) {
|
||||
std::cout << "Did not provide output file!" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::cout << "Input File: " << vm["input"].as<std::string>() << std::endl;
|
||||
std::cout << "Output File: " << vm["output"].as<std::string>() << std::endl;
|
||||
|
||||
// Read the input file
|
||||
std::string input_string;
|
||||
if (!read(vm["input"].as<std::string>(), input_string)) {
|
||||
std::cout << "File Error: Couldn't read file!" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Lexing
|
||||
std::cout << "Lexing:" << std::endl;
|
||||
std::vector<Token> tokens;
|
||||
if (!lex(input_string, tokens)) {
|
||||
std::cout << "Lexer Error: Unexpected Token!" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parsing
|
||||
std::cout << "Parsing:" << std::endl;
|
||||
const std::unique_ptr<Node> ast = parse(tokens);
|
||||
PrintObserver(*ast).Observer::traverse(); // Print the AST
|
||||
|
||||
// Codegen
|
||||
std::cout << "Codegen:" << std::endl;
|
||||
std::vector<std::string> output_string;
|
||||
CodegenObserver(*ast, output_string).Observer::traverse();
|
||||
for (const auto &instruction : output_string) {
|
||||
std::cout << instruction << std::endl;
|
||||
}
|
||||
|
||||
// Write the output file
|
||||
if (!write(vm["output"].as<std::string>(), output_string)) {
|
||||
std::cout << "File Error: Couldn't write file!" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
117
src/parser/Parser.cpp
Normal file
117
src/parser/Parser.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
//
|
||||
// Created by christoph on 20.03.23.
|
||||
//
|
||||
|
||||
#include "Parser.h"
|
||||
#include "../ast/nodes/MovNode.h"
|
||||
#include "../ast/nodes/ConstNode.h"
|
||||
#include "../ast/nodes/AluNode.h"
|
||||
#include "../ast/nodes/JumpNode.h"
|
||||
|
||||
// ! Helper Functions
|
||||
|
||||
// ! Public Functions
|
||||
|
||||
Parser::Parser(const std::vector<Token> &tokens) : position(tokens.begin()) {}
|
||||
|
||||
auto Parser::parse() -> std::unique_ptr<Node> {
|
||||
while (peek().getType() != Token::END) {
|
||||
if (peek().getType() != Token::MNEMONIC) {
|
||||
throw "Parser Error: Expected Mnemonic!";
|
||||
}
|
||||
|
||||
eaters[static_cast<std::string>(peek())](*this);
|
||||
}
|
||||
|
||||
return std::move(ast);
|
||||
}
|
||||
|
||||
// ! Private Functions
|
||||
|
||||
auto Parser::peek() const -> const Token & {
|
||||
return *position;
|
||||
}
|
||||
|
||||
auto Parser::get() -> const Token & {
|
||||
return *(position++);
|
||||
}
|
||||
|
||||
void Parser::mov() {
|
||||
if (peek().getType() != Token::MNEMONIC || static_cast<std::string_view>(peek()) != "MOV") {
|
||||
throw "Parser Error: Expected 'MOV'!";
|
||||
}
|
||||
get(); // Eat 'MOV'
|
||||
|
||||
uint8_t source = 0; // Load from reg0
|
||||
if (peek().getType() == Token::NUMBER) {
|
||||
ast->addChild(std::move(std::make_unique<ConstNode>(static_cast<uint8_t>(peek())))); // Load constant to reg0
|
||||
} else if (peek().getType() == Token::IDENTIFIER) {
|
||||
if (static_cast<std::string_view>(peek().subtoken(0, 3)) == "reg") {
|
||||
source = static_cast<uint8_t>(peek().subtoken(3, 1));
|
||||
} else if (static_cast<std::string_view>(peek()) == "input") {
|
||||
source = 6;
|
||||
}
|
||||
} else {
|
||||
throw "Parser Error: Expected Constant or Register!";
|
||||
}
|
||||
get(); // Eat source
|
||||
|
||||
uint8_t target = 0;
|
||||
if (peek().getType() == Token::IDENTIFIER) {
|
||||
if (static_cast<std::string_view>(peek().subtoken(0, 3)) == "reg") {
|
||||
target = static_cast<uint8_t>(peek().subtoken(3, 1));
|
||||
} else if (static_cast<std::string_view>(peek()) == "output") {
|
||||
target = 6;
|
||||
}
|
||||
} else {
|
||||
throw "Parser Error: Expected Register!";
|
||||
}
|
||||
get(); // Eat target
|
||||
|
||||
if (source > 6 || target > 6) {
|
||||
throw "Parser Error: Invalid Register!";
|
||||
}
|
||||
|
||||
if (source != target) {
|
||||
// This happens on e.g. MOV 10, reg0
|
||||
ast->addChild(std::move(std::make_unique<MovNode>(source, target)));
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::alu() {
|
||||
std::map<std::string, AluNode::AluOperation> aluMap = {{"AND", AluNode::AND},
|
||||
{"OR", AluNode::OR},
|
||||
{"NAND", AluNode::NAND},
|
||||
{"NOR", AluNode::NOR},
|
||||
{"ADD", AluNode::ADD},
|
||||
{"SUB", AluNode::SUB}};
|
||||
|
||||
if (peek().getType() != Token::MNEMONIC) {
|
||||
throw "Parser Error: Expected Mnemonic!";
|
||||
}
|
||||
if (!aluMap.contains(static_cast<std::string>(peek()))) {
|
||||
throw "Parser Error: Invalid ALU operation!";
|
||||
}
|
||||
|
||||
ast->addChild(std::move(std::make_unique<AluNode>(aluMap[static_cast<std::string>(get())]))); // Eat alu
|
||||
}
|
||||
|
||||
void Parser::jmp() {
|
||||
std::map<std::string, JumpNode::JumpOperation> jmpMap = {{"JMP", JumpNode::ALWAYS},
|
||||
{"JEQ", JumpNode::EQUAL_ZERO},
|
||||
{"JLE", JumpNode::LESS_ZERO},
|
||||
{"JLEQ", JumpNode::LESS_EQUAL_ZERO},
|
||||
{"NOP", JumpNode::NEVER}, // TODO: ?
|
||||
{"JNEQ", JumpNode::NOT_ZERO},
|
||||
{"JGR", JumpNode::GREATER_ZERO},
|
||||
{"JGEQ", JumpNode::GREATER_EQUAL_ZERO}};
|
||||
|
||||
if (peek().getType() != Token::MNEMONIC) {
|
||||
throw "Parser Error: Expected Mnemonic!";
|
||||
}
|
||||
if (!jmpMap.contains(static_cast<std::string>(peek()))) {
|
||||
throw "Parser Error: Invalid JMP operation!";
|
||||
}
|
||||
|
||||
ast->addChild(std::move(std::make_unique<JumpNode>(jmpMap[static_cast<std::string>(get())]))); // Eat jmp
|
||||
}
|
56
src/parser/Parser.h
Normal file
56
src/parser/Parser.h
Normal file
@ -0,0 +1,56 @@
|
||||
//
|
||||
// Created by christoph on 20.03.23.
|
||||
//
|
||||
|
||||
#ifndef LOGISIMASSEMBLER_PARSER_H
|
||||
#define LOGISIMASSEMBLER_PARSER_H
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include "../lexer/Token.h"
|
||||
#include "../ast/Node.h"
|
||||
#include "../ast/nodes/RootNode.h"
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
Parser(const std::vector<Token> &tokens);
|
||||
|
||||
Parser(std::vector<Token> &&tokens) = delete;
|
||||
|
||||
auto parse() -> std::unique_ptr<Node>;
|
||||
|
||||
private:
|
||||
[[nodiscard]] auto peek() const -> const Token &;
|
||||
|
||||
auto get() -> const Token &;
|
||||
|
||||
void mov();
|
||||
|
||||
void alu();
|
||||
|
||||
void jmp();
|
||||
|
||||
private:
|
||||
std::vector<Token>::const_iterator position;
|
||||
std::unique_ptr<Node> ast = std::make_unique<RootNode>();
|
||||
|
||||
std::map<std::string, std::function<void(Parser &)>> eaters = {{"MOV", &Parser::mov},
|
||||
{"AND", &Parser::alu},
|
||||
{"OR", &Parser::alu},
|
||||
{"NAND", &Parser::alu},
|
||||
{"NOR", &Parser::alu},
|
||||
{"ADD", &Parser::alu},
|
||||
{"SUB", &Parser::alu},
|
||||
{"JMP", &Parser::jmp},
|
||||
{"JEQ", &Parser::jmp},
|
||||
{"JLE", &Parser::jmp},
|
||||
{"JLEQ", &Parser::jmp},
|
||||
{"NOP", &Parser::jmp},
|
||||
{"JNEQ", &Parser::jmp},
|
||||
{"JGR", &Parser::jmp},
|
||||
{"JGEQ", &Parser::jmp}};
|
||||
};
|
||||
|
||||
#endif //LOGISIMASSEMBLER_PARSER_H
|
Reference in New Issue
Block a user