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::FeatureASTOptimizer; })
78 | option("-fno-optimizer").call([&] { passes &= ~Ark::FeatureASTOptimizer; })
79 ).doc("Toggle on and off the optimizer pass");
80 auto ir_optimizer_pass_flag = (
81 option("-firoptimizer").call([&] { passes |= Ark::FeatureIROptimizer; })
82 | option("-fno-iroptimizer").call([&] { passes &= ~Ark::FeatureIROptimizer; })
83 ).doc("Toggle on and off the IR optimizer pass");
84 auto vm_debugger_flag = (
85 option("-fdebugger").call([&] { passes |= Ark::FeatureVMDebugger; })
86 ).doc("Turn on the debugger");
87 auto ir_dump = option("-fdump-ir").call([&] { passes |= Ark::FeatureDumpIR; })
88 .doc("Dump IR to file.ark.ir");
89 auto no_cache = option("-fno-cache").call([&] { passes |= Ark::DisableCache; })
90 .doc("Disable the bytecode cache creation");
91
92 const auto run_flags = (
93 // cppcheck-suppress constStatement
94 debug_flag, lib_dir_flag, import_solver_pass_flag, macro_proc_pass_flag,
95 // cppcheck-suppress constStatement
96 optimizer_pass_flag, ir_optimizer_pass_flag, vm_debugger_flag, ir_dump,
97 no_cache
98 );
99
100 auto cli = (
101 option("-h", "--help").set(selected, mode::help).doc("Display this message")
102 | option("-v", "--version").set(selected, mode::version).doc("Display ArkScript version and exit")
103 | option("--dev-info").set(selected, mode::dev_info).doc("Display development information and exit")
104 | (
105 required("-e", "--eval").set(selected, mode::eval).doc("Evaluate ArkScript expression")
106 & value("expression", eval_expression)
107 )
108 | (
109 run_flags
110 , (
111 required("-c", "--compile").set(selected, mode::compile).doc("Compile the given program to bytecode, but do not run")
112 & value("file", file).doc("If file is -, it reads code from stdin")
113 )
114 | value("file", file).set(selected, mode::run)
115 )
116 | (
117 required("-f", "--format").set(selected, mode::format).doc("Format the given source file in place")
118 & value("file", file)
119 , (
120 option("--dry-run").set(format_dry_run, true).doc("Do not modify the file, only print out the changes")
121 | 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")
122 )
123 )
124 | (
125 debug_flag
126 , lib_dir_flag
127 , required("--ast").set(selected, mode::ast).doc("Compile the given program and output its AST as JSON to stdout")
128 & value("file", file)
129 )
130 | (
131 required("-bcr", "--bytecode-reader").set(selected, mode::bytecode_reader).doc("Launch the bytecode reader")
132 & value("file", file).doc(".arkc bytecode file or .ark source file that will be compiled first")
133 , (
134 option("-on", "--only-names").set(segment, Ark::BytecodeSegment::HeadersOnly).doc("Display only the bytecode segments names and sizes")
135 | (
136 (
137 option("-a", "--all").set(segment, Ark::BytecodeSegment::All).doc("Display all the bytecode segments (default)")
138 | option("-st", "--symbols").set(segment, Ark::BytecodeSegment::Symbols).doc("Display only the symbols table")
139 | option("-vt", "--values").set(segment, Ark::BytecodeSegment::Values).doc("Display only the values table")
140 | (
141 option("-cs", "--code").set(segment, Ark::BytecodeSegment::Code).doc("Display only the code segments")
142 , option("-p", "--page").set(segment, Ark::BytecodeSegment::Code).doc("Set the bytecode reader code segment to display")
143 & value("page", bcr_page)
144 )
145 )
146 , option("-s", "--slice").doc("Select a slice of instructions in the bytecode")
147 & value("start", bcr_start)
148 & value("end", bcr_end)
149 )
150 )
151 )
152 , any_other(script_args)
153 );
154 // clang-format on
155
156 auto fmt = doc_formatting {}
157 .first_column(8) // column where usage lines and documentation starts
158 .doc_column(36) // parameter docstring start col
159 .indent_size(2) // indent of documentation lines for children of a documented group
160 .split_alternatives(true) // split usage into several lines for large alternatives
161 .merge_alternative_flags_with_common_prefix(true) // [-fok] [-fno-ok] becomes [-f(ok|no-ok)]
162 .paragraph_spacing(1)
163 .ignore_newline_chars(false);
164 const auto man_page = make_man_page(cli, "arkscript", fmt)
165 .prepend_section("DESCRIPTION", " ArkScript programming language")
166 .append_section("VERSION", fmt::format(" {}", ARK_FULL_VERSION))
167 .append_section("BUILD DATE", fmt::format(" {}", ARK_BUILD_DATE))
168 .append_section("LICENSE", " Mozilla Public License 2.0");
169
170 if (auto result = parse(argc, argv, cli))
171 {
172 using namespace Ark;
173
174 std::vector<std::filesystem::path> lib_paths;
175 // if arkscript lib paths were provided by the CLI, bypass the automatic lookup
176 if (!libdir.empty())
177 {
178 std::ranges::transform(Utils::splitString(libdir, ';'), std::back_inserter(lib_paths), [](const std::string& path) {
179 return std::filesystem::path(path);
180 });
181 }
182 else
183 {
184 if (const char* arkpath = std::getenv("ARKSCRIPT_PATH"))
185 {
186 std::ranges::transform(Utils::splitString(arkpath, ';'), std::back_inserter(lib_paths), [](const std::string& path) {
187 return std::filesystem::path(path);
188 });
189 }
190 else if (Utils::fileExists("./lib") && Utils::fileExists("./lib/std/Prelude.ark"))
191 lib_paths.emplace_back("lib");
192 else if (debug > 0)
193 fmt::println(std::cerr, "{}: Couldn't read ARKSCRIPT_PATH environment variable", fmt::styled("Warning", fmt::fg(fmt::color::dark_orange)));
194 }
195
196 switch (selected)
197 {
198 case mode::help:
199 std::cout << man_page << std::endl;
200 break;
201
202 case mode::version:
203 fmt::println(ARK_FULL_VERSION);
204 break;
205
206 case mode::dev_info:
207 {
208 fmt::println("Compiler used: {}\n", ARK_COMPILER);
209 fmt::println("{:^34}|{:^8}|{:^10}", "Type", "SizeOf", "AlignOf");
210
211#define ARK_PRINT_SIZE(type) fmt::println("{:<34}| {:<7}| {:<9}", #type, sizeof(type), alignof(type))
212 ARK_PRINT_SIZE(char);
213
218 ARK_PRINT_SIZE(std::vector<Ark::Value>);
222
227
229#undef ARK_PRINT_SIZE
230 break;
231 }
232
233 case mode::repl:
234 {
235 Ark::Repl repl(lib_paths);
236 return repl.run();
237 }
238
239 case mode::compile:
240 {
241 Ark::State state(lib_paths);
242 state.setDebug(debug);
243
244 if (!state.doFile(file, passes))
245 return ArkErrorExitCode;
246 break;
247 }
248
249 case mode::run:
250 {
251 Ark::State state(lib_paths);
252 state.setDebug(debug);
253 state.setArgs(script_args);
254
255 if (file == "-")
256 {
257 std::string content(std::istreambuf_iterator<char>(std::cin), {});
258 if (!state.doString(content, passes))
259 return ArkErrorExitCode;
260 }
261 else if (!state.doFile(file, passes))
262 return ArkErrorExitCode;
263
264 Ark::VM vm(state);
265 return vm.run();
266 }
267
268 case mode::eval:
269 {
270 Ark::State state(lib_paths);
271 state.setDebug(debug);
272
273 if (!state.doString(eval_expression))
274 {
275 std::cerr << "Could not evaluate expression\n";
276 return ArkErrorExitCode;
277 }
278
279 Ark::VM vm(state);
280 return vm.run();
281 }
282
283 case mode::ast:
284 {
285 JsonCompiler compiler(debug, lib_paths);
286 compiler.feed(file);
287 fmt::println("{}", compiler.compile());
288 break;
289 }
290
291 case mode::bytecode_reader:
292 {
293 try
294 {
296 bcr.feed(file);
297 if (!bcr.checkMagic())
298 {
299 // we got a potentially non-compiled file
300 fmt::println("Compiling {}...", file);
301
302 Ark::Welder welder(debug, lib_paths);
303 welder.computeASTFromFile(file);
304 welder.generateBytecode();
305 bcr.feed(welder.bytecode());
306 }
307
308 if (bcr_page == Ark::MaxValue16Bits && bcr_start == Ark::MaxValue16Bits)
309 bcr.display(segment);
310 else if (bcr_page != Ark::MaxValue16Bits && bcr_start == Ark::MaxValue16Bits)
311 bcr.display(segment, std::nullopt, std::nullopt, bcr_page);
312 else if (bcr_page == Ark::MaxValue16Bits && bcr_start != Ark::MaxValue16Bits)
313 bcr.display(segment, bcr_start, bcr_end);
314 else
315 bcr.display(segment, bcr_start, bcr_end, bcr_page);
316 }
317 catch (const std::exception& e)
318 {
319 std::cerr << e.what() << std::endl;
320 return ArkErrorExitCode;
321 }
322 break;
323 }
324
325 case mode::format:
326 {
327 // dry run and check should not update the file
328 Formatter formatter(file, format_dry_run || format_check);
329 formatter.run();
330 if (format_dry_run)
331 fmt::println("{}", formatter.output());
332 if (formatter.codeModified())
333 return 1;
334 }
335 }
336 }
337 else
338 {
339 std::cerr << "Could not parse CLI arguments" << std::endl;
340
341 auto doc_label = [](const parameter& p) {
342 if (!p.flags().empty())
343 return p.flags().front();
344 if (!p.label().empty())
345 return p.label();
346 return doc_string { "<?>" };
347 };
348
349 std::cout << "args -> parameter mapping:\n";
350 for (const auto& m : result)
351 {
352 std::cout << "#" << m.index() << " " << m.arg() << " -> ";
353 if (const parameter* p = m.param(); p)
354 {
355 std::cout << doc_label(*p) << " \t";
356 if (m.repeat() > 0)
357 {
358 std::cout << (m.bad_repeat() ? "[bad repeat " : "[repeat ")
359 << m.repeat() << "]";
360 }
361 if (m.blocked())
362 std::cout << " [blocked]";
363 if (m.conflict())
364 std::cout << " [conflict]";
365 std::cout << '\n';
366 }
367 else
368 std::cout << " [unmapped]\n";
369 }
370
371 std::cout << "missing parameters:\n";
372 for (const auto& m : result.missing())
373 {
374 if (const parameter* p = m.param(); p)
375 {
376 std::cout << doc_label(*p) << " \t";
377 std::cout << " [missing after " << m.after_index() << "]\n";
378 }
379 }
380 }
381
382 return 0;
383}
A bytecode disassembler for ArkScript.
#define ARK_COMPILER
Definition Constants.hpp:25
#define ARK_BUILD_DATE
Definition Constants.hpp:26
constexpr std::string_view ARK_FULL_VERSION
Definition Constants.hpp:23
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:88
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:130
void setArgs(const std::vector< std::string > &args) noexcept
Set the script arguments in sys:args.
Definition State.cpp:153
void setDebug(unsigned level) noexcept
Set the debug level.
Definition State.cpp:163
A class to be use C++ objects in ArkScript.
Definition UserType.hpp:48
The ArkScript virtual machine, executing ArkScript bytecode.
Definition VM.hpp:48
int run(bool fail_with_exception=false)
Run the bytecode held in the state.
Definition VM.cpp:400
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:39
const bytecode_t & bytecode() const noexcept
Definition Welder.cpp:162
bool generateBytecode()
Compile the AST processed by computeASTFromFile / computeASTFromString.
Definition Welder.cpp:62
bool computeASTFromFile(const std::string &filename)
Definition Welder.cpp:38
Closure management.
Definition Closure.hpp:35
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:67
constexpr uint16_t FeatureImportSolver
Definition Constants.hpp:54
constexpr uint16_t FeatureIROptimizer
Definition Constants.hpp:57
constexpr uint16_t MaxValue16Bits
Definition Constants.hpp:73
constexpr uint16_t DisableCache
Definition Constants.hpp:60
constexpr uint16_t FeatureMacroProcessor
Definition Constants.hpp:55
constexpr uint16_t FeatureASTOptimizer
Disabled by default because embedding ArkScript should not prune nodes from the AST ; it is active in...
Definition Constants.hpp:56
constexpr uint16_t FeatureDumpIR
Definition Constants.hpp:62
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:59