1 //          Copyright Mario Kröplin 2021.
2 // Distributed under the Boost Software License, Version 1.0.
3 //    (See accompanying file LICENSE_1_0.txt or copy at
4 //          https://www.boost.org/LICENSE_1_0.txt)
5 
6 module epsilon.main;
7 
8 import epsilon.settings;
9 import io : Input, read;
10 import log;
11 import runtime;
12 import std.range;
13 import std.stdio;
14 
15 void main(string[] args)
16 {
17     import core.stdc.stdlib : exit, EXIT_FAILURE, EXIT_SUCCESS;
18     import std.exception : ErrnoException;
19     import std.getopt : defaultGetoptPrinter, getopt, GetoptResult;
20 
21     GetoptResult result;
22     Settings settings;
23 
24     try
25     {
26         with (settings)
27         {
28             result = getopt(args,
29                     "c", "Disable collapsing constant trees.", &c,
30                     "g", "Generate only, do not compile.", &generate,
31                     "p", "Parser ignores regular token marks at hyper-nonterminals.", &p,
32                     "o", "Disable optimizing of variable storage in compiled compiler.", &o,
33                     "r", "Disable reference counting in compiled compiler.", &r,
34                     "space|s", "Compiled compiler uses space instead of newline as separator.", &space,
35                     "verbose|v", "Print debug output.", &verbose,
36                     "write|w", "Write compilation output as default.", &write,
37                     "slag", "Generate SLAG evaluator.", &slag,
38                     "sweep", "Generate single-sweep evaluator.", &sweep,
39                     "soag", "Generate SOAG evaluator.", &soag,
40                     "output-directory", "Write compiled compiler to directory.", &outputDirectory,
41                     "offset", "Show error positions language-server friendly as offsets.", &offset,
42             );
43         }
44     }
45     catch (Exception exception)
46     {
47         error!"%s"(exception.msg);
48         exit(EXIT_FAILURE);
49     }
50     if (result.helpWanted)
51     {
52         import std.path : baseName;
53 
54         writefln!"Usage: %s [options] <file>..."(args.front.baseName);
55         writeln("Compile each Extended Affix Grammar file into a compiler.");
56         defaultGetoptPrinter("Options:", result.options);
57         exit(EXIT_SUCCESS);
58     }
59 
60     with (settings)
61     {
62         if (verbose)
63             levels |= Level.trace;
64 
65         if (!slag && !sweep && !soag)
66         {
67             // try all evaluators until one fits
68             slag = true;
69             sweep = true;
70             soag = true;
71         }
72         if (!outputDirectory.empty)
73         {
74             import std.file : mkdirRecurse;
75 
76             mkdirRecurse(outputDirectory);
77         }
78     }
79     try
80     {
81         import std.typecons : No, Yes;
82 
83         const offset = settings.offset ? Yes.offset : No.offset;
84 
85         if (args.dropOne.empty)
86             compile(read("stdin", stdin, offset), settings);
87 
88         foreach (arg; args.dropOne)
89             compile(read(arg, offset), settings);
90     }
91     catch (ErrnoException exception)
92     {
93         error!"%s"(exception.msg);
94         exit(EXIT_FAILURE);
95     }
96     catch (Exception exception)
97     {
98         exit(EXIT_FAILURE);
99     }
100 }
101 
102 void compile(Input input, Settings settings)
103 {
104     import analyzer = epsilon.analyzer;
105     import EAG = epsilon.eag;
106     import ELL1Gen = epsilon.ell1gen;
107     import LexGen = epsilon.lexgen;
108     import Predicates = epsilon.predicates;
109     import SLAGGen = epsilon.slaggen;
110     import SOAGGen = epsilon.soag.soaggen;
111     import Sweep = epsilon.sweep;
112     import std.exception : enforce;
113 
114     analyzer.Analyse(input);
115 
116     enforce(analyzer.ErrorCounter == 0);
117 
118     analyzer.Warnings;
119     Predicates.Check;
120 
121     ELL1Gen.Test(settings);
122 
123     enforce(!ELL1Gen.Error);
124 
125     string[] fileNames;
126     bool success = false;
127 
128     if (settings.slag)
129     {
130         SLAGGen.Test;
131         if (EAG.History & EAG.isSLAG)
132         {
133             fileNames = LexGen.Generate(settings) ~ fileNames;
134             fileNames = ELL1Gen.Generate(settings) ~ fileNames;
135             success = true;
136         }
137     }
138     if (!success && settings.sweep)
139     {
140         Sweep.Test(settings);
141         if (EAG.History & EAG.isSweep)
142         {
143             fileNames = LexGen.Generate(settings) ~ fileNames;
144             fileNames = Sweep.Generate(settings) ~ fileNames;
145             fileNames = ELL1Gen.GenerateParser(settings) ~ fileNames;
146             success = true;
147         }
148     }
149     if (!success && settings.soag)
150     {
151         fileNames = LexGen.Generate(settings) ~ fileNames;
152         fileNames = SOAGGen.Generate(settings) ~ fileNames;
153         if (settings.verbose)
154         {
155             import protocol = epsilon.soag.protocol;
156 
157             protocol.WriteRulesL4;
158             protocol.WriteSyms;
159         }
160         fileNames = ELL1Gen.GenerateParser(settings) ~ fileNames;
161         success = true;
162     }
163 
164     enforce(success);
165 
166     if (!fileNames.empty && !settings.generate)
167         build(fileNames, settings.outputDirectory);
168 }
169 
170 void build(string[] fileNames, string outputDirectory)
171 {
172     import core.stdc.stdlib : exit;
173     import std.format : format;
174     import std.path : stripExtension;
175     import std.process : spawnProcess, wait;
176     import std..string : join;
177 
178     auto args = "dmd" ~ fileNames ~ "-g" ~ "include/runtime.d"
179         ~ "src/io.d" ~ "src/log.d" ~ "src/epsilon/soag/listacks.d";
180 
181     if (!outputDirectory.empty)
182     {
183         args ~= format!"-od=%s"(outputDirectory);
184         args ~= format!"-of=%s"(fileNames.front.stripExtension);
185     }
186     writefln!"%s"(args.join(' '));
187 
188     auto pid = spawnProcess(args);
189     const status = wait(pid);
190 
191     if (status)
192         exit(status);
193 }