graphix-compiler 0.7.0

A dataflow language for UIs and network programming, compiler
Documentation
use crate::{
    expr::{
        parser::{
            csep, expr, fname, sep_by1_tok, sep_by_tok, spaces, spaces1, spstring,
            sptoken, typ, typname,
        },
        Expr, Pattern, StructurePattern,
    },
    typ::Type,
};
use arcstr::{literal, ArcStr};
use combine::{
    attempt, between, choice, optional,
    parser::char::string,
    stream::{position::SourcePosition, Range},
    token, unexpected_any, value, ParseError, Parser, RangeStream,
};
use fxhash::FxHashSet;
use netidx::utils::Either;
use netidx_value::parser::{value as parse_value, VAL_ESC, VAL_MUST_ESC};
use poolshark::local::LPooled;
use triomphe::Arc;

use super::not_prefix;

pub(super) fn slice_pattern<I>(
    all: Option<ArcStr>,
) -> impl Parser<I, Output = StructurePattern>
where
    I: RangeStream<Token = char, Position = SourcePosition>,
    I::Error: ParseError<I::Token, I::Range, I::Position>,
    I::Range: Range,
{
    macro_rules! all_left {
        ($pats:expr) => {{
            let mut err = false;
            let pats: Arc<[StructurePattern]> =
                Arc::from_iter($pats.drain(..).map(|s| match s {
                    Either::Left(s) => s,
                    Either::Right(_) => {
                        err = true;
                        StructurePattern::Ignore
                    }
                }));
            if err {
                return unexpected_any("invalid pattern").left();
            }
            pats
        }};
    }
    between(
        token('['),
        sptoken(']'),
        sep_by_tok(
            spaces().with(choice((
                string("..").map(|_| Either::Right(None)),
                attempt(fname().skip(spstring(".."))).map(|n| Either::Right(Some(n))),
                structure_pattern().map(|p| Either::Left(p)),
            ))),
            csep(),
            token(']'),
        ),
    )
    .then(move |mut pats: LPooled<Vec<Either<StructurePattern, Option<ArcStr>>>>| {
        let all = all.clone();
        if pats.len() == 0 {
            value(StructurePattern::Slice { all, binds: Arc::from_iter([]) }).right()
        } else if pats.len() == 1 {
            match pats.pop().unwrap() {
                Either::Left(s) => {
                    value(StructurePattern::Slice { all, binds: Arc::from_iter([s]) })
                        .right()
                }
                Either::Right(_) => unexpected_any("invalid singular range match").left(),
            }
        } else {
            match (&pats[0], &pats[pats.len() - 1]) {
                (Either::Right(_), Either::Right(_)) => {
                    unexpected_any("invalid pattern").left()
                }
                (Either::Right(_), Either::Left(_)) => {
                    let head = pats.remove(0).right().unwrap();
                    let suffix = all_left!(pats);
                    value(StructurePattern::SliceSuffix { all, head, suffix }).right()
                }
                (Either::Left(_), Either::Right(_)) => {
                    let tail = pats.pop().unwrap().right().unwrap();
                    let prefix = all_left!(pats);
                    value(StructurePattern::SlicePrefix { all, tail, prefix }).right()
                }
                (Either::Left(_), Either::Left(_)) => {
                    value(StructurePattern::Slice { all, binds: all_left!(pats) }).right()
                }
            }
        }
    })
}

fn tuple_pattern<I>(all: Option<ArcStr>) -> impl Parser<I, Output = StructurePattern>
where
    I: RangeStream<Token = char, Position = SourcePosition>,
    I::Error: ParseError<I::Token, I::Range, I::Position>,
    I::Range: Range,
{
    between(
        token('('),
        sptoken(')'),
        sep_by1_tok(structure_pattern(), csep(), token(')')),
    )
    .then(move |mut binds: LPooled<Vec<StructurePattern>>| {
        if binds.len() < 2 {
            unexpected_any("tuples must have at least 2 elements").left()
        } else {
            let all = all.clone();
            value(StructurePattern::Tuple { all, binds: Arc::from_iter(binds.drain(..)) })
                .right()
        }
    })
}

fn variant_pattern<I>(all: Option<ArcStr>) -> impl Parser<I, Output = StructurePattern>
where
    I: RangeStream<Token = char, Position = SourcePosition>,
    I::Error: ParseError<I::Token, I::Range, I::Position>,
    I::Range: Range,
{
    (
        token('`').with(typname()),
        optional(between(
            token('('),
            sptoken(')'),
            sep_by1_tok(structure_pattern(), csep(), token(')')),
        )),
    )
        .map(
            move |(tag, binds): (ArcStr, Option<LPooled<Vec<StructurePattern>>>)| {
                let all = all.clone();
                let mut binds = match binds {
                    None => LPooled::take(),
                    Some(a) => a,
                };
                StructurePattern::Variant {
                    all,
                    tag,
                    binds: Arc::from_iter(binds.drain(..)),
                }
            },
        )
}

