Compare commits

..

16 Commits

Author SHA1 Message Date
Jesse Brault
61d7c66e17 Found some unused imports. 2026-03-02 21:16:41 -06:00
Jesse Brault
db675b5601 Write README.md. 2026-03-02 21:16:18 -06:00
Jesse Brault
8c1d56dc1a Remove ir structs and fix misc. warnings. 2026-03-02 20:15:36 -06:00
Jesse Brault
68cb200494 One small todo. 2026-03-02 20:01:29 -06:00
Jesse Brault
0a0065a2c1 Add rudimentary type-use parsing and type-checking parameters. 2026-03-02 20:00:16 -06:00
Jesse Brault
2d601d6115 Add Any type. 2026-03-02 19:45:46 -06:00
Jesse Brault
ec18b5d5ba Yay! Hello world via platform function is working. 2026-03-02 18:22:24 -06:00
Jesse Brault
7c041e40ad Add sort-of-working error recovery to parser. 2026-03-02 14:06:14 -06:00
Jesse Brault
506e260c75 Add logic for checking if callee is platform fn. 2026-03-02 12:51:39 -06:00
Jesse Brault
b7b495178b Add extern_function AST node. 2026-03-02 12:41:11 -06:00
Jesse Brault
35a849233c Add extern keyword. 2026-03-02 11:48:41 -06:00
Jesse Brault
f2f4b0537e Fix lexer whitespace bug. 2026-03-02 11:46:40 -06:00
Jesse Brault
1188e2b671 Add codespan_reporting back into the mix. 2026-03-02 11:39:57 -06:00
Jesse Brault
0960516c4a Very minimal, but e2e hello world sort of working. 2026-03-01 16:11:57 -06:00
Jesse Brault
6593a1cfd1 Assembling directly from AST. 2026-02-28 08:22:57 -06:00
Jesse Brault
e5e1647b70 Multiple statements test. 2026-02-28 06:57:06 -06:00
57 changed files with 2098 additions and 610 deletions

54
Cargo.lock generated
View File

@ -86,9 +86,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.23" version = "4.5.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -96,9 +96,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.23" version = "4.5.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -108,9 +108,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.18" version = "4.5.55"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -120,9 +120,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.7.4" version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
[[package]] [[package]]
name = "codespan-reporting" name = "codespan-reporting"
@ -135,6 +135,17 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "codespan-reporting"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681"
dependencies = [
"serde",
"termcolor",
"unicode-width",
]
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
version = "1.0.3" version = "1.0.3"
@ -186,7 +197,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"ast-generator", "ast-generator",
"clap", "clap",
"codespan-reporting", "codespan-reporting 0.12.0",
"cst-test-generator", "cst-test-generator",
"indoc", "indoc",
"log", "log",
@ -204,9 +215,34 @@ dependencies = [
"crypto-common", "crypto-common",
] ]
[[package]]
name = "dm"
version = "0.1.0"
dependencies = [
"clap",
"codespan-reporting 0.13.1",
"dm-std-lib",
"dmc-lib",
"dvm-lib",
]
[[package]]
name = "dm-std-lib"
version = "0.1.0"
dependencies = [
"dvm-lib",
]
[[package]] [[package]]
name = "dmc-lib" name = "dmc-lib"
version = "0.1.0" version = "0.1.0"
dependencies = [
"dvm-lib",
]
[[package]]
name = "dvm-lib"
version = "0.1.0"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"

View File

@ -3,9 +3,9 @@ name = "deimos"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[[bin]] #[[bin]]
name = "dm" #name = "dm"
path = "src/bin/dvm/main.rs" #path = "src/bin/dvm/lib"
[[bin]] [[bin]]
name = "dmc" name = "dmc"
@ -25,4 +25,4 @@ cst-test-generator = { path = "cst-test-generator" }
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["ast-generator", "cst-test-generator", "dmc-lib"] members = ["ast-generator", "cst-test-generator", "dm", "dm-std-lib", "dmc-lib", "dvm-lib"]

224
README.md Normal file
View File

@ -0,0 +1,224 @@
# Deimos
### Brief History
Deimos lang is a project I properly began in November 2024 while a student at the
University of Wisconsin-Madison. I was learning Rust at the time and was inspired
by its elegance and complexity.
I had had ideas for creating a language as early as 2020, but did not yet have the computer
science skills necessary for designing and implementing it. I was mainly
inspired by the linguistic problems at hand in language design, especially
grammar design and parsing.
After completing an introduction-to-compilers course in spring 2025 (as well as my
summer internship), I returned to work on the project in the fall of that year.
I grew very frustrated with the state of the code base after a few months; my
best software engineering instincts and skills were running up against classic
problems: inflexibility, unmaintainability, and lack of proper testing.
As such, I rebooted the project in February 2026 from scratch. The old project
code all resides under `/src`, whereas one should look in the following directories
for the new, hopefully better, code:
- `/dm`: the main entry program; takes a script and runs it.
- `/dm-std-lib`: the runtime standard library for Deimos (Rust side)
- `/dmc-lib`: the compiler proper
- `/dvm-lib`: the virtual machine
Currently, one can run the `hello.dm` example (from the `/examples` directory)
by running:
`cargo run -p dm -- examples/hello.dm`
You will see `Hello, World!` as well as `None` printed to the console. The `None`
is returned by the Deimos Virtual Machine (dvm) upon completion; I'm planning on
having the `call()` function (in `dvm-lib/src/vm/mod.rs`) return whatever the `main`
(Deimos lang) function returns, possibly limiting it in a manner similar to Rust
(like a `Result<(), Error>` kind of thing).
## Goals
- Fast and embeddable, like Lua
- Scalable, like Java/JVM, but without the complexity
- Elegant syntax, like Ruby/Rust
- Convenient, like Groovy
- Mathematically inspired, like Haskell
## Grammar
Not all of this is implemented yet, nor is this complete by itself.
```
compilation_unit := module_level_declaration*
module_level_declaration := function | extern_function
function := FN identifier L_PARENS parameter_list? R_PARENS statement* END
extern_function := EXTERN FN identifier L_PARENS parameter_list? R_PARENS
identifier := [a-zA-Z_]+
parameter_list := parameter ( COMMA parameter )*
parameter := identifier COLON type_use
type_use := identifier
statement := let_statement | expression_statement
let_statement := LET identifier EQUALS expression
expression_statement := expression
expression := integer_literal | string_literal | call
integer_literal := [0-9]+
string_literal := DOUBLE_QUOTE .* DOUBLE_QUOTE
call := expression L_PARENS expression_list? R_PARENS
expression_list := expression ( COMMA expression )*
```
It's been enough to chew on for now.
## Type System
Again, not all implemented, but these are my ideas:
- Primitives
- Byte, Short, Int, Long (might have unsigned versions, too)
- Char, String (yes, String is primitive; everything UTF-8)
- Boolean
- Arrays
- Interfaces and Classes
- Closures/Function pointers
- Higher-kinded types/Traits like Haskell/Scala
## Examples
The following demonstrate some planned features for the language.
Basic hello world:
```
fn main()
println("Hello, World")
end
```
Map some cli arguments using closures:
```
fn main(args: Array<String>)
args
.map { it.toUppercase() }
.each { println(it) }
end
```
Interface, classes, and some other things:
```
int Animal
fn name() -> String
fn speak() -> Void
end
class Dog(name: String) : Animal
fn name() -> String = self.name
fn speak() -> String = 'Woof'
end
class Cat(name: String) : Animal
fn name() -> String = self.name
fn speak() -> String
self.name == 'Good Kitty' ? 'Meow' : 'MEOW'
end
end
fn main()
let doggo = Dog('Bearo')
let kitty = Cat('Willow')
let animals = [doggo, kitty] // inferred to be List<Animal>
animals.each { println(it.speak) } // Woof | MEOW
end
```
Higher-kinded types and enums:
```
trait Functor[T]
fn <U> map(f: fn (t: T) -> U) -> Self<U>
end
enum Option<T>
Some(T),
None
end
impl<T> Functor[T] for Option<T>
fn map(f: fn: (t: T) -> U) -> Self<U>
if self is Some(t) then
Some(f(t))
else
None
end
end
end
fn main()
let x = Some(42)
let y = x.map { it / 2 }
if y is Some(twentyOne) then
println(twentyOne) // 21
else
throw Exception()
end
end
```
Higher-kinded types can ask for static methods (sort of like Rust):
```
trait Default
static default() -> Self
end
int Animal
fn speak() -> String
end
class Dog(name: String) : Animal, Default
static fn default() -> Self = Dog('Skyeo')
fn speak() -> String = 'My name is ' + self.name
end
fn main()
let sixteenDogs = Array::fill::<Dog>(16) // or something like this
sixteenDogs.each { println(it.speak) } // My name is Skeyo (x16)
end
```
## Implementation
### Compiler
I'm currently using a handwritten lexer and parser. The grammar is embedded
throughout the methods of the Parser implementation; once the grammar is more
stable, I will use a proper predictive-parsing algorithm using a parse table
(built from FIRST and FOLLOW sets), which should be not only faster but much
cleaner.
Each AST node has the following methods:
- `gather_name_declarations`: The first phase of name analysis. This just gathers
all declared names (functions, parameters, variables, etc.), since the plan is
to allow entities that are declared later in the file to be in scope (unlike C,
where forward declarations are required to do so).
- `check_name_usages`: Make sure that usages of names refer to entities that are
in scope.
- `type_check`: Assert that types of parameters/arguments match, etc.
- `assemble`: Convert the AST nodes to Deimos assembly code.
The first three methods must all be called, in the order listed above, as they
constitute distinct, dependent phases of semantic analysis. If there are no
`Diagnostic`s at all from those phases, `assemble` can be safely called.
### Deimos Assembly Code
The `asm_*` modules contain a low-level set of Rust enums/structs which basically
map one-to-one to the actual code used by the virtual machine. I'm trying to
design this language with optimizations and register allocation algorithms in mind.
### Virtual Machine
The virtual machine is inspired by the Lua and Java virtual machines. It is register-based,
and uses a stack for function calls, etc. It supports calling Rust code from Deimos code via
a `PlatformFunction` API.

7
dm-std-lib/Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "dm-std-lib"
version = "0.1.0"
edition = "2024"
[dependencies]
dvm-lib = { path = "../dvm-lib" }

61
dm-std-lib/src/lib.rs Normal file
View File

@ -0,0 +1,61 @@
use dvm_lib::vm::value::Value;
use dvm_lib::vm::{DvmContext, DvmState};
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use std::rc::Rc;
pub fn add_all_std_core(context: &mut DvmContext) {
let platform_functions = context.platform_functions_mut();
platform_functions.insert(Rc::from("println"), std_core_println);
}
struct StdCoreError {
message: String,
}
impl StdCoreError {
pub fn new(message: &str) -> StdCoreError {
Self {
message: message.into(),
}
}
}
impl Debug for StdCoreError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl Display for StdCoreError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl Error for StdCoreError {}
pub fn std_core_println(
_context: &DvmContext,
_state: &DvmState,
args: &[Value],
) -> Result<Value, Box<dyn Error>> {
let maybe_to_print = args.get(0);
match maybe_to_print {
None => Err(Box::new(StdCoreError::new("Missing to_print arg"))),
Some(to_print) => {
match to_print {
Value::Int(i) => {
println!("{}", i);
}
Value::String(s) => {
println!("{}", s);
}
Value::Null => {
println!("null");
}
}
Ok(Value::Null)
}
}
}

11
dm/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "dm"
version = "0.1.0"
edition = "2024"
[dependencies]
dmc-lib = { path = "../dmc-lib" }
dvm-lib = { path = "../dvm-lib" }
dm-std-lib = { path = "../dm-std-lib" }
clap = { version = "4.5.60", features = ["derive"] }
codespan-reporting = "0.13.1"

112
dm/src/main.rs Normal file
View File

