Implement AST for 'const' expressions. TODO -- add const expressions to compiler

This commit is contained in:
2025-12-04 18:01:16 -07:00
parent e05f130040
commit 4b6c7eb63c
9 changed files with 123 additions and 19 deletions

View File

@@ -149,6 +149,7 @@ fn test_spilled_variable_update_in_branch() -> anyhow::Result<()> {
sub r0 sp 1 sub r0 sp 1
put db r0 99 #h put db r0 99 #h
L1: L1:
sub sp sp 1
" "
} }
); );

View File

@@ -34,6 +34,8 @@ fn no_arguments() -> anyhow::Result<()> {
#[test] #[test]
fn let_var_args() -> anyhow::Result<()> { fn let_var_args() -> anyhow::Result<()> {
// !IMPORTANT this needs to be stabilized as it currently incorrectly calculates sp offset at
// both ends of the cleanup lifecycle
let compiled = compile! { let compiled = compile! {
debug debug
" "
@@ -64,6 +66,7 @@ fn let_var_args() -> anyhow::Result<()> {
get r8 db r0 get r8 db r0
sub sp sp 1 sub sp sp 1
move r9 r15 #i move r9 r15 #i
sub sp sp 1
" "
} }
); );
@@ -123,6 +126,7 @@ fn inline_literal_args() -> anyhow::Result<()> {
get r8 db r0 get r8 db r0
sub sp sp 1 sub sp sp 1
move r9 r15 #returnedValue move r9 r15 #returnedValue
sub sp sp 1
" "
} }
); );
@@ -164,6 +168,7 @@ fn mixed_args() -> anyhow::Result<()> {
get r8 db r0 get r8 db r0
sub sp sp 1 sub sp sp 1
move r9 r15 #returnValue move r9 r15 #returnValue
sub sp sp 1
" "
} }
); );

View File

@@ -56,6 +56,7 @@ fn variable_declaration_numeric_literal_stack_spillover() -> anyhow::Result<()>
push 7 #h push 7 #h
push 8 #i push 8 #i
push 9 #j push 9 #j
sub sp sp 3
" "
} }
); );

View File

