diff --git a/CHANGELOG.md b/CHANGELOG.md index 604cd159a..9f12f9b13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## [Unreleased version] - 20XX-XX-XX ### Added - new builtin `disassemble` to print the bytecode of a function +- new builtin `io:readFileLines` to read lines from a file as a list of strings ### Changed - the formatter properly formats dictionaries (key-value pairs on their own line, always) diff --git a/examples/closures.ark b/examples/closures.ark index fc060ab51..a4f06b259 100644 --- a/examples/closures.ark +++ b/examples/closures.ark @@ -1,7 +1,7 @@ # Inspired by # Closures and object are equivalent: http://wiki.c2.com/?ClosuresAndObjectsAreEquivalent # this will construct a closure capturing the 3 arguments, plus a function to set the age -(let create-human (fun (name age weight) { +(let create-human (fun (name (mut age) weight) { # functions can be invoked in the closure scope (let set-age (fun (new-age) (set age new-age))) diff --git a/include/Ark/Builtins/Builtins.hpp b/include/Ark/Builtins/Builtins.hpp index 96d673b23..329a4877c 100644 --- a/include/Ark/Builtins/Builtins.hpp +++ b/include/Ark/Builtins/Builtins.hpp @@ -52,6 +52,7 @@ namespace Ark::internal::Builtins ARK_BUILTIN(writeFile); ARK_BUILTIN(appendToFile); ARK_BUILTIN(readFile); + ARK_BUILTIN(readLinesFile); ARK_BUILTIN(fileExists); ARK_BUILTIN(listFiles); ARK_BUILTIN(isDirectory); diff --git a/include/Ark/Compiler/Common.hpp b/include/Ark/Compiler/Common.hpp index f3d382f8b..5eeb3d3ae 100644 --- a/include/Ark/Compiler/Common.hpp +++ b/include/Ark/Compiler/Common.hpp @@ -43,6 +43,8 @@ namespace Ark::internal enum class NodeType { Symbol, + MutArg, + RefArg, Capture, Keyword, String, @@ -56,8 +58,10 @@ namespace Ark::internal }; /// Node types as string, in the same order as the enum NodeType - constexpr std::array nodeTypes = { + constexpr std::array nodeTypes = { "Symbol", + "MutArg", + "RefArg", "Capture", "Keyword", "String", diff --git a/include/Ark/Compiler/Instructions.hpp b/include/Ark/Compiler/Instructions.hpp index 134d1b718..cc551beb9 100644 --- a/include/Ark/Compiler/Instructions.hpp +++ b/include/Ark/Compiler/Instructions.hpp @@ -56,374 +56,378 @@ namespace Ark::internal // @role Take the value on top of the stack and create a variable in the current scope, named following the given symbol id (cf symbols table) STORE = 0x05, + // @args symbol id + // @role Store a value in a symbol without dereferencing it (used by functions only) + STORE_REF = 0x06, + // @args symbol id // @role Take the value on top of the stack and put it inside a variable named following the symbol id (cf symbols table), in the nearest scope. Raise an error if it couldn't find a scope where the variable exists - SET_VAL = 0x06, + SET_VAL = 0x07, // @args absolute address to jump to // @role Jump to the provided address if the last value on the stack was equal to false. Remove the value from the stack no matter what it is - POP_JUMP_IF_FALSE = 0x07, + POP_JUMP_IF_FALSE = 0x08, // @args absolute address to jump to // @role Jump to the provided address - JUMP = 0x08, + JUMP = 0x09, // @role If in a code segment other than the main one, quit it, and push the value on top of the stack to the new stack; should as well delete the current environment. Otherwise, acts as a `HALT` - RET = 0x09, + RET = 0x0a, // @role Stop the Virtual Machine - HALT = 0x0a, + HALT = 0x0b, // @role push pp, then ip on the stack, preparing for a call instruction - PUSH_RETURN_ADDRESS = 0x0b, + PUSH_RETURN_ADDRESS = 0x0c, // @args argument count // @role Call function from its symbol id located on top of the stack. Take the given number of arguments from the top of stack and give them to the function (the first argument taken from the stack will be the last one of the function). The stack of the function is now composed of its arguments, from the first to the last one - CALL = 0x0c, + CALL = 0x0d, // @args symbol id // @role Tell the Virtual Machine to capture the variable from the current environment. Main goal is to be able to handle closures, which need to save the environment in which they were created - CAPTURE = 0x0d, + CAPTURE = 0x0e, // @args symbol id // @role Tell the VM to use the given symbol for the next capture - RENAME_NEXT_CAPTURE = 0x0e, + RENAME_NEXT_CAPTURE = 0x0f, // @args builtin id // @role Push the corresponding builtin function object on the stack - BUILTIN = 0x0f, + BUILTIN = 0x10, // @args symbol id // @role Remove a variable/constant named following the given symbol id (cf symbols table) - DEL = 0x10, + DEL = 0x11, // @args constant id // @role Push a Closure with the page address pointed by the constant, along with the saved scope created by CAPTURE instruction(s) - MAKE_CLOSURE = 0x11, + MAKE_CLOSURE = 0x12, // @args symbol id // @role Read the field named following the given symbol id (cf symbols table) of a `Closure` stored in TS. Pop TS and push the value of field read on the stack - GET_FIELD = 0x12, + GET_FIELD = 0x13, // @args constant id // @role Load a plugin dynamically, plugin name is stored as a string in the constants table - PLUGIN = 0x13, + PLUGIN = 0x14, // @args number of elements // @role Create a list from the N elements pushed on the stack. Follows the function calling convention - LIST = 0x14, + LIST = 0x15, // @args number of elements // @role Append N elements to a list (TS). Elements are stored in TS(1)..TS(N). Follows the function calling convention - APPEND = 0x15, + APPEND = 0x16, // @args number of elements // @role Concatenate N lists to a list (TS). Lists to concat to TS are stored in TS(1)..TS(N). Follows the function calling convention - CONCAT = 0x16, + CONCAT = 0x17, // @args number of elements // @role Append N elements to a reference to a list (TS), the list is being mutated in-place, no new object created. Elements are stored in TS(1)..TS(N). Follows the function calling convention - APPEND_IN_PLACE = 0x17, + APPEND_IN_PLACE = 0x18, // @args number of elements // @role Concatenate N lists to a reference to a list (TS), the list is being mutated in-place, no new object created. Lists to concat to TS are stored in TS(1)..TS(N). Follows the function calling convention - CONCAT_IN_PLACE = 0x18, + CONCAT_IN_PLACE = 0x19, // @role Remove an element from a list (TS), given an index (TS1). Push a new list without the removed element to the stack - POP_LIST = 0x19, + POP_LIST = 0x1a, // @role Remove an element from a reference to a list (TS), given an index (TS1). The list is mutated in-place, no new object created - POP_LIST_IN_PLACE = 0x1a, + POP_LIST_IN_PLACE = 0x1b, // @role Modify a reference to a list or string (TS) by replacing the element at TS1 (must be a number) by the value in TS2. The object is mutated in-place, no new object created - SET_AT_INDEX = 0x1b, + SET_AT_INDEX = 0x1c, // @role Modify a reference to a list (TS) by replacing TS[TS2][TS1] by the value in TS3. TS[TS2] can be a string (if it is, TS3 must be a string). The object is mutated in-place, no new object created - SET_AT_2_INDEX = 0x1c, + SET_AT_2_INDEX = 0x1d, // @role Remove the top of the stack - POP = 0x1d, + POP = 0x1e, // @role Pop the top of the stack, if it's false, jump to an address - SHORTCIRCUIT_AND = 0x1e, + SHORTCIRCUIT_AND = 0x1f, // @role Pop the top of the stack, if it's true, jump to an address - SHORTCIRCUIT_OR = 0x1f, + SHORTCIRCUIT_OR = 0x20, // @role Create a new local scope - CREATE_SCOPE = 0x20, + CREATE_SCOPE = 0x21, // @role Reset the current scope so that it is empty, and jump to a given location - RESET_SCOPE_JUMP = 0x21, + RESET_SCOPE_JUMP = 0x22, // @role Destroy the last local scope - POP_SCOPE = 0x22, + POP_SCOPE = 0x23, // @args symbol id (function name) // @role Push the current page address as a value on the stack - GET_CURRENT_PAGE_ADDR = 0x23, + GET_CURRENT_PAGE_ADDR = 0x24, - FIRST_OPERATOR = 0x24, + FIRST_OPERATOR = 0x25, // @role Push `TS1 + TS` - ADD = 0x24, + ADD = 0x25, // @role Push `TS1 - TS` - SUB = 0x25, + SUB = 0x26, // @role Push `TS1 * TS` - MUL = 0x26, + MUL = 0x27, // @role Push `TS1 / TS` - DIV = 0x27, + DIV = 0x28, // @role Push `TS1 > TS` - GT = 0x28, + GT = 0x29, // @role Push `TS1 < TS` - LT = 0x29, + LT = 0x2a, // @role Push `TS1 <= TS` - LE = 0x2a, + LE = 0x2b, // @role Push `TS1 >= TS` - GE = 0x2b, + GE = 0x2c, // @role Push `TS1 != TS` - NEQ = 0x2c, + NEQ = 0x2d, // @role Push `TS1 == TS` - EQ = 0x2d, + EQ = 0x2e, // @role Push `len(TS)`, TS must be a list - LEN = 0x2e, + LEN = 0x2f, // @role Push `empty?(TS)`, TS must be a list or string - EMPTY = 0x2f, + EMPTY = 0x30, // @role Push `tail(TS)`, all the elements of TS except the first one. TS must be a list or string - TAIL = 0x30, + TAIL = 0x31, // @role Push `head(TS)`, the first element of TS or nil if empty. TS must be a list or string - HEAD = 0x31, + HEAD = 0x32, // @role Push true if TS is nil, false otherwise - ISNIL = 0x32, + ISNIL = 0x33, // @role Throw an exception if TS1 is false, and display TS (must be a string). Do not push anything on the stack - ASSERT = 0x33, + ASSERT = 0x34, // @role Convert TS to number (must be a string) - TO_NUM = 0x34, + TO_NUM = 0x35, // @role Convert TS to string - TO_STR = 0x35, + TO_STR = 0x36, // @role Push the value at index TS (must be a number) in TS1, which must be a list or string - AT = 0x36, + AT = 0x37, // @role Push the value at index TS (must be a number), inside the list or string at index TS1 (must be a number) in the list at TS2 - AT_AT = 0x37, + AT_AT = 0x38, // @role Push `TS1 % TS` - MOD = 0x38, + MOD = 0x39, // @role Push the type of TS as a string - TYPE = 0x39, + TYPE = 0x3a, // @role Check if TS1 is a closure field of TS. TS must be a Closure, TS1 a String - HASFIELD = 0x3a, + HASFIELD = 0x3b, // @role Push `!TS` - NOT = 0x3b, + NOT = 0x3c, // @args constant id, constant id // @role Load two consts (`primary` then `secondary`) on the stack in one instruction - LOAD_CONST_LOAD_CONST = 0x3c, + LOAD_CONST_LOAD_CONST = 0x3d, // @args constant id, symbol id // @role Load const `primary` into the symbol `secondary` (create a variable) - LOAD_CONST_STORE = 0x3d, + LOAD_CONST_STORE = 0x3e, // @args constant id, symbol id // @role Load const `primary` into the symbol `secondary` (search for the variable with the given symbol id) - LOAD_CONST_SET_VAL = 0x3e, + LOAD_CONST_SET_VAL = 0x3f, // @args symbol id, symbol id // @role Store the value of the symbol `primary` into a new variable `secondary` - STORE_FROM = 0x3f, + STORE_FROM = 0x40, // @args symbol index, symbol id // @role Store the value of the symbol `primary` into a new variable `secondary` - STORE_FROM_INDEX = 0x40, + STORE_FROM_INDEX = 0x41, // @args symbol id, symbol id // @role Store the value of the symbol `primary` into an existing variable `secondary` - SET_VAL_FROM = 0x41, + SET_VAL_FROM = 0x42, // @args symbol index, symbol id // @role Store the value of the symbol `primary` into an existing variable `secondary` - SET_VAL_FROM_INDEX = 0x42, + SET_VAL_FROM_INDEX = 0x43, // @args symbol id, count // @role Increment the variable `primary` by `count` and push its value on the stack - INCREMENT = 0x43, + INCREMENT = 0x44, // @args symbol index, count // @role Increment the variable `primary` by `count` and push its value on the stack - INCREMENT_BY_INDEX = 0x44, + INCREMENT_BY_INDEX = 0x45, // @args symbol id, count // @role Increment the variable `primary` by `count` and store its value in the given symbol id - INCREMENT_STORE = 0x45, + INCREMENT_STORE = 0x46, // @args symbol id, count // @role Decrement the variable `primary` by `count` and push its value on the stack - DECREMENT = 0x46, + DECREMENT = 0x47, // @args symbol index, count // @role Decrement the variable `primary` by `count` and push its value on the stack - DECREMENT_BY_INDEX = 0x47, + DECREMENT_BY_INDEX = 0x48, // @args symbol id, count // @role Decrement the variable `primary` by `count` and store its value in the given symbol id - DECREMENT_STORE = 0x48, + DECREMENT_STORE = 0x49, // @args symbol id, symbol id // @role Load the symbol `primary`, compute its tail, store it in a new variable `secondary` - STORE_TAIL = 0x49, + STORE_TAIL = 0x4a, // @args symbol index, symbol id // @role Load the symbol `primary`, compute its tail, store it in a new variable `secondary` - STORE_TAIL_BY_INDEX = 0x4a, + STORE_TAIL_BY_INDEX = 0x4b, // @args symbol id, symbol id // @role Load the symbol `primary`, compute its head, store it in a new variable `secondary` - STORE_HEAD = 0x4b, + STORE_HEAD = 0x4c, // @args symbol index, symbol id // @role Load the symbol `primary`, compute its head, store it in a new variable `secondary` - STORE_HEAD_BY_INDEX = 0x4c, + STORE_HEAD_BY_INDEX = 0x4d, // @args number, symbol id // @role Create a list of `number` elements, and store it in a new variable `secondary` - STORE_LIST = 0x4d, + STORE_LIST = 0x4e, // @args symbol id, symbol id // @role Load the symbol `primary`, compute its tail, store it in an existing variable `secondary` - SET_VAL_TAIL = 0x4e, + SET_VAL_TAIL = 0x4f, // @args symbol index, symbol id // @role Load the symbol `primary`, compute its tail, store it in an existing variable `secondary` - SET_VAL_TAIL_BY_INDEX = 0x4f, + SET_VAL_TAIL_BY_INDEX = 0x50, // @args symbol id, symbol id // @role Load the symbol `primary`, compute its head, store it in an existing variable `secondary` - SET_VAL_HEAD = 0x50, + SET_VAL_HEAD = 0x51, // @args symbol index, symbol id // @role Load the symbol `primary`, compute its head, store it in an existing variable `secondary` - SET_VAL_HEAD_BY_INDEX = 0x51, + SET_VAL_HEAD_BY_INDEX = 0x52, // @args builtin id, argument count // @role Call a builtin by its id in `primary`, with `secondary` arguments. Bypass the stack size check because we do not push IP/PP since builtins calls do not alter the stack - CALL_BUILTIN = 0x52, + CALL_BUILTIN = 0x53, // @args builtin id, argument count // @role Call a builtin by its id in `primary`, with `secondary` arguments. Bypass the stack size check because we do not push IP/PP since builtins calls do not alter the stack, as well as the return address removal - CALL_BUILTIN_WITHOUT_RETURN_ADDRESS = 0x53, + CALL_BUILTIN_WITHOUT_RETURN_ADDRESS = 0x54, // @args constant id, absolute address to jump to // @role Compare `TS < constant`, if the comparison fails, jump to the given address. Otherwise, does nothing - LT_CONST_JUMP_IF_FALSE = 0x54, + LT_CONST_JUMP_IF_FALSE = 0x55, // @args constant id, absolute address to jump to // @role Compare `TS < constant`, if the comparison succeeds, jump to the given address. Otherwise, does nothing - LT_CONST_JUMP_IF_TRUE = 0x55, + LT_CONST_JUMP_IF_TRUE = 0x56, // @args symbol id, absolute address to jump to // @role Compare `TS < symbol`, if the comparison fails, jump to the given address. Otherwise, does nothing - LT_SYM_JUMP_IF_FALSE = 0x56, + LT_SYM_JUMP_IF_FALSE = 0x57, // @args constant id, absolute address to jump to // @role Compare `TS > constant`, if the comparison succeeds, jump to the given address. Otherwise, does nothing - GT_CONST_JUMP_IF_TRUE = 0x57, + GT_CONST_JUMP_IF_TRUE = 0x58, // @args constant id, absolute address to jump to // @role Compare `TS > constant`, if the comparison fails, jump to the given address. Otherwise, does nothing - GT_CONST_JUMP_IF_FALSE = 0x58, + GT_CONST_JUMP_IF_FALSE = 0x59, // @args symbol id, absolute address to jump to // @role Compare `TS > symbol`, if the comparison fails, jump to the given address. Otherwise, does nothing - GT_SYM_JUMP_IF_FALSE = 0x59, + GT_SYM_JUMP_IF_FALSE = 0x5a, // @args constant id, absolute address to jump to // @role Compare `TS == constant`, if the comparison succeeds, jump to the given address. Otherwise, does nothing - EQ_CONST_JUMP_IF_TRUE = 0x5a, + EQ_CONST_JUMP_IF_TRUE = 0x5b, // @args symbol index, absolute address to jump to // @role Compare `TS == symbol`, if the comparison succeeds, jump to the given address. Otherwise, does nothing - EQ_SYM_INDEX_JUMP_IF_TRUE = 0x5b, + EQ_SYM_INDEX_JUMP_IF_TRUE = 0x5c, // @args constant id, absolute address to jump to // @role Compare `TS != constant`, if the comparison succeeds, jump to the given address. Otherwise, does nothing - NEQ_CONST_JUMP_IF_TRUE = 0x5c, + NEQ_CONST_JUMP_IF_TRUE = 0x5d, // @args symbol id, absolute address to jump to // @role Compare `TS != symbol`, if the comparison fails, jump to the given address. Otherwise, does nothing - NEQ_SYM_JUMP_IF_FALSE = 0x5d, + NEQ_SYM_JUMP_IF_FALSE = 0x5e, // @args symbol id, argument count // @role Call a symbol by its id in `primary`, with `secondary` arguments - CALL_SYMBOL = 0x5e, + CALL_SYMBOL = 0x5f, // @args symbol id (function name), argument count // @role Call the current page with `secondary` arguments - CALL_CURRENT_PAGE = 0x5f, + CALL_CURRENT_PAGE = 0x60, // @args symbol id, field id in symbols table // @role Push the field of a given symbol (which has to be a closure) on the stack - GET_FIELD_FROM_SYMBOL = 0x60, + GET_FIELD_FROM_SYMBOL = 0x61, // @args symbol index, field id in symbols table // @role Push the field of a given symbol (which has to be a closure) on the stack - GET_FIELD_FROM_SYMBOL_INDEX = 0x61, + GET_FIELD_FROM_SYMBOL_INDEX = 0x62, // @args symbol id, symbol id2 // @role Push symbol[symbol2] - AT_SYM_SYM = 0x62, + AT_SYM_SYM = 0x63, // @args symbol index, symbol index2 // @role Push symbol[symbol2] - AT_SYM_INDEX_SYM_INDEX = 0x63, + AT_SYM_INDEX_SYM_INDEX = 0x64, // @args symbol index, constant id // @role Push symbol[constant] - AT_SYM_INDEX_CONST = 0x64, + AT_SYM_INDEX_CONST = 0x65, // @args symbol id, constant id // @role Check that the type of symbol is the given constant, push true if so, false otherwise - CHECK_TYPE_OF = 0x65, + CHECK_TYPE_OF = 0x66, // @args symbol index, constant id // @role Check that the type of symbol is the given constant, push true if so, false otherwise - CHECK_TYPE_OF_BY_INDEX = 0x66, + CHECK_TYPE_OF_BY_INDEX = 0x67, // @args symbol id, number of elements // @role Append N elements to a reference to a list (symbol id), the list is being mutated in-place, no new object created. Elements are stored in TS(1)..TS(N). Follows the function calling convention - APPEND_IN_PLACE_SYM = 0x67, + APPEND_IN_PLACE_SYM = 0x68, // @args symbol index, number of elements // @role Append N elements to a reference to a list (symbol index), the list is being mutated in-place, no new object created. Elements are stored in TS(1)..TS(N). Follows the function calling convention - APPEND_IN_PLACE_SYM_INDEX = 0x68, + APPEND_IN_PLACE_SYM_INDEX = 0x69, // @args symbol index, symbol id // @role Compute the length of the list or string at symbol index, and store it in a variable (symbol id) - STORE_LEN = 0x69, + STORE_LEN = 0x6a, // @args symbol id, absolute address to jump to // @role Compute the length of a symbol (list or string), and pop TS to compare it, then jump if false - LT_LEN_SYM_JUMP_IF_FALSE = 0x6a, + LT_LEN_SYM_JUMP_IF_FALSE = 0x6b, InstructionsCount }; @@ -435,6 +439,7 @@ namespace Ark::internal "LOAD_CONST", "POP_JUMP_IF_TRUE", "STORE", + "STORE_REF", "SET_VAL", "POP_JUMP_IF_FALSE", "JUMP", diff --git a/include/Ark/VM/VM.inl b/include/Ark/VM/VM.inl index 0752f9d52..fb1d6313b 100644 --- a/include/Ark/VM/VM.inl +++ b/include/Ark/VM/VM.inl @@ -117,6 +117,8 @@ inline Value* VM::loadSymbolFromIndex(const uint16_t index, internal::ExecutionC // treatment only for function calls. auto& [id, value] = context.locals.back().atPosReverse(index); context.last_symbol = id; + if (value.valueType() == ValueType::Reference) + return value.reference(); return &value; } @@ -323,7 +325,8 @@ inline void VM::call(internal::ExecutionContext& context, const uint16_t argc, V needed_argc = 0; // every argument is a MUT declaration in the bytecode - while (m_state.inst(context.pp, index) == STORE) + while (m_state.inst(context.pp, index) == STORE || + m_state.inst(context.pp, index) == STORE_REF) { needed_argc += 1; index += 4; // instructions are on 4 bytes diff --git a/lib/std b/lib/std index 0734436be..4860f9d4d 160000 --- a/lib/std +++ b/lib/std @@ -1 +1 @@ -Subproject commit 0734436be5de2a7bdfe141f3b07b7c4f131b7ccc +Subproject commit 4860f9d4d76fec2740acd51450bf56361b7c0049 diff --git a/src/arkreactor/Builtins/Builtins.cpp b/src/arkreactor/Builtins/Builtins.cpp index 55b5b9198..d689f83e6 100644 --- a/src/arkreactor/Builtins/Builtins.cpp +++ b/src/arkreactor/Builtins/Builtins.cpp @@ -42,6 +42,7 @@ namespace Ark::internal::Builtins { "builtin__io:writeFile", Value(IO::writeFile) }, { "builtin__io:appendToFile", Value(IO::appendToFile) }, { "builtin__io:readFile", Value(IO::readFile) }, + { "builtin__io:readLinesFile", Value(IO::readLinesFile) }, { "builtin__io:fileExists?", Value(IO::fileExists) }, { "builtin__io:listFiles", Value(IO::listFiles) }, { "builtin__io:dir?", Value(IO::isDirectory) }, diff --git a/src/arkreactor/Builtins/IO.cpp b/src/arkreactor/Builtins/IO.cpp index 0813493af..87e6a7d2a 100644 --- a/src/arkreactor/Builtins/IO.cpp +++ b/src/arkreactor/Builtins/IO.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -145,6 +146,26 @@ namespace Ark::internal::Builtins::IO return Value(Utils::readFile(filename)); } + // cppcheck-suppress constParameterReference + Value readLinesFile(std::vector& n, VM* vm [[maybe_unused]]) + { + if (!types::check(n, ValueType::String)) + throw types::TypeCheckingError( + "io:readLinesFile", + { { types::Contract { { types::Typedef("filename", ValueType::String) } } } }, + n); + + std::string filename = n[0].string(); + if (!Utils::fileExists(filename)) + throw std::runtime_error( + fmt::format("io:readLinesFile: couldn't read file \"{}\" because it doesn't exist", filename)); + + Value out = Value(ValueType::List); + for (auto&& s : Utils::splitString(Utils::readFile(filename), '\n')) + out.push_back(Value(s)); + return out; + } + // cppcheck-suppress constParameterReference Value fileExists(std::vector& n, VM* vm [[maybe_unused]]) { diff --git a/src/arkreactor/Compiler/AST/Node.cpp b/src/arkreactor/Compiler/AST/Node.cpp index b71691a9b..5216bfd68 100644 --- a/src/arkreactor/Compiler/AST/Node.cpp +++ b/src/arkreactor/Compiler/AST/Node.cpp @@ -185,6 +185,14 @@ namespace Ark::internal data += string(); break; + case NodeType::MutArg: + data += "(mut " + string() + ")"; + break; + + case NodeType::RefArg: + data += "(ref " + string() + ")"; + break; + case NodeType::Capture: data += "&" + string(); break; @@ -284,6 +292,14 @@ namespace Ark::internal os << "Symbol:" << string(); break; + case NodeType::MutArg: + os << "MutArg:" << string(); + break; + + case NodeType::RefArg: + os << "RefArg:" << string(); + break; + case NodeType::Capture: os << "Capture:" << string(); break; diff --git a/src/arkreactor/Compiler/AST/Parser.cpp b/src/arkreactor/Compiler/AST/Parser.cpp index 28d15bd3d..570f4445e 100644 --- a/src/arkreactor/Compiler/AST/Parser.cpp +++ b/src/arkreactor/Compiler/AST/Parser.cpp @@ -477,6 +477,36 @@ namespace Ark::internal args->push_back(positioned(Node(NodeType::Capture, capture), pos)); } + else if (accept(IsChar('('))) + { + // attribute modifiers: mut, ref + std::string modifier; + std::ignore = newlineOrComment(); + if (!oneOf({ "mut", "ref" }, &modifier)) + // We cannot return an error like this: + // error("Expected an attribute modifier, either `mut' or `ref'", pos); + // Because it would break on macro instantiations like (fun ((suffix-dup a 3)) ()) + return std::nullopt; + + NodeType type = NodeType::Unused; + if (modifier == "mut") + type = NodeType::MutArg; + else if (modifier == "ref") + type = NodeType::RefArg; + + Node arg_with_attr = Node(type); + std::string comment2 = newlineOrComment(); + arg_with_attr.attachCommentAfter(comment2); + + std::string symbol_name; + if (!name(&symbol_name)) + error(fmt::format("Expected a symbol name for the attribute with modifier `{}'", modifier), pos); + arg_with_attr.setString(symbol_name); + + args->push_back(positioned(arg_with_attr, pos)); + std::ignore = newlineOrComment(); + expect(IsChar(')')); + } else { std::string symbol_name; diff --git a/src/arkreactor/Compiler/BytecodeReader.cpp b/src/arkreactor/Compiler/BytecodeReader.cpp index 07abc2b81..d37f0a8be 100644 --- a/src/arkreactor/Compiler/BytecodeReader.cpp +++ b/src/arkreactor/Compiler/BytecodeReader.cpp @@ -494,6 +494,7 @@ namespace Ark { LOAD_CONST, ArgKind::Constant }, { POP_JUMP_IF_TRUE, ArgKind::Raw }, { STORE, ArgKind::Symbol }, + { STORE_REF, ArgKind::Symbol }, { SET_VAL, ArgKind::Symbol }, { POP_JUMP_IF_FALSE, ArgKind::Raw }, { JUMP, ArgKind::Raw }, diff --git a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp index f6c341041..4a0b2b527 100644 --- a/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp +++ b/src/arkreactor/Compiler/Lowerer/ASTLowerer.cpp @@ -413,11 +413,16 @@ namespace Ark::internal // pushing arguments from the stack into variables in the new scope for (const auto& node : x.constList()[1].constList()) { - if (node.nodeType() == NodeType::Symbol) + if (node.nodeType() == NodeType::Symbol || node.nodeType() == NodeType::MutArg) { page(function_body_page).emplace_back(STORE, addSymbol(node)); m_locals_locator.addLocal(node.string()); } + else if (node.nodeType() == NodeType::RefArg) + { + page(function_body_page).emplace_back(STORE_REF, addSymbol(node)); + m_locals_locator.addLocal(node.string()); + } } // Register an opened variable as "#anonymous", which won't match any valid names inside ASTLowerer::handleCalls. @@ -425,7 +430,7 @@ namespace Ark::internal // (let name (fun (e) (map lst (fun (e) (name e))))) // Otherwise, `name` would have been optimized to a GET_CURRENT_PAGE_ADDRESS, which would have returned the wrong page. if (x.isAnonymousFunction()) - m_opened_vars.push("#anonymous"); + m_opened_vars.emplace("#anonymous"); // push body of the function compileExpression(x.list()[2], function_body_page, false, true); if (x.isAnonymousFunction()) diff --git a/src/arkreactor/Compiler/Macros/Processor.cpp b/src/arkreactor/Compiler/Macros/Processor.cpp index 9b4991468..d0a9e9299 100644 --- a/src/arkreactor/Compiler/Macros/Processor.cpp +++ b/src/arkreactor/Compiler/Macros/Processor.cpp @@ -740,6 +740,8 @@ namespace Ark::internal return isConstEval(child); }); + case NodeType::MutArg: + case NodeType::RefArg: case NodeType::Capture: case NodeType::Field: return false; diff --git a/src/arkreactor/Compiler/NameResolution/NameResolutionPass.cpp b/src/arkreactor/Compiler/NameResolution/NameResolutionPass.cpp index 0cdcb5b39..329ce11ca 100644 --- a/src/arkreactor/Compiler/NameResolution/NameResolutionPass.cpp +++ b/src/arkreactor/Compiler/NameResolution/NameResolutionPass.cpp @@ -243,10 +243,11 @@ namespace Ark::internal // this will prevent name conflicts, and handle scope resolution std::string old_name = child.string(); updateSymbolWithFullyQualifiedName(child); - // FIXME: addDefinedSymbol(fqn, true); ? addDefinedSymbol(old_name, true); } - else if (child.nodeType() == NodeType::Symbol) + else if (child.nodeType() == NodeType::Symbol || child.nodeType() == NodeType::RefArg) + addDefinedSymbol(child.string(), /* is_mutable= */ false); + else if (child.nodeType() == NodeType::MutArg) addDefinedSymbol(child.string(), /* is_mutable= */ true); } } diff --git a/src/arkreactor/VM/VM.cpp b/src/arkreactor/VM/VM.cpp index 96eafc52b..1f8f7d30d 100644 --- a/src/arkreactor/VM/VM.cpp +++ b/src/arkreactor/VM/VM.cpp @@ -543,6 +543,7 @@ namespace Ark &&TARGET_LOAD_CONST, &&TARGET_POP_JUMP_IF_TRUE, &&TARGET_STORE, + &&TARGET_STORE_REF, &&TARGET_SET_VAL, &&TARGET_POP_JUMP_IF_FALSE, &&TARGET_JUMP, @@ -705,6 +706,15 @@ namespace Ark DISPATCH(); } + TARGET(STORE_REF) + { + // Not resolving a potential ref is on purpose! + // This instruction is only used by functions when storing arguments + Value* tmp = pop(context); + store(arg, tmp, context); + DISPATCH(); + } + TARGET(SET_VAL) { setVal(arg, popAndResolveAsPtr(context), context); @@ -2037,7 +2047,8 @@ namespace Ark arg_names.emplace_back(""); // for formatting, so that we have a space between the function and the args std::size_t index = 0; - while (m_state.inst(context.pp, index) == STORE) + while (m_state.inst(context.pp, index) == STORE || + m_state.inst(context.pp, index) == STORE_REF) { const auto id = static_cast((m_state.inst(context.pp, index + 2) << 8) + m_state.inst(context.pp, index + 3)); arg_names.push_back(m_state.m_symbols[id]); diff --git a/src/arkscript/Formatter.cpp b/src/arkscript/Formatter.cpp index 63c94dc2a..f9782042e 100644 --- a/src/arkscript/Formatter.cpp +++ b/src/arkscript/Formatter.cpp @@ -186,6 +186,12 @@ std::string Formatter::format(const Node& node, std::size_t indent, bool after_n case NodeType::Symbol: result += node.string(); break; + case NodeType::MutArg: + result += fmt::format("(mut {})", node.string()); + break; + case NodeType::RefArg: + result += fmt::format("(ref {})", node.string()); + break; case NodeType::Capture: result += "&" + node.string(); break; diff --git a/src/arkscript/JsonCompiler.cpp b/src/arkscript/JsonCompiler.cpp index 1af94c2bb..0bb080daa 100644 --- a/src/arkscript/JsonCompiler.cpp +++ b/src/arkscript/JsonCompiler.cpp @@ -38,6 +38,22 @@ std::string JsonCompiler::_compile(const Node& node) break; } + case NodeType::MutArg: + { + json += fmt::format( + R"({{"type": "MutArg", "name": "{}"}})", + node.string()); + break; + } + + case NodeType::RefArg: + { + json += fmt::format( + R"({{"type": "RefArg", "name": "{}"}})", + node.string()); + break; + } + case NodeType::Spread: { json += fmt::format( diff --git a/tests/benchmarks/main.cpp b/tests/benchmarks/main.cpp index ca511f7f0..b518a7d6e 100644 --- a/tests/benchmarks/main.cpp +++ b/tests/benchmarks/main.cpp @@ -35,6 +35,7 @@ ARK_CREATE_RUNTIME_BENCH(binary_trees); ARK_CREATE_RUNTIME_BENCH(for_sum); ARK_CREATE_RUNTIME_BENCH(create_closure)->Iterations(500); ARK_CREATE_RUNTIME_BENCH(create_list)->Iterations(500); +ARK_CREATE_RUNTIME_BENCH(create_list_with_ref)->Iterations(500); ARK_CREATE_RUNTIME_BENCH(n_queens)->Iterations(50); // -------------------------------------------- diff --git a/tests/benchmarks/resources/parser/big.ark b/tests/benchmarks/resources/parser/big.ark index 1addbb6b1..43ff2f8b5 100644 --- a/tests/benchmarks/resources/parser/big.ark +++ b/tests/benchmarks/resources/parser/big.ark @@ -194,7 +194,7 @@ # (print (list:take [1 2 3 4 5 6 7 8 9] 4)) # [1 2 3 4] # =end # @author https://github.com/rstefanic -(let list:take (fun (_L _n) { +(let list:take (fun (_L (mut _n)) { (mut _index 0) (mut _output []) (set _n (math:min _n (len _L))) diff --git a/tests/benchmarks/resources/parser/bigger.ark b/tests/benchmarks/resources/parser/bigger.ark index 6d53575cc..767aa3257 100644 --- a/tests/benchmarks/resources/parser/bigger.ark +++ b/tests/benchmarks/resources/parser/bigger.ark @@ -413,7 +413,7 @@ # (print (take [1 2 3 4 5 6 7 8 9] 4)) # [1 2 3 4] # =end # @author https://github.com/rstefanic -(let take (fun (_L _n) { +(let take (fun (_L (mut _n)) { (mut _index 0) (mut _output []) (set _n (min _n (len _L))) @@ -673,7 +673,7 @@ # (print (list:insert b 1 [1 2])) # [0 1 2 9] # =end # @author https://github.com/SuperFola -(let insert (fun (_L _index _value) { +(let insert (fun (_L _index (mut _value)) { (let _size (len _L)) (assert (<= _index _size) "list:insert can not insert a value outside the list") diff --git a/tests/benchmarks/resources/runtime/create_list_with_ref.ark b/tests/benchmarks/resources/runtime/create_list_with_ref.ark new file mode 100644 index 000000000..b5ff87209 --- /dev/null +++ b/tests/benchmarks/resources/runtime/create_list_with_ref.ark @@ -0,0 +1,31 @@ +(let sum (fun ((ref data)) + (+ + (if (= "List" (type (@ data 0))) (sum (@ data 0)) (@ data 0)) + (if (= "List" (type (@ data 1))) (sum (@ data 1)) (@ data 1))))) + +(mut i 0) +(while (< i 100) { + (let tree + [ + [ + [1 2] + [ + 3 + [ + 4 + [5 6]]]] + [ + [ + [ + [7 8] + 9] + [10 11]] + [ + [ + 12 + [ + 13 + [14 15]]] + [16 17]]]]) + (sum tree) + (set i (+ 1 i) )}) diff --git a/tests/unittests/resources/ASTSuite/args_attr.ark b/tests/unittests/resources/ASTSuite/args_attr.ark new file mode 100644 index 000000000..aba8aa8f7 --- /dev/null +++ b/tests/unittests/resources/ASTSuite/args_attr.ark @@ -0,0 +1 @@ +(fun (a (mut b) (ref c)) (+ a b c)) diff --git a/tests/unittests/resources/ASTSuite/args_attr.json b/tests/unittests/resources/ASTSuite/args_attr.json new file mode 100644 index 000000000..eb8f77fbb --- /dev/null +++ b/tests/unittests/resources/ASTSuite/args_attr.json @@ -0,0 +1 @@ +{"type": "Begin", "children": [{"type": "Fun", "args": [{"type": "Symbol", "name": "a"}, {"type": "MutArg", "name": "b"}, {"type": "RefArg", "name": "c"}], "body": {"type": "FunctionCall", "name": {"type": "Symbol", "name": "+"}, "args": [{"type": "Symbol", "name": "a"}, {"type": "Symbol", "name": "b"}, {"type": "Symbol", "name": "c"}]}}]} diff --git a/tests/unittests/resources/ASTSuite/closures.ark b/tests/unittests/resources/ASTSuite/closures.ark index f818e5632..a0161e123 100644 --- a/tests/unittests/resources/ASTSuite/closures.ark +++ b/tests/unittests/resources/ASTSuite/closures.ark @@ -2,7 +2,7 @@ # Closures and object are equivalent: http://wiki.c2.com/?ClosuresAndObjectsAreEquivalent # this will construct a closure capturing the 3 arguments, plus a function to set the age -(let create-human (fun (name age weight) { +(let create-human (fun (name (mut age) weight) { # functions can be invoked in the closure scope (let set-age (fun (new-age) (set age new-age))) diff --git a/tests/unittests/resources/ASTSuite/closures.json b/tests/unittests/resources/ASTSuite/closures.json index 29c4565f4..15807051f 100644 --- a/tests/unittests/resources/ASTSuite/closures.json +++ b/tests/unittests/resources/ASTSuite/closures.json @@ -1 +1 @@ -{"type": "Begin", "children": [{"type": "Let", "name": {"type": "Symbol", "name": "create-human"}, "value": {"type": "Fun", "args": [{"type": "Symbol", "name": "name"}, {"type": "Symbol", "name": "age"}, {"type": "Symbol", "name": "weight"}], "body": {"type": "Begin", "children": [{"type": "Let", "name": {"type": "Symbol", "name": "set-age"}, "value": {"type": "Fun", "args": [{"type": "Symbol", "name": "new-age"}], "body": {"type": "Set", "name": {"type": "Symbol", "name": "age"}, "value": {"type": "Symbol", "name": "new-age"}}}}, {"type": "Fun", "args": [{"type": "Capture", "name": "set-age"}, {"type": "Capture", "name": "name"}, {"type": "Capture", "name": "age"}, {"type": "Capture", "name": "weight"}], "body": {"type": "Symbol", "name": "nil"}}]}}}, {"type": "Let", "name": {"type": "Symbol", "name": "bob"}, "value": {"type": "FunctionCall", "name": {"type": "Symbol", "name": "create-human"}, "args": [{"type": "String", "value": "Bob"}, {"type": "Number", "value": 0}, {"type": "Number", "value": 144}]}}, {"type": "Let", "name": {"type": "Symbol", "name": "john"}, "value": {"type": "FunctionCall", "name": {"type": "Symbol", "name": "create-human"}, "args": [{"type": "String", "value": "John"}, {"type": "Number", "value": 12}, {"type": "Number", "value": 15}]}}, {"type": "FunctionCall", "name": {"type": "Symbol", "name": "print"}, "args": [{"type": "String", "value": "Bob's age: "}, {"type": "Field", "children": [{"type": "Symbol", "name": "bob"}, {"type": "Symbol", "name": "age"}]}]}, {"type": "FunctionCall", "name": {"type": "Symbol", "name": "print"}, "args": [{"type": "String", "value": "Setting Bob's age to 10"}]}, [{"type": "Field", "children": [{"type": "Symbol", "name": "bob"}, {"type": "Symbol", "name": "set-age"}]}, {"type": "Number", "value": 10}], {"type": "FunctionCall", "name": {"type": "Symbol", "name": "print"}, "args": [{"type": "String", "value": "New age: "}, {"type": "Field", "children": [{"type": "Symbol", "name": "bob"}, {"type": "Symbol", "name": "age"}]}]}, {"type": "FunctionCall", "name": {"type": "Symbol", "name": "print"}, "args": [{"type": "String", "value": "John's age, didn't change: "}, {"type": "Field", "children": [{"type": "Symbol", "name": "john"}, {"type": "Symbol", "name": "age"}]}]}, {"type": "Let", "name": {"type": "Symbol", "name": "countdown-from"}, "value": {"type": "Fun", "args": [{"type": "Symbol", "name": "number"}], "body": {"type": "Fun", "args": [{"type": "Capture", "name": "number"}], "body": {"type": "Begin", "children": [{"type": "Set", "name": {"type": "Symbol", "name": "number"}, "value": {"type": "FunctionCall", "name": {"type": "Symbol", "name": "-"}, "args": [{"type": "Symbol", "name": "number"}, {"type": "Number", "value": 1}]}}, {"type": "Symbol", "name": "number"}]}}}}, {"type": "Let", "name": {"type": "Symbol", "name": "countdown-from-3"}, "value": {"type": "FunctionCall", "name": {"type": "Symbol", "name": "countdown-from"}, "args": [{"type": "Number", "value": 3}]}}, {"type": "FunctionCall", "name": {"type": "Symbol", "name": "print"}, "args": [{"type": "String", "value": "Countdown "}, [{"type": "Symbol", "name": "countdown-from-3"}]]}, {"type": "FunctionCall", "name": {"type": "Symbol", "name": "print"}, "args": [{"type": "String", "value": "Countdown "}, [{"type": "Symbol", "name": "countdown-from-3"}]]}, {"type": "FunctionCall", "name": {"type": "Symbol", "name": "print"}, "args": [{"type": "String", "value": "Countdown "}, [{"type": "Symbol", "name": "countdown-from-3"}]]}]} \ No newline at end of file +{"type": "Begin", "children": [{"type": "Let", "name": {"type": "Symbol", "name": "create-human"}, "value": {"type": "Fun", "args": [{"type": "Symbol", "name": "name"}, {"type": "MutArg", "name": "age"}, {"type": "Symbol", "name": "weight"}], "body": {"type": "Begin", "children": [{"type": "Let", "name": {"type": "Symbol", "name": "set-age"}, "value": {"type": "Fun", "args": [{"type": "Symbol", "name": "new-age"}], "body": {"type": "Set", "name": {"type": "Symbol", "name": "age"}, "value": {"type": "Symbol", "name": "new-age"}}}}, {"type": "Fun", "args": [{"type": "Capture", "name": "set-age"}, {"type": "Capture", "name": "name"}, {"type": "Capture", "name": "age"}, {"type": "Capture", "name": "weight"}], "body": {"type": "Symbol", "name": "nil"}}]}}}, {"type": "Let", "name": {"type": "Symbol", "name": "bob"}, "value": {"type": "FunctionCall", "name": {"type": "Symbol", "name": "create-human"}, "args": [{"type": "String", "value": "Bob"}, {"type": "Number", "value": 0}, {"type": "Number", "value": 144}]}}, {"type": "Let", "name": {"type": "Symbol", "name": "john"}, "value": {"type": "FunctionCall", "name": {"type": "Symbol", "name": "create-human"}, "args": [{"type": "String", "value": "John"}, {"type": "Number", "value": 12}, {"type": "Number", "value": 15}]}}, {"type": "FunctionCall", "name": {"type": "Symbol", "name": "print"}, "args": [{"type": "String", "value": "Bob's age: "}, {"type": "Field", "children": [{"type": "Symbol", "name": "bob"}, {"type": "Symbol", "name": "age"}]}]}, {"type": "FunctionCall", "name": {"type": "Symbol", "name": "print"}, "args": [{"type": "String", "value": "Setting Bob's age to 10"}]}, [{"type": "Field", "children": [{"type": "Symbol", "name": "bob"}, {"type": "Symbol", "name": "set-age"}]}, {"type": "Number", "value": 10}], {"type": "FunctionCall", "name": {"type": "Symbol", "name": "print"}, "args": [{"type": "String", "value": "New age: "}, {"type": "Field", "children": [{"type": "Symbol", "name": "bob"}, {"type": "Symbol", "name": "age"}]}]}, {"type": "FunctionCall", "name": {"type": "Symbol", "name": "print"}, "args": [{"type": "String", "value": "John's age, didn't change: "}, {"type": "Field", "children": [{"type": "Symbol", "name": "john"}, {"type": "Symbol", "name": "age"}]}]}, {"type": "Let", "name": {"type": "Symbol", "name": "countdown-from"}, "value": {"type": "Fun", "args": [{"type": "Symbol", "name": "number"}], "body": {"type": "Fun", "args": [{"type": "Capture", "name": "number"}], "body": {"type": "Begin", "children": [{"type": "Set", "name": {"type": "Symbol", "name": "number"}, "value": {"type": "FunctionCall", "name": {"type": "Symbol", "name": "-"}, "args": [{"type": "Symbol", "name": "number"}, {"type": "Number", "value": 1}]}}, {"type": "Symbol", "name": "number"}]}}}}, {"type": "Let", "name": {"type": "Symbol", "name": "countdown-from-3"}, "value": {"type": "FunctionCall", "name": {"type": "Symbol", "name": "countdown-from"}, "args": [{"type": "Number", "value": 3}]}}, {"type": "FunctionCall", "name": {"type": "Symbol", "name": "print"}, "args": [{"type": "String", "value": "Countdown "}, [{"type": "Symbol", "name": "countdown-from-3"}]]}, {"type": "FunctionCall", "name": {"type": "Symbol", "name": "print"}, "args": [{"type": "String", "value": "Countdown "}, [{"type": "Symbol", "name": "countdown-from-3"}]]}, {"type": "FunctionCall", "name": {"type": "Symbol", "name": "print"}, "args": [{"type": "String", "value": "Countdown "}, [{"type": "Symbol", "name": "countdown-from-3"}]]}]} \ No newline at end of file diff --git a/tests/unittests/resources/CompilerSuite/ir/99bottles.expected b/tests/unittests/resources/CompilerSuite/ir/99bottles.expected index 7d8fdf0dc..b583a6e76 100644 --- a/tests/unittests/resources/CompilerSuite/ir/99bottles.expected +++ b/tests/unittests/resources/CompilerSuite/ir/99bottles.expected @@ -37,7 +37,7 @@ page_0 LOAD_SYMBOL 4 LOAD_SYMBOL 4 LOAD_CONST 3 - BUILTIN 25 + BUILTIN 26 CALL 3 .L7: BUILTIN 9 @@ -52,7 +52,7 @@ page_0 PUSH_RETURN_ADDRESS L9 LOAD_SYMBOL 4 LOAD_CONST 4 - BUILTIN 25 + BUILTIN 26 CALL 2 .L9: BUILTIN 9 diff --git a/tests/unittests/resources/CompilerSuite/ir/args_attr.ark b/tests/unittests/resources/CompilerSuite/ir/args_attr.ark new file mode 100644 index 000000000..f2839daa5 --- /dev/null +++ b/tests/unittests/resources/CompilerSuite/ir/args_attr.ark @@ -0,0 +1,6 @@ +(let foo (fun ((mut a) (ref b)) { + (set a (len b)) + a })) + +(let data [1 2 3]) +(print (foo 1 data)) diff --git a/tests/unittests/resources/CompilerSuite/ir/args_attr.expected b/tests/unittests/resources/CompilerSuite/ir/args_attr.expected new file mode 100644 index 000000000..fa58aa6d7 --- /dev/null +++ b/tests/unittests/resources/CompilerSuite/ir/args_attr.expected @@ -0,0 +1,29 @@ +page_0 + LOAD_CONST 0 + STORE 0 + LOAD_CONST 1 + LOAD_CONST 2 + LOAD_CONST 3 + LIST 3 + STORE 3 + PUSH_RETURN_ADDRESS L0 + PUSH_RETURN_ADDRESS L1 + LOAD_SYMBOL_BY_INDEX 0 + LOAD_CONST 3 + LOAD_SYMBOL_BY_INDEX 1 + CALL 2 +.L1: + BUILTIN 9 + CALL 1 +.L0: + HALT 0 + +page_1 + STORE 1 + STORE_REF 2 + LOAD_SYMBOL_BY_INDEX 0 + LEN 0 + SET_VAL 1 + LOAD_SYMBOL_BY_INDEX 1 + RET 0 + HALT 0 diff --git a/tests/unittests/resources/CompilerSuite/ir/closures.ark b/tests/unittests/resources/CompilerSuite/ir/closures.ark index f818e5632..a0161e123 100644 --- a/tests/unittests/resources/CompilerSuite/ir/closures.ark +++ b/tests/unittests/resources/CompilerSuite/ir/closures.ark @@ -2,7 +2,7 @@ # Closures and object are equivalent: http://wiki.c2.com/?ClosuresAndObjectsAreEquivalent # this will construct a closure capturing the 3 arguments, plus a function to set the age -(let create-human (fun (name age weight) { +(let create-human (fun (name (mut age) weight) { # functions can be invoked in the closure scope (let set-age (fun (new-age) (set age new-age))) diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected index c2f94e80f..844c28dc3 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/99bottles.expected @@ -2,7 +2,7 @@ page_0 LOAD_CONST_STORE 0, 0 LOAD_CONST_STORE 1, 2 LOAD_CONST_STORE 2, 4 - BUILTIN 21 + BUILTIN 22 STORE 6 STORE_FROM 8, 7 STORE_FROM 10, 9 @@ -37,7 +37,7 @@ page_0 LOAD_SYMBOL 13 LOAD_SYMBOL 13 LOAD_CONST 6 - CALL_BUILTIN 25, 3 + CALL_BUILTIN 26, 3 .L10: CALL_BUILTIN 9, 1 .L9: @@ -47,7 +47,7 @@ page_0 PUSH_RETURN_ADDRESS L12 LOAD_SYMBOL 13 LOAD_CONST 7 - CALL_BUILTIN 25, 2 + CALL_BUILTIN 26, 2 .L12: CALL_BUILTIN 9, 1 .L11: @@ -58,19 +58,19 @@ page_0 HALT 0 page_1 - CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 22, 1 + CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 23, 1 .L0: RET 0 HALT 0 page_2 - CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 23, 1 + CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 24, 1 .L1: RET 0 HALT 0 page_3 - CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 24, 1 + CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 25, 1 .L2: RET 0 HALT 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/builtins.expected b/tests/unittests/resources/CompilerSuite/optimized_ir/builtins.expected index cc0932c95..8453e41f4 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/builtins.expected +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/builtins.expected @@ -5,7 +5,7 @@ page_0 HALT 0 page_1 - CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 53, 1 + CALL_BUILTIN_WITHOUT_RETURN_ADDRESS 54, 1 .L0: RET 0 HALT 0 diff --git a/tests/unittests/resources/CompilerSuite/optimized_ir/closures.ark b/tests/unittests/resources/CompilerSuite/optimized_ir/closures.ark index cfef323a4..b320e0b05 100644 --- a/tests/unittests/resources/CompilerSuite/optimized_ir/closures.ark +++ b/tests/unittests/resources/CompilerSuite/optimized_ir/closures.ark @@ -2,7 +2,7 @@ # Closures and object are equivalent: http://wiki.c2.com/?ClosuresAndObjectsAreEquivalent # this will construct a closure capturing the 3 arguments, plus a function to set the age -(let create-human (fun (name age weight) { +(let create-human (fun (name (mut age) weight) { # functions can be invoked in the closure scope (let set-age (fun (new-age) (set age new-age))) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/backtrace_builtin.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/backtrace_builtin.expected index 109484e65..f011910db 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/backtrace_builtin.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/backtrace_builtin.expected @@ -1,16 +1,16 @@ Function list:sort expected 1 argument -> list (List) was of type String -In file /lib/std/List.ark:40 - 37 | # (list:sort [4 2 3]) # [1 2 4] - 38 | # =end - 39 | # @author https://github.com/SuperFola - 40 | (let sort (fun (_L) (builtin__list:sort _L))) +In file /lib/std/List.ark:51 + 48 | # (list:sort [4 2 3]) # [1 2 4] + 49 | # =end + 50 | # @author https://github.com/SuperFola + 51 | (let sort (fun (_L) (builtin__list:sort _L))) | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 41 | - 42 | # @brief Generate a List of n copies of an element + 52 | + 53 | # @brief Generate a List of n copies of an element -[ 2] In function `list:sort' (/lib/std/List.ark:40) +[ 2] In function `list:sort' (/lib/std/List.ark:51) [ 1] In global scope (tests/unittests/resources/DiagnosticsSuite/runtime/backtrace_builtin.ark:3) Current scope variables values: diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/stackoverflow_recur.ark b/tests/unittests/resources/DiagnosticsSuite/runtime/stackoverflow_recur.ark index e94097637..a81548daa 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/stackoverflow_recur.ark +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/stackoverflow_recur.ark @@ -1,4 +1,4 @@ -(let A (fun (k x1 x0 x3 x4 x5) { +(let A (fun ((mut k) x1 x0 x3 x4 x5) { (let B (fun () { (set k (- k 1)) (B k A x1 x0 x3 x4) })) diff --git a/tests/unittests/resources/DiagnosticsSuite/runtime/stackoverflow_recur.expected b/tests/unittests/resources/DiagnosticsSuite/runtime/stackoverflow_recur.expected index 4aca87f80..ffe39c724 100644 --- a/tests/unittests/resources/DiagnosticsSuite/runtime/stackoverflow_recur.expected +++ b/tests/unittests/resources/DiagnosticsSuite/runtime/stackoverflow_recur.expected @@ -1,7 +1,7 @@ Stack overflow. You could consider rewriting your function to make use of tail-call optimization. In file tests/unittests/resources/DiagnosticsSuite/runtime/stackoverflow_recur.ark:4 - 1 | (let A (fun (k x1 x0 x3 x4 x5) { + 1 | (let A (fun ((mut k) x1 x0 x3 x4 x5) { 2 | (let B (fun () { 3 | (set k (- k 1)) 4 | (B k A x1 x0 x3 x4) })) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/ioreadlinesfile_num.ark b/tests/unittests/resources/DiagnosticsSuite/typeChecking/ioreadlinesfile_num.ark new file mode 100644 index 000000000..3efb77d84 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/ioreadlinesfile_num.ark @@ -0,0 +1 @@ +(builtin__io:readLinesFile 1) diff --git a/tests/unittests/resources/DiagnosticsSuite/typeChecking/ioreadlinesfile_num.expected b/tests/unittests/resources/DiagnosticsSuite/typeChecking/ioreadlinesfile_num.expected new file mode 100644 index 000000000..b9b9f2350 --- /dev/null +++ b/tests/unittests/resources/DiagnosticsSuite/typeChecking/ioreadlinesfile_num.expected @@ -0,0 +1,7 @@ +Function io:readLinesFile expected 1 argument + -> filename (String) was of type Number + +In file tests/unittests/resources/DiagnosticsSuite/typeChecking/ioreadlinesfile_num.ark:1 + 1 | (builtin__io:readLinesFile 1) + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 2 | diff --git a/tests/unittests/resources/FormatterSuite/functions.ark b/tests/unittests/resources/FormatterSuite/functions.ark index 2a148fb95..0897e0922 100644 --- a/tests/unittests/resources/FormatterSuite/functions.ark +++ b/tests/unittests/resources/FormatterSuite/functions.ark @@ -18,3 +18,14 @@ a &c) # body {}) (let foo (fun () (if true false nil))) + +(fun ( +# mutable +( +mut +a +) +# readonly ref +( +ref b +) &c) ()) diff --git a/tests/unittests/resources/FormatterSuite/functions.expected b/tests/unittests/resources/FormatterSuite/functions.expected index 1bd1d9afe..a967659bc 100644 --- a/tests/unittests/resources/FormatterSuite/functions.expected +++ b/tests/unittests/resources/FormatterSuite/functions.expected @@ -22,3 +22,10 @@ (if true false nil))) + +(fun ( + # mutable + (mut a) + # readonly ref + (ref b) + &c) ()) diff --git a/tests/unittests/resources/LangSuite/async-tests.ark b/tests/unittests/resources/LangSuite/async-tests.ark index 5765de8e7..c82c285ba 100644 --- a/tests/unittests/resources/LangSuite/async-tests.ark +++ b/tests/unittests/resources/LangSuite/async-tests.ark @@ -9,7 +9,7 @@ (let size 1000) (let data (list:fill size 1)) -(let sum (fun (a b src) { +(let sum (fun ((mut a) b src) { (mut acc 0) (while (< a b) { (set acc (+ acc (@ src a))) diff --git a/tests/unittests/resources/LangSuite/vm-tests.ark b/tests/unittests/resources/LangSuite/vm-tests.ark index a03f5a669..0a80d170c 100644 --- a/tests/unittests/resources/LangSuite/vm-tests.ark +++ b/tests/unittests/resources/LangSuite/vm-tests.ark @@ -22,7 +22,7 @@ (mut val 1) (let parent (fun (&val &child) ())) -(let create-human (fun (name age) { +(let create-human (fun (name (mut age)) { (let set-age (fun (new-age) (set age new-age))) (fun (&set-age &name &age) ()) })) (let bob (create-human "Bob" 38)) diff --git a/tests/unittests/resources/ParserSuite/failure/incomplete_arg_attr.ark b/tests/unittests/resources/ParserSuite/failure/incomplete_arg_attr.ark new file mode 100644 index 000000000..da9795f7c --- /dev/null +++ b/tests/unittests/resources/ParserSuite/failure/incomplete_arg_attr.ark @@ -0,0 +1 @@ +(fun ((mut)) ()) diff --git a/tests/unittests/resources/ParserSuite/failure/incomplete_arg_attr.expected b/tests/unittests/resources/ParserSuite/failure/incomplete_arg_attr.expected new file mode 100644 index 000000000..428fe3468 --- /dev/null +++ b/tests/unittests/resources/ParserSuite/failure/incomplete_arg_attr.expected @@ -0,0 +1,5 @@ +In file tests/unittests/resources/ParserSuite/failure/incomplete_arg_attr.ark:1 + 1 | (fun ((mut)) ()) + | ^~~~ + 2 | + Expected a symbol name for the attribute with modifier `mut' diff --git a/tests/unittests/resources/ParserSuite/success/fun_arg_attributes.ark b/tests/unittests/resources/ParserSuite/success/fun_arg_attributes.ark new file mode 100644 index 000000000..6778c3794 --- /dev/null +++ b/tests/unittests/resources/ParserSuite/success/fun_arg_attributes.ark @@ -0,0 +1,8 @@ +(fun (a (mut b) (ref c)) ()) +(fun ((mut b) (ref c)) ()) +(fun ((ref c)) ()) +(fun ((mut c)) ()) +(fun (a &cap (mut b) (ref c)) ()) +(fun (&cap (mut b) (ref c)) ()) +(fun (&cap (ref c)) ()) +(fun (&cap (mut c)) ()) diff --git a/tests/unittests/resources/ParserSuite/success/fun_arg_attributes.expected b/tests/unittests/resources/ParserSuite/success/fun_arg_attributes.expected new file mode 100644 index 000000000..c2ac8c0d8 --- /dev/null +++ b/tests/unittests/resources/ParserSuite/success/fun_arg_attributes.expected @@ -0,0 +1,8 @@ +( Keyword:Fun ( Symbol:a MutArg:b RefArg:c ) Symbol:nil ) +( Keyword:Fun ( MutArg:b RefArg:c ) Symbol:nil ) +( Keyword:Fun ( RefArg:c ) Symbol:nil ) +( Keyword:Fun ( MutArg:c ) Symbol:nil ) +( Keyword:Fun ( Symbol:a Capture:cap MutArg:b RefArg:c ) Symbol:nil ) +( Keyword:Fun ( Capture:cap MutArg:b RefArg:c ) Symbol:nil ) +( Keyword:Fun ( Capture:cap RefArg:c ) Symbol:nil ) +( Keyword:Fun ( Capture:cap MutArg:c ) Symbol:nil ) diff --git a/tests/unittests/resources/RosettaSuite/call_an_object_method.ark b/tests/unittests/resources/RosettaSuite/call_an_object_method.ark index 7052c3c4b..974a26ce0 100644 --- a/tests/unittests/resources/RosettaSuite/call_an_object_method.ark +++ b/tests/unittests/resources/RosettaSuite/call_an_object_method.ark @@ -1,4 +1,4 @@ -(let create_user (fun (username password age) { +(let create_user (fun ((mut username) password age) { (let change_username (fun (new_name) (set username new_name))) (let check_password (fun (pass)