modularize
This commit is contained in:
parent
038f6fe03d
commit
40fdac182f
4 changed files with 173 additions and 182 deletions
38
src/inst.rs
Normal file
38
src/inst.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
use core::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Inst<'a> {
|
||||||
|
Set(&'a str),
|
||||||
|
Var(&'a str),
|
||||||
|
Num(f64),
|
||||||
|
Call,
|
||||||
|
Block(Vec<Inst<'a>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Inst<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Inst::Set(v) => write!(f, "→{v}"),
|
||||||
|
Inst::Var(v) => write!(f, "{v}"),
|
||||||
|
Inst::Num(n) => write!(f, "{n}"),
|
||||||
|
Inst::Call => write!(f, "!"),
|
||||||
|
Inst::Block(insts) => {
|
||||||
|
write!(f, "(")?;
|
||||||
|
if insts.len() == 1 {
|
||||||
|
write!(f, "{}", insts[0])?;
|
||||||
|
} else if !insts.is_empty() {
|
||||||
|
for window in insts.windows(2) {
|
||||||
|
match window {
|
||||||
|
[a, Inst::Call] => write!(f, "{a}")?,
|
||||||
|
[a, _] => write!(f, "{a} ")?,
|
||||||
|
[last] => write!(f, "{last}")?,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, "{}", insts.last().unwrap())?;
|
||||||
|
}
|
||||||
|
write!(f, ")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
164
src/main.rs
164
src/main.rs
|
|
@ -1,167 +1,13 @@
|
||||||
use core::fmt;
|
use crate::{inst::Inst, parse::parse};
|
||||||
use std::io::{self, Read};
|
use std::io::{self, Read};
|
||||||
|
|
||||||
use ariadne::{Color, Label, Report, ReportKind, Source};
|
mod inst;
|
||||||
use chumsky::{
|
mod parse;
|
||||||
input::{Stream, ValueInput},
|
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
use logos::Logos;
|
|
||||||
|
|
||||||
#[derive(Logos, Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
#[logos(skip r"[ \t\n\r\f]+")]
|
|
||||||
enum Token<'a> {
|
|
||||||
#[token("->")]
|
|
||||||
#[token("→")]
|
|
||||||
Set,
|
|
||||||
|
|
||||||
#[token("(")]
|
|
||||||
OpenParen,
|
|
||||||
|
|
||||||
#[token(")")]
|
|
||||||
CloseParen,
|
|
||||||
|
|
||||||
#[regex(r"[a-zA-Z0-9+*/%.:=-]+")]
|
|
||||||
VarOrNum(&'a str),
|
|
||||||
|
|
||||||
#[token("!")]
|
|
||||||
Call,
|
|
||||||
|
|
||||||
Error,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Token<'_> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Token::Set => write!(f, "→"),
|
|
||||||
Token::OpenParen => write!(f, "("),
|
|
||||||
Token::CloseParen => write!(f, ")"),
|
|
||||||
Token::VarOrNum(s) => write!(f, "{s}"),
|
|
||||||
Token::Call => write!(f, "!"),
|
|
||||||
Token::Error => write!(f, "ERROR"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
enum Inst<'a> {
|
|
||||||
Set(&'a str),
|
|
||||||
Var(&'a str),
|
|
||||||
Num(f64),
|
|
||||||
Call,
|
|
||||||
Block(Vec<Inst<'a>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Inst<'_> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Inst::Set(v) => write!(f, "→{v}"),
|
|
||||||
Inst::Var(v) => write!(f, "{v}"),
|
|
||||||
Inst::Num(n) => write!(f, "{n}"),
|
|
||||||
Inst::Call => write!(f, "!"),
|
|
||||||
Inst::Block(insts) => {
|
|
||||||
write!(f, "(")?;
|
|
||||||
if insts.len() == 1 {
|
|
||||||
write!(f, "{}", insts[0])?;
|
|
||||||
} else if !insts.is_empty() {
|
|
||||||
for window in insts.windows(2) {
|
|
||||||
match window {
|
|
||||||
[a, Inst::Call] => write!(f, "{a}")?,
|
|
||||||
[a, _] => write!(f, "{a} ")?,
|
|
||||||
[last] => write!(f, "{last}")?,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
write!(f, "{}", insts.last().unwrap())?;
|
|
||||||
}
|
|
||||||
write!(f, ")")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parser<'tokens, 'src: 'tokens, I>()
|
|
||||||
-> impl Parser<'tokens, I, Vec<Inst<'src>>, extra::Err<Rich<'tokens, Token<'src>>>>
|
|
||||||
where
|
|
||||||
I: ValueInput<'tokens, Token = Token<'src>, Span = SimpleSpan>,
|
|
||||||
{
|
|
||||||
let inst = recursive(|inst| {
|
|
||||||
let block = inst
|
|
||||||
.clone()
|
|
||||||
.repeated()
|
|
||||||
.collect()
|
|
||||||
.map(Inst::Block)
|
|
||||||
.delimited_by(
|
|
||||||
just(Token::OpenParen),
|
|
||||||
just(Token::CloseParen).or(end().validate(|(), e, emitter| {
|
|
||||||
emitter.emit(Rich::custom(e.span(), "Expected close paren"));
|
|
||||||
Token::Error
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
choice((
|
|
||||||
just(Token::Set).ignore_then(inst.validate(|v: Inst<'_>, e, emitter| {
|
|
||||||
if let Inst::Var(v) = v {
|
|
||||||
Inst::Set(v)
|
|
||||||
} else {
|
|
||||||
emitter.emit(Rich::custom(
|
|
||||||
e.span(),
|
|
||||||
"Set target must be a variable".to_string(),
|
|
||||||
));
|
|
||||||
Inst::Set("invalid")
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
just(Token::Call).to(Inst::Call),
|
|
||||||
select! {
|
|
||||||
Token::VarOrNum(s) => s.parse::<f64>().map_or_else(|_| Inst::Var(s), Inst::Num),
|
|
||||||
},
|
|
||||||
block,
|
|
||||||
just(Token::Error).validate(|_, e, emitter| {
|
|
||||||
emitter.emit(Rich::custom(e.span(), "Invalid token".to_string()));
|
|
||||||
Inst::Call
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
});
|
|
||||||
|
|
||||||
inst.or(
|
|
||||||
just(Token::CloseParen).try_map(|_, span| Err(Rich::custom(span, "Unmatched close paren")))
|
|
||||||
)
|
|
||||||
.repeated()
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut src = String::new();
|
let mut src = String::new();
|
||||||
let _ = io::stdin().read_to_string(&mut src).unwrap();
|
let _ = io::stdin().read_to_string(&mut src).unwrap();
|
||||||
|
|
||||||
let token_iter = Token::lexer(&src).spanned().map(|(tok, span)| match tok {
|
let insts = parse(&src);
|
||||||
Ok(tok) => (tok, span.into()),
|
println!("{}!\n{insts:#?}", Inst::Block(insts.clone()));
|
||||||
Err(()) => (Token::Error, span.into()),
|
|
||||||
});
|
|
||||||
|
|
||||||
let token_stream =
|
|
||||||
Stream::from_iter(token_iter).map((0..src.len()).into(), |(t, s): (_, _)| (t, s));
|
|
||||||
|
|
||||||
let source_filename = "input";
|
|
||||||
|
|
||||||
match parser().parse(token_stream).into_result() {
|
|
||||||
Ok(insts) => println!("{}!\n{insts:#?}", Inst::Block(insts.clone())),
|
|
||||||
Err(errs) => {
|
|
||||||
for err in errs {
|
|
||||||
Report::build(
|
|
||||||
ReportKind::Error,
|
|
||||||
(source_filename, err.span().into_range()),
|
|
||||||
)
|
|
||||||
.with_config(ariadne::Config::new().with_index_type(ariadne::IndexType::Byte))
|
|
||||||
.with_label(
|
|
||||||
Label::new((source_filename, err.span().into_range()))
|
|
||||||
.with_message(err.reason())
|
|
||||||
.with_color(Color::Red),
|
|
||||||
)
|
|
||||||
.finish()
|
|
||||||
.eprint((source_filename, Source::from(&src)))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
130
src/parse.rs
Normal file
130
src/parse.rs
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
use core::fmt;
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
use ariadne::{Color, Label, Report, ReportKind, Source};
|
||||||
|
use chumsky::{
|
||||||
|
input::{Stream, ValueInput},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use logos::Logos;
|
||||||
|
|
||||||
|
use crate::inst::Inst;
|
||||||
|
|
||||||
|
#[derive(Logos, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
#[logos(skip r"[ \t\n\r\f]+")]
|
||||||
|
enum Token<'a> {
|
||||||
|
#[token("->")]
|
||||||
|
#[token("→")]
|
||||||
|
Set,
|
||||||
|
|
||||||
|
#[token("(")]
|
||||||
|
OpenParen,
|
||||||
|
|
||||||
|
#[token(")")]
|
||||||
|
CloseParen,
|
||||||
|
|
||||||
|
#[regex(r"[a-zA-Z0-9+*/%.:=-]+")]
|
||||||
|
VarOrNum(&'a str),
|
||||||
|
|
||||||
|
#[token("!")]
|
||||||
|
Call,
|
||||||
|
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Token<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Token::Set => write!(f, "→"),
|
||||||
|
Token::OpenParen => write!(f, "("),
|
||||||
|
Token::CloseParen => write!(f, ")"),
|
||||||
|
Token::VarOrNum(s) => write!(f, "{s}"),
|
||||||
|
Token::Call => write!(f, "!"),
|
||||||
|
Token::Error => write!(f, "ERROR"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parser<'tokens, 'src: 'tokens, I>()
|
||||||
|
-> impl Parser<'tokens, I, Vec<Inst<'src>>, extra::Err<Rich<'tokens, Token<'src>>>>
|
||||||
|
where
|
||||||
|
I: ValueInput<'tokens, Token = Token<'src>, Span = SimpleSpan>,
|
||||||
|
{
|
||||||
|
let inst = recursive(|inst| {
|
||||||
|
let block = inst
|
||||||
|
.clone()
|
||||||
|
.repeated()
|
||||||
|
.collect()
|
||||||
|
.map(Inst::Block)
|
||||||
|
.delimited_by(
|
||||||
|
just(Token::OpenParen),
|
||||||
|
just(Token::CloseParen).or(end().validate(|(), e, emitter| {
|
||||||
|
emitter.emit(Rich::custom(e.span(), "Expected close paren"));
|
||||||
|
Token::Error
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
choice((
|
||||||
|
just(Token::Set).ignore_then(inst.validate(|v: Inst<'_>, e, emitter| {
|
||||||
|
if let Inst::Var(v) = v {
|
||||||
|
Inst::Set(v)
|
||||||
|
} else {
|
||||||
|
emitter.emit(Rich::custom(
|
||||||
|
e.span(),
|
||||||
|
"Set target must be a variable".to_string(),
|
||||||
|
));
|
||||||
|
Inst::Set("invalid")
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
just(Token::Call).to(Inst::Call),
|
||||||
|
select! {
|
||||||
|
Token::VarOrNum(s) => s.parse::<f64>().map_or_else(|_| Inst::Var(s), Inst::Num),
|
||||||
|
},
|
||||||
|
block,
|
||||||
|
just(Token::Error).validate(|_, e, emitter| {
|
||||||
|
emitter.emit(Rich::custom(e.span(), "Invalid token".to_string()));
|
||||||
|
Inst::Call
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
inst.or(
|
||||||
|
just(Token::CloseParen).try_map(|_, span| Err(Rich::custom(span, "Unmatched close paren")))
|
||||||
|
)
|
||||||
|
.repeated()
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(src: &str) -> Vec<Inst<'_>> {
|
||||||
|
let token_iter = Token::lexer(src).spanned().map(|(tok, span)| match tok {
|
||||||
|
Ok(tok) => (tok, span.into()),
|
||||||
|
Err(()) => (Token::Error, span.into()),
|
||||||
|
});
|
||||||
|
|
||||||
|
let token_stream =
|
||||||
|
Stream::from_iter(token_iter).map((0..src.len()).into(), |(t, s): (_, _)| (t, s));
|
||||||
|
|
||||||
|
let source_filename = "input";
|
||||||
|
|
||||||
|
match parser().parse(token_stream).into_result() {
|
||||||
|
Ok(insts) => insts,
|
||||||
|
Err(errs) => {
|
||||||
|
for err in errs {
|
||||||
|
Report::build(
|
||||||
|
ReportKind::Error,
|
||||||
|
(source_filename, err.span().into_range()),
|
||||||
|
)
|
||||||
|
.with_config(ariadne::Config::new().with_index_type(ariadne::IndexType::Byte))
|
||||||
|
.with_label(
|
||||||
|
Label::new((source_filename, err.span().into_range()))
|
||||||
|
.with_message(err.reason())
|
||||||
|
.with_color(Color::Red),
|
||||||
|
)
|
||||||
|
.finish()
|
||||||
|
.eprint((source_filename, Source::from(&src)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
process::exit(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
xclip
23
xclip
|
|
@ -1,23 +0,0 @@
|
||||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s
|
|
||||||
Running `target/debug/puyo-lang`
|
|
||||||
Error:
|
|
||||||
╭─[ input:1:7 ]
|
|
||||||
│
|
|
||||||
1 │ a b ->1 c ->2 ->a ->(1 2 3)
|
|
||||||
│ ┬
|
|
||||||
│ ╰── Set target must be a variable
|
|
||||||
───╯
|
|
||||||
Error:
|
|
||||||
╭─[ input:1:13 ]
|
|
||||||
│
|
|
||||||
1 │ a b ->1 c ->2 ->a ->(1 2 3)
|
|
||||||
│ ┬
|
|
||||||
│ ╰── Set target must be a variable
|
|
||||||
───╯
|
|
||||||
Error:
|
|
||||||
╭─[ input:1:21 ]
|
|
||||||
│
|
|
||||||
1 │ a b ->1 c ->2 ->a ->(1 2 3)
|
|
||||||
│ ───┬───
|
|
||||||
│ ╰───── Set target must be a variable
|
|
||||||
───╯
|
|
||||||
Loading…
Reference in a new issue