@ -0,0 +1,112 @@
use clap::Parser;
use codespan_reporting::diagnostic::Label;
use codespan_reporting::files::SimpleFiles;
use codespan_reporting::term;
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
use dm_std_lib::add_all_std_core;
use dmc_lib::constants_table::ConstantsTable;
use dmc_lib::diagnostic::Diagnostic;
use dmc_lib::parser::parse_compilation_unit;
use dmc_lib::symbol_table::SymbolTable;
use dvm_lib::vm::constant::{Constant, StringConstant};
use dvm_lib::vm::{DvmContext, DvmState, call};
use std::path::PathBuf;
use std::rc::Rc;
#[derive(Debug, Parser)]
#[command(name = "dm", about = "Deimos", version = "0.1.0", long_about = None)]
struct Cli {
script: PathBuf,
#[arg(long)]
show_asm: bool,
}
fn main() {
let args = Cli::parse();
let input = std::fs::read_to_string(&args.script).unwrap();
let mut files: SimpleFiles<&str, &str> = SimpleFiles::new();
let script_file_id = files.add(args.script.to_str().unwrap(), &input);
let parse_result = parse_compilation_unit(&input);
let mut compilation_unit = match parse_result {
Ok(compilation_unit) => compilation_unit,
Err(diagnostics) => {
check_and_report_diagnostics(&files, script_file_id, &diagnostics);
unreachable!();
}
};
let mut symbol_table = SymbolTable::new();
let gather_names_diagnostics = compilation_unit.gather_declared_names(&mut symbol_table);
check_and_report_diagnostics(&files, script_file_id, &gather_names_diagnostics);
let name_usages_diagnostics = compilation_unit.check_name_usages(&symbol_table);
check_and_report_diagnostics(&files, script_file_id, &name_usages_diagnostics);
let type_check_diagnostics = compilation_unit.type_check(&symbol_table);
check_and_report_diagnostics(&files, script_file_id, &type_check_diagnostics);
let mut constants_table = ConstantsTable::new();
let asm_functions = compilation_unit.assemble(&symbol_table, &mut constants_table);
if args.show_asm {
for asm_function in &asm_functions {
println!("{:?}", asm_function);
}
}
let mut dvm_context = DvmContext::new();
// add std::core fns
add_all_std_core(&mut dvm_context);
for asm_function in &asm_functions {
let function = asm_function.dvm();
dvm_context.add_function(function);
}
for (name, content) in &constants_table.string_constants() {
dvm_context.add_constant(Constant::String(StringConstant::new(
Rc::from(name.clone()),
content.as_str(),
)));
}
let mut dvm_state = DvmState::new();
let result = call(&dvm_context, &mut dvm_state, "main", vec![]);
println!("{:?}", result);
}
fn check_and_report_diagnostics(
files: &SimpleFiles<&str, &str>,
script_file_id: usize,
diagnostics: &[Diagnostic],
) {
if !diagnostics.is_empty() {
let writer = StandardStream::stderr(ColorChoice::Always);
let config = term::Config::default();
for diagnostic in diagnostics {
let csr_diagnostic = codespan_reporting::diagnostic::Diagnostic::error()
.with_message(diagnostic.message())
.with_label(Label::primary(
script_file_id,
diagnostic.start()..diagnostic.end(),
));
term::emit_to_write_style(&mut writer.lock(), &config, files, &csr_diagnostic).unwrap();
}
if diagnostics.len() == 1 {
eprintln!("There was 1 diagnostic. See above for more information.");
} else {
eprintln!(
"There were {} diagnostics. See above for more information.",
diagnostics.len()
);
}
std::process::exit(1);
}
}

View File

@ -4,3 +4,4 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
dvm-lib = { path = "../dvm-lib" }

View File

@ -2,12 +2,19 @@ use crate::asm::asm_instruction::AsmInstruction;
#[derive(Debug)] #[derive(Debug)]
pub struct AsmBlock { pub struct AsmBlock {
id: usize, name: String,
instructions: Vec<AsmInstruction>, instructions: Vec<AsmInstruction>,
} }
impl AsmBlock { impl AsmBlock {
pub fn new(id: usize, instructions: Vec<AsmInstruction>) -> Self { pub fn new(name: &str, instructions: Vec<AsmInstruction>) -> Self {
Self { id, instructions } Self {
name: name.into(),
instructions,
}
}
pub fn instructions(&self) -> &[AsmInstruction] {
&self.instructions
} }
} }

View File

@ -1,4 +1,6 @@
use crate::asm::asm_block::AsmBlock; use crate::asm::asm_block::AsmBlock;
use crate::asm::asm_instruction::AsmInstruction;
use dvm_lib::vm::function::Function;
#[derive(Debug)] #[derive(Debug)]
pub struct AsmFunction { pub struct AsmFunction {
@ -13,4 +15,15 @@ impl AsmFunction {
blocks, blocks,
} }
} }
pub fn dvm(&self) -> Function {
// very naive impl
let dvm_instructions = self
.blocks
.iter()
.flat_map(|block| block.instructions().iter().map(AsmInstruction::dvm))
.collect::<Vec<_>>();
Function::new(&self.name, dvm_instructions, 16)
}
} }

View File

@ -1,3 +1,6 @@
use dvm_lib::instruction::Instruction;
use std::rc::Rc;
#[derive(Debug)] #[derive(Debug)]
pub enum AsmInstruction { pub enum AsmInstruction {
Move(Move), Move(Move),
@ -7,10 +10,25 @@ pub enum AsmInstruction {
LoadConstant(LoadConstant), LoadConstant(LoadConstant),
} }
impl AsmInstruction {
pub fn dvm(&self) -> Instruction {
match self {
AsmInstruction::Move(asm_move) => asm_move.dvm(),
AsmInstruction::Push(push) => push.dvm(),
AsmInstruction::Pop(pop) => pop.dvm(),
AsmInstruction::InvokePlatformStatic(invoke_platform_static) => {
invoke_platform_static.dvm()
}
AsmInstruction::LoadConstant(load_constant) => load_constant.dvm(),
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum Operand { pub enum Operand {
IntegerLiteral(i64), IntegerLiteral(i32),
Register(usize), Register(usize),
StackFrameOffset(isize),
} }
#[derive(Debug)] #[derive(Debug)]
@ -26,6 +44,18 @@ impl Move {
destination_register, destination_register,
} }
} }
pub fn dvm(&self) -> Instruction {
match self.source {
Operand::IntegerLiteral(i) => Instruction::MoveInt(i, self.destination_register),
Operand::Register(register) => {
Instruction::MoveRegister(register, self.destination_register)
}
Operand::StackFrameOffset(offset) => {
Instruction::MoveStackFrameOffset(offset, self.destination_register)
}
}
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -37,29 +67,55 @@ impl Push {
pub fn new(source: Operand) -> Self { pub fn new(source: Operand) -> Self {
Self { source } Self { source }
} }
pub fn dvm(&self) -> Instruction {
match self.source {
Operand::IntegerLiteral(i) => Instruction::PushInt(i),
Operand::Register(register) => Instruction::PushRegister(register),
Operand::StackFrameOffset(offset) => Instruction::PushStackFrameOffset(offset),
}
}
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Pop { pub struct Pop {
destination_register: usize, destination_register: Option<usize>,
} }
impl Pop { impl Pop {
pub fn new(destination_register: usize) -> Self { pub fn new(destination_register: usize) -> Self {
Self { Self {
destination_register, destination_register: Some(destination_register),
} }
} }
pub fn empty() -> Self {
Self {
destination_register: None,
}
}
pub fn dvm(&self) -> Instruction {
Instruction::Pop(self.destination_register)
}
} }
#[derive(Debug)] #[derive(Debug)]
pub struct InvokePlatformStatic { pub struct InvokePlatformStatic {
name: String, name: String,
arg_count: usize,
} }
impl InvokePlatformStatic { impl InvokePlatformStatic {
pub fn new(name: &str) -> Self { pub fn new(name: &str, arg_count: usize) -> Self {
Self { name: name.into() } Self {
name: name.into(),
arg_count,
}
}
pub fn dvm(&self) -> Instruction {
Instruction::InvokePlatformStatic(Rc::from(self.name.clone()), self.arg_count)
} }
} }
@ -76,4 +132,8 @@ impl LoadConstant {
destination_register, destination_register,
} }
} }
pub fn dvm(&self) -> Instruction {
Instruction::LoadStringConstant(Rc::from(self.name.clone()), self.destination_register)
}
} }

View File

@ -1,3 +1,47 @@
pub mod asm_block; pub mod asm_block;
pub mod asm_function; pub mod asm_function;
pub mod asm_instruction; pub mod asm_instruction;
#[cfg(test)]
mod smoke_tests {
use crate::asm::asm_function::AsmFunction;
use crate::constants_table::ConstantsTable;
use crate::parser::parse_compilation_unit;
use crate::symbol_table::SymbolTable;
fn assemble(src: &str) -> Vec<AsmFunction> {
let parse_result = parse_compilation_unit(src);
let mut compilation_unit = match parse_result {
Ok(compilation_unit) => compilation_unit,
Err(diagnostics) => {
for diagnostic in diagnostics {
eprintln!("{:?}", diagnostic);
}
panic!();
}
};
let mut symbol_table = SymbolTable::new();
compilation_unit.gather_declared_names(&mut symbol_table);
compilation_unit.check_name_usages(&symbol_table);
compilation_unit.type_check(&symbol_table);
compilation_unit.assemble(&symbol_table, &mut ConstantsTable::new())
}
#[test]
fn multiple_statements() {
let asm_functions = assemble(
"
extern fn println()
fn main()
let x = 42
println(x)
println(16)
let y = \"Hello, World!\"
println(y)
end",
);
for asm_function in &asm_functions {
println!("{:#?}", asm_function);
}
}
}

View File

@ -0,0 +1,114 @@
use crate::asm::asm_block::AsmBlock;
use crate::asm::asm_function::AsmFunction;
use crate::asm::asm_instruction::AsmInstruction;
use crate::source_range::SourceRange;
pub struct AssembleContext {
functions: Vec<AsmFunction>,
current_function: Option<FunctionAssembler>,
}
impl AssembleContext {
pub fn new() -> Self {
Self {
functions: vec![],
current_function: None,
}
}
pub fn take_functions(&mut self) -> Vec<AsmFunction> {
std::mem::take(&mut self.functions)
}
pub fn new_function(&mut self, name: &str, source_range: &SourceRange) {
self.current_function = Some(FunctionAssembler::new(name, source_range));
}
pub fn complete_function(&mut self) {
let mut function_assembler = self.current_function.take().unwrap();
function_assembler.complete_block();
let asm_function = function_assembler.result();
self.functions.push(asm_function);
}
pub fn new_block(&mut self, name: &str) {
self.current_function.as_mut().unwrap().new_block(name);
}
pub fn new_local_register(&mut self) -> usize {
self.current_function.as_mut().unwrap().new_local_register()
}
pub fn instruction(&mut self, to_add: AsmInstruction) {
self.current_function.as_mut().unwrap().instruction(to_add);
}
}
struct FunctionAssembler {
name: String,
source_range: SourceRange,
blocks: Vec<AsmBlock>,
current_block: Option<BlockAssembler>,
local_register_counter: usize,
}
impl FunctionAssembler {
fn new(name: &str, source_range: &SourceRange) -> Self {
Self {
name: name.into(),
source_range: source_range.clone(),
blocks: vec![],
current_block: None,
local_register_counter: 0,
}
}
fn new_block(&mut self, name: &str) {
let maybe_block_assembler = self.current_block.take();
if let Some(mut block_assembler) = maybe_block_assembler {
self.blocks.push(block_assembler.result());
}
self.current_block = Some(BlockAssembler::new(name))
}
fn complete_block(&mut self) {
let mut block_assembler = self.current_block.take().unwrap();
self.blocks.push(block_assembler.result());
}
fn result(&mut self) -> AsmFunction {
AsmFunction::new(&self.name, std::mem::take(&mut self.blocks))
}
fn new_local_register(&mut self) -> usize {
let register = self.local_register_counter;
self.local_register_counter += 1;
register
}
fn instruction(&mut self, to_add: AsmInstruction) {
self.current_block.as_mut().unwrap().instruction(to_add);
}
}
struct BlockAssembler {
name: String,
instructions: Vec<AsmInstruction>,
}
impl BlockAssembler {
fn new(name: &str) -> Self {
Self {
name: name.into(),
instructions: vec![],
}
}
fn instruction(&mut self, to_add: AsmInstruction) {
self.instructions.push(to_add);
}
fn result(&mut self) -> AsmBlock {
AsmBlock::new(&self.name, std::mem::take(&mut self.instructions))
}
}

View File

