Skip to content

Commit 2de072a

Browse files
authored
Introduce translation phase (#101)
This takes the parser's internal Abstract Syntax Tree and lowers it to an Intermediate Representation with symbols resolved and a series of operations to be evaluated. This happens in a new `translation` phase after the `parsing` phase to the compiler. The _technique check_ command is now augmented with an `--until` option that if specified stops the compiler after reaching and completing that phase. The Intermediate Representation deliberately uses type names that are different than those used by the surface language's Abstract Syntax Tree representation. So a Technique language::Procedure becomes program::Subroutine and so on. Reorganized the known-good and known-bad test samples by phase.
2 parents 99db77c + 2f7a407 commit 2de072a

42 files changed

Lines changed: 3082 additions & 65 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "technique"
3-
version = "0.5.5"
3+
version = "0.5.6"
44
edition = "2021"
55
description = "A domain specific language for procedures."
66
authors = [ "Andrew Cowie" ]

src/language/quantity.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
use crate::regex::*;
2424
use std::fmt::{self, Display};
2525

26-
#[derive(Debug, PartialEq, Eq)]
26+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2727
pub struct Quantity<'i> {
2828
pub mantissa: Decimal,
2929
pub uncertainty: Option<Decimal>,

src/language/types.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ impl<'i> Procedure<'i> {
108108
}
109109
}
110110

111-
#[derive(Eq, Debug)]
111+
#[derive(Clone, Copy, Eq, Debug)]
112112
pub struct Identifier<'i> {
113113
pub value: &'i str,
114114
pub span: Span,
@@ -445,7 +445,7 @@ impl PartialEq for Expression<'_> {
445445
}
446446
}
447447

