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
86 changes: 86 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,92 @@ jobs:
echo /tmp/ArkScript/lib; ls /tmp/ArkScript/lib
DYLD_PRINT_LIBRARIES=1 /tmp/ArkScript/bin/arkscript -e '(import std.List) (print list:map " is a function")' || exit 1

cli:
runs-on: ${{ matrix.os }}
name: Test ArkScript CLI on ${{ matrix.os }}
needs: [ check ]

strategy:
fail-fast: false
matrix:
os:
- ubuntu-24.04
- macos-14

steps:
- uses: actions/checkout@v5
with:
submodules: recursive

- name: Setup compilers, dependencies, project and build
uses: ./.github/workflows/setup-compilers
with:
os_name: ${{ matrix.os }}
compiler: clang
compiler_version: 16
sanitizers: "On"
with_deps: false

- name: Execute script normally
run: |
export ARKSCRIPT_PATH=$(pwd)/lib
CODE='(import std.Sys) (if (!= ["1" "2" "3"] sys:args) { (print sys:args) (sys:exit 1) })'
echo $CODE > a.ark
rm -rf __arkscript__

./build/arkscript a.ark 1 2 3
# the cache should be created
if ! [ -d __arkscript__ ]; then
echo "Cache folder doesn't exist"
exit 1
fi

- name: -fno-cache should not create cache folder
run: |
export ARKSCRIPT_PATH=$(pwd)/lib
CODE='(import std.Sys) (if (!= ["1" "2" "3"] sys:args) { (print sys:args) (sys:exit 1) })'
echo $CODE > a.ark
rm -rf __arkscript__

./build/arkscript -fno-cache a.ark 1 2 3
# -fno-cache should prevent the cache creation
if [ -d __arkscript__ ]; then
echo "Cache folder exists"
exit 1
fi

- name: Take input from stdin
run: |
export ARKSCRIPT_PATH=$(pwd)/lib
CODE='(import std.Sys) (if (!= ["1" "2" "3"] sys:args) { (print sys:args) (sys:exit 1) })'
echo $CODE > a.ark
rm -rf __arkscript__

./build/arkscript - 1 2 3 <a.ark
# we read from stdin, there should be no cache created
if [ -d __arkscript__ ]; then
echo "Cache folder exists"
exit 1
fi

- name: Take input from CLI with heredoc
run: |
export ARKSCRIPT_PATH=$(pwd)/lib
rm -rf __arkscript__

./build/arkscript - 1 2 3 << EOF
(import std.Sys)
(if (!= ["1" "2" "3"] sys:args)
{
(print sys:args)
(sys:exit 1) })
EOF
# there should not be any cache generated when evaluating code from the CLI
if [ -d __arkscript__ ]; then
echo "Cache folder exists"
exit 1
fi

build:
runs-on: ${{ matrix.config.os }}
name: ${{ matrix.config.name }}
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
# Change Log

## [Unreleased changes] - 2026-XX-YY
### Breaking change
- in macros, `len`, `empty?`, `head`, `tail`, `@` have been renamed to `$len`, `$empty?`, `$head`, `$tail` and `$at`. Those versions only work inside macros too, inside of having a weird dichotomy where they sometimes got applied and sometimes not

### Added
- `apply` function: `(apply func [args...])`, to call a function with a set of arguments stored in a list. Works with functions, closures and builtins
- `+`, `-`, `*`, `/` and many other operators can now be passed around, like builtins. This now works: `(list:reduce [1 2 3] +)`, where before we would get a compile time error about a "freestanding operator '+'"
- `builtin__slice` builtin, for strings and lists: `(builtin__slice data start end [step=1])` ; **this is an experimentation and may be removed in future versions**
- arguments of builtin macros are properly type-checked and will now raise runtime errors if the type is incorrect
- `-fno-cache` cli option to disable the creation of the bytecode cache folder `__arkscript__`
- in the CLI, `file` can be `-` to read code from stdin

## [4.2.0] - 2026-02-04
### Breaking changes
Expand Down
34 changes: 20 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,28 +173,28 @@ DESCRIPTION
ArkScript programming language

