This commit is contained in:
nora 2022-07-04 12:13:00 +02:00
parent 30735f6298
commit 8d03964f76
11 changed files with 60 additions and 46 deletions

View file

@ -2,18 +2,19 @@
[[section-building-block-view]]
== Building Block View
==== 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]
----
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`

View file

@ -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.
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>

View file

@ -1,22 +1,23 @@
:sourcedir: ../../src
[[section-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
=== _<Concept 2>_
_<explanation>_
...
=== _<Concept n>_
_<explanation>_
Currently, `#![forbid(unsafe_code)]` is used in the crate, but unsafe code may be used for performance improvements in the future.

View file

@ -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.
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.

View file

@ -1,13 +1,29 @@
[[section-quality-scenarios]]
== Quality Requirements
* Parser tests
* Idiomatic Rust code
* Good diagnostics
=== 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
[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.
|===

View file

@ -7,3 +7,5 @@
// where are images located?
:imagesdir: ./images
:sourcedir: ../../src

View file

@ -3,12 +3,15 @@ use dbg_pls::DebugPls;
use logos::Span;
#[derive(Debug, DebugPls)]
// tag::error[]
pub struct CompilerError {
pub msg: String,
pub span: Span,
pub notes: Vec<(String, Span)>,
pub help: Option<String>,
}
// end::error[]
impl CompilerError {
pub fn new(msg: String, span: Span, notes: Vec<(String, Span)>, help: Option<String>) -> Self {
Self {

View file

@ -212,9 +212,7 @@ impl InterpretCtx {
fn write_addr(&mut self, addr: usize, value: u64) {
assert!(addr + 7 < self.memory.len());
let bytes = value.to_le_bytes();
for i in 0..8 {
self.memory[addr + i] = bytes[i];
}
self.memory[addr..(addr + 8)].copy_from_slice(&bytes[..8]);
}
fn reg(&self, reg: Register) -> u64 {

View file

@ -157,7 +157,7 @@ impl CompileCtx {
nested.span,
"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(),
expr.span,
))
@ -169,7 +169,7 @@ impl CompileCtx {
match expr.kind {
ExprKind::Number(n) => Ok(Value::Literal(n)),
ExprKind::Symbol(_) => {
return Err(CompilerError::simple(
Err(CompilerError::simple(
"symbol not allowed here".to_owned(),
expr.span,
))

View file

@ -1,3 +1,5 @@
#![forbid(unsafe_code)]
use std::{io, process};
use crate::error::CompilerError;
@ -22,6 +24,6 @@ fn main() -> Result<(), io::Error> {
}
fn report_and_exit(file: &str, error: CompilerError) -> ! {
error::report(error, "test.at", &file);
error::report(error, "test.at", file);
process::exit(1);
}

View file

@ -63,6 +63,7 @@ impl DebugPls for Stmt {
}
#[derive(Debug, PartialEq, Eq, DebugPls)]
// tag::stmt[]
pub enum StmtKind {
Mov { to: Expr, from: Expr },
Movb { to: Expr, from: Expr },
@ -76,6 +77,7 @@ pub enum StmtKind {
Cmp { lhs: Expr, rhs: Expr },
Label { name: String },
}
// end::stmt[]
#[derive(Debug, PartialEq, Eq)]
pub struct Expr {
@ -145,7 +147,7 @@ where
{
fn program(&mut self) -> Result<Vec<Stmt>> {
let mut stmts = Vec::new();
while let Ok(_) = self.peek() {
while self.peek().is_ok() {
let stmt = self.stmt()?;
stmts.push(stmt);
}
@ -220,7 +222,7 @@ where
}
Token::Label(name) => {
let name = name
.strip_suffix(":")
.strip_suffix(':')
.expect("lexer produced invalid label")
.to_owned();
stmt(span, StmtKind::Label { name })
@ -232,7 +234,7 @@ where
Token::Word(word) => {
return Err(CompilerError::new(
"{word}".to_string(),
span.clone(),
span,
vec![],
Some(format!("Consider using a label instead: `{}:`", word)),
))
@ -253,7 +255,7 @@ where
}
Token::Number(n) => expr(ExprKind::Number(n), span),
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 n > 15 {
return Err(CompilerError::new(
@ -286,17 +288,15 @@ where
}
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)> {
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>> {
// end::parse[]
let lexer = lex(src).spanned();
let mut parser = Parser {
iter: lexer.peekable(),