Moving to dmc-lib, keeping it smaller!

This commit is contained in:
Jesse Brault 2026-02-27 12:13:28 -06:00
parent 8ba79be920
commit 8df46eec8d
23 changed files with 1352 additions and 1 deletions

4
Cargo.lock generated
View File

@ -204,6 +204,10 @@ dependencies = [
"crypto-common", "crypto-common",
] ]
[[package]]
name = "dmc-lib"
version = "0.1.0"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.35" version = "0.8.35"

View File

@ -25,4 +25,4 @@ cst-test-generator = { path = "cst-test-generator" }
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["ast-generator", "cst-test-generator"] members = ["ast-generator", "cst-test-generator", "dmc-lib"]

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

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

80
dmc-lib/src/ast/call.rs Normal file
View File

@ -0,0 +1,80 @@
use crate::ast::expression::Expression;
use crate::diagnostic::Diagnostic;
use crate::source_range::SourceRange;
use crate::symbol_table::SymbolTable;
use crate::type_info::TypeInfo;
pub struct Call {
callee: Box<Expression>,
arguments: Vec<Expression>,
source_range: SourceRange,
}
impl Call {
pub fn new(callee: Expression, arguments: Vec<Expression>, source_range: SourceRange) -> Self {
Self {
callee: callee.into(),
arguments,
source_range,
}
}
pub fn callee(&self) -> &Expression {
&self.callee
}
pub fn arguments(&self) -> Vec<&Expression> {
self.arguments.iter().collect()
}
pub fn gather_declared_names(&mut self, symbol_table: &mut SymbolTable) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
diagnostics.append(&mut self.callee.gather_declared_names(symbol_table));
for argument in &mut self.arguments {
diagnostics.append(&mut argument.gather_declared_names(symbol_table));
}
diagnostics
}
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
diagnostics.append(&mut self.callee.check_name_usages(symbol_table));
for argument in &mut self.arguments {
diagnostics.append(&mut argument.check_name_usages(symbol_table));
}
diagnostics
}
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
diagnostics.append(&mut self.callee.type_check(symbol_table));
for argument in &mut self.arguments {
diagnostics.append(&mut argument.type_check(symbol_table));
}
// check that callee is callable
match self.callee.type_info() {
TypeInfo::Function(_) => {}
_ => {
diagnostics.push(Diagnostic::new(
"Receiver is not callable",
self.callee.source_range().start(),
self.callee.source_range().end(),
));
}
}
diagnostics
}
pub fn type_info(&self) -> TypeInfo {
match self.callee.type_info() {
TypeInfo::Function(function_symbol) => function_symbol.return_type(),
_ => panic!(),
}
}
pub fn source_range(&self) -> &SourceRange {
&self.source_range
}
}

View File

@ -0,0 +1,43 @@
use crate::ast::function::Function;
use crate::diagnostic::Diagnostic;
use crate::symbol_table::SymbolTable;
pub struct CompilationUnit {
functions: Vec<Function>,
}
impl CompilationUnit {
pub fn new(functions: Vec<Function>) -> Self {
Self { functions }
}
pub fn functions(&self) -> Vec<&Function> {
self.functions.iter().collect()
}
pub fn gather_declared_names(&mut self, symbol_table: &mut SymbolTable) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
symbol_table.push_scope("compilation_unit_scope");
for function in &mut self.functions {
diagnostics.append(&mut function.gather_declared_names(symbol_table));
}
symbol_table.pop_scope();
diagnostics
}
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
for function in &mut self.functions {
diagnostics.append(&mut function.check_name_usages(symbol_table));
}
diagnostics
}
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
for function in &mut self.functions {
diagnostics.append(&mut function.type_check(symbol_table));
}
diagnostics
}
}

View File

