Add first warning to compiler output
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
use std::{error::Error, fmt::Display, ops::Range};
|
||||
|
||||
use crate::world::World;
|
||||
|
||||
pub type CompileResult = Result<Vec<CompileMessage>, Vec<CompileMessage>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CompileMessage {
|
||||
pub level: Level,
|
||||
pub message: String,
|
||||
pub location: Location,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Level {
|
||||
Error,
|
||||
Warning,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Location {
|
||||
pub file_path: Option<String>,
|
||||
pub board: Option<usize>,
|
||||
pub stat: Option<usize>,
|
||||
pub span: Option<Range<usize>>,
|
||||
}
|
||||
|
||||
impl Display for CompileMessage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
let level = match self.level {
|
||||
Level::Error => "Error",
|
||||
Level::Warning => "Warning",
|
||||
};
|
||||
write!(f, "{}: {}", level, self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for CompileMessage {}
|
||||
|
||||
impl CompileMessage {
|
||||
pub fn rich_format(&self, world: &World) -> String {
|
||||
format!(
|
||||
"{}\n{}",
|
||||
self.to_string(),
|
||||
self.location.rich_format(&world)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Location {
|
||||
pub fn rich_format(&self, world: &World) -> String {
|
||||
let board = self.board.map(|i| &world.boards[i]);
|
||||
let stat = self.stat.and_then(|i| board.map(|b| &b.stats[i]));
|
||||
|
||||
let mut parts: Vec<Option<String>> = vec![];
|
||||
parts.push(self.file_path.clone());
|
||||
parts.push(board.map(|board| board.name.clone()));
|
||||
parts.push(stat.map(|stat| format!("stat({},{})", stat.x, stat.y)));
|
||||
parts.push(self.span.as_ref().and_then(|span| {
|
||||
stat.map(|stat| {
|
||||
let mut line = 1;
|
||||
let mut col = 1;
|
||||
for c in (&stat.code[0..span.start]).chars() {
|
||||
if c == '\n' {
|
||||
line += 1;
|
||||
col = 1;
|
||||
} else {
|
||||
col += 1;
|
||||
}
|
||||
}
|
||||
format!("line {line}, column {col}")
|
||||
})
|
||||
}));
|
||||
|
||||
while parts.len() > 0 && parts[parts.len() - 1].is_none() {
|
||||
parts.pop();
|
||||
}
|
||||
let parts: Vec<_> = parts
|
||||
.into_iter()
|
||||
.map(|x| x.unwrap_or("???".into()))
|
||||
.collect();
|
||||
parts.join(", ")
|
||||
}
|
||||
}
|
||||
+30
-4
@@ -3,7 +3,11 @@ use std::ops::Range;
|
||||
use compact_str::CompactString;
|
||||
use grammar::Tag;
|
||||
|
||||
use crate::{peg::ParseState, world::Stat};
|
||||
use crate::{
|
||||
error::{CompileMessage, Level, Location},
|
||||
peg::ParseState,
|
||||
world::Stat,
|
||||
};
|
||||
|
||||
pub type ParsedStat = Vec<Chunk>;
|
||||
|
||||
@@ -24,7 +28,7 @@ pub struct LabelName {
|
||||
pub local: Option<CompactString>,
|
||||
}
|
||||
|
||||
pub fn parse_stat_labels(stat: &Stat) -> ParsedStat {
|
||||
pub fn parse_stat_labels(stat: &Stat) -> (ParsedStat, Vec<CompileMessage>) {
|
||||
let code = &stat.code;
|
||||
let mut parser = ParseState::new(code);
|
||||
assert!(
|
||||
@@ -33,6 +37,25 @@ pub fn parse_stat_labels(stat: &Stat) -> ParsedStat {
|
||||
code
|
||||
);
|
||||
|
||||
let mut messages = vec![];
|
||||
for cap in parser.walk_captures() {
|
||||
match cap.kind() {
|
||||
Tag::WarnTrailing => {
|
||||
messages.push(CompileMessage {
|
||||
level: Level::Warning,
|
||||
message: "Trailing characters at end of line".into(),
|
||||
location: Location {
|
||||
file_path: None,
|
||||
board: None,
|
||||
stat: None,
|
||||
span: None,
|
||||
},
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Find all #Label captures and record which ones were references
|
||||
let mut label_captures = vec![];
|
||||
for cap in parser.captures() {
|
||||
@@ -83,7 +106,7 @@ pub fn parse_stat_labels(stat: &Stat) -> ParsedStat {
|
||||
if last_index < code.len() {
|
||||
result.push(Chunk::Verbatim(code[last_index..code.len()].into()));
|
||||
}
|
||||
result
|
||||
(result, messages)
|
||||
}
|
||||
|
||||
mod grammar {
|
||||
@@ -139,7 +162,7 @@ mod grammar {
|
||||
"zap" sp message
|
||||
) /
|
||||
message // shorthand send
|
||||
) s eol; // note: this allows trailing (ignored) whitespace
|
||||
) warn_trailing eol;
|
||||
|
||||
//
|
||||
// Common definitions
|
||||
@@ -205,6 +228,9 @@ mod grammar {
|
||||
&'t'..'w' ("tiger" / "torch" / "transporter" / "water")
|
||||
) eow;
|
||||
|
||||
// Warnings
|
||||
warn_trailing = (#WarnTrailing:(!eol ANY)+)?; // TODO: Document precedence rules
|
||||
|
||||
//
|
||||
// Generic helpers
|
||||
//
|
||||
|
||||
+21
-7
@@ -1,19 +1,31 @@
|
||||
use compact_str::CompactString;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::world::Board;
|
||||
use crate::{
|
||||
error::{CompileMessage, CompileResult},
|
||||
world::Board,
|
||||
};
|
||||
|
||||
use super::{
|
||||
parse::{Chunk, ParsedStat, parse_stat_labels},
|
||||
sanitize::Registry,
|
||||
};
|
||||
|
||||
pub fn process_labels(board: &mut Board) {
|
||||
pub fn process_labels(board: &mut Board) -> CompileResult {
|
||||
// Parse stats into chunks
|
||||
let mut messages: Vec<CompileMessage> = vec![];
|
||||
let mut stats: Vec<ParsedStat> = board
|
||||
.stats
|
||||
.iter()
|
||||
.map(|stat| parse_stat_labels(&stat))
|
||||
.enumerate()
|
||||
.map(|(index, stat)| {
|
||||
let (parsed_stat, mut new_messages) = parse_stat_labels(&stat);
|
||||
new_messages
|
||||
.iter_mut()
|
||||
.for_each(|m| m.location.stat = Some(index));
|
||||
messages.extend(new_messages);
|
||||
parsed_stat
|
||||
})
|
||||
.collect();
|
||||
let mut registry = Registry::new();
|
||||
|
||||
@@ -39,6 +51,8 @@ pub fn process_labels(board: &mut Board) {
|
||||
.collect();
|
||||
old_stat.code = new_code;
|
||||
}
|
||||
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
/// Resolve ".local" labels to "name.local" form.
|
||||
@@ -244,28 +258,28 @@ mod test {
|
||||
#[test]
|
||||
fn test_label_sanitization() {
|
||||
let mut board = board_from_text("tests/labels/sanitize.txt");
|
||||
process_labels(&mut board);
|
||||
let _ = process_labels(&mut board);
|
||||
assert_snapshot!(board_to_text(board));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_anonymous_labels() {
|
||||
let mut board = board_from_text("tests/labels/anonymous.txt");
|
||||
process_labels(&mut board);
|
||||
let _ = process_labels(&mut board);
|
||||
assert_snapshot!(board_to_text(board));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_local_labels() {
|
||||
let mut board = board_from_text("tests/labels/local.txt");
|
||||
process_labels(&mut board);
|
||||
let _ = process_labels(&mut board);
|
||||
assert_snapshot!(board_to_text(board));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_namespaces() {
|
||||
let mut board = board_from_text("tests/labels/namespaces.txt");
|
||||
process_labels(&mut board);
|
||||
let _ = process_labels(&mut board);
|
||||
assert_snapshot!(board_to_text(board));
|
||||
}
|
||||
}
|
||||
|
||||
+17
-2
@@ -1,4 +1,5 @@
|
||||
mod encoding;
|
||||
mod error;
|
||||
mod labels;
|
||||
mod peg;
|
||||
mod preprocess;
|
||||
@@ -36,8 +37,22 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
|
||||
// Resolve labels to proper ZZT-OOP
|
||||
for mut board in &mut world.boards {
|
||||
process_labels(&mut board);
|
||||
let mut messages = vec![];
|
||||
for (i, mut board) in world.boards.iter_mut().enumerate() {
|
||||
let mut result = process_labels(&mut board).unwrap();
|
||||
for message in result.iter_mut() {
|
||||
message.location.board = Some(i);
|
||||
message.location.file_path = Some(world_filename.clone());
|
||||
}
|
||||
messages.extend(result);
|
||||
}
|
||||
|
||||
// Print diagnostics
|
||||
for (i, message) in messages.iter().enumerate() {
|
||||
if i > 0 {
|
||||
println!();
|
||||
}
|
||||
println!("{}", message.rich_format(&world));
|
||||
}
|
||||
|
||||
// Try to write a modified world file
|
||||
|
||||
Reference in New Issue
Block a user