ArkScript
A small, lisp-inspired, functional scripting language
main.cpp
Go to the documentation of this file.
1#include <iostream>
2#include <optional>
3#include <filesystem>
4#include <cstdlib>
5
6#include <clipp.h>
7#include <fmt/core.h>
8#include <fmt/color.h>
9#include <fmt/ostream.h>
10
11#include <Ark/Utils/Files.hpp>
13#include <Ark/VM/Value/Dict.hpp>
14#include <CLI/JsonCompiler.hpp>
15#include <CLI/REPL/Repl.hpp>
16#include <CLI/Formatter.hpp>
17
18#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
19constexpr int ArkErrorExitCode = 0;
20#else
21constexpr int ArkErrorExitCode = -1;
22#endif
23
24int main(int argc, char** argv)
25{
26 using namespace clipp;
27
28 enum class mode
29 {
30 help,
31 dev_info,
32 bytecode_reader,
33 version,
34 run,
35 repl,
36 compile,
37 eval,
38 ast,
39 format
40 };
41 auto selected = mode::repl;
42
43 unsigned debug = 0;
44
45 // Bytecode reader
46 // by default, select all pages and segment types, without slicing anything
47 uint16_t bcr_page = Ark::MaxValue16Bits;
48 uint16_t bcr_start = Ark::MaxValue16Bits;
49 uint16_t bcr_end = Ark::MaxValue16Bits;
50 auto segment = Ark::BytecodeSegment::All;
51 // Eval / Run / AST dump
52 std::string file, eval_expression;
53 std::string libdir;
54 // Formatting
55 bool format_dry_run = false;
56 bool format_check = false;
57 // Generic arguments
58 std::vector<std::string> script_args;
59
61
62 // clang-format off
63 auto debug_flag = joinable(repeatable(option("-d", "--debug").call([&]{ debug++; })
64 .doc("Increase debug level (default: 0)\n")));
65 auto lib_dir_flag = option("-L", "--lib").doc("Set the location of the ArkScript standard library. Paths can be delimited by ';'\n")
66 & value("lib_dir", libdir);
67
68 auto import_solver_pass_flag = (
69 option("-fimportsolver").call([&] { passes |= Ark::FeatureImportSolver; })
70 | option("-fno-importsolver").call([&] { passes &= ~Ark::FeatureImportSolver; })
71 ).doc("Toggle on and off the import solver pass");
72 auto macro_proc_pass_flag = (
73 option("-fmacroprocessor").call([&] { passes |= Ark::FeatureMacroProcessor; })
74 | option("-fno-macroprocessor").call([&] { passes &= ~Ark::FeatureMacroProcessor; })
75 ).doc("Toggle on and off the macro processor pass");
76 auto optimizer_pass_flag = (
77 option("-foptimizer").call([&] { passes |= Ark::FeatureASTOptimiser; })
78 | option("-fno-optimizer").call([&] { passes &= ~Ark::FeatureASTOptimiser; })
79 ).doc("Toggle on and off the optimizer pass");
80 auto ir_inliner_pass_flag = (
81 option("-firinliner").call([&] { passes |= Ark::FeatureIRInliner; })
82 | option("-fno-irinliner").call([&] { passes &= ~Ark::FeatureIRInliner; })
83 ).doc("Toggle on and off the IR inliner pass");
84 auto ir_optimizer_pass_flag = (
85 option("-firoptimizer").call([&] { passes |= Ark::FeatureIROptimiser; })
86 | option("-fno-iroptimizer").call([&] { passes &= ~Ark::FeatureIROptimiser; })
87 ).doc("Toggle on and off the IR optimizer pass");
88 auto vm_debugger_flag = (
89 option("-fdebugger").call([&] { passes |= Ark::FeatureVMDebugger; })
90 ).doc("Turn on the debugger");
91 auto ir_dump = option("-fdump-ir").call([&] { passes |= Ark::FeatureDumpIR; })
92 .doc("Dump IR to file.ark.ir");
93 auto no_cache = option("-fno-cache").call([&] { passes |= Ark::DisableCache; })
94 .doc("Disable the bytecode cache creation");
95
96 const auto run_flags = (
97 // cppcheck-suppress constStatement
98 debug_flag, lib_dir_flag, import_solver_pass_flag, macro_proc_pass_flag,
99 // cppcheck-suppress constStatement
100 optimizer_pass_flag, ir_inliner_pass_flag, ir_optimizer_pass_flag,
101 // cppcheck-suppress constStatement
102 vm_debugger_flag, ir_dump,
103 no_cache
104 );
105
106 auto cli = (
107 option("-h", "--help").set(selected, mode::help).doc("Display this message")
108 | option("-v", "--version").set(selected, mode::version).doc("Display ArkScript version and exit")
109 | option("--dev-info").set(selected, mode::dev_info).doc("Display development information and exit")
110 | (
111 required("-e", "--eval").set(selected, mode::eval).doc("Evaluate ArkScript expression")
112 & value("expression", eval_expression)
113 )
114 | (
115 run_flags
116 , (
117 required("-c", "--compile").set(selected, mode::compile).doc("Compile the given program to bytecode, but do not run")
118 & value("file", file).doc("If file is -, it reads code from stdin")
119 )
120 | value("file", file).set(selected, mode::run)
121 )
122 | (
123 required("-f", "--format").set(selected, mode::format).doc("Format the given source file in place")
124 & value("file", file)
125 , (
126 option("--dry-run").set(format_dry_run, true).doc("Do not modify the file, only print out the changes")
127 | option("--check").set(format_check, true).doc("Check if a file formating is correctly, without modifying it. Return 1 if formating is needed, 0 otherwise")
128 )
129 )
130 | (
131 debug_flag
132 , lib_dir_flag
133 , required("--ast").set(selected, mode::ast).doc("Compile the given program and output its AST as JSON to stdout")
134 & value("file", file)
135 )
136 | (
137 required("-bcr", "--bytecode-reader").set(selected, mode::bytecode_reader).doc("Launch the bytecode reader")
138 & value("file", file).doc(".arkc bytecode file or .ark source file that will be compiled first")
139 , (
140 option("-on", "--only-names").set(segment, Ark::BytecodeSegment::HeadersOnly).doc("Display only the bytecode segments names and sizes")
141 | (
142 (
143 option("-a", "--all").set(segment, Ark::BytecodeSegment::All).doc("Display all the bytecode segments (default)")
144 | option("-st", "--symbols").set(segment, Ark::BytecodeSegment::Symbols).doc("Display only the symbols table")
145 | option("-vt", "--values").set(segment, Ark::BytecodeSegment::Values).doc("Display only the values table")
146 | (
147 option("-cs", "--code").set(segment, Ark::BytecodeSegment::Code).doc("Display only the code segments")
148 , option("-p", "--page").set(segment, Ark::BytecodeSegment::Code).doc("Set the bytecode reader code segment to display")
149 & value("page", bcr_page)
150 )
151 )
152 , option("-s", "--slice").doc("Select a slice of instructions in the bytecode")
153 & value("start", bcr_start)
154 & value("end", bcr_end)
155 )
156 )
157 )
158 , any_other(script_args)
159 );
160 // clang-format on
161
162 auto fmt = doc_formatting {}
163 .first_column(8) // column where usage lines and documentation starts
164 .doc_column(36) // parameter docstring start col
165 .indent_size(2) // indent of documentation lines for children of a documented group
166 .split_alternatives(true) // split usage into several lines for large alternatives
167 .merge_alternative_flags_with_common_prefix(true) // [-fok] [-fno-ok] becomes [-f(ok|no-ok)]
168 .paragraph_spacing(1)
169 .ignore_newline_chars(false);
170 const auto man_page = make_man_page(cli, "arkscript", fmt)
171 .prepend_section("DESCRIPTION", " ArkScript programming language")
172 .append_section("VERSION", fmt::format(" {}", ARK_FULL_VERSION))
173 .append_section("BUILD DATE", fmt::format(" {}", ARK_BUILD_DATE))
174 .append_section("LICENSE", " Mozilla Public License 2.0");
175
176 if (auto result = parse(argc, argv, cli))
177 {
178 using namespace Ark;
179
180 std::vector<std::filesystem::path> lib_paths;
181 // if arkscript lib paths were provided by the CLI, bypass the automatic lookup
182 if (!libdir.empty())
183 {
184 std::ranges::transform(Utils::splitString(libdir, ';'), std::back_inserter(lib_paths), [](const std::string& path) {
185 return std::filesystem::path(path);
186 });
187 }
188 else
189 {
190 if (const char* arkpath = std::getenv("ARKSCRIPT_PATH"))
191 {
192 std::ranges::transform(Utils::splitString(arkpath, ';'), std::back_inserter(lib_paths), [](const std::string& path) {
193 return std::filesystem::path(path);
194 });
195 }
196 else if (Utils::fileExists("./lib") && Utils::fileExists("./lib/std/Prelude.ark"))
197 lib_paths.emplace_back("lib");
198 else if (!DefaultLibFolder.empty() && Utils::fileExists(std::string(DefaultLibFolder) + "/std/Prelude.ark"))
199 lib_paths.emplace_back(DefaultLibFolder);
200 else if (debug > 0)
201 fmt::println(std::cerr, "{}: Couldn't read ARKSCRIPT_PATH environment variable", fmt::styled("Warning", fmt::fg(fmt::color::dark_orange)));
202 }
203
204 switch (selected)
205 {
206 case mode::help:
207 std::cout << man_page << std::endl;
208 break;
209
210 case mode::version:
211 fmt::println(ARK_FULL_VERSION);
212 break;
213
214 case mode::dev_info:
215 {
216 fmt::println("Compiler used: {}\n", ARK_COMPILER);
217 fmt::println("{:^34}|{:^8}|{:^10}", "Type", "SizeOf", "AlignOf");
218
219#define ARK_PRINT_SIZE(type) fmt::println("{:<34}| {:<7}| {:<9}", #type, sizeof(type), alignof(type))
220 ARK_PRINT_SIZE(char);
221
226 ARK_PRINT_SIZE(std::vector<Ark::Value>);
230
235
237#undef ARK_PRINT_SIZE
238 break;
239 }
240
241 case mode::repl:
242 {
243 Ark::Repl repl(lib_paths);
244 return repl.run();
245 }
246
247 case mode::compile:
248 {
249 Ark::State state(lib_paths);
250 state.setDebug(debug);
251
252 if (!state.doFile(file, passes))
253 return ArkErrorExitCode;
254 break;
255 }
256
257 case mode::run:
258 {
259 Ark::State state(lib_paths);
260 state.setDebug(debug);
261 state.setArgs(script_args);
262
263 if (file == "-")
264 {
265 std::string content(std::istreambuf_iterator<char>(std::cin), {});
266 if (!state.doString(content, passes))
267 return ArkErrorExitCode;
268 }
269 else if (!state.doFile(file, passes))
270 return ArkErrorExitCode;
271
272 Ark::VM vm(state);
273 return vm.run();
274 }
275
276 case mode::eval:
277 {
278 Ark::State state(lib_paths);
279 state.setDebug(debug);
280
281 if (!state.doString(eval_expression))
282 {
283 std::cerr << "Could not evaluate expression\n";
284 return ArkErrorExitCode;
285 }
286
287 Ark::VM vm(state);
288 return vm.run();
289 }
290
291 case mode::ast:
292 {
293 JsonCompiler compiler(debug, lib_paths);
294 compiler.feed(file);
295 fmt::println("{}", compiler.compile());
296 break;
297 }
298
299 case mode::bytecode_reader:
300 {
301 try
302 {
304 bcr.feed(file);
305 if (!bcr.checkMagic())
306 {
307 // we got a potentially non-compiled file
308 fmt::println("Compiling {}...", file);
309
310 Ark::Welder welder(debug, lib_paths);
311 welder.computeASTFromFile(file);
312 welder.generateBytecode();
313 bcr.feed(welder.bytecode());
314 }
315
316 if (bcr_page == Ark::MaxValue16Bits && bcr_start == Ark::MaxValue16Bits)
317 bcr.display(segment);
318 else if (bcr_page != Ark::MaxValue16Bits && bcr_start == Ark::MaxValue16Bits)
319 bcr.display(segment, std::nullopt, std::nullopt, bcr_page);
320 else if (bcr_page == Ark::MaxValue16Bits && bcr_start != Ark::MaxValue16Bits)
321 bcr.display(segment, bcr_start, bcr_end);
322 else
323 bcr.display(segment, bcr_start, bcr_end, bcr_page);
324 }
325 catch (const std::exception& e)
326 {
327 std::cerr << e.what() << std::endl;
328 return ArkErrorExitCode;
329 }
330 break;
331 }
332
333 case mode::format:
334 {
335 // dry run and check should not update the file
336 Formatter formatter(file, format_dry_run || format_check);
337 formatter.run();
338 if (format_dry_run)
339 fmt::println("{}", formatter.output());
340 if (formatter.codeModified())
341 return 1;
342 }
343 }
344 }
345 else
346 {
347 std::cerr << "Could not parse CLI arguments" << std::endl;
348
349 auto doc_label = [](const parameter& p) {
350 if (!p.flags().empty())
351 return p.flags().front();
352 if (!p.label().empty())
353 return p.label();
354 return doc_string { "<?>" };
355 };
356
357 std::cout << "args -> parameter mapping:\n";
358 for (const auto& m : result)
359 {
360 std::cout << "#" << m.index() << " " << m.arg() << " -> ";
361 if (const parameter* p = m.param(); p)
362 {
363 std::cout << doc_label(*p) << " \t";
364 if (m.repeat() > 0)
365 {
366 std::cout << (m.bad_repeat() ? "[bad repeat " : "[repeat ")
367 << m.repeat() << "]";
368 }
369 if (m.blocked())
370 std::cout << " [blocked]";
371 if (m.conflict())
372 std::cout << " [conflict]";
373 std::cout << '\n';
374 }
375 else
376 std::cout << " [unmapped]\n";
377 }
378
379 std::cout << "missing parameters:\n";
380 for (const auto& m : result.missing())
381 {
382 if (const parameter* p = m.param(); p)
383 {
384 std::cout << doc_label(*p) << " \t";
385 std::cout << " [missing after " << m.after_index() << "]\n";
386 }
387 }
388 }
389
390 return 0;
391}
A bytecode disassembler for ArkScript.
#define ARK_COMPILER
Definition Constants.hpp:31
#define ARK_BUILD_DATE
Definition Constants.hpp:32
constexpr std::string_view ARK_FULL_VERSION
Definition Constants.hpp:28
Define how dictionaries are handled.
Lots of utilities about the filesystem.
ArkScript REPL - Read Eval Print Loop.
int main()
Definition main.cpp:24
#define ARK_PRINT_SIZE(type)
constexpr int ArkErrorExitCode
Definition main.cpp:21
This class is just a helper to.
void display(BytecodeSegment segment=BytecodeSegment::All, std::optional< uint16_t > sStart=std::nullopt, std::optional< uint16_t > sEnd=std::nullopt, std::optional< uint16_t > cPage=std::nullopt) const
Display the bytecode opcode in a human friendly way.
void feed(const std::string &file)
Construct needed data before displaying information about a given file.
Storage class to hold custom functions.
Definition Procedure.hpp:26
int run()
Start the REPL.
Definition Repl.cpp:29
Ark state to handle the dirty job of loading and compiling ArkScript code.
Definition State.hpp:38
bool doFile(const std::string &file_path, uint16_t features=DefaultFeatures, std::ostream *stream=nullptr)
Compile a file, and use the resulting bytecode.
Definition State.cpp:87
bool doString(const std::string &code, uint16_t features=DefaultFeatures, std::ostream *stream=nullptr)
Compile a string (representing ArkScript code) and store resulting bytecode in m_bytecode.
Definition State.cpp:129
void setArgs(const std::vector< std::string > &args) noexcept
Set the script arguments in sys:args.
Definition State.cpp:152
void setDebug(unsigned level) noexcept
Set the debug level.
Definition State.cpp:162
A class to be use C++ objects in ArkScript.
Definition UserType.hpp:48
The ArkScript virtual machine, executing ArkScript bytecode.
Definition VM.hpp:40
int run(bool fail_with_exception=false)
Run the bytecode held in the state.
Definition VM.cpp:394
std::variant< Number_t, String_t, internal::PageAddr_t, Procedure, internal::Closure, UserType, List_t, std::shared_ptr< Dict_t >, Ref_t > Value_t
Definition Value.hpp:101
The welder joins all the compiler passes.
Definition Welder.hpp:40
const bytecode_t & bytecode() const noexcept
Definition Welder.cpp:170
bool generateBytecode()
Compile the AST processed by computeASTFromFile / computeASTFromString.
Definition Welder.cpp:63
bool computeASTFromFile(const std::string &filename)
Definition Welder.cpp:40
Closure management.
Definition Closure.hpp:36
A node of an Abstract Syntax Tree for ArkScript.
Definition Node.hpp:32
A class to handle the VM scope more efficiently.
Definition ScopeView.hpp:27
void run()
Read the file and process it. The file isn't modified.
Definition Formatter.cpp:24
bool codeModified() const
Definition Formatter.cpp:62
const std::string & output() const
Definition Formatter.cpp:57
void feed(const std::string &filename)
Feed the different variables with information taken from the given source code file.
std::string compile()
Start the compilation.
constexpr uint16_t DefaultFeatures
Definition Constants.hpp:74
constexpr uint16_t FeatureImportSolver
Definition Constants.hpp:60
constexpr uint16_t MaxValue16Bits
Definition Constants.hpp:81
constexpr uint16_t FeatureASTOptimiser
Disabled by default because embedding ArkScript should not prune nodes from the AST ; it is active in...
Definition Constants.hpp:62
constexpr uint16_t DisableCache
Definition Constants.hpp:67
constexpr uint16_t FeatureIROptimiser
Definition Constants.hpp:63
constexpr uint16_t FeatureMacroProcessor
Definition Constants.hpp:61
constexpr uint16_t FeatureIRInliner
Definition Constants.hpp:64
constexpr uint16_t FeatureDumpIR
Definition Constants.hpp:69
ValueType
Definition Value.hpp:32
constexpr uint16_t FeatureVMDebugger
Disabled by default because embedding ArkScript should not launch the debugger on every error when ru...
Definition Constants.hpp:66