online-dsl-forge 0.2.0

A bounded parser and runtime for in-memory DSL expressions.
Documentation
use std::collections::{BTreeMap, BTreeSet};

use crate::parser::{AstExpression, BinaryOp, SourceSpan, UnaryOp};
use regex::{Regex, RegexBuilder};

use crate::sema::profile::{BodyNeedSummary, SecurityProfile};
use crate::sema::schema::{CapabilityMeta, CapabilityTicket, RegexFlavor};

#[derive(Debug, Clone)]
pub struct VerifiedProgram {
  ast: AstExpression,
  root: VerifiedExpression,
  profile: SecurityProfile,
  body_need: BodyNeedSummary,
  static_cost_upper_bound: u64,
  regex_literals: Vec<RegexLiteral>,
  regex_cache: CompiledRegexCache,
  required_capabilities: BTreeSet<CapabilityTicket>,
  required_capability_metadata: BTreeMap<CapabilityTicket, CapabilityMeta>,
}

impl VerifiedProgram {
  pub(crate) fn new(parts: VerifiedProgramParts) -> Self {
    Self {
      ast: parts.ast,
      root: parts.root,
      profile: parts.profile,
      body_need: parts.body_need,
      static_cost_upper_bound: parts.static_cost_upper_bound,
      regex_literals: parts.regex_literals,
      regex_cache: parts.regex_cache,
      required_capabilities: parts.required_capabilities,
      required_capability_metadata: parts.required_capability_metadata,
    }
  }

  pub fn ast(&self) -> &AstExpression {
    &self.ast
  }

  pub fn root(&self) -> &VerifiedExpression {
    &self.root
  }

  pub fn profile(&self) -> &SecurityProfile {
    &self.profile
  }

  pub fn body_need(&self) -> BodyNeedSummary {
    self.body_need
  }

  pub fn static_cost_upper_bound(&self) -> u64 {
    self.static_cost_upper_bound
  }

  pub fn regex_literals(&self) -> &[RegexLiteral] {
    &self.regex_literals
  }

  pub fn regex_cache(&self) -> &CompiledRegexCache {
    &self.regex_cache
  }

  pub fn required_capabilities(&self) -> &BTreeSet<CapabilityTicket> {
    &self.required_capabilities
  }

  pub fn required_capability_metadata(&self) -> &BTreeMap<CapabilityTicket, CapabilityMeta> {
    &self.required_capability_metadata
  }
}

pub(crate) struct VerifiedProgramParts {
  pub ast: AstExpression,
  pub root: VerifiedExpression,
  pub profile: SecurityProfile,
  pub body_need: BodyNeedSummary,
  pub static_cost_upper_bound: u64,
  pub regex_literals: Vec<RegexLiteral>,
  pub regex_cache: CompiledRegexCache,
  pub required_capabilities: BTreeSet<CapabilityTicket>,
  pub required_capability_metadata: BTreeMap<CapabilityTicket, CapabilityMeta>,
}

#[derive(Debug, Clone)]
pub struct CompiledExpression {
  verified: VerifiedProgram,
}

impl CompiledExpression {
  pub(crate) fn new(verified: VerifiedProgram) -> Self {
    Self { verified }
  }

  pub fn ast(&self) -> &AstExpression {
    self.verified.ast()
  }

  pub fn into_ast(self) -> AstExpression {
    self.verified.ast
  }

  pub fn verified_program(&self) -> &VerifiedProgram {
    &self.verified
  }

  pub fn into_verified_program(self) -> VerifiedProgram {
    self.verified
  }
}

#[derive(Debug, Clone)]
pub struct VerifiedExpression {
  kind: VerifiedExprKind,
  span: SourceSpan,
  capability_ticket: Option<CapabilityTicket>,
}

impl VerifiedExpression {
  pub(crate) fn new(kind: VerifiedExprKind, span: SourceSpan) -> Self {
    Self {
      kind,
      span,
      capability_ticket: None,
    }
  }

  pub(crate) fn with_capability_ticket(mut self, ticket: CapabilityTicket) -> Self {
    self.capability_ticket = Some(ticket);
    self
  }

  pub fn span(&self) -> SourceSpan {
    self.span
  }

  pub fn capability_ticket(&self) -> Option<&CapabilityTicket> {
    self.capability_ticket.as_ref()
  }

