1use std::{
2 env,
3 fs::read_to_string,
4 io::{BufRead, BufReader, Write},
5 net::{TcpListener, TcpStream},
6 path::Path,
7 process::exit,
8};
9
10use asm::asm_html;
11use diagram::{DARKDIAGRAM, LIGHTDIAGRAM};
12use lex::lex_stage_html;
13use parse::parse_stage_html;
14use rayon::iter::{ParallelBridge, ParallelIterator};
15use semanal::semantic_analysis_stage_html;
16use tactile::tactile_stage_html;
17
18use crate::{
19 Program, asm::asm, lex::Lexed, parse::parse, semantic_analysis::sem_anal, tactile::tactile,
20 write::write_program,
21};
22
23pub mod asm;
24mod diagram;
25pub mod lex;
26pub mod parse;
27pub mod semanal;
28pub mod tactile;
29
30fn redirect_html(msg: &str, light: bool) -> String {
31 let background_color = if light { "ffffff" } else { "000000" };
32 let foreground_color = if light { "000000" } else { "ffffff" };
33 format!("<!DOCTYPE html>
34<html>
35<style>
36 body {{
37 background-color: #{background_color};
38 color: #{foreground_color};
39 line-height: 1.7;
40 font-family: monospace;
41 font-size: 100px;
42 text-align: center;
43 }}
44</style>
45
46<head>
47 <meta http-equiv=\"Refresh\" content=\"1; url='{msg}'\" />
48</head>
49
50<body style=>
51 <p>You'll be lexed shortly.</p>
52</body>
53
54</html>")
55}
56
57static DARKSTYLE: &str = r"<!DOCTYPE html>
58<html>
59<head>
60<style>
61 .keyword {
62 background: #ff000066;
63 padding: 0.2em 0.2em 0.2em 0.2em;
64 }
65
66 .literal {
67 background: #ffcc0066;
68 padding: 0.2em 0.2em 0.2em 0.2em;
69 }
70
71 .identifier {
72 background: #ffffff66;
73 padding: 0.2em 0.2em 0.2em 0.2em;
74 }
75
76 .operator {
77 background: #0000ff66;
78 padding: 0.2em 0.2em 0.2em 0.2em;
79 }
80
81 .miscSymbol {
82 background: #c0c0c066;
83 padding: 0.2em 0.2em 0.2em 0.2em;
84 }
85
86 .flex-container {
87 display: flex;
88 flex-wrap: nowrap;
89 flex: 1 1 0;
90 }
91
92 .normal-code-container {
93 padding: 0em 2em 0em 0em;
94 word-spacing: 3px;
95 }
96
97 .token-container {
98 padding: 0em 2em 0em 0em;
99 min-width: 30em;
100 }
101
102 .next-button {
103 border: none;
104 background-color: #111111;
105 color: #AAAAAA;
106 padding: 1em 2em;
107 text-align: center;
108 width:100%;
109 font-size: 20px;
110 }
111
112 .tooltip {
113 position: relative;
114 display: inline-block;
115 border-bottom: 1px dotted white;
116 }
117
118 .tooltip .tooltiptext {
119 visibility: hidden;
120 width: 120px;
121 background-color: black;
122 color: #fff;
123 text-align: center;
124 border-radius: 6px;
125 padding: 5px 0;
126
127 /* Position the tooltip */
128 position: absolute;
129 z-index: 1;
130 bottom: 100%;
131 left: 50%;
132 margin-left: -60px;
133
134 opacity: 0;
135 transition: opacity 1s;
136 }
137
138 .tooltip:hover .tooltiptext {
139 visibility: visible;
140 opacity: 1;
141 }
142
143 body {
144 background-color: #000000;
145 color: #ffffff;
146 line-height: 2;
147 font-family: monospace;
148 font-size: 22px;
149 }
150</style>
151";
152
153static LIGHTSTYLE: &str = r"<!DOCTYPE html>
154<html>
155<head>
156<style>
157 .keyword {
158 background: #f008;
159 padding: 0.2em 0.2em 0.2em 0.2em;
160 }
161
162 .literal {
163 background: #fc08;
164 padding: 0.2em 0.2em 0.2em 0.2em;
165 }
166
167 .identifier {
168 background: #fff8;
169 padding: 0.2em 0.2em 0.2em 0.2em;
170 }
171
172 .operator {
173 background: #00f8;
174 padding: 0.2em 0.2em 0.2em 0.2em;
175 }
176
177 .miscSymbol {
178 background: #c0c0c088;
179 padding: 0.2em 0.2em 0.2em 0.2em;
180 }
181
182 .flex-container {
183 display: flex;
184 flex-wrap: nowrap;
185 flex: 1 1 0;
186 }
187
188 .normal-code-container {
189 padding: 0em 2em 0em 0em;
190 word-spacing: 3px;
191 }
192
193 .token-container {
194 padding: 0em 2em 0em 0em;
195 min-width: 30em;
196 }
197
198 .next-button {
199 border: none;
200 background-color: #EEE;
201 color: #777;
202 padding: 1em 2em;
203 text-align: center;
204 width:100%;
205 font-size: 20px;
206 }
207
208 .tooltip {
209 position: relative;
210 display: inline-block;
211 border-bottom: 1px dotted black;
212 }
213
214 .tooltip .tooltiptext {
215 visibility: hidden;
216 width: 120px;
217 background-color: white;
218 color: #000;
219 text-align: center;
220 border-radius: 6px;
221 padding: 5px 0;
222
223 /* Position the tooltip */
224 position: absolute;
225 z-index: 1;
226 bottom: 100%;
227 left: 50%;
228 margin-left: -60px;
229
230 opacity: 0;
231 transition: opacity 1s;
232 }
233
234 .tooltip:hover .tooltiptext {
235 visibility: visible;
236 opacity: 1;
237 }
238
239 body {
240 background-color: #fff;
241 color: #000;
242 line-height: 2;
243 font-family: monospace;
244 font-size: 22px;
245 }
246</style>
247";
248
249pub static EPILOGUE: &str = r"
250</body>
251
252</html>";
253
254fn file_style() -> Option<String> {
255 let custom_file = env::var("CUSTOM_RECC_FRONTEND_CSS").ok()?;
256 let path = Path::new(&custom_file);
257 if !path.exists() {
258 return None;
259 }
260
261 Some(format!(
262 "\n<!DOCTYPE html>\n<html>\n<head>\n<style>\n{}\n</style>\n",
263 read_to_string(path).ok()?
264 ))
265}
266
267struct AllState {
268 pub lexed: String,
269 pub parsed: String,
270 pub semantically_analyzed: String,
271 pub tactile: String,
272 pub asm: String,
273 pub light: bool,
274}
275
276fn generate_html(program: Program<Lexed>) -> AllState {
277 let light = program.light_mode;
278 let pot_style = file_style();
279 let style: &str = match (&pot_style, light) {
280 (Some(string), _) => string,
281 (None, true) => LIGHTSTYLE,
282 (None, false) => DARKSTYLE,
283 };
284
285 let (lexed, tokened) = lex_stage_html(&program.state, style);
286
287 let program = parse(program).unwrap_or_else(|f| {
288 eprintln!("{f}");
289 exit(1);
290 });
291
292 let (parsed, pre_graph) = parse_stage_html(&program, &tokened, style, light);
293
294 let (program, static_vars) = sem_anal(program).unwrap_or_else(|f| {
295 eprintln!("{f}");
296 exit(1);
297 });
298
299 let (semantically_analyzed, post_graph) = semantic_analysis_stage_html(&program.state, pre_graph, style, light);
300
301 let program = tactile((program, static_vars));
302 let (tactile, tactile_tops) = tactile_stage_html(&program.state, post_graph, style);
303
304 let program = write_program(asm(program));
305
306 let asm = asm_html(program.state, &tactile_tops, style);
307
308 AllState {
309 lexed,
310 parsed,
311 semantically_analyzed,
312 tactile,
313 asm,
314 light,
315 }
316}
317
318pub fn frontend(program: Program<Lexed>) -> std::io::Result<()> {
319 let state = generate_html(program);
320 Ok(TcpListener::bind("127.0.0.1:7878")?
321 .incoming()
322 .par_bridge()
323 .filter_map(|f| Some(handle_connection(f.ok()?, &state)))
324 .for_each(drop))
325}
326
327fn handle_connection(mut stream: TcpStream, state: &AllState) -> Option<()> {
328 let request = BufReader::new(&stream).lines().next()?.ok()?;
329
330 let mut request_split = request.split(' ');
331 let (Some("GET"), Some(path)) = (request_split.next(), request_split.next()) else {
332 return None;
333 };
334 let path = path.trim_start_matches('/');
335
336 if path.is_empty() {
337 let html = redirect_html("http://localhost:7878/lex", state.light);
338
339 _ = stream.write_all(&format!("HTTP/1.1 200 OK\r\n\r\n\r\n\r\n{html}").into_bytes());
340 } else if path == "lex" {
341 let response = format!("HTTP/1.1 200 OK\r\n\r\n\r\n\r\n{}", state.lexed);
342
343 stream.write_all(response.as_bytes()).ok()?;
344 } else if path == "parse" {
345 let response = format!("HTTP/1.1 200 OK\r\n\r\n\r\n\r\n{}", state.parsed);
346
347 stream.write_all(response.as_bytes()).ok()?;
348 } else if path == "semanal" {
349 let response = format!("HTTP/1.1 200 OK\r\n\r\n\r\n\r\n{}", state.semantically_analyzed);
350
351 stream.write_all(response.as_bytes()).ok()?;
352 } else if path == "tactile" {
353 let response = format!("HTTP/1.1 200 OK\r\n\r\n\r\n\r\n{}", state.tactile);
354
355 stream.write_all(response.as_bytes()).ok()?;
356 } else if path == "asm" {
357 let response = format!("HTTP/1.1 200 OK\r\n\r\n\r\n\r\n{}", state.asm);
358
359 stream.write_all(response.as_bytes()).ok()?;
360 } else if path.starts_with("html") {
361 let diagram = if state.light { LIGHTDIAGRAM } else { DARKDIAGRAM };
362 let response = format!("HTTP/1.1 200 OK\r\n\r\n\r\n\r\n{diagram}");
363
364 stream.write_all(response.as_bytes()).ok()?;
365 }
366
367 Some(())
368}