@ -1,9 +1,12 @@
use crate::asm::asm_instruction::{
AsmInstruction, InvokePlatformStatic, LoadConstant, Operand, Push,
};
use crate::ast::assemble_context::AssembleContext;
use crate::ast::expression::Expression; use crate::ast::expression::Expression;
use crate::ast::function::FunctionLoweringContext; use crate::constants_table::ConstantsTable;
use crate::diagnostic::Diagnostic; use crate::diagnostic::Diagnostic;
use crate::ir::ir_call::IrCall;
use crate::ir::ir_expression::IrExpression;
use crate::source_range::SourceRange; use crate::source_range::SourceRange;
use crate::symbol::ExpressibleSymbol;
use crate::symbol_table::SymbolTable; use crate::symbol_table::SymbolTable;
use crate::type_info::TypeInfo; use crate::type_info::TypeInfo;
@ -56,14 +59,55 @@ impl Call {
} }
// check that callee is callable // check that callee is callable
match self.callee.type_info() { let function_symbol = match self.callee.type_info() {
TypeInfo::Function(_) => {} TypeInfo::Function(function_symbol) => function_symbol,
_ => { _ => {
diagnostics.push(Diagnostic::new( diagnostics.push(Diagnostic::new(
"Receiver is not callable", "Receiver is not callable",
self.callee.source_range().start(), self.callee.source_range().start(),
self.callee.source_range().end(), self.callee.source_range().end(),
)); ));
return diagnostics;
}
};
// check arguments length
let function_symbol_ref = function_symbol.borrow();
let parameters = function_symbol_ref.parameters();
if parameters.len() != self.arguments.len() {
diagnostics.push(Diagnostic::new(
&format!(
"Wrong number of arguments; expected {} but found {}",
parameters.len(),
self.arguments.len()
),
self.source_range.start(),
self.source_range.end(),
));
}
if !diagnostics.is_empty() {
return diagnostics;
}
// check argument types
for i in 0..parameters.len() {
let parameter = &parameters[i];
let argument = &self.arguments[i];
if !parameter
.borrow()
.type_info()
.is_assignable_from(&argument.type_info())
{
diagnostics.push(Diagnostic::new(
&format!(
"Mismatched types; expected {} but found {}",
parameter.borrow().type_info(),
argument.type_info()
),
argument.source_range().start(),
argument.source_range().end(),
))
} }
} }
@ -72,22 +116,79 @@ impl Call {
pub fn type_info(&self) -> TypeInfo { pub fn type_info(&self) -> TypeInfo {
match self.callee.type_info() { match self.callee.type_info() {
TypeInfo::Function(function_symbol) => function_symbol.return_type(), TypeInfo::Function(function_symbol) => function_symbol.borrow().return_type(),
_ => panic!(), _ => panic!(),
} }
} }
pub fn lower_to_ir(&self, context: &mut FunctionLoweringContext) -> IrExpression { pub fn assemble(
let function_name = match self.callee() { &self,
Expression::Identifier(identifier) => identifier.name(), context: &mut AssembleContext,
symbol_table: &SymbolTable,
constants_table: &mut ConstantsTable,
) {
// push all args
for argument in &self.arguments {
match argument {
Expression::Call(call) => {
call.assemble(context, symbol_table, constants_table); // will leave return val on stack
}
Expression::IntegerLiteral(integer_literal) => {
context.instruction(AsmInstruction::Push(Push::new(Operand::IntegerLiteral(
integer_literal.value(),
))));
}
Expression::String(string_literal) => {
let name = constants_table.insert_string(string_literal.content());
let temp_register = context.new_local_register();
context.instruction(AsmInstruction::LoadConstant(LoadConstant::new(
&name,
temp_register,
)));
context.instruction(AsmInstruction::Push(Push::new(Operand::Register(
temp_register,
))));
}
Expression::Identifier(identifier) => match identifier.expressible_symbol() {
ExpressibleSymbol::Function(_) => {
panic!("Pushing function symbols not supported.")
}
ExpressibleSymbol::Parameter(parameter_symbol) => {
context.instruction(AsmInstruction::Push(Push::new(
Operand::StackFrameOffset(
parameter_symbol.borrow().stack_frame_offset(),
),
)));
}
ExpressibleSymbol::Variable(variable_symbol) => {
context.instruction(AsmInstruction::Push(Push::new(Operand::Register(
variable_symbol.borrow().register(),
))))
}
},
}
}
let function_symbol = match self.callee() {
Expression::Identifier(identifier) => {
let expressible_symbol = identifier.expressible_symbol();
match expressible_symbol {
ExpressibleSymbol::Function(function_symbol) => function_symbol.clone(),
_ => panic!("Calling things other than functions not yet supported."),
}
}
_ => panic!("Calling things other than identifiers not yet supported."), _ => panic!("Calling things other than identifiers not yet supported."),
}; };
let arguments = self
.arguments() let function_symbol = function_symbol.borrow();
.iter() if function_symbol.is_platform() {
.map(|arg| arg.lower_to_ir(context)) let arg_count = function_symbol.parameters().len();
.collect(); context.instruction(AsmInstruction::InvokePlatformStatic(
IrExpression::Call(IrCall::new(function_name, arguments)) InvokePlatformStatic::new(function_symbol.name(), arg_count),
));
} else {
todo!("non-platform invoke")
}
} }
pub fn source_range(&self) -> &SourceRange { pub fn source_range(&self) -> &SourceRange {

View File

@ -1,26 +1,28 @@
use crate::ast::function::Function; use crate::asm::asm_function::AsmFunction;
use crate::ast::assemble_context::AssembleContext;
use crate::ast::module_level_declaration::ModuleLevelDeclaration;
use crate::constants_table::ConstantsTable;
use crate::diagnostic::Diagnostic; use crate::diagnostic::Diagnostic;
use crate::ir::Ir;
use crate::symbol_table::SymbolTable; use crate::symbol_table::SymbolTable;
pub struct CompilationUnit { pub struct CompilationUnit {
functions: Vec<Function>, declarations: Vec<ModuleLevelDeclaration>,
} }
impl CompilationUnit { impl CompilationUnit {
pub fn new(functions: Vec<Function>) -> Self { pub fn new(declarations: Vec<ModuleLevelDeclaration>) -> Self {
Self { functions } Self { declarations }
} }
pub fn functions(&self) -> Vec<&Function> { pub fn declarations(&self) -> &[ModuleLevelDeclaration] {
self.functions.iter().collect() &self.declarations
} }
pub fn gather_declared_names(&mut self, symbol_table: &mut SymbolTable) -> Vec<Diagnostic> { pub fn gather_declared_names(&mut self, symbol_table: &mut SymbolTable) -> Vec<Diagnostic> {
let mut diagnostics = vec![]; let mut diagnostics = vec![];
symbol_table.push_scope("compilation_unit_scope"); symbol_table.push_scope("compilation_unit_scope");
for function in &mut self.functions { for declaration in &mut self.declarations {
diagnostics.append(&mut function.gather_declared_names(symbol_table)); diagnostics.append(&mut declaration.gather_declared_names(symbol_table));
} }
symbol_table.pop_scope(); symbol_table.pop_scope();
diagnostics diagnostics
@ -28,25 +30,29 @@ impl CompilationUnit {
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> { pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
let mut diagnostics = vec![]; let mut diagnostics = vec![];
for function in &mut self.functions { for declaration in &mut self.declarations {
diagnostics.append(&mut function.check_name_usages(symbol_table)); diagnostics.append(&mut declaration.check_name_usages(symbol_table));
} }
diagnostics diagnostics
} }
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> { pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
let mut diagnostics = vec![]; let mut diagnostics = vec![];
for function in &mut self.functions { for declaration in &mut self.declarations {
diagnostics.append(&mut function.type_check(symbol_table)); diagnostics.append(&mut declaration.type_check(symbol_table));
} }
diagnostics diagnostics
} }
pub fn lower_to_ir(&self) -> Vec<Ir> { pub fn assemble(
let mut irs = vec![]; &self,
for function in &self.functions { symbol_table: &SymbolTable,
irs.append(&mut function.lower_to_ir()); constants_table: &mut ConstantsTable,
) -> Vec<AsmFunction> {
let mut context = AssembleContext::new();
for declaration in &self.declarations {
declaration.assemble(&mut context, symbol_table, constants_table);
} }
irs context.take_functions()
} }
} }

View File

@ -1,10 +1,8 @@
use crate::ast::call::Call; use crate::ast::call::Call;
use crate::ast::function::FunctionLoweringContext;
use crate::ast::identifier::Identifier; use crate::ast::identifier::Identifier;
use crate::ast::integer_literal::IntegerLiteral; use crate::ast::integer_literal::IntegerLiteral;
use crate::ast::string_literal::StringLiteral; use crate::ast::string_literal::StringLiteral;
use crate::diagnostic::Diagnostic; use crate::diagnostic::Diagnostic;
use crate::ir::ir_expression::IrExpression;
use crate::source_range::SourceRange; use crate::source_range::SourceRange;
use crate::symbol_table::SymbolTable; use crate::symbol_table::SymbolTable;
use crate::type_info::TypeInfo; use crate::type_info::TypeInfo;
@ -57,13 +55,4 @@ impl Expression {
Expression::Identifier(identifier) => identifier.source_range(), Expression::Identifier(identifier) => identifier.source_range(),
} }
} }
pub fn lower_to_ir(&self, context: &mut FunctionLoweringContext) -> IrExpression {
match self {
Expression::Call(call) => call.lower_to_ir(context),
Expression::IntegerLiteral(integer_literal) => integer_literal.lower_to_ir(context),
Expression::String(string_literal) => string_literal.lower_to_ir(context),
Expression::Identifier(identifier) => identifier.lower_to_ir(context),
}
}
} }

View File

@ -1,8 +1,7 @@
use crate::ast::assemble_context::AssembleContext;
use crate::ast::expression::Expression; use crate::ast::expression::Expression;
use crate::ast::function::FunctionLoweringContext; use crate::constants_table::ConstantsTable;
use crate::diagnostic::Diagnostic; use crate::diagnostic::Diagnostic;
use crate::ir::ir_expression::IrExpression;
use crate::ir::ir_statement::IrStatement;
use crate::symbol_table::SymbolTable; use crate::symbol_table::SymbolTable;
pub struct ExpressionStatement { pub struct ExpressionStatement {
@ -32,21 +31,17 @@ impl ExpressionStatement {
self.expression.type_check(symbol_table) self.expression.type_check(symbol_table)
} }
pub fn lower_to_ir(&self, context: &mut FunctionLoweringContext) { pub fn assemble(
let ir_expression = self.expression.lower_to_ir(context); &self,
match ir_expression { context: &mut AssembleContext,
IrExpression::Call(ir_call) => { symbol_table: &SymbolTable,
context.add_statement(IrStatement::Call(ir_call)); constants_table: &mut ConstantsTable,
} ) {
IrExpression::Constant(ir_constant) => { match self.expression.as_ref() {
unimplemented!() Expression::Call(call) => {
} call.assemble(context, symbol_table, constants_table);
IrExpression::IntegerLiteral(i) => {
unimplemented!()
}
IrExpression::Variable(ir_variable) => {
unimplemented!()
} }
_ => unreachable!(),
} }
} }
} }

View File

@ -0,0 +1,90 @@
use crate::ast::parameter::Parameter;
use crate::diagnostic::Diagnostic;
use crate::source_range::SourceRange;
use crate::symbol::FunctionSymbol;
use crate::symbol_table::{SymbolInsertError, SymbolTable};
use std::cell::RefCell;
use std::rc::Rc;
pub struct ExternFunction {
declared_name: String,
declared_name_source_range: SourceRange,
parameters: Vec<Parameter>,
}
impl ExternFunction {
pub fn new(
name: &str,
declared_name_source_range: SourceRange,
parameters: Vec<Parameter>,
) -> ExternFunction {
ExternFunction {
declared_name: name.into(),
declared_name_source_range,
parameters,
}
}
pub fn declared_name(&self) -> &str {
&self.declared_name
}
pub fn gather_declared_names(&mut self, symbol_table: &mut SymbolTable) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
let insert_result =
symbol_table.insert_function_symbol(FunctionSymbol::new(&self.declared_name, true));
let mut maybe_function_symbol: Option<Rc<RefCell<FunctionSymbol>>> = None;
match insert_result {
Ok(function_symbol) => {
maybe_function_symbol = Some(function_symbol);
}
Err(symbol_insert_error) => match symbol_insert_error {
SymbolInsertError::AlreadyDeclared(already_declared) => {
diagnostics.push(Diagnostic::new(
&format!(
"Function {} already declared in current scope.",
already_declared.name()
),
self.declared_name_source_range.start(),
self.declared_name_source_range.end(),
));
}
},
}
symbol_table.push_scope(&format!("extern_function_scope({})", &self.declared_name));
let mut parameter_symbols = vec![];
for parameter in &mut self.parameters {
let parameter_result = parameter.gather_declared_names(symbol_table);
match parameter_result {
Ok(parameter_symbol) => {
parameter_symbols.push(parameter_symbol);
}
Err(mut parameter_diagnostics) => {
diagnostics.append(&mut parameter_diagnostics);
}
}
}
maybe_function_symbol
.unwrap()
.borrow_mut()
.set_parameters(parameter_symbols);
symbol_table.pop_scope();
diagnostics
}
pub fn check_name_usages(&mut self, _symbol_table: &SymbolTable) -> Vec<Diagnostic> {
// no-op (for now)
vec![]
}
pub fn type_check(&mut self, _symbol_table: &SymbolTable) -> Vec<Diagnostic> {
// no-op (for now)
vec![]
}
}

View File

