pull/21/head
Ondřej Hruška 3 years ago
commit 30cd0304d2
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 1
      .gitignore
  2. 12
      .idea/csnparse.iml
  3. 8
      .idea/modules.xml
  4. 6
      .idea/vcs.xml
  5. 265
      .idea/workspace.xml
  6. 77
      Cargo.lock
  7. 5
      Cargo.toml
  8. 11
      csn_asm/Cargo.toml
  9. 113
      csn_asm/src/data/literal.rs
  10. 61
      csn_asm/src/data/mask.rs
  11. 98
      csn_asm/src/data/mod.rs
  12. 22
      csn_asm/src/data/reg.rs
  13. 48
      csn_asm/src/error.rs
  14. 73
      csn_asm/src/instr/cond.rs
  15. 68
      csn_asm/src/instr/mod.rs
  16. 57
      csn_asm/src/instr/op.rs
  17. 158
      csn_asm/src/lib.rs
  18. 31
      csn_asm/src/parse/mod.rs
  19. 41
      csn_asm/src/parse/parse_cond.rs
  20. 86
      csn_asm/src/parse/parse_data.rs
  21. 51
      csn_asm/src/parse/parse_instr.rs
  22. 105
      csn_asm/src/parse/parse_op.rs
  23. 21
      csn_asm/src/parse/parse_routines.rs
  24. 60
      csn_asm/src/parse/sexp_expect.rs
  25. 5
      csn_asm/src/patches/mod.rs
  26. 23
      csn_asm/src/patches/sexp_is_a.rs
  27. 17
      csn_asm/src/patches/try_remove.rs

1
.gitignore vendored