  pub fn kind(&self) -> VerifiedExprKindRef<'_> {
    match &self.kind {
      VerifiedExprKind::Null => VerifiedExprKindRef::Null,
      VerifiedExprKind::Bool(value) => VerifiedExprKindRef::Bool(*value),
      VerifiedExprKind::Int(value) => VerifiedExprKindRef::Int(*value),
      VerifiedExprKind::Float(value) => VerifiedExprKindRef::Float(*value),
      VerifiedExprKind::String(value) => VerifiedExprKindRef::String(value),
      VerifiedExprKind::Array(items) => VerifiedExprKindRef::Array(items),
      VerifiedExprKind::Identifier(name) => VerifiedExprKindRef::Identifier(name),
      VerifiedExprKind::Member { receiver, name } => VerifiedExprKindRef::Member { receiver, name },
      VerifiedExprKind::FunctionCall { name, args } => {
        VerifiedExprKindRef::FunctionCall { name, args }
      }
      VerifiedExprKind::ExpressionFunctionCall {
        name,
        params,
        args,
        body,
      } => VerifiedExprKindRef::ExpressionFunctionCall {
        name,
        params,
        args,
        body,
      },
      VerifiedExprKind::MethodCall {
        receiver,
        name,
        args,
      } => VerifiedExprKindRef::MethodCall {
        receiver,
        name,
        args,
      },
      VerifiedExprKind::Unary { op, expr } => VerifiedExprKindRef::Unary { op: *op, expr },
      VerifiedExprKind::Binary { left, op, right } => VerifiedExprKindRef::Binary {
        left,
        op: *op,
        right,
      },
    }
  }
}

#[derive(Debug, Clone)]
pub(crate) enum VerifiedExprKind {
  Null,
  Bool(bool),
  Int(i64),
  Float(f64),
  String(String),
  Array(Vec<VerifiedExpression>),
  Identifier(String),
  Member {
    receiver: Box<VerifiedExpression>,
    name: String,
  },
  FunctionCall {
    name: String,
    args: Vec<VerifiedExpression>,
  },
  ExpressionFunctionCall {
    name: String,
    params: Vec<String>,
    args: Vec<VerifiedExpression>,
    body: Box<VerifiedExpression>,
  },
  MethodCall {
    receiver: Box<VerifiedExpression>,
    name: String,
    args: Vec<VerifiedExpression>,
  },
  Unary {
    op: UnaryOp,
    expr: Box<VerifiedExpression>,
  },
  Binary {
    left: Box<VerifiedExpression>,
    op: BinaryOp,
    right: Box<VerifiedExpression>,
  },
}

pub enum VerifiedExprKindRef<'a> {
  Null,
  Bool(bool),
  Int(i64),
  Float(f64),
  String(&'a str),
  Array(&'a [VerifiedExpression]),
  Identifier(&'a str),
  Member {
    receiver: &'a VerifiedExpression,
    name: &'a str,
  },
  FunctionCall {
    name: &'a str,
    args: &'a [VerifiedExpression],
  },
  ExpressionFunctionCall {
    name: &'a str,
    params: &'a [String],
    args: &'a [VerifiedExpression],
    body: &'a VerifiedExpression,
  },
  MethodCall {
    receiver: &'a VerifiedExpression,
    name: &'a str,
    args: &'a [VerifiedExpression],
  },
  Unary {
    op: UnaryOp,
    expr: &'a VerifiedExpression,
  },
  Binary {
    left: &'a VerifiedExpression,
    op: BinaryOp,
    right: &'a VerifiedExpression,
  },
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct RegexLiteral {
  pub pattern: String,
  pub flavor: RegexFlavor,
  pub span: SourceSpan,
}

#[derive(Debug, Clone, Default)]
pub struct CompiledRegexCache {
  default: BTreeMap<String, Regex>,
  header_name: BTreeMap<String, Regex>,
}

impl CompiledRegexCache {
  pub fn insert(&mut self, literal: &RegexLiteral) -> Result<(), regex::Error> {
    let target = match literal.flavor {
      RegexFlavor::Default => &mut self.default,
      RegexFlavor::HeaderName => &mut self.header_name,
    };
    if target.contains_key(&literal.pattern) {
      return Ok(());
    }
    target.insert(literal.pattern.clone(), compile_regex(literal)?);
    Ok(())
  }

  pub fn get(&self, flavor: RegexFlavor, pattern: &str) -> Option<&Regex> {
    match flavor {
      RegexFlavor::Default => self.default.get(pattern),
      RegexFlavor::HeaderName => self.header_name.get(pattern),
    }
  }

  pub fn is_match(&self, flavor: RegexFlavor, pattern: &str, haystack: &str) -> Option<bool> {
    self
      .get(flavor, pattern)
      .map(|regex| regex.is_match(haystack))
  }

  pub fn len(&self) -> usize {
    self.default.len() + self.header_name.len()
  }

  pub fn is_empty(&self) -> bool {
    self.len() == 0
  }
}

fn compile_regex(literal: &RegexLiteral) -> Result<Regex, regex::Error> {
  match literal.flavor {
    RegexFlavor::Default => Regex::new(&literal.pattern),
    RegexFlavor::HeaderName => RegexBuilder::new(&literal.pattern)
      .case_insensitive(true)
      .build(),
  }
}