From 2b03218cca632234614a75f450488e4c60de9a1c Mon Sep 17 00:00:00 2001 From: Antonius Naumann Date: Sat, 6 Jan 2024 03:19:25 +0100 Subject: [PATCH] Transpile trailing closure syntax and add std functions for if, try and for --- galvan-ast/src/item/closure.rs | 78 ++++++++++++++++++ galvan-ast/src/item/fn.rs | 2 +- galvan-ast/src/item/function_call.rs | 82 ++++++++++++++++++- galvan-ast/src/item/mod.rs | 2 + galvan-ast/src/item/statement.rs | 15 +++- galvan-ast/src/item/tasks.rs | 8 +- galvan-ast/src/item/toplevel.rs | 2 +- galvan-ast/tests/test_ast_conversion.rs | 12 +-- galvan-pest/galvan.pest | 48 +++++++---- galvan-transpiler/src/lib.rs | 3 +- galvan-transpiler/src/sanitize.rs | 8 +- .../src/transpile_item/closure.rs | 20 +++++ .../src/transpile_item/function_call.rs | 2 +- galvan-transpiler/src/transpile_item/ident.rs | 3 +- galvan-transpiler/src/transpile_item/mod.rs | 1 + .../src/transpile_item/statement.rs | 11 ++- src/{std.rs => std/borrow.rs} | 0 src/std/control_flow.rs | 64 +++++++++++++++ src/std/mod.rs | 5 ++ 19 files changed, 319 insertions(+), 47 deletions(-) create mode 100644 galvan-ast/src/item/closure.rs create mode 100644 galvan-transpiler/src/transpile_item/closure.rs rename src/{std.rs => std/borrow.rs} (100%) create mode 100644 src/std/control_flow.rs create mode 100644 src/std/mod.rs diff --git a/galvan-ast/src/item/closure.rs b/galvan-ast/src/item/closure.rs new file mode 100644 index 0000000..602bed5 --- /dev/null +++ b/galvan-ast/src/item/closure.rs @@ -0,0 +1,78 @@ +use crate::{ + Block, ConstructorCall, Expression, FunctionCall, Ident, MemberFieldAccess, MemberFunctionCall, + TypeElement, +}; +use from_pest::pest::iterators::Pairs; +use from_pest::ConversionError::NoMatch; +use from_pest::{ConversionError, FromPest, Void}; +use galvan_pest::Rule; +use typeunion::type_union; + +#[derive(Debug, PartialEq, Eq)] +pub struct Closure { + pub arguments: Vec, + pub block: Block, +} + +impl FromPest<'_> for Closure { + type Rule = Rule; + type FatalError = Void; + + fn from_pest( + pairs: &mut Pairs<'_, Self::Rule>, + ) -> Result> { + let Some(pair) = pairs.next() else { + return Err(NoMatch); + }; + if pair.as_rule() != Rule::closure && pair.as_rule() != Rule::trailing_closure { + return Err(NoMatch); + } + + let mut pairs = pair.into_inner(); + let arguments = Vec::::from_pest(&mut pairs)?; + let block = Block::from_pest(&mut pairs)?; + + Ok(Self { arguments, block }) + } +} + +#[derive(Debug, PartialEq, Eq, FromPest)] +#[pest_ast(rule(Rule::closure_argument))] +pub struct ClosureArgument { + pub ident: Ident, + pub ty: Option, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ElseExpression { + pub receiver: Box, + pub block: Block, +} + +#[type_union(super = Expression)] +#[derive(Debug, PartialEq, Eq, FromPest)] +#[pest_ast(rule(Rule::allowed_before_else_expression))] +type AllowedBeforeElseExpression = + FunctionCall + ConstructorCall + MemberFunctionCall + MemberFieldAccess + Ident; + +impl FromPest<'_> for ElseExpression { + type Rule = Rule; + type FatalError = Void; + + fn from_pest( + pairs: &mut Pairs<'_, Self::Rule>, + ) -> Result> { + let Some(pair) = pairs.next() else { + return Err(NoMatch); + }; + if pair.as_rule() != Rule::else_expression { + return Err(NoMatch); + } + + let mut pairs = pair.into_inner(); + let receiver = Box::new(AllowedBeforeElseExpression::from_pest(&mut pairs)?.into()); + let block = Block::from_pest(&mut pairs)?; + + Ok(Self { receiver, block }) + } +} diff --git a/galvan-ast/src/item/fn.rs b/galvan-ast/src/item/fn.rs index 3a76141..edc52ed 100644 --- a/galvan-ast/src/item/fn.rs +++ b/galvan-ast/src/item/fn.rs @@ -9,7 +9,7 @@ use super::*; #[pest_ast(rule(Rule::function))] pub struct FnDecl { pub signature: FnSignature, - pub block: Block, + pub block: Body, } #[derive(Debug, PartialEq, Eq, FromPest)] diff --git a/galvan-ast/src/item/function_call.rs b/galvan-ast/src/item/function_call.rs index e5ca400..30ab757 100644 --- a/galvan-ast/src/item/function_call.rs +++ b/galvan-ast/src/item/function_call.rs @@ -1,21 +1,95 @@ -use crate::{DeclModifier, Expression, Ident, TypeIdent}; +use crate::{ + ArithmeticOperation, BooleanLiteral, Closure, CollectionOperation, ComparisonOperation, + DeclModifier, Expression, Ident, LogicalOperation, NumberLiteral, StringLiteral, TypeIdent, +}; use derive_more::From; +use from_pest::pest::iterators::Pairs; +use from_pest::ConversionError::NoMatch; +use from_pest::{ConversionError, FromPest, Void}; use galvan_pest::Rule; +use typeunion::type_union; -#[derive(Debug, PartialEq, Eq, FromPest)] -#[pest_ast(rule(Rule::function_call))] +#[derive(Debug, PartialEq, Eq)] pub struct FunctionCall { pub identifier: Ident, pub arguments: Vec, } -#[derive(Debug, PartialEq, Eq, From, FromPest)] +impl FromPest<'_> for FunctionCall { + type Rule = Rule; + type FatalError = Void; + + fn from_pest( + pairs: &mut Pairs<'_, Self::Rule>, + ) -> Result> { + let Some(pair) = pairs.next() else { + return Err(NoMatch); + }; + let rule = pair.as_rule(); + match rule { + Rule::function_call | Rule::trailing_closure_call => { + let mut pairs = pair.into_inner(); + let identifier = Ident::from_pest(&mut pairs)?; + + let arguments = if rule == Rule::function_call { + Vec::::from_pest(&mut pairs)? + } else { + let arguments = Vec::::from_pest(&mut pairs)?; + let closure = Closure::from_pest(&mut pairs)?; + let mut arguments = arguments + .into_iter() + .map(|arg| FunctionCallArg { + modifier: arg.modifier, + expression: arg.expression.into(), + }) + .collect::>(); + arguments.push(FunctionCallArg { + modifier: DeclModifier::Inherited, + expression: closure.into(), + }); + arguments + }; + + Ok(Self { + identifier, + arguments, + }) + } + _ => Err(NoMatch), + } + } +} + +#[derive(Debug, PartialEq, Eq, FromPest)] #[pest_ast(rule(Rule::function_call_arg))] pub struct FunctionCallArg { pub modifier: DeclModifier, pub expression: Expression, } +#[derive(Debug, PartialEq, Eq, FromPest)] +#[pest_ast(rule(Rule::trailing_closure_call_arg))] +struct TrailingClosureCallArg { + modifier: DeclModifier, + expression: AllowedInTrailingClosureCall, +} + +#[type_union(super = Expression)] +#[derive(Debug, PartialEq, Eq, FromPest)] +#[pest_ast(rule(Rule::allowed_in_trailing_closure_call))] +type AllowedInTrailingClosureCall = LogicalOperation + + ComparisonOperation + + CollectionOperation + + ArithmeticOperation + + FunctionCall + + ConstructorCall + + MemberFunctionCall + + MemberFieldAccess + + BooleanLiteral + + StringLiteral + + NumberLiteral + + Ident; + #[derive(Debug, PartialEq, Eq, FromPest)] #[pest_ast(rule(Rule::member_function_call))] pub struct MemberFunctionCall { diff --git a/galvan-ast/src/item/mod.rs b/galvan-ast/src/item/mod.rs index b6acbee..047fe2d 100644 --- a/galvan-ast/src/item/mod.rs +++ b/galvan-ast/src/item/mod.rs @@ -1,4 +1,5 @@ mod assignment; +mod closure; mod r#fn; mod function_call; mod ident; @@ -12,6 +13,7 @@ mod r#type; mod type_item; pub use assignment::*; +pub use closure::*; pub use function_call::*; pub use ident::*; pub use literal::*; diff --git a/galvan-ast/src/item/statement.rs b/galvan-ast/src/item/statement.rs index 7c8b5f4..60b6e55 100644 --- a/galvan-ast/src/item/statement.rs +++ b/galvan-ast/src/item/statement.rs @@ -1,17 +1,24 @@ use super::*; +use crate::item::closure::Closure; use galvan_pest::Rule; use typeunion::type_union; #[derive(Debug, PartialEq, Eq, FromPest)] #[pest_ast(rule(Rule::body))] -pub struct Block { +pub struct Body { pub statements: Vec, } +#[derive(Debug, PartialEq, Eq, FromPest)] +#[pest_ast(rule(Rule::block))] +pub struct Block { + pub body: Body, +} + #[type_union] #[derive(Debug, PartialEq, Eq, FromPest)] #[pest_ast(rule(Rule::statement))] -pub type Statement = Assignment + Expression + Declaration; +pub type Statement = Assignment + Expression + Declaration + Block; #[derive(Debug, PartialEq, Eq, FromPest)] #[pest_ast(rule(Rule::declaration))] @@ -25,7 +32,9 @@ pub struct Declaration { #[type_union] #[derive(Debug, PartialEq, Eq, FromPest)] #[pest_ast(rule(Rule::expression))] -pub type Expression = LogicalOperation +pub type Expression = ElseExpression + + Closure + + LogicalOperation + ComparisonOperation + CollectionOperation + ArithmeticOperation diff --git a/galvan-ast/src/item/tasks.rs b/galvan-ast/src/item/tasks.rs index 9fed3cb..873ef1b 100644 --- a/galvan-ast/src/item/tasks.rs +++ b/galvan-ast/src/item/tasks.rs @@ -1,18 +1,18 @@ use galvan_pest::Rule; -use super::{Block, Ident, StringLiteral}; +use super::{Body, Ident, StringLiteral}; #[derive(Debug, PartialEq, Eq, FromPest)] #[pest_ast(rule(Rule::main))] pub struct MainDecl { - pub body: Block, + pub body: Body, } #[derive(Debug, PartialEq, Eq, FromPest)] #[pest_ast(rule(Rule::test))] pub struct TestDecl { pub name: Option, - pub body: Block, + pub body: Body, } #[derive(Debug, PartialEq, Eq, FromPest)] @@ -20,5 +20,5 @@ pub struct TestDecl { pub struct TaskDecl { pub ident: Ident, // name: Option, - pub body: Block, + pub body: Body, } diff --git a/galvan-ast/src/item/toplevel.rs b/galvan-ast/src/item/toplevel.rs index 2fe40c0..c7981f9 100644 --- a/galvan-ast/src/item/toplevel.rs +++ b/galvan-ast/src/item/toplevel.rs @@ -1,7 +1,7 @@ use derive_more::From; use galvan_pest::Rule; -use super::{FnDecl, MainDecl, TaskDecl, TestDecl, TypeDecl}; +use super::{FnDecl, MainDecl, TestDecl, TypeDecl}; #[derive(Debug, PartialEq, Eq, From, FromPest)] #[pest_ast(rule(Rule::toplevel))] diff --git a/galvan-ast/tests/test_ast_conversion.rs b/galvan-ast/tests/test_ast_conversion.rs index 6db5b28..db9c0a1 100644 --- a/galvan-ast/tests/test_ast_conversion.rs +++ b/galvan-ast/tests/test_ast_conversion.rs @@ -21,7 +21,7 @@ mod test_utils { pub fn main(statements: Vec) -> RootItem { RootItem::Main(MainDecl { - body: Block { statements }, + body: Body { statements }, }) } @@ -117,7 +117,7 @@ mod test_utils { name: &str, parameters: ParamList, return_type: Option, - block: Block, + block: Body, ) -> FnDecl { FnDecl { signature: FnSignature { @@ -143,12 +143,12 @@ mod test_utils { } } - pub fn empty_body() -> Block { - Block { statements: vec![] } + pub fn empty_body() -> Body { + Body { statements: vec![] } } - pub fn body(statements: Vec) -> Block { - Block { statements } + pub fn body(statements: Vec) -> Body { + Body { statements } } pub fn number(value: &str) -> Expression { diff --git a/galvan-pest/galvan.pest b/galvan-pest/galvan.pest index ce7e71b..2e66ef2 100644 --- a/galvan-pest/galvan.pest +++ b/galvan-pest/galvan.pest @@ -61,9 +61,9 @@ param_base = _{ declaration_modifier ~ ident ~ colon ~ type_item } task = { ident ~ brace_open ~ body ~ brace_close } -body = { ((newline)* ~ statement ~ (newline)+)* ~ statement? } +body = { ((newline)* ~ statement ~ (newline | ";")+)* ~ statement? } -statement = { assignment | declaration | expression } +statement = { assignment | declaration | expression | block } assignment = { assignment_target ~ assignment_operator ~ expression } assignment_target = { member_field_access | ident } @@ -88,19 +88,15 @@ expression = { | string_literal | number_literal | ident - | block_expression } allowed_before_else_expression = !{ trailing_closure_call - | logical_expression - | arithmetic_expression | function_call | constructor_call | member_function_call | member_field_access | ident - | block_expression } allowed_in_trailing_closure_call = !{ @@ -116,7 +112,6 @@ allowed_in_trailing_closure_call = !{ | string_literal | number_literal | ident - | block_expression } allowed_in_logical = !{ @@ -131,7 +126,6 @@ allowed_in_logical = !{ | string_literal | number_literal | ident - | block_expression } allowed_in_comparison = !{ @@ -145,7 +139,6 @@ allowed_in_comparison = !{ | string_literal | number_literal | ident - | block_expression } allowed_in_collection = !{ @@ -158,7 +151,6 @@ allowed_in_collection = !{ | string_literal | number_literal | ident - | block_expression } allowed_in_arithmetic = !{ @@ -170,17 +162,27 @@ allowed_in_arithmetic = !{ | string_literal | number_literal | ident - | block_expression } -closure = { "|" ~ closure_arguments ~ "|" ~ block_expression } +closure = { "|" ~ closure_arguments? ~ "|" ~ block } closure_arguments = _{ (closure_argument ~ (comma ~ closure_argument)*)? ~ comma? } closure_argument = { ident ~ (colon ~ type_item)? } -block_expression = { (brace_open ~ body ~ brace_close) } +block = { (brace_open ~ body ~ brace_close) } // TODO: Also allow block expression here as closure with implicit names ("it" or #0, #1, #2) -trailing_closure_call = { ident ~ allowed_in_trailing_closure_call ~ (closure | newline) } -else_expression = { allowed_before_else_expression ~ else_keyword ~ block_expression } +trailing_closure_call = ${ + ident + ~ whitespace* ~ ( + trailing_closure + | ( + trailing_closure_call_arg + ~ whitespace* ~ (comma ~ trailing_closure_call_arg)* + ~ whitespace* ~ comma? + ~ whitespace* ~ trailing_closure?)) +} +trailing_closure = { ("|" ~ closure_arguments ~ "|")? ~ block } +trailing_closure_call_arg = { declaration_modifier ~ allowed_in_trailing_closure_call } +else_expression = { allowed_before_else_expression ~ else_keyword ~ block } logical_expression = ${ allowed_in_logical ~ (space+ ~ logical_infix_operator ~ space+ ~ allowed_in_logical)+ } comparison_expression = ${ allowed_in_comparison ~ space+ ~ comparison_operator ~ space+ ~ allowed_in_comparison } collection_expression = ${ allowed_in_collection ~ space+ ~ collection_operator ~ space+ ~ allowed_in_collection } @@ -208,7 +210,7 @@ constructor_call = { type_ident ~ paren_open ~ constructor_call_args ~ paren_clo constructor_call_args = _{ (constructor_call_arg ~ (comma ~ constructor_call_arg)*)? } constructor_call_arg = { ident ~ colon ~ expression } -ident = @{ used_ident | unused_ident | discard } +ident = @{ !(keyword) ~ (used_ident | unused_ident | discard) } used_ident = _{ ASCII_ALPHA_LOWER ~ (ASCII_ALPHA_LOWER | ASCII_DIGIT | "_")* } unused_ident = _{ "_" ~ (ASCII_ALPHA_LOWER | ASCII_DIGIT | "_")* } discard = _{ "_" } @@ -222,6 +224,18 @@ keyword = _{ | type_keyword | ref_keyword | let_keyword + | mut_keyword + | else_keyword + | async_keyword + | const_keyword + | pub_keyword + | true_keyword + | false_keyword + | and + | or + | xor + | not + | contains } @@ -320,7 +334,7 @@ pub_keyword = @{ "pub" } at_sign = @{ "@" } -else_keyword = @{ "else" } +else_keyword = _{ "else" } // # Type Definitions type_item = { result_type | optional_type | allowed_in_error_variant } diff --git a/galvan-transpiler/src/lib.rs b/galvan-transpiler/src/lib.rs index a74344e..c3e58b1 100644 --- a/galvan-transpiler/src/lib.rs +++ b/galvan-transpiler/src/lib.rs @@ -291,7 +291,8 @@ punct!( TupleTypeMember, Param, FunctionCallArg, - ConstructorCallArg + ConstructorCallArg, + ClosureArgument ); punct!(",\n", StructTypeMember); punct!("\n\n", RootItem, FnDecl); diff --git a/galvan-transpiler/src/sanitize.rs b/galvan-transpiler/src/sanitize.rs index bf0700b..14137f0 100644 --- a/galvan-transpiler/src/sanitize.rs +++ b/galvan-transpiler/src/sanitize.rs @@ -8,10 +8,10 @@ pub(crate) fn sanitize_name(name: &str) -> Cow { } } -const RUST_KEYWORDS: [&str; 51] = [ +const RUST_KEYWORDS: [&str; 49] = [ "as", "break", "const", "continue", "crate", "dyn", "else", "enum", "extern", "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", - "return", "self", "Self", "static", "struct", "super", "trait", "true", "try", "type", - "unsafe", "use", "where", "while", "async", "await", "abstract", "become", "box", "do", - "final", "macro", "override", "priv", "typeof", "unsized", "virtual", "yield", + "return", "static", "struct", "super", "trait", "true", "try", "type", "unsafe", "use", + "where", "while", "async", "await", "abstract", "become", "box", "do", "final", "macro", + "override", "priv", "typeof", "unsized", "virtual", "yield", ]; diff --git a/galvan-transpiler/src/transpile_item/closure.rs b/galvan-transpiler/src/transpile_item/closure.rs new file mode 100644 index 0000000..22f4e2d --- /dev/null +++ b/galvan-transpiler/src/transpile_item/closure.rs @@ -0,0 +1,20 @@ +use crate::context::Context; +use crate::macros::{impl_transpile, transpile}; +use crate::Transpile; +use galvan_ast::{Block, Closure, ClosureArgument, ElseExpression}; + +impl_transpile!(Closure, "|{}| {}", arguments, block); +impl_transpile!(Block, "{{ {} }}", body); +// TODO: Allow a second variant that takes an error as an argument +impl_transpile!(ElseExpression, "({}).__or_else(|| {})", receiver, block); + +impl Transpile for ClosureArgument { + fn transpile(&self, ctx: &Context) -> String { + if let Some(ty) = &self.ty { + transpile!(ctx, "{}: {}", self.ident, ty) + } else { + // TODO: Handle refs and mut here as well + self.ident.transpile(ctx) + } + } +} diff --git a/galvan-transpiler/src/transpile_item/function_call.rs b/galvan-transpiler/src/transpile_item/function_call.rs index 7a3dc6a..5dc57a9 100644 --- a/galvan-transpiler/src/transpile_item/function_call.rs +++ b/galvan-transpiler/src/transpile_item/function_call.rs @@ -3,7 +3,7 @@ use crate::macros::{impl_transpile, impl_transpile_match, transpile}; use crate::Transpile; use galvan_ast::{ ConstructorCall, ConstructorCallArg, DeclModifier, Expression, FunctionCall, FunctionCallArg, - Ident, MemberFieldAccess, MemberFunctionCall, + MemberFieldAccess, MemberFunctionCall, }; impl Transpile for FunctionCall { diff --git a/galvan-transpiler/src/transpile_item/ident.rs b/galvan-transpiler/src/transpile_item/ident.rs index 97cc4e7..e04b8e2 100644 --- a/galvan-transpiler/src/transpile_item/ident.rs +++ b/galvan-transpiler/src/transpile_item/ident.rs @@ -1,11 +1,12 @@ use crate::context::Context; +use crate::sanitize::sanitize_name; use crate::{Ident, Transpile, TypeIdent}; impl Transpile for Ident { fn transpile(&self, ctx: &Context) -> String { // TODO: Escape ident when name has collision with rust keyword // TODO: Use lookup to insert fully qualified name - format!("{self}") + sanitize_name(self.as_str()).into() } } diff --git a/galvan-transpiler/src/transpile_item/mod.rs b/galvan-transpiler/src/transpile_item/mod.rs index 437fdcb..9e95a18 100644 --- a/galvan-transpiler/src/transpile_item/mod.rs +++ b/galvan-transpiler/src/transpile_item/mod.rs @@ -9,3 +9,4 @@ mod task; mod toplevel; mod r#type; mod visibility; +mod closure; diff --git a/galvan-transpiler/src/transpile_item/statement.rs b/galvan-transpiler/src/transpile_item/statement.rs index 7fcc14c..5265fbb 100644 --- a/galvan-transpiler/src/transpile_item/statement.rs +++ b/galvan-transpiler/src/transpile_item/statement.rs @@ -1,12 +1,13 @@ use crate::context::Context; use crate::macros::{impl_transpile, impl_transpile_variants, transpile}; -use crate::{Block, Transpile}; +use crate::{Body, Transpile}; use galvan_ast::{ - BooleanLiteral, DeclModifier, Declaration, Expression, NumberLiteral, Statement, StringLiteral, + Block, BooleanLiteral, Closure, DeclModifier, Declaration, ElseExpression, Expression, + NumberLiteral, Statement, StringLiteral, }; -impl_transpile!(Block, "{{\n{}\n}}", statements); -impl_transpile_variants!(Statement; Assignment, Expression, Declaration); +impl_transpile!(Body, "{{\n{}\n}}", statements); +impl_transpile_variants!(Statement; Assignment, Expression, Declaration, Block); impl Transpile for Declaration { fn transpile(&self, ctx: &Context) -> String { @@ -62,6 +63,8 @@ fn transpile_assignment_expression(ctx: &Context, expr: &Expression) -> String { } impl_transpile_variants! { Expression; + ElseExpression, + Closure, LogicalOperation, ComparisonOperation, CollectionOperation, diff --git a/src/std.rs b/src/std/borrow.rs similarity index 100% rename from src/std.rs rename to src/std/borrow.rs diff --git a/src/std/control_flow.rs b/src/std/control_flow.rs new file mode 100644 index 0000000..f265e4a --- /dev/null +++ b/src/std/control_flow.rs @@ -0,0 +1,64 @@ +// TODO: Improve transpilation for copy types and take bool instead of &bool here +#[inline(always)] +pub fn r#if(condition: &bool, body: F) -> Option +where + F: FnOnce() -> T, +{ + if *condition { + Some(body()) + } else { + None + } +} + +#[inline(always)] +pub fn r#for(iter: I, mut body: F) +where + I: IntoIterator, + F: FnMut(I::Item), +{ + for item in iter { + body(item) + } +} + +pub trait __ToOption { + type Inner; + + fn __to_option(&self) -> Option; + + #[inline(always)] + fn __or_else(&self, f: F) -> Self::Inner + where + F: FnOnce() -> Self::Inner, + { + self.__to_option().unwrap_or_else(f) + } +} + +impl __ToOption for Option { + type Inner = T::Owned; + + #[inline(always)] + fn __to_option(&self) -> Option { + self.as_ref().map(|x| x.to_owned()) + } +} + +impl __ToOption for Result { + type Inner = T::Owned; + + #[inline(always)] + fn __to_option(&self) -> Option { + self.as_ref().ok().map(|x| x.to_owned()) + } +} + +#[inline(always)] +pub fn r#try(fallible: &O, body: F) -> Option +where + F: FnOnce(O::Inner) -> T, + O: __ToOption + ToOwned, +{ + fallible.__to_option().map(body) +} diff --git a/src/std/mod.rs b/src/std/mod.rs new file mode 100644 index 0000000..50c45f3 --- /dev/null +++ b/src/std/mod.rs @@ -0,0 +1,5 @@ +mod borrow; +pub use borrow::*; + +mod control_flow; +pub use control_flow::*;