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
10#include <Ark/Utils/Files.hpp>
12#include <Ark/VM/Value/Dict.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
18constexpr int ArkErrorExitCode = 0;
19#else
20constexpr int ArkErrorExitCode = -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 // Bytecode reader
45 // by default, select all pages and segment types, without slicing anything
46 uint16_t bcr_page = Ark::MaxValue16Bits;
47 uint16_t bcr_start = Ark::MaxValue16Bits;
48 uint16_t bcr_end = Ark::MaxValue16Bits;
49 auto segment = Ark::BytecodeSegment::All;
50 // Eval / Run / AST dump
51 std::string file, eval_expression;
52 std::string libdir;
53 // Formatting
54 bool format_dry_run = false;
55 bool format_check = false;
56 // Generic arguments
57 std::vector<std::string> script_args;
58
60
61 // clang-format off
62 auto debug_flag = joinable(repeatable(option("-d", "--debug").call([&]{ debug++; })
63 .doc("Increase debug level (default: 0)\n")));
64 auto lib_dir_flag = option("-L", "--lib").doc("Set the location of the ArkScript standard library. Paths can be delimited by ';'\n")
65 & value("lib_dir", libdir);
66
67 auto import_solver_pass_flag = (
68 option("-fimportsolver").call([&] { passes |= Ark::FeatureImportSolver; })
69 | option("-fno-importsolver").call([&] { passes &= ~Ark::FeatureImportSolver; })
70 ).doc("Toggle on and off the import solver pass");
71 auto macro_proc_pass_flag = (
72 option("-fmacroprocessor").call([&] { passes |= Ark::FeatureMacroProcessor; })
73 | option("-fno-macroprocessor").call([&] { passes &= ~Ark::FeatureMacroProcessor; })
74 ).doc("Toggle on and off the macro processor pass");
75 auto optimizer_pass_flag = (
76 option("-foptimizer").call([&] { passes |= Ark::FeatureASTOptimizer; })
77 | option("-fno-optimizer").call([&] { passes &= ~Ark::FeatureASTOptimizer; })
78 ).doc("Toggle on and off the optimizer pass");
79 auto ir_optimizer_pass_flag = (
80 option("-firoptimizer").call([&] { passes |= Ark::FeatureIROptimizer; })
81 | option("-fno-iroptimizer").call([&] { passes &= ~Ark::FeatureIROptimizer; })
82 ).doc("Toggle on and off the IR optimizer pass");
83 auto ir_dump = option("-fdump-ir").call([&] { passes |= Ark::FeatureDumpIR; })
84 .doc("Dump IR to file.ark.ir");
85
86 const auto run_flags = (
87 // cppcheck-suppress constStatement
88 debug_flag, lib_dir_flag, import_solver_pass_flag, macro_proc_pass_flag, optimizer_pass_flag, ir_optimizer_pass_flag, ir_dump
89 );
90
91 auto cli = (
92 option("-h", "--help").set(selected, mode::help).doc("Display this message")
93 | option("-v", "--version").set(selected, mode::version).doc("Display ArkScript version and exit")
94 | option("--dev-info").set(selected, mode::dev_info).doc("Display development information and exit")
95 | (
96 required("-e", "--eval").set(selected, mode::eval).doc("Evaluate ArkScript expression\n")
97 & value("expression", eval_expression)
98 )
99 | (
100 run_flags
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 )
105 | value("file", file).set(selected, mode::run)
106 )
107 | (
108 required("-f", "--format").set(selected, mode::format).doc("Format the given source file in place")
109 & value("file", file)
110 , (
111 option("--dry-run").set(format_dry_run, true).doc("Do not modify the file, only print out the changes")
112 | 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")
113 )
114 )
115 | (
116 debug_flag
117 , lib_dir_flag
118 , required("--ast").set(selected, mode::ast).doc("Compile the given program and output its AST as JSON to stdout")
119 & value("file", file)
120 )
121 | (
122 required("-bcr", "--bytecode-reader").set(selected, mode::bytecode_reader).doc("Launch the bytecode reader")
123 & value("file", file).doc(".arkc bytecode file or .ark source file that will be compiled first")
124 , (
125 option("-on", "--only-names").set(segment, Ark::BytecodeSegment::HeadersOnly).doc("Display only the bytecode segments names and sizes")
126 | (
127 (
128 option("-a", "--all").set(segment, Ark::BytecodeSegment::All).doc("Display all the bytecode segments (default)")
129 | option("-st", "--symbols").set(segment, Ark::BytecodeSegment::Symbols).doc("Display only the symbols table")
130 | option("-vt", "--values").set(segment, Ark::BytecodeSegment::Values).doc("Display only the values table")
131 | (
132 option("-cs", "--code").set(segment, Ark::BytecodeSegment::Code).doc("Display only the code segments")
133 , option("-p", "--page").set(segment, Ark::BytecodeSegment::Code).doc("Set the bytecode reader code segment to display")
134 & value("page", bcr_page)
135 )
136 )
137 , option("-s", "--slice").doc("Select a slice of instructions in the bytecode")
138 & value("start", bcr_start)
139 & value("end", bcr_end)
140 )
141 )
142 )
143 , any_other(script_args)
144 );
145 // clang-format on
146
147 auto fmt = doc_formatting {}
148 .first_column(8) // column where usage lines and documentation starts
149 .doc_column(36) // parameter docstring start col
150 .indent_size(2) // indent of documentation lines for children of a documented group
151 .split_alternatives(true) // split usage into several lines for large alternatives
152 .merge_alternative_flags_with_common_prefix(true) // [-fok] [-fno-ok] becomes [-f(ok|no-ok)]
153 .paragraph_spacing(1)
154 .ignore_newline_chars(false);
155 const auto man_page = make_man_page(cli, "arkscript", fmt)
156 .prepend_section("DESCRIPTION", " ArkScript programming language")
157 .append_section("VERSION", fmt::format(" {}", ARK_FULL_VERSION))
158 .append_section("LICENSE", " Mozilla Public License 2.0");
159
160 if (parse(argc, argv, cli))
161 {
162 using namespace Ark;
163
164 std::vector<std::filesystem::path> lib_paths;
165 // if arkscript lib paths were provided by the CLI, bypass the automatic lookup
166 if (!libdir.empty())
167 {
168 std::ranges::transform(Utils::splitString(libdir, ';'), std::back_inserter(lib_paths), [](const std::string& path) {
169 return std::filesystem::path(path);
170 });
171 }
172 else
173 {
174 if (const char* arkpath = std::getenv("ARKSCRIPT_PATH"))
175 {
176 std::ranges::transform(Utils::splitString(arkpath, ';'), std::back_inserter(lib_paths), [](const std::string& path) {
177 return std::filesystem::path(path);
178 });
179 }
180 else if (Utils::fileExists("./lib"))
181 lib_paths.emplace_back("lib");
182 else
183 fmt::println("{}: Couldn't read ARKSCRIPT_PATH environment variable", fmt::styled("Warning", fmt::fg(fmt::color::dark_orange)));
184 }
185
186 switch (selected)
187 {
188 case mode::help:
189 std::cout << man_page << std::endl;
190 break;
191
192 case mode::version:
193 fmt::println(ARK_FULL_VERSION);
194 break;
195
196 case mode::dev_info:
197 {
198 fmt::println("Compiler used: {}\n", ARK_COMPILER);
199 fmt::println("{:^34}|{:^8}|{:^10}", "Type", "SizeOf", "AlignOf");
200
201#define ARK_PRINT_SIZE(type) fmt::println("{:<34}| {:<7}| {:<9}", #type, sizeof(type), alignof(type))
202 ARK_PRINT_SIZE(char);
203
208 ARK_PRINT_SIZE(std::vector<Ark::Value>);
212
217
219#undef ARK_PRINT_SIZE
220 break;
221 }
222
223 case mode::repl:
224 {
225 Ark::Repl repl(lib_paths);
226 return repl.run();
227 }
228
229 case mode::compile:
230 {
231 Ark::State state(lib_paths);
232 state.setDebug(debug);
233
234 if (!state.doFile(file, passes))
235 return ArkErrorExitCode;
236 break;
237 }
238
239 case mode::run:
240 {
241 Ark::State state(lib_paths);
242 state.setDebug(debug);
243 state.setArgs(script_args);
244
245 if (!state.doFile(file, passes))
246 return ArkErrorExitCode;
247
248 Ark::VM vm(state);
249 return vm.run();
250 }
251
252 case mode::eval:
253 {
254 Ark::State state(lib_paths);
255 state.setDebug(debug);
256
257 if (!state.doString(eval_expression))
258 {
259 std::cerr << "Could not evaluate expression\n";
260 return ArkErrorExitCode;
261 }
262
263 Ark::VM vm(state);
264 return vm.run();
265 }
266
267 case mode::ast:
268 {
269 JsonCompiler compiler(debug, lib_paths);
270 compiler.feed(file);
271 fmt::println("{}", compiler.compile());
272 break;
273 }
274
275 case mode::bytecode_reader:
276 {
277 try
278 {
280 bcr.feed(file);
281 if (!bcr.checkMagic())
282 {
283 // we got a potentially non-compiled file
284 fmt::println("Compiling {}...", file);
285
286 Ark::Welder welder(debug, lib_paths);
287 welder.computeASTFromFile(file);
288 welder.generateBytecode();
289 bcr.feed(welder.bytecode());
290 }
291
292 if (bcr_page == Ark::MaxValue16Bits && bcr_start == Ark::MaxValue16Bits)
293 bcr.display(segment);
294 else if (bcr_page != Ark::MaxValue16Bits && bcr_start == Ark::MaxValue16Bits)
295 bcr.display(segment, std::nullopt, std::nullopt, bcr_page);
296 else if (bcr_page == Ark::MaxValue16Bits && bcr_start != Ark::MaxValue16Bits)
297 bcr.display(segment, bcr_start, bcr_end);
298 else
299 bcr.display(segment, bcr_start, bcr_end, bcr_page);
300 }
301 catch (const std::exception& e)
302 {
303 std::cerr << e.what() << std::endl;
304 return ArkErrorExitCode;
305 }
306 break;
307 }
308
309 case mode::format:
310 {
311 // dry run and check should not update the file
312 Formatter formatter(file, format_dry_run || format_check);
313 formatter.run();
314 if (format_dry_run)
315 fmt::println("{}", formatter.output());
316 if (formatter.codeModified())
317 return 1;
318 }
319 }
320 }
321
322 return 0;
323}
A bytecode disassembler for ArkScript.
#define ARK_COMPILER
Definition Constants.hpp:25
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: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_path, uint16_t features=DefaultFeatures)
Compile a file, and use the resulting bytecode.
Definition State.cpp:82
void setArgs(const std::vector< std::string > &args) noexcept
Set the script arguments in sys:args.
Definition State.cpp:131
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:113
void setDebug(unsigned level) noexcept
Set the debug level.
Definition State.cpp:141
A class to be use C++ objects in ArkScript.
Definition UserType.hpp:48
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:486
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:99
The welder joins all the compiler passes.
Definition Welder.hpp:37
const bytecode_t & bytecode() const noexcept
Definition Welder.cpp:109
bool generateBytecode()
Compile the AST processed by computeASTFromFile / computeASTFromString.
Definition Welder.cpp:51
bool computeASTFromFile(const std::string &filename)
Definition Welder.cpp:36
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:23
bool codeModified() const
Definition Formatter.cpp:61
const std::string & output() const
Definition Formatter.cpp:56
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:64
constexpr uint16_t FeatureImportSolver
Definition Constants.hpp:53
constexpr uint16_t FeatureIROptimizer
Definition Constants.hpp:56
constexpr uint16_t MaxValue16Bits
Definition Constants.hpp:70
constexpr uint16_t FeatureMacroProcessor
Definition Constants.hpp:54
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:55
constexpr uint16_t FeatureDumpIR
Definition Constants.hpp:59
ValueType
Definition Value.hpp:32