@@ -462,6 +462,15 @@ impl<'a> Parser<'a> {
}) })
} }
TokenType::Keyword(Keyword::Const) => {
let spanned_const = self.spanned(|p| p.const_declaration())?;
Some(Spanned {
span: spanned_const.span,
node: Expression::ConstDeclaration(spanned_const),
})
}
TokenType::Keyword(Keyword::Fn) => { TokenType::Keyword(Keyword::Fn) => {
let spanned_fn = self.spanned(|p| p.function())?; let spanned_fn = self.spanned(|p| p.function())?;
Some(Spanned { Some(Spanned {
@@ -1220,6 +1229,46 @@ impl<'a> Parser<'a> {
Ok(BlockExpression(expressions)) Ok(BlockExpression(expressions))
} }
fn const_declaration(&mut self) -> Result<ConstDeclarationExpression, Error> {
// const
let current_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?;
if !self_matches_current!(self, TokenType::Keyword(Keyword::Const)) {
return Err(Error::UnexpectedToken(
self.current_span(),
current_token.clone(),
));
}
// variable_name
let ident_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?;
let ident_span = Self::token_to_span(ident_token);
let ident = match ident_token.token_type {
TokenType::Identifier(ref id) => id.clone(),
_ => return Err(Error::UnexpectedToken(ident_span, ident_token.clone())),
};
// `=`
let assign_token = self.get_next()?.ok_or(Error::UnexpectedEOF)?.clone();
if !token_matches!(assign_token, TokenType::Symbol(Symbol::Assign)) {
return Err(Error::UnexpectedToken(
Self::token_to_span(&assign_token),
assign_token,
));
}
// literal value
self.assign_next()?;
let lit = self.spanned(|p| p.literal())?;
Ok(ConstDeclarationExpression {
name: Spanned {
span: ident_span,
node: ident,
},
value: lit,
})
}
fn declaration(&mut self) -> Result<Expression, Error> { fn declaration(&mut self) -> Result<Expression, Error> {
let current_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?; let current_token = self.current_token.as_ref().ok_or(Error::UnexpectedEOF)?;
if !self_matches_current!(self, TokenType::Keyword(Keyword::Let)) { if !self_matches_current!(self, TokenType::Keyword(Keyword::Let)) {

View File

@@ -1,12 +0,0 @@
use crate::sys_call;
use helpers::Documentation;
use pretty_assertions::assert_eq;
#[test]
fn test_token_tree_docs() -> anyhow::Result<()> {
let syscall = sys_call::System::Yield;
assert_eq!(syscall.docs(), "");
Ok(())
}

View File

@@ -1,12 +1,11 @@
#[macro_export] #[macro_export]
macro_rules! parser { macro_rules! parser {
($input:expr) => { ($input:expr) => {
Parser::new(Tokenizer::from($input.to_owned())) Parser::new(Tokenizer::from($input))
}; };
} }
mod blocks; mod blocks;
mod docs;
use super::Parser; use super::Parser;
use super::Tokenizer; use super::Tokenizer;
use anyhow::Result; use anyhow::Result;
@@ -33,7 +32,7 @@ fn test_declarations() -> Result<()> {
// The below line should fail // The below line should fail
let y = 234 let y = 234
"#; "#;
let tokenizer = Tokenizer::from(input.to_owned()); let tokenizer = Tokenizer::from(input);
let mut parser = Parser::new(tokenizer); let mut parser = Parser::new(tokenizer);
let expression = parser.parse()?.unwrap(); let expression = parser.parse()?.unwrap();
@@ -45,6 +44,36 @@ fn test_declarations() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_const_declaration() -> Result<()> {
let input = r#"
const item = 20c;
const decimal = 200.15;
const nameConst = "str_lit";
"#;
let tokenizer = Tokenizer::from(input);
let mut parser = Parser::new(tokenizer);
assert_eq!(
"(const item = 293.15)",
parser.parse()?.unwrap().to_string()
);
assert_eq!(
"(const decimal = 200.15)",
parser.parse()?.unwrap().to_string()
);
assert_eq!(
r#"(const nameConst = "str_lit")"#,
parser.parse()?.unwrap().to_string()
);
assert_eq!(None, parser.parse()?);
Ok(())
}
#[test] #[test]
fn test_function_expression() -> Result<()> { fn test_function_expression() -> Result<()> {
let input = r#" let input = r#"
@@ -54,7 +83,7 @@ fn test_function_expression() -> Result<()> {
} }
"#; "#;
let tokenizer = Tokenizer::from(input.to_owned()); let tokenizer = Tokenizer::from(input);
let mut parser = Parser::new(tokenizer); let mut parser = Parser::new(tokenizer);
let expression = parser.parse()?.unwrap(); let expression = parser.parse()?.unwrap();
@@ -73,7 +102,7 @@ fn test_function_invocation() -> Result<()> {
add(); add();
"#; "#;
let tokenizer = Tokenizer::from(input.to_owned()); let tokenizer = Tokenizer::from(input);
let mut parser = Parser::new(tokenizer); let mut parser = Parser::new(tokenizer);
let expression = parser.parse()?.unwrap(); let expression = parser.parse()?.unwrap();
@@ -89,7 +118,7 @@ fn test_priority_expression() -> Result<()> {
let x = (4); let x = (4);
"#; "#;
let tokenizer = Tokenizer::from(input.to_owned()); let tokenizer = Tokenizer::from(input);
let mut parser = Parser::new(tokenizer); let mut parser = Parser::new(tokenizer);
let expression = parser.parse()?.unwrap(); let expression = parser.parse()?.unwrap();

View File

@@ -195,6 +195,18 @@ impl std::fmt::Display for LiteralOrVariable {
} }
} }
#[derive(Debug, PartialEq, Eq)]
pub struct ConstDeclarationExpression {
pub name: Spanned<String>,
pub value: Spanned<Literal>,
}
impl std::fmt::Display for ConstDeclarationExpression {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(const {} = {})", self.name, self.value)
}
}
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct DeviceDeclarationExpression { pub struct DeviceDeclarationExpression {
/// any variable-like name /// any variable-like name
@@ -316,6 +328,7 @@ pub enum Expression {
Binary(Spanned<BinaryExpression>), Binary(Spanned<BinaryExpression>),
Block(Spanned<BlockExpression>), Block(Spanned<BlockExpression>),
Break(Span), Break(Span),
ConstDeclaration(Spanned<ConstDeclarationExpression>),
Continue(Span), Continue(Span),
Declaration(Spanned<String>, Box<Spanned<Expression>>), Declaration(Spanned<String>, Box<Spanned<Expression>>),
DeviceDeclaration(Spanned<DeviceDeclarationExpression>), DeviceDeclaration(Spanned<DeviceDeclarationExpression>),
@@ -342,6 +355,7 @@ impl std::fmt::Display for Expression {
Expression::Binary(e) => write!(f, "{}", e), Expression::Binary(e) => write!(f, "{}", e),
Expression::Block(e) => write!(f, "{}", e), Expression::Block(e) => write!(f, "{}", e),
Expression::Break(_) => write!(f, "break"), Expression::Break(_) => write!(f, "break"),
Expression::ConstDeclaration(e) => write!(f, "{}", e),
Expression::Continue(_) => write!(f, "continue"), Expression::Continue(_) => write!(f, "continue"),
Expression::Declaration(id, e) => write!(f, "(let {} = {})", id, e), Expression::Declaration(id, e) => write!(f, "(let {} = {})", id, e),
Expression::DeviceDeclaration(e) => write!(f, "{}", e), Expression::DeviceDeclaration(e) => write!(f, "{}", e),
@@ -362,4 +376,3 @@ impl std::fmt::Display for Expression {
} }
} }
} }

View File

@@ -463,6 +463,7 @@ impl<'a> Tokenizer<'a> {
"break" if next_ws!() => keyword!(Break), "break" if next_ws!() => keyword!(Break),
"while" if next_ws!() => keyword!(While), "while" if next_ws!() => keyword!(While),
"continue" if next_ws!() => keyword!(Continue), "continue" if next_ws!() => keyword!(Continue),
"const" if next_ws!() => keyword!(Const),
"true" if next_ws!() => { "true" if next_ws!() => {
return Ok(Token::new( return Ok(Token::new(
TokenType::Boolean(true), TokenType::Boolean(true),

View File

@@ -295,6 +295,20 @@ documented! {
/// } /// }
/// ``` /// ```
Continue, Continue,
/// Prepresents the `const` keyword. This allows you to define a variable that will never
/// change throughout the lifetime of the program, similar to `define` in IC10. If you are
/// not planning on mutating the variable (changing it), it is recommend you store it as a
/// const, as the compiler will not assign it to a register or stack variable.
///
/// ## Example
/// ```
/// const targetTemp = 20c;
/// device gasSensor = "d0";
/// device airCon = "d1";
///
/// airCon.On = gasSensor.Temperature > targetTemp;
/// ```
Const,
/// Represents the `let` keyword, used to declare variables within Slang. /// Represents the `let` keyword, used to declare variables within Slang.
/// ## Example /// ## Example
/// ``` /// ```
@@ -304,6 +318,9 @@ documented! {
/// ``` /// ```
Let, Let,
/// Represents the `fn` keyword, used to declare functions within Slang. /// Represents the `fn` keyword, used to declare functions within Slang.
/// # WARNING
/// Functions are currently unstable and are subject to change until stabilized. Use at
/// your own risk! (They are also heavily not optimized and produce a LOT of code bloat)
/// ## Example /// ## Example
/// ``` /// ```
/// // This allows you to now call `doSomething` with specific arguments. /// // This allows you to now call `doSomething` with specific arguments.