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 <limits>
5#include <cstdlib>
6
7#include <clipp.h>
8#include <fmt/core.h>
9#include <fmt/color.h>
10
11#include <Ark/Files.hpp>
13#include <CLI/JsonCompiler.hpp>
14#include <CLI/REPL/Repl.hpp>
15#include <CLI/Formatter.hpp>
16
17#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
18# define ARK_ERROR_EXIT_CODE 0
19#else
20# define ARK_ERROR_EXIT_CODE (-1)
21#endif
22
23int main(int argc, char** argv)
24{
25 using namespace clipp;
26
27 enum class mode
28 {
29 help,
30 dev_info,
31 bytecode_reader,
32 version,
33 run,
34 repl,
35 compile,
36 eval,
37 ast,
38 format
39 };
40 auto selected = mode::repl;
41
42 unsigned debug = 0;
43
44 constexpr uint16_t max_uint16 = std::numeric_limits<uint16_t>::max();
45
46 // Bytecode reader
47 // by default, select all pages and segment types, without slicing anything
48 uint16_t bcr_page = max_uint16;
49 uint16_t bcr_start = max_uint16;
50 uint16_t bcr_end = max_uint16;
51 auto segment = Ark::BytecodeSegment::All;
52 // Eval / Run / AST dump
53 std::string file, eval_expression;
54 std::string libdir;
55 // Formatting
56 bool format_dry_run = false;
57 bool format_check = false;
58 // Generic arguments
59 std::vector<std::string> wrong, script_args;
60
62
63 // clang-format off
64 auto debug_flag = joinable(repeatable(option("-d", "--debug").call([&]{ debug++; })
65 .doc("Increase debug level (default: 0)\n")));
66 auto lib_dir_flag = option("-L", "--lib").doc("Set the location of the ArkScript standard library. Paths can be delimited by ';'\n")
67 & value("lib_dir", libdir);
68
69 auto import_solver_pass_flag = (
70 option("-fimportsolver").call([&] { passes |= Ark::FeatureImportSolver; })
71 | option("-fno-importsolver").call([&] { passes &= ~Ark::FeatureImportSolver; })
72 ).doc("Toggle on and off the import solver pass");
73 auto macro_proc_pass_flag = (
74 option("-fmacroprocessor").call([&] { passes |= Ark::FeatureMacroProcessor; })
75 | option("-fno-macroprocessor").call([&] { passes &= ~Ark::FeatureMacroProcessor; })
76 ).doc("Toggle on and off the macro processor pass");
77 auto optimizer_pass_flag = (
78 option("-foptimizer").call([&] { passes |= Ark::FeatureASTOptimizer; })
79 | option("-fno-optimizer").call([&] { passes &= ~Ark::FeatureASTOptimizer; })
80 ).doc("Toggle on and off the optimizer pass");
81 auto ir_optimizer_pass_flag = (
82 option("-firoptimizer").call([&] { passes |= Ark::FeatureIROptimizer; })
83 | option("-fno-iroptimizer").call([&] { passes &= ~Ark::FeatureIROptimizer; })
84 ).doc("Toggle on and off the IR optimizer pass");
85 auto ir_dump = option("-fdump-ir").call([&] { passes |= Ark::FeatureDumpIR; })
86 .doc("Dump IR to file.ark.ir");
87
88 const auto compiler_passes_flag = (
89 // cppcheck-suppress constStatement
90 import_solver_pass_flag, macro_proc_pass_flag, optimizer_pass_flag, ir_optimizer_pass_flag, ir_dump
91 );
92
93 auto cli = (
94 option("-h", "--help").set(selected, mode::help).doc("Display this message")
95 | option("-v", "--version").set(selected, mode::version).doc("Display ArkScript version and exit")
96 | option("--dev-info").set(selected, mode::dev_info).doc("Display development information and exit")
97 | (
98 required("-e", "--eval").set(selected, mode::eval).doc("Evaluate ArkScript expression\n")
99 & value("expression", eval_expression)
100 )
101 | (
102 required("-c", "--compile").set(selected, mode::compile).doc("Compile the given program to bytecode, but do not run")
103 & value("file", file)
104 , debug_flag
105 , compiler_passes_flag
106 )
107 | (
108 value("file", file).set(selected, mode::run)
109 , (
110 debug_flag
111 , lib_dir_flag
112 , compiler_passes_flag
113 )
114 , any_other(script_args)
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 required("--ast").set(selected, mode::ast).doc("Compile the given program and output its AST as JSON to stdout")
126 & value("file", file)
127 , debug_flag
128 , lib_dir_flag
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(wrong)
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("LICENSE", " Mozilla Public License 2.0");
168
169 if (parse(argc, argv, cli) && wrong.empty())
170 {
171 using namespace Ark;
172
173 std::vector<std::filesystem::path> lib_paths;
174 // if arkscript lib paths were provided by the CLI, bypass the automatic lookup
175 if (!libdir.empty())
176 {
177 std::ranges::transform(Utils::splitString(libdir, ';'), std::back_inserter(lib_paths), [](const std::string& path) {
178 return std::filesystem::path(path);
179 });
180 }
181 else
182 {
183 if (const char* arkpath = std::getenv("ARKSCRIPT_PATH"))
184 {
185 std::ranges::transform(Utils::splitString(arkpath, ';'), std::back_inserter(lib_paths), [](const std::string& path) {
186 return std::filesystem::path(path);
187 });
188 }
189 else if (Utils::fileExists("./lib"))
190 lib_paths.emplace_back("lib");
191 else
192 fmt::println("{}: Couldn't read ARKSCRIPT_PATH environment variable", fmt::styled("Warning", fmt::fg(fmt::color::dark_orange)));
193 }
194
195 switch (selected)
196 {
197 case mode::help:
198 std::cout << man_page << std::endl;
199 break;
200
201 case mode::version:
202 fmt::println(ARK_FULL_VERSION);
203 break;
204
205 case mode::dev_info:
206 {
207 fmt::println(
208 "Have been compiled with {}\n\n"
209 "sizeof(Ark::Value) = {}B\n"
210 " sizeof(Value_t) = {}B\n"
211 " sizeof(ValueType) = {}B\n"
212 " sizeof(Ark::Procedure) = {}B\n"
213 " sizeof(Ark::Closure) = {}B\n"
214 " sizeof(Ark::UserType) = {}B\n"
215 "\nVirtual Machine\n"
216 "sizeof(Ark::VM) = {}B\n"
217 " sizeof(Ark::State) = {}B\n"
218 " sizeof(Ark::Scope) = {}B\n"
219 " sizeof(ExecutionContext) = {}B\n"
220 "\nMisc\n"
221 " sizeof(vector<Ark::Value>) = {}B\n"
222 " sizeof(char) = {}B\n"
223 "\nsizeof(Node) = {}B",
225 // value
226 sizeof(Ark::Value),
227 sizeof(Ark::Value::Value_t),
228 sizeof(Ark::ValueType),
229 sizeof(Ark::Procedure),
231 sizeof(Ark::UserType),
232 // vm
233 sizeof(Ark::VM),
234 sizeof(Ark::State),
237 // misc
238 sizeof(std::vector<Ark::Value>),
239 sizeof(char),
240 sizeof(Ark::internal::Node));
241 break;
242 }
243
244 case mode::repl:
245 {
246 Ark::Repl repl(lib_paths);
247 return repl.run();
248 }
249
250 case mode::compile:
251 {
252 Ark::State state(lib_paths);
253 state.setDebug(debug);
254
255 if (!state.doFile(file, passes))
256 return ARK_ERROR_EXIT_CODE;
257
258 break;
259 }
260
261 case mode::run:
262 {
263 Ark::State state(lib_paths);
264 state.setDebug(debug);
265 state.setArgs(script_args);
266
267 if (!state.doFile(file, passes))
268 return ARK_ERROR_EXIT_CODE;
269
270 Ark::VM vm(state);
271 return vm.run();
272 }
273
274 case mode::eval:
275 {
276 Ark::State state(lib_paths);
277 state.setDebug(debug);
278
279 if (!state.doString(eval_expression))
280 {
281 std::cerr << "Could not evaluate expression\n";
282 return ARK_ERROR_EXIT_CODE;
283 }
284
285 Ark::VM vm(state);
286 return vm.run();
287 }
288
289 case mode::ast:
290 {
291 JsonCompiler compiler(debug, lib_paths);
292 compiler.feed(file);
293 fmt::println("{}", compiler.compile());
294 break;
295 }
296
297 case mode::bytecode_reader:
298 {
299 try
300 {
302 bcr.feed(file);
303 if (!bcr.checkMagic())
304 {
305 // we got a potentially non-compiled file
306 fmt::println("Compiling {}...", file);
307
308 Ark::Welder welder(debug, lib_paths);
309 welder.computeASTFromFile(file);
310 welder.generateBytecode();
311 bcr.feed(welder.bytecode());
312 }
313
314 if (bcr_page == max_uint16 && bcr_start == max_uint16)
315 bcr.display(segment);
316 else if (bcr_page != max_uint16 && bcr_start == max_uint16)
317 bcr.display(segment, std::nullopt, std::nullopt, bcr_page);
318 else if (bcr_page == max_uint16 && bcr_start != max_uint16)
319 bcr.display(segment, bcr_start, bcr_end);
320 else
321 bcr.display(segment, bcr_start, bcr_end, bcr_page);
322 }
323 catch (const std::exception& e)
324 {
325 std::cerr << e.what() << std::endl;
326 return ARK_ERROR_EXIT_CODE;
327 }
328 break;
329 }
330
331 case mode::format:
332 {
333 // dry run and check should not update the file
334 Formatter formatter(file, format_dry_run || format_check);
335 formatter.run();
336 if (format_dry_run)
337 fmt::println("{}", formatter.output());
338 if (formatter.codeModified())
339 return 1;
340 }
341 }
342 }
343 else
344 {
345 for (const auto& arg : wrong)
346 std::cerr << "'" << arg.c_str() << "' isn't a valid argument\n";
347
348 std::cout << usage_lines(cli, fmt) << std::endl;
349 }
350
351 return 0;
352}
A bytecode disassembler for ArkScript.
#define ARK_COMPILER
Definition Constants.hpp:24
constexpr std::string_view ARK_FULL_VERSION
Definition Constants.hpp:22
Lots of utilities about the filesystem.
ArkScript REPL - Read Eval Print Loop.
int main()
Definition main.cpp:24
#define ARK_ERROR_EXIT_CODE
Definition main.cpp:20
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:24
Ark state to handle the dirty job of loading and compiling ArkScript code.
Definition State.hpp:33
bool doFile(const std::string &file, uint16_t features=DefaultFeatures)
Compile a file, and use the resulting bytecode.
Definition State.cpp:77
void setArgs(const std::vector< std::string > &args) noexcept
Set the script arguments in sys:args.
Definition State.cpp:126
bool doString(const std::string &code, uint16_t features=DefaultFeatures)
Compile a string (representing ArkScript code) and store resulting bytecode in m_bytecode.
Definition State.cpp:108
void setDebug(unsigned level) noexcept
Set the debug level.
Definition State.cpp:136
A class to be use C++ objects in ArkScript.
Definition UserType.hpp:47
The ArkScript virtual machine, executing ArkScript bytecode.
Definition VM.hpp:46
int run(bool fail_with_exception=false)
Run the bytecode held in the state.
Definition VM.cpp:443
std::variant< double, std::string, internal::PageAddr_t, Procedure, internal::Closure, UserType, std::vector< Value >, Value * > Value_t
Definition Value.hpp:74
The welder joins all the compiler passes.
Definition Welder.hpp:37
const bytecode_t & bytecode() const noexcept
Definition Welder.cpp:108
bool generateBytecode()
Compile the AST processed by computeASTFromFile / computeASTFromString.
Definition Welder.cpp:50
bool computeASTFromFile(const std::string &filename)
Definition Welder.cpp:35
Closure management.
Definition Closure.hpp:36
A node of an Abstract Syntax Tree for ArkScript.
Definition Node.hpp:30
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:22
bool codeModified() const
Definition Formatter.cpp:60
const std::string & output() const
Definition Formatter.cpp:55
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:59
constexpr uint16_t FeatureImportSolver
Definition Constants.hpp:48
constexpr uint16_t FeatureIROptimizer
Definition Constants.hpp:51
constexpr uint16_t FeatureMacroProcessor
Definition Constants.hpp:49
ValueType
Definition Value.hpp:33
constexpr uint16_t FeatureASTOptimizer
This is disabled so that embedding ArkScript does not prune nodes from the AST ; it is active in the ...
Definition Constants.hpp:50
constexpr uint16_t FeatureDumpIR
Definition Constants.hpp:54