sipha 3.0.0

PEG parser, syntax trees, and code generation
Documentation
#![cfg(feature = "std")]

use sipha::SyntaxKinds;
use sipha::prelude::*;

#[derive(Debug, Clone, Copy, PartialEq, Eq, SyntaxKinds)]
#[repr(u16)]
enum K {
    Root,
    Host,
    Body,
    Embedded,
    JsonRoot,
    JsonString,
    JsonLBrace,
    JsonRBrace,
    JsonColon,
    Ws,
}

fn host_grammar() -> BuiltGraph {
    let mut g = GrammarBuilder::new();
    g.set_trivia_rule("ws");

    g.parser_rule("start", |g| {
        g.node(K::Root, |g| {
            g.node(K::Host, |g| {
                g.call("body");
            });
        });
        g.accept();
    });

    g.lexer_rule("ws", |g| {
        g.trivia(K::Ws, |g| {
            g.zero_or_more(|g| {
                g.class(classes::WHITESPACE);
            });
        });
    });

    g.lexer_rule("body", |g| {
        g.token(K::Body, |g| {
            g.literal(b"{\"a\":\"b\"}");
        });
    });

    g.finish().unwrap()
}

fn json_grammar() -> BuiltGraph {
    let mut g = GrammarBuilder::new();
    g.set_trivia_rule("ws");

    g.parser_rule("start", |g| {
        g.node(K::JsonRoot, |g| {
            g.token(K::JsonLBrace, |g| {
                g.byte(b'{');
            });
            g.call("jstring");
            g.token(K::JsonColon, |g| {
                g.byte(b':');
            });
            g.call("jstring");
            g.token(K::JsonRBrace, |g| {
                g.byte(b'}');
            });
            g.skip();
        });
        g.end_of_input();
        g.accept();
    });

    g.lexer_rule("ws", |g| {
        g.trivia(K::Ws, |g| {
            g.zero_or_more(|g| {
                g.class(classes::WHITESPACE);
            });
        });
    });

    g.lexer_rule("jstring", |g| {
        g.token(K::JsonString, |g| {
            g.byte(b'"');
            g.zero_or_more(|g| {
                g.neg_lookahead(|g| {
                    g.byte(b'"');
                });
                g.class(CharClass::ANY);
            });
            g.byte(b'"');
        });
    });

    g.finish().unwrap()
}

#[test]
fn can_splice_embedded_tree_events() {
    let host = host_grammar();
    let json = json_grammar();
    let src = b"{\"a\":\"b\"}";

    let mut engine = Engine::new();
    let host_out = engine.parse(&host.as_graph(), src).unwrap();

    let (rewritten, errors) = apply_sublanguages(
        &mut engine,
        src,
        &host_out.tree_events,
        &[SubLanguage {
            host_kind: K::Host as SyntaxKind,
            span: EmbeddedSpan::TokenKind(K::Body as SyntaxKind),
            embedded: json.as_graph(),
            wrapper_kind: Some(K::Embedded as SyntaxKind),
            error_kind: Some(K::Embedded as SyntaxKind),
        }],
    );

    assert!(errors.is_empty(), "{errors:#?}");
    assert!(
        rewritten.iter().any(
            |e| matches!(e, TreeEvent::NodeOpen { kind, .. } if *kind == K::Embedded as SyntaxKind)
        ),
        "expected an Embedded wrapper node to be inserted"
    );
}