@ -0,0 +1,58 @@
use crate::ast::call::Call;
use crate::ast::identifier::Identifier;
use crate::ast::integer_literal::IntegerLiteral;
use crate::ast::string_literal::StringLiteral;
use crate::diagnostic::Diagnostic;
use crate::source_range::SourceRange;
use crate::symbol_table::SymbolTable;
use crate::type_info::TypeInfo;
pub enum Expression {
Call(Call),
IntegerLiteral(IntegerLiteral),
String(StringLiteral),
Identifier(Identifier),
}
impl Expression {
pub fn gather_declared_names(&mut self, symbol_table: &mut SymbolTable) -> Vec<Diagnostic> {
match self {
Expression::Call(call) => call.gather_declared_names(symbol_table),
Expression::Identifier(identifier) => identifier.gather_declared_names(symbol_table),
_ => vec![],
}
}
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
match self {
Expression::Call(call) => call.check_name_usages(symbol_table),
Expression::Identifier(identifier) => identifier.check_name_usages(symbol_table),
_ => vec![],
}
}
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
match self {
Expression::Call(call) => call.type_check(symbol_table),
_ => vec![],
}
}
pub fn type_info(&self) -> TypeInfo {
match self {
Expression::Call(call) => call.type_info(),
Expression::IntegerLiteral(_) => TypeInfo::Integer,
Expression::String(_) => TypeInfo::String,
Expression::Identifier(identifier) => identifier.type_info(),
}
}
pub fn source_range(&self) -> &SourceRange {
match self {
Expression::Call(call) => call.source_range(),
Expression::IntegerLiteral(integer_literal) => integer_literal.source_range(),
Expression::String(string_literal) => string_literal.source_range(),
Expression::Identifier(identifier) => identifier.source_range(),
}
}
}

View File

@ -0,0 +1,78 @@
use crate::ast::statement::Statement;
use crate::diagnostic::Diagnostic;
use crate::source_range::SourceRange;
use crate::symbol::FunctionSymbol;
use crate::symbol_table::{SymbolInsertError, SymbolTable};
pub struct Function {
declared_name: String,
declared_name_source_range: SourceRange,
statements: Vec<Statement>,
}
impl Function {
pub fn new(
declared_name: &str,
declared_name_source_range: SourceRange,
statements: Vec<Statement>,
) -> Self {
Self {
declared_name: declared_name.to_string(),
declared_name_source_range,
statements,
}
}
pub fn declared_name(&self) -> &str {
&self.declared_name
}
pub fn statements(&self) -> Vec<&Statement> {
self.statements.iter().collect()
}
pub fn gather_declared_names(&mut self, symbol_table: &mut SymbolTable) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
// insert function symbol
let insert_result = symbol_table.insert_function_symbol(FunctionSymbol::new(
self.declared_name(),
&vec![], // todo
));
if let Err(symbol_insert_error) = insert_result {
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!("function_scope({})", self.declared_name()));
for statement in &mut self.statements {
diagnostics.append(&mut statement.gather_declared_names(symbol_table));
}
symbol_table.pop_scope();
diagnostics
}
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
for statement in &mut self.statements {
diagnostics.append(&mut statement.check_name_usages(symbol_table));
}
diagnostics
}
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
for statement in &mut self.statements {
diagnostics.append(&mut statement.type_check(symbol_table));
}
diagnostics
}
}

View File

