use std::str::{Chars, FromStr};
use anyhow::Result;
use num_bigint::BigInt;
use smol_str::SmolStr;
pub fn tokenize(mut text: &str) -> Result<OpcodeTokens, LexerError> {
text = text.trim();
let Some((name, args)) = text.split_once(' ') else {
return Ok(OpcodeTokens {
name: SmolStr::from(text),
args: Vec::new(),
});
};
let mut res = Vec::new();
let mut has_comma = false;
let mut lexer = Lexer::new(args);
while let Some(first_char) = lexer.bump() {
match first_char {
'c' => {
has_comma = false;
res.push(lexer.read_control_reg()?);
}
's' => {
has_comma = false;
res.push(lexer.read_stack_item()?)
}
'(' => {
has_comma = false;
match lexer.first() {
'x' => {
lexer.bump();
res.push(lexer.read_slice(true)?);
}
_ => res.push(lexer.read_cell()?),
}
match lexer.bump() {
Some(')') => {}
Some(_) => return Err(LexerError::UnclosedParen),
None => return Err(LexerError::UnexpectedEof),
}
}
'x' => {
has_comma = false;
res.push(lexer.read_slice(false)?);
}
c if c.is_ascii_whitespace() => {}
',' => {
if has_comma {
return Err(LexerError::ArgumentSkipped);
}
has_comma = true;
}
c => {
has_comma = false;
res.push(lexer.read_number(c)?);
}
}
}
Ok(OpcodeTokens {
name: SmolStr::new(name),
args: res,
})
}
struct Lexer<'a> {
input: &'a str,
chars: Chars<'a>,
}
impl<'a> Lexer<'a> {
fn new(input: &'a str) -> Self {
Self {
input,
chars: input.chars(),
}
}
fn first(&self) -> char {
self.chars.clone().next().unwrap_or(EOF_CHAR)
}
fn bump(&mut self) -> Option<char> {
let c = self.chars.next()?;
Some(c)
}
fn pos(&self) -> usize {
self.input.len() - self.chars.as_str().len()
}
fn read_control_reg(&mut self) -> Result<OpcodeArgToken, LexerError> {
match self.bump() {
Some(c) if c.is_ascii_digit() => Ok(OpcodeArgToken::Reg {
idx: c as u8 - b'0',
}),
Some(c) => Err(LexerError::UnexpectedToken(c)),
None => Err(LexerError::UnexpectedEof),
}
}
fn read_stack_item(&mut self) -> Result<OpcodeArgToken, LexerError> {
let from = self.pos();
let mut has_digits = false;
match self.bump() {
Some('-') => {}
Some('0'..='9') => has_digits = true,
Some(c) => return Err(LexerError::UnexpectedToken(c)),
None => return Err(LexerError::UnexpectedEof),
}
loop {
match self.first() {
'0'..='9' => {
self.bump();
has_digits = true;
}
_ if has_digits => {
let to = self.pos();
break Ok(OpcodeArgToken::Stack {
idx: self.input[from..to].parse::<i32>().unwrap(),
});
}
_ => match self.bump() {
Some(c) => break Err(LexerError::UnexpectedToken(c)),
None => break Err(LexerError::UnexpectedEof),
},
}
}
}
fn read_cell(&mut self) -> Result<OpcodeArgToken, LexerError> {
for _ in 0..64 {
match self.bump() {
Some('0'..='9' | 'a'..='f' | 'A'..='F') => {}
Some(c) => return Err(LexerError::UnexpectedToken(c)),
None => return Err(LexerError::UnexpectedEof),
}
}
Ok(OpcodeArgToken::Cell)
}
fn read_slice(&mut self, wrapped: bool) -> Result<OpcodeArgToken, LexerError> {
loop {
match self.first() {
'0'..='9' | 'a'..='f' | 'A'..='F' => {
self.bump();
}
'_' => {
self.bump();
break;
}
')' if wrapped => return Ok(OpcodeArgToken::Slice),
_ => break,
}
}
if !wrapped {
return Ok(OpcodeArgToken::Slice);
}
match self.bump() {
Some(',') => {}
Some(c) => return Err(LexerError::UnexpectedToken(c)),
None => return Err(LexerError::UnexpectedEof),
}
match self.bump() {
Some('0'..='9') => Ok(OpcodeArgToken::Slice),
Some(c) => Err(LexerError::UnexpectedToken(c)),
None => Err(LexerError::UnexpectedEof),
}
}
fn read_number(&mut self, first_digit: char) -> Result<OpcodeArgToken, LexerError> {
let from = self.pos() - 1;
let mut has_digits = false;
match first_digit {
'-' => {}
'0'..='9' => has_digits = true,
c => return Err(LexerError::UnexpectedToken(c)),
}
loop {
match self.first() {
'0'..='9' => {
self.bump();
has_digits = true;
}
_ if has_digits => {
let to = self.pos();
break Ok(OpcodeArgToken::Int(
BigInt::from_str(&self.input[from..to]).unwrap(),
));
}
_ => match self.bump() {
Some(c) => break Err(LexerError::UnexpectedToken(c)),
None => break Err(LexerError::UnexpectedEof),
},
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OpcodeTokens {
pub name: SmolStr,
pub args: Vec<OpcodeArgToken>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OpcodeArgToken {
Int(BigInt),
Stack { idx: i32 },
Reg { idx: u8 },
Cell,
Slice,
}
#[derive(thiserror::Error, Debug)]
pub enum LexerError {
#[error("unexpected opcode EOF")]
UnexpectedEof,
#[error("unexpected token: {0}")]
UnexpectedToken(char),
#[error("unclosed paren")]
UnclosedParen,
#[error("argument skipped")]
ArgumentSkipped,
}
const EOF_CHAR: char = '\0';
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tokenize_works() {
assert_eq!(tokenize("DUP").unwrap(), OpcodeTokens {
name: SmolStr::new("DUP"),
args: Vec::new(),
});
assert_eq!(tokenize("PUSHINT 123").unwrap(), OpcodeTokens {
name: SmolStr::new("PUSHINT"),
args: vec![OpcodeArgToken::Int(123i32.into())],
});
assert_eq!(tokenize("PUSHSLICECONST x").unwrap(), OpcodeTokens {
name: SmolStr::new("PUSHSLICECONST"),
args: vec![OpcodeArgToken::Slice],
});
assert_eq!(tokenize("PUSHSLICECONST x6").unwrap(), OpcodeTokens {
name: SmolStr::new("PUSHSLICECONST"),
args: vec![OpcodeArgToken::Slice],
});
assert_eq!(
tokenize("DICTPUSHCONST 19 (xC_,1)").unwrap(),
OpcodeTokens {
name: SmolStr::new("DICTPUSHCONST"),
args: vec![OpcodeArgToken::Int(19.into()), OpcodeArgToken::Slice],
}
);
assert_eq!(
tokenize(
"PUSHREFCONT (96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7)"
)
.unwrap(),
OpcodeTokens {
name: SmolStr::new("PUSHREFCONT"),
args: vec![OpcodeArgToken::Cell],
}
);
assert_eq!(
tokenize(
"IFREFELSEREF (96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7) \
(96a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7)"
)
.unwrap(),
OpcodeTokens {
name: SmolStr::new("IFREFELSEREF"),
args: vec![OpcodeArgToken::Cell, OpcodeArgToken::Cell],
}
);
}
}