From 40fdac182fbabcec8caa479547060c6c3f1afc1f Mon Sep 17 00:00:00 2001 From: mehbark Date: Wed, 2 Jul 2025 14:49:27 -0400 Subject: [PATCH] modularize --- src/inst.rs | 38 ++++++++++++ src/main.rs | 164 ++------------------------------------------------- src/parse.rs | 130 ++++++++++++++++++++++++++++++++++++++++ xclip | 23 -------- 4 files changed, 173 insertions(+), 182 deletions(-) create mode 100644 src/inst.rs create mode 100644 src/parse.rs delete mode 100644 xclip diff --git a/src/inst.rs b/src/inst.rs new file mode 100644 index 0000000..803823f --- /dev/null +++ b/src/inst.rs @@ -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>), +} + +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, ")") + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 69b9aca..470a925 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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>), -} - -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>, extra::Err>>> -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::().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())); } diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..1ce35ea --- /dev/null +++ b/src/parse.rs @@ -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>, extra::Err>>> +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::().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> { + 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); + } + } +} diff --git a/xclip b/xclip deleted file mode 100644 index 7640a43..0000000 --- a/xclip +++ /dev/null @@ -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 -───╯