pub(super) fn struct_pattern<I>(
    all: Option<ArcStr>,
) -> impl Parser<I, Output = StructurePattern>
where
    I: RangeStream<Token = char, Position = SourcePosition>,
    I::Error: ParseError<I::Token, I::Range, I::Position>,
    I::Range: Range,
{
    between(
        token('{'),
        sptoken('}'),
        spaces().with(sep_by1_tok(
            choice((
                string("..").map(|_| (literal!(""), StructurePattern::Ignore, false)),
                fname()
                    .skip(spaces())
                    .then(|name| {
                        optional(token(':').with(structure_pattern()))
                            .map(move |pat| (name.clone(), pat))
                    })
                    .map(|(name, pat)| match pat {
                        Some(pat) => (name, pat, true),
                        None => {
                            let pat = StructurePattern::Bind(name.clone());
                            (name, pat, true)
                        }
                    }),
            )),
            csep(),
            token('}'),
        )),
    )
    .then(move |mut binds: LPooled<Vec<(ArcStr, StructurePattern, bool)>>| {
        let mut exhaustive = true;
        binds.retain(|(_, _, ex)| {
            exhaustive &= *ex;
            *ex
        });
        binds.sort_by_key(|(s, _, _)| s.clone());
        let s = binds.iter().map(|(s, _, _)| s).collect::<LPooled<FxHashSet<_>>>();
        if s.len() < binds.len() {
            unexpected_any("struct fields must be unique").left()
        } else {
            drop(s);
            let all = all.clone();
            let binds = Arc::from_iter(binds.drain(..).map(|(s, p, _)| (s, p)));
            value(StructurePattern::Struct { all, exhaustive, binds }).right()
        }
    })
}

fn underbar_pattern<I>(all: bool) -> impl Parser<I, Output = StructurePattern>
where
    I: RangeStream<Token = char>,
    I::Error: ParseError<I::Token, I::Range, I::Position>,
    I::Range: Range,
{
    token('_').then(move |_| {
        if all {
            unexpected_any("all patterns are not supported by _").left()
        } else {
            value(StructurePattern::Ignore).right()
        }
    })
}

fn bind_pattern<I>(all: bool) -> impl Parser<I, Output = StructurePattern>
where
    I: RangeStream<Token = char>,
    I::Error: ParseError<I::Token, I::Range, I::Position>,
    I::Range: Range,
{
    fname().then(move |name| {
        if all {
            unexpected_any("all patterns are not supported by bind").left()
        } else {
            value(StructurePattern::Bind(name)).right()
        }
    })
}

fn literal_pattern<I>(all: bool) -> impl Parser<I, Output = StructurePattern>
where
    I: RangeStream<Token = char>,
    I::Error: ParseError<I::Token, I::Range, I::Position>,
    I::Range: Range,
{
    attempt(parse_value(&VAL_MUST_ESC, &VAL_ESC)).skip(not_prefix()).then(move |v| {
        if all {
            unexpected_any("all patterns are not supported by literals").left()
        } else {
            value(StructurePattern::Literal(v)).right()
        }
    })
}

fn all_pattern<I>() -> impl Parser<I, Output = ArcStr>
where
    I: RangeStream<Token = char>,
    I::Error: ParseError<I::Token, I::Range, I::Position>,
    I::Range: Range,
{
    fname().skip(sptoken('@')).skip(spaces())
}

parser! {
    pub(crate) fn structure_pattern[I]()(I) -> StructurePattern
    where [I: RangeStream<Token = char, Position = SourcePosition>, I::Range: Range]
    {
        spaces().with(optional(attempt(all_pattern()))).then(|all| choice((
            slice_pattern(all.clone()),
            tuple_pattern(all.clone()),
            struct_pattern(all.clone()),
            variant_pattern(all.clone()),
            underbar_pattern(all.is_some()),
            literal_pattern(all.is_some()),
            bind_pattern(all.is_some()),
        )))
    }
}

pub(crate) fn pattern<I>() -> impl Parser<I, Output = Pattern>
where
    I: RangeStream<Token = char, Position = SourcePosition>,
    I::Error: ParseError<I::Token, I::Range, I::Position>,
    I::Range: Range,
{
    (
        optional(attempt(typ().skip(spaces1()).skip(string("as")).skip(spaces1()))),
        structure_pattern(),
        optional(attempt(spaces1().with(string("if")).with(spaces1()).with(expr()))),
    )
        .map(
            |(type_predicate, structure_predicate, guard): (
                Option<Type>,
                StructurePattern,
                Option<Expr>,
            )| { Pattern { type_predicate, structure_predicate, guard } },
        )
}