use facet::Facet;
use std::collections::HashMap;
#[derive(Debug, Clone, Facet)]
pub struct Grammar {
#[facet(default, rename = "$schema")]
pub schema: Option<String>,
pub name: String,
#[facet(default)]
pub inherits: Option<String>,
pub rules: HashMap<String, Rule>,
#[facet(default)]
pub extras: Option<Vec<Rule>>,
#[facet(default)]
pub externals: Option<Vec<Rule>>,
#[facet(default)]
pub inline: Option<Vec<String>>,
#[facet(default)]
pub precedences: Option<Vec<Vec<Precedence>>>,
#[facet(default)]
pub conflicts: Option<Vec<Vec<String>>>,
#[facet(default)]
pub reserved: Option<HashMap<String, Vec<Rule>>>,
#[facet(default)]
pub word: Option<String>,
#[facet(default)]
pub supertypes: Option<Vec<String>>,
}
#[derive(Debug, Clone, Facet)]
#[repr(u8)]
pub enum Precedence {
String(String),
Symbol {
name: String,
},
}
#[derive(Debug, Clone, Facet)]
pub struct Rule {
#[facet(rename = "type")]
pub rule_type: RuleType,
#[facet(default)]
pub value: Option<RuleValue>,
#[facet(default)]
pub name: Option<String>,
#[facet(default)]
pub content: Option<Box<Rule>>,
#[facet(default)]
pub members: Option<Vec<Rule>>,
#[facet(default)]
pub named: Option<bool>,
#[facet(default)]
pub flags: Option<String>,
#[facet(default)]
pub context_name: Option<String>,
}
#[derive(Debug, Clone, Facet)]
#[repr(u8)]
pub enum RuleValue {
String(String),
Integer(i32),
}
#[derive(Debug, Clone, Facet)]
#[repr(u8)]
pub enum RuleType {
#[facet(rename = "BLANK")]
Blank,
#[facet(rename = "STRING")]
String,
#[facet(rename = "PATTERN")]
Pattern,
#[facet(rename = "SYMBOL")]
Symbol,
#[facet(rename = "CHOICE")]
Choice,
#[facet(rename = "SEQ")]
Seq,
#[facet(rename = "REPEAT")]
Repeat,
#[facet(rename = "REPEAT1")]
Repeat1,
#[facet(rename = "PREC")]
Prec,
#[facet(rename = "PREC_LEFT")]
PrecLeft,
#[facet(rename = "PREC_RIGHT")]
PrecRight,
#[facet(rename = "PREC_DYNAMIC")]
PrecDynamic,
#[facet(rename = "FIELD")]
Field,
#[facet(rename = "ALIAS")]
Alias,
#[facet(rename = "TOKEN")]
Token,
#[facet(rename = "IMMEDIATE_TOKEN")]
ImmediateToken,
#[facet(rename = "RESERVED")]
Reserved,
}
pub fn parse_grammar(json: &str) -> Result<Grammar, GrammarError> {
facet_json::from_str(json).map_err(|e| GrammarError::JsonParse(e.to_string()))
}
#[derive(Debug)]
pub enum GrammarError {
JsonParse(String),
Validation(String),
}
impl std::fmt::Display for GrammarError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
GrammarError::JsonParse(e) => write!(f, "JSON parse error: {e}"),
GrammarError::Validation(msg) => write!(f, "validation error: {msg}"),
}
}
}
impl std::error::Error for GrammarError {}
impl Rule {
#[must_use]
pub fn type_name(&self) -> &'static str {
match self.rule_type {
RuleType::Blank => "BLANK",
RuleType::String => "STRING",
RuleType::Pattern => "PATTERN",
RuleType::Symbol => "SYMBOL",
RuleType::Choice => "CHOICE",
RuleType::Seq => "SEQ",
RuleType::Repeat => "REPEAT",
RuleType::Repeat1 => "REPEAT1",
RuleType::Prec => "PREC",
RuleType::PrecLeft => "PREC_LEFT",
RuleType::PrecRight => "PREC_RIGHT",
RuleType::PrecDynamic => "PREC_DYNAMIC",
RuleType::Field => "FIELD",
RuleType::Alias => "ALIAS",
RuleType::Token => "TOKEN",
RuleType::ImmediateToken => "IMMEDIATE_TOKEN",
RuleType::Reserved => "RESERVED",
}
}
#[must_use]
pub fn is_terminal(&self) -> bool {
matches!(self.rule_type, RuleType::String | RuleType::Pattern)
}
#[must_use]
pub fn is_symbol(&self) -> bool {
matches!(self.rule_type, RuleType::Symbol)
}
#[must_use]
pub fn symbol_name(&self) -> Option<&str> {
if self.is_symbol() {
self.name.as_deref()
} else {
None
}
}
#[must_use]
pub fn precedence(&self) -> Option<i32> {
match self.rule_type {
RuleType::Prec | RuleType::PrecLeft | RuleType::PrecRight | RuleType::PrecDynamic => {
self.value.as_ref().and_then(|v| match v {
RuleValue::Integer(i) => Some(*i),
RuleValue::String(_) => None,
})
}
_ => None,
}
}
#[must_use]
pub fn string_value(&self) -> Option<&str> {
if matches!(self.rule_type, RuleType::String) {
self.value.as_ref().and_then(|v| match v {
RuleValue::String(s) => Some(s.as_str()),
RuleValue::Integer(_) => None,
})
} else {
None
}
}
#[must_use]
pub fn pattern_value(&self) -> Option<&str> {
if matches!(self.rule_type, RuleType::Pattern) {
self.value.as_ref().and_then(|v| match v {
RuleValue::String(s) => Some(s.as_str()),
RuleValue::Integer(_) => None,
})
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_grammar() {
let json = r#"{
"name": "test",
"rules": {
"source_file": {
"type": "SYMBOL",
"name": "expression"
},
"expression": {
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "hello"
},
{
"type": "PATTERN",
"value": "[0-9]+"
}
]
}
}
}"#;
let grammar = parse_grammar(json).unwrap_or_else(|e| {
if let GrammarError::JsonParse(inner) = e {
eprintln!("JSON parse error:\n{}", inner);
} else {
eprintln!("Grammar error: {}", e);
}
std::process::exit(1);
});
assert_eq!(grammar.name, "test");
assert_eq!(grammar.rules.len(), 2);
}
#[test]
fn test_parse_precedence() {
let json = r#"{
"name": "test",
"rules": {
"expr": {
"type": "PREC_LEFT",
"value": 1,
"content": {
"type": "SEQ",
"members": [
{"type": "SYMBOL", "name": "expr"},
{"type": "STRING", "value": "+"},
{"type": "SYMBOL", "name": "expr"}
]
}
}
}
}"#;
let grammar = parse_grammar(json).unwrap_or_else(|e| {
if let GrammarError::JsonParse(inner) = e {
eprintln!("JSON parse error:\n{}", inner);
} else {
eprintln!("Grammar error: {}", e);
}
std::process::exit(1);
});
let expr_rule = grammar.rules.get("expr").unwrap();
assert_eq!(expr_rule.precedence(), Some(1));
assert!(matches!(expr_rule.rule_type, RuleType::PrecLeft));
}
}