Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
- added in place list mutation: `(@= list|string index new_value)`, `(@@= list|list<string> index1 index2 new_value|char)` (bound checked)
- compile time argument count check for `and` and `or`
- basic dead code elimination in the AST optimizer
- new operator `@@` to get elements in list of lists / list of strings

### Changed
- instructions are on 4 bytes: 1 byte for the instruction, 1 byte of padding, 2 bytes for an immediate argument
Expand Down Expand Up @@ -99,6 +100,7 @@
- `string:find` takes an optional third argument, startIndex (where to start the lookup from, default 0
- `list:setAt` can work with negative indexes, and is now bound checked
- re-enabled the AST optimizer, only used for the main `arkscript` executable (not enabled when embedding arkscript, so that one can grab variables from the VM)
- loops have their own scope: variables created inside a loop won't leak outside it

### Removed
- removed unused `NodeType::Closure`
Expand Down
26 changes: 13 additions & 13 deletions examples/macros.ark
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,23 @@
(last 1 5 6 7 8)

{
(print "Testing macros in scopes and macro shadowing")
(print "Testing macros in scopes and macro shadowing")

($ test (+ 1 2 3))
(print "(global) Reading macro 'test', expected 6, " test)
($ test (+ 1 2 3))
(print "(global) Reading macro 'test', expected 6, " test)

((fun () {
($ test (- 1 2 3))
(print "(sub scope) Reading macro 'test', expected -4, " test) }))
((fun () {
($ test (- 1 2 3))
(print "(sub scope) Reading macro 'test', expected -4, " test) }))

(print "(global) Reading macro 'test', expected 6, " test)
(print "(global) Reading macro 'test', expected 6, " test)

{
($ test 555)
(print "(subscope) Reading macro 'test', expected 555, " test)
($ undef test)
(print "(subscope, undef test) Reading macro 'test', expected 6, " test)
($ undef a) } }
{
($ test 555)
(print "(subscope) Reading macro 'test', expected 555, " test)
($ undef test)
(print "(subscope, undef test) Reading macro 'test', expected 6, " test)
($ undef a) } }
(print "Demonstrating a threading macro")

