WIP - temperature literals

This commit is contained in:
2024-12-01 20:33:54 -07:00
parent e56ff02742
commit 4ec3278296
6 changed files with 161 additions and 65 deletions

View File

@@ -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<SysCall, ParseError> {
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());

View File

@@ -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,
}
}
}

View File

@@ -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<Expression>),
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),
}
}
}

View File

@@ -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<Token, TokenizerError> {
let mut primary = String::with_capacity(16);
let mut decimal: Option<String> = 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::<i128>()
.map_err(|e| TokenizerError::NumberParseError(e, self.line, self.column))?;
Ok(Token::new(
TokenType::Number(Number::Decimal(
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::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 {

View File

@@ -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,
}

View File

@@ -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);