SYNOPSIS
arkscript -h
arkscript -v
arkscript --dev-info
arkscript -e <expression>
arkscript -h
arkscript -v
arkscript --dev-info
arkscript -e <expression>
arkscript [-d] [-L <lib_dir>] [-f(importsolver|no-importsolver)]
[-f(macroprocessor|no-macroprocessor)] [-f(optimizer|no-optimizer)]
[-f(iroptimizer|no-iroptimizer)] [-fdump-ir] ((-c <file>) | (<file> ))
[-f(iroptimizer|no-iroptimizer)] [-fdebugger] [-fdump-ir] [-fno-cache] ((-c
<file>) | <file>)

arkscript -f <file> [--(dry-run|check)]
arkscript [-d] [-L <lib_dir>] --ast <file>
arkscript -bcr <file> -on
arkscript -bcr <file> -a [-s <start> <end>]
arkscript -bcr <file> -st [-s <start> <end>]
arkscript -bcr <file> -vt [-s <start> <end>]
arkscript -bcr <file> [-cs] [-p <page>] [-s <start> <end>]
arkscript -f <file> [--(dry-run|check)]
arkscript [-d] [-L <lib_dir>] --ast <file>
arkscript -bcr <file> -on
arkscript -bcr <file> -a [-s <start> <end>]
arkscript -bcr <file> -st [-s <start> <end>]
arkscript -bcr <file> -vt [-s <start> <end>]
arkscript -bcr <file> [-cs] [-p <page>] [-s <start> <end>]

OPTIONS
-h, --help Display this message
-v, --version Display ArkScript version and exit
--dev-info Display development information and exit
-e, --eval Evaluate ArkScript expression

-d, --debug... Increase debug level (default: 0)

-L, --lib Set the location of the ArkScript standard library. Paths can be
Expand All @@ -210,8 +210,11 @@ OPTIONS
-f(iroptimizer|no-iroptimizer)
Toggle on and off the IR optimizer pass

-fdebugger Turn on the debugger
-fdump-ir Dump IR to file.ark.ir
-fno-cache Disable the bytecode cache creation
-c, --compile Compile the given program to bytecode, but do not run
<file> If file is -, it reads code from stdin
-f, --format Format the given source file in place
--dry-run Do not modify the file, only print out the changes
--check Check if a file formating is correctly, without modifying it.
Expand All @@ -236,7 +239,10 @@ OPTIONS
-s, --slice Select a slice of instructions in the bytecode

VERSION
4.0.0-32c501fb
4.2.0-94e546d6

BUILD DATE
2026-02-21T20:42:38Z

LICENSE
Mozilla Public License 2.0
Expand Down
124 changes: 124 additions & 0 deletions docs/arkdoc/Macros.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
--#
* @name $undef
* @brief Delete a given macro in the nearest scope
* @param name macro name
* =begin
* (macro a 5)
* ($undef a)
* (print a) # will fail, as 'a' doesn't exist anymore
* =end
#--

--#
* @name $argcount
* @brief Retrieve at compile time the number of arguments taken by a given function.
* @details The function must have been defined before using `$argcount`, or must be an anonymous function: `($argcount (fun (a b c) ()))`, `($argcount my-function)`.
* @param node
* =begin
* (let foo (fun (a b) (+ a b)))
* (print ($argcount foo)) # 2
* =end
#--

--#
* @name $symcat
* @brief Create a new symbol by concatenating a symbol with numbers, strings and/or other symbols
* @param symbol
* @param args... numbers, strings or symbols
* =begin
* (macro foo () (let ($symcat a 5) 6))
* (foo)
* (print a5)
* =end
#--

--#
* @name $repr
* @brief Return the AST representation of a given node, as a string.
* @details Indentation, newlines and comments are not preserved.
* @param node
* =begin
* ($repr foobar) # will return "foobar"
* ($repr (fun () (+ 1 2 3))) # will return "(fun () (+ 1 2 3))"
* =end
#--

--#
* @name $as-is
* @brief Use a given node as it is, without evaluating it any further in the macro context. Useful to stop the evaluation of arguments passed to a function macro.
* @param node
#--