($ -> (arg fn1 ...fn) {
Expand Down
4 changes: 2 additions & 2 deletions include/Ark/Compiler/Common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,13 @@ namespace Ark::internal
// This list is related to include/Ark/Compiler/Instructions.hpp
// from FIRST_OPERATOR, to LAST_OPERATOR
// The order is very important
constexpr std::array<std::string_view, 23> operators = {
constexpr std::array<std::string_view, 24> operators = {
"+", "-", "*", "/",
">", "<", "<=", ">=", "!=", "=",
"len", "empty?", "tail", "head",
"nil?", "assert",
"toNumber", "toString",
"@", "mod",
"@", "@@", "mod",
"type", "hasField",
"not"
};
Expand Down
9 changes: 9 additions & 0 deletions include/Ark/Compiler/Compiler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,15 @@ namespace Ark::internal
*/
static bool isUnaryInst(Instruction inst) noexcept;

/**
* @brief Check if a given instruction is ternary (takes three arguments)
*
* @param inst
* @return true the instruction is ternary
* @return false
*/
static bool isTernaryInst(Instruction inst) noexcept;

/**
* @brief Display a warning message
*
Expand Down
78 changes: 42 additions & 36 deletions include/Ark/Compiler/Instructions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,44 +61,47 @@ namespace Ark::internal
SET_AT_2_INDEX = 0x19,
POP = 0x1a,
DUP = 0x1b,
CREATE_SCOPE = 0x1c,
POP_SCOPE = 0x1d,

FIRST_OPERATOR = 0x1c,
ADD = 0x1c,
SUB = 0x1d,
MUL = 0x1e,
DIV = 0x1f,
GT = 0x20,
LT = 0x21,
LE = 0x22,
GE = 0x23,
NEQ = 0x24,
EQ = 0x25,
LEN = 0x26,
EMPTY = 0x27,
TAIL = 0x28,
HEAD = 0x29,
ISNIL = 0x2a,
ASSERT = 0x2b,
TO_NUM = 0x2c,
TO_STR = 0x2d,
AT = 0x2e,
MOD = 0x2f,
TYPE = 0x30,
HASFIELD = 0x31,
NOT = 0x32,
FIRST_OPERATOR = 0x1e,
ADD = 0x1e,
SUB = 0x1f,
MUL = 0x20,
DIV = 0x21,
GT = 0x22,
LT = 0x23,
LE = 0x24,
GE = 0x25,
NEQ = 0x26,
EQ = 0x27,
LEN = 0x28,
EMPTY = 0x29,
TAIL = 0x2a,
HEAD = 0x2b,
ISNIL = 0x2c,
ASSERT = 0x2d,
TO_NUM = 0x2e,
TO_STR = 0x2f,
AT = 0x30,
AT_AT = 0x31,
MOD = 0x32,
TYPE = 0x33,
HASFIELD = 0x34,
NOT = 0x35,

LOAD_CONST_LOAD_CONST = 0x33,
LOAD_CONST_STORE = 0x34,
LOAD_CONST_SET_VAL = 0x35,
STORE_FROM = 0x36,
SET_VAL_FROM = 0x37,
INCREMENT = 0x38,
DECREMENT = 0x39,
STORE_TAIL = 0x3a,
STORE_HEAD = 0x3b,
SET_VAL_TAIL = 0x3c,
SET_VAL_HEAD = 0x3d,
CALL_BUILTIN = 0x3e
LOAD_CONST_LOAD_CONST = 0x36,
LOAD_CONST_STORE = 0x37,
LOAD_CONST_SET_VAL = 0x38,
STORE_FROM = 0x39,
SET_VAL_FROM = 0x3a,
INCREMENT = 0x3b,
DECREMENT = 0x3c,
STORE_TAIL = 0x3d,
STORE_HEAD = 0x3e,
SET_VAL_TAIL = 0x3f,
SET_VAL_HEAD = 0x40,
CALL_BUILTIN = 0x41
};

constexpr std::array InstructionNames = {
Expand Down Expand Up @@ -130,6 +133,8 @@ namespace Ark::internal
"SET_AT_2_INDEX",
"POP",
"DUP",
"CREATE_SCOPE",
"POP_SCOPE",
// operators
"ADD",
"SUB",
Expand All @@ -150,6 +155,7 @@ namespace Ark::internal
"TO_NUM",
"TO_STR",
"AT",
"AT_AT",
"MOD",
"TYPE",
"HASFIELD",
Expand Down
27 changes: 25 additions & 2 deletions src/arkreactor/Compiler/Compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,18 @@ namespace Ark::internal
}
}

bool Compiler::isTernaryInst(const Instruction inst) noexcept
{
switch (inst)
{
case AT_AT:
return true;

default:
return false;
}
}

void Compiler::compilerWarning(const std::string& message, const Node& node)
{
fmt::println("{} {}", fmt::styled("Warning", fmt::fg(fmt::color::dark_orange)), Diagnostics::makeContextWithNode(message, node));
Expand Down Expand Up @@ -394,6 +406,8 @@ namespace Ark::internal
if (x.constList().size() != 3)
throwCompilerError("Invalid node ; if it was computed by a macro, check that a node is returned", x);

page(p).emplace_back(CREATE_SCOPE);

// save current position to jump there at the end of the loop
const auto label_loop = IR::Entity::Label(m_current_label++);
page(p).emplace_back(label_loop);
Expand All @@ -410,6 +424,8 @@ namespace Ark::internal

// absolute address to jump to if condition is false
page(p).emplace_back(label_end);

page(p).emplace_back(POP_SCOPE);
}

void Compiler::compilePluginImport(const Node& x, const Page p)
Expand Down Expand Up @@ -558,7 +574,7 @@ namespace Ark::internal

// in order to be able to handle things like (op A B C D...)
// which should be transformed into A B op C op D op...
if (exp_count >= 2)
if (exp_count >= 2 && !isTernaryInst(op))
page(p).emplace_back(op);
}

Expand All @@ -568,6 +584,12 @@ namespace Ark::internal
throwCompilerError(fmt::format("Operator needs one argument, but was called with {}", exp_count), x.constList()[0]);
page(p).emplace_back(op);
}
else if (isTernaryInst(op))
{
if (exp_count != 3)
throwCompilerError(fmt::format("Operator needs three arguments, but was called with {}", exp_count), x.constList()[0]);
page(p).emplace_back(op);
}
else if (exp_count <= 1)
throwCompilerError(fmt::format("Operator needs two arguments, but was called with {}", exp_count), x.constList()[0]);

Expand All @@ -581,7 +603,8 @@ namespace Ark::internal
case SUB: [[fallthrough]];
case MUL: [[fallthrough]];
case DIV: [[fallthrough]];
case MOD:
case MOD: [[fallthrough]];
case AT_AT:
break;

default:
Expand Down
9 changes: 9 additions & 0 deletions src/arkreactor/Compiler/NameResolution/NameResolutionPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,15 @@ namespace Ark::internal
m_plugin_names.push_back(node.constList()[1].constList().back().string());
break;

case Keyword::While:
// create a new scope to track variables
m_scope_resolver.createNew();
for (auto& child : node.list())
visit(child, register_declarations);
// remove the scope once the loop has been compiled, only we were registering declarations
m_scope_resolver.removeLastScope();
break;

case Keyword::Fun:
// create a new scope to track variables
m_scope_resolver.createNew();
Expand Down
60 changes: 60 additions & 0 deletions src/arkreactor/VM/VM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,8 @@ namespace Ark
&&TARGET_SET_AT_2_INDEX,
&&TARGET_POP,
&&TARGET_DUP,
&&TARGET_CREATE_SCOPE,
&&TARGET_POP_SCOPE,
&&TARGET_ADD,
&&TARGET_SUB,
&&TARGET_MUL,
Expand All @@ -402,6 +404,7 @@ namespace Ark
&&TARGET_TO_NUM,
&&TARGET_TO_STR,
&&TARGET_AT,
&&TARGET_AT_AT,
&&TARGET_MOD,
&&TARGET_TYPE,
&&TARGET_HASFIELD,
Expand Down Expand Up @@ -911,6 +914,18 @@ namespace Ark
DISPATCH();
}

TARGET(CREATE_SCOPE)
{
context.locals.emplace_back();
DISPATCH();
}

TARGET(POP_SCOPE)
{
context.locals.pop_back();
DISPATCH();
}

#pragma endregion

#pragma region "Operators"
Expand Down Expand Up @@ -1155,6 +1170,51 @@ namespace Ark
DISPATCH();
}

TARGET(AT_AT)
{
{
Value* x = popAndResolveAsPtr(context);
Value* y = popAndResolveAsPtr(context);
Value list = *popAndResolveAsPtr(context); // be careful, it's not a pointer

if (y->valueType() != ValueType::Number || x->valueType() != ValueType::Number ||
list.valueType() != ValueType::List)
types::generateError(
"@@",
{ { types::Contract {
{ types::Typedef("src", ValueType::List),
types::Typedef("y", ValueType::Number),
types::Typedef("x", ValueType::Number) } } } },
{ list, *y, *x });

long idx_y = static_cast<long>(y->number());
idx_y = idx_y < 0 ? static_cast<long>(list.list().size()) + idx_y : idx_y;
if (std::cmp_greater_equal(idx_y, list.list().size()))
throwVMError(
ErrorKind::Index,
fmt::format("@@ index ({}) out of range (list size: {})", idx_y, list.list().size()));

const bool is_list = list.list()[static_cast<std::size_t>(idx_y)].valueType() == ValueType::List;
const std::size_t size =
is_list
? list.list()[static_cast<std::size_t>(idx_y)].list().size()
: list.list()[static_cast<std::size_t>(idx_y)].stringRef().size();

long idx_x = static_cast<long>(x->number());
idx_x = idx_x < 0 ? static_cast<long>(size) + idx_x : idx_x;
if (std::cmp_greater_equal(idx_x, size))
throwVMError(
ErrorKind::Index,
fmt::format("@@ index (x: {}) out of range (inner indexable size: {})", idx_x, size));

if (is_list)
push(list.list()[static_cast<std::size_t>(idx_y)].list()[static_cast<std::size_t>(idx_x)], context);
else
push(Value(std::string(1, list.list()[static_cast<std::size_t>(idx_y)].stringRef()[static_cast<std::size_t>(idx_x)])), context);
}
DISPATCH();
}

TARGET(MOD)
{
Value *b = popAndResolveAsPtr(context), *a = popAndResolveAsPtr(context);
Expand Down
2 changes: 2 additions & 0 deletions tests/unittests/resources/CompilerSuite/ir/99bottles.expected
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ page_0
STORE 2
LOAD_SYMBOL 2
STORE 3
CREATE_SCOPE 0
.L4:
LOAD_SYMBOL 3
LOAD_CONST 0
Expand All @@ -50,4 +51,5 @@ page_0
POP 0
GOTO L4
.L5:
POP_SCOPE 0
HALT 0
2 changes: 2 additions & 0 deletions tests/unittests/resources/CompilerSuite/ir/factorial.expected
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ page_1
STORE 2
LOAD_CONST 2
STORE 3
CREATE_SCOPE 0
.L0:
LOAD_SYMBOL 3
LOAD_SYMBOL 1
Expand All @@ -30,6 +31,7 @@ page_1
SET_VAL 3
GOTO L0
.L1:
POP_SCOPE 0
LOAD_SYMBOL 2
RET 0
HALT 0
Loading
Loading