Skip to content

Commit

Permalink
Refactor errors and continue scope implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
WilliamRagstad committed Aug 16, 2023
1 parent 7bc791d commit de4fd9f
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 42 deletions.
134 changes: 97 additions & 37 deletions src/file/parser.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::{path::PathBuf, io::{BufReader, Read, Seek, SeekFrom}};
use crate::{file::webx::WebXFile, reporting::error::{exit_error, ERROR_PARSE_IO, ERROR_SYNTAX}};
use crate::{file::webx::WebXFile, reporting::error::{exit_error, ERROR_PARSE_IO, ERROR_SYNTAX, exit_error_unexpected_char, exit_error_unexpected, exit_error_expected_any_of_but_found, exit_error_expected_but_found}};

use super::webx::WebXScope;

struct WebXFileParser<'a> {
file: &'a PathBuf,
Expand Down Expand Up @@ -50,18 +52,21 @@ impl<'a> WebXFileParser<'a> {
///
/// # Errors
/// If EOF is reached, or the next character is not the expected one, an error is returned and the program exits.
fn expect_specific(&mut self, c: char) {
let nc = self.next();
fn expect_specific(&mut self, nc: Option<char>, expected: char) {
if nc.is_none() {
exit_error(format!("Unexpected EOF, expected '{}' at line {}, column {}", c, self.line, self.column), ERROR_SYNTAX);
} else if nc.unwrap() != c {
exit_error(format!("Expected '{}' but found '{}' at line {}, column {}", c, nc.unwrap(), self.line, self.column), ERROR_SYNTAX);
exit_error_unexpected("EOF".to_string(), self.line, self.column, ERROR_SYNTAX);
} else if nc.unwrap() != expected {
exit_error_expected_but_found(expected.to_string(), nc.unwrap().to_string(), self.line, self.column, ERROR_SYNTAX);
}
}

fn expect_specific_str(&mut self, s: &str) {
for c in s.chars() {
self.expect_specific(c);
fn expect_next_specific(&mut self, expected: char) {
self.expect_specific(self.next(), expected);
}

fn expect_specific_str(&mut self, expected: &str) {
for c in expected.chars() {
self.expect_specific(self.next(), c);
}
}

Expand All @@ -70,20 +75,34 @@ impl<'a> WebXFileParser<'a> {
///
/// # Errors
/// If EOF is reached, or the next character is not one of the expected ones, an error is returned and the program exits.
fn expect_any_of(&mut self, cs: Vec<char>) -> char {
let nc = self.next();
fn expect_any_of(&mut self, nc: Option<char>, cs: Vec<char>) -> char {
if nc.is_none() {
exit_error(format!("Unexpected EOF, expected any of {:?} at line {}, column {}", cs, self.line, self.column), ERROR_SYNTAX);
exit_error_unexpected("EOF".to_string(), self.line, self.column, ERROR_SYNTAX);
}
let nc = nc.unwrap();
if !cs.contains(&nc) {
exit_error(format!("Expected any of {:?} but found '{}' at line {}, column {}", cs, nc.unwrap(), self.line, self.column), ERROR_SYNTAX);
exit_error_expected_any_of_but_found(format!("{:?}", cs), nc, self.line, self.column, ERROR_SYNTAX);
}
nc
}

fn expect_next_any_of(&mut self, cs: Vec<char>) -> char {
self.expect_any_of(self.next(), cs)
}

fn next_skip_whitespace(&mut self, skip_newlines: bool) -> Option<char> {
loop {
let c = self.next();
if c.is_none() { break; }
let c = c.unwrap();
if c == ' ' || c == '\t' || (skip_newlines && c == '\n') { continue; }
return Some(c); // Return the first non-whitespace character.
}
None
}

fn parse_comment(&mut self) {
match self.expect_any_of(vec!['/', '*']) {
match self.expect_next_any_of(vec!['/', '*']) {
'/' => {
loop {
let c = self.next();
Expand All @@ -105,46 +124,87 @@ impl<'a> WebXFileParser<'a> {
}
}

fn parse_location_scope(&mut self) {
self.expect_specific_str("ocation");
fn parse_string(&mut self) -> String {
let mut s = String::new();
loop {
let c = self.next();
if c.is_none() { break; }
if c.unwrap() == '}' { break; }
let c = c.unwrap();
if c == '"' { break; }
s.push(c);
}
s
}

/// Parse an include statement.
///
/// # Example
/// ```
/// include "path/to/file.webx";
/// ```
fn parse_include(&mut self, includes: &Vec<String>) {
self.expect_specific_str("nclude");
self.expect_next_specific('"');
let path = self.parse_string();
self.expect_any_of(self.next_skip_whitespace(false), vec!['\n', ';']);
includes.push(path);
}

fn parse_scope(&mut self) {

fn parse_location(&mut self) -> Result<WebXScope, String> {
self.expect_specific_str("ocation");
self.expect_specific(self.next_skip_whitespace(false), '{');
self.parse_scope(false)
}

fn parse_module(&mut self) -> Result<WebXFile, String> {
let mut module = WebXFile {
path: self.file.clone(),
/// Parse either the global module scope, or a location scope.
/// The function parses all basic components making up a webx
/// module scope such as includes, nested locations, handlers,
/// routes, and models.
///
/// # Arguments
/// * `is_global` - Whether the scope is global or not.
fn parse_scope(&mut self, is_global: bool) -> Result<WebXScope, String> {
let mut scope = WebXScope {
global_ts: String::new(),
includes: vec![],
models: vec![],
handlers: vec![],
routes: vec![],
scopes: vec![],
};

// Keywords: handler, include, location, module, { } and all HTTP methods.
// Only expect a keyword at the start of a line, whitespace, or // comments.
// Pass to dedicated parser function, otherwise error.

loop {
let c = self.next();
if c.is_none() { break; }
let c = c.unwrap();
match c {
' ' | '\t' | '\n' => (), // Ignore whitespace.
let c = self.next_skip_whitespace(true);
if c.is_none() {
// EOF is only allowed if the scope is global.
if is_global { break; }
else { exit_error_unexpected("EOF".to_string(), self.line, self.column, ERROR_SYNTAX); }
}
// Keywords: handler, include, location, module, { } and all HTTP methods.
// Only expect a keyword at the start of a line, whitespace, or // comments.
// Pass to dedicated parser function, otherwise error.
match c.unwrap() {
'}' => {
if is_global { exit_error_unexpected_char('}', self.line, self.column, ERROR_SYNTAX); }
else { break; }
},
'/' => self.parse_comment(),
'{' => self.parse_location_scope(),
'i' => self.parse_include(),
'l' => self.parse_location(),
'm' => self.parse_module_keyword(),
'i' => self.parse_include(&scope.includes),
'l' => scope.scopes.push(self.parse_location()?),
'm' => self.parse_model(),
'h' => self.parse_handler(),
'r' => self.parse_route(),
't' => self.parse_type(),
_ => exit_error_unexpected_char(c.unwrap(), self.line, self.column, ERROR_SYNTAX),
}
}
Ok(scope)
}

Ok(module)
fn parse_module(&mut self) -> Result<WebXFile, String> {
Ok(WebXFile {
path: self.file.clone(),
module_scope: self.parse_scope(true)?,
})
}
}

Expand Down
12 changes: 7 additions & 5 deletions src/file/webx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ use std::path::PathBuf;
pub struct WebXFile {
/// The path to the file.
pub path: PathBuf,
/// The dependencies of the file.
pub includes: Vec<String>,
/// Scopes defined in the file.
/// Created by root and the `location` keyword.
pub scopes: Vec<WebXScope>,
/// Global webx module scope.
pub module_scope: WebXScope,
}

#[derive(Debug)]
pub struct WebXScope {
/// The dependencies of the scope.
pub includes: Vec<String>,
/// Global TypeScript code block
pub global_ts: String,
/// ORM Model definitions
Expand All @@ -23,6 +22,9 @@ pub struct WebXScope {
pub handlers: Vec<WebXHandler>,
/// Route endpoints
pub routes: Vec<WebXRoute>,
/// Nested scopes.
/// Created by root and the `location` keyword.
pub scopes: Vec<WebXScope>,
}

#[derive(Debug)]
Expand Down
16 changes: 16 additions & 0 deletions src/reporting/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,19 @@ pub fn error(message: String) {
pub fn exit_error(message: String, code: i32) -> ! {
exit_error_generic(message, code, "Error");
}

pub fn exit_error_unexpected(what: String, line: usize, column: usize, code: i32) -> ! {
exit_error(format!("Unexpected {} at line {}, column {}", what, line, column), code);
}

pub fn exit_error_expected_but_found(expected: String, found: String, line: usize, column: usize, code: i32) -> ! {
exit_error(format!("Expected {} but found '{}' at line {}, column {}", expected, found, line, column), code);
}

pub fn exit_error_expected_any_of_but_found(expected: String, found: char, line: usize, column: usize, code: i32) -> ! {
exit_error(format!("Expected any of {} but found '{}' at line {}, column {}", expected, found, line, column), code);
}

pub fn exit_error_unexpected_char(what: char, line: usize, column: usize, code: i32) -> ! {
exit_error_unexpected(format!("character '{}'", what), line, column, code);
}

0 comments on commit de4fd9f

Please sign in to comment.