use serde::Serialize;
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct Span {
pub file: u32, pub lo: u32, pub hi: u32, }
impl Span {
pub fn dummy() -> Self {
Span {
file: 0,
lo: 0,
hi: 0,
}
}
}
#[derive(Debug, Default)]
pub struct SourceMap {
pub files: Vec<(PathBuf, String)>,
}
impl SourceMap {
pub fn add(&mut self, path: PathBuf, text: String) -> u32 {
self.files.push((path, text));
(self.files.len() - 1) as u32
}
pub fn resolve(&self, span: Span) -> (&PathBuf, u32, u32) {
let (path, text) = &self.files[span.file as usize];
let upto = &text[..(span.lo as usize).min(text.len())];
let line = upto.bytes().filter(|&b| b == b'\n').count() as u32 + 1;
let col = (upto.len() - upto.rfind('\n').map(|i| i + 1).unwrap_or(0)) as u32 + 1;
(path, line, col)
}
pub fn line_text(&self, span: Span) -> &str {
let (_, text) = &self.files[span.file as usize];
let lo = (span.lo as usize).min(text.len());
let start = text[..lo].rfind('\n').map(|i| i + 1).unwrap_or(0);
let end = text[lo..].find('\n').map(|i| lo + i).unwrap_or(text.len());
&text[start..end]
}
}
#[derive(Debug)]
pub struct VclError {
pub span: Option<Span>,
pub msg: String,
}
impl VclError {
pub fn new(span: Span, msg: impl Into<String>) -> Self {
VclError {
span: Some(span),
msg: msg.into(),
}
}
pub fn render(&self, sm: &SourceMap) -> String {
match self.span {
Some(span) => {
let (path, line, col) = sm.resolve(span);
let src = sm.line_text(span);
let caret = " ".repeat((col as usize).saturating_sub(1)) + "^";
format!(
"{}:{}:{}: {}\n {}\n {}",
path.display(),
line,
col,
self.msg,
src,
caret
)
}
None => self.msg.clone(),
}
}
}
pub type Result<T> = std::result::Result<T, VclError>;
#[derive(Debug, Clone, Serialize)]
pub struct Program {
#[serde(skip)]
#[allow(dead_code)]
pub vcl_version: Span,
pub decls: Vec<Decl>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Decl {
Import {
name: String,
from: Option<String>,
#[serde(skip)]
span: Span,
},
Backend {
name: String,
none: bool,
body: Option<Vec<Field>>,
#[serde(skip)]
span: Span,
},
Probe {
name: String,
body: Vec<Field>,
#[serde(skip)]
span: Span,
},
Acl {
name: String,
entries: Vec<AclEntry>,
#[serde(skip)]
span: Span,
},
Sub {
name: String,
body: Vec<Stmt>,
#[serde(skip)]
span: Span,
},
}
impl Decl {
pub fn name(&self) -> &str {
match self {
Decl::Import { name, .. }
| Decl::Backend { name, .. }
| Decl::Probe { name, .. }
| Decl::Acl { name, .. }
| Decl::Sub { name, .. } => name,
}
}
pub fn span(&self) -> Span {
match self {
Decl::Import { span, .. }
| Decl::Backend { span, .. }
| Decl::Probe { span, .. }
| Decl::Acl { span, .. }
| Decl::Sub { span, .. } => *span,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Field {
pub name: String, pub value: FieldValue,
#[serde(skip)]
pub span: Span,
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
pub enum FieldValue {
Expr(Expr),
Probe(Vec<Field>),
ProbeRef(String),
StringList(Vec<String>),
}
#[derive(Debug, Clone, Serialize)]
pub struct AclEntry {
pub negated: bool,
pub addr: String,
pub mask: Option<u8>,
#[serde(skip)]
pub span: Span,
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Stmt {
Set {
lhs: Lvalue,
rhs: Expr,
#[serde(skip)]
span: Span,
},
Unset {
lhs: Lvalue,
#[serde(skip)]
span: Span,
},
Call {
sub: String,
#[serde(skip)]
span: Span,
},
Return {
action: Option<ReturnAction>,
#[serde(skip)]
span: Span,
},
Synthetic {
value: Expr,
#[serde(skip)]
span: Span,
},
If {
arms: Vec<(Expr, Vec<Stmt>)>,
else_body: Option<Vec<Stmt>>,
#[serde(skip)]
span: Span,
},
New {
name: String,
vmod: String,
ctor: String,
args: Vec<Arg>,
#[serde(skip)]
span: Span,
},
Expr {
expr: Expr,
#[serde(skip)]
span: Span,
},
}
impl Stmt {
pub fn span(&self) -> Span {
match self {
Stmt::Set { span, .. }
| Stmt::Unset { span, .. }
| Stmt::Call { span, .. }
| Stmt::Return { span, .. }
| Stmt::Synthetic { span, .. }
| Stmt::If { span, .. }
| Stmt::New { span, .. }
| Stmt::Expr { span, .. } => *span,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ReturnAction {
pub name: String, pub args: Vec<Expr>,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(transparent)]
pub struct Lvalue {
pub parts: Vec<String>, }
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type", content = "value", rename_all = "snake_case")]
pub enum Expr {
Str(String),
Num(String),
Duration(f64),
Bytes(u64),
Bool(bool),
Omitted,
CSource(String),
Var(Vec<String>),
Call {
target: Vec<String>,
args: Vec<Arg>,
},
Unary {
op: UnOp,
expr: Box<Expr>,
},
Binary {
op: BinOp,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
}
#[derive(Debug, Clone, Serialize)]
pub struct Arg {
pub name: Option<String>,
pub value: Expr,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum UnOp {
Not, Neg, }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum BinOp {
Eq, Ne, Match, NotMatch, Lt,
Le,
Gt,
Ge,
And, Or, Add,
Sub,
Mul,
Div,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct NameMap {
pub entries: Vec<(String, String, String)>,
}
pub const BUILTIN_SUB_ORDER: &[&str] = &[
"vcl_init",
"vcl_connect",
"vcl_recv",
"vcl_pipe",
"vcl_pass",
"vcl_hash",
"vcl_purge",
"vcl_hit",
"vcl_miss",
"vcl_deliver",
"vcl_synth",
"vcl_backend_fetch",
"vcl_backend_response",
"vcl_backend_error",
"vcl_fini",
];
pub fn is_builtin_sub(name: &str) -> bool {
name.starts_with("vcl_")
}
pub fn duration_secs(num: f64, unit: &str) -> Option<f64> {
let mult = match unit {
"ms" => 0.001,
"s" => 1.0,
"m" => 60.0,
"h" => 3600.0,
"d" => 86400.0,
"w" => 604800.0,
"y" => 31_557_600.0,
_ => return None,
};
Some(num * mult)
}
pub fn bytes_val(num: f64, unit: &str) -> Option<u64> {
let mult: u64 = match unit {
"B" => 1,
"KB" => 1 << 10,
"MB" => 1 << 20,
"GB" => 1 << 30,
"TB" => 1 << 40,
_ => return None,
};
Some((num * mult as f64) as u64)
}
#[cfg(any(test, feature = "testutil"))]
#[allow(dead_code)]
pub mod builder {
use super::*;
pub fn sp() -> Span {
Span::dummy()
}
pub fn program(decls: Vec<Decl>) -> Program {
Program {
vcl_version: sp(),
decls,
}
}
pub fn import(name: &str) -> Decl {
Decl::Import {
name: name.into(),
from: None,
span: sp(),
}
}
pub fn backend(name: &str, fields: Vec<Field>) -> Decl {
Decl::Backend {
name: name.into(),
none: false,
body: Some(fields),
span: sp(),
}
}
pub fn backend_none(name: &str) -> Decl {
Decl::Backend {
name: name.into(),
none: true,
body: None,
span: sp(),
}
}
pub fn probe(name: &str, fields: Vec<Field>) -> Decl {
Decl::Probe {
name: name.into(),
body: fields,
span: sp(),
}
}
pub fn acl(name: &str, entries: Vec<AclEntry>) -> Decl {
Decl::Acl {
name: name.into(),
entries,
span: sp(),
}
}
pub fn acl_entry(addr: &str, mask: Option<u8>, negated: bool) -> AclEntry {
AclEntry {
negated,
addr: addr.into(),
mask,
span: sp(),
}
}
pub fn sub(name: &str, body: Vec<Stmt>) -> Decl {
Decl::Sub {
name: name.into(),
body,
span: sp(),
}
}
pub fn field(name: &str, value: FieldValue) -> Field {
Field {
name: name.into(),
value,
span: sp(),
}
}
pub fn fexpr(name: &str, e: Expr) -> Field {
field(name, FieldValue::Expr(e))
}
pub fn set(parts: &[&str], rhs: Expr) -> Stmt {
Stmt::Set {
lhs: lval(parts),
rhs,
span: sp(),
}
}
pub fn unset(parts: &[&str]) -> Stmt {
Stmt::Unset {
lhs: lval(parts),
span: sp(),
}
}
pub fn call(sub: &str) -> Stmt {
Stmt::Call {
sub: sub.into(),
span: sp(),
}
}
pub fn ret(action: Option<ReturnAction>) -> Stmt {
Stmt::Return { action, span: sp() }
}
pub fn ret_action(name: &str, args: Vec<Expr>) -> Stmt {
ret(Some(ReturnAction {
name: name.into(),
args,
}))
}
pub fn synthetic(value: Expr) -> Stmt {
Stmt::Synthetic { value, span: sp() }
}
pub fn if_(arms: Vec<(Expr, Vec<Stmt>)>, else_body: Option<Vec<Stmt>>) -> Stmt {
Stmt::If {
arms,
else_body,
span: sp(),
}
}
pub fn new_(name: &str, vmod: &str, ctor: &str, args: Vec<Arg>) -> Stmt {
Stmt::New {
name: name.into(),
vmod: vmod.into(),
ctor: ctor.into(),
args,
span: sp(),
}
}
pub fn expr_stmt(expr: Expr) -> Stmt {
Stmt::Expr { expr, span: sp() }
}
pub fn lval(parts: &[&str]) -> Lvalue {
Lvalue {
parts: parts.iter().map(|s| s.to_string()).collect(),
}
}
pub fn str_(s: &str) -> Expr {
Expr::Str(s.into())
}
pub fn num(s: &str) -> Expr {
Expr::Num(s.into())
}
pub fn dur(secs: f64) -> Expr {
Expr::Duration(secs)
}
pub fn bytes(n: u64) -> Expr {
Expr::Bytes(n)
}
pub fn bool_(b: bool) -> Expr {
Expr::Bool(b)
}
pub fn var(parts: &[&str]) -> Expr {
Expr::Var(parts.iter().map(|s| s.to_string()).collect())
}
pub fn fcall(target: &[&str], args: Vec<Arg>) -> Expr {
Expr::Call {
target: target.iter().map(|s| s.to_string()).collect(),
args,
}
}
pub fn arg(value: Expr) -> Arg {
Arg { name: None, value }
}
pub fn narg(name: &str, value: Expr) -> Arg {
Arg {
name: Some(name.into()),
value,
}
}
pub fn un(op: UnOp, e: Expr) -> Expr {
Expr::Unary {
op,
expr: Box::new(e),
}
}
pub fn bin(op: BinOp, l: Expr, r: Expr) -> Expr {
Expr::Binary {
op,
lhs: Box::new(l),
rhs: Box::new(r),
}
}
}