@ -1,9 +1,7 @@
use crate::ast::assemble_context::AssembleContext;
use crate::ast::statement::Statement; use crate::ast::statement::Statement;
use crate::constants_table::ConstantsTable;
use crate::diagnostic::Diagnostic; use crate::diagnostic::Diagnostic;
use crate::ir::Ir;
use crate::ir::ir_constant::IrConstant;
use crate::ir::ir_function::IrFunction;
use crate::ir::ir_statement::IrStatement;
use crate::source_range::SourceRange; use crate::source_range::SourceRange;
use crate::symbol::FunctionSymbol; use crate::symbol::FunctionSymbol;
use crate::symbol_table::{SymbolInsertError, SymbolTable}; use crate::symbol_table::{SymbolInsertError, SymbolTable};
@ -38,10 +36,8 @@ impl Function {
pub fn gather_declared_names(&mut self, symbol_table: &mut SymbolTable) -> Vec<Diagnostic> { pub fn gather_declared_names(&mut self, symbol_table: &mut SymbolTable) -> Vec<Diagnostic> {
let mut diagnostics = vec![]; let mut diagnostics = vec![];
// insert function symbol // insert function symbol
let insert_result = symbol_table.insert_function_symbol(FunctionSymbol::new( let insert_result =
self.declared_name(), symbol_table.insert_function_symbol(FunctionSymbol::new(self.declared_name(), false));
&vec![], // todo
));
if let Err(symbol_insert_error) = insert_result { if let Err(symbol_insert_error) = insert_result {
match symbol_insert_error { match symbol_insert_error {
SymbolInsertError::AlreadyDeclared(already_declared) => { SymbolInsertError::AlreadyDeclared(already_declared) => {
@ -56,6 +52,9 @@ impl Function {
} }
} }
} }
// todo: parameters
symbol_table.push_scope(&format!("function_scope({})", self.declared_name())); symbol_table.push_scope(&format!("function_scope({})", self.declared_name()));
for statement in &mut self.statements { for statement in &mut self.statements {
diagnostics.append(&mut statement.gather_declared_names(symbol_table)); diagnostics.append(&mut statement.gather_declared_names(symbol_table));
@ -80,63 +79,17 @@ impl Function {
diagnostics diagnostics
} }
pub fn lower_to_ir(&self) -> Vec<Ir> { pub fn assemble(
let mut context = FunctionLoweringContext::new(); &self,
context: &mut AssembleContext,
symbol_table: &SymbolTable,
constants_table: &mut ConstantsTable,
) {
context.new_function(&self.declared_name, &self.declared_name_source_range);
context.new_block(&format!("{}_enter", self.declared_name));
for statement in &self.statements { for statement in &self.statements {
statement.lower_to_ir(&mut context); statement.assemble(context, symbol_table, constants_table);
} }
let mut irs = vec![]; context.complete_function();
for constant in context.take_constants() {
irs.push(Ir::Constant(constant));
}
let ir_function = IrFunction::new(&self.declared_name, context.take_statements());
irs.push(Ir::Function(ir_function));
irs
}
}
pub struct FunctionLoweringContext {
temp_variable_counter: usize,
constant_counter: usize,
constants: Vec<IrConstant>,
statements: Vec<IrStatement>,
}
impl FunctionLoweringContext {
pub fn new() -> Self {
Self {
temp_variable_counter: 0,
constant_counter: 0,
constants: vec![],
statements: vec![],
}
}
pub fn next_temp_variable(&mut self) -> String {
let temp_variable = format!("t_{}", self.temp_variable_counter);
self.temp_variable_counter += 1;
temp_variable
}
pub fn next_constant_name(&mut self) -> String {
let constant_name = format!("%const_{}", self.constant_counter);
self.constant_counter += 1;
constant_name
}
pub fn add_constant(&mut self, constant: IrConstant) {
self.constants.push(constant);
}
pub fn take_constants(&mut self) -> Vec<IrConstant> {
std::mem::take(&mut self.constants)
}
pub fn add_statement(&mut self, statement: IrStatement) {
self.statements.push(statement);
}
pub fn take_statements(&mut self) -> Vec<IrStatement> {
std::mem::take(&mut self.statements)
} }
} }

View File

@ -1,7 +1,4 @@
use crate::ast::function::FunctionLoweringContext;
use crate::diagnostic::Diagnostic; use crate::diagnostic::Diagnostic;
use crate::ir::ir_expression::IrExpression;
use crate::ir::ir_variable::IrVariable;
use crate::source_range::SourceRange; use crate::source_range::SourceRange;
use crate::symbol::ExpressibleSymbol; use crate::symbol::ExpressibleSymbol;
use crate::symbol_table::SymbolTable; use crate::symbol_table::SymbolTable;
@ -56,13 +53,17 @@ impl Identifier {
ExpressibleSymbol::Function(function_symbol) => { ExpressibleSymbol::Function(function_symbol) => {
TypeInfo::Function(function_symbol.clone()) TypeInfo::Function(function_symbol.clone())
} }
ExpressibleSymbol::Parameter(parameter_symbol) => parameter_symbol.type_info().clone(), ExpressibleSymbol::Parameter(parameter_symbol) => {
ExpressibleSymbol::Variable(variable_symbol) => variable_symbol.type_info().clone(), parameter_symbol.borrow().type_info().clone()
}
ExpressibleSymbol::Variable(variable_symbol) => {
variable_symbol.borrow().type_info().clone()
}
} }
} }
pub fn lower_to_ir(&self, context: &mut FunctionLoweringContext) -> IrExpression { pub fn expressible_symbol(&self) -> &ExpressibleSymbol {
IrExpression::Variable(IrVariable::new(self.name())) self.expressible_symbol.as_ref().unwrap()
} }
pub fn source_range(&self) -> &SourceRange { pub fn source_range(&self) -> &SourceRange {

View File

@ -1,28 +1,22 @@
use crate::ast::function::FunctionLoweringContext;
use crate::ir::ir_expression::IrExpression;
use crate::source_range::SourceRange; use crate::source_range::SourceRange;
pub struct IntegerLiteral { pub struct IntegerLiteral {
value: i64, value: i32,
source_range: SourceRange, source_range: SourceRange,
} }
impl IntegerLiteral { impl IntegerLiteral {
pub fn new(value: i64, source_range: SourceRange) -> Self { pub fn new(value: i32, source_range: SourceRange) -> Self {
Self { Self {
value, value,
source_range, source_range,
} }
} }
pub fn value(&self) -> i64 { pub fn value(&self) -> i32 {
self.value self.value
} }
pub fn lower_to_ir(&self, context: &mut FunctionLoweringContext) -> IrExpression {
IrExpression::IntegerLiteral(self.value)
}
pub fn source_range(&self) -> &SourceRange { pub fn source_range(&self) -> &SourceRange {
&self.source_range &self.source_range
} }

View File

@ -1,17 +1,17 @@
use crate::asm::asm_instruction::{AsmInstruction, LoadConstant, Move, Operand, Pop};
use crate::ast::assemble_context::AssembleContext;
use crate::ast::expression::Expression; use crate::ast::expression::Expression;
use crate::ast::function::FunctionLoweringContext; use crate::constants_table::ConstantsTable;
use crate::diagnostic::Diagnostic; use crate::diagnostic::Diagnostic;
use crate::ir::ir_assign::IrAssign;
use crate::ir::ir_statement::IrStatement;
use crate::ir::ir_variable::IrVariable;
use crate::source_range::SourceRange; use crate::source_range::SourceRange;
use crate::symbol::VariableSymbol; use crate::symbol::{ExpressibleSymbol, VariableSymbol};
use crate::symbol_table::{SymbolInsertError, SymbolTable}; use crate::symbol_table::{SymbolInsertError, SymbolTable};
pub struct LetStatement { pub struct LetStatement {
declared_name: String, declared_name: String,
declared_name_source_range: SourceRange, declared_name_source_range: SourceRange,
initializer: Box<Expression>, initializer: Box<Expression>,
scope_id: Option<usize>,
} }
impl LetStatement { impl LetStatement {
@ -24,6 +24,7 @@ impl LetStatement {
declared_name: declared_name.to_string(), declared_name: declared_name.to_string(),
declared_name_source_range, declared_name_source_range,
initializer: initializer.into(), initializer: initializer.into(),
scope_id: None,
} }
} }
@ -60,6 +61,7 @@ impl LetStatement {
} }
} }
} }
self.scope_id = Some(symbol_table.current_scope_id());
diagnostics diagnostics
} }
@ -73,10 +75,61 @@ impl LetStatement {
diagnostics diagnostics
} }
pub fn lower_to_ir(&self, context: &mut FunctionLoweringContext) { pub fn assemble(
let data = self.initializer.lower_to_ir(context); &self,
let destination = IrVariable::new(self.declared_name()); context: &mut AssembleContext,
let assign_statement = IrAssign::new(destination, data); symbol_table: &SymbolTable,
context.add_statement(IrStatement::Assign(assign_statement)); constants_table: &mut ConstantsTable,
) {
let destination_register = context.new_local_register();
// save register to symbol
let variable_symbol =
symbol_table.get_variable_symbol(self.scope_id.unwrap(), self.declared_name());
variable_symbol
.borrow_mut()
.set_register(destination_register);
match self.initializer() {
Expression::Call(call) => {
call.assemble(context, symbol_table, constants_table);
context.instruction(AsmInstruction::Pop(Pop::new(destination_register)));
}
Expression::IntegerLiteral(integer_literal) => {
context.instruction(AsmInstruction::Move(Move::new(
Operand::IntegerLiteral(integer_literal.value()),
destination_register,
)));
}
Expression::String(string_literal) => {
let name = constants_table.insert_string(string_literal.content());
context.instruction(AsmInstruction::LoadConstant(LoadConstant::new(
&name,
destination_register,
)));
}
Expression::Identifier(identifier) => {
let expressible_symbol = identifier.expressible_symbol();
match expressible_symbol {
ExpressibleSymbol::Function(_) => {
panic!("Moving functions to registers not yet supported");
}
ExpressibleSymbol::Parameter(parameter_symbol) => {
context.instruction(AsmInstruction::Move(Move::new(
Operand::StackFrameOffset(
parameter_symbol.borrow().stack_frame_offset(),
),
destination_register,
)));
}
ExpressibleSymbol::Variable(variable_symbol) => {
context.instruction(AsmInstruction::Move(Move::new(
Operand::Register(variable_symbol.borrow().register()),
destination_register,
)));
}
}
}
}
} }
} }

View File

@ -1,83 +1,16 @@
pub mod assemble_context;
pub mod call; pub mod call;
pub mod compilation_unit; pub mod compilation_unit;
pub mod expression; pub mod expression;
pub mod expression_statement; pub mod expression_statement;
pub mod extern_function;
pub mod fqn; pub mod fqn;
pub mod function; pub mod function;
pub mod identifier; pub mod identifier;
pub mod integer_literal; pub mod integer_literal;
pub mod let_statement; pub mod let_statement;
pub mod module_level_declaration;
pub mod parameter;
pub mod statement; pub mod statement;
pub mod string_literal; pub mod string_literal;
pub mod type_use;
#[cfg(test)]
mod name_tests {
use crate::ir::Ir;
use crate::ir::assemble_context::AssembleContext;
use crate::parser::parse_compilation_unit;
use crate::symbol_table::SymbolTable;
#[test]
fn smoke_screen() {
let mut symbol_table = SymbolTable::new();
let mut compilation_unit =
parse_compilation_unit("fn println() end fn main() let x = 42 println(x) end");
assert_eq!(
compilation_unit
.gather_declared_names(&mut symbol_table)
.len(),
0
);
assert_eq!(compilation_unit.check_name_usages(&symbol_table).len(), 0);
let irs = compilation_unit.lower_to_ir();
for ir in &irs {
println!("{:#?}", ir);
}
for ir in &irs {
match ir {
Ir::Function(ir_function) => {
let asm_function = ir_function.assemble(&mut AssembleContext::new());
println!("{:#?}", asm_function);
}
_ => {}
}
}
}
#[test]
fn hello_world() {
let mut symbol_table = SymbolTable::new();
let mut compilation_unit =
parse_compilation_unit("fn println() end fn main() println(\"Hello, World!\") end");
compilation_unit.gather_declared_names(&mut symbol_table);
compilation_unit.check_name_usages(&symbol_table);
compilation_unit.type_check(&symbol_table);
let irs = compilation_unit.lower_to_ir();
for ir in &irs {
println!("{:#?}", ir);
}
for ir in &irs {
match ir {
Ir::Function(ir_function) => {
let asm_function = ir_function.assemble(&mut AssembleContext::new());
println!("{:#?}", asm_function);
}
_ => {}
}
}
}
#[test]
fn get_some_diagnostics() {
let mut symbol_table = SymbolTable::new();
let mut compilation_unit = parse_compilation_unit("fn main() notDefined(uhOh) end");
assert_eq!(
compilation_unit
.gather_declared_names(&mut symbol_table)
.len(),
0
);
let name_usage_diagnostics = compilation_unit.check_name_usages(&symbol_table);
assert_eq!(name_usage_diagnostics.len(), 2);
}
}

View File

@ -0,0 +1,58 @@
use crate::ast::assemble_context::AssembleContext;
use crate::ast::extern_function::ExternFunction;
use crate::ast::function::Function;
use crate::constants_table::ConstantsTable;
use crate::diagnostic::Diagnostic;
use crate::symbol_table::SymbolTable;
pub enum ModuleLevelDeclaration {
Function(Function),
ExternFunction(ExternFunction),
}
impl ModuleLevelDeclaration {
pub fn gather_declared_names(&mut self, symbol_table: &mut SymbolTable) -> Vec<Diagnostic> {
match self {
ModuleLevelDeclaration::Function(function) => {
function.gather_declared_names(symbol_table)
}
ModuleLevelDeclaration::ExternFunction(extern_function) => {
extern_function.gather_declared_names(symbol_table)
}
}
}
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
match self {
ModuleLevelDeclaration::Function(function) => function.check_name_usages(symbol_table),
ModuleLevelDeclaration::ExternFunction(extern_function) => {
extern_function.check_name_usages(symbol_table)
}
}
}
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
match self {
ModuleLevelDeclaration::Function(function) => function.type_check(symbol_table),
ModuleLevelDeclaration::ExternFunction(extern_function) => {
extern_function.type_check(symbol_table)
}
}
}
pub fn assemble(
&self,
context: &mut AssembleContext,
symbol_table: &SymbolTable,
constants_table: &mut ConstantsTable,
) {
match self {
ModuleLevelDeclaration::Function(function) => {
function.assemble(context, symbol_table, constants_table)
}
ModuleLevelDeclaration::ExternFunction(_) => {
// no-op
}
}
}
}

View File

