mirror of
https://github.com/Noratrieb/crapderive.git
synced 2026-01-16 17:45:07 +01:00
d o c
This commit is contained in:
parent
30735f6298
commit
8d03964f76
11 changed files with 60 additions and 46 deletions
|
|
@ -2,18 +2,19 @@
|
||||||
|
|
||||||
[[section-building-block-view]]
|
[[section-building-block-view]]
|
||||||
|
|
||||||
|
|
||||||
== Building Block View
|
== Building Block View
|
||||||
|
|
||||||
==== Parser `parser.rs`
|
==== Parser `parser.rs`
|
||||||
|
|
||||||
Lexes the source code, and then parses those tokens into an abstract syntax tree.
|
Tokenizes the source code, and then parses those tokens into an abstract syntax tree.
|
||||||
|
|
||||||
[source,rust]
|
[source,rust]
|
||||||
----
|
----
|
||||||
include::{sourcedir}/parser.rs[tag=parse]
|
include::{sourcedir}/parser.rs[tag=stmt]
|
||||||
----
|
----
|
||||||
|
|
||||||
|
The AST accepts arbitrary expressions as arguments to instructions. This allows it to generate better diagnostics later.
|
||||||
|
|
||||||
|
|
||||||
==== Compiler and IR `ir.rs`
|
==== Compiler and IR `ir.rs`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,3 @@ The interpreter follows a classic interpreter architecture. First, the source is
|
||||||
Then, a handwritten recursive descent parser parses the token stream. The abstract syntax tree is then given to a small compiler, that compiles it down to a smaller and more limited IR. It also resolves jump labels to offsets.
|
Then, a handwritten recursive descent parser parses the token stream. The abstract syntax tree is then given to a small compiler, that compiles it down to a smaller and more limited IR. It also resolves jump labels to offsets.
|
||||||
|
|
||||||
The interpreter then executes this lower level IR.
|
The interpreter then executes this lower level IR.
|
||||||
|
|
||||||
|
|
||||||
=== <Runtime Scenario 1>
|
|
||||||
|
|
||||||
* _<insert runtime diagram or textual description of the scenario>_
|
|
||||||
* _<insert description of the notable aspects of the interactions between the
|
|
||||||
building block instances depicted in this diagram.>_
|
|
||||||
|
|
||||||
=== <Runtime Scenario 2>
|
|
||||||
|
|
||||||
=== ...
|
|
||||||
|
|
||||||
=== <Runtime Scenario n>
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,23 @@
|
||||||
|
:sourcedir: ../../src
|
||||||
|
|
||||||
[[section-concepts]]
|
[[section-concepts]]
|
||||||
== Cross-cutting Concepts
|
== Cross-cutting Concepts
|
||||||
|
|
||||||
|
=== Version control
|
||||||
|
|
||||||
|
This project uses git and Github for version control.
|
||||||
|
|
||||||
|
=== Error handling
|
||||||
|
|
||||||
|
A single type is used for handling invalid input. The error has a single message, a span that shows the location in the source code, and optionally some notes or a help message. This message is then printed using the `ariadne` crate.
|
||||||
|
|
||||||
=== _<Concept 1>_
|
[source,rust]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/error.rs[tag=error]
|
||||||
|
----
|
||||||
|
|
||||||
_<explanation>_
|
If the interpreter enters an invalid state, it panics.
|
||||||
|
|
||||||
|
=== Unsafe code
|
||||||
|
|
||||||
|
Currently, `#![forbid(unsafe_code)]` is used in the crate, but unsafe code may be used for performance improvements in the future.
|
||||||
=== _<Concept 2>_
|
|
||||||
|
|
||||||
_<explanation>_
|
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
=== _<Concept n>_
|
|
||||||
|
|
||||||
_<explanation>_
|
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,8 @@ It would be simpler to simply walk over the AST during interpretation, but this
|
||||||
|
|
||||||
* There would still have to be a second pass over the AST to resolve labels.
|
* There would still have to be a second pass over the AST to resolve labels.
|
||||||
|
|
||||||
Therefore, an IR is used to resolve the labels and interpret it.
|
Therefore, an IR is used to resolve the labels and interpret it.
|
||||||
|
|
||||||
|
=== Depend on `logos` for the lexer
|
||||||
|
|
||||||
|
Don't write a lexer by hand, let `logos` generate it. This saves development time and makes it easier to add new features to the lexer.
|
||||||
|
|
@ -1,13 +1,29 @@
|
||||||
[[section-quality-scenarios]]
|
[[section-quality-scenarios]]
|
||||||
== Quality Requirements
|
== Quality Requirements
|
||||||
|
|
||||||
|
* Parser tests
|
||||||
|
* Idiomatic Rust code
|
||||||
|
* Good diagnostics
|
||||||
|
|
||||||
=== Quality Tree
|
=== Quality Tree
|
||||||
|
|
||||||
|
[plantuml]
|
||||||
|
----
|
||||||
|
left to right direction
|
||||||
|
(Quality) --> (maintainability)
|
||||||
|
(maintainability) --> (1 parser tests)
|
||||||
|
(maintainability) --> (2 idiomatic rust code)
|
||||||
|
|
||||||
|
(Quality) --> (usability)
|
||||||
|
(usability) --> (3 diagnostics)
|
||||||
|
----
|
||||||
|
|
||||||
=== Quality Scenarios
|
=== Quality Scenarios
|
||||||
|
|
||||||
|
[cols="e,4e" options="header"]
|
||||||
|
|===
|
||||||
|
|ID|Scenario
|
||||||
|
|1|A developer wants to add a new feature to the parser. It should be ensured that they don't break existing functionality.
|
||||||
|
|2|A new developer that is already familiar with rust wants to get started contributing to the project. The project should be familiar to them.
|
||||||
|
|3|Someone wants to use crapderive, but they have syntax errors since they aren't familiar with the language yet. The compiler should help them find the issues and fix them.
|
||||||
|
|===
|
||||||
|
|
@ -7,3 +7,5 @@
|
||||||
|
|
||||||
// where are images located?
|
// where are images located?
|
||||||
:imagesdir: ./images
|
:imagesdir: ./images
|
||||||
|
|
||||||
|
:sourcedir: ../../src
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,15 @@ use dbg_pls::DebugPls;
|
||||||
use logos::Span;
|
use logos::Span;
|
||||||
|
|
||||||
#[derive(Debug, DebugPls)]
|
#[derive(Debug, DebugPls)]
|
||||||
|
// tag::error[]
|
||||||
pub struct CompilerError {
|
pub struct CompilerError {
|
||||||
pub msg: String,
|
pub msg: String,
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
pub notes: Vec<(String, Span)>,
|
pub notes: Vec<(String, Span)>,
|
||||||
pub help: Option<String>,
|
pub help: Option<String>,
|
||||||
}
|
}
|
||||||
|
// end::error[]
|
||||||
|
|
||||||
impl CompilerError {
|
impl CompilerError {
|
||||||
pub fn new(msg: String, span: Span, notes: Vec<(String, Span)>, help: Option<String>) -> Self {
|
pub fn new(msg: String, span: Span, notes: Vec<(String, Span)>, help: Option<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
|
||||||
|
|
@ -212,9 +212,7 @@ impl InterpretCtx {
|
||||||
fn write_addr(&mut self, addr: usize, value: u64) {
|
fn write_addr(&mut self, addr: usize, value: u64) {
|
||||||
assert!(addr + 7 < self.memory.len());
|
assert!(addr + 7 < self.memory.len());
|
||||||
let bytes = value.to_le_bytes();
|
let bytes = value.to_le_bytes();
|
||||||
for i in 0..8 {
|
self.memory[addr..(addr + 8)].copy_from_slice(&bytes[..8]);
|
||||||
self.memory[addr + i] = bytes[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reg(&self, reg: Register) -> u64 {
|
fn reg(&self, reg: Register) -> u64 {
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ impl CompileCtx {
|
||||||
nested.span,
|
nested.span,
|
||||||
"save the first result in a temporary register".to_string(),
|
"save the first result in a temporary register".to_string(),
|
||||||
)),
|
)),
|
||||||
ExprKind::Symbol(_) => return Err(CompilerError::simple(
|
ExprKind::Symbol(_) => Err(CompilerError::simple(
|
||||||
"symbol not allowed here".to_owned(),
|
"symbol not allowed here".to_owned(),
|
||||||
expr.span,
|
expr.span,
|
||||||
))
|
))
|
||||||
|
|
@ -169,7 +169,7 @@ impl CompileCtx {
|
||||||
match expr.kind {
|
match expr.kind {
|
||||||
ExprKind::Number(n) => Ok(Value::Literal(n)),
|
ExprKind::Number(n) => Ok(Value::Literal(n)),
|
||||||
ExprKind::Symbol(_) => {
|
ExprKind::Symbol(_) => {
|
||||||
return Err(CompilerError::simple(
|
Err(CompilerError::simple(
|
||||||
"symbol not allowed here".to_owned(),
|
"symbol not allowed here".to_owned(),
|
||||||
expr.span,
|
expr.span,
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use std::{io, process};
|
use std::{io, process};
|
||||||
|
|
||||||
use crate::error::CompilerError;
|
use crate::error::CompilerError;
|
||||||
|
|
@ -22,6 +24,6 @@ fn main() -> Result<(), io::Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn report_and_exit(file: &str, error: CompilerError) -> ! {
|
fn report_and_exit(file: &str, error: CompilerError) -> ! {
|
||||||
error::report(error, "test.at", &file);
|
error::report(error, "test.at", file);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ impl DebugPls for Stmt {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, DebugPls)]
|
#[derive(Debug, PartialEq, Eq, DebugPls)]
|
||||||
|
// tag::stmt[]
|
||||||
pub enum StmtKind {
|
pub enum StmtKind {
|
||||||
Mov { to: Expr, from: Expr },
|
Mov { to: Expr, from: Expr },
|
||||||
Movb { to: Expr, from: Expr },
|
Movb { to: Expr, from: Expr },
|
||||||
|
|
@ -76,6 +77,7 @@ pub enum StmtKind {
|
||||||
Cmp { lhs: Expr, rhs: Expr },
|
Cmp { lhs: Expr, rhs: Expr },
|
||||||
Label { name: String },
|
Label { name: String },
|
||||||
}
|
}
|
||||||
|
// end::stmt[]
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct Expr {
|
pub struct Expr {
|
||||||
|
|
@ -145,7 +147,7 @@ where
|
||||||
{
|
{
|
||||||
fn program(&mut self) -> Result<Vec<Stmt>> {
|
fn program(&mut self) -> Result<Vec<Stmt>> {
|
||||||
let mut stmts = Vec::new();
|
let mut stmts = Vec::new();
|
||||||
while let Ok(_) = self.peek() {
|
while self.peek().is_ok() {
|
||||||
let stmt = self.stmt()?;
|
let stmt = self.stmt()?;
|
||||||
stmts.push(stmt);
|
stmts.push(stmt);
|
||||||
}
|
}
|
||||||
|
|
@ -220,7 +222,7 @@ where
|
||||||
}
|
}
|
||||||
Token::Label(name) => {
|
Token::Label(name) => {
|
||||||
let name = name
|
let name = name
|
||||||
.strip_suffix(":")
|
.strip_suffix(':')
|
||||||
.expect("lexer produced invalid label")
|
.expect("lexer produced invalid label")
|
||||||
.to_owned();
|
.to_owned();
|
||||||
stmt(span, StmtKind::Label { name })
|
stmt(span, StmtKind::Label { name })
|
||||||
|
|
@ -232,7 +234,7 @@ where
|
||||||
Token::Word(word) => {
|
Token::Word(word) => {
|
||||||
return Err(CompilerError::new(
|
return Err(CompilerError::new(
|
||||||
"{word}".to_string(),
|
"{word}".to_string(),
|
||||||
span.clone(),
|
span,
|
||||||
vec![],
|
vec![],
|
||||||
Some(format!("Consider using a label instead: `{}:`", word)),
|
Some(format!("Consider using a label instead: `{}:`", word)),
|
||||||
))
|
))
|
||||||
|
|
@ -253,7 +255,7 @@ where
|
||||||
}
|
}
|
||||||
Token::Number(n) => expr(ExprKind::Number(n), span),
|
Token::Number(n) => expr(ExprKind::Number(n), span),
|
||||||
Token::Word(name) => {
|
Token::Word(name) => {
|
||||||
if let Some(r_number) = name.strip_prefix("r") {
|
if let Some(r_number) = name.strip_prefix('r') {
|
||||||
if let Ok(n) = r_number.parse::<u8>() {
|
if let Ok(n) = r_number.parse::<u8>() {
|
||||||
if n > 15 {
|
if n > 15 {
|
||||||
return Err(CompilerError::new(
|
return Err(CompilerError::new(
|
||||||
|
|
@ -286,17 +288,15 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn peek(&mut self) -> Result<&(Token<'a>, Span)> {
|
fn peek(&mut self) -> Result<&(Token<'a>, Span)> {
|
||||||
self.iter.peek().ok_or(CompilerError::eof())
|
self.iter.peek().ok_or_else(CompilerError::eof)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next(&mut self) -> Result<(Token<'a>, Span)> {
|
fn next(&mut self) -> Result<(Token<'a>, Span)> {
|
||||||
self.iter.next().ok_or(CompilerError::eof())
|
self.iter.next().ok_or_else(CompilerError::eof)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// tag::parse[]
|
|
||||||
pub fn parse(src: &str) -> Result<Vec<Stmt>> {
|
pub fn parse(src: &str) -> Result<Vec<Stmt>> {
|
||||||
// end::parse[]
|
|
||||||
let lexer = lex(src).spanned();
|
let lexer = lex(src).spanned();
|
||||||
let mut parser = Parser {
|
let mut parser = Parser {
|
||||||
iter: lexer.peekable(),
|
iter: lexer.peekable(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue