use std::{cmp::min, collections::BTreeMap};
use crate::{
diagnostic,
diagnostics::ErrorKind,
lexer::{
KEYSMASH_MAX_LEN,
token::{Token, TokenStream, TokenType},
},
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,
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,
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.iter_mut().next_back() {
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().is_none() {
break;
}
}
let end = self.ctx.source.len();
self.emit_op(Op::new(OpCode::Return, end..end));
}
fn parse_instruction(&mut self) -> Option<()> {
if let Some(tok) = self.next() {
let range = tok.range();
match tok.kind() {
TokenType::FlusteredW => self.emit_op(Op::new(OpCode::Swap, range)),
TokenType::FlusteredTilde => self.emit_op(Op::new(OpCode::Pop, range)),
TokenType::ColonThree { len } => {
let range = tok.range();
for _ in 0..len {
self.emit_op(Op::new(OpCode::Add, range));
}
}
TokenType::Blush { len } => {
let range = tok.range();
for _ in 0..len {
self.emit_op(Op::new(OpCode::Duplicate, 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 { 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) {
return self.parse_subroutine(range.start);
}
if self.matches(TokenType::Point) {
self.emit_op(Op::new(OpCode::Jump(ident), range))
}
self.emit_op(Op::new(OpCode::Push(val), range))
}
TokenType::Print { utf } => {
let range = tok.range();
if utf {
self.emit_op(Op::new(OpCode::PrintUtf, range))
} else {
self.emit_op(Op::new(OpCode::Print, range))
}
}
TokenType::Error | TokenType::Eof => {
self.next();
return None;
}
}
}
Some(())
}
fn parse_subroutine(&mut self, start: usize) -> Option<()> {
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));
return Some(());
}
self.parse_instruction();
}
}
}