Begin fixing built-in functions

This commit is contained in:
Jeff 2024-06-24 07:13:54 -04:00
parent f106d64367
commit 18859cda77
11 changed files with 187 additions and 260 deletions

View File

@ -14,242 +14,179 @@ use crate::{
use super::{AbstractNode, Evaluation, Expression, Type, TypeConstructor};
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum BuiltInFunctionCall {
Length(Length),
ReadFile(ReadFile),
ReadLine(ReadLine),
Sleep(Sleep),
WriteLine(WriteLine),
JsonParse(JsonParse),
pub enum BuiltInFunction {
Length,
ReadLine,
ReadFile,
Sleep,
WriteLine,
JsonParse,
}
impl AbstractNode for BuiltInFunctionCall {
fn define_types(&self, _context: &Context) -> Result<(), ValidationError> {
Ok(())
}
fn validate(&self, _context: &Context, _manage_memory: bool) -> Result<(), ValidationError> {
Ok(())
}
fn evaluate(
self,
_context: &Context,
_manage_memory: bool,
) -> Result<Option<Evaluation>, RuntimeError> {
impl BuiltInFunction {
pub fn r#type(&self) -> Type {
match self {
BuiltInFunctionCall::Length(inner) => inner.call(_context, _manage_memory),
BuiltInFunctionCall::ReadFile(inner) => inner.call(_context, _manage_memory),
BuiltInFunctionCall::ReadLine(inner) => inner.call(_context, _manage_memory),
BuiltInFunctionCall::Sleep(inner) => inner.call(_context, _manage_memory),
BuiltInFunctionCall::WriteLine(inner) => inner.call(_context, _manage_memory),
BuiltInFunctionCall::JsonParse(inner) => inner.call(_context, _manage_memory),
BuiltInFunction::Length => Length::r#type(),
BuiltInFunction::ReadLine => ReadLine::r#type(),
BuiltInFunction::ReadFile => ReadFile::r#type(),
BuiltInFunction::Sleep => Sleep::r#type(),
BuiltInFunction::WriteLine => WriteLine::r#type(),
BuiltInFunction::JsonParse => JsonParse::r#type(),
}
}
fn expected_type(&self, _context: &Context) -> Result<Option<Type>, ValidationError> {
pub fn call(&self, context: &Context, manage_memory: bool) -> Result<Value, RuntimeError> {
match self {
BuiltInFunctionCall::Length(inner) => inner.return_type(_context),
BuiltInFunctionCall::ReadFile(inner) => inner.return_type(_context),
BuiltInFunctionCall::ReadLine(inner) => inner.return_type(_context),
BuiltInFunctionCall::Sleep(inner) => inner.return_type(_context),
BuiltInFunctionCall::WriteLine(inner) => inner.return_type(_context),
BuiltInFunctionCall::JsonParse(inner) => inner.return_type(_context),
BuiltInFunction::Length => Length::call(context, manage_memory),
BuiltInFunction::ReadLine => ReadLine::call(context, manage_memory),
BuiltInFunction::ReadFile => ReadFile::call(context, manage_memory),
BuiltInFunction::Sleep => Sleep::call(context, manage_memory),
BuiltInFunction::WriteLine => WriteLine::call(context, manage_memory),
BuiltInFunction::JsonParse => JsonParse::call(context, manage_memory),
}
}
}
trait FunctionLogic {
fn return_type(&self, context: &Context) -> Result<Option<Type>, ValidationError>;
fn call(
self,
context: &Context,
manage_memory: bool,
) -> Result<Option<Evaluation>, RuntimeError>;
fn r#type() -> Type;
fn call(context: &Context, manage_memory: bool) -> Result<Value, RuntimeError>;
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Length(Box<Expression>);
impl Length {
pub fn new(expression: Expression) -> Self {
Length(Box::new(expression))
}
}
struct Length;
impl FunctionLogic for Length {
fn return_type(&self, _: &Context) -> Result<Option<Type>, ValidationError> {
Ok(Some(Type::Integer))
fn r#type() -> Type {
Type::Function {
type_parameters: None,
value_parameters: Some(vec![(
Identifier::new("list"),
Type::ListOf(Box::new(Type::Any)),
)]),
return_type: Some(Box::new(Type::Integer)),
}
}
fn call(
self,
context: &Context,
manage_memory: bool,
) -> Result<Option<Evaluation>, RuntimeError> {
let position = self.0.position();
let evaluation = self.0.evaluate(context, manage_memory)?;
let value = if let Some(Evaluation::Return(value)) = evaluation {
fn call(context: &Context, manage_memory: bool) -> Result<Value, RuntimeError> {
let value = if let Some(value) = context.get_value(&Identifier::new("input"))? {
value
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::ExpectedExpression(position),
ValidationError::BuiltInFunctionFailure("input does not exist"),
));
};
let list = if let ValueInner::List(list) = value.inner().as_ref() {
list
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::ExpectedList {
actual: value.r#type(context)?,
position,
},
ValidationError::BuiltInFunctionFailure("list is not a list"),
));
};
Ok(Some(Evaluation::Return(Value::integer(list.len() as i64))))
Ok(Value::integer(list.len() as i64))
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ReadFile(Box<Expression>);
impl ReadFile {
pub fn new(expression: Expression) -> Self {
ReadFile(Box::new(expression))
}
}
struct ReadFile;
impl FunctionLogic for ReadFile {
fn return_type(&self, _: &Context) -> Result<Option<Type>, ValidationError> {
Ok(Some(Type::String))
}
fn call(self, context: &Context, _: bool) -> Result<Option<Evaluation>, RuntimeError> {
if let Ok(Some(value)) = context.get_value(&Identifier::new("path")) {
if let ValueInner::String(path) = value.inner().as_ref() {
let file_content = read_to_string(path)?;
return Ok(Some(Evaluation::Return(Value::string(file_content))));
}
fn r#type() -> Type {
Type::Function {
type_parameters: None,
value_parameters: None,
return_type: None,
}
Err(RuntimeError::ValidationFailure(
ValidationError::BuiltInFunctionFailure(self.0.position()),
))
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ReadLine;
impl FunctionLogic for ReadLine {
fn return_type(&self, _: &Context) -> Result<Option<Type>, ValidationError> {
Ok(Some(Type::String))
}
fn call(self, _: &Context, _: bool) -> Result<Option<Evaluation>, RuntimeError> {
let mut user_input = String::new();
stdin().read_line(&mut user_input)?;
Ok(Some(Evaluation::Return(Value::string(
user_input.trim_end_matches('\n'),
))))
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Sleep(Box<Expression>);
impl Sleep {
pub fn new(expression: Expression) -> Self {
Sleep(Box::new(expression))
}
}
impl FunctionLogic for Sleep {
fn return_type(&self, _: &Context) -> Result<Option<Type>, ValidationError> {
Ok(None)
}
fn call(self, _: &Context, _: bool) -> Result<Option<Evaluation>, RuntimeError> {
fn call(context: &Context, manage_memory: bool) -> Result<Value, RuntimeError> {
todo!()
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct WriteLine(Box<Expression>);
struct ReadLine;
impl WriteLine {
pub fn new(expression: Expression) -> Self {
WriteLine(Box::new(expression))
}
}
impl FunctionLogic for WriteLine {
fn return_type(&self, _: &Context) -> Result<Option<Type>, ValidationError> {
Ok(None)
impl FunctionLogic for ReadLine {
fn r#type() -> Type {
Type::Function {
type_parameters: None,
value_parameters: None,
return_type: None,
}
}
fn call(
self,
context: &Context,
manage_memory: bool,
) -> Result<Option<Evaluation>, RuntimeError> {
let position = self.0.position();
let evaluation = self.0.evaluate(context, manage_memory)?;
let value = if let Some(Evaluation::Return(value)) = evaluation {
value
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::ExpectedExpression(position),
));
};
println!("{value}");
Ok(None)
fn call(context: &Context, manage_memory: bool) -> Result<Value, RuntimeError> {
todo!()
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct JsonParse(TypeConstructor, Box<Expression>);
struct Sleep;
impl JsonParse {
pub fn new(constructor: TypeConstructor, expression: Expression) -> Self {
JsonParse(constructor, Box::new(expression))
impl FunctionLogic for Sleep {
fn r#type() -> Type {
Type::Function {
type_parameters: None,
value_parameters: None,
return_type: None,
}
}
fn call(context: &Context, manage_memory: bool) -> Result<Value, RuntimeError> {
todo!()
}
}
impl FunctionLogic for JsonParse {
fn return_type(&self, context: &Context) -> Result<Option<Type>, ValidationError> {
self.0.construct(context).map(|r#type| Some(r#type))
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
struct WriteLine;
impl FunctionLogic for WriteLine {
fn r#type() -> Type {
Type::Function {
type_parameters: None,
value_parameters: None,
return_type: None,
}
}
fn call(
self,
context: &Context,
manage_memory: bool,
) -> Result<Option<Evaluation>, RuntimeError> {
let target_type = self.0.construct(context)?;
let position = self.1.position();
let evaluation = self.1.evaluate(context, manage_memory)?;
let value = if let Some(Evaluation::Return(value)) = evaluation {
fn call(context: &Context, manage_memory: bool) -> Result<Value, RuntimeError> {
todo!()
}
}
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
struct JsonParse;
impl FunctionLogic for JsonParse {
fn r#type() -> Type {
Type::Function {
type_parameters: None,
value_parameters: None,
return_type: None,
}
}
fn call(context: &Context, manage_memory: bool) -> Result<Value, RuntimeError> {
let target_type = if let Some(r#type) = context.get_type(&Identifier::new("T"))? {
r#type
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::BuiltInFunctionFailure("T does not exist"),
));
};
let value = if let Some(value) = context.get_value(&Identifier::new("input"))? {
value
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::ExpectedExpression(position),
ValidationError::BuiltInFunctionFailure("input does not exist"),
));
};
let input = if let ValueInner::String(string) = value.inner().as_ref() {
string
} else {
return Err(RuntimeError::ValidationFailure(
ValidationError::ExpectedString {
actual: value.r#type(context)?,
position,
},
ValidationError::BuiltInFunctionFailure("input is not a string"),
));
};
@ -279,8 +216,6 @@ impl FunctionLogic for JsonParse {
Ok(value)
}
let value = parse_value(&input, target_type)?;
Ok(Some(Evaluation::Return(value)))
parse_value(&input, target_type)
}
}

View File

@ -7,14 +7,13 @@ use crate::{
};
use super::{
AbstractNode, As, BuiltInFunctionCall, Evaluation, FunctionCall, ListIndex, Logic, MapIndex,
Math, SourcePosition, Type, ValueNode, WithPosition,
AbstractNode, As, Evaluation, FunctionCall, ListIndex, Logic, MapIndex, Math, SourcePosition,
Type, ValueNode, WithPosition,
};
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Expression {
As(WithPosition<Box<As>>),
BuiltIn(WithPosition<BuiltInFunctionCall>),
FunctionCall(WithPosition<FunctionCall>),
Identifier(WithPosition<Identifier>),
MapIndex(WithPosition<Box<MapIndex>>),
@ -35,7 +34,6 @@ impl Expression {
Expression::Logic(inner) => inner.position,
Expression::Math(inner) => inner.position,
Expression::Value(inner) => inner.position,
Expression::BuiltIn(inner) => inner.position,
}
}
}
@ -46,7 +44,6 @@ impl AbstractNode for Expression {
match self {
Expression::As(inner) => inner.node.define_types(_context),
Expression::BuiltIn(inner) => inner.node.define_types(_context),
Expression::FunctionCall(inner) => inner.node.define_types(_context),
Expression::Identifier(_) => Ok(()),
Expression::MapIndex(inner) => inner.node.define_types(_context),
@ -62,9 +59,6 @@ impl AbstractNode for Expression {
match self {
Expression::As(r#as) => r#as.node.validate(context, manage_memory),
Expression::BuiltIn(built_in_function_call) => {
built_in_function_call.node.validate(context, manage_memory)
}
Expression::FunctionCall(function_call) => {
function_call.node.validate(context, manage_memory)
}
@ -123,9 +117,6 @@ impl AbstractNode for Expression {
Expression::Logic(logic) => logic.node.evaluate(context, manage_memory),
Expression::Math(math) => math.node.evaluate(context, manage_memory),
Expression::Value(value_node) => value_node.node.evaluate(context, manage_memory),
Expression::BuiltIn(built_in_function_call) => {
built_in_function_call.node.evaluate(context, manage_memory)
}
}
}
@ -150,9 +141,6 @@ impl AbstractNode for Expression {
Expression::Logic(logic) => logic.node.expected_type(_context),
Expression::Math(math) => math.node.expected_type(_context),
Expression::Value(value_node) => value_node.node.expected_type(_context),
Expression::BuiltIn(built_in_function_call) => {
built_in_function_call.node.expected_type(_context)
}
}
}
}

View File

@ -33,7 +33,7 @@ pub use self::{
assignment::{Assignment, AssignmentOperator},
async_block::AsyncBlock,
block::Block,
built_in_function::BuiltInFunctionCall,
built_in_function::BuiltInFunction,
enum_declaration::{EnumDeclaration, EnumVariant},
expression::Expression,
function_call::FunctionCall,

View File

@ -10,12 +10,14 @@ use crate::{
};
use super::{
AbstractNode, Block, Evaluation, Expression, Type, TypeConstructor, WithPos, WithPosition,
AbstractNode, Block, BuiltInFunction, Evaluation, Expression, Type, TypeConstructor, WithPos,
WithPosition,
};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum ValueNode {
Boolean(bool),
BuiltInFunction(BuiltInFunction),
EnumInstance {
type_name: WithPosition<Identifier>,
variant: WithPosition<Identifier>,
@ -239,7 +241,7 @@ impl AbstractNode for ValueNode {
fn evaluate(
self,
context: &Context,
_manage_memory: bool,
manage_memory: bool,
) -> Result<Option<Evaluation>, RuntimeError> {
let value = match self {
ValueNode::Boolean(boolean) => Value::boolean(boolean),
@ -253,7 +255,7 @@ impl AbstractNode for ValueNode {
for expression in expressions {
let position = expression.position();
let evaluation = expression.evaluate(context, _manage_memory)?;
let evaluation = expression.evaluate(context, manage_memory)?;
if let Some(Evaluation::Return(value)) = evaluation {
values.push(value);
@ -277,7 +279,7 @@ impl AbstractNode for ValueNode {
for expression in expression_list {
let position = expression.position();
let evaluation = expression.evaluate(context, _manage_memory)?;
let evaluation = expression.evaluate(context, manage_memory)?;
let value = if let Some(Evaluation::Return(value)) = evaluation {
value
} else {
@ -296,7 +298,7 @@ impl AbstractNode for ValueNode {
for (identifier, _type, expression) in property_list {
let position = expression.position();
let evaluation = expression.evaluate(context, _manage_memory)?;
let evaluation = expression.evaluate(context, manage_memory)?;
let value = if let Some(Evaluation::Return(value)) = evaluation {
value
} else {
@ -355,7 +357,7 @@ impl AbstractNode for ValueNode {
for (identifier, expression) in expressions {
let position = expression.position();
let evaluation = expression.evaluate(context, _manage_memory)?;
let evaluation = expression.evaluate(context, manage_memory)?;
let value = if let Some(Evaluation::Return(value)) = evaluation {
value
} else {
@ -369,6 +371,9 @@ impl AbstractNode for ValueNode {
Value::structure(name, fields)
}
ValueNode::BuiltInFunction(built_in_function) => {
built_in_function.call(context, manage_memory)?
}
};
Ok(Some(Evaluation::Return(value)))
@ -488,6 +493,7 @@ impl AbstractNode for ValueNode {
.collect(),
}
}
ValueNode::BuiltInFunction(built_in_function) => built_in_function.r#type(),
};
Ok(Some(r#type))
@ -612,6 +618,8 @@ impl Ord for ValueNode {
}
}
(Structure { .. }, _) => Ordering::Greater,
(BuiltInFunction(left), BuiltInFunction(right)) => left.cmp(right),
(BuiltInFunction(_), _) => Ordering::Greater,
}
}
}

View File

@ -103,7 +103,7 @@ impl PartialEq for RuntimeError {
#[derive(Debug, PartialEq)]
pub enum ValidationError {
BuiltInFunctionFailure(SourcePosition),
BuiltInFunctionFailure(&'static str),
CannotAssignToNone(SourcePosition),
CannotIndex {
r#type: Type,

View File

@ -13,9 +13,7 @@ use crate::{
};
use self::{
built_in_function::{
BuiltInFunctionCall, JsonParse, Length, ReadFile, ReadLine, Sleep, WriteLine,
},
built_in_function::BuiltInFunction,
enum_declaration::EnumVariant,
type_constructor::{RawTypeConstructor, TypeInvokationConstructor},
};
@ -375,60 +373,50 @@ pub fn parser<'src>(
)
});
let _built_in_function = |keyword| {
let underscored = |keyword| {
just(Token::Keyword(keyword)).delimited_by(
just(Token::Symbol(Symbol::DoubleUnderscore)),
just(Token::Symbol(Symbol::DoubleUnderscore)),
)
};
let built_in_function_call = choice((
_built_in_function(Keyword::Length)
.ignore_then(expression.clone())
.map_with(|argument, state| {
Expression::BuiltIn(
BuiltInFunctionCall::Length(Length::new(argument))
.with_position(state.span()),
)
}),
_built_in_function(Keyword::ReadFile)
.ignore_then(expression.clone())
.map_with(|argument, state| {
Expression::BuiltIn(
BuiltInFunctionCall::ReadFile(ReadFile::new(argument))
.with_position(state.span()),
)
}),
_built_in_function(Keyword::ReadLine).map_with(|_, state| {
Expression::BuiltIn(
BuiltInFunctionCall::ReadLine(ReadLine).with_position(state.span()),
let built_in_function = choice((
underscored(Keyword::Length).map_with(|_, state| {
Expression::Value(
ValueNode::BuiltInFunction(BuiltInFunction::Length)
.with_position(state.span()),
)
}),
underscored(Keyword::ReadLine).map_with(|_, state| {
Expression::Value(
ValueNode::BuiltInFunction(BuiltInFunction::ReadLine)
.with_position(state.span()),
)
}),
underscored(Keyword::ReadFile).map_with(|_, state| {
Expression::Value(
ValueNode::BuiltInFunction(BuiltInFunction::ReadFile)
.with_position(state.span()),
)
}),
underscored(Keyword::Sleep).map_with(|_, state| {
Expression::Value(
ValueNode::BuiltInFunction(BuiltInFunction::Sleep)
.with_position(state.span()),
)
}),
underscored(Keyword::WriteLine).map_with(|_, state| {
Expression::Value(
ValueNode::BuiltInFunction(BuiltInFunction::WriteLine)
.with_position(state.span()),
)
}),
underscored(Keyword::JsonParse).map_with(|_, state| {
Expression::Value(
ValueNode::BuiltInFunction(BuiltInFunction::JsonParse)
.with_position(state.span()),
)
}),
_built_in_function(Keyword::Sleep)
.ignore_then(expression.clone())
.map_with(|argument, state| {
Expression::BuiltIn(
BuiltInFunctionCall::Sleep(Sleep::new(argument))
.with_position(state.span()),
)
}),
_built_in_function(Keyword::WriteLine)
.ignore_then(expression.clone())
.map_with(|argument, state| {
Expression::BuiltIn(
BuiltInFunctionCall::WriteLine(WriteLine::new(argument))
.with_position(state.span()),
)
}),
_built_in_function(Keyword::JsonParse)
.ignore_then(type_constructor.clone())
.then(expression.clone())
.map_with(|(constructor, argument), state| {
Expression::BuiltIn(
BuiltInFunctionCall::JsonParse(JsonParse::new(constructor, argument))
.with_position(state.span()),
)
}),
))
.validate(move |expression, state, emitter| {
if !allow_built_ins {
@ -456,6 +444,7 @@ pub fn parser<'src>(
);
let atom = choice((
built_in_function.clone(),
enum_instance.clone(),
range.clone(),
function.clone(),
@ -646,8 +635,8 @@ pub fn parser<'src>(
));
choice((
built_in_function_call,
logic_math_indexes_as_and_function_calls,
built_in_function,
enum_instance,
range,
function,

View File

@ -270,15 +270,15 @@ fn built_in_function() {
assert_eq!(
statements[0],
Statement::Expression(Expression::BuiltIn(
BuiltInFunctionCall::ReadLine(ReadLine).with_position((0, 13))
Statement::Expression(Expression::Value(
ValueNode::BuiltInFunction(BuiltInFunction::ReadLine).with_position((0, 13))
))
);
}
#[test]
fn built_in_function_with_arg() {
let tokens = lex("__WRITE_LINE__ 'hiya'").unwrap();
let tokens = lex("__WRITE_LINE__('hiya')").unwrap();
let statements = parser(true)
.parse(tokens.spanned((tokens.len()..tokens.len()).into()))
.into_result()
@ -292,11 +292,17 @@ fn built_in_function_with_arg() {
assert_eq!(
statements[0],
Statement::Expression(Expression::BuiltIn(
BuiltInFunctionCall::WriteLine(WriteLine::new(Expression::Value(
ValueNode::String("hiya".to_string()).with_position((15, 21))
)))
.with_position((0, 21))
Statement::Expression(Expression::FunctionCall(
FunctionCall::new(
Expression::Value(
ValueNode::BuiltInFunction(BuiltInFunction::WriteLine).with_position((0, 14))
),
None,
Some(vec![Expression::Value(
ValueNode::String("hiya".to_string()).with_position((15, 21))
)])
)
.with_position((0, 22))
))
);
}

View File

@ -14,7 +14,7 @@ use serde::{
};
use crate::{
abstract_tree::{AbstractNode, Block, Evaluation, Type, WithPosition},
abstract_tree::{AbstractNode, Block, BuiltInFunction, Evaluation, Type, WithPosition},
context::Context,
error::{PoisonError, RuntimeError, ValidationError},
identifier::Identifier,
@ -214,6 +214,7 @@ impl Display for Value {
write!(f, "}}")
}
ValueInner::BuiltInFunction(_) => todo!(),
}
}
}
@ -305,6 +306,7 @@ impl Serialize for Value {
struct_ser.end()
}
ValueInner::BuiltInFunction(_) => todo!(),
}
}
}
@ -554,6 +556,7 @@ impl<'de> Deserialize<'de> for Value {
#[derive(Clone, Debug, PartialEq)]
pub enum ValueInner {
Boolean(bool),
BuiltInFunction(BuiltInFunction),
EnumInstance {
type_name: Identifier,
variant: Identifier,
@ -628,6 +631,7 @@ impl ValueInner {
});
}
}
ValueInner::BuiltInFunction(_) => todo!(),
};
Ok(r#type)
@ -717,6 +721,8 @@ impl Ord for ValueInner {
}
}
(Structure { .. }, _) => Ordering::Greater,
(BuiltInFunction(left), BuiltInFunction(right)) => left.cmp(right),
(BuiltInFunction(_), _) => Ordering::Greater,
}
}
}

View File

@ -1,8 +1,3 @@
/*
This is is simple program to get the length of a json array.
Use `cargo run --package dust-shell examples/json_length.ds`
*/
input = fs.read_file('examples/assets/data.json')
data = json.parse(input)

View File

@ -1,3 +1,3 @@
length = fn (list: [any]) -> int {
__LENGTH__ list
__LENGTH__(list)
}

View File

@ -1,5 +1,5 @@
fs = {
read_file = fn (path: str) -> str {
__READ_FILE__ path
__READ_FILE__(path)
}
}