diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 32522d9..dc88d66 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -9,6 +9,7 @@ use crate::{ }, }; use std::io::SeekFrom; +use sys_call::SysCall; use tree_node::*; quick_error! { @@ -165,14 +166,7 @@ impl Parser { let expr = Some(match current_token.token_type { // match unsupported keywords TokenType::Keyword(e) - if matches_keyword!( - e, - Keyword::Import, - Keyword::Export, - Keyword::Enum, - Keyword::If, - Keyword::Else - ) => + if matches_keyword!(e, Keyword::Enum, Keyword::If, Keyword::Else) => { return Err(ParseError::UnsupportedKeyword(current_token.clone())) } @@ -187,6 +181,11 @@ impl Parser { // match functions with a `fn` keyword TokenType::Keyword(Keyword::Fn) => Expression::FunctionExpression(self.function()?), + // match syscalls with a `syscall` keyword + TokenType::Identifier(ref id) if SysCall::is_syscall(id) => { + Expression::SyscallExpression(self.syscall()?) + } + // match a variable expression with opening parenthesis TokenType::Identifier(_) if self_matches_peek!(self, TokenType::Symbol(Symbol::LParen)) => @@ -689,6 +688,10 @@ impl Parser { body: self.block()?, }) } + + fn syscall(&mut self) -> Result { + todo!("Syscalls are not implemented yet.") + } } #[cfg(test)] @@ -704,12 +707,6 @@ mod tests { #[test] fn test_unsupported_keywords() -> Result<()> { - let mut parser = parser!("import x;"); - assert!(parser.parse().is_err()); - - let mut parser = parser!("export x;"); - assert!(parser.parse().is_err()); - let mut parser = parser!("enum x;"); assert!(parser.parse().is_err()); diff --git a/src/parser/sys_call.rs b/src/parser/sys_call.rs index 596139a..cbc3575 100644 --- a/src/parser/sys_call.rs +++ b/src/parser/sys_call.rs @@ -1,4 +1,4 @@ -use super::{Literal, LiteralOrVariable}; +use super::LiteralOrVariable; #[derive(Debug, PartialEq, Eq)] pub enum Math { @@ -68,6 +68,29 @@ pub enum Math { Trunc(LiteralOrVariable), } +impl std::fmt::Display for Math { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Math::Acos(a) => write!(f, "acos {}", a), + Math::Asin(a) => write!(f, "asin {}", a), + Math::Atan(a) => write!(f, "atan {}", a), + Math::Atan2(a, b) => write!(f, "atan2 {} {}", a, b), + Math::Abs(a) => write!(f, "abs {}", a), + Math::Ceil(a) => write!(f, "ceil {}", a), + Math::Cos(a) => write!(f, "cos {}", a), + Math::Floor(a) => write!(f, "floor {}", a), + Math::Log(a) => write!(f, "log {}", a), + Math::Max(a, b) => write!(f, "max {} {}", a, b), + Math::Min(a, b) => write!(f, "min {} {}", a, b), + Math::Rand => write!(f, "rand"), + Math::Sin(a) => write!(f, "sin {}", a), + Math::Sqrt(a) => write!(f, "sqrt {}", a), + Math::Tan(a) => write!(f, "tan {}", a), + Math::Trunc(a) => write!(f, "trunc {}", a), + } + } +} + #[derive(Debug, PartialEq, Eq)] pub enum System { /// Pauses execution for exactly 1 tick and then resumes. @@ -78,22 +101,10 @@ pub enum System { /// ## In Game /// `sleep a(r?|num)` Sleep(LiteralOrVariable), - /// Represents a function that can be called to load a variable from a device into a register. - /// ## In Game - /// `l r? d? logicType` - LoadVar(Literal, Literal), /// Gets the in-game hash for a specific prefab name. /// ## In Game /// `HASH("prefabName")` Hash(LiteralOrVariable), - /// Represents a function which will clear the stack for a provided device. - /// ## In Game - /// `clr d?` - StackClear(Literal), - /// Represents a function which reads a value from a device at a specific address and stores it in a register. - /// ## In Game - /// `get r? d? address(r?|num)` - Get(Literal, LiteralOrVariable), /// Represents a function which loads a device variable into a register. /// ## In Game /// `l r? d? var` @@ -109,6 +120,18 @@ pub enum System { Store(String, LiteralOrVariable, LiteralOrVariable), } +impl std::fmt::Display for System { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + System::Yield => write!(f, "yield"), + System::Sleep(a) => write!(f, "sleep {}", a), + System::Hash(a) => write!(f, "HASH({})", a), + System::Load(a, b) => write!(f, "l {} {}", a, b), + System::Store(a, b, c) => write!(f, "s {} {} {}", a, b, c), + } + } +} + #[derive(Debug, PartialEq, Eq)] /// This represents built in functions that cannot be overwritten, but can be invoked by the user as functions. pub enum SysCall { @@ -116,3 +139,23 @@ pub enum SysCall { /// Represents any mathmatical function that can be called. Math(Math), } + +impl std::fmt::Display for SysCall { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SysCall::System(s) => write!(f, "{}", s), + SysCall::Math(m) => write!(f, "{}", m), + } + } +} + +impl SysCall { + pub fn is_syscall(identifier: &str) -> bool { + match identifier { + "yield" | "sleep" | "HASH" | "loadFromDevice" | "setOnDevice" => true, + "acos" | "asin" | "atan" | "atan2" | "abs" | "ceil" | "cos" | "floor" | "log" + | "max" | "min" | "rand" | "sin" | "sqrt" | "tan" | "trunc" => true, + _ => false, + } + } +} diff --git a/src/parser/tree_node.rs b/src/parser/tree_node.rs index a1f448d..9786bdf 100644 --- a/src/parser/tree_node.rs +++ b/src/parser/tree_node.rs @@ -1,5 +1,7 @@ use crate::tokenizer::token::Number; +use super::sys_call::SysCall; + #[derive(Debug, Eq, PartialEq)] pub enum Literal { Number(Number), @@ -144,6 +146,15 @@ pub enum LiteralOrVariable { Variable(String), } +impl std::fmt::Display for LiteralOrVariable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LiteralOrVariable::Literal(l) => write!(f, "{}", l), + LiteralOrVariable::Variable(v) => write!(f, "{}", v), + } + } +} + #[derive(Debug, PartialEq, Eq)] pub struct DeviceDeclarationExpression { /// any variable-like name @@ -174,6 +185,7 @@ pub enum Expression { ReturnExpression(Box), Variable(String), DeviceDeclarationExpression(DeviceDeclarationExpression), + SyscallExpression(SysCall), } impl std::fmt::Display for Expression { @@ -192,6 +204,7 @@ impl std::fmt::Display for Expression { Expression::PriorityExpression(e) => write!(f, "({})", e), Expression::ReturnExpression(e) => write!(f, "(return {})", e), Expression::DeviceDeclarationExpression(e) => write!(f, "{}", e), + Expression::SyscallExpression(e) => write!(f, "{}", e), } } } diff --git a/src/tokenizer/mod.rs b/src/tokenizer/mod.rs index a9d3414..ee30eaa 100644 --- a/src/tokenizer/mod.rs +++ b/src/tokenizer/mod.rs @@ -8,7 +8,7 @@ use std::{ io::{BufReader, Cursor, Read, Seek, SeekFrom}, path::PathBuf, }; -use token::{Keyword, Number, Symbol, Token, TokenType}; +use token::{Keyword, Number, Symbol, Temperature, Token, TokenType}; quick_error! { #[derive(Debug)] @@ -274,7 +274,7 @@ impl Tokenizer { } } - /// Tokenizes a number literal + /// Tokenizes a number literal. Also handles temperatures with a suffix of `c`, `f`, or `k`. fn tokenize_number(&mut self, first_char: char) -> Result { let mut primary = String::with_capacity(16); let mut decimal: Option = None; @@ -315,29 +315,40 @@ impl Tokenizer { self.next_char()?; } - if let Some(decimal) = decimal { + let number: Number = if let Some(decimal) = decimal { let decimal_scale = decimal.len() as u32; let number = format!("{}{}", primary, decimal) .parse::() .map_err(|e| TokenizerError::NumberParseError(e, self.line, self.column))?; + Number::Decimal( + Decimal::try_from_i128_with_scale(number, decimal_scale) + .map_err(|e| TokenizerError::DecimalParseError(e, line, column))?, + ) + } else { + Number::Integer( + primary + .parse() + .map_err(|e| TokenizerError::NumberParseError(e, line, column))?, + ) + }; + + // check if the next char is a temperature suffix + if let Some(next_char) = self.peek_next_char()? { + let temperature = match next_char { + 'c' => Temperature::Celsius(number), + 'f' => Temperature::Fahrenheit(number), + 'k' => Temperature::Kelvin(number), + _ => return Ok(Token::new(TokenType::Number(number), line, column)), + }; + + self.next_char()?; Ok(Token::new( - TokenType::Number(Number::Decimal( - Decimal::try_from_i128_with_scale(number, decimal_scale) - .map_err(|e| TokenizerError::DecimalParseError(e, line, column))?, - )), + TokenType::Temperature(temperature), line, column, )) } else { - Ok(Token::new( - TokenType::Number(Number::Integer( - primary - .parse() - .map_err(|e| TokenizerError::NumberParseError(e, line, column))?, - )), - line, - column, - )) + Ok(Token::new(TokenType::Number(number), line, column)) } } @@ -404,8 +415,6 @@ impl Tokenizer { "else" if next_ws!() => keyword!(Else), "return" if next_ws!() => keyword!(Return), "enum" if next_ws!() => keyword!(Enum), - "import" if next_ws!() => keyword!(Import), - "export" if next_ws!() => keyword!(Export), "device" if next_ws!() => keyword!(Device), // boolean literals @@ -622,19 +631,29 @@ mod tests { } #[test] - fn test_skip_line() -> Result<()> { - let mut tokenizer = Tokenizer::from(String::from( - r#" -This is a skippable line"#, - )); + fn test_temperature_unit() -> Result<()> { + let mut tokenizer = Tokenizer::from(String::from("10c 10f 10k")); - tokenizer.skip_line()?; + let token = tokenizer.next_token()?.unwrap(); - assert_eq!(tokenizer.line, 2); - assert_eq!(tokenizer.column, 1); + assert_eq!( + token.token_type, + TokenType::Temperature(Temperature::Celsius(Number::Integer(10))) + ); - let next_char = tokenizer.next_char()?; - assert_eq!(next_char, Some('T')); + let token = tokenizer.next_token()?.unwrap(); + + assert_eq!( + token.token_type, + TokenType::Temperature(Temperature::Fahrenheit(Number::Integer(10))) + ); + + let token = tokenizer.next_token()?.unwrap(); + + assert_eq!( + token.token_type, + TokenType::Temperature(Temperature::Kelvin(Number::Integer(10))) + ); Ok(()) } @@ -772,8 +791,7 @@ This is a skippable line"#, #[test] fn test_keyword_parse() -> Result<()> { - let mut tokenizer = - Tokenizer::from(String::from("let fn if else return enum import export")); + let mut tokenizer = Tokenizer::from(String::from("let fn if else return enum")); let expected_tokens = vec![ TokenType::Keyword(Keyword::Let), @@ -782,8 +800,6 @@ This is a skippable line"#, TokenType::Keyword(Keyword::Else), TokenType::Keyword(Keyword::Return), TokenType::Keyword(Keyword::Enum), - TokenType::Keyword(Keyword::Import), - TokenType::Keyword(Keyword::Export), ]; for expected_token in expected_tokens { diff --git a/src/tokenizer/token.rs b/src/tokenizer/token.rs index e507186..cfea770 100644 --- a/src/tokenizer/token.rs +++ b/src/tokenizer/token.rs @@ -20,6 +20,23 @@ impl Token { } } +#[derive(Debug, PartialEq, Hash, Eq, Clone)] +pub enum Temperature { + Celsius(Number), + Fahrenheit(Number), + Kelvin(Number), +} + +impl std::fmt::Display for Temperature { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Temperature::Celsius(n) => write!(f, "{}°C", n), + Temperature::Fahrenheit(n) => write!(f, "{}°F", n), + Temperature::Kelvin(n) => write!(f, "{}K", n), + } + } +} + #[derive(Debug, PartialEq, Hash, Eq, Clone)] pub enum TokenType { /// Represents a string token @@ -34,6 +51,7 @@ pub enum TokenType { Identifier(String), /// Represents a symbol token Symbol(Symbol), + Temperature(Temperature), /// Represents an end of file token EOF, } @@ -47,6 +65,7 @@ impl std::fmt::Display for TokenType { TokenType::Keyword(k) => write!(f, "{:?}", k), TokenType::Identifier(i) => write!(f, "{}", i), TokenType::Symbol(s) => write!(f, "{:?}", s), + TokenType::Temperature(t) => write!(f, "{}", t), TokenType::EOF => write!(f, "EOF"), } } @@ -172,8 +191,6 @@ pub enum Keyword { Return, /// Represents the `enum` keyword Enum, - /// Represents an import keyword - Import, - /// Represents an export keyword - Export, + /// Represents the `loop` keyword + Loop, } diff --git a/tests/file.stlg b/tests/file.stlg index 341aa7a..530a510 100644 --- a/tests/file.stlg +++ b/tests/file.stlg @@ -1,5 +1,15 @@ -fn doStuff(i) { +device airConditioner = "d1"; +device roomTemperatureSensor = "d2"; + +loop { + + let currentTemperature = loadFromDevice(roomTemperatureSensor, "Temperature"); + + if (currentTemperature > 25c) { + setOnDevice(airConditioner, "On", true); + } else if (currentTemperature <= 20c) { + setOnDevice(airConditioner, "On", false); + }; }; -doStuff(25 * 2); \ No newline at end of file