448-
#[derive(Debug, PartialEq, Eq)]
448+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
449449
pub enum Numeric<'i> {
450450
Integral(i64),
451451
Scientific(Quantity<'i>),

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@ pub mod formatting;
33
pub mod highlighting;
44
pub mod language;
55
pub mod parsing;
6+
pub mod program;
67
pub(crate) mod regex;
78
pub mod templating;
9+
pub mod translation;

src/main.rs

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,26 @@ use technique::formatting::{self, Identity};
1010
use technique::highlighting::{self, Terminal};
1111
use technique::parsing;
1212
use technique::templating::{self, Checklist, NasaEsaIss, Procedure, Recipe, Source};
13+
use technique::translation;
1314

1415
mod editor;
1516
mod output;
1617
mod problem;
1718

1819
#[derive(Eq, Debug, PartialEq)]
20+
#[allow(dead_code)]
1921
enum Output {
22+
Terminal,
2023
Native,
2124
Silent,
2225
}
2326

27+
#[derive(Eq, Debug, PartialEq)]
28+
enum Phase {
29+
Parsing,
30+
Translation,
31+
}
32+
2433
fn main() {
2534
const VERSION: &str = concat!("v", env!("CARGO_PKG_VERSION"));
2635

@@ -72,6 +81,18 @@ fn main() {
7281
.action(ArgAction::Set)
7382
.help("Which kind of diagnostic output to print when checking.")
7483
)
84+
.arg(
85+
Arg::new("until")
86+
.long("until")
87+
.value_name("phase")
88+
.value_parser(["parsing", "translation"])
89+
.default_value("parsing")
90+
.action(ArgAction::Set)
91+
.help("Stop compilation after the given phase is complete so that the result can be inspected. \
92+
Use this in conjunction with the --output option. The phases are: \
93+
parsing, where the input is parsed from the surface language to an internal abstract syntax tree; then \
94+
translation, which resolves names, checks references, and ensures the input is valid Technique.")
95+
)
7596
.arg(
7697
Arg::new("filename")
7798
.required(true)
@@ -173,12 +194,23 @@ fn main() {
173194
.unwrap();
174195
let output = match output.as_str() {
175196
"native" => Output::Native,
176-
"none" => Output::Silent,
197+
"none" => Output::Terminal,
177198
_ => panic!("Unrecognized --output value"),
178199
};
179200

180201
debug!(?output);
181202

203+
let until = submatches
204+
.get_one::<String>("until")
205+
.unwrap();
206+
let until = match until.as_str() {
207+
"parsing" => Phase::Parsing,
208+
"translation" => Phase::Translation,
209+
_ => panic!("Unrecognized --until value"),
210+
};
211+
212+
debug!(?until);
213+
182214
let filename = submatches
183215
.get_one::<String>("filename")
184216
.unwrap(); // argument are required by definition so always present
@@ -213,12 +245,51 @@ fn main() {
213245
}
214246
};
215247

216-
// TODO continue with validation of the returned technique
248+
if let Phase::Parsing = until {
249+
match output {
250+
Output::Terminal => {
251+
eprintln!("{}", "ok".bright_green());
252+
}
253+
Output::Native => {
254+
println!("{:#?}", technique);
255+
}
256+
Output::Silent => {}
257+
}
258+
std::process::exit(0);
259+
}
217260

218-
eprintln!("{}", "ok".bright_green());
261+
let program = match translation::translate(&technique) {
262+
Ok(program) => program,
263+
Err(errors) => {
264+
for (i, error) in errors
265+
.iter()
266+
.enumerate()
267+
{
268+
if i > 0 {
269+
eprintln!();
270+
}
271+
eprintln!(
272+
"{}",
273+
problem::concise_translation_error(
274+
&error, &filename, &content, &Terminal
275+
)
276+
);
277+
}
278+
std::process::exit(1);
279+
}
280+
};
219281

220-
if let Output::Native = output {
221-
println!("{:#?}", technique);
282+
if let Phase::Translation = until {
283+
match output {
284+
Output::Terminal => {
285+
eprintln!("{}", "ok".bright_green());
286+
}
287+
Output::Native => {
288+
println!("{:#?}", program);
289+
}
290+
Output::Silent => {}
291+
}
292+
std::process::exit(0);
222293
}
223294
}
224295
Some(("format", submatches)) => {

src/parsing/checks/verify.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1566,7 +1566,7 @@ III. Implementation
15661566

15671567
#[test]
15681568
fn spans_are_populated() {
1569-
let source = std::fs::read_to_string("tests/samples/KnownSpanLengths.tq").unwrap();
1569+
let source = std::fs::read_to_string("tests/samples/parsing/KnownSpanLengths.tq").unwrap();
15701570

15711571
let mut input = Parser::new();
15721572
input.initialize(&source);

src/problem/format.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
use super::messages::generate_error_message;
1+
use super::messages::{generate_error_message, generate_translation_error};
22
use owo_colors::OwoColorize;
33
use std::path::Path;
4-
use technique::{formatting::Render, language::LoadingError, parsing::ParsingError};
4+
use technique::{
5+
formatting::Render, language::LoadingError, parsing::ParsingError,
6+
translation::TranslationError,
7+
};
58

69
/// Format a parsing error with full details including source code context
710
pub fn full_parsing_error<'i>(
@@ -89,6 +92,33 @@ pub fn concise_parsing_error<'i>(
8992
)
9093
}
9194

95+
/// Format a translation error with concise single-line output.
96+
pub fn concise_translation_error<'i>(
97+
error: &TranslationError<'i>,
98+
filename: &'i Path,
99+
source: &'i str,
100+
renderer: &impl Render,
101+
) -> String {
102+
let (problem, _) = generate_translation_error(error, renderer);
103+
let input = generate_filename(filename);
104+
let offset = error
105+
.span()
106+
.offset;
107+
let i = calculate_line_number(source, offset);
108+
let j = calculate_column_number(source, offset);
109+
let line = i + 1;
110+
let column = j + 1;
111+
112+
format!(
113+
"{}: {}:{}:{} {}",
114+
"error".bright_red(),
115+
input,
116+
line,
117+
column,
118+
problem.bold(),
119+
)
120+
}
121+
92122
/// Format a LoadingError with concise single-line output
93123
pub fn concise_loading_error<'i>(error: &LoadingError<'i>) -> String {
94124
format!(

src/problem/messages.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::problem::Present;
2-
use technique::{formatting::Render, language::*, parsing::ParsingError};
2+
use technique::{
3+
formatting::Render, language::*, parsing::ParsingError, translation::TranslationError,
4+
};
35

46
/// Generate problem and detail messages for parsing errors using AST construction
57
pub fn generate_error_message<'i>(error: &ParsingError, renderer: &dyn Render) -> (String, String) {
@@ -1015,3 +1017,34 @@ Hyphens, underscores, spaces, or subscripts are not valid in unit symbols.
10151017
}
10161018
}
10171019
}
1020+
1021+
/// Generate problem and detail messages for translation errors.
1022+
pub fn generate_translation_error<'i>(
1023+
error: &TranslationError<'i>,
1024+
_renderer: &dyn Render,
1025+
) -> (String, String) {
1026+
match error {
1027+
TranslationError::DuplicateProcedure(Identifier { value: name, .. }) => (
1028+
format!("Duplicate procedure name '{}'", name),
1029+
"A procedure with this name has already been declared in this document.".to_string(),
1030+
),
1031+
TranslationError::DuplicateTitle {
1032+
procedure: Identifier { value: name, .. },
1033+
..
1034+
} => (
1035+
format!("Duplicate title in procedure '{}'", name),
1036+
"A procedure can have at most one title.".to_string(),
1037+
),
1038+
TranslationError::InterleavedDescription {
1039+
procedure: Identifier { value: name, .. },
1040+
..
1041+
} => (
1042+
format!("Description out of place in procedure '{}'", name),
1043+
"A procedure's free-text description must appear immediately after the title and before any steps or code blocks.".to_string(),
1044+
),
1045+
TranslationError::UnresolvedProcedure(Identifier { value: name, .. }) => (
1046+
format!("Unresolved procedure '{}'", name),
1047+
"A `<name>` invocation must refer to a procedure declared in this document. Built-in functions use the `name(...)` form (without angle brackets).".to_string(),
1048+
),
1049+
}
1050+
}

src/program/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//! Intermediate Representation suitable for an interpreter.
2+
3+
mod types;
4+
5+
// Re-export all public symbols
6+
pub use types::*;

0 commit comments

Comments
 (0)