@ -0,0 +1,64 @@
use crate::diagnostic::Diagnostic;
use crate::source_range::SourceRange;
use crate::symbol::ExpressibleSymbol;
use crate::symbol_table::SymbolTable;
use crate::type_info::TypeInfo;
pub struct Identifier {
name: String,
scope_id: Option<usize>,
expressible_symbol: Option<ExpressibleSymbol>,
source_range: SourceRange,
}
impl Identifier {
pub fn new(name: &str, source_range: SourceRange) -> Self {
Self {
name: name.into(),
scope_id: None,
expressible_symbol: None,
source_range,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn gather_declared_names(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
self.scope_id = Some(symbol_table.current_scope_id());
vec![]
}
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
let maybe_expressible_symbol =
symbol_table.find_expressible_symbol(self.scope_id.unwrap(), &self.name);
match maybe_expressible_symbol {
None => {
vec![Diagnostic::new(
&format!("Unable to resolve symbol {}", self.name),
self.source_range.start(),
self.source_range.end(),
)]
}
Some(expressible_symbol) => {
self.expressible_symbol = Some(expressible_symbol);
vec![]
}
}
}
pub fn type_info(&self) -> TypeInfo {
match self.expressible_symbol.as_ref().unwrap() {
ExpressibleSymbol::Function(function_symbol) => {
TypeInfo::Function(function_symbol.clone())
}
ExpressibleSymbol::Parameter(parameter_symbol) => parameter_symbol.type_info().clone(),
ExpressibleSymbol::Variable(variable_symbol) => variable_symbol.type_info().clone(),
}
}
pub fn source_range(&self) -> &SourceRange {
&self.source_range
}
}

View File

@ -0,0 +1,23 @@
use crate::source_range::SourceRange;
pub struct IntegerLiteral {
value: i64,
source_range: SourceRange,
}
impl IntegerLiteral {
pub fn new(value: i64, source_range: SourceRange) -> Self {
Self {
value,
source_range,
}
}
pub fn value(&self) -> i64 {
self.value
}
pub fn source_range(&self) -> &SourceRange {
&self.source_range
}
}

View File

@ -0,0 +1,71 @@
use crate::ast::expression::Expression;
use crate::diagnostic::Diagnostic;
use crate::source_range::SourceRange;
use crate::symbol::VariableSymbol;
use crate::symbol_table::{SymbolInsertError, SymbolTable};
pub struct LetStatement {
declared_name: String,
declared_name_source_range: SourceRange,
initializer: Box<Expression>,
}
impl LetStatement {
pub fn new(
declared_name: &str,
declared_name_source_range: SourceRange,
initializer: Expression,
) -> Self {
Self {
declared_name: declared_name.to_string(),
declared_name_source_range,
initializer: initializer.into(),
}
}
pub fn declared_name(&self) -> &str {
&self.declared_name
}
pub fn initializer(&self) -> &Expression {
&self.initializer
}
pub fn initializer_mut(&mut self) -> &mut Expression {
&mut self.initializer
}
pub fn gather_declared_names(&mut self, symbol_table: &mut SymbolTable) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
self.initializer_mut().gather_declared_names(symbol_table);
let insert_result = symbol_table.insert_variable_symbol(VariableSymbol::new(
self.declared_name(),
self.initializer().type_info().clone(),
));
if let Err(symbol_insert_error) = insert_result {
match symbol_insert_error {
SymbolInsertError::AlreadyDeclared(already_declared) => {
diagnostics.push(Diagnostic::new(
&format!(
"Symbol {} already declared in current scope",
already_declared.name()
),
self.declared_name_source_range.start(),
self.declared_name_source_range.end(),
))
}
}
}
diagnostics
}
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
self.initializer.check_name_usages(symbol_table)
}
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
let mut diagnostics = vec![];
diagnostics.append(&mut self.initializer.type_check(symbol_table));
diagnostics
}
}

43
dmc-lib/src/ast/mod.rs Normal file
View File

@ -0,0 +1,43 @@
pub mod call;
pub mod compilation_unit;
pub mod expression;
pub mod function;
pub mod identifier;
pub mod integer_literal;
pub mod let_statement;
pub mod statement;
pub mod string_literal;
#[cfg(test)]
mod name_tests {
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);
}
#[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,32 @@
use crate::ast::expression::Expression;
use crate::ast::let_statement::LetStatement;
use crate::diagnostic::Diagnostic;
use crate::symbol_table::SymbolTable;
pub enum Statement {
Let(LetStatement),
Expression(Expression),
}
impl Statement {
pub fn gather_declared_names(&mut self, symbol_table: &mut SymbolTable) -> Vec<Diagnostic> {
match self {
Statement::Let(let_statement) => let_statement.gather_declared_names(symbol_table),
Statement::Expression(expression) => expression.gather_declared_names(symbol_table),
}
}
pub fn check_name_usages(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
match self {
Statement::Let(let_statement) => let_statement.check_name_usages(symbol_table),
Statement::Expression(expression) => expression.check_name_usages(symbol_table),
}
}
pub fn type_check(&mut self, symbol_table: &SymbolTable) -> Vec<Diagnostic> {
match self {
Statement::Let(let_statement) => let_statement.type_check(symbol_table),
Statement::Expression(expression) => expression.type_check(symbol_table),
}
}
}

View File

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

28
dmc-lib/src/diagnostic.rs Normal file
View File

