From 10fb87de09cbbd03768d52cee42fd39fc618e407 Mon Sep 17 00:00:00 2001 From: Avery Date: Thu, 6 Mar 2025 23:32:06 +0100 Subject: [PATCH] Frontend and Error printing --- src/error.rs | 213 ++++++++++++++++++++++++++++++++++ src/main.rs | 318 ++++++++++++++++++++++++++++++++++----------------- 2 files changed, 424 insertions(+), 107 deletions(-) create mode 100644 src/error.rs diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..54e99f5 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,213 @@ +use std::fmt::{self, Display, Write}; + +use crate::{ + compiler::CompilerError, + parser::{Location, ParseError}, +}; + +pub fn print_error(err: E, src: Option<&str>) { + let mut buf = String::new(); + match src { + Some(src) => { + err.fmt_with_src(&mut buf, &src).unwrap(); + } + None => err.fmt(&mut buf).unwrap(), + } + print!("{buf}"); +} + +pub trait FormattedLocatedError { + fn fmt(&self, w: &mut W) -> fmt::Result; + fn fmt_with_src(&self, w: &mut W, src: &str) -> fmt::Result; +} + +impl Display for Location { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Location::Char { line, col } => write!(f, "{line}:{col}"), + Location::String { lines, cols } => write!(f, "{}:{}", lines.start(), cols.start()), + Location::All => write!(f, "Entire file"), + } + } +} + +impl FormattedLocatedError for ParseError { + fn fmt(&self, w: &mut W) -> fmt::Result { + match self { + ParseError::UnexpectedParenClose(location) => { + writeln!(w, "Unexpected closing paren at {location}")? + } + ParseError::UnexpectedEof => writeln!(w, "Unexpected end of file")?, + } + + Ok(()) + } + + fn fmt_with_src(&self, w: &mut W, src: &str) -> fmt::Result { + match self { + ParseError::UnexpectedParenClose(location) => { + writeln!(w, "Unexpected closing paren at {location}")?; + here(location, src, Some("Unexpected ')'".to_string()), w)?; + } + ParseError::UnexpectedEof => writeln!(w, "Unexpected end of file")?, + } + + Ok(()) + } +} + +impl FormattedLocatedError for CompilerError { + fn fmt(&self, w: &mut W) -> fmt::Result { + match self { + CompilerError::ExpectedSymbol(location) => { + writeln!( + w, + "Expected a symbol but got a list or nothing at {location}" + ) + } + CompilerError::ExpectedList(location) => { + writeln!( + w, + "Expected a list but got a symbol or nothing at {location}" + ) + } + CompilerError::UnknownKeyword(k, location) => { + writeln!(w, "Got unexpected keyword {k} at {location}")?; + writeln!(w, "Expected one of: module, import, bind, dec, entry, fun") + } + CompilerError::ExpectedOneOf(vec, k, location) => { + writeln!(w, "Got unexpected token {k} at {location}")?; + writeln!(w, "Expected one of: {}", vec.join(", ")) + } + CompilerError::TrailingTokens(location) => { + writeln!(w, "Got unexpected trailing tokens at {location}")?; + writeln!(w, "Expected end of list") + } + CompilerError::UnknownBind(b, location) => { + writeln!(w, "Got unexpected keyword {b} at {location}")?; + writeln!(w, "Expected one of: BuiltIn, Location") + } + CompilerError::ExpectedType(location) => { + writeln!(w, "Expected name:type but got name at {location}") + } + } + } + + fn fmt_with_src(&self, w: &mut W, src: &str) -> fmt::Result { + match self { + CompilerError::ExpectedSymbol(location) => { + writeln!( + w, + "Expected a symbol but got a list or nothing at {location}" + )?; + here(location, src, Some("Expected symbol".to_string()), w) + } + CompilerError::ExpectedList(location) => { + writeln!( + w, + "Expected a list but got a symbol or nothing at {location}" + )?; + here(location, src, Some("Expected List".to_string()), w) + } + CompilerError::UnknownKeyword(k, location) => { + writeln!(w, "Got unexpected keyword {k} at {location}")?; + writeln!(w, "Expected one of: module, import, bind, dec, entry, fun")?; + here(location, src, Some("Unexpected keyword".to_string()), w) + } + CompilerError::ExpectedOneOf(vec, k, location) => { + writeln!(w, "Got unexpected token {k} at {location}")?; + writeln!(w, "Expected one of: {}", vec.join(", "))?; + here(location, src, Some("Unexpected token".to_string()), w) + } + CompilerError::TrailingTokens(location) => { + writeln!(w, "Got unexpected trailing tokens at {location}")?; + writeln!(w, "Expected end of list")?; + here(location, src, Some("Unexpected token".to_string()), w) + } + CompilerError::UnknownBind(b, location) => { + writeln!(w, "Got unexpected keyword {b} at {location}")?; + writeln!(w, "Expected one of: BuiltIn, Location")?; + here(location, src, Some("Unexpected keyword".to_string()), w) + } + CompilerError::ExpectedType(location) => { + writeln!(w, "Expected name:type but got name at {location}")?; + here(location, src, None, w) + } + } + } +} + +fn here( + loc: &Location, + src: &str, + comment: Option, + w: &mut W, +) -> fmt::Result { + writeln!(w, "Here:")?; + writeln!( + w, + "{}", + annotate_src(&loc, src, comment.unwrap_or_default()) + )?; + + Ok(()) +} + +fn annotate_src(loc: &Location, src: &str, comment: String) -> String { + let src_lines = src.split('\n').collect::>(); + + let mut buf = String::new(); + + match loc { + Location::Char { line, col } => { + writeln!(buf, "{}|{}", line_num(*line - 1), src_lines[*line - 2]).unwrap(); + writeln!(buf, "{}|{}", line_num(*line), src_lines[*line - 1]).unwrap(); + writeln!(buf, "{}|{}^ {}", " ", " ".repeat(col - 1), comment).unwrap(); + writeln!(buf, "{}|{}", line_num(*line + 1), src_lines[*line]).unwrap(); + } + Location::String { lines, cols } => { + writeln!( + buf, + "{}|{}", + line_num(*lines.start() - 1), + src_lines[*lines.start() - 2] + ) + .unwrap(); + writeln!( + buf, + "{}", + src_lines[(lines.start() - 1)..*lines.end()] + .iter() + .zip(lines.clone()) + .map(|(l, n)| format!("{}|{}", line_num(n), l)) + .collect::>() + .join("\n") + ) + .unwrap(); + writeln!( + buf, + "{}|{}{} {}", + " ", + " ".repeat(cols.start() - 1), + "^".repeat(cols.end() - cols.start() + 1), + comment + ) + .unwrap(); + writeln!( + buf, + "{}|{}", + line_num(*lines.end() + 1), + src_lines[*lines.end()] + ) + .unwrap(); + } + Location::All => todo!(), + } + + buf +} + +fn line_num(num: usize) -> String { + let num = format!("{num}"); + format!("{num}{}", " ".repeat(6 - num.len())) +} diff --git a/src/main.rs b/src/main.rs index 2374721..9e9b216 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,115 +1,219 @@ -use std::fmt::Write; +use compiler::{backend::spirv_meta, meta_compile}; +use error::print_error; +use std::{env, fs, path::PathBuf}; + +use parser::parse_string; pub mod compiler; +mod error; pub mod parser; +#[derive(Debug, Default)] +struct CompilerArgs { + input: Option, + output: Option, + print_help: bool, + print_version: bool, +} + +const SPIRV_VERSION: &'static str = "1.0"; + fn main() { - let mut ops: Vec<(Option, Vec)> = Vec::new(); - - // OpMemoryModel Logical GLSL450 - // OpEntryPoint Fragment %main "main" - // OpExecutionMode %main OriginUpperLeft - // OpSource GLSL 450 - // OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" - // OpSourceExtension "GL_GOOGLE_include_directive" - // OpName %main "main" - //%void = OpTypeVoid - //%3 = OpTypeFunction %void - //%main = OpFunction %void None %3 - //%5 = OpLabel - // OpReturn - // OpFunctionEnd - - ops.push((None, vec!["OpCapability".to_string(), "Shader".to_string()])); - ops.push(( - Some("%1".to_string()), - vec![ - "OpExtInstImport".to_string(), - "\"GLSL.std.450\"".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpMemoryModel".to_string(), - "Logical".to_string(), - "GLSL450".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpEntryPoint".to_string(), - "Fragment".to_string(), - "%main".to_string(), - "\"main\"".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpExecutionMode".to_string(), - "%main".to_string(), - "OriginUpperLeft".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpSource".to_string(), - "GLSL".to_string(), - "450".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpSourceExtension".to_string(), - "\"GL_GOOGLE_cpp_style_line_directive\"".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpSourceExtension".to_string(), - "\"GL_GOOGLE_include_directive\"".to_string(), - ], - )); - ops.push(( - None, - vec![ - "OpName".to_string(), - "%main".to_string(), - "\"main\"".to_string(), - ], - )); - ops.push((Some("%void".to_string()), vec!["OpTypeVoid".to_string()])); - ops.push(( - Some("%3".to_string()), - vec!["OpTypeFunction".to_string(), "%void".to_string()], - )); - ops.push(( - Some("%main".to_string()), - vec![ - "OpFunction".to_string(), - "%void".to_string(), - "None".to_string(), - "%3".to_string(), - ], - )); - ops.push((Some("%5".to_string()), vec!["OpLabel".to_string()])); - ops.push((None, vec!["OpReturn".to_string()])); - ops.push((None, vec!["OpFunctionEnd".to_string()])); - - let mut out: String = String::new(); - - for op in ops { - if op.0.is_some() { - write!(out, "{} = ", op.0.unwrap()).unwrap(); + let args = parse_args(); + if args.print_version || args.print_help { + if args.print_help { + print_help(); } - for arg in op.1 { - write!(out, "{} ", arg).unwrap(); + if args.print_version { + print_version(); } - writeln!(out).unwrap(); + return; } - println!("{}", out); + + let Some(input) = args.input else { + eprintln!("No input specified"); + return; + }; + + let output = args.output.unwrap_or(PathBuf::from("./out.spv")); + + let src = match fs::read_to_string(input) { + Ok(i) => i, + Err(e) => { + eprintln!("{e}"); + return; + } + }; + + let ast = match parse_string(&src) { + Ok(i) => i, + Err(e) => { + print_error(e, Some(&src)); + return; + } + }; + + let module = match meta_compile(ast) { + Ok(m) => m, + Err(e) => { + print_error(e, Some(&src)); + return; + } + }; + + let res = spirv_meta(module); + + fs::write(output, res).unwrap(); +} + +fn parse_args() -> CompilerArgs { + let mut args = env::args(); + + let _prog = args.next().unwrap(); + + let mut parsed_args = CompilerArgs::default(); + + while let Some(arg) = args.next() { + if let Some(arg) = arg.strip_prefix("--") { + match arg { + "help" => parsed_args.print_help = true, + "version" => parsed_args.print_version = true, + "input" => { + if parsed_args.input.is_none() { + let Some(input) = args.next() else { + eprintln!("input needs a file"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + }; + + parsed_args.input = Some(PathBuf::from(input)); + } else { + eprintln!("input can be passed only once"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + "output" => { + if parsed_args.output.is_none() { + let Some(output) = args.next() else { + parsed_args.print_help = true; + eprintln!("output needs a file"); + eprintln!(); + return parsed_args; + }; + + parsed_args.output = Some(PathBuf::from(output)); + } else { + eprintln!("output can be passed only once"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + a => { + eprintln!("unknown arg --{a}"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + continue; + } + + if let Some(arg) = arg.strip_prefix("-") { + let mut chars = arg.chars(); + while let Some(arg) = chars.next() { + match arg { + 'i' => { + if chars.next().is_some() { + parsed_args.print_help = true; + eprintln!("-i needs to be last in combined args"); + return parsed_args; + } + + if parsed_args.input.is_none() { + let Some(input) = args.next() else { + eprintln!("input needs a file"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + }; + + parsed_args.input = Some(PathBuf::from(input)); + } else { + eprintln!("input can be passed only once"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + 'o' => { + if chars.next().is_some() { + parsed_args.print_help = true; + eprintln!("-o needs to be last in combined args"); + eprintln!(); + return parsed_args; + } + + if parsed_args.output.is_none() { + let Some(output) = args.next() else { + parsed_args.print_help = true; + eprintln!("output needs a file"); + return parsed_args; + }; + + parsed_args.output = Some(PathBuf::from(output)); + } else { + eprintln!("output can be passed only once"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + 'h' => parsed_args.print_help = true, + 'v' => parsed_args.print_version = true, + a => { + eprintln!("unknown arg -{a}"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + } + } + continue; + } + + eprintln!("unknown arg {arg}"); + eprintln!(); + parsed_args.print_help = true; + return parsed_args; + } + + parsed_args +} + +fn print_help() { + println!("-- ShaderC shader compiler --"); + println!(); + println!("Arguments:"); + println!("\t-i --input:"); + println!("\t\tThe shader to be parsed and compiled, manadory argument"); + println!(); + println!("\t-o --output:"); + println!("\t\tWhere to output the compiled spirv assembly to, default: out.spv"); + println!(); + println!("\t-h --help:"); + println!("\t\tPrint this and exit"); + println!("\t-v --version:"); + println!("\t\tPrint the compilers version"); + println!(); +} + +fn print_version() { + println!( + "ShaderC version: {}; SPIR-V version: {}", + env!("CARGO_PKG_VERSION"), + SPIRV_VERSION + ) }