use brink_syntax::ast::{self, AstPtr, SyntaxNodePtr};
use rowan::TextRange;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FileId(pub u32);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Name {
pub text: String,
pub range: TextRange,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Path {
pub segments: Vec<Name>,
pub range: TextRange,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Tag {
pub parts: Vec<ContentPart>,
pub ptr: AstPtr<ast::Tag>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HirFile {
pub root_content: Block,
pub knots: Vec<Knot>,
pub variables: Vec<VarDecl>,
pub constants: Vec<ConstDecl>,
pub lists: Vec<ListDecl>,
pub externals: Vec<ExternalDecl>,
pub includes: Vec<IncludeSite>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ContainerPtr {
Knot(AstPtr<ast::KnotDef>),
Stitch(AstPtr<ast::StitchDef>),
}
impl ContainerPtr {
pub fn text_range(&self) -> TextRange {
match self {
Self::Knot(p) => p.text_range(),
Self::Stitch(p) => p.text_range(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Knot {
pub ptr: ContainerPtr,
pub name: Name,
pub is_function: bool,
pub params: Vec<Param>,
pub body: Block,
pub stitches: Vec<Stitch>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Stitch {
pub ptr: AstPtr<ast::StitchDef>,
pub name: Name,
pub params: Vec<Param>,
pub body: Block,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Param {
pub name: Name,
pub is_ref: bool,
pub is_divert: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Block {
pub label: Option<Name>,
pub stmts: Vec<Stmt>,
pub container_id: Option<brink_format::DefinitionId>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Stmt {
Content(Content),
Divert(Divert),
TunnelCall(TunnelCall),
ThreadStart(ThreadStart),
TempDecl(TempDecl),
Assignment(Assignment),
Return(Return),
ChoiceSet(Box<ChoiceSet>),
LabeledBlock(Box<Block>),
Conditional(Conditional),
Sequence(Sequence),
ExprStmt(Expr),
EndOfLine,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChoiceSetContext {
Weave,
Inline,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChoiceSet {
pub choices: Vec<Choice>,
pub continuation: Block,
pub context: ChoiceSetContext,
pub depth: u32,
pub gather_id: Option<brink_format::DefinitionId>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Choice {
pub ptr: AstPtr<ast::Choice>,
pub is_sticky: bool,
pub is_fallback: bool,
pub label: Option<Name>,
pub condition: Option<Expr>,
pub start_content: Option<Content>,
pub bracket_content: Option<Content>,
pub inner_content: Option<Content>,
pub tags: Vec<Tag>,
pub body: Block,
pub container_id: Option<brink_format::DefinitionId>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Content {
pub ptr: Option<SyntaxNodePtr>,
pub parts: Vec<ContentPart>,
pub tags: Vec<Tag>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ContentPart {
Text(String),
Glue,
Spring,
Interpolation(Expr),
InlineConditional(Conditional),
InlineSequence(Sequence),
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SequenceType: u8 {
const STOPPING = 0x01;
const CYCLE = 0x02;
const ONCE = 0x04;
const SHUFFLE = 0x08;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CondKind {
InitialCondition,
IfElse,
Switch(Expr),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Conditional {
pub ptr: SyntaxNodePtr,
pub kind: CondKind,
pub branches: Vec<CondBranch>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CondBranch {
pub condition: Option<Expr>,
pub body: Block,
pub container_id: Option<brink_format::DefinitionId>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Sequence {
pub ptr: SyntaxNodePtr,
pub kind: SequenceType,
pub branches: Vec<Block>,
pub container_id: Option<brink_format::DefinitionId>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Divert {
pub ptr: Option<SyntaxNodePtr>,
pub target: DivertTarget,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TunnelCall {
pub ptr: AstPtr<ast::DivertNode>,
pub targets: Vec<DivertTarget>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ThreadStart {
pub ptr: AstPtr<ast::ThreadStart>,
pub target: DivertTarget,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DivertTarget {
pub path: DivertPath,
pub args: Vec<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DivertPath {
Path(Path),
Done,
End,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Return {
pub ptr: Option<AstPtr<ast::ReturnStmt>>,
pub value: Option<Expr>,
pub onwards_args: Vec<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expr {
Int(i32),
Float(FloatBits),
Bool(bool),
String(StringExpr),
Null,
Path(Path),
DivertTarget(Path),
ListLiteral(Vec<Path>),
Prefix(PrefixOp, Box<Expr>),
Infix(Box<Expr>, InfixOp, Box<Expr>),
Postfix(Box<Expr>, PostfixOp),
Call(Path, Vec<Expr>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FloatBits(pub u64);
impl FloatBits {
pub fn from_f64(f: f64) -> Self {
Self(f.to_bits())
}
pub fn to_f64(self) -> f64 {
f64::from_bits(self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StringExpr {
pub parts: Vec<StringPart>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StringPart {
Literal(String),
Interpolation(Box<Expr>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PrefixOp {
Negate,
Not,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PostfixOp {
Increment,
Decrement,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InfixOp {
Add,
Sub,
Mul,
Div,
Mod,
Intersect,
Eq,
NotEq,
Lt,
Gt,
LtEq,
GtEq,
And,
Or,
Has,
HasNot,
}
#[must_use]
pub fn display_expr(expr: &Expr) -> String {
match expr {
Expr::Path(p) => {
let mut out = String::new();
for (i, seg) in p.segments.iter().enumerate() {
if i > 0 {
out.push('.');
}
out.push_str(&seg.text);
}
out
}
Expr::Int(n) => n.to_string(),
Expr::Float(f) => f.to_f64().to_string(),
Expr::Bool(b) => b.to_string(),
Expr::String(_) => "\"...\"".to_string(),
Expr::Null => "null".to_string(),
Expr::DivertTarget(p) => {
let mut out = "-> ".to_string();
for (i, seg) in p.segments.iter().enumerate() {
if i > 0 {
out.push('.');
}
out.push_str(&seg.text);
}
out
}
Expr::ListLiteral(_) => "(...)".to_string(),
Expr::Prefix(op, inner) => {
format!("{}{}", op.as_str(), display_expr(inner))
}
Expr::Infix(lhs, op, rhs) => {
format!(
"{} {} {}",
display_expr(lhs),
op.as_str(),
display_expr(rhs)
)
}
Expr::Postfix(inner, op) => {
format!("{}{}", display_expr(inner), op.as_str())
}
Expr::Call(path, _) => {
let mut name = String::new();
for (i, seg) in path.segments.iter().enumerate() {
if i > 0 {
name.push('.');
}
name.push_str(&seg.text);
}
format!("{name}(...)")
}
}
}
impl PrefixOp {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Negate => "-",
Self::Not => "not ",
}
}
}
impl PostfixOp {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Increment => "++",
Self::Decrement => "--",
}
}
}
impl InfixOp {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Add => "+",
Self::Sub => "-",
Self::Mul => "*",
Self::Div => "/",
Self::Mod => "%",
Self::Intersect => "^",
Self::Eq => "==",
Self::NotEq => "!=",
Self::Lt => "<",
Self::Gt => ">",
Self::LtEq => "<=",
Self::GtEq => ">=",
Self::And => "&&",
Self::Or => "||",
Self::Has => "?",
Self::HasNot => "!?",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VarDecl {
pub ptr: AstPtr<ast::VarDecl>,
pub name: Name,
pub value: Expr,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConstDecl {
pub ptr: AstPtr<ast::ConstDecl>,
pub name: Name,
pub value: Expr,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TempDecl {
pub ptr: AstPtr<ast::TempDecl>,
pub name: Name,
pub value: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Assignment {
pub ptr: AstPtr<ast::Assignment>,
pub target: Expr,
pub op: AssignOp,
pub value: Expr,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AssignOp {
Set,
Add,
Sub,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListDecl {
pub ptr: AstPtr<ast::ListDecl>,
pub name: Name,
pub members: Vec<ListMember>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListMember {
pub name: Name,
pub value: Option<i32>,
pub is_active: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExternalDecl {
pub ptr: AstPtr<ast::ExternalDecl>,
pub name: Name,
pub param_count: u8,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IncludeSite {
pub file_path: String,
pub ptr: AstPtr<ast::IncludeStmt>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
pub file: FileId,
pub range: TextRange,
pub message: String,
pub code: DiagnosticCode,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Severity {
Error,
Warning,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DiagnosticCode {
E001,
E002,
E003,
E004,
E005,
E006,
E007,
E008,
E009,
E010,
E011,
E012,
E013,
E014,
E015,
E016,
E017,
E018,
E019,
E020,
E021,
E022,
E023,
E024,
E025,
E026,
E027,
E028,
E029,
E030,
E031,
E032,
E033,
E034,
E035,
E036,
E037,
E038,
E039,
E040,
E041,
E042,
E043,
}
impl DiagnosticCode {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::E001 => "E001",
Self::E002 => "E002",
Self::E003 => "E003",
Self::E004 => "E004",
Self::E005 => "E005",
Self::E006 => "E006",
Self::E007 => "E007",
Self::E008 => "E008",
Self::E009 => "E009",
Self::E010 => "E010",
Self::E011 => "E011",
Self::E012 => "E012",
Self::E013 => "E013",
Self::E014 => "E014",
Self::E015 => "E015",
Self::E016 => "E016",
Self::E017 => "E017",
Self::E018 => "E018",
Self::E019 => "E019",
Self::E020 => "E020",
Self::E021 => "E021",
Self::E022 => "E022",
Self::E023 => "E023",
Self::E024 => "E024",
Self::E025 => "E025",
Self::E026 => "E026",
Self::E027 => "E027",
Self::E028 => "E028",
Self::E029 => "E029",
Self::E030 => "E030",
Self::E031 => "E031",
Self::E032 => "E032",
Self::E033 => "E033",
Self::E034 => "E034",
Self::E035 => "E035",
Self::E036 => "E036",
Self::E037 => "E037",
Self::E038 => "E038",
Self::E039 => "E039",
Self::E040 => "E040",
Self::E041 => "E041",
Self::E042 => "E042",
Self::E043 => "E043",
}
}
#[must_use]
pub fn title(self) -> &'static str {
match self {
Self::E001 => "knot is missing a name",
Self::E002 => "stitch is missing a name",
Self::E003 => "parameter is missing a name",
Self::E004 => "VAR declaration is missing a name",
Self::E005 => "VAR declaration is missing an initializer",
Self::E006 => "CONST declaration is missing a name",
Self::E007 => "CONST declaration is missing an initializer",
Self::E008 => "LIST declaration is missing a name",
Self::E009 => "LIST member is missing a name",
Self::E010 => "EXTERNAL declaration is missing a name",
Self::E011 => "INCLUDE statement is missing a file path",
Self::E012 => "divert is missing a target",
Self::E013 => "thread start is missing a target",
Self::E014 => "logic line has no effect",
Self::E015 => "expression is missing an operand",
Self::E016 => "unknown or unsupported operator",
Self::E017 => "function call is missing a name",
Self::E018 => "divert target expression is missing a path",
Self::E019 => "choice is missing bullet markers",
Self::E020 => "inline conditional is missing a condition",
Self::E021 => "inline sequence has no branches",
Self::E022 => "duplicate knot definition",
Self::E023 => "duplicate variable/constant definition",
Self::E024 => "unresolved divert target",
Self::E025 => "unresolved variable reference",
Self::E026 => "duplicate list item",
Self::E027 => "ambiguous bare list item reference",
Self::E028 => "circular INCLUDE dependency",
Self::E029 => "choice in conditional must explicitly divert",
Self::E030 => "string interpolation in constant initializer is ignored",
Self::E031 => "function call argument count mismatch",
Self::E032 => "return statement outside function",
Self::E033 => "unreachable code after divert",
Self::E034 => "choice set has only fallback choices",
Self::E035 => "name shadows a built-in function",
Self::E036 => "expected diagnostic not produced",
Self::E037 => "syntax error",
Self::E038 => "malformed doc-comment tag",
Self::E039 => "manifest disagrees with EXTERNAL arity",
Self::E040 => "unknown semantic type",
Self::E041 => "external argument type mismatch",
Self::E042 => "external argument out of domain",
Self::E043 => "doc-comment tag not applicable to this declaration",
}
}
#[must_use]
pub fn severity(self) -> Severity {
match self {
Self::E014
| Self::E022
| Self::E023
| Self::E026
| Self::E030
| Self::E031
| Self::E033
| Self::E034
| Self::E035
| Self::E038
| Self::E043 => Severity::Warning,
_ => Severity::Error,
}
}
#[must_use]
pub fn from_str_code(s: &str) -> Option<Self> {
match s {
"E001" => Some(Self::E001),
"E002" => Some(Self::E002),
"E003" => Some(Self::E003),
"E004" => Some(Self::E004),
"E005" => Some(Self::E005),
"E006" => Some(Self::E006),
"E007" => Some(Self::E007),
"E008" => Some(Self::E008),
"E009" => Some(Self::E009),
"E010" => Some(Self::E010),
"E011" => Some(Self::E011),
"E012" => Some(Self::E012),
"E013" => Some(Self::E013),
"E014" => Some(Self::E014),
"E015" => Some(Self::E015),
"E016" => Some(Self::E016),
"E017" => Some(Self::E017),
"E018" => Some(Self::E018),
"E019" => Some(Self::E019),
"E020" => Some(Self::E020),
"E021" => Some(Self::E021),
"E022" => Some(Self::E022),
"E023" => Some(Self::E023),
"E024" => Some(Self::E024),
"E025" => Some(Self::E025),
"E026" => Some(Self::E026),
"E027" => Some(Self::E027),
"E028" => Some(Self::E028),
"E029" => Some(Self::E029),
"E030" => Some(Self::E030),
"E031" => Some(Self::E031),
"E032" => Some(Self::E032),
"E033" => Some(Self::E033),
"E034" => Some(Self::E034),
"E035" => Some(Self::E035),
"E036" => Some(Self::E036),
"E037" => Some(Self::E037),
"E038" => Some(Self::E038),
"E039" => Some(Self::E039),
"E040" => Some(Self::E040),
"E041" => Some(Self::E041),
"E042" => Some(Self::E042),
"E043" => Some(Self::E043),
_ => None,
}
}
}