@ -0,0 +1,28 @@
#[derive(Debug)]
pub struct Diagnostic {
message: String,
start: usize,
end: usize,
}
impl Diagnostic {
pub fn new(message: &str, start: usize, end: usize) -> Self {
Self {
message: message.into(),
start,
end,
}
}
pub fn message(&self) -> &str {
&self.message
}
pub fn start(&self) -> usize {
self.start
}
pub fn end(&self) -> usize {
self.end
}
}

157
dmc-lib/src/lexer.rs Normal file
View File

@ -0,0 +1,157 @@
use crate::token::{Token, TokenKind};
pub struct Lexer<'a> {
input: &'a str,
position: usize,
}
impl<'a> Lexer<'a> {
pub fn new(input: &'a str) -> Self {
Self { input, position: 0 }
}
pub fn next(&mut self) -> Option<Result<Token, LexerError>> {
let maybe_chunk = self.input.get(self.position..);
if maybe_chunk.is_none() {
return None;
}
let mut chunk = maybe_chunk.unwrap();
if chunk.is_empty() {
// in case we're done
return None;
}
while chunk.starts_with(&[' ', '\t', '\r', '\n']) {
// ignore whitespace
self.position += 1;
let maybe_chunk = self.input.get(self.position..);
if maybe_chunk.is_none() {
return None;
} else {
chunk = maybe_chunk.unwrap();
}
}
let token = if chunk.starts_with("(") {
Token::new(self.position, self.position + 1, TokenKind::LeftParentheses)
} else if chunk.starts_with(")") {
Token::new(
self.position,
self.position + 1,
TokenKind::RightParentheses,
)
} else if chunk.starts_with("=") {
Token::new(self.position, self.position + 1, TokenKind::Equals)
} else {
// more than one char token
if chunk.starts_with(|c: char| c.is_ascii_digit()) {
// number literal
let mut end = self.position;
for char in chunk.chars() {
if char.is_ascii_digit() {
end += 1;
} else {
break;
}
}
Token::new(self.position, end, TokenKind::IntegerLiteral)
} else if chunk.starts_with("\"") {
// string literal
let mut end = self.position;
let mut terminated = false;
let mut chars = chunk.chars();
chars.next(); // skip opening quote
end += 1;
for char in chars {
end += 1;
if char == '"' {
terminated = true;
break;
}
}
if !terminated {
return Some(Err(LexerError::new(LexerErrorKind::UnterminatedString)));
}
Token::new(self.position, end, TokenKind::String)
} else {
// keyword or identifier
let mut prefix = String::new();
for char in chunk.chars() {
if char.is_alphanumeric() || char == '_' {
prefix.push(char);
} else {
break;
}
}
let token_kind = match prefix.as_str() {
"fn" => TokenKind::Fn,
"end" => TokenKind::End,
"let" => TokenKind::Let,
_ => TokenKind::Identifier,
};
Token::new(self.position, self.position + prefix.len(), token_kind)
}
};
self.position += token.end() - token.start();
Some(Ok(token))
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct LexerError {
kind: LexerErrorKind,
}
impl LexerError {
pub fn new(kind: LexerErrorKind) -> Self {
Self { kind }
}
pub fn kind(&self) -> LexerErrorKind {
self.kind
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum LexerErrorKind {
UnterminatedString,
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_next(lexer: &mut Lexer, kind: TokenKind, length: usize) {
let token = lexer.next().unwrap().unwrap();
println!("{:?}", token);
assert_eq!(token.kind(), kind);
assert_eq!(token.end() - token.start(), length);
}
#[test]
fn forty_two() {
let mut lexer = Lexer::new("fn main() 42 end");
assert_next(&mut lexer, TokenKind::Fn, 2);
assert_next(&mut lexer, TokenKind::Identifier, 4);
assert_next(&mut lexer, TokenKind::LeftParentheses, 1);
assert_next(&mut lexer, TokenKind::RightParentheses, 1);
assert_next(&mut lexer, TokenKind::IntegerLiteral, 2);
assert_next(&mut lexer, TokenKind::End, 3);
assert_eq!(lexer.next(), None);
}
#[test]
fn hello_world() {
let mut lexer = Lexer::new("fn main() println(\"Hello, World!\") end");
assert_next(&mut lexer, TokenKind::Fn, 2);
assert_next(&mut lexer, TokenKind::Identifier, 4);
assert_next(&mut lexer, TokenKind::LeftParentheses, 1);
assert_next(&mut lexer, TokenKind::RightParentheses, 1);
assert_next(&mut lexer, TokenKind::Identifier, 7);
assert_next(&mut lexer, TokenKind::LeftParentheses, 1);
assert_next(&mut lexer, TokenKind::String, 15);
assert_next(&mut lexer, TokenKind::RightParentheses, 1);
assert_next(&mut lexer, TokenKind::End, 3);
assert_eq!(lexer.next(), None);
}
}

10
dmc-lib/src/lib.rs Normal file
View File

@ -0,0 +1,10 @@
mod ast;
mod diagnostic;
mod lexer;
mod parser;
mod scope;
mod source_range;
mod symbol;
mod symbol_table;
mod token;
mod type_info;

279
dmc-lib/src/parser.rs Normal file
View File

@ -0,0 +1,279 @@
use crate::ast::call::Call;
use crate::ast::compilation_unit::CompilationUnit;
use crate::ast::expression::Expression;
use crate::ast::function::Function;
use crate::ast::identifier::Identifier;
use crate::ast::integer_literal::IntegerLiteral;
use crate::ast::let_statement::LetStatement;
use crate::ast::statement::Statement;
use crate::ast::string_literal::StringLiteral;
use crate::lexer::Lexer;
use crate::source_range::SourceRange;
use crate::token::{Token, TokenKind};
use std::str::FromStr;
pub fn parse_compilation_unit(input: &str) -> CompilationUnit {
let mut parser = Parser::new(input);
parser.compilation_unit()
}
struct Parser<'a> {
input: &'a str,
lexer: Lexer<'a>,
current: Option<Token>,
lookahead: Option<Token>,
}
impl<'a> Parser<'a> {
fn new(input: &'a str) -> Self {
Self {
input,
lexer: Lexer::new(input),
current: None,
lookahead: None,
}
}
fn advance(&mut self) {
if self.lookahead.is_some() {
// we've advanced at least once
self.current = self.lookahead.take();
self.lookahead = match self.lexer.next() {
None => None,
Some(result) => match result {
Ok(token) => Some(token),
Err(lexer_error) => {
panic!("{:?}", lexer_error);
}
},
}
} else {
// we've not yet advanced, so fetch both
// current
match self.lexer.next() {
None => {}
Some(result) => match result {
Ok(token) => {
self.current = Some(token);
}
Err(lexer_error) => {
panic!("{:?}", lexer_error);
}
},
}
// lookahead
match self.lexer.next() {
None => {}
Some(result) => match result {
Ok(token) => {
self.lookahead = Some(token);
}
Err(lexer_error) => {
panic!("{:?}", lexer_error);
}
},
}
}
}
fn expect_advance(&mut self, token_kind: TokenKind) -> Token {
match self.current.take() {
None => {
panic!("Expected {:?} but found end of input", token_kind);
}
Some(token) => {
if token.kind() == token_kind {
self.advance();
token
} else {
panic!(
"Expected {:?} but found {:?} at {}",
token_kind,
token.kind(),
token.start()
);
}
}
}
}
fn peek_current(&self, token_kind: TokenKind) -> bool {
match &self.current {
None => false,
Some(token) => token.kind() == token_kind,
}
}
fn get_current(&self) -> &Token {
match &self.current {
None => {
panic!("Unexpected end of input");
}
Some(token) => token,
}
}
fn sample_input(&self, start: usize, end: usize) -> &'a str {
&self.input[start..end]
}
fn token_text(&self, token: &Token) -> &'a str {
self.sample_input(token.start(), token.end())
}
pub fn compilation_unit(&mut self) -> CompilationUnit {
let mut functions = vec![];
self.advance();
while self.current.is_some() {
functions.push(self.function());
}
CompilationUnit::new(functions)
}
fn function(&mut self) -> Function {
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![];
while !self.peek_current(TokenKind::End) {
statements.push(self.statement());
}
self.expect_advance(TokenKind::End);
Function::new(
self.token_text(&identifier_token),
SourceRange::new(identifier_token.start(), identifier_token.end()),
statements,
)
}
fn statement(&mut self) -> Statement {
let current = self.get_current();
match current.kind() {
TokenKind::Let => self.let_statement(),
_ => self.expression_statement(),
}
}
fn let_statement(&mut self) -> Statement {
self.expect_advance(TokenKind::Let);
let identifier = self.expect_advance(TokenKind::Identifier);
self.expect_advance(TokenKind::Equals);
let expression = self.expression();
Statement::Let(LetStatement::new(
self.token_text(&identifier),
SourceRange::new(identifier.start(), identifier.end()),
expression,
))
}
fn expression_statement(&mut self) -> Statement {
Statement::Expression(self.expression())
}
fn expression(&mut self) -> Expression {
let current = self.get_current();
let mut result = match current.kind() {
TokenKind::IntegerLiteral => {
let raw = self.token_text(current);
let source_range = SourceRange::new(current.start(), current.end());
self.advance();
Expression::IntegerLiteral(IntegerLiteral::new(
i64::from_str(raw).unwrap(),
source_range,
))
}
TokenKind::String => {
let with_quotes = self.token_text(current);
let source_range = SourceRange::new(current.start(), current.end());
self.advance();
Expression::String(StringLiteral::new(
&with_quotes[1..with_quotes.len() - 1],
source_range,
))
}
TokenKind::Identifier => {
let declared_name = self.token_text(current);
let source_range = SourceRange::new(current.start(), current.end());
self.advance();
Expression::Identifier(Identifier::new(declared_name, source_range))
}
_ => panic!("Unexpected token {:?}", current.kind()),
};
// postfixes
while let Some(current) = &self.current {
match current.kind() {
TokenKind::LeftParentheses => {
result = Expression::Call(self.call(result));
}
_ => break,
}
}
result
}
fn call(&mut self, callee: Expression) -> Call {
self.expect_advance(TokenKind::LeftParentheses);
let mut arguments = vec![];
while !self.peek_current(TokenKind::RightParentheses) {
arguments.push(self.expression());
}
let right_parentheses_token = self.expect_advance(TokenKind::RightParentheses);
let source_range =
SourceRange::new(callee.source_range().start(), right_parentheses_token.end());
Call::new(callee, arguments, source_range)
}
}
#[cfg(test)]
mod smoke_tests {
use super::*;
#[test]
fn forty_two() {
parse_compilation_unit("fn main() 42 end");
}
#[test]
fn hello_world() {
let compilation_unit = parse_compilation_unit("fn main() println(\"Hello, World!\") end");
let functions = compilation_unit.functions();
assert_eq!(functions.len(), 1);
let function = functions[0];
assert_eq!(function.declared_name(), "main");
let statements = function.statements();
assert_eq!(statements.len(), 1);
if let Statement::Expression(expression) = statements[0] {
if let Expression::Call(call) = expression {
let callee = call.callee();
match callee {
Expression::Identifier(identifier) => {
assert_eq!(identifier.name(), "println");
}
_ => panic!("Expected identifier"),
}
let arguments = call.arguments();
assert_eq!(arguments.len(), 1);
let first_argument = arguments[0];
match first_argument {
Expression::String(s) => {
assert_eq!(s.content(), "Hello, World!");
}
_ => panic!("Expected string"),
}
} else {
panic!("Expected call");
}
} else {
panic!("Expected expression");
}
}
#[test]
fn chained_calls() {
parse_compilation_unit("fn main() getCl()() end");
}
}

51
dmc-lib/src/scope.rs Normal file
View File

@ -0,0 +1,51 @@
use crate::symbol::{FunctionSymbol, ParameterSymbol, VariableSymbol};
use std::collections::HashMap;
use std::rc::Rc;
pub struct Scope {
debug_name: String,
parent_id: Option<usize>,
function_symbols: HashMap<Rc<str>, Rc<FunctionSymbol>>,
parameter_symbols: HashMap<Rc<str>, Rc<ParameterSymbol>>,
variable_symbols: HashMap<Rc<str>, Rc<VariableSymbol>>,
}
impl Scope {
pub fn new(debug_name: &str, parent_id: Option<usize>) -> Self {
Self {
debug_name: debug_name.into(),
parent_id,
function_symbols: HashMap::new(),
parameter_symbols: HashMap::new(),
variable_symbols: HashMap::new(),
}
}
pub fn function_symbols(&self) -> &HashMap<Rc<str>, Rc<FunctionSymbol>> {
&self.function_symbols
}
pub fn function_symbols_mut(&mut self) -> &mut HashMap<Rc<str>, Rc<FunctionSymbol>> {
&mut self.function_symbols
}
pub fn parameter_symbols(&self) -> &HashMap<Rc<str>, Rc<ParameterSymbol>> {
&self.parameter_symbols
}
pub fn parameter_symbols_mut(&mut self) -> &mut HashMap<Rc<str>, Rc<ParameterSymbol>> {
&mut self.parameter_symbols
}
pub fn variable_symbols(&self) -> &HashMap<Rc<str>, Rc<VariableSymbol>> {
&self.variable_symbols
}
pub fn variable_symbols_mut(&mut self) -> &mut HashMap<Rc<str>, Rc<VariableSymbol>> {
&mut self.variable_symbols
}
pub fn parent_id(&self) -> Option<usize> {
self.parent_id
}
}

View File

@ -0,0 +1,18 @@
pub struct SourceRange {
start: usize,
end: usize,
}
impl SourceRange {
pub fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
pub fn start(&self) -> usize {
self.start
}
pub fn end(&self) -> usize {
self.end
}
}

90
dmc-lib/src/symbol.rs Normal file
View File

@ -0,0 +1,90 @@
use crate::type_info::TypeInfo;
use std::rc::Rc;
pub struct FunctionSymbol {
name: Rc<str>,
parameters: Vec<Rc<ParameterSymbol>>,
}
impl FunctionSymbol {
pub fn new(name: &str, parameters: &[Rc<ParameterSymbol>]) -> Self {
Self {
name: name.into(),
parameters: parameters.into(),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn name_owned(&self) -> Rc<str> {
self.name.clone()
}
pub fn parameters(&self) -> &[Rc<ParameterSymbol>] {
&self.parameters
}
pub fn return_type(&self) -> TypeInfo {
todo!()
}
}
pub struct ParameterSymbol {
name: Rc<str>,
type_info: TypeInfo,
}
impl ParameterSymbol {
pub fn new(name: &str, type_info: TypeInfo) -> Self {
Self {
name: name.into(),
type_info,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn name_owned(&self) -> Rc<str> {
self.name.clone()
}
pub fn type_info(&self) -> &TypeInfo {
&self.type_info
}
}
pub struct VariableSymbol {
name: Rc<str>,
type_info: TypeInfo,
}
impl VariableSymbol {
pub fn new(name: &str, type_info: TypeInfo) -> Self {
Self {
name: name.into(),
type_info,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn name_owned(&self) -> Rc<str> {
self.name.clone()
}
pub fn type_info(&self) -> &TypeInfo {
&self.type_info
}
}
pub enum ExpressibleSymbol {
Function(Rc<FunctionSymbol>),
Parameter(Rc<ParameterSymbol>),
Variable(Rc<VariableSymbol>),
}

146
dmc-lib/src/symbol_table.rs Normal file
View File

@ -0,0 +1,146 @@
use crate::scope::Scope;
use crate::symbol::{ExpressibleSymbol, FunctionSymbol, ParameterSymbol, VariableSymbol};
use std::rc::Rc;
pub struct SymbolTable {
scopes: Vec<Scope>,
current_scope_id: Option<usize>,
}
impl SymbolTable {
pub fn new() -> Self {
Self {
scopes: vec![],
current_scope_id: None,
}
}
pub fn push_scope(&mut self, debug_name: &str) -> usize {
let scope_id = self.scopes.len();
let parent_id = self.current_scope_id;
let scope = Scope::new(debug_name, parent_id);
self.scopes.push(scope);
self.current_scope_id = Some(scope_id);
scope_id
}
pub fn pop_scope(&mut self) {
self.current_scope_id = self.current_scope().parent_id();
}
pub fn current_scope_id(&self) -> usize {
self.current_scope_id.unwrap()
}
fn current_scope(&self) -> &Scope {
&self.scopes[self.current_scope_id.unwrap()]
}
fn current_scope_mut(&mut self) -> &mut Scope {
&mut self.scopes[self.current_scope_id.unwrap()]
}
fn current_scope_has_name(&self, name: &str) -> bool {
let current_scope = self.current_scope();
current_scope.function_symbols().contains_key(name)
|| current_scope.parameter_symbols().contains_key(name)
|| current_scope.variable_symbols().contains_key(name)
}
pub fn insert_function_symbol(
&mut self,
function_symbol: FunctionSymbol,
) -> Result<(), SymbolInsertError> {
if self.current_scope_has_name(function_symbol.name()) {
return Err(SymbolInsertError::AlreadyDeclared(AlreadyDeclared::new(
function_symbol.name(),
)));
}
self.current_scope_mut()
.function_symbols_mut()
.insert(function_symbol.name_owned(), Rc::new(function_symbol));
Ok(())
}
pub fn insert_parameter_symbol(
&mut self,
parameter_symbol: ParameterSymbol,
) -> Result<(), SymbolInsertError> {
if self.current_scope_has_name(parameter_symbol.name()) {
return Err(SymbolInsertError::AlreadyDeclared(AlreadyDeclared::new(
parameter_symbol.name(),
)));
}
self.current_scope_mut()
.parameter_symbols_mut()
.insert(parameter_symbol.name_owned(), Rc::new(parameter_symbol));
Ok(())
}
pub fn insert_variable_symbol(
&mut self,
variable_symbol: VariableSymbol,
) -> Result<(), SymbolInsertError> {
if self.current_scope_has_name(variable_symbol.name()) {
return Err(SymbolInsertError::AlreadyDeclared(AlreadyDeclared::new(
variable_symbol.name(),
)));
}
self.current_scope_mut()
.variable_symbols_mut()
.insert(variable_symbol.name_owned(), Rc::new(variable_symbol));
Ok(())
}
pub fn find_expressible_symbol(
&self,
scope_id: usize,
name: &str,
) -> Option<ExpressibleSymbol> {
let mut maybe_scope = self.scopes.get(scope_id);
if maybe_scope.is_none() {
panic!("Invalid scope_id: {}", scope_id);
}
while let Some(scope) = maybe_scope {
let maybe_expressible_symbol = scope
.variable_symbols()
.get(name)
.map(|variable_symbol| ExpressibleSymbol::Variable(variable_symbol.clone()))
.or_else(|| {
scope
.function_symbols()
.get(name)
.map(|function_symbol| ExpressibleSymbol::Function(function_symbol.clone()))
})
.or_else(|| {
scope.parameter_symbols().get(name).map(|parameter_symbol| {
ExpressibleSymbol::Parameter(parameter_symbol.clone())
})
});
if maybe_expressible_symbol.is_some() {
return maybe_expressible_symbol;
} else {
maybe_scope = scope.parent_id().map(|id| &self.scopes[id]);
}
}
None
}
}
pub enum SymbolInsertError {
AlreadyDeclared(AlreadyDeclared),
}
pub struct AlreadyDeclared {
name: String,
}
impl AlreadyDeclared {
pub fn new(name: &str) -> Self {
Self { name: name.into() }
}
pub fn name(&self) -> &str {
&self.name
}
}

38
dmc-lib/src/token.rs Normal file
View File

@ -0,0 +1,38 @@
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Token {
start: usize,
end: usize,
kind: TokenKind,
}
impl Token {
pub fn new(start: usize, end: usize, kind: TokenKind) -> Self {
Self { start, end, kind }
}
pub fn start(&self) -> usize {
self.start
}
pub fn end(&self) -> usize {
self.end
}
pub fn kind(&self) -> TokenKind {
self.kind
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum TokenKind {
Fn,
Identifier,
LeftParentheses,
RightParentheses,
End,
Let,
Equals,
IntegerLiteral,
LongLiteral,
String,
}

9
dmc-lib/src/type_info.rs Normal file
View File

@ -0,0 +1,9 @@
use crate::symbol::FunctionSymbol;
use std::rc::Rc;
#[derive(Clone)]
pub enum TypeInfo {
Integer,
String,
Function(Rc<FunctionSymbol>),
}