@ -0,0 +1 @@
/target

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="CPP_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/csn_asm/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/csnparse.iml" filepath="$PROJECT_DIR$/.idea/csnparse.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -0,0 +1,265 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeRunConfigurationManager" shouldGenerate="true" shouldDeleteObsolete="true">
<generated />
</component>
<component name="CMakeSettings">
<configurations>
<configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
</configurations>
</component>
<component name="CargoProjects">
<cargoProject FILE="$PROJECT_DIR$/Cargo.toml" />
</component>
<component name="ChangeListManager">
<list default="true" id="88ed7268-df97-4c85-be28-09f4faa117a4" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/csn_asm/src/data/literal.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/csn_asm/src/data/mask.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/csn_asm/src/data/mod.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/csn_asm/src/data/reg.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/csn_asm/src/error.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/csn_asm/src/instr/cond.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/csn_asm/src/instr/op.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/csn_asm/src/parse/mod.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/csn_asm/src/parse/parse_cond.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/csn_asm/src/parse/parse_data.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/csn_asm/src/parse/parse_instr.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/csn_asm/src/parse/parse_routines.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/csn_asm/src/parse/sexp_expect.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/csn_asm/src/patches/sexp_is_a.rs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/csn_asm/src/patches/try_remove.rs" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ClangdSettings">
<option name="formatViaClangd" value="false" />
</component>
<component name="FileColors">
<fileColor scope="Non-Project Files (Material Default)" color="2E3C43" />
<fileColor scope="Non-Project Files (Material Darker)" color="323232" />
<fileColor scope="Non-Project Files (Material Lighter)" color="eae8e8" />
<fileColor scope="Non-Project Files (Material Palenight)" color="2f2e43" />
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Rust File" />
</list>
</option>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="MacroExpansionManager">
<option name="directoryName" value="feqwje3c" />
</component>
<component name="ProjectId" id="1hp4uAFugp7o1RX53zbV0AsyASy" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
<property name="ToolWindowDatabase.ShowToolbar" value="false" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="cf.first.check.clang-format" value="false" />
<property name="node.js.detected.package.eslint" value="true" />
<property name="node.js.detected.package.tslint" value="true" />
<property name="node.js.path.for.package.eslint" value="project" />
<property name="node.js.path.for.package.tslint" value="project" />
<property name="node.js.selected.package.eslint" value="(autodetect)" />
<property name="node.js.selected.package.tslint" value="(autodetect)" />
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
<property name="nodejs_npm_path_reset_for_default_project" value="true" />
<property name="nodejs_package_manager_path" value="npm" />
<property name="org.rust.cargo.project.model.PROJECT_DISCOVERY" value="true" />
<property name="settings.editor.selected.configurable" value="preferences.lookFeel" />
</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/csn_asm/src/patches" />
<recent name="$PROJECT_DIR$/csn_asm/src/parse" />
<recent name="$PROJECT_DIR$/csn_asm/src/instr" />
<recent name="$PROJECT_DIR$/csn_asm/src" />
</key>
</component>
<component name="RunManager" selected="Cargo Command.Test lib::tests">
<configuration name="Build" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="build" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Test lib::tests" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --package csn_asm --lib tests" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Test tests::test_parse_basic" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --package csn_asm --lib tests::test_parse_basic -- --exact" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Test tests::test_parse_data_formats" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --package csn_asm --lib tests::test_parse_data_formats -- --exact" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration name="Test tests::test_parse_empty_routine" type="CargoCommandRunConfiguration" factoryName="Cargo Command" temporary="true">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --package csn_asm --lib tests::test_parse_empty_routine -- --exact" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<configuration default="true" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run" />
<option name="allFeatures" value="false" />
<option name="emulateTerminal" value="false" />
<option name="backtrace" value="SHORT" />
<envs />
<method v="2">
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
</method>
</configuration>
<list>
<item itemvalue="Cargo Command.Build" />
<item itemvalue="Cargo Command.Test tests::test_parse_basic" />
<item itemvalue="Cargo Command.Test tests::test_parse_empty_routine" />
<item itemvalue="Cargo Command.Test lib::tests" />
<item itemvalue="Cargo Command.Test tests::test_parse_data_formats" />
</list>
<recent_temporary>
<list>
<item itemvalue="Cargo Command.Test lib::tests" />
<item itemvalue="Cargo Command.Test tests::test_parse_data_formats" />
<item itemvalue="Cargo Command.Test tests::test_parse_empty_routine" />
<item itemvalue="Cargo Command.Test tests::test_parse_basic" />
</list>
</recent_temporary>
</component>
<component name="RustProjectSettings">
<option name="toolchainHomeDirectory" value="/usr/bin" />
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="88ed7268-df97-4c85-be28-09f4faa117a4" name="Default Changelist" comment="" />
<created>1600694919906</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1600694919906</updated>
<workItem from="1600694921394" duration="24965000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" />
</component>
<component name="WindowStateProjectService">
<state x="419" y="270" width="1081" height="718" key="#com.intellij.execution.impl.EditConfigurationsDialog" timestamp="1600708890006">
<screen x="0" y="32" width="1920" height="1168" />
</state>
<state x="419" y="270" width="1081" height="718" key="#com.intellij.execution.impl.EditConfigurationsDialog/0.32.1920.1168/1920.0.1600.1200@0.32.1920.1168" timestamp="1600708890006" />
<state x="100" y="130" width="1720" height="970" key="#com.intellij.execution.junit2.states.ComparisonFailureState$DiffDialog" timestamp="1600720457803">
<screen x="0" y="32" width="1920" height="1168" />
</state>
<state x="100" y="130" width="1720" height="970" key="#com.intellij.execution.junit2.states.ComparisonFailureState$DiffDialog/0.32.1920.1168/1920.0.1600.1200@0.32.1920.1168" timestamp="1600720457803" />
<state x="769" y="331" key="#com.intellij.ide.util.MemberChooser" timestamp="1600717864594">
<screen x="0" y="32" width="1920" height="1168" />
</state>
<state x="769" y="331" key="#com.intellij.ide.util.MemberChooser/0.32.1920.1168/1920.0.1600.1200@0.32.1920.1168" timestamp="1600717864594" />
<state x="743" y="384" key="FileChooserDialogImpl" timestamp="1600708889153">
<screen x="0" y="32" width="1920" height="1168" />
</state>
<state x="743" y="384" key="FileChooserDialogImpl/0.32.1920.1168/1920.0.1600.1200@0.32.1920.1168" timestamp="1600708889153" />
<state width="1592" height="1109" key="GridCell.Tab.0.bottom" timestamp="1600722282000">
<screen x="0" y="32" width="1920" height="1168" />
</state>
<state width="1592" height="1109" key="GridCell.Tab.0.bottom/0.32.1920.1168/1920.0.1600.1200@0.32.1920.1168" timestamp="1600722282000" />
<state width="1592" height="1109" key="GridCell.Tab.0.center" timestamp="1600722282000">
<screen x="0" y="32" width="1920" height="1168" />
</state>
<state width="1592" height="1109" key="GridCell.Tab.0.center/0.32.1920.1168/1920.0.1600.1200@0.32.1920.1168" timestamp="1600722282000" />
<state width="1592" height="1109" key="GridCell.Tab.0.left" timestamp="1600722282000">
<screen x="0" y="32" width="1920" height="1168" />
</state>
<state width="1592" height="1109" key="GridCell.Tab.0.left/0.32.1920.1168/1920.0.1600.1200@0.32.1920.1168" timestamp="1600722282000" />
<state width="1592" height="1109" key="GridCell.Tab.0.right" timestamp="1600722282000">
<screen x="0" y="32" width="1920" height="1168" />
</state>
<state width="1592" height="1109" key="GridCell.Tab.0.right/0.32.1920.1168/1920.0.1600.1200@0.32.1920.1168" timestamp="1600722282000" />
<state width="1870" height="388" key="GridCell.Tab.1.bottom" timestamp="1600717739429">
<screen x="0" y="32" width="1920" height="1168" />
</state>
<state width="1870" height="388" key="GridCell.Tab.1.bottom/0.32.1920.1168/1920.0.1600.1200@0.32.1920.1168" timestamp="1600717739429" />
<state width="1870" height="388" key="GridCell.Tab.1.center" timestamp="1600717739429">
<screen x="0" y="32" width="1920" height="1168" />
</state>
<state width="1870" height="388" key="GridCell.Tab.1.center/0.32.1920.1168/1920.0.1600.1200@0.32.1920.1168" timestamp="1600717739429" />
<state width="1870" height="388" key="GridCell.Tab.1.left" timestamp="1600717739429">
<screen x="0" y="32" width="1920" height="1168" />
</state>
<state width="1870" height="388" key="GridCell.Tab.1.left/0.32.1920.1168/1920.0.1600.1200@0.32.1920.1168" timestamp="1600717739429" />
<state width="1870" height="388" key="GridCell.Tab.1.right" timestamp="1600717739429">
<screen x="0" y="32" width="1920" height="1168" />
</state>
<state width="1870" height="388" key="GridCell.Tab.1.right/0.32.1920.1168/1920.0.1600.1200@0.32.1920.1168" timestamp="1600717739429" />
<state x="442" y="247" key="SettingsEditor" timestamp="1600713864019">
<screen x="0" y="32" width="1920" height="1168" />
</state>
<state x="442" y="247" key="SettingsEditor/0.32.1920.1168/1920.0.1600.1200@0.32.1920.1168" timestamp="1600713864019" />
<state x="1960" y="30" width="1575" height="1096" maximized="true" key="dock-window-1" timestamp="1600712938231">
<screen x="1920" y="0" width="1600" height="1200" />
</state>
<state x="1960" y="30" width="1575" height="1096" maximized="true" key="dock-window-1/0.32.1920.1168/1920.0.1600.1200@0.32.1920.1168" timestamp="1600712938231" />
<state x="1920" y="104" width="1575" height="1096" key="dock-window-2" timestamp="1600721359003">
<screen x="1920" y="0" width="1600" height="1200" />
</state>
<state x="1920" y="104" width="1575" height="1096" key="dock-window-2/0.32.1920.1168/1920.0.1600.1200@0.32.1920.1168" timestamp="1600721359003" />
<state x="651" y="295" width="617" height="640" key="find.popup" timestamp="1600706360212">
<screen x="0" y="32" width="1920" height="1168" />
</state>
<state x="651" y="295" width="617" height="640" key="find.popup/0.32.1920.1168/1920.0.1600.1200@0.32.1920.1168" timestamp="1600706360212" />
<state width="1007" height="578" key="javadoc.popup.new" timestamp="1600715844130">
<screen x="0" y="32" width="1920" height="1168" />
</state>
<state width="1007" height="578" key="javadoc.popup.new/0.32.1920.1168/1920.0.1600.1200@0.32.1920.1168" timestamp="1600715844130" />
</component>
</project>