--#
* @name $type
* @brief Return the type of a given node, as a string.
* @param node
* =begin
* (print ($type foobar)) # Symbol
* (print ($type (fun () (+ 1 2 3)))) # List
* =end
#--

--#
* @name $len
* @brief Return the length of a node
* @param node
* =begin
* (macro -> (arg fn1 ...fn) {
* # we use $len to check if we have more functions to apply
* ($if (> ($len fn) 0)
* (-> (fn1 arg) ...fn)
* (fn1 arg)) })
*
* (macro foo () ($len (fun () ())))
* (print (foo)) # 3
* =end
#--

--#
* @name $empty?
* @brief Check if a node is empty. An empty list, `[]` or `(list)`, is considered empty.
* @param node
* =begin
* (macro not_empty_node () ($empty? (fun () ())))
* (print (not_empty_node)) # false
* =end
#--

--#
* @name $head
* @brief Return the head node in a list of nodes. The head of a `[1 2 3]` / `(list 1 2 3)` disregards the `list` and returns 1.
* @param node
* =begin
* (macro h (...args) ($head args))
* (print (h)) # nil
* (print (h 1)) # 1
* (print (h 1 2)) # 1
* =end
#--

--#
* @name $tail
* @brief Return the tails nodes in a list of nodes, as a `(list ...)`
* @param node
* =begin
* (macro g (...args) ($tail args))
* (print (g)) # []
* (print (g 1)) # []
* (print (g 1 2)) # [2]
* (print (g 1 2 3)) # [2 3]
* =end
#--

--#
* @name $at
* @brief Return the node at a given index in a list of nodes
* @param node
* @param index must be a number
* =begin
* (macro one (...args) ($at args 1))
* (print (one 1 2)) # 2
* (print (one 1 3 4)) # 3
* (print (one 1 5 6 7 8)) # 5
* =end
#--
4 changes: 2 additions & 2 deletions examples/macros.ark
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
($symcat sym x) })

(macro partial (func ...defargs) {
(macro bloc (suffix-dup a (- ($argcount func) (len defargs))))
(macro bloc (suffix-dup a (- ($argcount func) ($len defargs))))
(fun (bloc) (func ...defargs bloc))
($undef bloc) })

Expand Down Expand Up @@ -65,7 +65,7 @@
(print "Demonstrating a threading macro")

(macro -> (arg fn1 ...fn) {
($if (> (len fn) 0)
($if (> ($len fn) 0)
(-> (fn1 arg) ...fn)
(fn1 arg)) })

Expand Down
2 changes: 2 additions & 0 deletions include/Ark/Builtins/Builtins.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ namespace Ark::internal::Builtins
ARK_BUILTIN(disassemble);
}

ARK_BUILTIN(slice);

namespace Operators
{
ARK_BUILTIN(add);
Expand Down
17 changes: 17 additions & 0 deletions include/Ark/Compiler/Lowerer/ASTLowerer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ namespace Ark::internal

Logger m_logger;

enum class ErrorKind
{
InvalidNodeMacro,
InvalidNodeNoReturnValue,
InvalidNodeInOperatorNoReturnValue,
InvalidNodeInTailCallNoReturnValue
};

Page createNewCodePage(const bool temp = false) noexcept
{
if (!temp)
Expand Down Expand Up @@ -226,6 +234,15 @@ namespace Ark::internal
*/
[[noreturn]] static void buildAndThrowError(const std::string& message, const Node& node);

/**
* @brief Throw a nice error message, using a message builder
*
* @param kind error kind
* @param node erroneous node
* @param additional_ctx optional context for the error, e.g. the macro name
*/
static void makeError(ErrorKind kind, const Node& node, const std::string& additional_ctx);

/**
* @brief Compile an expression (a node) recursively
*
Expand Down
3 changes: 2 additions & 1 deletion include/Ark/Compiler/Macros/Executor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,9 @@ namespace Ark::internal
* Proxy function for MacroProcessor::handleMacroNode
*
* @param node A node of type Macro
* @param depth
*/
void handleMacroNode(Node& node) const;
void handleMacroNode(Node& node, unsigned depth) const;

/**
* @brief Check if a node can be evaluated to true
Expand Down
Loading
Loading