modularize

This commit is contained in:
mehbark 2025-07-02 14:49:27 -04:00
parent 038f6fe03d
commit 40fdac182f
4 changed files with 173 additions and 182 deletions

38
src/inst.rs Normal file
View 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, ")")
}
}
}
}

View file

@ -1,167 +1,13 @@
use core::fmt;
use crate::{inst::Inst, parse::parse};
use std::io::{self, Read};
use ariadne::{Color, Label, Report, ReportKind, Source};
use chumsky::{
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()
}
mod inst;
mod parse;
fn main() {
let mut src = String::new();
let _ = io::stdin().read_to_string(&mut src).unwrap();
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) => 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();
}
}
}
let insts = parse(&src);
println!("{}!\n{insts:#?}", Inst::Block(insts.clone()));
}

130
src/parse.rs Normal file
View 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
View file

@ -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
───╯