oakc/
lib.rs

1#![allow(warnings, clippy, unknown_lints)]
2use std::{collections::BTreeMap, fmt::Display, io::Result, path::PathBuf, process::exit};
3pub type Identifier = String;
4pub type StringLiteral = String;
5
6pub mod asm;
7pub mod hir;
8pub mod mir;
9pub mod tir;
10use hir::HirProgram;
11use tir::TirProgram;
12
13mod target;
14pub use target::{Go, Target, C, TS};
15
16use asciicolor::Colorize;
17use comment::cpp::strip;
18
19use lalrpop_util::{lalrpop_mod, ParseError};
20lalrpop_mod!(pub parser);
21
22pub fn generate_docs(cwd: &PathBuf, input: impl ToString, filename: impl ToString, target: impl Target) -> String {
23    match parse(input).compile(cwd) {
24        Ok(output) => output,
25        Err(e) => print_compile_error(e)
26    }.generate_docs(filename.to_string(), &target, &mut BTreeMap::new(), false)
27}
28
29fn print_compile_error(e: impl Display) -> ! {
30    eprintln!("compilation error: {}", e.bright_red().underline());
31    exit(1);
32}
33
34pub fn compile(cwd: &PathBuf, input: impl ToString, target: impl Target) -> Result<()> {
35    let mut tir = parse(input);
36    let mut hir = match tir.compile(cwd) {
37        Ok(output) => output,
38        Err(e) => print_compile_error(e)
39    };
40
41    hir.extend_declarations(match parse(include_str!("core.ok")).compile(cwd) {
42        Ok(output) => output,
43        Err(e) => print_compile_error(e)
44    }.get_declarations());
45
46    if hir.use_std() {
47        hir.extend_declarations(match parse(include_str!("std.ok")).compile(cwd) {
48            Ok(output) => output,
49            Err(e) => print_compile_error(e)
50        }.get_declarations());
51    }
52
53    match hir.compile(cwd, &target, &mut BTreeMap::new()) {
54        Ok(mir) => match mir.assemble() {
55            Ok(asm) => match asm.assemble(&target) {
56                Ok(result) => target.compile(if hir.use_std() {
57                    target.core_prelude() + &target.std() + &result + &target.core_postlude()
58                } else {
59                    target.core_prelude() + &result + &target.core_postlude()
60                }),
61                Err(e) => print_compile_error(e),
62            },
63            Err(e) => print_compile_error(e),
64        },
65        Err(e) => print_compile_error(e),
66    }
67}
68
69pub fn parse(input: impl ToString) -> TirProgram {
70    let code = &strip(input.to_string()).unwrap();
71    match parser::ProgramParser::new().parse(code) {
72        // if the parser succeeds, build will succeed
73        Ok(parsed) => parsed,
74        // if the parser succeeds, annotate code with comments
75        Err(e) => {
76            eprintln!("{}", format_error(&code, e));
77            exit(1);
78        }
79    }
80}
81
82type Error<'a, T> = ParseError<usize, T, &'a str>;
83
84/// This formats an error properly given the line, the `unexpected` token as a string,
85/// the line number, and the column number of the unexpected token.
86fn make_error(line: &str, unexpected: &str, line_number: usize, column_number: usize) -> String {
87    // The string used to underline the unexpected token
88    let underline = format!(
89        "{}^{}",
90        " ".repeat(column_number),
91        "-".repeat(unexpected.len() - 1)
92    );
93
94    // Format string properly and return
95    format!(
96        "{WS} |
97{line_number} | {line}
98{WS} | {underline}
99{WS} |
100{WS} = unexpected `{unexpected}`",
101        WS = " ".repeat(line_number.to_string().len()),
102        line_number = line_number,
103        line = line.bright_yellow().underline(),
104        underline = underline,
105        unexpected = unexpected.bright_yellow().underline()
106    )
107}
108
109// Gets the line number, the line, and the column number of the error
110fn get_line(script: &str, location: usize) -> (usize, String, usize) {
111    // Get the line number from the character location
112    let line_number = script[..location + 1].lines().count();
113    // Get the line from the line number
114    let line = match script.lines().nth(line_number - 1) {
115        Some(line) => line,
116        None => {
117            if let Some(line) = script.lines().last() {
118                line
119            } else {
120                ""
121            }
122        }
123    }
124    .replace("\t", "    ");
125
126    // Get the column number from the location
127    let mut column = {
128        let mut current_column = 0;
129        // For every character in the script until the location of the error,
130        // keep track of the column location
131        for ch in script[..location].chars() {
132            if ch == '\n' {
133                current_column = 0;
134            } else if ch == '\t' {
135                current_column += 4;
136            } else {
137                current_column += 1;
138            }
139        }
140        current_column
141    };
142
143    // Trim the beginning of the line and subtract the number of spaces from the column
144    let trimmed_line = line.trim_start();
145    column -= (line.len() - trimmed_line.len()) as i32;
146
147    (line_number, String::from(trimmed_line), column as usize)
148}
149
150/// This is used to take an LALRPOP error and convert
151/// it into a nicely formatted error message
152fn format_error<T: core::fmt::Debug>(script: &str, err: Error<T>) -> String {
153    match err {
154        Error::InvalidToken { location } => {
155            let (line_number, line, column) = get_line(script, location);
156            make_error(
157                &line,
158                &(script.as_bytes()[location] as char).to_string(),
159                line_number,
160                column,
161            )
162        }
163        Error::UnrecognizedEOF { location, .. } => {
164            let (line_number, line, _) = get_line(script, location);
165            make_error(&line, "EOF", line_number, line.len())
166        }
167        Error::UnrecognizedToken { token, .. } => {
168            // The start and end of the unrecognized token
169            let start = token.0;
170            let end = token.2;
171
172            let (line_number, line, column) = get_line(script, start);
173            let unexpected = &script[start..end];
174            make_error(&line, unexpected, line_number, column)
175        }
176        Error::ExtraToken { token } => {
177            // The start and end of the extra token
178            let start = token.0;
179            let end = token.2;
180
181            let (line_number, line, column) = get_line(script, start);
182            let unexpected = &script[start..end];
183
184            make_error(&line, unexpected, line_number, column)
185        }
186        Error::User { error } => format!(
187            "  |\n? | {}\n  | {}\n  |\n  = unexpected compiling error",
188            error,
189            format!("^{}", "-".repeat(error.len() - 1))
190        ),
191    }
192}