// Inline Expression Grammar for Permission Checking
// 
// This grammar parses inline DSL expressions used for permission checks.
// It supports predicate calls, OWA Fusion blocks (exclusive composition),
// challenge predicates (* prefix for out-of-band), and defeasible logic (UNLESS).
// AND/OR operators removed — OWA Fusion is the only composition mechanism.
//
// Usage: npx peggy -o src/ast/parser/ExpressionParser.js src/ast/grammar/expression.peggy

{
  // Helper functions
  function makeVariable(name, path) {
    return { type: 'Variable', name, path: path || [] };
  }

  function makePredicate(name, args) {
    return { type: 'Predicate', name, args: args || [] };
  }

  function makeChallengePredicate(name, args) {
    return { type: 'Predicate', name, args: args || [], challenge: true };
  }

  function makeFusion(expressions, aggregator) {
    return { type: 'Fusion', aggregator, expressions };
  }

  function makeDefeasible(primary, exception) {
    return { type: 'Defeasible', primary, exception };
  }
}

// Entry point
Expression
  = _ expr:DefeasibleExpr _ { return expr; }

// Defeasible logic: primary UNLESS exception
DefeasibleExpr
  = primary:PrimaryExpr _ "UNLESS" _ exception:PredicateCall {
      return makeDefeasible(primary, exception);
    }
  / PrimaryExpr

// Primary expressions: Fusion blocks or predicate calls
PrimaryExpr
  = FusionBlock
  / ChallengePredicate
  / PredicateCall

// OWA Fusion block: FUSION <aggregator> { expr1 expr2 ... }
FusionBlock
  = "FUSION" _ aggregator:AggregatorKeyword _ "{" _ expressions:ExpressionList _ "}" {
      return makeFusion(expressions, aggregator);
    }

// Aggregator keywords (subset of ADR-000 DSL v2 aggregators)
AggregatorKeyword
  = "max" / "min" / "majority" / "average" / "sum" / "sum_unbounded"
  / "median" / "optimistic" / "pessimistic" / "top2" / "top3" / "priority"

// List of expressions (whitespace-separated)
ExpressionList
  = head:Expression tail:(_ Expression)* {
      const exprs = [head];
      for (const t of tail) {
        exprs.push(t[1]);
      }
      return exprs;
    }

// Challenge predicate (* prefixed): *name(arg1, arg2, ...)
ChallengePredicate
  = "*" name:Identifier _ "(" _ args:ArgumentList? _ ")" {
      return makeChallengePredicate(name, args || []);
    }

// Predicate call: name(arg1, arg2, ...)
PredicateCall
  = name:Identifier _ "(" _ args:ArgumentList? _ ")" {
      return makePredicate(name, args || []);
    }

// Comma-separated arguments
ArgumentList
  = head:Argument tail:(_ "," _ Argument)* {
      const args = [head];
      for (const t of tail) {
        args.push(t[3]);
      }
      return args;
    }

// Argument types
// Order matters: try VariableBinding first (starts with :), 
// then Literal (strings/numbers), then TypedReference (which looks like an identifier)
Argument
  = VariableBinding
  / Literal
  / TypedReference

// Variable binding: :name or :name.path.subpath
VariableBinding
  = ":" name:Identifier path:("." Identifier)* {
      return makeVariable(name, path.map(p => p[1]));
    }

// Typed reference: Type::path.subpath (e.g., document::params.id)
TypedReference
  = refType:Identifier "::" path:Path {
      return { type: 'Reference', refType: refType, path: path };
    }

// Path for typed references
Path
  = head:Identifier tail:("." Identifier)* {
      const parts = [head];
      for (const t of tail) {
        parts.push(t[1]);
      }
      return parts;
    }

// Literals
Literal
  = StringLiteral
  / NumberLiteral

// String literals (single or double quoted)
StringLiteral
  = '"' chars:([^"\\] / EscapeSequence)* '"' {
      return { type: 'Literal', value: chars.join(''), dataType: 'string' };
    }
  / "'" chars:([^'\\] / EscapeSequence)* "'" {
      return { type: 'Literal', value: chars.join(''), dataType: 'string' };
    }

// Escape sequences
EscapeSequence
  = "\\" char:["'\\nrt] {
      const escapes = { '"': '"', "'": "'", '\\': '\\', 'n': '\n', 'r': '\r', 't': '\t' };
      return escapes[char] || char;
    }

// Number literals
NumberLiteral
  = digits:([0-9]+) {
      return { type: 'Literal', value: parseInt(digits.join(''), 10), dataType: 'number' };
    }

// Identifiers (support hyphens like doc-123, user-456)
Identifier
  = first:[a-zA-Z_] rest:[a-zA-Z0-9_-]* {
      return first + rest.join('');
    }

// Whitespace and comments
_ "whitespace"
  = (WS / LineComment / BlockComment)*

WS
  = [ \t\n\r]+

LineComment
  = "//" [^\n]*

BlockComment
  = "/*" (!"*/" .)* "*/"
