WIP - temperature literals
This commit is contained in:
@@ -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());
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))?;
|
||||
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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user