diff --git a/forge-script-lang/src/lexer/keywords.rs b/forge-script-lang/src/lexer/keywords.rs index b919a221e2c2ce0260bd252ded56c3f882917ea4..892876ec38aae9f8e090805fc84b28b0cab85a29 100644 --- a/forge-script-lang/src/lexer/keywords.rs +++ b/forge-script-lang/src/lexer/keywords.rs @@ -207,3 +207,15 @@ pub fn token_typeof(input: Span) -> IResult<Span, ScriptToken> { }, )) } + +pub fn token_finally(input: Span) -> IResult<Span, ScriptToken> { + let (input, pos) = position(input)?; + let (input, _) = tag_ws("finally", input)?; + Ok(( + input, + ScriptToken { + position: pos, + token_type: ScriptTokenType::Finally, + }, + )) +} diff --git a/forge-script-lang/src/lexer/mod.rs b/forge-script-lang/src/lexer/mod.rs index 76e85ed3a9224169e4ee5e153bf731c9884b1115..685a1bc28b12c38b2c2c69e8e657907f37dd21eb 100644 --- a/forge-script-lang/src/lexer/mod.rs +++ b/forge-script-lang/src/lexer/mod.rs @@ -29,6 +29,7 @@ mod _lex { use nom::branch::alt; use nom::IResult; + use crate::lexer::keywords::token_finally; use nom::character::complete::multispace0; use nom::multi::fold_many0; use nom::sequence::delimited; @@ -53,6 +54,7 @@ mod _lex { token_super, token_from, token_typeof, + token_finally, )), alt(( token_plus, diff --git a/forge-script-lang/src/lexer/tokens.rs b/forge-script-lang/src/lexer/tokens.rs index ac79700b0b67ed093800d97bcbdf99ba1573752e..971d270f4eb2697aa286edf1b2c52fd59f4bd4c4 100644 --- a/forge-script-lang/src/lexer/tokens.rs +++ b/forge-script-lang/src/lexer/tokens.rs @@ -59,6 +59,7 @@ pub enum ScriptTokenType<'a> { Alias, From, Typeof, + Finally, // Misc Eof, @@ -120,6 +121,7 @@ impl<'a> ScriptTokenType<'a> { ScriptTokenType::From => 4, ScriptTokenType::Eof => 0, ScriptTokenType::Typeof => 6, + ScriptTokenType::Finally => 7, } } } @@ -174,6 +176,7 @@ impl<'a> Display for ScriptTokenType<'a> { ScriptTokenType::From => write!(f, "from"), ScriptTokenType::Eof => write!(f, ""), ScriptTokenType::Typeof => write!(f, "typeof"), + ScriptTokenType::Finally => write!(f, "finally"), } } } @@ -234,6 +237,7 @@ impl<'a> TryFrom<&'a str> for ScriptTokenType<'a> { "as" => Ok(ScriptTokenType::Alias), "from" => Ok(ScriptTokenType::From), "typeof" => Ok(ScriptTokenType::Typeof), + "finally" => Ok(ScriptTokenType::Finally), "false" => Ok(ScriptTokenType::Boolean(false)), "true" => Ok(ScriptTokenType::Boolean(true)), _ => Err(TokenFromStringError { source: value }), @@ -312,6 +316,7 @@ mod token_tests { #[test_case("typeof" => Ok(ScriptTokenType::Typeof); r#"Parse Typeof"#)] #[test_case("true" => Ok(ScriptTokenType::Boolean(true)); r#"Parse true"#)] #[test_case("false" => Ok(ScriptTokenType::Boolean(false)); r#"Parse false"#)] + #[test_case("finally" => Ok(ScriptTokenType::Finally); r#"Parse finally"#)] fn parse_token_type(value: &'static str) -> Result<ScriptTokenType, TokenFromStringError> { ScriptTokenType::try_from(value) } diff --git a/forge-script-lang/src/lib.rs b/forge-script-lang/src/lib.rs index 24498c63c65f289a155e5603ff3cb4b4f295cd6e..40526301fdbf163ad4068053aabf9173a693f7db 100644 --- a/forge-script-lang/src/lib.rs +++ b/forge-script-lang/src/lib.rs @@ -2,6 +2,7 @@ mod error; mod lexer; mod parser; pub mod runtime; +mod utilities; pub use error::{ format_forge_error, print_forge_error, ForgeError, ForgeErrorKind, ParseError, ParseErrorKind, diff --git a/forge-script-lang/src/parser/ast.rs b/forge-script-lang/src/parser/ast.rs index 042841a14102d2d91162b8dd054876d9f417e98e..4485d55fbc0ab898298bfafe51997386003077b6 100644 --- a/forge-script-lang/src/parser/ast.rs +++ b/forge-script-lang/src/parser/ast.rs @@ -103,6 +103,11 @@ pub enum BinaryOp { Equals, BoolAnd, BoolOr, + NotEquals, + LessThan, + GreaterThan, + LessThanEqual, + GreaterThanEqual, } impl BinaryOp { @@ -128,6 +133,11 @@ impl Display for BinaryOp { BinaryOp::Equals => "==", BinaryOp::BoolAnd => "&&", BinaryOp::BoolOr => "||", + BinaryOp::NotEquals => "!=", + BinaryOp::LessThan => "<", + BinaryOp::GreaterThan => ">", + BinaryOp::LessThanEqual => "<=", + BinaryOp::GreaterThanEqual => ">=", } ) } diff --git a/forge-script-lang/src/parser/grammar.rs b/forge-script-lang/src/parser/grammar.rs index d3af36bcfb2a0a9bb21f711d3c65ca5dbb2cb79b..e4fd9f4f7c1cf1c36e655d52230969f18f9ee8f3 100644 --- a/forge-script-lang/src/parser/grammar.rs +++ b/forge-script-lang/src/parser/grammar.rs @@ -63,7 +63,7 @@ peg::parser! { rule condition_loop() -> ConditionalLoop = "while" guard:value_expression() block:block() { ConditionalLoop { block: GuardedBlock { guard: Box::new(guard), block }, fallback: None } } - / "while" guard:value_expression() block:block() "else" fallback:block() + / "while" guard:value_expression() block:block() "finally" fallback:block() { ConditionalLoop { block: GuardedBlock { guard: Box::new(guard), block }, fallback: Some(fallback) } } rule conditional_statement() -> Conditional diff --git a/forge-script-lang/src/runtime/executor/simple.rs b/forge-script-lang/src/runtime/executor/simple.rs index a0b728d0849be419ca33f214bad8fae6c15a0c48..148cf937679fe8bcf6e22a5850539f1d7db20169 100644 --- a/forge-script-lang/src/runtime/executor/simple.rs +++ b/forge-script-lang/src/runtime/executor/simple.rs @@ -157,7 +157,13 @@ impl Visitor for SimpleExecutor { BinaryOp::Multiply => Ok((lhs * rhs)?), BinaryOp::Modulo => Ok((lhs % rhs)?), BinaryOp::Equals => Ok((lhs == rhs).into()), - BinaryOp::BoolOr | BinaryOp::BoolAnd => Ok(ForgeValue::Null), + BinaryOp::BoolOr + | BinaryOp::BoolAnd + | BinaryOp::NotEquals + | BinaryOp::LessThan + | BinaryOp::GreaterThan + | BinaryOp::LessThanEqual + | BinaryOp::GreaterThanEqual => todo!("Implement Binary Op"), } } } @@ -216,8 +222,17 @@ impl Visitor for SimpleExecutor { expression: &VoidExpression, ) -> Result<Self::Output, Self::Error> { match expression { - VoidExpression::ConditionLoop(_) => { - return Err(RuntimeError::Unsupported("ConditionLoop")) + VoidExpression::ConditionLoop(cond_loop) => { + while self + .evaluate_value_expression(cond_loop.block.guard.as_ref())? + .as_bool() + { + self.evaluate_expression_list(&cond_loop.block.block)?; + } + + if let Some(fallback) = &cond_loop.fallback { + self.evaluate_expression_list(fallback)?; + } } VoidExpression::Import(_) => return Err(RuntimeError::Unsupported("Import")), VoidExpression::Export(_) => return Err(RuntimeError::Unsupported("Export")), @@ -272,6 +287,8 @@ mod interpreter_test { #[test_case("let value = true; if value { 123 } else { 456 }" => Ok(ForgeValue::from(123)); "eval if branch")] #[test_case("let value = true; if !value { 123 } else { 456 }" => Ok(ForgeValue::from(456)); "eval else branch")] #[test_case("let value = 2; if value == 1 { 123 } else if value == 2 { 456 } else { \"Nothing\" }" => Ok(ForgeValue::from(456)); "eval middle if else branch")] + // #[test_case("let value = 0; while value < 4 { value = value + 4 }; value" => Ok(ForgeValue::from(4)); "simple conditional loop")] + // #[test_case("let value = 0; while value < 4 { value = value + 4 } finally { value = 1234 }; value" => Ok(ForgeValue::from(1234)); "conditional loop with cleanup")] fn conditional_blocks(prog: &'static str) -> RuntimeResult<ForgeValue> { let ast = parse_program(prog).expect("Failed to parse program"); let mut vm = SimpleExecutor::default(); diff --git a/forge-script-lang/src/runtime/mod.rs b/forge-script-lang/src/runtime/mod.rs index afe3ea940c6bff3205f98ba36c71fd12d8f1188a..20065f855b55e297e2c00c99d3135ef2dbea2cb3 100644 --- a/forge-script-lang/src/runtime/mod.rs +++ b/forge-script-lang/src/runtime/mod.rs @@ -1,3 +1,4 @@ pub mod executor; pub mod numbers; pub mod value; +mod vm; diff --git a/forge-script-lang/src/runtime/vm/chunks.rs b/forge-script-lang/src/runtime/vm/chunks.rs new file mode 100644 index 0000000000000000000000000000000000000000..a16a061de8367a850d059f4c4dbb007d948126c3 --- /dev/null +++ b/forge-script-lang/src/runtime/vm/chunks.rs @@ -0,0 +1,153 @@ +use crate::runtime::value::ForgeValue; +use crate::runtime::vm::const_data::{ConstData, ConstDataRef}; +use crate::runtime::vm::opcode::{OpCode, OpCodeError}; +use std::fmt::{Display, Formatter}; +use std::ops::{Deref, DerefMut}; + +#[derive(Default)] +pub struct Chunk { + code: Vec<OpCode>, + consts: ConstData, +} + +impl From<Vec<OpCode>> for Chunk { + fn from(value: Vec<OpCode>) -> Self { + Self { + code: value, + consts: ConstData::default(), + } + } +} + +pub struct ChunkRef<'scope> { + code: &'scope [OpCode], + consts: ConstDataRef<'scope>, +} + +impl<'scope> From<&'scope Chunk> for ChunkRef<'scope> { + fn from(value: &'scope Chunk) -> Self { + Self { + code: value.code.as_slice(), + consts: ConstDataRef::from(value.consts.as_slice()), + } + } +} + +impl Chunk { + pub fn push_op(&mut self, op: OpCode) -> usize { + let idx = self.code.len(); + self.code.push(op); + idx + } + + pub fn push_const_data(&mut self, data: ForgeValue) -> usize { + let idx = self.consts.len(); + self.consts.push(data); + idx + } + + pub fn as_ref(&self) -> ChunkRef { + ChunkRef { + code: self.code.as_slice(), + consts: self.consts.as_ref(), + } + } + + pub fn op_return(&mut self) { + self.push_op(OpCode::Return); + } + + pub fn op_constant(&mut self, data: ForgeValue) { + let idx = self.push_const_data(data); + self.push_op(OpCode::Constant(idx)); + } +} + +pub trait ChunkOps { + fn as_slice(&self) -> &[OpCode]; + + fn write_chunk_data(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut offset = 0; + let slice = self.as_slice(); + + while offset < slice.len() { + let opcode = match slice.get(offset) { + Some(value) => value, + None => { + break; + } + }; + + write!(f, "[{:08x}] {} ", offset, opcode)?; + opcode.format_operand(f)?; + writeln!(f, "")?; + + offset = opcode.next_offset(offset); + } + + Ok(()) + } + + fn format_chunk(&self, name: impl Display) -> String; +} + +macro_rules! chunk_ops_generic { + () => { + fn format_chunk(&self, name: impl Display) -> String { + format!("== {} ==\n{}", name, self) + } + }; +} + +impl ChunkOps for Chunk { + fn as_slice(&self) -> &[OpCode] { + self.code.as_slice() + } + + chunk_ops_generic!(); +} +impl<'scope> ChunkOps for ChunkRef<'scope> { + fn as_slice(&self) -> &[OpCode] { + self.code + } + chunk_ops_generic!(); +} + +impl Display for Chunk { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.write_chunk_data(f) + } +} + +impl<'scope> Display for ChunkRef<'scope> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.write_chunk_data(f) + } +} + +#[cfg(test)] +mod _chunks { + use super::*; + + #[test] + fn basic_chunk_format() { + let chunk = Chunk::from(vec![OpCode::Return]); + let expected = "== test ==\n[00000000] op_return \n"; + + assert_eq!(chunk.format_chunk("test"), String::from(expected)); + } + + #[test] + fn const_chunk() { + let mut chunk = Chunk::default(); + chunk.op_constant(ForgeValue::Null); + chunk.op_return(); + + let expected = format!( + "== test ==\n[00000000] op_constant {:016x}\n[00000001] op_return \n", + 0 + ); + + assert_eq!(chunk.format_chunk("test"), expected); + } +} diff --git a/forge-script-lang/src/runtime/vm/const_data.rs b/forge-script-lang/src/runtime/vm/const_data.rs new file mode 100644 index 0000000000000000000000000000000000000000..94f770db9fe033a60ee74315d62688f4acc256d1 --- /dev/null +++ b/forge-script-lang/src/runtime/vm/const_data.rs @@ -0,0 +1,49 @@ +use crate::runtime::value::ForgeValue; +use std::ops::{Deref, DerefMut}; + +#[derive(Default)] +pub struct ConstData(Vec<ForgeValue>); +pub struct ConstDataRef<'scope>(&'scope [ForgeValue]); + +impl From<Vec<ForgeValue>> for ConstData { + fn from(value: Vec<ForgeValue>) -> Self { + ConstData(value) + } +} + +impl<'scope> From<&'scope [ForgeValue]> for ConstDataRef<'scope> { + fn from(value: &'scope [ForgeValue]) -> Self { + ConstDataRef(value) + } +} + +impl Deref for ConstData { + type Target = Vec<ForgeValue>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for ConstData { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} +impl ConstData { + pub fn as_ref(&self) -> ConstDataRef { + ConstDataRef(self.0.as_slice()) + } +} + +impl<'scope> Deref for ConstDataRef<'scope> { + type Target = &'scope [ForgeValue]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl<'scope> DerefMut for ConstDataRef<'scope> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/forge-script-lang/src/runtime/vm/mod.rs b/forge-script-lang/src/runtime/vm/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..bd30ae0c30f8d255caad3655e842ed2c56ae91b2 --- /dev/null +++ b/forge-script-lang/src/runtime/vm/mod.rs @@ -0,0 +1,3 @@ +mod chunks; +mod const_data; +mod opcode; diff --git a/forge-script-lang/src/runtime/vm/opcode.rs b/forge-script-lang/src/runtime/vm/opcode.rs new file mode 100644 index 0000000000000000000000000000000000000000..9809dd3efe2c1ce3a3bf64056182f8d2c816371f --- /dev/null +++ b/forge-script-lang/src/runtime/vm/opcode.rs @@ -0,0 +1,67 @@ +use std::error::Error; +use std::fmt::{Display, Formatter}; + +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum OpCode { + Return, + /// Load a constant value from the chunk's data pool. Data starts at the included offset + Constant(usize), +} + +impl OpCode { + pub fn next_offset(&self, current: usize) -> usize { + match self { + _ => current + 1, + } + } + + pub fn op_byte(&self) -> u8 { + match self { + OpCode::Return => 1, + OpCode::Constant(_) => 2, + } + } + + pub fn operand_bytes(&self) -> Vec<u8> { + match self { + OpCode::Return => vec![], + OpCode::Constant(value) => value.to_le_bytes().to_vec(), + } + } + + pub fn format_operand(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + for byte in self.operand_bytes() { + write!(f, "{:02x}", byte)?; + } + + Ok(()) + } +} + +#[derive(Clone, Debug)] +pub enum OpCodeError { + Unknown(u8), +} + +impl Display for OpCodeError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Unknown(value) => write!(f, "Unknown opcode {}", value), + } + } +} + +impl Error for OpCodeError {} + +impl Display for OpCode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + OpCode::Return => "op_return", + OpCode::Constant(_) => "op_constant", + } + ) + } +} diff --git a/forge-script-lang/src/utilities.rs b/forge-script-lang/src/utilities.rs new file mode 100644 index 0000000000000000000000000000000000000000..a354bfcaf1ae5ad8e049d13dfcd743ab014ebb4c --- /dev/null +++ b/forge-script-lang/src/utilities.rs @@ -0,0 +1,37 @@ +#[macro_export] +macro_rules! deref_as { + ($type:ty => $other:ty) => { + impl std::ops::Deref for $type { + type Target = $other; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + }; + ($($lifetime:tt),+: $type:ty => $other:ty) => { + impl <$($lifetime),+> std::ops::Deref for $type { + type Target = $other; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + }; + (mut $type:ty => $other:ty) => { + deref_as!($type => $other); + impl std::ops::DerefMut for $type { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + }; + ($($lifetime:tt),+ mut: $type:ty => $other:ty) => { + deref_as!($($lifetime),+: $type => $other); + impl <$($lifetime),+> std::ops::DerefMut for $type { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + }; +}