start implementing

This commit is contained in:
JOLIMAITRE Matthieu 2024-06-07 03:31:12 +02:00
parent e27c3e624e
commit e6d013c716
9 changed files with 846 additions and 1 deletions

289
Cargo.lock generated Normal file
View file

@ -0,0 +1,289 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "allocator-api2"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "backtrace"
version = "0.3.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "backtrace-on-stack-overflow"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fd2d70527f3737a1ad17355e260706c1badebabd1fa06a7a053407380df841b"
dependencies = [
"backtrace",
"libc",
"nix",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cc"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chumsky"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9"
dependencies = [
"hashbrown",
"stacker",
]
[[package]]
name = "gimli"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
name = "libc"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "memchr"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "miniz_oxide"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae"
dependencies = [
"adler",
]
[[package]]
name = "nix"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c"
dependencies = [
"bitflags",
"cc",
"cfg-if",
"libc",
"memoffset",
]
[[package]]
name = "object"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "persist"
version = "0.1.0"
dependencies = [
"backtrace-on-stack-overflow",
"chumsky",
]
[[package]]
name = "proc-macro2"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23"
dependencies = [
"unicode-ident",
]
[[package]]
name = "psm"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874"
dependencies = [
"cc",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "stacker"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce"
dependencies = [
"cc",
"cfg-if",
"libc",
"psm",
"winapi",
]
[[package]]
name = "syn"
version = "2.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "zerocopy"
version = "0.7.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View file

@ -6,3 +6,5 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
backtrace-on-stack-overflow = "0.3.0"
chumsky = "0.9.3"

10
run.sh Executable file
View file

@ -0,0 +1,10 @@
#!/bin/sh
set -e
cd "$(dirname "$(realpath "$0")")"
echo "
let value = 4 * 3 + 2 * 1;
let expects = (4 * 3) + (2 * 1);
print(value, expects)
" | cargo run

2
rustfmt.toml Normal file
View file

@ -0,0 +1,2 @@
hard_tabs = true
max_width = 120

3
src/lib/mod.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod parse;
pub type Span = std::ops::Range<usize>;

304
src/lib/parse/ast/mod.rs Normal file
View file

@ -0,0 +1,304 @@
use crate::lib::parse::{token::Tok, AstParser};
use std::{
any::{type_name, type_name_of_val},
fmt::Display,
};
use chumsky::{prelude::*, Parser as _};
use text::{ident, int, whitespace};
use super::token::Key;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Program {
body: Body,
}
impl Program {
pub fn parser() -> impl AstParser<Self> {
let expr = Expression::parser();
Body::parser(expr).map(|body| Self { body }).then_ignore(end())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Statement {
Definition(Definition),
Assign(Assign),
Expression(Expression),
}
impl Statement {
fn parser(expr: impl AstParser<Expression> + 'static) -> impl AstParser<Self> {
let def = Definition::parser(expr.clone()).map(Self::Definition);
let assign = Assign::parser(expr).map(Self::Assign);
let expr = Expression::parser().map(Self::Expression);
def.or(assign).or(expr)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Definition {
defined: Pattern,
value: Expression,
}
impl Definition {
fn parser(expr: impl AstParser<Expression> + 'static) -> impl AstParser<Self> {
just(Tok::Let)
.ignore_then(Pattern::parser())
.then_ignore(just(Tok::Key(Key::Assign)))
.then(expr)
.map(|(defined, value)| Self { defined, value })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Assign {
assigned: Pattern,
value: Expression,
}
impl Assign {
fn parser(expr: impl AstParser<Expression> + 'static) -> impl AstParser<Self> {
Pattern::parser()
.then_ignore(just(Tok::Key(Key::Assign)))
.then(expr)
.map(|(assigned, value)| Self { assigned, value })
.boxed()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Name(String);
impl Name {
fn parser() -> impl AstParser<Self> {
select! { Tok::Id(name) => name.clone() }.map(Self)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Pattern {
Name(Name),
}
impl Pattern {
fn parser() -> impl AstParser<Self> {
Name::parser().map(Self::Name)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Expression {
Literal(Literal),
Variable(Name),
Block(Body),
Op(BoxEx, Op, BoxEx),
Access(BoxEx, Access),
}
impl Expression {
#[allow(clippy::let_and_return)]
fn parser() -> impl AstParser<Self> {
recursive(|rec| {
let literal = Literal::parser().map(Self::Literal);
let variable = Name::parser().map(Self::Variable);
let value = literal.or(variable);
let closed = value.or(rec
.clone()
.boxed()
.delimited_by(just(Tok::Key(Key::BracketsL)), just(Tok::Key(Key::BracketsR))));
let access = closed
.then(Access::parser(rec.clone()).repeated())
.foldl(|e, a| Self::Access(BoxEx::new(e), a));
let product = access
.clone()
.then(Op::parser_mul().then(access).repeated())
.foldl(|a, (op, b)| Self::Op(BoxEx::new(a), op, BoxEx::new(b)));
let addition = product
.clone()
.then(Op::parser_add().then(product).repeated())
.foldl(|a, (op, b)| Self::Op(BoxEx::new(a), op, BoxEx::new(b)));
let comparisons = addition
.clone()
.then(Op::parser_cmp().then(addition).repeated())
.foldl(|a, (op, b)| Self::Op(BoxEx::new(a), op, BoxEx::new(b)));
comparisons
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct BoxEx(pub Box<Expression>);
impl BoxEx {
fn new(expr: Expression) -> Self {
Self(Box::new(expr))
}
}
trait ExprParser<I: Clone>: Parser<I, Expression> {
fn boxify(self) -> chumsky::combinator::Map<Self, fn(Expression) -> BoxEx, Expression>
where
Self: Sized,
{
self.map(|e| BoxEx(Box::new(e)))
}
}
impl<T, I> ExprParser<I> for T
where
T: Parser<I, Expression>,
I: Clone,
{
}
#[derive(Debug, Clone, PartialEq)]
pub enum Literal {
Number(f32),
String(String),
Bool(bool),
}
impl std::hash::Hash for Literal {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
}
}
impl Eq for Literal {}
fn parse_num(num: &str) -> Option<f32> {
if num.starts_with('.') {
format!("0{num}").parse().ok()
} else {
num.parse().ok()
}
}
impl Literal {
fn parser() -> impl AstParser<Self> {
let num = select! { Tok::Num(num) => num }
.map(|s| parse_num(&s).unwrap())
.map(Self::Number);
let str = select! {Tok::Str(str) => str}.map(Self::String);
let bool = select! { Tok::Bool(bool) => bool }.map(Self::Bool);
num.or(str).or(bool)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Access {
Invoke(Invoke),
Index(Index),
Property(Property),
}
impl Access {
fn parser(expr: impl AstParser<Expression>) -> impl AstParser<Self> {
Invoke::parser(expr.clone())
.map(Self::Invoke)
.or(Index::parser(expr).map(Self::Index))
.or(Property::parser().map(Self::Property))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Invoke {
arguments: Vec<Expression>,
}
impl Invoke {
fn parser(expr: impl AstParser<Expression>) -> impl AstParser<Self> {
expr.separated_by(just(Tok::Key(Key::Comma)))
.delimited_by(just(Tok::Key(Key::BracketsL)), just(Tok::Key(Key::BracketsR)))
.map(|arguments| Self { arguments })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Index {
index: BoxEx,
}
impl Index {
fn parser(expr: impl AstParser<Expression>) -> impl AstParser<Self> {
expr.delimited_by(just(Tok::Key(Key::SquBrackL)), just(Tok::Key(Key::SquBrackR)))
.boxify()
.map(|index| Self { index })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Property {
index: Name,
}
impl Property {
fn parser() -> impl AstParser<Self> {
Name::parser().map(|index| Self { index })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Op {
Add,
Sub,
Div,
Mul,
Eq,
Neq,
Lt,
Gt,
Leq,
Geq,
And,
Or,
}
impl Op {
fn parser_add() -> impl AstParser<Self> {
let add = just(Tok::Key(Key::Add)).to(Self::Add);
let sub = just(Tok::Key(Key::Sub)).to(Self::Sub);
add.or(sub)
}
fn parser_mul() -> impl AstParser<Self> {
let div = just(Tok::Key(Key::Div)).to(Self::Div);
let mul = just(Tok::Key(Key::Mul)).to(Self::Mul);
div.or(mul)
}
fn parser_cmp() -> impl AstParser<Self> {
let eq = just(Tok::Key(Key::Eq)).to(Self::Eq);
let neq = just(Tok::Key(Key::Neq)).to(Self::Neq);
let lt = just(Tok::Key(Key::AngBrackL)).to(Self::Lt);
let gt = just(Tok::Key(Key::AngBrackR)).to(Self::Gt);
let leq = just(Tok::Key(Key::Leq)).to(Self::Leq);
let geq = just(Tok::Key(Key::Geq)).to(Self::Geq);
eq.or(neq).or(lt).or(gt).or(leq).or(geq)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Body {
statements: Vec<Statement>,
last_expr: Option<BoxEx>,
}
impl Body {
fn parser(expr: impl AstParser<Expression> + 'static) -> impl AstParser<Self> {
let statements = Statement::parser(expr.clone())
.then_ignore(just(Key::Semi.tok()))
.repeated();
statements
.then(expr.boxify().or_not())
.map(|(statements, last_expr)| Self { statements, last_expr })
}
}

33
src/lib/parse/mod.rs Normal file
View file

@ -0,0 +1,33 @@
use std::hash::Hash;
use chumsky::error::Simple;
use token::Tok;
pub mod ast;
pub mod token;
/// Parser over Generic input
pub trait AnyParser<I, O>: chumsky::Parser<I, O, Error = Simple<I>>
where
I: Clone + Hash + Eq,
{
}
impl<T, I, O> AnyParser<I, O> for T
where
I: Clone + Hash + Eq,
T: chumsky::Parser<I, O, Error = Simple<I>>,
{
}
pub type AstParseErr = Simple<Tok>;
/// Parser over Token Streams
pub trait AstParser<O: Clone>: AstParserNonClone<O> + Clone {}
impl<T, O> AstParser<O> for T
where
T: AstParserNonClone<O> + Clone,
O: Clone,
{
}
pub trait AstParserNonClone<O>: AnyParser<Tok, O> {}
impl<T, O> AstParserNonClone<O> for T where T: AnyParser<Tok, O> {}

185
src/lib/parse/token.rs Normal file
View file

@ -0,0 +1,185 @@
use std::fmt::Display;
use chumsky::{prelude::*, Parser as _};
use text::{ident, int, whitespace};
use crate::lib::Span;
use super::AnyParser;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Key {
/// `!`
Not,
/// `-`
Sub,
/// `+`
Add,
/// `*`
Mul,
/// `/`
Div,
/// `-=`
SubEq,
/// `+=`
AddEq,
/// `*=`
MulEq,
/// `/=`
DivEq,
/// `==`
Eq,
/// `!=`
Neq,
/// `<=`
Leq,
/// `>=`
Geq,
/// `(`
BracketsL,
/// `)`
BracketsR,
/// `{`
CurBrackL,
/// `}`
CurBrackR,
/// `[`
SquBrackL,
/// `]`
SquBrackR,
/// `<`
AngBrackL,
/// `>`
AngBrackR,
/// `:`
Colon,
/// `;`
Semi,
/// `,`
Comma,
/// `.`
Dot,
/// `=`
Assign,
/// `...`
Unfold,
}
impl Key {
pub fn parser() -> impl AnyParser<char, Self> {
one_of("")
.to(Key::Add)
.or(just('!').to(Key::Not))
.or(just("+=").to(Key::AddEq))
.or(just("-=").to(Key::SubEq))
.or(just("*=").to(Key::MulEq))
.or(just("/=").to(Key::DivEq))
.or(just('-').to(Key::Add))
.or(just('+').to(Key::Sub))
.or(just('*').to(Key::Mul))
.or(just('/').to(Key::Div))
.or(just("<=").to(Key::Leq))
.or(just(">=").to(Key::Geq))
.or(just('(').to(Key::BracketsL))
.or(just(')').to(Key::BracketsR))
.or(just('{').to(Key::CurBrackL))
.or(just('}').to(Key::CurBrackR))
.or(just('[').to(Key::SquBrackL))
.or(just(']').to(Key::SquBrackR))
.or(just('<').to(Key::AngBrackL))
.or(just('>').to(Key::AngBrackR))
.or(just(':').to(Key::Colon))
.or(just(';').to(Key::Semi))
.or(just(',').to(Key::Comma))
.or(just("...").to(Key::Unfold))
.or(just('.').to(Key::Dot))
.or(just("==").to(Key::Eq))
.or(just("!=").to(Key::Neq))
.or(just("=").to(Key::Assign))
}
pub fn tok(self) -> Tok {
Tok::Key(self)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Tok {
Id(String),
Str(String),
Num(String),
Bool(bool),
Key(Key),
Let,
Fn,
Type,
If,
Else,
Loop,
While,
For,
In,
Use,
Pub,
And,
Or,
Is,
}
impl Display for Tok {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Token")
}
}
impl Tok {
pub fn parser() -> impl AnyParser<char, (Tok, Span)> {
let key = Key::parser().map(Self::Key);
let integer = int(10);
let float = int(10)
.or_not()
.map(|op| op.unwrap_or("".into()))
.then(just('.').then(int(10)))
.map(|(int, (dot, rest))| format!("{int}{dot}{rest}"));
let num = integer.or(float).map(Self::Num);
let str = just('"')
.not()
.repeated()
.delimited_by(just('"'), just('"'))
.map(|e| e.iter().collect())
.map(Self::Str);
let id = ident().map(|id: String| match id.as_str() {
"true" => Self::Bool(true),
"false" => Self::Bool(false),
"if" => Self::If,
"else" => Self::Else,
"let" => Self::Let,
"fn" => Self::Fn,
"type" => Self::Type,
"loop" => Self::Loop,
"while" => Self::While,
"for" => Self::For,
"in" => Self::In,
"use" => Self::Use,
"pub" => Self::Pub,
"and" => Self::And,
"or" => Self::Or,
"is" => Self::Is,
_ => Self::Id(id),
});
key.or(str)
.or(num)
.or(id)
.then_ignore(whitespace())
.map_with_span(|tok, span| (tok, span))
}
pub fn parse_all() -> impl AnyParser<char, Vec<(Tok, Span)>> {
let toks = Self::parser().then_ignore(whitespace()).repeated();
whitespace().ignore_then(toks).then_ignore(end()).collect()
}
}

View file

@ -1,3 +1,20 @@
#![allow(unused)]
#![allow(special_module_name)]
use std::io::{stdin, Read};
use chumsky::Parser;
use lib::parse::{ast::Program, token::Tok};
mod lib;
fn main() {
println!("Hello, world!");
unsafe { backtrace_on_stack_overflow::enable() };
let mut line = String::new();
stdin().read_to_string(&mut line);
let toks = Tok::parse_all().parse(line).unwrap();
let toks = toks.into_iter().map(|(t, r)| t).collect::<Vec<_>>();
let program = Program::parser().parse(toks);
dbg!(program);
}