From ab55887719952edd2fd095cc0d526e9e0da82bcd Mon Sep 17 00:00:00 2001 From: Louis Capitanchik <contact@louiscap.co> Date: Mon, 12 Jun 2023 16:22:40 +0100 Subject: [PATCH] Support return statements in AST, function declerations in grammar, add full file parsing tests --- .idea/micro_script.iml | 1 + forge-script-lang/src/parser/ast.rs | 94 ++++++++++++++++--- .../src/parser/forge_grammar.lalrpop | 47 ++++++++-- forge-script-lang/src/parser/forge_script.rs | 4 + .../src/runtime/executor/printer.rs | 4 + .../src/runtime/executor/simple.rs | 6 +- .../src/runtime/vm/chunk_builder.rs | 1 + forge-script-lang/tests/cases/basic_maths.fs | 4 + forge-script-lang/tests/cases/conditionals.fs | 10 ++ .../tests/cases/declare_items.fs | 19 ++++ forge-script-lang/tests/program_tests.rs | 36 +++++++ 11 files changed, 202 insertions(+), 24 deletions(-) create mode 100644 forge-script-lang/tests/cases/basic_maths.fs create mode 100644 forge-script-lang/tests/cases/conditionals.fs create mode 100644 forge-script-lang/tests/cases/declare_items.fs create mode 100644 forge-script-lang/tests/program_tests.rs diff --git a/.idea/micro_script.iml b/.idea/micro_script.iml index 5fd23c3..031f739 100644 --- a/.idea/micro_script.iml +++ b/.idea/micro_script.iml @@ -7,6 +7,7 @@ <sourceFolder url="file://$MODULE_DIR$/forge-script-web/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/forge-script/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/forge-script-lang/tests" isTestSource="true" /> <excludeFolder url="file://$MODULE_DIR$/target" /> </content> <orderEntry type="inheritedJdk" /> diff --git a/forge-script-lang/src/parser/ast.rs b/forge-script-lang/src/parser/ast.rs index e61e240..7bc290f 100644 --- a/forge-script-lang/src/parser/ast.rs +++ b/forge-script-lang/src/parser/ast.rs @@ -7,13 +7,21 @@ pub trait AstNode {} #[derive(Clone)] #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "snake_case") +)] pub struct Program(pub ExpressionList); impl AstNode for Program {} #[derive(Clone)] #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "snake_case") +)] pub struct ExpressionList { pub expressions: Vec<Expression>, pub is_void: bool, @@ -155,11 +163,16 @@ pub enum VoidExpression { Import(Import), Export(Export), Print(Print), + Return(Return), } #[derive(Clone)] #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "snake_case") +)] pub struct GroupedExpression { pub inner: Box<ValueExpression>, } @@ -297,12 +310,20 @@ impl ValueExpression { #[derive(Clone)] #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "snake_case") +)] pub struct TypeofValue(pub Box<ValueExpression>); #[derive(Clone)] #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "snake_case") +)] pub struct ConditionalLoop { pub block: GuardedBlock, pub fallback: Option<ExpressionList>, @@ -325,7 +346,11 @@ impl ConditionalLoop { #[derive(Clone)] #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "snake_case") +)] pub struct Conditional { pub blocks: Vec<GuardedBlock>, pub fallback: Option<ExpressionList>, @@ -333,7 +358,11 @@ pub struct Conditional { #[derive(Clone)] #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "snake_case") +)] pub struct GuardedBlock { pub guard: Box<ValueExpression>, pub block: ExpressionList, @@ -341,7 +370,11 @@ pub struct GuardedBlock { #[derive(Clone)] #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "snake_case") +)] pub struct Import { pub source: String, pub items: IdentifierList, @@ -349,14 +382,22 @@ pub struct Import { #[derive(Clone)] #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "snake_case") +)] pub struct Export { pub items: IdentifierList, } #[derive(Clone)] #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "snake_case") +)] pub struct Print { pub expr: Box<ValueExpression>, } @@ -368,12 +409,34 @@ impl From<ValueExpression> for Print { } } +#[derive(Clone)] +#[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "snake_case") +)] +pub struct Return { + pub expr: Box<ValueExpression>, +} +impl From<ValueExpression> for Return { + fn from(value: ValueExpression) -> Self { + Return { + expr: Box::new(value), + } + } +} + pub type IdentifierList = Vec<IdentifierNode>; pub type ParameterList = Vec<ValueExpression>; #[derive(Clone)] #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "snake_case") +)] pub struct Identifier(pub String); impl Display for Identifier { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -385,7 +448,11 @@ impl Display for Identifier { /// IdentifierAlias(original, alias) => identifier "as" alias #[derive(Clone)] #[cfg_attr(any(feature = "debug-ast", test), derive(Debug))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "snake_case") +)] pub struct IdentifierAlias(pub String, pub String); impl Display for IdentifierAlias { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -568,5 +635,6 @@ impl_into_void_expr!( Print => VoidExpression::Print, ConditionalLoop => VoidExpression::ConditionLoop, Import => VoidExpression::Import, - Export => VoidExpression::Export + Export => VoidExpression::Export, + Return => VoidExpression::Return ); diff --git a/forge-script-lang/src/parser/forge_grammar.lalrpop b/forge-script-lang/src/parser/forge_grammar.lalrpop index e81ee08..0d20fe9 100644 --- a/forge-script-lang/src/parser/forge_grammar.lalrpop +++ b/forge-script-lang/src/parser/forge_grammar.lalrpop @@ -25,18 +25,22 @@ ExpressionList: ExpressionList = { }, } +FunctionDeclParamList = CommaSeperatedList<FunctionDeclParam>; + +FunctionDeclParam: DeclareIdent = { + Identifier => DeclareIdent::WithoutValue(<>), + <id:Identifier> "=" <def:ValueExpression> => DeclareIdent::WithValue(Assignment { + ident: id, + value: Box::new(def), + }), +} + ParameterList: Vec<ValueExpression> = { "(" <CommaSeperatedValueExpressionList> ")" => <>, "(" ")" => Vec::new(), } -CommaSeperatedValueExpressionList: Vec<ValueExpression> = { - ValueExpression => vec![<>], - <mut ls:CommaSeperatedValueExpressionList> "," <ve:ValueExpression> => { - ls.push(ve); - ls - }, -} +CommaSeperatedValueExpressionList = CommaSeperatedList<ValueExpression>; pub Expression: Expression = { ValueExpression => Expression::Value(<>), @@ -44,9 +48,9 @@ pub Expression: Expression = { } ValueExpression: ValueExpression = { - BoolOrExpression => <>, + FirstClassDeclareExpression => <>, "let" <Identifier> => DeclareIdent::WithoutValue(<>).into(), - "let" <id:Identifier> "=" <expr:BoolOrExpression> => DeclareIdent::WithValue( + "let" <id:Identifier> "=" <expr:FirstClassDeclareExpression> => DeclareIdent::WithValue( Assignment { ident: id, value :Box::new(expr) } ).into(), } @@ -55,6 +59,11 @@ VoidExpression: VoidExpression = { UnaryStatement => <> } +FirstClassDeclareExpression: ValueExpression = { + BoolOrExpression => <>, + DeclareFunction => <>.into(), +} + BoolOrExpression: ValueExpression = { BoolAndExpression => <>, <lhs:BoolOrExpression> "||" <rhs:BoolAndExpression> => ValueExpression::bool_or(lhs, rhs), @@ -93,7 +102,8 @@ MultiplicativeExpression: ValueExpression = { } UnaryStatement: VoidExpression = { - "print" <UnaryExpression> => Print { expr: Box::new(<>) }.into(), + "print" <FirstClassDeclareExpression> => Print { expr: Box::new(<>) }.into(), + "return" <FirstClassDeclareExpression> => Return { expr: Box::new(<>) }.into(), } UnaryExpression: ValueExpression = { @@ -123,6 +133,14 @@ BaseExpression: ValueExpression = { "(" <ValueExpression> ")" => GroupedExpression { inner: Box::new(<>) }.into(), } +DeclareFunction: DeclareFunction = { + "fn" <ident:Identifier> "(" <params:FunctionDeclParamList?> ")" <body:ExpressionBlock> => DeclareFunction { + ident, + params: params.unwrap_or_default(), + body, + } +} + AccessorChainAnyEnding: Vec<AccessorType> = { AccessorChainEndingWithProperty => <>, FunctionCall => vec![AccessorType::FunctionCall(<>)], @@ -198,6 +216,14 @@ StringValue: String = { "owned_string" => <>, }; +CommaSeperatedList<Type>: Vec<Type> = { + Type => vec![<>], + <mut ls:CommaSeperatedList<Type>> "," <ty:Type> => { + ls.push(ty); + ls + } +} + extern { type Location = usize; type Error = String; @@ -212,6 +238,7 @@ extern { "-" => ScriptTokenType::Minus, "+" => ScriptTokenType::Plus, ";" => ScriptTokenType::Semicolon, + ":" => ScriptTokenType::Colon, "/" => ScriptTokenType::Slash, "*" => ScriptTokenType::Asterisk, "!" => ScriptTokenType::Bang, diff --git a/forge-script-lang/src/parser/forge_script.rs b/forge-script-lang/src/parser/forge_script.rs index f1a6083..6c2d578 100644 --- a/forge-script-lang/src/parser/forge_script.rs +++ b/forge-script-lang/src/parser/forge_script.rs @@ -56,6 +56,10 @@ mod grammar_test { #[test_case("foo.bar().baz" => matches Ok(_) ; "nested property accessor")] #[test_case("if something_else {}.bar" => matches Ok(_) ; "expression property accessor")] #[test_case("123 + if if foo { bar } else { baz } { 200 } else { 300 }" => matches Ok(_) ; "Parse inline condition")] + #[test_case("fn add(a, b) { a + b }" => matches Ok(_) ; "Declare basic fn")] + #[test_case("fn has_no_params() {}" => matches Ok(_) ; "Declare empty params fn")] + #[test_case("fn do_some_stuff(foo = 123, bar, baz = \"My Default String\") {}" => matches Ok(_) ; "Declare complex fn")] + #[test_case("let som_value = fn do_some_stuff(foo = 123, bar, baz = \"My Default String\") {}" => matches Ok(_) ; "First class fn")] fn expression_parsing(prog: &str) -> Result<Expression, String> { parse_expression(prog).map(|expr| { dbg!(&expr); diff --git a/forge-script-lang/src/runtime/executor/printer.rs b/forge-script-lang/src/runtime/executor/printer.rs index 565b403..5ebad13 100644 --- a/forge-script-lang/src/runtime/executor/printer.rs +++ b/forge-script-lang/src/runtime/executor/printer.rs @@ -268,6 +268,10 @@ impl Visitor for TreePrinter { self.write("print "); self.evaluate_value_expression(print.expr.as_ref()); } + VoidExpression::Return(ret) => { + self.write("return "); + self.evaluate_value_expression(ret.expr.as_ref()); + } } Ok(()) diff --git a/forge-script-lang/src/runtime/executor/simple.rs b/forge-script-lang/src/runtime/executor/simple.rs index de0af32..ad063a8 100644 --- a/forge-script-lang/src/runtime/executor/simple.rs +++ b/forge-script-lang/src/runtime/executor/simple.rs @@ -1,5 +1,5 @@ use crate::parse::ast::{ - BinaryOp, DeclareFunction, DeclareIdent, ExpressionList, Print, Program, UnaryOp, + BinaryOp, DeclareFunction, DeclareIdent, ExpressionList, Print, Program, Return, UnaryOp, ValueExpression, VoidExpression, }; use crate::parser::ast::Assignment; @@ -241,6 +241,10 @@ impl Visitor for SimpleExecutor { let value = self.evaluate_value_expression(expr.as_ref())?; println!("{}", value); } + VoidExpression::Return(Return { expr }) => { + let value = self.evaluate_value_expression(expr.as_ref())?; + eprintln!("Currently Unsupported: Return. Value: {}", value); + } } Ok(ForgeValue::Null) diff --git a/forge-script-lang/src/runtime/vm/chunk_builder.rs b/forge-script-lang/src/runtime/vm/chunk_builder.rs index f644c1f..ef643c1 100644 --- a/forge-script-lang/src/runtime/vm/chunk_builder.rs +++ b/forge-script-lang/src/runtime/vm/chunk_builder.rs @@ -124,6 +124,7 @@ impl Visitor for ChunkBuilder { VoidExpression::Import(_) => Err(VmError::chunk_parser("Unsupported expression")), VoidExpression::Export(_) => Err(VmError::chunk_parser("Unsupported expression")), VoidExpression::Print(_) => Err(VmError::chunk_parser("Unsupported expression")), + VoidExpression::Return(_) => Err(VmError::chunk_parser("Unsupported expression")), } } diff --git a/forge-script-lang/tests/cases/basic_maths.fs b/forge-script-lang/tests/cases/basic_maths.fs new file mode 100644 index 0000000..5390183 --- /dev/null +++ b/forge-script-lang/tests/cases/basic_maths.fs @@ -0,0 +1,4 @@ +2 + 2; +3 + 3 - 4 * 5; +3 + (3 - 2); +4+4+4 + 4 + 4 + 4; diff --git a/forge-script-lang/tests/cases/conditionals.fs b/forge-script-lang/tests/cases/conditionals.fs new file mode 100644 index 0000000..b43b799 --- /dev/null +++ b/forge-script-lang/tests/cases/conditionals.fs @@ -0,0 +1,10 @@ +import { random_float, random_int, random_bool } from "@runtime:random" +import { truncate } from "@runtime:maths" + +let var_a = random_int(0, 10) >= 5; +let var_b = random_bool(); +let my_var = if if 1 + 2 > 4 { var_a } else { var_b } { + truncate(random_float() * 10) +} else { + random_int(100, 200) +}; \ No newline at end of file diff --git a/forge-script-lang/tests/cases/declare_items.fs b/forge-script-lang/tests/cases/declare_items.fs new file mode 100644 index 0000000..fee703b --- /dev/null +++ b/forge-script-lang/tests/cases/declare_items.fs @@ -0,0 +1,19 @@ +let foo; +let bar = 123 + foo; + +fn my_func() { + let foo; + let bar = 123 + foo; +}; + +let other_name = fn some_internal_name(a, b = 2) { + a * b +}; + +fn has_early_return(a, b, c) { + if b { + return false; + }; + + a + c +} \ No newline at end of file diff --git a/forge-script-lang/tests/program_tests.rs b/forge-script-lang/tests/program_tests.rs new file mode 100644 index 0000000..40e59fb --- /dev/null +++ b/forge-script-lang/tests/program_tests.rs @@ -0,0 +1,36 @@ +use forge_script_lang::parse::{parse_expression, parse_program}; +use std::path::PathBuf; + +fn get_base_path() -> Result<PathBuf, std::io::Error> { + let mut cwd = std::env::current_dir()?; + let mut path = PathBuf::from(file!()); + cwd.pop(); + path.pop(); + path.push("cases"); + Ok(cwd.join(path)) +} + +fn iterate_files() -> Result<Vec<PathBuf>, std::io::Error> { + let base = get_base_path()?; + let mut files = Vec::with_capacity(10); + for file in std::fs::read_dir(base.display().to_string())? { + let entry = file?; + files.push(entry.path()); + } + + Ok(files) +} + +fn read_and_parse(path: PathBuf) -> Result<(), std::io::Error> { + let file = std::fs::read_to_string(path)?; + parse_program(file.as_str()).expect("Failed to parse"); + Ok(()) +} + +#[test] +fn parse_all_programs() -> Result<(), std::io::Error> { + for file in iterate_files()?.into_iter() { + read_and_parse(file)?; + } + Ok(()) +} -- GitLab