1 module tagion.tools.wasmutil.wasmutil;
2 
3 import std.exception : assumeUnique;
4 import std.file : exists, fread = read, readText, fwrite = write;
5 import std.format;
6 import std.getopt;
7 import std.json;
8 import std.meta;
9 import std.path : baseName, extension, setExtension;
10 import std.range : empty, only;
11 import std.stdio;
12 import std.traits : EnumMembers;
13 import tagion.basic.Types : Buffer, FileExtension;
14 import tagion.basic.basic : basename;
15 import tagion.hibon.Document : Document;
16 import tagion.hibon.HiBON : HiBON;
17 import tagion.hibon.HiBONJSON;
18 import tagion.wasm.WasmBase;
19 import tagion.wasm.WasmBetterC : wasmBetterC;
20 import tagion.wasm.WasmException;
21 import tagion.wasm.WasmGas;
22 import tagion.wasm.WasmReader;
23 import tagion.wasm.WasmWat : wat;
24 import tagion.wasm.WasmWriter;
25 
26 //import tagion.script.StandardRecords;
27 import std.array : join;
28 import tagion.tools.Basic : Main;
29 import tagion.tools.revision;
30 
31 template Produce(FileExtension ext) {
32     static if (ext == FileExtension.wat) {
33         import tagion.wasm.WasmWat;
34 
35         alias Produce = wat;
36     }
37     else static if (ext == FileExtension.dsrc) {
38         import tagion.wasm.WasmBetterC;
39 
40         alias Produce = wasmBetterC;
41     }
42     else {
43         static assert(0, format("Can not produce a %s format not supported", ext));
44     }
45 }
46 
47 auto produce(FileExtension ext)(WasmReader wasm_reader, File fout) {
48     return Produce!(ext)(wasm_reader, fout);
49 }
50 
51 enum OutputType {
52     wat, /// WASM text output type in wat format (FileExtension.wat) 
53     wasm, /// WASM binary output type (FileExtension.wasm)
54     betterc, /// BetterC source file (FileExtension.dsrc)
55 }
56 
57 FileExtension typeExtension(const OutputType type) pure nothrow @nogc {
58     final switch (type) {
59     case OutputType.wat:
60         return FileExtension.wat;
61     case OutputType.wasm:
62         return FileExtension.wasm;
63     case OutputType.betterc:
64         return FileExtension.dsrc;
65     }
66 }
67 
68 mixin Main!_main;
69 
70 int _main(string[] args) {
71     immutable program = args[0];
72     bool version_switch;
73 
74     string inputfilename;
75     string outputfilename;
76     //bool print;
77     //bool betterc;
78     string module_name;
79     string[] imports;
80     string[] attributes;
81     bool inject_gas;
82     bool verbose_switch;
83 
84     string[] modify_from;
85     string[] modify_to;
86 
87     OutputType type;
88     try {
89         auto main_args = getopt(args,
90                 std.getopt.config.caseSensitive,
91                 std.getopt.config.bundling,
92                 "version", "display the version", &version_switch, //"inputfile|i", "Sets the HiBON input file name", &inputfilename,
93                 //"outputfile|o", "Sets the output file name", &outputfilename, // "bin|b", "Use HiBON or else use JSON", &binary,
94                 // "value|V", format("Bill value : default: %d", value), &value,
95                 "gas|g", format("Inject gas countes: %s", inject_gas), &inject_gas,
96                 "verbose|v", format("Verbose %s", verbose_switch), &verbose_switch,
97                 "mod|m", "Modify import module name from ", &modify_from,
98                 "to", "Modify import module name from ", &modify_to,
99                 "imports|i", "Import list", &imports,
100                 "name", "Import list", &module_name, //                "print|p", format("Print the wasm as wat: %s", print), &print,
101 
102                 //                "betterc|d", format("Print the wasm as wat: %s", betterc), &betterc,
103 
104                 "type|t", format("Sets stdout file type (%-(%s %))", [EnumMembers!OutputType]), &type,
105         "global-attribute", "Sets the global attribute for the D transpiling", &attributes,
106         );
107 
108         void help() {
109             defaultGetoptPrinter(
110                     [
111                     // format("%s version %s", program, REVNO),
112                     "Documentation: https://tagion.org/",
113                     "",
114                     "Usage:",
115                     format("%s [<option>...] <in-file> <out-file>", program),
116                     format("%s [<option>...] <in-file>", program),
117                     "",
118                     "Where:",
119                     format("<in-file>           Is an input file in (%-(%s -%)) format",
120                         only(FileExtension.wasm, FileExtension.wat)),
121                     format("<out-file>          Is an output file in (%-(%s -%)) format",
122                         only(FileExtension.wat, FileExtension.dsrc)),
123                     "                    stdout is used of the output is not specifed the",
124                     "",
125 
126                     "<option>:",
127 
128                     ].join("\n"),
129                     main_args.options);
130         }
131 
132         if (version_switch) {
133             revision_text.writeln;
134             return 0;
135         }
136 
137         version (none)
138             if (verbose_switch && (!print || outputfilename.length is 0)) {
139                 verbose.mode = VerboseMode.STANDARD;
140             }
141 
142         if (main_args.helpWanted) {
143             help;
144             return 0;
145         }
146 
147         foreach (file; args[1 .. $]) {
148             with (FileExtension) {
149                 switch (file.extension) {
150                 case wasm:
151                 case wat:
152                     if (inputfilename.empty) {
153                         inputfilename = file;
154                         break;
155                     }
156                     check(outputfilename is null,
157                             format("Only one outputfile allowed (both %s and %s has been specifiled)",
158                             inputfilename, file));
159                     outputfilename = file;
160                     break;
161                 case dsrc:
162                     check(outputfilename is null,
163                             format("Only one outputfile allowed (both %s and %s has been specifiled)",
164                             inputfilename, file));
165                     outputfilename = file;
166                     break;
167                 case wast:
168 
169                     if (inputfilename.empty) {
170                         inputfilename = file;
171                         type = OutputType.wasm;
172 
173                     }
174 
175                     if (module_name.empty) {
176                         module_name = inputfilename.baseName(FileExtension.wast);
177                     }
178                     break;
179                 default:
180                     check(0, format("File %s is not supported", file));
181                 }
182             }
183         }
184         if (modify_from.length !is modify_to.length) {
185             stderr.writefln("Modify set must be set in pair");
186             stderr.writefln("mod=%s", modify_from);
187             stderr.writefln("to=%s", modify_to);
188             help;
189             return 4;
190         }
191 
192         immutable standard_output = (outputfilename.length == 0);
193 
194         WasmReader wasm_reader;
195         WasmWriter wasm_writer;
196         with (FileExtension) {
197             switch (inputfilename.extension) {
198             case wasm, wo:
199                 immutable read_data = assumeUnique(cast(ubyte[]) fread(inputfilename));
200                 wasm_reader = WasmReader(read_data);
201                 verbose.hex(0, read_data);
202                 wasm_writer = WasmWriter(wasm_reader);
203                 break;
204             case wast:
205                 import tagion.wasm.WastParser;
206                 import tagion.wasm.WastTokenizer;
207 
208                 immutable wast_text = inputfilename.readText;
209                 auto tokenizer = WastTokenizer(wast_text);
210                 wasm_writer = new WasmWriter;
211                 auto wast_parser = WastParser(wasm_writer);
212                 wast_parser.parse(tokenizer);
213                 writefln("Before wasmwrite");
214                 writefln("wasm_writer=%(%02X %)", wasm_writer.serialize);
215                 break;
216             default:
217                 check(0, format("File extensions %s not valid for input file (only %-(%s, %))",
218                         inputfilename.extension, only(FileExtension.wasm, FileExtension.wo)));
219             }
220         }
221 
222         //WasmWriter wasm_writer = WasmWriter(wasm_reader);
223 
224         if (inject_gas) {
225             auto wasmgas = WasmGas(wasm_writer);
226             wasmgas.modify;
227         }
228         immutable data_out = wasm_writer.serialize;
229         writefln("after data_out");
230         if (verbose_switch) {
231             verbose.mode = VerboseMode.STANDARD;
232         }
233 
234         const output_extension = (outputfilename.empty) ? type.typeExtension : outputfilename.extension;
235         with (FileExtension) {
236         WasmOutCase:
237             switch (output_extension) {
238                 static foreach (WasmOut; AliasSeq!(wat, dsrc)) {
239             case WasmOut:
240                     File fout = stdout;
241                     if (!outputfilename.empty) {
242                         fout = File(outputfilename, "w");
243                     }
244                     scope (exit) {
245                         if (fout !is stdout) {
246                             fout.close;
247                         }
248                     }
249                     auto prod = produce!WasmOut(WasmReader(data_out), fout);
250                     static if (__traits(hasMember, prod, "module_name")) {
251                         writefln("BetterC");
252                         prod.module_name = module_name;
253                         prod.imports = imports;
254                         prod.attributes = attributes;
255                     }
256                     // produce!(FileExtension.wat)(WasmReader(data_out), outputfilename);
257                     //   import _wast=tagion.wasm.Wat;
258                     //       _wast.wat(WasmReader(data_out), stdout).serialize;
259                     prod.serialize;
260                     break WasmOutCase;
261                 }
262             case wasm:
263                 if (outputfilename.empty) {
264                     outputfilename = inputfilename.setExtension(FileExtension.wasm);
265                 }
266                 outputfilename.fwrite(data_out);
267                 break;
268             default:
269                 check(outputfilename is null,
270                         format("File extensions %s not valid output file (only %s)",
271                         outputfilename.extension,
272                         only(FileExtension.wasm, FileExtension.wat)));
273                 version (none)
274                     if (print) {
275                         Wat(wasm_reader, stdout).serialize();
276                     }
277             }
278         }
279     }
280     catch (Exception e) {
281         //verbose(e);
282         stderr.writefln("Error: %s", e.msg);
283     }
284     return 0;
285 }