@ -0,0 +1,48 @@
use crate::ast::type_use::TypeUse;
use crate::diagnostic::Diagnostic;
use crate::source_range::SourceRange;
use crate::symbol::ParameterSymbol;
use crate::symbol_table::{SymbolInsertError, SymbolTable};
use crate::type_info::TypeInfo;
use std::cell::RefCell;
use std::rc::Rc;
pub struct Parameter {
declared_name: String,
declared_name_source_range: SourceRange,
type_use: TypeUse,
}
impl Parameter {
pub fn new(
declared_name: &str,
declared_name_source_range: SourceRange,
type_use: TypeUse,
) -> Self {
Self {
declared_name: declared_name.into(),
declared_name_source_range,
type_use,
}
}
pub fn gather_declared_names(
&mut self,
symbol_table: &mut SymbolTable,
) -> Result<Rc<RefCell<ParameterSymbol>>, Vec<Diagnostic>> {
let insert_result = symbol_table.insert_parameter_symbol(ParameterSymbol::new(
&self.declared_name,
TypeInfo::from_declared_name(self.type_use.declared_name()),
));
match insert_result {
Ok(parameter_symbol) => Ok(parameter_symbol),
Err(symbol_insert_error) => match symbol_insert_error {
SymbolInsertError::AlreadyDeclared(already_declared) => Err(vec![Diagnostic::new(
&format!("Parameter {} already declared.", already_declared.name()),
self.declared_name_source_range.start(),
self.declared_name_source_range.end(),
)]),
},
}
}
}

View File

@ -1,6 +1,7 @@
use crate::ast::assemble_context::AssembleContext;
use crate::ast::expression_statement::ExpressionStatement; use crate::ast::expression_statement::ExpressionStatement;
use crate::ast::function::FunctionLoweringContext;
use crate::ast::let_statement::LetStatement; use crate::ast::let_statement::LetStatement;
use crate::constants_table::ConstantsTable;
use crate::diagnostic::Diagnostic; use crate::diagnostic::Diagnostic;
use crate::symbol_table::SymbolTable; use crate::symbol_table::SymbolTable;
@ -37,13 +38,18 @@ impl Statement {
} }
} }
pub fn lower_to_ir(&self, context: &mut FunctionLoweringContext) { pub fn assemble(
&self,
context: &mut AssembleContext,
symbol_table: &SymbolTable,
constants_table: &mut ConstantsTable,
) {
match self { match self {
Statement::Let(let_statement) => { Statement::Let(let_statement) => {
let_statement.lower_to_ir(context); let_statement.assemble(context, symbol_table, constants_table);
} }
Statement::Expression(expression) => { Statement::Expression(expression_statement) => {
expression.lower_to_ir(context); expression_statement.assemble(context, symbol_table, constants_table);
} }
} }
} }

View File

@ -1,8 +1,4 @@
use crate::ast::function::FunctionLoweringContext;
use crate::ir::ir_constant::{IrConstant, IrStringConstant};
use crate::ir::ir_expression::IrExpression;
use crate::source_range::SourceRange; use crate::source_range::SourceRange;
use std::rc::Rc;
pub struct StringLiteral { pub struct StringLiteral {
content: String, content: String,
@ -21,15 +17,6 @@ impl StringLiteral {
&self.content &self.content
} }
pub fn lower_to_ir(&self, context: &mut FunctionLoweringContext) -> IrExpression {
let ir_string_constant = Rc::new(IrStringConstant::new(
self.content(),
&context.next_constant_name(),
));
context.add_constant(IrConstant::String(ir_string_constant.clone()));
IrExpression::Constant(IrConstant::String(ir_string_constant))
}
pub fn source_range(&self) -> &SourceRange { pub fn source_range(&self) -> &SourceRange {
&self.source_range &self.source_range
} }

View File

@ -0,0 +1,19 @@
use crate::source_range::SourceRange;
pub struct TypeUse {
declared_name: String,
declared_name_source_range: SourceRange,
}
impl TypeUse {
pub fn new(declared_name: &str, declared_name_source_range: SourceRange) -> Self {
Self {
declared_name: declared_name.into(),
declared_name_source_range,
}
}
pub fn declared_name(&self) -> &str {
&self.declared_name
}
}

View File

@ -0,0 +1,30 @@
use std::collections::HashMap;
pub struct ConstantsTable {
string_counter: usize,
strings_to_names: HashMap<String, String>,
}
impl ConstantsTable {
pub fn new() -> Self {
Self {
string_counter: 0,
strings_to_names: HashMap::new(),
}
}
pub fn insert_string(&mut self, s: &str) -> String {
let name = format!("s_{}", self.string_counter);
self.string_counter += 1;
self.strings_to_names.insert(s.into(), name.clone());
name
}
pub fn string_constants(&self) -> HashMap<String, String> {
let mut constants = HashMap::new();
self.strings_to_names.iter().for_each(|(content, name)| {
constants.insert(name.clone(), content.clone());
});
constants
}
}

View File

@ -1,7 +0,0 @@
pub struct AssembleContext {}
impl AssembleContext {
pub fn new() -> Self {
Self {}
}
}

View File

@ -1,51 +0,0 @@
use crate::asm::asm_instruction::{AsmInstruction, LoadConstant, Move, Operand, Pop};
use crate::ir::assemble_context::AssembleContext;
use crate::ir::ir_constant::IrConstant;
use crate::ir::ir_expression::IrExpression;
use crate::ir::ir_variable::IrVariable;
#[derive(Debug)]
pub struct IrAssign {
destination: Box<IrVariable>,
value: Box<IrExpression>,
}
impl IrAssign {
pub fn new(destination: IrVariable, value: IrExpression) -> Self {
Self {
destination: destination.into(),
value: value.into(),
}
}
pub fn assemble(&self, context: &mut AssembleContext) -> Vec<AsmInstruction> {
let mut instructions = vec![];
match self.value.as_ref() {
IrExpression::Call(ir_call) => {
instructions.append(&mut ir_call.assemble(context));
instructions.push(AsmInstruction::Pop(Pop::new(self.destination.register())));
}
IrExpression::Constant(ir_constant) => match ir_constant {
IrConstant::String(ir_string_constant) => {
instructions.push(AsmInstruction::LoadConstant(LoadConstant::new(
ir_string_constant.name(),
self.destination.register(),
)));
}
},
IrExpression::IntegerLiteral(i) => {
instructions.push(AsmInstruction::Move(Move::new(
Operand::IntegerLiteral(*i),
self.destination.register(),
)));
}
IrExpression::Variable(ir_variable) => {
instructions.push(AsmInstruction::Move(Move::new(
Operand::Register(ir_variable.register()),
self.destination.register(),
)))
}
};
instructions
}
}

View File

@ -1,53 +0,0 @@
use crate::asm::asm_instruction::{
AsmInstruction, InvokePlatformStatic, LoadConstant, Operand, Push,
};
use crate::ir::assemble_context::AssembleContext;
use crate::ir::ir_constant::IrConstant;
use crate::ir::ir_expression::IrExpression;
#[derive(Debug)]
pub struct IrCall {
name: String,
arguments: Vec<IrExpression>,
}
impl IrCall {
pub fn new(name: &str, arguments: Vec<IrExpression>) -> Self {
Self {
name: name.into(),
arguments,
}
}
pub fn assemble(&self, context: &mut AssembleContext) -> Vec<AsmInstruction> {
let mut instructions = vec![];
for argument in &self.arguments {
match argument {
IrExpression::Call(ir_call) => {
instructions.append(&mut ir_call.assemble(context));
}
IrExpression::Constant(ir_constant) => {
match ir_constant {
IrConstant::String(string_constant) => {
instructions.push(AsmInstruction::LoadConstant(LoadConstant::new(
string_constant.name(),
0,
)));
}
}
instructions.push(AsmInstruction::Push(Push::new(Operand::Register(0))))
}
IrExpression::IntegerLiteral(i) => {
instructions.push(AsmInstruction::Push(Push::new(Operand::IntegerLiteral(*i))));
}
IrExpression::Variable(ir_variable) => instructions.push(AsmInstruction::Push(
Push::new(Operand::Register(ir_variable.register())),
)),
}
}
instructions.push(AsmInstruction::InvokePlatformStatic(
InvokePlatformStatic::new(&self.name),
));
instructions
}
}

View File

@ -1,29 +0,0 @@
use std::rc::Rc;
#[derive(Debug)]
pub enum IrConstant {
String(Rc<IrStringConstant>),
}
#[derive(Debug)]
pub struct IrStringConstant {
value: String,
name: String,
}
impl IrStringConstant {
pub fn new(value: &str, name: &str) -> Self {
Self {
value: value.into(),
name: name.into(),
}
}
pub fn value(&self) -> &str {
&self.value
}
pub fn name(&self) -> &str {
&self.name
}
}

View File

@ -1,11 +0,0 @@
use crate::ir::ir_call::IrCall;
use crate::ir::ir_constant::IrConstant;
use crate::ir::ir_variable::IrVariable;
#[derive(Debug)]
pub enum IrExpression {
Call(IrCall),
Constant(IrConstant),
IntegerLiteral(i64),
Variable(IrVariable),
}

View File

@ -1,28 +0,0 @@
use crate::asm::asm_block::AsmBlock;
use crate::asm::asm_function::AsmFunction;
use crate::ir::assemble_context::AssembleContext;
use crate::ir::ir_statement::IrStatement;
#[derive(Debug)]
pub struct IrFunction {
name: String,
statements: Vec<IrStatement>,
}
impl IrFunction {
pub fn new(name: &str, statements: Vec<IrStatement>) -> Self {
Self {
name: name.into(),
statements,
}
}
pub fn assemble(&self, context: &mut AssembleContext) -> AsmFunction {
let mut instructions = vec![];
for statement in &self.statements {
instructions.append(&mut statement.assemble(context));
}
let blocks = vec![AsmBlock::new(0, instructions)];
AsmFunction::new(&self.name, blocks)
}
}

View File

@ -1,19 +0,0 @@
use crate::asm::asm_instruction::AsmInstruction;
use crate::ir::assemble_context::AssembleContext;
use crate::ir::ir_assign::IrAssign;
use crate::ir::ir_call::IrCall;
#[derive(Debug)]
pub enum IrStatement {
Assign(IrAssign),
Call(IrCall),
}
impl IrStatement {
pub fn assemble(&self, context: &mut AssembleContext) -> Vec<AsmInstruction> {
match self {
IrStatement::Assign(ir_assign) => ir_assign.assemble(context),
IrStatement::Call(ir_call) => ir_call.assemble(context),
}
}
}

View File

@ -1,22 +0,0 @@
#[derive(Debug)]
pub struct IrVariable {
name: String,
register: Option<usize>,
}
impl IrVariable {
pub fn new(name: &str) -> Self {
Self {
name: name.into(),
register: None,
}
}
pub fn set_register(&mut self, register: usize) {
self.register = Some(register);
}
pub fn register(&self) -> usize {
0
}
}

View File

@ -1,17 +0,0 @@
use crate::ir::ir_constant::IrConstant;
use crate::ir::ir_function::IrFunction;
pub mod assemble_context;
pub mod ir_assign;
pub mod ir_call;
pub mod ir_constant;
pub mod ir_expression;
pub mod ir_function;
pub mod ir_statement;
pub mod ir_variable;
#[derive(Debug)]
pub enum Ir {
Function(IrFunction),
Constant(IrConstant),
}

View File

@ -27,8 +27,10 @@ impl<'a> Lexer<'a> {
let maybe_chunk = self.input.get(self.position..); let maybe_chunk = self.input.get(self.position..);
if maybe_chunk.is_none() { if maybe_chunk.is_none() {
return None; return None;
} else { }
chunk = maybe_chunk.unwrap(); chunk = maybe_chunk.unwrap();
if chunk.is_empty() {
return None;
} }
} }
@ -42,6 +44,10 @@ impl<'a> Lexer<'a> {
) )
} else if chunk.starts_with("=") { } else if chunk.starts_with("=") {
Token::new(self.position, self.position + 1, TokenKind::Equals) Token::new(self.position, self.position + 1, TokenKind::Equals)
} else if chunk.starts_with(",") {
Token::new(self.position, self.position + 1, TokenKind::Comma)
} else if chunk.starts_with(":") {
Token::new(self.position, self.position + 1, TokenKind::Colon)
} else { } else {
// more than one char token // more than one char token
if chunk.starts_with(|c: char| c.is_ascii_digit()) { if chunk.starts_with(|c: char| c.is_ascii_digit()) {
@ -87,6 +93,7 @@ impl<'a> Lexer<'a> {
"fn" => TokenKind::Fn, "fn" => TokenKind::Fn,
"end" => TokenKind::End, "end" => TokenKind::End,
"let" => TokenKind::Let, "let" => TokenKind::Let,
"extern" => TokenKind::Extern,
_ => TokenKind::Identifier, _ => TokenKind::Identifier,
}; };
Token::new(self.position, self.position + prefix.len(), token_kind) Token::new(self.position, self.position + prefix.len(), token_kind)
@ -154,4 +161,17 @@ mod tests {
assert_next(&mut lexer, TokenKind::End, 3); assert_next(&mut lexer, TokenKind::End, 3);
assert_eq!(lexer.next(), None); assert_eq!(lexer.next(), None);
} }
#[test]
fn blank_after_last_token_returns_none() {
let mut lexer = Lexer::new("fn ");
assert_next(&mut lexer, TokenKind::Fn, 2);
assert_eq!(lexer.next(), None);
}
#[test]
fn extern_returned() {
let mut lexer = Lexer::new("extern");
assert_next(&mut lexer, TokenKind::Extern, 6);
}
} }

