minsc 0.2.0

A Miniscript-based high-level scripting language for Bitcoin contracts
Documentation
use crate::ast::{Expr, Stmt, self};
use crate::util::{concat, parse_str_prefix};

grammar;

// Enable `//` comments
match {
    r"\s*" => { },
    r"//[^\n\r]*[\n\r]*" => { },
    _,
}

pub Program: Expr = <stmts:Stmt*> <ret:Return?> =>
  ast::Block { stmts,  return_value: ret.map(Into::into) }.into();

Block: Expr = <stmts:Stmt*> <ret:Return> =>
  ast::Block { stmts,  return_value: Some(ret.into()) }.into();

Stmt: Stmt = {
  FnDef,
  Assign,
}

Expr: Expr = {
  SimpleExpr,
  And,
  Or,
  ChildDerive,
};

SimpleExpr: Expr = {
  Number,
  Ident,
  Call,
  Thresh,
  BlockExpr,
  WithProb,
  Array,
  ArrayAccess,
  Duration,
  DateTime,
  Hash,
  PubKey,
  Paren<And>,
  Paren<Or>,
  Paren<ChildDerive>,
  Paren<SimpleExpr>,
};

SExpr: Expr = {
  Number,
  Ident,
  Call,
  BlockExpr,
};

Return: Expr = {
  Expr, // the preferred form
  "return" <Expr> ";"?,
};

// Expressions

Number: Expr = <s:r"\d{1,39}"> => ast::Expr::Number(<>.parse().unwrap()).into();

IdentTerm: ast::Ident = <s:r"[a-zA-Z_$][a-zA-Z0-9_$]{0,38}"> => ast::Ident(<>.into());
Ident: Expr = IdentTerm => <>.into();

Call: Expr = <ident:IdentTerm> "(" <args:List0<Expr, ",">> ")" =>
    ast::Call { ident, args }.into();

And: Expr = <List2<SimpleExpr, "&&">> => ast::And(<>).into();
Or: Expr = <List2<SimpleExpr, "||">> => ast::Or(<>).into();

Thresh: Expr = <thresh:SExpr> "of" <policies:SimpleExpr> =>
  ast::Thresh { thresh: thresh.into(), policies: policies.into() }.into();

BlockExpr: Expr = "{" <Block> "}" => <>.into();

WithProb: Expr = <prob:SExpr> "@" <expr:SimpleExpr> =>
    ast::WithProb { prob: prob.into(), expr: expr.into() }.into();

Array: Expr = "[" <List0<Expr, ",">> "]" =>
  ast::Array(<>).into();

ArrayAccess: Expr = <array:ArrayAccessLHS> "." <index:Number> =>
  ast::ArrayAccess { array: array.into(), index: index.into() }.into();

// TODO support all Paren<Expr>
ArrayAccessLHS = { Ident, Call, Array, BlockExpr };

// Statements

Assign: Stmt = "let"? <assigns:List1<Assignment, ",">> ";" =>
    ast::Assign(assigns).into();

Assignment: ast::Assignment = <lhs:IdentTerm> "=" <rhs:Expr> =>
    ast::Assignment { lhs, rhs };

FnDef: Stmt = {
    "fn" <ident:IdentTerm> "(" <signature:List0<IdentTerm, ",">> ")" "=" <body:Expr> ";" =>
        ast::FnDef { ident, signature, body }.into(),
    "fn" <ident:IdentTerm> "(" <signature:List0<IdentTerm, ",">> ")" "{" <body:Block> "}" ";"? =>
        ast::FnDef { ident, signature, body }.into(),
}

// An xpub or compressed standalone public key (uncomporessed is unsupported), with optional bip32 origin
PubKey: Expr = <s:r"(\[[a-f0-9]{8}(/\d+['h]?)*\])?([a-f0-9]{66}|([xt]pub[0-9a-zA-Z]{100,120}))"> =>
    Expr::PubKey(<>.into());

Hash: Expr = <s:r"[a-f0-9]{64}|[a-f0-9]{40}"> =>
    Expr::Hash(<>.into());

ChildDerive: Expr = {
    <parent:SimpleExpr> "/" <path:List1<SimpleExpr, "/">> <wildcard:"/*"?> =>
      ast::ChildDerive { parent: parent.into(), path, is_wildcard: wildcard.is_some() }.into(),
    <parent:SimpleExpr> "/*" =>
      ast::ChildDerive { parent: parent.into(), path: vec![], is_wildcard: true }.into(),
};

// Duration and times

Duration = { DurationBlocks, DurationClock };

DurationBlocks: Expr = r"\d+\s+blocks?" =>
  ast::Duration::BlockHeight(parse_str_prefix(<>)).into();

DurationClock: Expr = <heightwise:"heightwise"?> <parts:DurationClockPart+> =>
  ast::Duration::BlockTime { parts, heightwise: heightwise.is_some() }.into();

DurationClockPart: ast::DurationPart = {
  r"(\d+(?:\.\d+)?)\s+years?" => ast::DurationPart::Years(parse_str_prefix(<>)),
  r"(\d+(?:\.\d+)?)\s+months?" => ast::DurationPart::Months(parse_str_prefix(<>)),
  r"(\d+(?:\.\d+)?)\s+weeks?" => ast::DurationPart::Weeks(parse_str_prefix(<>)),
  r"(\d+(?:\.\d+)?)\s+days?" => ast::DurationPart::Days(parse_str_prefix(<>)),
  r"(\d+(?:\.\d+)?)\s+hours?" => ast::DurationPart::Hours(parse_str_prefix(<>)),
  r"(\d+(?:\.\d+)?)\s+min(ute)?s?" => ast::DurationPart::Minutes(parse_str_prefix(<>)),
  r"(\d+(?:\.\d+)?)\s+sec(ond)?s?" => ast::DurationPart::Seconds(parse_str_prefix(<>)),
}

DateTime: Expr = r"\d{4}-\d{1,2}-\d{1,2}(\s+\d{1,2}:\d{1,2})?" =>
  Expr::DateTime(<>.into());

// Helpers

// A `S`-separated list of zero or more `T` values
List0<T, S>: Vec<T> = <l:(<T> S)*> <t:T?> => concat(l, t);

// A `S`-separated list of one or more `T` values
List1<T, S>: Vec<T> = <l:(<T> S)*> <t:T> => concat(l, Some(t));

// A `S`-separated list of two or more `T` values
List2<T, S>: Vec<T> = <l:(<T> S)+> <t:T> => concat(l, Some(t));

Paren<T> = "(" <T> ")";