use anyhow::{anyhow, Result};
use float_next_after::NextAfter;
-use num::Bounded;
+use num::{Bounded, Zero};
use ordered_float::OrderedFloat;
-use std::{fmt::Display, iter::Peekable, str::Chars};
+use std::{
+ collections::{hash_map::Entry, HashMap},
+ fmt::Display,
+ iter::{repeat, Peekable},
+ str::Chars,
+};
use crate::endian::{Endian, ToBytes};
pub fn sack(input: &str, endian: Endian) -> Result<Vec<u8>> {
+ let mut symbol_table = HashMap::new();
+ let output = _sack(input, endian, &mut symbol_table)?;
+ let output = if !symbol_table.is_empty() {
+ for (k, v) in symbol_table.iter() {
+ if v.is_none() {
+ return Err(anyhow!("label {k} used but never defined"));
+ }
+ }
+ _sack(input, endian, &mut symbol_table)?
+ } else {
+ output
+ };
+ Ok(output)
+}
+
+fn _sack(
+ input: &str,
+ endian: Endian,
+ symbol_table: &mut HashMap<String, Option<u32>>,
+) -> Result<Vec<u8>> {
let mut lexer = Lexer::new(input, endian)?;
let mut output = Vec::new();
- while parse_data_item(&mut lexer, &mut output)? {}
- Ok(Vec::new())
+ while parse_data_item(&mut lexer, &mut output, symbol_table)? {}
+ Ok(output)
}
-fn parse_data_item(lexer: &mut Lexer, output: &mut Vec<u8>) -> Result<bool> {
+fn parse_data_item(
+ lexer: &mut Lexer,
+ output: &mut Vec<u8>,
+ symbol_table: &mut HashMap<String, Option<u32>>,
+) -> Result<bool> {
if lexer.token.is_none() {
return Ok(false);
};
+
+ let initial_len = output.len();
match lexer.take()? {
Token::Integer(integer) => output.extend_from_slice(&lexer.endian.to_bytes(integer)),
Token::Float(float) => output.extend_from_slice(&lexer.endian.to_bytes(float.0)),
Token::PcSysmis => {
output.extend_from_slice(&[0xf5, 0x1e, 0x26, 0x02, 0x8a, 0x8c, 0xed, 0xff])
}
- Token::I8 => collect_integers::<u8, 1>(lexer, "i8", output)?,
- Token::I16 => collect_integers::<u16, 2>(lexer, "i16", output)?,
- Token::I64 => collect_integers::<i64, 8>(lexer, "i64", output)?,
- _ => return Err(anyhow!("syntax error")),
+ Token::I8 => put_integers::<u8, 1>(lexer, "i8", output)?,
+ Token::I16 => put_integers::<u16, 2>(lexer, "i16", output)?,
+ Token::I64 => put_integers::<i64, 8>(lexer, "i64", output)?,
+ Token::String(string) => output.extend_from_slice(string.as_bytes()),
+ Token::S(size) => {
+ let Some(Token::String(ref string)) = lexer.token else {
+ return Err(anyhow!("string expected after 's{size}'"));
+ };
+ let len = string.len();
+ if len > size {
+ return Err(anyhow!(
+ "{len}-byte string is longer than pad length {size}"
+ ));
+ }
+ output.extend_from_slice(string.as_bytes());
+ output.extend(repeat(b' ').take(size - len));
+ lexer.get()?;
+ }
+ Token::LParen => {
+ while lexer.token != Some(Token::RParen) {
+ parse_data_item(lexer, output, symbol_table)?;
+ }
+ lexer.get()?;
+ }
+ Token::Count => put_counted_items::<u32, 4>(lexer, "COUNT", output, symbol_table)?,
+ Token::Count8 => put_counted_items::<u8, 1>(lexer, "COUNT8", output, symbol_table)?,
+ Token::Hex => {
+ let Some(Token::String(ref string)) = lexer.token else {
+ return Err(anyhow!("string expected after 'hex'"));
+ };
+ let mut i = string.chars();
+ loop {
+ let Some(c0) = i.next() else { return Ok(true) };
+ let Some(c1) = i.next() else {
+ return Err(anyhow!("hex string has odd number of characters"));
+ };
+ let (Some(digit0), Some(digit1)) = (c0.to_digit(16), c1.to_digit(16)) else {
+ return Err(anyhow!("invalid digit in hex string"));
+ };
+ let byte = digit0 * 16 + digit1;
+ output.push(byte as u8);
+ }
+ }
+ Token::Label(name) => {
+ let value = output.len() as u32;
+ match symbol_table.entry(name) {
+ Entry::Vacant(v) => {
+ v.insert(Some(value));
+ }
+ Entry::Occupied(o) => {
+ if let Some(v) = o.get() {
+ if *v != value {
+ return Err(anyhow!("syntax error"));
+ }
+ }
+ }
+ };
+ }
+ Token::At(name) => {
+ let mut value = symbol_table.entry(name).or_insert(None).unwrap_or(0);
+ lexer.get()?;
+ loop {
+ let plus = match lexer.token {
+ Some(Token::Plus) => true,
+ Some(Token::Minus) => false,
+ _ => break,
+ };
+ lexer.get()?;
+
+ let operand = match lexer.token {
+ Some(Token::At(ref name)) => if let Some(value) = symbol_table.get(name) {
+ *value
+ } else {
+ symbol_table.insert(name.clone(), None);
+ None
+ }
+ .unwrap_or(0),
+ Some(Token::Integer(integer)) => integer
+ .try_into()
+ .map_err(|msg| anyhow!("bad offset literal ({msg})"))?,
+ _ => return Err(anyhow!("expecting @label or integer literal")),
+ };
+ lexer.get()?;
+
+ value = if plus {
+ value.checked_add(operand)
+ } else {
+ value.checked_sub(operand)
+ }
+ .ok_or_else(|| anyhow!("overflow in offset arithmetic"))?;
+ }
+ output.extend_from_slice(&lexer.endian.to_bytes(value));
+ }
+ _ => (),
+ };
+ if lexer.token == Some(Token::Asterisk) {
+ lexer.get()?;
+ let Token::Integer(count) = lexer.take()? else {
+ return Err(anyhow!("positive integer expected after '*'"));
+ };
+ if count < 1 {
+ return Err(anyhow!("positive integer expected after '*'"));
+ };
+ let final_len = output.len();
+ for _ in 1..count {
+ output.extend_from_within(initial_len..final_len);
+ }
+ }
+ match lexer.token {
+ Some(Token::Semicolon) => {
+ lexer.get()?;
+ }
+ Some(Token::RParen) => (),
+ _ => return Err(anyhow!("';' expected")),
}
Ok(true)
}
-fn collect_integers<T, const N: usize>(
+fn put_counted_items<T, const N: usize>(
+ lexer: &mut Lexer,
+ name: &str,
+ output: &mut Vec<u8>,
+ symbol_table: &mut HashMap<String, Option<u32>>,
+) -> Result<()>
+where
+ T: Zero + TryFrom<usize>,
+ Endian: ToBytes<T, N>,
+{
+ let old_size = output.len();
+ output.extend_from_slice(&lexer.endian.to_bytes(T::zero()));
+ if lexer.token != Some(Token::LParen) {
+ return Err(anyhow!("'(' expected after '{name}'"));
+ }
+ lexer.get()?;
+ while lexer.token != Some(Token::RParen) {
+ parse_data_item(lexer, output, symbol_table)?;
+ }
+ lexer.get()?;
+ let delta = output.len() - old_size;
+ let Ok(delta): Result<T, _> = delta.try_into() else {
+ return Err(anyhow!("{delta} bytes is too much for '{name}'"));
+ };
+ let dest = &mut output[old_size..old_size + N];
+ dest.copy_from_slice(&lexer.endian.to_bytes(delta));
+ Ok(())
+}
+
+fn put_integers<T, const N: usize>(
lexer: &mut Lexer,
name: &str,
output: &mut Vec<u8>,
let Some(ref token) = self.token else {
return Ok(None);
};
- match condition(&token) {
+ match condition(token) {
Some(value) => {
self.token = self.next()?;
Ok(Some(value))
None => Ok(None),
}
}
- fn get(&'a mut self) -> Result<Option<&'a Token>> {
+ fn get(&mut self) -> Result<Option<&Token>> {
if self.token.is_none() {
Err(anyhow!("unexpected end of input"))
} else {