View File

@ -1,12 +1,12 @@
mod asm; pub mod asm;
mod ast; pub mod ast;
mod diagnostic; pub mod constants_table;
mod ir; pub mod diagnostic;
mod lexer; pub mod lexer;
mod parser; pub mod parser;
mod scope; pub mod scope;
mod source_range; pub mod source_range;
mod symbol; pub mod symbol;
mod symbol_table; pub mod symbol_table;
mod token; pub mod token;
mod type_info; pub mod type_info;

View File

@ -2,18 +2,23 @@ use crate::ast::call::Call;
use crate::ast::compilation_unit::CompilationUnit; use crate::ast::compilation_unit::CompilationUnit;
use crate::ast::expression::Expression; use crate::ast::expression::Expression;
use crate::ast::expression_statement::ExpressionStatement; use crate::ast::expression_statement::ExpressionStatement;
use crate::ast::extern_function::ExternFunction;
use crate::ast::function::Function; use crate::ast::function::Function;
use crate::ast::identifier::Identifier; use crate::ast::identifier::Identifier;
use crate::ast::integer_literal::IntegerLiteral; use crate::ast::integer_literal::IntegerLiteral;
use crate::ast::let_statement::LetStatement; use crate::ast::let_statement::LetStatement;
use crate::ast::module_level_declaration::ModuleLevelDeclaration;
use crate::ast::parameter::Parameter;
use crate::ast::statement::Statement; use crate::ast::statement::Statement;
use crate::ast::string_literal::StringLiteral; use crate::ast::string_literal::StringLiteral;
use crate::ast::type_use::TypeUse;
use crate::diagnostic::Diagnostic;
use crate::lexer::Lexer; use crate::lexer::Lexer;
use crate::source_range::SourceRange; use crate::source_range::SourceRange;
use crate::token::{Token, TokenKind}; use crate::token::{Token, TokenKind};
use std::str::FromStr; use std::str::FromStr;
pub fn parse_compilation_unit(input: &str) -> CompilationUnit { pub fn parse_compilation_unit(input: &str) -> Result<CompilationUnit, Vec<Diagnostic>> {
let mut parser = Parser::new(input); let mut parser = Parser::new(input);
parser.compilation_unit() parser.compilation_unit()
} }
@ -35,6 +40,22 @@ impl<'a> Parser<'a> {
} }
} }
fn advance_until(&mut self, token_kinds: &[TokenKind]) {
while self.current.is_some() {
self.advance();
match &self.current {
None => {
// reached eoi
}
Some(current) => {
if token_kinds.contains(&current.kind()) {
break;
}
}
}
}
}
fn advance(&mut self) { fn advance(&mut self) {
if self.lookahead.is_some() { if self.lookahead.is_some() {
// we've advanced at least once // we've advanced at least once
@ -48,6 +69,9 @@ impl<'a> Parser<'a> {
} }
}, },
} }
} else if self.lookahead.is_none() && self.current.is_some() {
// we're on the last token
self.current = None;
} else { } else {
// we've not yet advanced, so fetch both // we've not yet advanced, so fetch both
// current // current
@ -77,22 +101,24 @@ impl<'a> Parser<'a> {
} }
} }
fn expect_advance(&mut self, token_kind: TokenKind) -> Token { fn expect_advance(&mut self, token_kind: TokenKind) -> Result<Token, Vec<Diagnostic>> {
match self.current.take() { match self.current.take() {
None => { None => Err(vec![Diagnostic::new(
panic!("Expected {:?} but found end of input", token_kind); &format!("Expected {:?} but found end-of-input.", token_kind),
} self.input.len(),
self.input.len(),
)]),
Some(token) => { Some(token) => {
if token.kind() == token_kind { if token.kind() == token_kind {
self.advance(); self.advance();
token Ok(token)
} else { } else {
panic!( self.advance_until(&[token_kind]);
"Expected {:?} but found {:?} at {}", Err(vec![Diagnostic::new(
token_kind, &format!("Expected {:?} but found {:?}", token_kind, token.kind()),
token.kind(), token.start(),
token.start() token.end(),
); )])
} }
} }
} }
@ -100,7 +126,7 @@ impl<'a> Parser<'a> {
fn peek_current(&self, token_kind: TokenKind) -> bool { fn peek_current(&self, token_kind: TokenKind) -> bool {
match &self.current { match &self.current {
None => false, None => panic!("Unexpected end of input."),
Some(token) => token.kind() == token_kind, Some(token) => token.kind() == token_kind,
} }
} }
@ -114,6 +140,13 @@ impl<'a> Parser<'a> {
} }
} }
fn peek_lookahead(&self, token_kind: TokenKind) -> bool {
match &self.lookahead {
None => panic!("Unexpected end of input."),
Some(token) => token.kind() == token_kind,
}
}
fn sample_input(&self, start: usize, end: usize) -> &'a str { fn sample_input(&self, start: usize, end: usize) -> &'a str {
&self.input[start..end] &self.input[start..end]
} }
@ -122,71 +155,226 @@ impl<'a> Parser<'a> {
self.sample_input(token.start(), token.end()) self.sample_input(token.start(), token.end())
} }
pub fn compilation_unit(&mut self) -> CompilationUnit { pub fn compilation_unit(&mut self) -> Result<CompilationUnit, Vec<Diagnostic>> {
let mut functions = vec![]; let mut declarations = vec![];
self.advance(); let mut diagnostics = vec![];
self.advance(); // get started
while self.current.is_some() { while self.current.is_some() {
functions.push(self.function()); let current = self.get_current();
match current.kind() {
TokenKind::Fn | TokenKind::Extern => {
let declaration_result = self.module_level_declaration();
match declaration_result {
Ok(declaration) => declarations.push(declaration),
Err(mut declaration_diagnostics) => {
diagnostics.append(&mut declaration_diagnostics)
}
}
}
_ => {
diagnostics.push(Diagnostic::new(
&format!(
"Expected any of: {:?}; found {:?}",
[TokenKind::Fn, TokenKind::Extern],
current.kind()
),
current.start(),
current.end(),
));
self.advance_until(&[TokenKind::Fn, TokenKind::Extern]);
}
}
}
if diagnostics.is_empty() {
Ok(CompilationUnit::new(declarations))
} else {
Err(diagnostics)
} }
CompilationUnit::new(functions)
} }
fn function(&mut self) -> Function { fn module_level_declaration(&mut self) -> Result<ModuleLevelDeclaration, Vec<Diagnostic>> {
self.expect_advance(TokenKind::Fn); let current = self.get_current();
let identifier_token = self.expect_advance(TokenKind::Identifier); match current.kind() {
self.expect_advance(TokenKind::LeftParentheses); TokenKind::Fn => {
// add params let function_result = self.function();
self.expect_advance(TokenKind::RightParentheses); match function_result {
let mut statements = vec![]; Ok(function) => Ok(ModuleLevelDeclaration::Function(function)),
while !self.peek_current(TokenKind::End) { Err(function_diagnostics) => Err(function_diagnostics),
statements.push(self.statement());
} }
self.expect_advance(TokenKind::End); }
Function::new( TokenKind::Extern => {
let extern_function_result = self.extern_function();
match extern_function_result {
Ok(extern_function) => {
Ok(ModuleLevelDeclaration::ExternFunction(extern_function))
}
Err(extern_function_diagnostics) => Err(extern_function_diagnostics),
}
}
_ => unreachable!(),
}
}
fn function(&mut self) -> Result<Function, Vec<Diagnostic>> {
self.expect_advance(TokenKind::Fn)?;
let identifier_token = self.expect_advance(TokenKind::Identifier)?;
self.expect_advance(TokenKind::LeftParentheses)?;
// add params
self.expect_advance(TokenKind::RightParentheses)?;
let mut statements = vec![];
let mut diagnostics = vec![];
while self.current.is_some() && !self.peek_current(TokenKind::End) {
let statement_result = self.statement();
match statement_result {
Ok(statement) => {
statements.push(statement);
}
Err(mut statement_diagnostics) => {
diagnostics.append(&mut statement_diagnostics);
}
}
}
// if we're missing "end", append it to the other statement diagnostics
let end_result = self.expect_advance(TokenKind::End);
match end_result {
Err(mut end_diagnostics) => {
diagnostics.append(&mut end_diagnostics);
}
_ => {}
}
if diagnostics.is_empty() {
Ok(Function::new(
self.token_text(&identifier_token), self.token_text(&identifier_token),
SourceRange::new(identifier_token.start(), identifier_token.end()), SourceRange::new(identifier_token.start(), identifier_token.end()),
statements, statements,
) ))
} else {
Err(diagnostics)
}
} }
fn statement(&mut self) -> Statement { fn extern_function(&mut self) -> Result<ExternFunction, Vec<Diagnostic>> {
self.expect_advance(TokenKind::Extern)?;
self.expect_advance(TokenKind::Fn)?;
let identifier_token = self.expect_advance(TokenKind::Identifier)?;
self.expect_advance(TokenKind::LeftParentheses)?;
let mut diagnostics = vec![];
let mut maybe_parameters: Option<Vec<Parameter>> = None;
let params_result = self.parameter_list();
match params_result {
Ok(parameters) => {
maybe_parameters = Some(parameters);
}
Err(mut parameter_list_diagnostics) => {
diagnostics.append(&mut parameter_list_diagnostics);
}
}
let right_parentheses_result = self.expect_advance(TokenKind::RightParentheses);
match right_parentheses_result {
Err(mut right_parentheses_diagnostics) => {
diagnostics.append(&mut right_parentheses_diagnostics);
}
Ok(_) => {}
}
if diagnostics.is_empty() {
Ok(ExternFunction::new(
self.token_text(&identifier_token),
SourceRange::new(identifier_token.start(), identifier_token.end()),
maybe_parameters.unwrap(),
))
} else {
Err(diagnostics)
}
}
fn parameter_list(&mut self) -> Result<Vec<Parameter>, Vec<Diagnostic>> {
let mut parameters = vec![];
let mut diagnostics = vec![];
while self.current.is_some() && self.peek_current(TokenKind::Identifier) {
let parameter_result = self.parameter();
match parameter_result {
Ok(parameter) => {
parameters.push(parameter);
}
Err(mut parameter_diagnostics) => {
diagnostics.append(&mut parameter_diagnostics);
}
}
if self.lookahead.is_some() && self.peek_lookahead(TokenKind::Comma) {
self.advance();
}
}
if diagnostics.is_empty() {
Ok(parameters)
} else {
Err(diagnostics)
}
}
fn parameter(&mut self) -> Result<Parameter, Vec<Diagnostic>> {
let identifier_token = self.expect_advance(TokenKind::Identifier)?;
self.expect_advance(TokenKind::Colon)?;
let type_use = self.type_use()?;
Ok(Parameter::new(
self.token_text(&identifier_token),
SourceRange::new(identifier_token.start(), identifier_token.end()),
type_use,
))
}
fn type_use(&mut self) -> Result<TypeUse, Vec<Diagnostic>> {
let identifier_token = self.expect_advance(TokenKind::Identifier)?;
Ok(TypeUse::new(
self.token_text(&identifier_token),
SourceRange::new(identifier_token.start(), identifier_token.end()),
))
}
fn statement(&mut self) -> Result<Statement, Vec<Diagnostic>> {
let current = self.get_current(); let current = self.get_current();
match current.kind() { match current.kind() {
TokenKind::Let => self.let_statement(), TokenKind::Let => Ok(Statement::Let(self.let_statement()?)),
_ => self.expression_statement(), _ => Ok(Statement::Expression(self.expression_statement()?)),
} }
} }
fn let_statement(&mut self) -> Statement { fn let_statement(&mut self) -> Result<LetStatement, Vec<Diagnostic>> {
self.expect_advance(TokenKind::Let); self.expect_advance(TokenKind::Let)?;
let identifier = self.expect_advance(TokenKind::Identifier); let identifier = self.expect_advance(TokenKind::Identifier)?;
self.expect_advance(TokenKind::Equals); self.expect_advance(TokenKind::Equals)?;
let expression = self.expression(); let expression = self.expression()?;
Statement::Let(LetStatement::new( Ok(LetStatement::new(
self.token_text(&identifier), self.token_text(&identifier),
SourceRange::new(identifier.start(), identifier.end()), SourceRange::new(identifier.start(), identifier.end()),
expression, expression,
)) ))
} }
fn expression_statement(&mut self) -> Statement { fn expression_statement(&mut self) -> Result<ExpressionStatement, Vec<Diagnostic>> {
Statement::Expression(ExpressionStatement::new(self.expression())) Ok(ExpressionStatement::new(self.expression()?))
} }
fn expression(&mut self) -> Expression { fn expression(&mut self) -> Result<Expression, Vec<Diagnostic>> {
let current = self.get_current(); let current = self.get_current().clone(); // I don't love this clone
let mut result = match current.kind() { let mut diagnostics = vec![];
let mut expression = match current.kind() {
TokenKind::IntegerLiteral => { TokenKind::IntegerLiteral => {
let raw = self.token_text(current); let raw = self.token_text(&current);
let source_range = SourceRange::new(current.start(), current.end()); let source_range = SourceRange::new(current.start(), current.end());
self.advance(); self.advance();
Expression::IntegerLiteral(IntegerLiteral::new( Expression::IntegerLiteral(IntegerLiteral::new(
i64::from_str(raw).unwrap(), i32::from_str(raw).unwrap(),
source_range, source_range,
)) ))
} }
TokenKind::String => { TokenKind::String => {
let with_quotes = self.token_text(current); let with_quotes = self.token_text(&current);
let source_range = SourceRange::new(current.start(), current.end()); let source_range = SourceRange::new(current.start(), current.end());
self.advance(); self.advance();
Expression::String(StringLiteral::new( Expression::String(StringLiteral::new(
@ -195,37 +383,73 @@ impl<'a> Parser<'a> {
)) ))
} }
TokenKind::Identifier => { TokenKind::Identifier => {
let declared_name = self.token_text(current); let declared_name = self.token_text(&current);
let source_range = SourceRange::new(current.start(), current.end()); let source_range = SourceRange::new(current.start(), current.end());
self.advance(); self.advance();
Expression::Identifier(Identifier::new(declared_name, source_range)) Expression::Identifier(Identifier::new(declared_name, source_range))
} }
_ => panic!("Unexpected token {:?}", current.kind()), _ => {
diagnostics.push(Diagnostic::new(
&format!(
"Expected any of {:?} but found {:?}",
[
TokenKind::IntegerLiteral,
TokenKind::String,
TokenKind::Identifier
],
current.kind()
),
current.start(),
current.end(),
));
self.advance_until(&[
TokenKind::IntegerLiteral,
TokenKind::String,
TokenKind::Identifier,
]);
if self.current.is_some() {
let try_again_result = self.expression();
match try_again_result {
Ok(expression) => expression,
Err(mut try_again_diagnostics) => {
diagnostics.append(&mut try_again_diagnostics);
return Err(diagnostics);
}
}
} else {
return Err(diagnostics);
}
}
}; };
// postfixes // postfixes
while let Some(current) = &self.current { while let Some(current) = &self.current {
match current.kind() { match current.kind() {
TokenKind::LeftParentheses => { TokenKind::LeftParentheses => {
result = Expression::Call(self.call(result)); expression = Expression::Call(self.call(expression)?);
} }
_ => break, _ => break,
} }
} }
result if diagnostics.is_empty() {
Ok(expression)
} else {
Err(diagnostics)
}
} }
fn call(&mut self, callee: Expression) -> Call { fn call(&mut self, callee: Expression) -> Result<Call, Vec<Diagnostic>> {
self.expect_advance(TokenKind::LeftParentheses); self.expect_advance(TokenKind::LeftParentheses)?;
let mut arguments = vec![]; let mut arguments = vec![];
while !self.peek_current(TokenKind::RightParentheses) { while self.current.is_some() && !self.peek_current(TokenKind::RightParentheses) {
arguments.push(self.expression()); arguments.push(self.expression()?);
} }
let right_parentheses_token = self.expect_advance(TokenKind::RightParentheses); let right_parentheses_token = self.expect_advance(TokenKind::RightParentheses)?;
let source_range = let source_range =
SourceRange::new(callee.source_range().start(), right_parentheses_token.end()); SourceRange::new(callee.source_range().start(), right_parentheses_token.end());
Call::new(callee, arguments, source_range) Ok(Call::new(callee, arguments, source_range))
} }
} }
@ -233,17 +457,76 @@ impl<'a> Parser<'a> {
mod smoke_tests { mod smoke_tests {
use super::*; use super::*;
fn smoke_test(input: &str) {
let parse_result = parse_compilation_unit(input);
match parse_result {
Ok(_) => {}
Err(diagnostics) => {
eprintln!("{:#?}", diagnostics);
panic!("There were diagnostics during parsing");
}
}
}
#[test] #[test]
fn forty_two() { fn forty_two() {
parse_compilation_unit("fn main() 42 end"); smoke_test("fn main() 42 end");
}
#[test]
fn chained_calls() {
smoke_test("fn main() getCl()() end");
}
#[test]
fn extern_fn_with_param() {
smoke_test("extern fn println(message: Any)")
}
}
#[cfg(test)]
mod concrete_tests {
use super::*;
#[test]
fn parses_extern_fn() {
let parse_result = parse_compilation_unit("extern fn println()");
let compilation_unit = match parse_result {
Ok(compilation_unit) => compilation_unit,
Err(diagnostics) => {
for diagnostic in diagnostics {
eprintln!("{:?}", diagnostic);
}
panic!();
}
};
let declarations = compilation_unit.declarations();
assert_eq!(declarations.len(), 1);
let extern_function = match &declarations[0] {
ModuleLevelDeclaration::ExternFunction(extern_function) => extern_function,
_ => panic!(),
};
assert_eq!(extern_function.declared_name(), "println");
} }
#[test] #[test]
fn hello_world() { fn hello_world() {
let compilation_unit = parse_compilation_unit("fn main() println(\"Hello, World!\") end"); let parse_result = parse_compilation_unit("fn main() println(\"Hello, World!\") end");
let functions = compilation_unit.functions(); let compilation_unit = match parse_result {
assert_eq!(functions.len(), 1); Ok(compilation_unit) => compilation_unit,
let function = functions[0]; Err(diagnostics) => {
for diagnostic in &diagnostics {
eprintln!("{:?}", diagnostic)
}
panic!()
}
};
let declarations = compilation_unit.declarations();
assert_eq!(declarations.len(), 1);
let function = match &declarations[0] {
ModuleLevelDeclaration::Function(function) => function,
_ => panic!(),
};
assert_eq!(function.declared_name(), "main"); assert_eq!(function.declared_name(), "main");
let statements = function.statements(); let statements = function.statements();
assert_eq!(statements.len(), 1); assert_eq!(statements.len(), 1);
@ -272,9 +555,36 @@ mod smoke_tests {
panic!("Expected expression"); panic!("Expected expression");
} }
} }
}
#[cfg(test)]
mod parse_failure_tests {
use super::*;
#[test] #[test]
fn chained_calls() { fn lone_end() {
parse_compilation_unit("fn main() getCl()() end"); let parse_result = parse_compilation_unit("end");
match parse_result {
Err(diagnostics) => {
assert_eq!(diagnostics.len(), 1);
for diagnostic in &diagnostics {
println!("{:?}", diagnostic)
}
}
Ok(_) => panic!(),
}
}
#[test]
fn two_ends() {
let parse_result = parse_compilation_unit("end end");
match parse_result {
Err(diagnostics) => {
// Should only have an error on the first end, since we advance until we find a
// token we can recover from (fn or extern)
assert_eq!(diagnostics.len(), 1);
}
Ok(_) => panic!(),
}
} }
} }

