mod error;
pub use error::*;
mod parser;
pub use parser::*;
pub mod tokenizer;
pub use tokenizer::{Token, TokenKind};
pub mod impls;
mod span;
pub use span::Span;
use crate::cst;
#[cfg(test)]
mod test {
use crate::{
cst::{Cst, Kind},
print::CstPrinter,
};
fn assert_success(text: &str) -> Cst {
let cst = Cst::parse(text);
let mut buf = String::new();
cst.print(&mut buf, text);
println!("{buf}");
assert!(cst.errors.is_empty(), "Parse errors: {:#?}", cst.errors);
assert_spans_nested(&cst);
cst
}
fn assert_spans_nested(cst: &Cst) {
for node in &cst.nodes {
for child in node.children.get(cst) {
let span = child.span(cst);
assert!(
node.span.start <= span.start && span.end <= node.span.end,
"{:?} ({}..{}) does not cover its child ({}..{})",
node.kind,
node.span.start,
node.span.end,
span.start,
span.end,
);
}
}
}
#[allow(unused, reason = "tests will use this")]
fn assert_fail(text: &str) {
let cst = Cst::parse(text);
let mut buf = String::new();
cst.print(&mut buf, text);
println!("{buf}");
assert!(!cst.errors.is_empty(), "Parsed successfully",);
}
#[test]
fn eof() {
assert_fail("0:[?");
assert_fail("a = ?");
assert_fail("a:");
}
#[test]
fn error_spans_stay_within_the_source() {
for text in ["entries: map[hash,embed] = {", "a: u32 =", "a:", ""] {
let cst = Cst::parse(text);
for err in &cst.errors {
assert!(
err.span.start <= err.span.end,
"inverted error span {:?} for {text:?}",
err.span
);
assert!(
err.span.end as usize <= text.len(),
"error span {:?} leaves the source (len {}) for {text:?}",
err.span,
text.len()
);
}
}
}
#[test]
fn comments() {
assert_success(
r#"
#PROP_text
type: string = "my_str" # inline comment
version: u32 = 3
linked: list[string] = { }
entries: map[hash, embed] = {
"foo" = Bar {
a = 5 # 23
# asdasd
}
}
"#,
);
}
#[test]
fn inline_block_spans() {
let text = "a: list[pointer] = { B { x: f32 = 1 } }";
let cst = assert_success(text);
let block = class_block(&cst).expect("class block");
assert_eq!(&text[block.span], "{ x: f32 = 1 }");
}
#[test]
fn nested_inline_block_spans() {
let text = r#"loadscreen: embed = CensoredImage {
image: embed = Image { src: string = "val" }
}"#;
let cst = assert_success(text);
let block = class_block(&cst).expect("class block");
assert_eq!(
&text[block.span],
"{\n image: embed = Image { src: string = \"val\" }\n}"
);
}
fn class_block(cst: &Cst) -> Option<&crate::cst::Node> {
let class = cst.nodes.iter().find(|n| n.kind == Kind::Class)?;
class
.children
.get(cst)
.iter()
.find_map(|c| c.tree(cst))
.filter(|n| n.kind == Kind::Block)
}
#[test]
fn inline_comment_into_eof() {
assert_success(r#"mVelMultiplier: f32 = 0 # asd"#);
}
#[ignore = "Nice to have"]
#[test]
fn naked_class() {
let text = r#"
#PROP_text
type: string = "PROP"
version: u32 = 3
linked: list[string] = { }
entries: map[hash, embed] = {
"thing" = ClassThing { ooo }
}
"#;
let cst = assert_success(text);
let partial = cst.build_bin(text);
assert!(
!partial.diagnostics.is_empty(),
"There should be an error for the naked 'ooo' in the class block"
);
}
#[test]
fn smoke_test() {
let text = r#"
#PROP_text
type: string = "PROP"
version: u32 = 3
linked: list[string] = { }
entries: map[hash, embed] = {
"foo" = Foo {
guy: u32 = "asdasd"
}
}
"#;
let cst = Cst::parse(text);
let mut str = String::new();
cst.print(&mut str, text);
eprintln!("text len: {}", text.len());
eprintln!("{str}\n====== errors: ======\n");
let errors = &cst.errors;
for err in errors {
eprintln!("{:?}: {:#?}", &text[err.span], err.kind);
}
assert!(errors.is_empty());
let ast = cst.build_ast(&str);
let tree = ast.to_bin(&str);
eprintln!("{str}\n====== type errors: ======\n");
for err in errors {
eprintln!("{:?}: {:#?}", &text[err.span], err);
}
eprintln!("==== FINAL TREE =====\n{tree:#?}");
}
#[test]
fn writer_test() {
let text = r#"
#PROP_text
type: string = "PROP"
version: u32 = 3
linked: list[string] = { }
entries: map[hash,embed] = {
"myPath" = VfxEmitter {
a: string = "hello"
b: list[i8] = {3 6 1}
}
"cock" = VfxEmitterDefinitionData {
rate: embed = ValueFloat {
constantValue: f32 = 1
}
particleLifetime: embed = ValueFloat {
constantValue: f32 = 1
}
particleLinger: option[f32] = {
2
}
lifetime: option[f32] = {
1
}
emitterName: string = "JudgementCut"
bindWeight: embed = ValueFloat {
constantValue: f32 = 1
}
primitive: pointer = VfxPrimitiveMesh {
mMesh: embed = VfxMeshDefinitionData {
mMeshName: string = "ASSETS/Characters/viego/Skins/base/judgementcut.skn"
mMeshSkeletonName: string = "ASSETS/Characters/viego/Skins/base/judgementcut.skl"
mAnimationName: string = "ASSETS/Characters/viego/Skins/base/judgementcut.anm"
}
}
birthScale0: embed = ValueVector3 {
constantValue: vec3 = { 15, 15, 15 }
}
blendMode: u8 = 1
disableBackfaceCull: bool = true
miscRenderFlags: u8 = 1
texture: string = "ASSETS/Characters/viego/Skins/base/slashes.dds"
particleUVScrollRate: embed = IntegratedValueVector2 {
constantValue: vec2 = { 1, 0 }
dynamics: pointer = VfxAnimatedVector2fVariableData {
times: list[f32] = {
0
}
values: list[vec2] = {
{ 1, 0 }
}
}
}
}
}
"#;
let cst = Cst::parse(text);
let mut str = String::new();
cst.print(&mut str, text);
println!("============= CST ===========");
println!("{str}");
let mut str = String::new();
CstPrinter::new(text, &mut str, Default::default())
.print(&cst)
.unwrap();
println!("{}", "*".repeat(80));
println!("========\n{str}");
}
}