Skip to content

Commit a63d477

Browse files
committed
feat(cli): ARK-336, accept '-' as a filename to mean 'read code from stdin'
1 parent 186e5a7 commit a63d477

4 files changed

Lines changed: 146 additions & 7 deletions

File tree

.github/workflows/ci.yml

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,92 @@ jobs:
160160
echo /tmp/ArkScript/lib; ls /tmp/ArkScript/lib
161161
DYLD_PRINT_LIBRARIES=1 /tmp/ArkScript/bin/arkscript -e '(import std.List) (print list:map " is a function")' || exit 1
162162
163+
cli:
164+
runs-on: ${{ matrix.os }}
165+
name: Test ArkScript CLI on ${{ matrix.os }}
166+
needs: [ check ]
167+
168+
strategy:
169+
fail-fast: false
170+
matrix:
171+
os:
172+
- ubuntu-24.04
173+
- macos-14
174+
175+
steps:
176+
- uses: actions/checkout@v5
177+
with:
178+
submodules: recursive
179+
180+
- name: Setup compilers, dependencies, project and build
181+
uses: ./.github/workflows/setup-compilers
182+
with:
183+
os_name: ${{ matrix.os }}
184+
compiler: clang
185+
compiler_version: 16
186+
sanitizers: "On"
187+
with_deps: false
188+
189+
- name: Execute script normally
190+
run: |
191+
export ARKSCRIPT_PATH=$(pwd)/lib
192+
CODE='(import std.Sys) (if (!= ["1" "2" "3"] sys:args) { (print sys:args) (sys:exit 1) })'
193+
echo $CODE > a.ark
194+
rm -rf __arkscript__
195+
196+
./build/arkscript a.ark 1 2 3
197+
# the cache should be created
198+
if ! [ -d __arkscript__ ]; then
199+
echo "Cache folder doesn't exist"
200+
exit 1
201+
fi
202+
203+
- name: -fno-cache should not create cache folder
204+
run: |
205+
export ARKSCRIPT_PATH=$(pwd)/lib
206+
CODE='(import std.Sys) (if (!= ["1" "2" "3"] sys:args) { (print sys:args) (sys:exit 1) })'
207+
echo $CODE > a.ark
208+
rm -rf __arkscript__
209+
210+
./build/arkscript -fno-cache a.ark 1 2 3
211+
# -fno-cache should prevent the cache creation
212+
if [ -d __arkscript__ ]; then
213+
echo "Cache folder exists"
214+
exit 1
215+
fi
216+
217+
- name: Take input from stdin
218+
run: |
219+
export ARKSCRIPT_PATH=$(pwd)/lib
220+
CODE='(import std.Sys) (if (!= ["1" "2" "3"] sys:args) { (print sys:args) (sys:exit 1) })'
221+
echo $CODE > a.ark
222+
rm -rf __arkscript__
223+
224+
./build/arkscript - 1 2 3 <a.ark
225+
# we read from stdin, there should be no cache created
226+
if [ -d __arkscript__ ]; then
227+
echo "Cache folder exists"
228+
exit 1
229+
fi
230+
231+
- name: Take input from CLI with heredoc
232+
run: |
233+
export ARKSCRIPT_PATH=$(pwd)/lib
234+
rm -rf __arkscript__
235+
236+
./build/arkscript - 1 2 3 << EOF
237+
(import std.Sys)
238+
(if (!= ["1" "2" "3"] sys:args)
239+
{
240+
(print sys:args)
241+
(sys:exit 1) })
242+
EOF
243+
# there should not be any cache generated when evaluating code from the CLI
244+
if [ -d __arkscript__ ]; then
245+
echo "Cache folder exists"
246+
exit 1
247+
fi
248+
163249
build:
164250
runs-on: ${{ matrix.config.os }}
165251
name: ${{ matrix.config.name }}

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- `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**
1111
- arguments of builtin macros are properly type-checked and will now raise runtime errors if the type is incorrect
1212
- `-fno-cache` cli option to disable the creation of the bytecode cache folder `__arkscript__`
13+
- in the CLI, `file` can be `-` to read code from stdin
1314

1415
## [4.2.0] - 2026-02-04
1516
### Breaking changes

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,6 @@ OPTIONS
195195
-v, --version Display ArkScript version and exit
196196
--dev-info Display development information and exit
197197
-e, --eval Evaluate ArkScript expression
198-
199198
-d, --debug... Increase debug level (default: 0)
200199
201200
-L, --lib Set the location of the ArkScript standard library. Paths can be
@@ -215,6 +214,7 @@ OPTIONS
215214
-fdump-ir Dump IR to file.ark.ir
216215
-fno-cache Disable the bytecode cache creation
217216
-c, --compile Compile the given program to bytecode, but do not run
217+
<file> If file is -, it reads code from stdin
218218
-f, --format Format the given source file in place
219219
--dry-run Do not modify the file, only print out the changes
220220
--check Check if a file formating is correctly, without modifying it.

src/arkscript/main.cpp

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,14 @@ int main(int argc, char** argv)
101101
| option("-v", "--version").set(selected, mode::version).doc("Display ArkScript version and exit")
102102
| option("--dev-info").set(selected, mode::dev_info).doc("Display development information and exit")
103103
| (
104-
required("-e", "--eval").set(selected, mode::eval).doc("Evaluate ArkScript expression\n")
104+
required("-e", "--eval").set(selected, mode::eval).doc("Evaluate ArkScript expression")
105105
& value("expression", eval_expression)
106106
)
107107
| (
108108
run_flags
109109
, (
110110
required("-c", "--compile").set(selected, mode::compile).doc("Compile the given program to bytecode, but do not run")
111-
& value("file", file)
111+
& value("file", file).doc("If file is -, it reads code from stdin")
112112
)
113113
| value("file", file).set(selected, mode::run)
114114
)
@@ -166,7 +166,7 @@ int main(int argc, char** argv)
166166
.append_section("BUILD DATE", fmt::format(" {}", ARK_BUILD_DATE))
167167
.append_section("LICENSE", " Mozilla Public License 2.0");
168168

169-
if (parse(argc, argv, cli))
169+
if (auto result = parse(argc, argv, cli))
170170
{
171171
using namespace Ark;
172172

@@ -186,9 +186,9 @@ int main(int argc, char** argv)
186186
return std::filesystem::path(path);
187187
});
188188
}
189-
else if (Utils::fileExists("./lib"))
189+
else if (Utils::fileExists("./lib") && Utils::fileExists("./lib/std/Prelude.ark"))
190190
lib_paths.emplace_back("lib");
191-
else
191+
else if (debug > 0)
192192
fmt::println("{}: Couldn't read ARKSCRIPT_PATH environment variable", fmt::styled("Warning", fmt::fg(fmt::color::dark_orange)));
193193
}
194194

@@ -251,7 +251,15 @@ int main(int argc, char** argv)
251251
state.setDebug(debug);
252252
state.setArgs(script_args);
253253

254-
if (!state.doFile(file, passes))
254+
if (file == "-")
255+
{
256+
std::string content, line;
257+
while (std::getline(std::cin, line))
258+
content += line;
259+
if (!state.doString(content, passes))
260+
return ArkErrorExitCode;
261+
}
262+
else if (!state.doFile(file, passes))
255263
return ArkErrorExitCode;
256264

257265
Ark::VM vm(state);
@@ -327,6 +335,50 @@ int main(int argc, char** argv)
327335
}
328336
}
329337
}
338+
else
339+
{
340+
std::cerr << "Could not parse CLI arguments" << std::endl;
341+
342+
auto doc_label = [](const parameter& p) {
343+
if (!p.flags().empty())
344+
return p.flags().front();
345+
if (!p.label().empty())
346+
return p.label();
347+
return doc_string { "<?>" };
348+
};
349+
350+
std::cout << "args -> parameter mapping:\n";
351+
for (const auto& m : result)
352+
{
353+
std::cout << "#" << m.index() << " " << m.arg() << " -> ";
354+
if (const parameter* p = m.param(); p)
355+
{
356+
std::cout << doc_label(*p) << " \t";
357+
if (m.repeat() > 0)
358+
{
359+
std::cout << (m.bad_repeat() ? "[bad repeat " : "[repeat ")
360+
<< m.repeat() << "]";
361+
}
362+
if (m.blocked())
363+
std::cout << " [blocked]";
364+
if (m.conflict())
365+
std::cout << " [conflict]";
366+
std::cout << '\n';
367+
}
368+
else
369+
std::cout << " [unmapped]\n";
370+
}
371+
372+
std::cout << "missing parameters:\n";
373+
for (const auto& m : result.missing())
374+
{
375+
if (const parameter* p = m.param(); p)
376+
{
377+
std::cout << doc_label(*p) << " \t";
378+
std::cout << " [missing after " << m.after_index() << "]\n";
379+
}
380+
}
381+
}
330382

331383
return 0;
332384
}

0 commit comments

Comments
 (0)