View File

@ -1,13 +1,14 @@
use crate::symbol::{FunctionSymbol, ParameterSymbol, VariableSymbol}; use crate::symbol::{FunctionSymbol, ParameterSymbol, VariableSymbol};
use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
pub struct Scope { pub struct Scope {
debug_name: String, debug_name: String,
parent_id: Option<usize>, parent_id: Option<usize>,
function_symbols: HashMap<Rc<str>, Rc<FunctionSymbol>>, function_symbols: HashMap<Rc<str>, Rc<RefCell<FunctionSymbol>>>,
parameter_symbols: HashMap<Rc<str>, Rc<ParameterSymbol>>, parameter_symbols: HashMap<Rc<str>, Rc<RefCell<ParameterSymbol>>>,
variable_symbols: HashMap<Rc<str>, Rc<VariableSymbol>>, variable_symbols: HashMap<Rc<str>, Rc<RefCell<VariableSymbol>>>,
} }
impl Scope { impl Scope {
@ -21,27 +22,27 @@ impl Scope {
} }
} }
pub fn function_symbols(&self) -> &HashMap<Rc<str>, Rc<FunctionSymbol>> { pub fn function_symbols(&self) -> &HashMap<Rc<str>, Rc<RefCell<FunctionSymbol>>> {
&self.function_symbols &self.function_symbols
} }
pub fn function_symbols_mut(&mut self) -> &mut HashMap<Rc<str>, Rc<FunctionSymbol>> { pub fn function_symbols_mut(&mut self) -> &mut HashMap<Rc<str>, Rc<RefCell<FunctionSymbol>>> {
&mut self.function_symbols &mut self.function_symbols
} }
pub fn parameter_symbols(&self) -> &HashMap<Rc<str>, Rc<ParameterSymbol>> { pub fn parameter_symbols(&self) -> &HashMap<Rc<str>, Rc<RefCell<ParameterSymbol>>> {
&self.parameter_symbols &self.parameter_symbols
} }
pub fn parameter_symbols_mut(&mut self) -> &mut HashMap<Rc<str>, Rc<ParameterSymbol>> { pub fn parameter_symbols_mut(&mut self) -> &mut HashMap<Rc<str>, Rc<RefCell<ParameterSymbol>>> {
&mut self.parameter_symbols &mut self.parameter_symbols
} }
pub fn variable_symbols(&self) -> &HashMap<Rc<str>, Rc<VariableSymbol>> { pub fn variable_symbols(&self) -> &HashMap<Rc<str>, Rc<RefCell<VariableSymbol>>> {
&self.variable_symbols &self.variable_symbols
} }
pub fn variable_symbols_mut(&mut self) -> &mut HashMap<Rc<str>, Rc<VariableSymbol>> { pub fn variable_symbols_mut(&mut self) -> &mut HashMap<Rc<str>, Rc<RefCell<VariableSymbol>>> {
&mut self.variable_symbols &mut self.variable_symbols
} }

View File

