Skip to main content

ironwork_syntax/
lib.rs

1//! ironwork for COBOL: from fixed-format source text to a syntax tree. Nothing here knows about
2//! storage or execution.
3
4pub mod ast;
5pub mod bms;
6pub mod copy;
7pub mod lexer;
8pub mod parser;
9pub mod source;
10pub mod system;
11
12use std::fmt;
13
14/// Where a diagnostic points: 1-based source line and column.
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
16pub struct Pos {
17    /// Index into the program's file table: 0 for the program itself, then each COPY member.
18    pub file: u16,
19    pub line: u32,
20    pub col: u32,
21}
22
23impl fmt::Display for Pos {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        write!(f, "{}:{}", self.line, self.col)
26    }
27}
28
29#[derive(Clone, Debug, PartialEq, Eq)]
30pub struct Error {
31    pub pos: Pos,
32    pub message: String,
33    /// The COPY member the position is in, when it is not the program itself.
34    pub file: Option<String>,
35}
36
37impl Error {
38    pub fn at(pos: Pos, message: impl Into<String>) -> Self {
39        Self { pos, message: message.into(), file: None }
40    }
41
42    /// Names the member a position in a COPY member came from.
43    pub fn in_files(mut self, files: &[String]) -> Self {
44        self.file = files.get(self.pos.file as usize).filter(|f| !f.is_empty()).cloned();
45        self
46    }
47
48    /// `file:line:col: message`, with `main` naming the program itself.
49    pub fn place(&self, main: &str) -> String {
50        match (&self.file, self.pos.line) {
51            (Some(f), _) => format!("{f}:{self}"),
52            (None, 0) => format!("{main}: {}", self.message),
53            (None, _) => format!("{main}:{self}"),
54        }
55    }
56}
57
58impl fmt::Display for Error {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        write!(f, "{}: {}", self.pos, self.message)
61    }
62}
63
64impl std::error::Error for Error {}
65
66/// The first program in the source.
67pub fn parse(text: &str) -> Result<ast::Program, Error> {
68    parse_with(text, &copy::Libraries::default())
69}
70
71/// The first program in the source, with COPY members resolved from `libraries`.
72pub fn parse_with(text: &str, libraries: &copy::Libraries) -> Result<ast::Program, Error> {
73    Ok(parse_all_with(text, libraries)?.remove(0))
74}
75
76/// Every program in the source, in order, nested programs after the one that contains them.
77pub fn parse_all_with(text: &str, libraries: &copy::Libraries) -> Result<Vec<ast::Program>, Error> {
78    let mut files = vec![String::new()];
79    let source = source::read(text).and_then(|s| copy::expand(s, libraries, &mut files)).map_err(|e| e.in_files(&files))?;
80    let tokens = lexer::lex(&source).map_err(|e| e.in_files(&files))?;
81    let mut programs = parser::parse(&tokens, source.options).map_err(|e| e.in_files(&files))?;
82    for p in &mut programs {
83        p.sources = files.clone();
84    }
85    Ok(programs)
86}