77
Cargo.lock generated

@ -0,0 +1,77 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "anyhow"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"
[[package]]
name = "csn_asm"
version = "0.1.0"
dependencies = [
"anyhow",
"sexp",
"thiserror",
]
[[package]]
name = "proc-macro2"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36e28516df94f3dd551a587da5357459d9b36d945a7c37c3557928c1c2ff2a2c"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
"proc-macro2",
]
[[package]]
name = "sexp"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8fa7ac9df84000b0238cf497cb2d3056bac2ff2a7d8cf179d2803b4b58571f"
[[package]]
name = "syn"
version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6690e3e9f692504b941dc6c3b188fd28df054f7fb8469ab40680df52fdcc842b"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "thiserror"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"

@ -0,0 +1,5 @@
[workspace]
members = [
"csn_asm",
]

@ -0,0 +1,11 @@
[package]
name = "csn_asm"
version = "0.1.0"
authors = ["Ondřej Hruška <ondra@ondrovo.com>"]
edition = "2018"
publish = false
[dependencies]
sexp = "1.1.4"
thiserror = "1.0.20"
anyhow = "1.0.32"

@ -0,0 +1,113 @@
use std::fmt::{self, Display, Formatter};
use std::convert::TryFrom;
use std::sync::atomic::AtomicU32;
use std::borrow::Cow;
pub type DebugMsg = Cow<'static, str>;
/// Immediate value
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct Value(pub i64);
impl From<i64> for Value {
fn from(n: i64) -> Self {
Self(n)
}
}
impl Display for Value {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "{:#010x}", self.0)
} else {
write!(f, "{}", self.0)
}
}
}
impl Value {
pub fn as_u64(self) -> u64 {
u64::from_ne_bytes(self.0.to_ne_bytes())
}
pub fn as_u32(self) -> Option<u32> {
u32::try_from(self.as_u64()).ok()
}
pub fn as_i32(self) -> Option<i32> {
i32::try_from(self.0).ok()
}
}
/// Immediate address
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct Addr(pub u64);
impl Display for Addr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "@{:#010x}", self.0)
}
}
impl From<u64> for Addr {
fn from(n: u64) -> Self {
Self(n)
}
}
/// Label name
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Label {
Named(String),
Numbered(u32),
}
impl Label {
/// Generate a unique numbered label from a counter
pub fn unique(counter : &AtomicU32) -> Self {
Label::Numbered(counter.fetch_add(1, std::sync::atomic::Ordering::Relaxed))
}
}
impl Display for Label {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Label::Named(name) => write!(f, ":{}", name),
Label::Numbered(num) => write!(f, ":#{}", num),
}
}
}
impl From<&str> for Label {
fn from(n: &str) -> Self {
Self::Named(n.to_string())
}
}
impl From<String> for Label {
fn from(n: String) -> Self {
Self::Named(n)
}
}
/// Routine name
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct RoutineName(pub String);
impl Display for RoutineName {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<&str> for RoutineName {
fn from(n: &str) -> Self {
Self(n.to_string())
}
}
impl From<String> for RoutineName {
fn from(n: String) -> Self {
Self(n)
}
}

@ -0,0 +1,61 @@
//! Mask applied to a data source or destination
use crate::error::AsmError;
/// Bit mask to apply to a value
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct Mask {
/// Length of the selected bit slice
len: u8,
/// Offset of the selected bit slice from bit zero
offset: u8,
}
impl Default for Mask {
fn default() -> Self {
Mask {
len: 64,
offset: 0
}
}
}
impl Mask {
pub const BYTE: Mask = Mask {
len: 8,
offset: 0,
};
pub const HALF_WORD: Mask = Mask {
len: 16,
offset: 0,
};
pub const WORD: Mask = Mask {
len: 32,
offset: 0,
};
pub const DOUBLE_WORD: Mask = Mask {
len: 64,
offset: 0,
};
pub const FULL: Mask = Self::DOUBLE_WORD;
pub fn new(len: u8, offset: u8) -> Result<Self, AsmError> {
if len == 0 || offset >= 64 {
// create the invalid mask to display it in the error
return Err(AsmError::BadMask(Mask {
len,
offset
}));
}
Ok(Self {
len: len.min(64 - offset),
offset,
})
}
/// Get a binary mask representing the span
pub fn as_bitmask(self) -> u64 {
((1 << self.len) - 1) << self.offset
}
}

@ -0,0 +1,98 @@
use super::error::AsmError;
pub(crate) mod literal;
mod reg;
mod mask;
pub use reg::Register;
pub use mask::Mask;
use literal::Addr;
use std::convert::TryFrom;
use crate::data::literal::Value;
/// Data source disposition
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum DataDisp {
/// Constant value
Immediate(Value),
/// Constant memory address
ImmediatePtr(Addr),
/// Register
Register(Register),
/// Pointer into memory, stored in a numbered register
RegisterPtr(Register),
}
/// Data source disposition
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum SrcDisp {
/// Constant value
Immediate(Value),
/// Constant memory address
ImmediatePtr(Addr),
/// Register
Register(Register),
/// Pointer into memory, stored in a numbered register
RegisterPtr(Register),
}
impl TryFrom<DataDisp> for SrcDisp {
type Error = AsmError;
fn try_from(value: DataDisp) -> Result<Self, Self::Error> {
match value {
DataDisp::Immediate(x) => Ok(SrcDisp::Immediate(x)),
DataDisp::ImmediatePtr(x) => Ok(SrcDisp::ImmediatePtr(x)),
DataDisp::Register(x) => Ok(SrcDisp::Register(x)),
DataDisp::RegisterPtr(x) => Ok(SrcDisp::RegisterPtr(x)),
}
}
}
/// Data destination disposition
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum DstDisp {
/// Constant memory address
ImmediatePtr(Addr),
/// Register
Register(Register),
/// Pointer into memory, stored in a numbered register
RegisterPtr(Register),
}
impl TryFrom<DataDisp> for DstDisp {
type Error = AsmError;
fn try_from(value: DataDisp) -> Result<Self, Self::Error> {
match value {
DataDisp::Immediate(_x) => Err(AsmError::ValueAsOutput),
DataDisp::ImmediatePtr(x) => Ok(DstDisp::ImmediatePtr(x)),
DataDisp::Register(x) => Ok(DstDisp::Register(x)),
DataDisp::RegisterPtr(x) => Ok(DstDisp::RegisterPtr(x)),
}
}
}
/// Data source argument (read-only)
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct Rd(pub SrcDisp, pub Mask);
impl Rd {
pub fn new(src : SrcDisp) -> Self {
Rd(src, Mask::default())
}
}
/// Data destination argument (read-write)
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct Wr(pub DstDisp, pub Mask);
impl Wr {
pub fn new(dst : DstDisp) -> Self {
Wr(dst, Mask::default())
}
}

@ -0,0 +1,22 @@
use std::fmt::{self, Display, Formatter};
/// Register name
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Register {
/// Argument register, read-only
Arg(u8),
/// Result register, read-only
Res(u8),
/// General purpose register
Gen(u8)
}
impl Display for Register {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Register::Arg(n) => write!(f, "arg{}", n),
Register::Res(n) => write!(f, "res{}", n),
Register::Gen(n) => write!(f, "r{}", n),
}
}
}