@ -1,3 +1,4 @@
#[derive(Debug, Clone)]
pub struct SourceRange { pub struct SourceRange {
start: usize, start: usize,
end: usize, end: usize,

View File

@ -1,16 +1,19 @@
use crate::type_info::TypeInfo; use crate::type_info::TypeInfo;
use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
pub struct FunctionSymbol { pub struct FunctionSymbol {
name: Rc<str>, name: Rc<str>,
parameters: Vec<Rc<ParameterSymbol>>, is_platform: bool,
parameters: Option<Vec<Rc<RefCell<ParameterSymbol>>>>,
} }
impl FunctionSymbol { impl FunctionSymbol {
pub fn new(name: &str, parameters: &[Rc<ParameterSymbol>]) -> Self { pub fn new(name: &str, is_platform: bool) -> Self {
Self { Self {
name: name.into(), name: name.into(),
parameters: parameters.into(), is_platform,
parameters: None,
} }
} }
@ -22,18 +25,27 @@ impl FunctionSymbol {
self.name.clone() self.name.clone()
} }
pub fn parameters(&self) -> &[Rc<ParameterSymbol>] { pub fn set_parameters(&mut self, parameters: Vec<Rc<RefCell<ParameterSymbol>>>) {
&self.parameters self.parameters = Some(parameters);
}
pub fn parameters(&self) -> &[Rc<RefCell<ParameterSymbol>>] {
self.parameters.as_ref().unwrap()
} }
pub fn return_type(&self) -> TypeInfo { pub fn return_type(&self) -> TypeInfo {
todo!() todo!()
} }
pub fn is_platform(&self) -> bool {
self.is_platform
}
} }
pub struct ParameterSymbol { pub struct ParameterSymbol {
name: Rc<str>, name: Rc<str>,
type_info: TypeInfo, type_info: TypeInfo,
stack_frame_offset: Option<isize>,
} }
impl ParameterSymbol { impl ParameterSymbol {
@ -41,6 +53,7 @@ impl ParameterSymbol {
Self { Self {
name: name.into(), name: name.into(),
type_info, type_info,
stack_frame_offset: None,
} }
} }
@ -55,11 +68,20 @@ impl ParameterSymbol {
pub fn type_info(&self) -> &TypeInfo { pub fn type_info(&self) -> &TypeInfo {
&self.type_info &self.type_info
} }
pub fn set_stack_frame_offset(&mut self, offset: isize) {
self.stack_frame_offset = Some(offset);
}
pub fn stack_frame_offset(&self) -> isize {
self.stack_frame_offset.unwrap()
}
} }
pub struct VariableSymbol { pub struct VariableSymbol {
name: Rc<str>, name: Rc<str>,
type_info: TypeInfo, type_info: TypeInfo,
register: Option<usize>,
} }
impl VariableSymbol { impl VariableSymbol {
@ -67,6 +89,7 @@ impl VariableSymbol {
Self { Self {
name: name.into(), name: name.into(),
type_info, type_info,
register: None,
} }
} }
@ -81,10 +104,18 @@ impl VariableSymbol {
pub fn type_info(&self) -> &TypeInfo { pub fn type_info(&self) -> &TypeInfo {
&self.type_info &self.type_info
} }
pub fn set_register(&mut self, register: usize) {
self.register = Some(register);
}
pub fn register(&self) -> usize {
self.register.unwrap()
}
} }
pub enum ExpressibleSymbol { pub enum ExpressibleSymbol {
Function(Rc<FunctionSymbol>), Function(Rc<RefCell<FunctionSymbol>>),
Parameter(Rc<ParameterSymbol>), Parameter(Rc<RefCell<ParameterSymbol>>),
Variable(Rc<VariableSymbol>), Variable(Rc<RefCell<VariableSymbol>>),
} }

View File

@ -1,5 +1,6 @@
use crate::scope::Scope; use crate::scope::Scope;
use crate::symbol::{ExpressibleSymbol, FunctionSymbol, ParameterSymbol, VariableSymbol}; use crate::symbol::{ExpressibleSymbol, FunctionSymbol, ParameterSymbol, VariableSymbol};
use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
pub struct SymbolTable { pub struct SymbolTable {
@ -50,31 +51,35 @@ impl SymbolTable {
pub fn insert_function_symbol( pub fn insert_function_symbol(
&mut self, &mut self,
function_symbol: FunctionSymbol, function_symbol: FunctionSymbol,
) -> Result<(), SymbolInsertError> { ) -> Result<Rc<RefCell<FunctionSymbol>>, SymbolInsertError> {
if self.current_scope_has_name(function_symbol.name()) { if self.current_scope_has_name(function_symbol.name()) {
return Err(SymbolInsertError::AlreadyDeclared(AlreadyDeclared::new( return Err(SymbolInsertError::AlreadyDeclared(AlreadyDeclared::new(
function_symbol.name(), function_symbol.name(),
))); )));
} }
let name = function_symbol.name_owned();
let as_rc = Rc::new(RefCell::new(function_symbol));
self.current_scope_mut() self.current_scope_mut()
.function_symbols_mut() .function_symbols_mut()
.insert(function_symbol.name_owned(), Rc::new(function_symbol)); .insert(name, as_rc.clone());
Ok(()) Ok(as_rc)
} }
pub fn insert_parameter_symbol( pub fn insert_parameter_symbol(
&mut self, &mut self,
parameter_symbol: ParameterSymbol, parameter_symbol: ParameterSymbol,
) -> Result<(), SymbolInsertError> { ) -> Result<Rc<RefCell<ParameterSymbol>>, SymbolInsertError> {
if self.current_scope_has_name(parameter_symbol.name()) { if self.current_scope_has_name(parameter_symbol.name()) {
return Err(SymbolInsertError::AlreadyDeclared(AlreadyDeclared::new( return Err(SymbolInsertError::AlreadyDeclared(AlreadyDeclared::new(
parameter_symbol.name(), parameter_symbol.name(),
))); )));
} }
let as_rc = Rc::new(RefCell::new(parameter_symbol));
self.current_scope_mut() self.current_scope_mut()
.parameter_symbols_mut() .parameter_symbols_mut()
.insert(parameter_symbol.name_owned(), Rc::new(parameter_symbol)); .insert(as_rc.borrow().name_owned(), as_rc.clone());
Ok(()) Ok(as_rc)
} }
pub fn insert_variable_symbol( pub fn insert_variable_symbol(
@ -86,9 +91,10 @@ impl SymbolTable {
variable_symbol.name(), variable_symbol.name(),
))); )));
} }
self.current_scope_mut() self.current_scope_mut().variable_symbols_mut().insert(
.variable_symbols_mut() variable_symbol.name_owned(),
.insert(variable_symbol.name_owned(), Rc::new(variable_symbol)); Rc::new(RefCell::new(variable_symbol)),
);
Ok(()) Ok(())
} }
@ -125,6 +131,11 @@ impl SymbolTable {
} }
None None
} }
pub fn get_variable_symbol(&self, scope_id: usize, name: &str) -> Rc<RefCell<VariableSymbol>> {
let scope = self.scopes.get(scope_id).unwrap();
scope.variable_symbols().get(name).unwrap().clone()
}
} }
pub enum SymbolInsertError { pub enum SymbolInsertError {

View File

@ -35,4 +35,7 @@ pub enum TokenKind {
IntegerLiteral, IntegerLiteral,
LongLiteral, LongLiteral,
String, String,
Extern,
Comma,
Colon,
} }

View File

@ -1,9 +1,56 @@
use crate::symbol::FunctionSymbol; use crate::symbol::FunctionSymbol;
use std::cell::RefCell;
use std::fmt::{Display, Formatter};
use std::rc::Rc; use std::rc::Rc;
#[derive(Clone)] #[derive(Clone)]
pub enum TypeInfo { pub enum TypeInfo {
Any,
Integer, Integer,
String, String,
Function(Rc<FunctionSymbol>), Function(Rc<RefCell<FunctionSymbol>>),
}
impl Display for TypeInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
TypeInfo::Any => write!(f, "Any"),
TypeInfo::Integer => write!(f, "Int"),
TypeInfo::String => write!(f, "String"),
TypeInfo::Function(function_symbol) => {
write!(f, "fn(")?;
for parameter in function_symbol.borrow().parameters() {
parameter.borrow().type_info().fmt(f)?;
}
write!(f, ")")
}
}
}
}
impl TypeInfo {
// This is very naive but works for now
pub fn from_declared_name(declared_name: &str) -> Self {
match declared_name {
"Any" => TypeInfo::Any,
"Int" => TypeInfo::Integer,
"String" => TypeInfo::String,
_ => panic!("Unknown type: {}", declared_name),
}
}
pub fn is_assignable_from(&self, other: &TypeInfo) -> bool {
match self {
TypeInfo::Any => true,
TypeInfo::Integer => {
matches!(other, TypeInfo::Integer)
}
TypeInfo::String => {
matches!(other, TypeInfo::String)
}
TypeInfo::Function(_) => {
unimplemented!("Type matching on Functions not yet supported.")
}
}
}
} }

6
dvm-lib/Cargo.toml Normal file
View File

@ -0,0 +1,6 @@
[package]
name = "dvm-lib"
version = "0.1.0"
edition = "2024"
[dependencies]

View File

@ -0,0 +1,22 @@
use std::rc::Rc;
pub type Register = usize;
pub type ConstantName = Rc<str>;
pub type FunctionName = Rc<str>;
pub type ArgCount = usize;
pub enum Instruction {
MoveRegister(Register, Register),
MoveInt(i32, Register),
MoveStackFrameOffset(isize, Register),
PushRegister(Register),
PushInt(i32),
PushStackFrameOffset(isize),
InvokePlatformStatic(FunctionName, ArgCount),
LoadStringConstant(ConstantName, Register),
Pop(Option<Register>),
}

3
dvm-lib/src/lib.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod instruction;
pub mod platform_function;
pub mod vm;

View File

@ -0,0 +1,6 @@
use crate::vm::value::Value;
use crate::vm::{DvmContext, DvmState};
use std::error::Error;
pub type PlatformFunction =
fn(context: &DvmContext, state: &DvmState, args: &[Value]) -> Result<Value, Box<dyn Error>>;

View File

@ -0,0 +1,31 @@
use std::rc::Rc;
pub enum Constant {
String(StringConstant),
}
pub struct StringConstant {
name: Rc<str>,
content: Rc<str>,
}
impl StringConstant {
pub fn new(name: Rc<str>, content: &str) -> Self {
Self {
name,
content: content.into(),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn name_owned(&self) -> Rc<str> {
self.name.clone()
}
pub fn content_owned(&self) -> Rc<str> {
self.content.clone()
}
}

View File

@ -0,0 +1,34 @@
use crate::instruction::Instruction;
use std::rc::Rc;
pub struct Function {
name: Rc<str>,
instructions: Vec<Instruction>,
register_count: usize,
}
impl Function {
pub fn new(name: &str, instructions: Vec<Instruction>, register_count: usize) -> Self {
Self {
name: name.into(),
instructions,
register_count,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn name_owned(&self) -> Rc<str> {
self.name.clone()
}
pub fn instructions(&self) -> &Vec<Instruction> {
&self.instructions
}
pub fn register_count(&self) -> usize {
self.register_count
}
}

224
dvm-lib/src/vm/mod.rs Normal file
View File

@ -0,0 +1,224 @@
use crate::instruction::Instruction;
use crate::platform_function::PlatformFunction;
use crate::vm::constant::Constant;
use crate::vm::function::Function;
use crate::vm::value::Value;
use std::collections::HashMap;
use std::rc::Rc;
pub mod constant;
pub mod function;
pub mod value;
pub struct DvmContext {
functions: HashMap<Rc<str>, Function>,
platform_functions: HashMap<Rc<str>, PlatformFunction>,
constants: HashMap<Rc<str>, Constant>,
}
impl DvmContext {
pub fn new() -> Self {
Self {
functions: HashMap::new(),
platform_functions: HashMap::new(),
constants: HashMap::new(),
}
}
pub fn functions(&self) -> &HashMap<Rc<str>, Function> {
&self.functions
}
pub fn functions_mut(&mut self) -> &mut HashMap<Rc<str>, Function> {
&mut self.functions
}
pub fn platform_functions(&self) -> &HashMap<Rc<str>, PlatformFunction> {
&self.platform_functions
}
pub fn platform_functions_mut(&mut self) -> &mut HashMap<Rc<str>, PlatformFunction> {
&mut self.platform_functions
}
pub fn add_function(&mut self, function: Function) {
self.functions.insert(function.name_owned(), function);
}
pub fn constants(&self) -> &HashMap<Rc<str>, Constant> {
&self.constants
}
pub fn add_constant(&mut self, constant: Constant) {
match &constant {
Constant::String(string_constant) => {
self.constants
.insert(string_constant.name_owned(), constant);
}
}
}
}
pub struct DvmState {
stack: Vec<Value>,
registers: Vec<Value>,
ip: usize,
fp: usize,
}
impl DvmState {
pub fn new() -> Self {
Self {
stack: vec![],
registers: vec![],
ip: 0,
fp: 0,
}
}
pub fn stack(&self) -> &Vec<Value> {
&self.stack
}
pub fn stack_mut(&mut self) -> &mut Vec<Value> {
&mut self.stack
}
pub fn registers(&self) -> &Vec<Value> {
&self.registers
}
pub fn registers_mut(&mut self) -> &mut Vec<Value> {
&mut self.registers
}
pub fn ensure_registers(&mut self, count: usize) {
self.registers.resize_with(count, Default::default);
}
pub fn ip(&self) -> usize {
self.ip
}
pub fn increment_ip(&mut self) {
self.ip += 1;
}
pub fn fp(&self) -> usize {
self.fp
}
pub fn set_fp(&mut self, fp: usize) {
self.fp = fp;
}
}
pub fn call(
context: &DvmContext,
state: &mut DvmState,
function_name: &str,
arguments: Vec<Value>,
) -> Option<Value> {
let function = context
.functions
.get(function_name)
.expect(&format!("Function {} not found", function_name));
let instructions = function.instructions();
state.ensure_registers(function.register_count());
// put each arg on the stack
for argument in arguments {
state.stack_mut().push(argument);
}
while state.ip() < instructions.len() {
let instruction = &instructions[state.ip()];
match instruction {
/* Move instructions */
Instruction::MoveRegister(source, destination) => {
// copy value from one register to another register
let value = state.registers()[*source].clone();
state.registers_mut()[*destination] = value;
}
Instruction::MoveInt(value, destination) => {
state.registers_mut()[*destination] = Value::Int(*value);
}
Instruction::MoveStackFrameOffset(offset, destination) => {
// copy a value offset from the current frame pointer (fp) to a register
let value_index = state
.fp()
.checked_add_signed(*offset)
.expect("Overflow when adding offset to fp");
let value = state.stack()[value_index].clone();
state.registers_mut()[*destination] = value;
}
/* Push instructions */
Instruction::PushRegister(source) => {
// copy a value from a register to the top of the stack
let value = state.registers()[*source].clone();
state.stack_mut().push(value);
}
Instruction::PushInt(value) => {
state.stack_mut().push(Value::Int(*value));
}
Instruction::PushStackFrameOffset(offset) => {
// copy a value from somewhere on the stack to the top of the stack
let value_index = state
.fp()
.checked_add_signed(*offset)
.expect("Overflow when adding offset to fp");
let value = state.stack()[value_index].clone();
state.stack_mut().push(value);
}
/* Invoke instructions */
Instruction::InvokePlatformStatic(function_name, arg_count) => {
let stack = state.stack();
let args = &stack[stack.len() - arg_count..];
let platform_function = context
.platform_functions()
.get(function_name)
.expect(&format!("Platform function {} not found", function_name));
let result = platform_function(context, state, args);
match result {
Ok(return_value) => {
// pop args off the stack
let mut i: usize = 0;
while i < *arg_count {
state.stack_mut().pop();
i += 1;
}
state.stack_mut().push(return_value);
}
Err(error) => {
// Eventually we will have some kind of exception handling
panic!("{}", error);
}
}
}
/* Load constant instructions */
Instruction::LoadStringConstant(constant_name, destination) => {
let constant = &context.constants()[constant_name];
match constant {
Constant::String(string_constant) => {
state.registers_mut()[*destination] =
Value::String(string_constant.content_owned());
}
}
}
/* Pop instructions */
Instruction::Pop(maybe_register) => {
let value = state.stack_mut().pop().unwrap();
if let Some(register) = maybe_register {
state.registers_mut()[*register] = value;
}
}
}
state.increment_ip();
}
None // todo: returning results from main functions
}

14
dvm-lib/src/vm/value.rs Normal file
View File

@ -0,0 +1,14 @@
use std::rc::Rc;
#[derive(Clone, Debug)]
pub enum Value {
Int(i32),
String(Rc<str>),
Null,
}
impl Default for Value {
fn default() -> Value {
Value::Null
}
}

6
examples/hello.dm Normal file
View File

@ -0,0 +1,6 @@
extern fn println(message: Any)
fn main()
let x = "Hello, World!"
println(x)
end

View File

@ -0,0 +1,2 @@
fn main()
println(42)