pub mod ast;
pub use ast::*;
use pest::Parser as PestParser;
use pest::iterators::{Pair, Pairs};
use pest_derive::Parser;
use std::path::Path;
use crate::error::ParseError;
#[derive(Parser)]
#[grammar = "parser/grammar.pest"]
pub struct PastaParser2;
pub fn parse_str(source: &str, filename: &str) -> Result<PastaFile, ParseError> {
let pairs = PastaParser2::parse(Rule::file, source).map_err(|e| {
let (line, column) = match e.line_col {
pest::error::LineColLocation::Pos((l, c)) => (l, c),
pest::error::LineColLocation::Span((l, c), _) => (l, c),
};
let message = format!("Parse error in {} at {}:{}: {}", filename, line, column, e);
ParseError::SyntaxError {
file: filename.to_string(),
line,
column,
message,
}
})?;
build_ast(pairs, filename, source)
}
pub fn parse_file(path: &Path) -> Result<PastaFile, ParseError> {
let source = std::fs::read_to_string(path)?;
let filename = path.to_string_lossy();
let mut ast = parse_str(&source, &filename)?;
ast.path = path.to_path_buf();
Ok(ast)
}
fn build_ast(pairs: Pairs<Rule>, filename: &str, source: &str) -> Result<PastaFile, ParseError> {
let mut file = PastaFile::new(std::path::PathBuf::from(filename));
let mut last_global_scene_name: Option<String> = None;
let line_count = source.lines().count().max(1);
let last_line_len = source.lines().last().map(|l| l.len()).unwrap_or(0);
file.span = Span::new(1, 1, line_count, last_line_len + 1, 0, source.len());
for pair in pairs {
match pair.as_rule() {
Rule::file_scope => {
let scope = parse_file_scope(pair)?;
for attr in scope.attrs {
file.items.push(FileItem::FileAttr(attr));
}
for word in scope.words {
file.items.push(FileItem::GlobalWord(word));
}
}
Rule::global_scene_scope => {
let scene = parse_global_scene_scope(pair, &mut last_global_scene_name, filename)?;
file.items.push(FileItem::GlobalSceneScope(scene));
}
Rule::actor_scope => {
let actor = parse_actor_scope(pair)?;
file.items.push(FileItem::ActorScope(actor));
}
Rule::EOI => {}
_ => {}
}
}
Ok(file)
}
fn parse_file_scope(pair: Pair<Rule>) -> Result<FileScope, ParseError> {
let mut scope = FileScope::default();
for inner in pair.into_inner() {
match inner.as_rule() {
Rule::file_attr_line => {
for attr_pair in inner.into_inner() {
if attr_pair.as_rule() == Rule::attr {
scope.attrs.push(parse_attr(attr_pair)?);
}
}
}
Rule::file_word_line => {
for kw_pair in inner.into_inner() {
if kw_pair.as_rule() == Rule::key_words {
scope.words.push(parse_key_words(kw_pair)?);
}
}
}
_ => {}
}
}
Ok(scope)
}
fn parse_actor_scope(pair: Pair<Rule>) -> Result<ActorScope, ParseError> {
let span = Span::from(&pair.as_span());
let mut name = String::new();
let mut attrs = Vec::new();
let mut words = Vec::new();
let mut var_sets = Vec::new();
let mut code_blocks = Vec::new();
for inner in pair.into_inner() {
match inner.as_rule() {
Rule::actor_line => {
for id_pair in inner.into_inner() {
if id_pair.as_rule() == Rule::id {
name = id_pair.as_str().to_string();
}
}
}
Rule::global_scene_attr_line => {
for attr_pair in inner.into_inner() {
if attr_pair.as_rule() == Rule::attr {
attrs.push(parse_attr(attr_pair)?);
}
}
}
Rule::global_scene_word_line => {
for kw_pair in inner.into_inner() {
if kw_pair.as_rule() == Rule::key_words {
words.push(parse_key_words(kw_pair)?);
}
}
}
Rule::var_set_local | Rule::var_set_global | Rule::var_set_none => {
var_sets.push(parse_var_set(inner)?);
}
Rule::code_block => {
code_blocks.push(parse_code_block(inner)?);
}
_ => {}
}
}
Ok(ActorScope {
name,
attrs,
words,
var_sets,
code_blocks,
span,
})
}
mod parse_scene;
mod parse_action;
mod parse_elements;
use parse_scene::*;
use parse_action::*;
use parse_elements::*;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normalize_number_str_half_width() {
assert_eq!(normalize_number_str("123"), "123");
assert_eq!(normalize_number_str("-456"), "-456");
assert_eq!(normalize_number_str("3.14"), "3.14");
}
#[test]
fn test_normalize_number_str_full_width() {
assert_eq!(normalize_number_str("123"), "123");
assert_eq!(normalize_number_str("-456"), "-456");
assert_eq!(normalize_number_str("3.14"), "3.14");
}
#[test]
fn test_normalize_number_str_mixed() {
assert_eq!(normalize_number_str("123"), "123");
assert_eq!(normalize_number_str("3.14"), "3.14");
assert_eq!(normalize_number_str("-123"), "-123");
}
#[test]
fn test_pest_parser_compiles() {
let result = PastaParser2::parse(Rule::file, "");
assert!(result.is_ok());
}
}