@ -0,0 +1,48 @@
use crate::instr::{Cond};
use crate::data::{Mask, Register};
use thiserror::Error;
use std::borrow::Cow;
/// csn_asm unified error type
#[derive(Error,Debug)]
pub enum Error {
#[error("S-expression syntax error: {0:?}")]
PreParse(#[from] Box<sexp::Error>),
#[error("Parse error: {0:?}")]
Parse(Cow<'static, str>),
#[error("Parse error in {1:?}: {0:?}")]
ParseIn(Cow<'static, str>, sexp::Sexp),
#[error("Assembler error: {0:?}")]
Asm(AsmError),
#[error("Architecture error: {0:?}")]
Arch(ArchError),
#[error(transparent)]
Other(#[from] anyhow::Error),
}
/// Error from the assembler stage (after parsing S-expressions and basic validation)
#[derive(Error,Debug)]
pub enum AsmError {
#[error("Unknown instruction")]
UnknownInstruction,
#[error("Bad bit mask")]
BadMask(Mask),
#[error("Uneven operand size")]
UnevenOperandSize(Mask, Mask),
#[error("Value provided as output argument")]
ValueAsOutput,
#[error("Conditional branch already defined for \"{0}\"")]
ConditionalAlreadyUsed(Cond),
}
/// Architectural error - the code is syntactically OK, but cannot run
#[derive(Error,Debug)]
pub enum ArchError {
#[error("Register {0} does not exist")]
RegisterNotExist(Register),
#[error("Register {0} is not writable")]
RegisterNotWritable(Register),
#[error("Register {0} is not readable")]
RegisterNotReadable(Register),
}

@ -0,0 +1,73 @@
use std::fmt::{self, Display, Formatter};
use std::ops::Not;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum Cond {
Equal,
NotEqual,
Zero,
NotZero,
Less,
LessOrEqual,
Greater,
GreaterOrEqual,
Positive,
NonPositive,
Negative,
NonNegative,
Overflow,
NotOverflow,
Carry,
NotCarry,
}
impl Display for Cond {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Cond::Equal => "eq",
Cond::NotEqual => "ne",
Cond::Zero => "z",
Cond::NotZero => "nz",
Cond::Less => "lt",
Cond::LessOrEqual => "le",
Cond::Greater => "gt",
Cond::GreaterOrEqual => "ge",
Cond::Positive => "pos",
Cond::Negative => "neg",
Cond::NonPositive => "npos",
Cond::NonNegative => "nneg",
Cond::Overflow => "ov",
Cond::Carry => "c",
Cond::NotCarry => "nc",
Cond::NotOverflow => "nov"
})
}
}
impl Not for Cond {
type Output = Cond;
fn not(self) -> Self::Output {
match self {
Cond::Equal => Cond::NotEqual,
Cond::Zero => Cond::NotZero,
Cond::Overflow => Cond::NotOverflow,
Cond::Carry => Cond::NotCarry,
Cond::Positive => Cond::NonPositive,
Cond::Negative => Cond::NonNegative,
Cond::NonPositive => Cond::Positive,
Cond::NonNegative => Cond::Negative,
Cond::NotEqual => Cond::Equal,
Cond::NotZero => Cond::Zero,
Cond::NotOverflow => Cond::Overflow,
Cond::NotCarry => Cond::Carry,
Cond::Less => Cond::GreaterOrEqual,
Cond::Greater => Cond::LessOrEqual,
Cond::LessOrEqual => Cond::Greater,
Cond::GreaterOrEqual => Cond::Less,
}
}
}

@ -0,0 +1,68 @@
mod op;
mod cond;
pub use op::Op;
pub use cond::Cond;
use crate::data::literal::{Label, RoutineName};
use std::sync::atomic::{AtomicU32};
use std::collections::HashMap;
use crate::error::{AsmError, Error};
/// A higher-level instruction
pub struct Instr {
pub op: Op,
pub branches: Option<Vec<(Cond, Vec<Instr>)>>,
}
/// A routine
pub struct Routine {
pub name: RoutineName,
pub body: Vec<Instr>,
}
/// A trait for something that can turn into multiple instructions
pub trait Flatten {
fn flatten(self, label_num: &AtomicU32) -> Result<Vec<Op>, Error>;
}
impl Flatten for Instr {
fn flatten(self, label_num: &AtomicU32) -> Result<Vec<Op>, Error> {
let mut ops = vec![self.op];
if let Some(branches) = self.branches {
let labels = HashMap::<Cond, u32>::new();
let _branch_count = branches.len();
for (_cnt, (cond, branch)) in branches.into_iter().enumerate() {
if labels.contains_key(&cond) {
return Err(Error::Asm(AsmError::ConditionalAlreadyUsed(cond)));
}
let next_lbl = Label::unique(label_num);
ops.push(Op::JumpIf(!cond, next_lbl.clone()));
for branch_instr in branch {
ops.extend(branch_instr.flatten(label_num)?);
}
ops.push(Op::Label(next_lbl));
}
}
Ok(ops)
}
}
impl Flatten for Routine {
fn flatten(self, label_num: &AtomicU32) -> Result<Vec<Op>, Error> {
let mut ops = vec![
Op::Routine(self.name.clone()),
];
for instr in self.body {
ops.extend(instr.flatten(label_num)?);
}
ops.push(Op::Barrier(Some(format!("Routine \"{}\" overrun", self.name).into())));
Ok(ops)
}
}

@ -0,0 +1,57 @@
use crate::data::{
Wr, Rd,
literal::Label,
literal::RoutineName,
literal::DebugMsg,
};
use crate::instr::{Cond};
/// A low level instruction
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Op {
/* Marker instructions */
/// Mark a jump target.
/// Is optimized out when jumps are replaced by relative skips
Label(Label),
/// Mark a far jump target (can be jumped to from another routine).
/// This label is preserved in optimized code.
FarLabel(Label),
/* Control flow */
/// Jump to a label
Jump(Label),
/// Jump to a label that can be in another function
FarJump(Label),
/// Call a routine with arguments
Call(RoutineName, Vec<Rd>),
/// Exit the current routine with return values
Ret(Vec<Rd>),
/* Synthetic instructions */
/// Mark a routine entry point (call target)
Routine(RoutineName),
/// Skip backward or forward
Skip(Rd),
/// Jump to a label if a flag is set
JumpIf(Cond, Label),
/// Deny jumps, skips and run across this address, producing a run-time fault with a message.
Barrier(Option<DebugMsg>),
/// Generate a run-time fault with a debugger message
Fault(Option<DebugMsg>),
/* Arithmetic */
/// Copy a value
Mov(Wr, Rd),
/// Compare two values and set conditional flags
Cmp(Rd, Rd),
// Increment a value
Inc(Wr),
// Decrement a value
Dec(Wr),
// TODO arithmetics, bit manipulation, byte operations
}

@ -0,0 +1,158 @@
mod data;
mod error;
mod instr;
mod parse;
mod patches;
pub use parse::parse;
#[cfg(test)]
mod tests {
use crate::parse;
use crate::instr::{Op, Flatten};
use crate::data::{Wr, DstDisp, Register, SrcDisp, Rd};
use crate::data::literal::{Value, Addr};
use std::sync::atomic::AtomicU32;
#[test]
fn test_parse_empty() {
let parsed = parse("
()
").unwrap();
assert_eq!(Vec::<Op>::new(), parsed);
}
#[test]
fn test_parse_empty_routine() {
let parsed = parse("
(
(hello)
)
").unwrap();
assert_eq!(vec![
Op::Routine("hello".into()),
Op::Barrier(Some("Routine \"hello\" overrun".into()))
], parsed);
let parsed = parse("
(
(hello)
(world)
)
").unwrap();
assert_eq!(vec![
Op::Routine("hello".into()),
Op::Barrier(Some("Routine \"hello\" overrun".into())),
Op::Routine("world".into()),
Op::Barrier(Some("Routine \"world\" overrun".into()))
], parsed);
}
#[test]
fn test_parse_data_formats() {
let parsed = parse("
(
(move
(mov r0 r1)
(mov r15 7)
(mov r15 0xabcd)
(mov r7 0b11110000)
(mov r7 arg1)
(mov r255 arg255)
(mov r7 res0)
(mov r7 res255)
(mov @r0 @r0) ; test in both Rd and Wr positions
(mov @r0 @arg0)
(mov @r0 @res0)
(mov @123456 @0x123456)
(mov @0b010101 @0b010101)
)
)
").unwrap();
assert_eq!(vec![
Op::Routine("move".into()),
// (mov r0 r1)
Op::Mov(
Wr::new(DstDisp::Register(Register::Gen(0))),
Rd::new(SrcDisp::Register(Register::Gen(1))),
),
// (mov r15 7)
Op::Mov(
Wr::new(DstDisp::Register(Register::Gen(15))),
Rd::new(SrcDisp::Immediate(Value(7))),
),
// (mov r15 0xabcd)
Op::Mov(
Wr::new(DstDisp::Register(Register::Gen(15))),
Rd::new(SrcDisp::Immediate(Value(0xabcd))),
),
// (mov r7 0b11110000)
Op::Mov(
Wr::new(DstDisp::Register(Register::Gen(7))),
Rd::new(SrcDisp::Immediate(Value(0b11110000))),
),
// (mov r7 arg1)
Op::Mov(
Wr::new(DstDisp::Register(Register::Gen(7))),
Rd::new(SrcDisp::Register(Register::Arg(1))),
),
// (mov r255 arg255)
Op::Mov(
Wr::new(DstDisp::Register(Register::Gen(255))),
Rd::new(SrcDisp::Register(Register::Arg(255))),
),
// (mov r7 res0)
Op::Mov(
Wr::new(DstDisp::Register(Register::Gen(7))),
Rd::new(SrcDisp::Register(Register::Res(0))),
),
// (mov r7 res255)
Op::Mov(
Wr::new(DstDisp::Register(Register::Gen(7))),
Rd::new(SrcDisp::Register(Register::Res(255))),
),
// (mov @r0 @r0)
Op::Mov(
Wr::new(DstDisp::RegisterPtr(Register::Gen(0))),
Rd::new(SrcDisp::RegisterPtr(Register::Gen(0))),
),
// (mov @r0 @arg0)
Op::Mov(
Wr::new(DstDisp::RegisterPtr(Register::Gen(0))),
Rd::new(SrcDisp::RegisterPtr(Register::Arg(0))),
),
// (mov @r0 @res0)
Op::Mov(
Wr::new(DstDisp::RegisterPtr(Register::Gen(0))),
Rd::new(SrcDisp::RegisterPtr(Register::Res(0))),
),
// (mov @123456 @0x123456)
Op::Mov(
Wr::new(DstDisp::ImmediatePtr(Addr(123456))),
Rd::new(SrcDisp::ImmediatePtr(Addr(0x123456))),
),
// (mov @0b010101 @0b010101)
Op::Mov(
Wr::new(DstDisp::ImmediatePtr(Addr(0b010101))),
Rd::new(SrcDisp::ImmediatePtr(Addr(0b010101))),
),
Op::Barrier(Some("Routine \"move\" overrun".into())),
], parsed);
}
fn parse_single_instr(src : &str) -> anyhow::Result<Vec<Op>> {
let num = AtomicU32::new(0);
Ok(parse::parse_instructions(vec![sexp::parse(src)?])?.remove(0).flatten(&num)?)
}
#[test]
fn test_parse_single() {
let parsed = parse_single_instr("(mov r0 r1)").unwrap();
assert_eq!(vec![
Op::Mov(
Wr::new(DstDisp::Register(Register::Gen(0))),
Rd::new(SrcDisp::Register(Register::Gen(1))),
),
], parsed);
}
}

@ -0,0 +1,31 @@
use crate::instr::{Routine, Op, Flatten};
use crate::error::Error;
use std::sync::atomic::AtomicU32;
use crate::parse::sexp_expect::expect_list;
mod parse_cond;
mod parse_instr;
mod parse_data;
mod parse_routines;
mod sexp_expect;
mod parse_op;
use parse_routines::parse_routines;
pub use parse_instr::parse_instructions;
pub fn parse(source: &str) -> Result<Vec<Op>, Error> {
let root = sexp::parse(source)?;
let subs: Vec<Routine> = parse_routines(expect_list(Some(root), true)?)?;
let mut combined = vec![];
let label_num = AtomicU32::new(0);
for sub in subs {
combined.extend(sub.flatten(&label_num)?);
}
Ok(combined)
}

@ -0,0 +1,41 @@
use crate::parse::sexp_expect::{expect_list, expect_string_atom};
use sexp::Sexp;
use crate::instr::{Cond, Instr};
use crate::error::Error;
use crate::parse::parse_instr::parse_instructions;
use crate::patches::TryRemove;
pub fn parse_cond_branch(tok: Sexp) -> Result<(Cond, Vec<Instr>), Error> {
let mut list = expect_list(Some(tok), false)?;
let kw = expect_string_atom(list.try_remove(0))?;
if !kw.ends_with('?') {
return Err(Error::Parse(format!("Condition must end with '?': {}", kw).into()));
}
Ok((parse_cond(&kw)?, parse_instructions(list)?))
}
pub fn parse_cond(text: &str) -> Result<Cond, Error> {
Ok(match text.trim_end_matches('?') {
"eq" | "=" | "==" => Cond::Equal,
"ne" | "<>" | "!=" | "≠" => Cond::NotEqual,
"z" | "0" => Cond::Zero,
"nz" | "<>0" | "!0" => Cond::NotZero,
"lt" | "<" => Cond::Less,
"le" | "<=" | "≤" => Cond::LessOrEqual,
"gt" => Cond::Greater,
"ge" | ">=" | "≥" => Cond::GreaterOrEqual,
"pos" | "+" | ">0" => Cond::Positive,
"neg" | "-" | "<0" => Cond::Negative,
"npos" | "!+" | "0-" | "<=0" | "≥0" => Cond::NonPositive,
"nneg" | "!-" | "0+" | ">=0" | "≤0" => Cond::NonNegative,
"ov" | "^" => Cond::Overflow,
"c" => Cond::Carry,
"nc" | "!c" => Cond::NotCarry,
"nov" | "!ov" | "!^" => Cond::NotOverflow,
_ => {
return Err(Error::Parse(format!("Unknown cond: {}", text).into()));
}
})
}

@ -0,0 +1,86 @@
use sexp::{Sexp, Atom};
use crate::data::{DataDisp, Register, Rd, Mask, Wr, DstDisp, SrcDisp};
use crate::error::Error;
use crate::data::literal::{Value, Addr, Label};
use std::convert::TryFrom;
use crate::parse::sexp_expect::expect_string_atom;
pub fn parse_label(name : Option<Sexp>) -> Result<Label, Error> {
let name = expect_string_atom(name)?;
Ok(Label::Named(name.trim_start_matches(':').into()))
}
/// Parse data disposition (address/value, without the read/write restriction)
pub fn parse_data_disp(tok: Option<Sexp>) -> Result<DataDisp, Error> {
let tok = if let Some(tok) = tok {
tok
} else {
return Err(Error::Parse("Expected data disposition token".into()));
};
// TODO implement masks
match &tok {
Sexp::Atom(Atom::I(val)) => {
Ok(DataDisp::Immediate(Value(*val)))
},
Sexp::Atom(Atom::S(s)) => {
if let Some(reference) = s.strip_prefix('@') {
if reference.starts_with(|c : char| c.is_ascii_digit()) {
let val : u64 = parse_u64(reference)?;
Ok(DataDisp::ImmediatePtr(Addr(val)))
} else {
Ok(DataDisp::RegisterPtr(parse_reg(reference)?))
}
} else if s.starts_with(|c : char| c.is_ascii_digit()) {
Ok(DataDisp::Immediate(Value(parse_i64(s)?)))
} else {
Ok(DataDisp::Register(parse_reg(s)?))
}
},
_ => {
Err(Error::Parse(format!("bad data disp: {:?}", tok).into()))
},
}
}
pub fn parse_reg(name : &str) -> anyhow::Result<Register> {
if let Some(rn) = name.strip_prefix("arg") {
let val : u8 = rn.parse()?;
Ok(Register::Arg(val))
} else if let Some(rn) = name.strip_prefix("res") {
let val : u8 = rn.parse()?;
Ok(Register::Res(val))
} else if let Some(rn) = name.strip_prefix("r") {
let val : u8 = rn.parse()?;
Ok(Register::Gen(val))
} else {
Err(Error::Parse(format!("Bad reg name: {}", name).into()))?
}
}
pub fn parse_u64(literal : &str) -> anyhow::Result<u64> {
if let Some(hex) = literal.strip_prefix("0x") {
Ok(u64::from_str_radix(hex, 16)?)
} else if let Some(hex) = literal.strip_prefix("0b") {
Ok(u64::from_str_radix(hex, 2)?)
} else {
Ok(u64::from_str_radix(literal, 10)?)
}
}
pub fn parse_i64(literal : &str) -> anyhow::Result<i64> {
if let Some(_value) = literal.strip_prefix("-") {
Ok(-1 * i64::try_from(parse_u64(literal)?)?)
} else {
Ok(i64::try_from(parse_u64(literal)?)?)
}
}
pub fn parse_rd(tok: Option<Sexp>) -> anyhow::Result<Rd> {
Ok(Rd(SrcDisp::try_from(parse_data_disp(tok)?)?, Mask::default()))
}
pub fn parse_wr(tok: Option<Sexp>) -> anyhow::Result<Wr> {
Ok(Wr(DstDisp::try_from(parse_data_disp(tok)?)?, Mask::default()))
}

@ -0,0 +1,51 @@
use sexp::Sexp;
use crate::instr::{Instr, Op};
use crate::error::Error;
use crate::parse::parse_cond::{parse_cond_branch, parse_cond};
use crate::data::literal::{Label, RoutineName};
use crate::parse::parse_data::{parse_rd, parse_wr};
use crate::parse::sexp_expect::{expect_list, expect_string_atom};
use crate::patches::SexpIsA;
use super::parse_op::parse_op;
pub fn parse_instructions(instrs: Vec<Sexp>) -> Result<Vec<Instr>, Error> {
let mut parsed = vec![];
for expr in instrs {
let tokens = expect_list(Some(expr), false)?;
let mut toki = tokens.into_iter();
let mut name = expect_string_atom(toki.next())?;
let far = if name == "far" {
name = expect_string_atom(toki.next())?;
true
} else {
false
};
let arg_tokens = toki.clone().take_while(|e| e.is_atom());
let branch_tokens = toki
.skip_while(|e| e.is_atom())
.take_while(|e| e.is_list());
let branches = {
let mut branches = vec![];
for t in branch_tokens {
branches.push(parse_cond_branch(t)?);
}
if branches.is_empty() {
None
} else {
Some(branches)
}
};
parsed.push(Instr {
op: parse_op(name.as_str(), far, arg_tokens)?,
branches
});
}
Ok(parsed)
}

@ -0,0 +1,105 @@
use crate::instr::Op;
use sexp::Sexp;