recc/front/
mod.rs

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}