use crate::instruction::Instruction;
use crate::operator::Operator;
use crate::token::Token;
use bumpalo::collections::Vec;
use bumpalo::Bump;
#[derive(Clone, Copy)]
enum State {
Empty,
If(usize),
Else(usize),
ElseIf(usize, usize),
Select,
SelectElse(usize),
SelectArm(usize, usize),
}
struct Parser<'s, 'b, I: Iterator<Item = Token<'s>>> {
bump: &'b Bump,
tokens: I,
state: State,
stack: Vec<'b, State>,
ret: Vec<'b, Instruction<'b>>,
}
impl<'s, 'b, I: Iterator<Item = Token<'s>>> Parser<'s, 'b, I> {
fn new(bump: &'b Bump, tokens: I) -> Self {
Self {
bump,
tokens,
state: State::Empty,
stack: Vec::with_capacity_in(20, bump),
ret: Vec::with_capacity_in(1000, bump),
}
}
#[inline(always)]
fn push(&mut self, instruction: Instruction<'b>) {
self.ret.push(instruction);
}
#[inline(always)]
fn backup_state(&mut self) {
self.stack.push(self.state);
}
#[inline(always)]
fn restore_state(&mut self) {
self.state = self.stack.pop().unwrap();
}
#[inline(always)]
fn set_state(&mut self, state: State) {
self.state = state;
}
#[inline(always)]
fn next_pos(&self) -> usize {
self.ret.len()
}
fn intern(&self, s: &str) -> &'b str {
self.bump.alloc_str(s)
}
fn move_next_open_brace(&mut self) {
loop {
match self.tokens.next().unwrap() {
Token::OpenBrace => {
break;
}
token => self.step(token),
}
}
}
fn read_select_arm(&mut self, prev_end: usize) {
self.backup_state();
match self.tokens.next().unwrap() {
token @ Token::IntLit(_) | token @ Token::StrLit(_) => {
self.push(Instruction::Duplicate);
self.step(token);
self.push(Instruction::Operator(Operator::Equal));
self.set_state(State::SelectArm(prev_end, self.ret.len()));
self.push(Instruction::Nop);
}
Token::Else => {
self.set_state(State::SelectElse(prev_end));
}
token => panic!("Unexpected token {:?}", token),
}
assert_eq!(self.tokens.next(), Some(Token::OpenBrace));
}
fn step(&mut self, token: Token<'s>) {
match token {
Token::Conditional => self.push(Instruction::Conditional),
Token::Duplicate => self.push(Instruction::Duplicate),
Token::Pop => self.push(Instruction::Pop),
Token::Exit => self.push(Instruction::Exit),
Token::IntLit(num) => self.push(Instruction::LoadInt(num)),
Token::StrLit(text) => self.push(Instruction::LoadStr(self.intern(text))),
Token::Variable(ident) => self.push(Instruction::LoadVar(self.intern(ident))),
Token::Builtin(ident) => self.push(Instruction::CallBuiltin(self.intern(ident))),
Token::Assign(ident) => self.push(Instruction::StoreVar(self.intern(ident))),
Token::Else => unreachable!(),
Token::Operator(op) => self.push(Instruction::Operator(op)),
Token::At => self.push(Instruction::NewLine),
Token::Sharp => self.push(Instruction::Wait),
Token::Select => {
self.backup_state();
self.set_state(State::Select);
self.move_next_open_brace();
self.read_select_arm(0);
}
Token::OpenBrace => {
let pos = self.next_pos();
match self.state {
State::Select => unreachable!(),
_ => {
self.backup_state();
self.push(Instruction::Nop);
self.set_state(State::If(pos));
}
};
}
Token::CloseBrace => {
let pos = self.next_pos();
match self.state {
State::Empty => panic!("Unexpected block close, {:?}", self.ret),
State::ElseIf(if_end, top) => {
self.ret[if_end] = Instruction::Goto(pos + 1);
self.set_state(State::If(top));
self.step(Token::CloseBrace);
}
State::Select => {
self.restore_state();
self.push(Instruction::Pop);
}
State::SelectArm(prev_end, top) => {
self.restore_state();
self.ret[top] = Instruction::GotoIfNot(pos + 1);
self.push(Instruction::Nop);
if prev_end != 0 {
self.ret[prev_end] = Instruction::Goto(pos);
}
self.read_select_arm(pos);
}
State::SelectElse(top) => {
self.restore_state();
if top != 0 {
self.ret[top] = Instruction::Goto(pos);
}
}
State::If(top) => {
match self.tokens.next() {
Some(Token::Else) => {
match self.tokens.next().unwrap() {
Token::OpenBrace => {
self.ret[top] = Instruction::GotoIfNot(pos + 1);
self.push(Instruction::Nop);
self.set_state(State::Else(pos));
}
token => {
self.ret[top] = Instruction::GotoIfNot(pos + 1);
self.push(Instruction::Nop);
self.step(token);
self.move_next_open_brace();
self.set_state(State::ElseIf(pos, self.ret.len()));
self.push(Instruction::Nop);
}
}
}
Some(token) => {
self.ret[top] = Instruction::GotoIfNot(pos);
self.restore_state();
self.step(token);
}
None => {
self.ret[top] = Instruction::GotoIfNot(pos);
self.restore_state();
self.push(Instruction::Nop);
}
}
}
State::Else(if_end) => {
self.restore_state();
self.ret[if_end] = Instruction::Goto(pos);
}
};
}
}
}
fn parse(mut self) -> Vec<'b, Instruction<'b>> {
while let Some(token) = self.tokens.next() {
self.step(token);
}
self.ret
}
}
pub fn parse<'b, 's, I: Iterator<Item = Token<'s>>>(
bump: &'b Bump,
tokens: I,
) -> Vec<'b, Instruction<'b>> {
Parser::new(bump, tokens).parse()
}
#[test]
fn parse_condition() {
use crate::lexer::lex;
use pretty_assertions::assert_eq;
let bump = Bump::new();
let instructions = parse(
&bump,
lex("
5 [$0]
$0 2 % '$0은 짝수' '$0은 홀수' [?]
"),
);
assert_eq!(
instructions,
&[
Instruction::LoadInt(5),
Instruction::StoreVar("0"),
Instruction::LoadVar("0"),
Instruction::LoadInt(2),
Instruction::Operator(Operator::Rem),
Instruction::LoadStr("$0은 짝수"),
Instruction::LoadStr("$0은 홀수"),
Instruction::Conditional,
]
);
}
#[test]
fn parse_assign() {
use crate::lexer::lex;
use pretty_assertions::assert_eq;
let bump = Bump::new();
let instructions = parse(
&bump,
lex("
1 2 + [$1]
"),
);
assert_eq!(
instructions,
&[
Instruction::LoadInt(1),
Instruction::LoadInt(2),
Instruction::Operator(Operator::Add),
Instruction::StoreVar("1"),
]
);
}
#[test]
fn parse_if_test() {
use crate::lexer::lex;
use pretty_assertions::assert_eq;
let bump = Bump::new();
let instructions = parse(
&bump,
lex("
1 2 < {
'1은 2보다 작다'@
}
'3 + 4 = ' 3 4 + @
"),
);
assert_eq!(
&instructions,
&[
Instruction::LoadInt(1),
Instruction::LoadInt(2),
Instruction::Operator(Operator::Less),
Instruction::GotoIfNot(6),
Instruction::LoadStr("1은 2보다 작다"),
Instruction::NewLine,
Instruction::LoadStr("3 + 4 = "),
Instruction::LoadInt(3),
Instruction::LoadInt(4),
Instruction::Operator(Operator::Add),
Instruction::NewLine,
]
);
}
#[test]
fn parse_if_else_test() {
use crate::lexer::lex;
use pretty_assertions::assert_eq;
let bump = Bump::new();
let instructions = parse(
&bump,
lex("
1 2 < {
'1은 2보다 작다'@
} 그외 2 2 == {
'2와 2는 같다'@
} 그외 1 2 > {
'1은 2보다 크다'@
} 그외 {
'1은 2와 같다'@
}
'foo'@
"),
);
assert_eq!(
&instructions,
&[
Instruction::LoadInt(1),
Instruction::LoadInt(2),
Instruction::Operator(Operator::Less),
Instruction::GotoIfNot(7),
Instruction::LoadStr("1은 2보다 작다"),
Instruction::NewLine,
Instruction::Goto(14),
Instruction::LoadInt(2),
Instruction::LoadInt(2),
Instruction::Operator(Operator::Equal),
Instruction::GotoIfNot(14),
Instruction::LoadStr("2와 2는 같다"),
Instruction::NewLine,
Instruction::Goto(21),
Instruction::LoadInt(1),
Instruction::LoadInt(2),
Instruction::Operator(Operator::Greater),
Instruction::GotoIfNot(21),
Instruction::LoadStr("1은 2보다 크다"),
Instruction::NewLine,
Instruction::Goto(23),
Instruction::LoadStr("1은 2와 같다"),
Instruction::NewLine,
Instruction::LoadStr("foo"),
Instruction::NewLine,
]
);
}
#[test]
fn parse_select_else() {
use crate::lexer::lex;
use pretty_assertions::assert_eq;
let bump = Bump::new();
let instructions = parse(
&bump,
lex("
선택 1 {
그외 {
''@
}
}
''@
"),
);
assert_eq!(
instructions,
&[
Instruction::LoadInt(1),
Instruction::LoadStr(""),
Instruction::NewLine,
Instruction::Pop,
Instruction::LoadStr(""),
Instruction::NewLine,
]
);
}
#[test]
fn parse_select() {
use crate::lexer::lex;
use pretty_assertions::assert_eq;
let bump = Bump::new();
let instructions = parse(
&bump,
lex("
선택 1 2 + {
3 {
'3'@
}
2 {
'2'@
}
1 {
'1'@
}
그외 {
'other'@
}
}
'foo'@
"),
);
assert_eq!(
&instructions,
&[
Instruction::LoadInt(1),
Instruction::LoadInt(2),
Instruction::Operator(Operator::Add),
Instruction::Duplicate,
Instruction::LoadInt(3),
Instruction::Operator(Operator::Equal),
Instruction::GotoIfNot(10),
Instruction::LoadStr("3"),
Instruction::NewLine,
Instruction::Goto(16),
Instruction::Duplicate,
Instruction::LoadInt(2),
Instruction::Operator(Operator::Equal),
Instruction::GotoIfNot(17),
Instruction::LoadStr("2"),
Instruction::NewLine,
Instruction::Goto(23),
Instruction::Duplicate,
Instruction::LoadInt(1),
Instruction::Operator(Operator::Equal),
Instruction::GotoIfNot(24),
Instruction::LoadStr("1"),
Instruction::NewLine,
Instruction::Goto(26),
Instruction::LoadStr("other"),
Instruction::NewLine,
Instruction::Pop,
Instruction::LoadStr("foo"),
Instruction::NewLine,
]
);
}