use std::{cmp::min, collections::BTreeMap};
use crate::{
diagnostic,
diagnostics::ErrorKind,
lexer::{
KEYSMASH_MAX_LEN,
token::{
PrintKind, Token, TokenStream,
TokenType::{self},
},
},
source::SourceContext,
vm::{INTERNAL_ROOT_SUBROUTINE, Op, OpCode, Subroutine, SubroutineMap},
};
pub(crate) mod tests;
pub(crate) struct Parser<'p> {
ctx: &'p mut SourceContext,
tokens: TokenStream,
current: usize,
sub: String,
pub(crate) submap: SubroutineMap,
}
impl<'p> Parser<'p> {
pub(crate) fn new(tokens: TokenStream, ctx: &'p mut SourceContext) -> Self {
let mut submap = BTreeMap::new();
submap.insert(INTERNAL_ROOT_SUBROUTINE.to_string(), Subroutine::default());
Self {
ctx,
tokens,
current: 0,
sub: INTERNAL_ROOT_SUBROUTINE.to_string(),
submap,
}
}
fn peek(&self) -> Option<&Token> {
let peek = self.tokens.get(self.current);
peek
}
fn peekp(&self) -> Option<&Token> {
if self.current == 0 {
return None;
}
self.tokens.get(self.current.saturating_sub(1))
}
fn next(&mut self) -> Option<&Token> {
let len = self.tokens.len();
let current = self.current.saturating_add(1);
self.current = min(current, len);
self.peekp()
}
fn check(&self, expected: TokenType) -> bool {
self.peek().is_some_and(|tok| tok.kind() == expected)
}
fn matches(&mut self, expected: TokenType) -> bool {
if self.check(expected) {
self.next();
return true;
}
false
}
fn is_eof(&mut self) -> bool {
let peek = self.peek();
peek.is_none() || peek.is_some_and(|tok| tok.kind() == TokenType::Eof)
}
fn emit_op(&mut self, op: Op) {
if let Some(sub) = self.submap.get_mut(&self.sub) {
sub.emit_op(op);
}
}
pub(crate) fn parse(&mut self) {
if self.is_eof() {
self.ctx.report(diagnostic!(
ErrorKind::EmptySource {
praise_term: self.ctx.rand_praise_term().into(),
interp_title: self.ctx.rand_interp_title().into(),
},
labels = [(0..0, "")]
));
}
while !self.is_eof() {
if self.parse_instruction(true).is_none() {
break;
}
}
let end = self.ctx.source.len();
self.submap
.get_mut(INTERNAL_ROOT_SUBROUTINE)
.unwrap()
.emit_op(Op::new(OpCode::Return, end..end));
}
fn parse_instruction(&mut self, root: bool) -> Option<()> {
if let Some(tok) = self.next() {
let range = tok.range();
match tok.kind() {
TokenType::HappyX => {
self.emit_op(Op::new(OpCode::Eq, range));
if self.matches(TokenType::FlusteredDot) {
self.emit_op(Op::new(OpCode::Return, self.peekp()?.range()));
}
}
TokenType::HappyO => {
self.emit_op(Op::new(OpCode::Greater, range));
if self.matches(TokenType::FlusteredDot) {
self.emit_op(Op::new(OpCode::Return, self.peekp()?.range()));
}
}
TokenType::HappyW => {
self.emit_op(Op::new(OpCode::Less, range));
if self.matches(TokenType::FlusteredDot) {
self.emit_op(Op::new(OpCode::Return, self.peekp()?.range()));
}
}
TokenType::FlusteredX => {
self.emit_op(Op::new(OpCode::Neq, range));
if self.matches(TokenType::FlusteredDot) {
self.emit_op(Op::new(OpCode::Return, self.peekp()?.range()));
}
}
TokenType::FlusteredO => {
self.emit_op(Op::new(OpCode::GreaterEq, range));
if self.matches(TokenType::FlusteredDot) {
self.emit_op(Op::new(OpCode::Return, self.peekp()?.range()));
}
}
TokenType::FlusteredW => {
self.emit_op(Op::new(OpCode::LessEq, range));
if self.matches(TokenType::FlusteredDot) {
self.emit_op(Op::new(OpCode::Return, self.peekp()?.range()));
}
}
TokenType::FlusteredTilde => self.emit_op(Op::new(OpCode::Input, range)),
TokenType::HeavyFlusteredAt => self.emit_op(Op::new(OpCode::Swap, range)),
TokenType::HeavyFlusteredO => self.emit_op(Op::new(OpCode::Rotate, range)),
TokenType::HeavyFlusteredZero => self.emit_op(Op::new(OpCode::Flip, range)),
TokenType::Uwu => self.emit_op(Op::new(OpCode::Pop, range)),
TokenType::Owo => self.emit_op(Op::new(OpCode::PopScratchPad, range)),
TokenType::InterpTitle { tilde } => {
self.emit_op(Op::new(OpCode::PrintStack(tilde), range))
}
TokenType::ColonThree { add, len } => {
let range = tok.range();
for _ in 0..len {
let code = if add { OpCode::Add } else { OpCode::Sub };
self.emit_op(Op::new(code, range));
}
}
TokenType::Blush { double, len } => {
let range = tok.range();
for _ in 0..len {
self.emit_op(Op::new(OpCode::Duplicate(double), range));
}
}
TokenType::FlusteredDot => self.emit_op(Op::new(OpCode::Return, range)),
TokenType::Sub => {
let range = tok.range();
self.ctx.report(diagnostic!(
ErrorKind::UnnamedSub {
petname: self.ctx.rand_petname().into(),
interp_title: self.ctx.rand_interp_title().into(),
},
labels = [(range, "")]
));
}
TokenType::Point => {
let range = tok.range();
self.ctx.report(diagnostic!(
ErrorKind::UnnamedJump {
petname: self.ctx.rand_petname().into(),
interp_title: self.ctx.rand_interp_title().into(),
praise_term: self.ctx.rand_praise_term().into(),
},
labels = [(range, "")]
));
}
TokenType::Keysmash {
tilde,
lowercase,
len,
} => {
let ident = tok.lexeme().to_owned();
let range = tok.range();
let val = if lowercase {
len - 1
} else {
len - 1 + KEYSMASH_MAX_LEN
};
if self.matches(TokenType::Sub) {
if !root {
self.ctx.report(diagnostic!(
ErrorKind::NestedSubroutine {
interp_title: self.ctx.rand_interp_title().into()
},
labels = [(range, "")]
));
return Some(());
}
return self.parse_subroutine(ident, range.start);
}
if self.matches(TokenType::Point) {
self.emit_op(Op::new(OpCode::Jump(ident), range));
return Some(());
}
let code = if tilde {
OpCode::PushScratchPad(val)
} else {
OpCode::Push(val)
};
self.emit_op(Op::new(code, range))
}
TokenType::Print { kind } => {
let range = tok.range();
match kind {
PrintKind::Normal => self.emit_op(Op::new(OpCode::Print, range)),
PrintKind::Utf => self.emit_op(Op::new(OpCode::PrintUtf, range)),
PrintKind::Ansi => self.emit_op(Op::new(OpCode::PrintAnsi, range)),
PrintKind::Literal => self.emit_op(Op::new(OpCode::PrintLiteral, range)),
}
}
TokenType::Error | TokenType::Eof => {
self.next();
return None;
}
}
}
Some(())
}
fn parse_subroutine(&mut self, ident: String, start: usize) -> Option<()> {
self.submap.insert(ident.clone(), Subroutine::default());
self.sub = ident;
loop {
let end = self.peekp()?.end();
if self.is_eof() {
self.ctx.report(diagnostic!(
ErrorKind::SubWithoutReturn {
interp_title: self.ctx.rand_interp_title().into()
},
labels = [(start..end, "")]
));
return None;
}
if self.matches(TokenType::FlusteredDot) {
self.emit_op(Op::new(OpCode::Return, start..end));
self.sub = INTERNAL_ROOT_SUBROUTINE.to_string();
return Some(());
}
self.parse_instruction(false);
}
}
}