ArkScript
A small, fast, functional and scripting language for video games
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
17int main(int argc, char** argv)
18{
19 using namespace clipp;
20
21 enum class mode
22 {
23 help,
24 dev_info,
25 bytecode_reader,
26 version,
27 run,
28 repl,
29 compile,
30 eval,
31 ast,
32 format
33 };
34 auto selected = mode::repl;
35
36 unsigned debug = 0;
37
38 constexpr uint16_t max_uint16 = std::numeric_limits<uint16_t>::max();
39
40 // Bytecode reader
41 // by default, select all pages and segment types, without slicing anything
42 uint16_t bcr_page = max_uint16;
43 uint16_t bcr_start = max_uint16;
44 uint16_t bcr_end = max_uint16;
45 auto segment = Ark::BytecodeSegment::All;
46 // Eval / Run / AST dump
47 std::string file, eval_expression;
48 std::string libdir;
49 // Formatting
50 bool format_dry_run = false;
51 bool format_check = false;
52 // Generic arguments
53 std::vector<std::string> wrong, script_args;
54
56
57 // clang-format off
58 auto debug_flag = joinable(repeatable(option("-d", "--debug").call([&]{ debug++; })
59 .doc("Increase debug level (default: 0)\n")));
60 auto lib_dir_flag = option("-L", "--lib").doc("Set the location of the ArkScript standard library. Paths can be delimited by ';'\n")
61 & value("lib_dir", libdir);
62
63 auto import_solver_pass_flag = (
64 option("-fimportsolver").call([&] { passes |= Ark::FeatureImportSolver; })
65 | option("-fno-importsolver").call([&] { passes &= ~Ark::FeatureImportSolver; })
66 ).doc("Toggle on and off the import solver pass");
67 auto macro_proc_pass_flag = (
68 option("-fmacroprocessor").call([&] { passes |= Ark::FeatureMacroProcessor; })
69 | option("-fno-macroprocessor").call([&] { passes &= ~Ark::FeatureMacroProcessor; })
70 ).doc("Toggle on and off the macro processor pass");
71 auto optimizer_pass_flag = (
72 option("-foptimizer").call([&] { passes |= Ark::FeatureASTOptimizer; })
73 | option("-fno-optimizer").call([&] { passes &= ~Ark::FeatureASTOptimizer; })
74 ).doc("Toggle on and off the optimizer pass");
75 auto ir_optimizer_pass_flag = (
76 option("-firoptimizer").call([&] { passes |= Ark::FeatureIROptimizer; })
77 | option("-fno-iroptimizer").call([&] { passes &= ~Ark::FeatureIROptimizer; })
78 ).doc("Toggle on and off the IR optimizer pass");
79 auto ir_dump = option("-fdump-ir").call([&] { passes |= Ark::FeatureDumpIR; })
80 .doc("Dump IR to file.ark.ir");
81
82 const auto compiler_passes_flag = (
83 // cppcheck-suppress constStatement
84 import_solver_pass_flag, macro_proc_pass_flag, optimizer_pass_flag, ir_optimizer_pass_flag, ir_dump
85 );
86
87 auto cli = (
88 option("-h", "--help").set(selected, mode::help).doc("Display this message")
89 | option("-v", "--version").set(selected, mode::version).doc("Display ArkScript version and exit")
90 | option("--dev-info").set(selected, mode::dev_info).doc("Display development information and exit")
91 | (
92 required("-e", "--eval").set(selected, mode::eval).doc("Evaluate ArkScript expression\n")
93 & value("expression", eval_expression)
94 )
95 | (
96 required("-c", "--compile").set(selected, mode::compile).doc("Compile the given program to bytecode, but do not run")
97 & value("file", file)
98 , debug_flag
99 , compiler_passes_flag
100 )
101 | (
102 value("file", file).set(selected, mode::run)
103 , (
104 debug_flag
105 , lib_dir_flag
106 , compiler_passes_flag
107 )
108 , any_other(script_args)
109 )
110 | (
111 required("-f", "--format").set(selected, mode::format).doc("Format the given source file in place")
112 & value("file", file)
113 , (
114 option("--dry-run").set(format_dry_run, true).doc("Do not modify the file, only print out the changes")
115 | 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")
116 )
117 )
118 | (
119 required("--ast").set(selected, mode::ast).doc("Compile the given program and output its AST as JSON to stdout")
120 & value("file", file)
121 , debug_flag
122 , lib_dir_flag
123 )
124 | (
125 required("-bcr", "--bytecode-reader").set(selected, mode::bytecode_reader).doc("Launch the bytecode reader")
126 & value("file", file).doc("If file isn't a bytecode file, the cached compiled will be loaded ; if there are none, it will be compiled first")
127 , (
128 option("-on", "--only-names").set(segment, Ark::BytecodeSegment::HeadersOnly).doc("Display only the bytecode segments names and sizes")
129 | (
130 (
131 option("-a", "--all").set(segment, Ark::BytecodeSegment::All).doc("Display all the bytecode segments (default)")
132 | option("-st", "--symbols").set(segment, Ark::BytecodeSegment::Symbols).doc("Display only the symbols table")
133 | option("-vt", "--values").set(segment, Ark::BytecodeSegment::Values).doc("Display only the values table")
134 | (
135 option("-cs", "--code").set(segment, Ark::BytecodeSegment::Code).doc("Display only the code segments")
136 , option("-p", "--page").set(segment, Ark::BytecodeSegment::Code).doc("Set the bytecode reader code segment to display")
137 & value("page", bcr_page)
138 )
139 )
140 , option("-s", "--slice").doc("Select a slice of instructions in the bytecode")
141 & value("start", bcr_start)
142 & value("end", bcr_end)
143 )
144 )
145 )
146 , any_other(wrong)
147 );
148 // clang-format on
149
150 auto fmt = doc_formatting {}
151 .first_column(8) // column where usage lines and documentation starts
152 .doc_column(36) // parameter docstring start col
153 .indent_size(2) // indent of documentation lines for children of a documented group
154 .split_alternatives(true) // split usage into several lines for large alternatives
155 .merge_alternative_flags_with_common_prefix(true) // [-fok] [-fno-ok] becomes [-f(ok|no-ok)]
156 .paragraph_spacing(1)
157 .ignore_newline_chars(false);
158 const auto man_page = make_man_page(cli, "arkscript", fmt)
159 .prepend_section("DESCRIPTION", " ArkScript programming language")
160 .append_section("VERSION", fmt::format(" {}", ARK_FULL_VERSION))
161 .append_section("LICENSE", " Mozilla Public License 2.0");
162
163 if (parse(argc, argv, cli) && wrong.empty())
164 {
165 using namespace Ark;
166
167 std::vector<std::filesystem::path> lib_paths;
168 // if arkscript lib paths were provided by the CLI, bypass the automatic lookup
169 if (!libdir.empty())
170 {
171 std::ranges::transform(Utils::splitString(libdir, ';'), std::back_inserter(lib_paths), [](const std::string& path) {
172 return std::filesystem::path(path);
173 });
174 }
175 else
176 {
177 if (const char* arkpath = std::getenv("ARKSCRIPT_PATH"))
178 {
179 std::ranges::transform(Utils::splitString(arkpath, ';'), std::back_inserter(lib_paths), [](const std::string& path) {
180 return std::filesystem::path(path);
181 });
182 }
183 else if (Utils::fileExists("./lib"))
184 lib_paths.emplace_back("lib");
185 else
186 fmt::println("{}: Couldn't read ARKSCRIPT_PATH environment variable", fmt::styled("Warning", fmt::fg(fmt::color::dark_orange)));
187 }
188
189 switch (selected)
190 {
191 case mode::help:
192 std::cout << man_page << std::endl;
193 break;
194
195 case mode::version:
196 fmt::println(ARK_FULL_VERSION);
197 break;
198
199 case mode::dev_info:
200 {
201 fmt::println(
202 "Have been compiled with {}\n\n"
203 "sizeof(Ark::Value) = {}B\n"
204 " sizeof(Value_t) = {}B\n"
205 " sizeof(ValueType) = {}B\n"
206 " sizeof(ProcType) = {}B\n"
207 " sizeof(Ark::Closure) = {}B\n"
208 " sizeof(Ark::UserType) = {}B\n"
209 "\nVirtual Machine\n"
210 "sizeof(Ark::VM) = {}B\n"
211 " sizeof(Ark::State) = {}B\n"
212 " sizeof(Ark::Scope) = {}B\n"
213 " sizeof(ExecutionContext) = {}B\n"
214 "\nMisc\n"
215 " sizeof(vector<Ark::Value>) = {}B\n"
216 " sizeof(char) = {}B\n"
217 "\nsizeof(Node) = {}B",
219 // value
220 sizeof(Ark::Value),
221 sizeof(Ark::Value::Value_t),
222 sizeof(Ark::ValueType),
223 sizeof(Ark::Value::ProcType),
225 sizeof(Ark::UserType),
226 // vm
227 sizeof(Ark::VM),
228 sizeof(Ark::State),
229 sizeof(Ark::internal::Scope),
231 // misc
232 sizeof(std::vector<Ark::Value>),
233 sizeof(char),
234 sizeof(Ark::internal::Node));
235 break;
236 }
237
238 case mode::repl:
239 {
240 Ark::Repl repl(lib_paths);
241 return repl.run();
242 }
243
244 case mode::compile:
245 {
246 Ark::State state(lib_paths);
247 state.setDebug(debug);
248
249 if (!state.doFile(file, passes))
250 return -1;
251
252 break;
253 }
254
255 case mode::run:
256 {
257 Ark::State state(lib_paths);
258 state.setDebug(debug);
259 state.setArgs(script_args);
260
261 if (!state.doFile(file, passes))
262 return -1;
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 -1;
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
298 if (bcr_page == max_uint16 && bcr_start == max_uint16)
299 bcr.display(segment);
300 else if (bcr_page != max_uint16 && bcr_start == max_uint16)
301 bcr.display(segment, std::nullopt, std::nullopt, bcr_page);
302 else if (bcr_page == max_uint16 && bcr_start != max_uint16)
303 bcr.display(segment, bcr_start, bcr_end);
304 else
305 bcr.display(segment, bcr_start, bcr_end, bcr_page);
306 }
307 catch (const std::exception& e)
308 {
309 std::cerr << e.what() << std::endl;
310 return -1;
311 }
312 break;
313 }
314
315 case mode::format:
316 {
317 // dry run and check should not update the file
318 Formatter formatter(file, format_dry_run || format_check);
319 formatter.run();
320 if (format_dry_run)
321 fmt::println("{}", formatter.output());
322 if (formatter.codeModified())
323 return 1;
324 }
325 }
326 }
327 else
328 {
329 for (const auto& arg : wrong)
330 std::cerr << "'" << arg.c_str() << "' isn't a valid argument\n";
331
332 std::cout << usage_lines(cli, fmt) << std::endl;
333 }
334
335 return 0;
336}
A bytecode disassembler for ArkScript.
#define ARK_COMPILER
Definition Constants.hpp:25
constexpr std::string_view ARK_FULL_VERSION
Definition Constants.hpp:23
Lots of utilities about the filesystem.
ArkScript REPL - Read Eval Print Loop.
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.
int run()
Start the REPL.
Ark state to handle the dirty job of loading and compiling ArkScript code.
Definition State.hpp:32
bool doFile(const std::string &file, uint16_t features=DefaultFeatures)
Compile a file, and use the resulting bytecode.
Definition State.cpp:72
void setArgs(const std::vector< std::string > &args) noexcept
Set the script arguments in sys:args.
Definition State.cpp:121
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:103
void setDebug(unsigned level) noexcept
Set the debug level.
Definition State.cpp:132
A class to be use C++ objects in ArkScript.
Definition UserType.hpp:48
The ArkScript virtual machine, executing ArkScript bytecode.
Definition VM.hpp:44
int run(bool fail_with_exception=false)
Run the bytecode held in the state.
Definition VM.cpp:313
Value(*)(std::vector< Value > &, VM *) ProcType
Definition Value.hpp:62
std::variant< double, std::string, internal::PageAddr_t, ProcType, internal::Closure, UserType, std::vector< Value >, Value * > Value_t
Definition Value.hpp:65
Closure management.
Definition Closure.hpp:37
A node of an Abstract Syntax Tree for ArkScript.
Definition Node.hpp:31
A class to handle the VM scope more efficiently.
Definition Scope.hpp:28
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.
int main(int argc, char **argv)
Definition main.cpp:17
constexpr uint16_t DefaultFeatures
Definition Constants.hpp:60
constexpr uint16_t FeatureImportSolver
Definition Constants.hpp:49
constexpr uint16_t FeatureIROptimizer
Definition Constants.hpp:52
constexpr uint16_t FeatureMacroProcessor
Definition Constants.hpp:50
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:51
constexpr uint16_t FeatureDumpIR
Definition Constants.hpp:55