use super::token::CsName;
use crate::command;
use crate::command::BuiltIn;
use crate::command::Command;
use crate::error;
use crate::texmacro;
use crate::token;
use crate::token::lexer;
use crate::token::trace;
use crate::token::CsNameInterner;
use crate::token::Token;
use crate::token::Value;
use crate::variable;
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::PathBuf;
use std::rc::Rc;
use texcraft_stdext::collections::groupingmap;
#[cfg(feature = "serde")]
mod serde;
mod streams;
pub use streams::*;
pub trait Handlers<S: TexlangState> {
fn character_handler(
token: token::Token,
input: &mut ExecutionInput<S>,
) -> Result<(), Box<error::Error>> {
_ = (token, input);
Ok(())
}
fn undefined_command_handler(
token: token::Token,
input: &mut ExecutionInput<S>,
) -> Result<(), Box<error::Error>> {
Err(error::UndefinedCommandError::new(input.vm(), token).into())
}
fn unexpanded_expansion_command(
token: token::Token,
input: &mut ExecutionInput<S>,
) -> Result<(), Box<error::Error>> {
_ = (token, input);
Ok(())
}
}
pub struct DefaultHandlers;
impl<S: TexlangState> Handlers<S> for DefaultHandlers {}
impl<S: TexlangState> VM<S> {
pub fn run<H: Handlers<S>>(&mut self) -> Result<(), Box<error::Error>> {
let input = ExecutionInput::new(self);
loop {
let token = match input.next()? {
None => break,
Some(token) => token,
};
match token.value() {
Value::ControlSequence(name) => {
match input.commands_map().get_command(&name) {
Some(Command::Execution(cmd, _)) => {
if let Err(err) = cmd(token, input) {
return Err(error::Error::new_propagated(
input.vm(),
error::PropagationContext::Execution,
token,
err,
));
}
}
Some(Command::Variable(cmd)) => {
let cmd = cmd.clone();
let scope = S::variable_assignment_scope_hook(input.state_mut());
cmd.set_value_using_input(token, input, scope)?;
}
Some(Command::Character(token_value)) => {
H::character_handler(
Token::new_from_value(*token_value, token.trace_key()),
input,
)?
}
Some(Command::Expansion(_, _)) | Some(Command::Macro(_)) => {
H::unexpanded_expansion_command(token, input)?
}
None => H::undefined_command_handler(token, input)?,
}
}
Value::Active(_) => {
todo!()
}
Value::BeginGroup(_) => {
input.begin_group();
}
Value::EndGroup(_) => {
input.end_group(token)?;
}
Value::MathShift(_)
| Value::AlignmentTab(_)
| Value::Parameter(_)
| Value::Superscript(_)
| Value::Subscript(_)
| Value::Space(_)
| Value::Letter(_)
| Value::Other(_) => H::character_handler(token, input)?,
};
}
Ok(())
}
}
#[derive(Debug)]
struct EndOfGroupError {
trace: trace::SourceCodeTrace,
}
impl error::TexError for EndOfGroupError {
fn kind(&self) -> error::Kind {
error::Kind::Token(&self.trace)
}
fn title(&self) -> String {
"there is no group to end".into()
}
}
pub struct VM<S> {
pub state: S,
pub commands_map: command::Map<S>,
pub file_system: Box<dyn FileSystem>,
pub terminal: Rc<RefCell<dyn std::io::Write>>,
pub log_file: Rc<RefCell<dyn std::io::Write>>,
pub working_directory: Option<std::path::PathBuf>,
internal: Internal<S>,
}
pub trait FileSystem {
fn read_to_string(&self, path: &std::path::Path) -> std::io::Result<String>;
fn write_bytes(&self, path: &std::path::Path, contents: &[u8]) -> std::io::Result<()>;
}
struct RealFileSystem;
impl FileSystem for RealFileSystem {
fn read_to_string(&self, path: &std::path::Path) -> std::io::Result<String> {
std::fs::read_to_string(path)
}
fn write_bytes(&self, path: &std::path::Path, contents: &[u8]) -> std::io::Result<()> {
std::fs::write(path, contents)
}
}
pub trait TexlangState: Sized {
fn cat_code(&self, c: char) -> token::CatCode {
token::CatCode::PLAIN_TEX_DEFAULTS
.get(c as usize)
.copied()
.unwrap_or_default()
}
fn post_macro_expansion_hook(
token: Token,
input: &ExpansionInput<Self>,
tex_macro: &texmacro::Macro,
arguments: &[&[Token]],
reversed_expansion: &[Token],
) {
_ = (token, input, tex_macro, arguments, reversed_expansion);
}
fn expansion_override_hook(
token: token::Token,
input: &mut ExpansionInput<Self>,
tag: Option<command::Tag>,
) -> Result<Option<Token>, Box<command::Error>> {
_ = (token, input, tag);
Ok(None)
}
fn pre_source_code_addition_hook(
token: Option<token::Token>,
num_existing_sources: usize,
) -> Result<(), Box<error::Error>> {
_ = token;
if num_existing_sources + 1 >= 100 {
}
Ok(())
}
fn variable_assignment_scope_hook(state: &mut Self) -> groupingmap::Scope {
_ = state;
groupingmap::Scope::Local
}
}
impl TexlangState for () {}
impl<S: Default> VM<S> {
pub fn new(initial_built_ins: HashMap<&str, BuiltIn<S>>) -> Box<VM<S>> {
let mut internal = Internal::new(Default::default());
let initial_built_ins = initial_built_ins
.into_iter()
.map(|(key, value)| (internal.cs_name_interner.get_or_intern(key), value))
.collect();
Box::new(VM {
state: Default::default(),
commands_map: command::Map::new(initial_built_ins),
internal,
file_system: Box::new(RealFileSystem {}),
terminal: Rc::new(RefCell::new(std::io::stderr())),
log_file: Rc::new(RefCell::new(std::io::sink())),
working_directory: match std::env::current_dir() {
Ok(path_buf) => Some(path_buf),
Err(err) => {
println!("failed to determine the working directory: {err}");
None
}
},
})
}
}
#[cfg(feature = "serde")]
impl<'de, S: ::serde::Deserialize<'de>> VM<S> {
pub fn deserialize<D: ::serde::Deserializer<'de>>(
deserializer: D,
initial_built_ins: HashMap<&str, BuiltIn<S>>,
) -> Box<Self> {
serde::deserialize(deserializer, initial_built_ins)
}
}
impl<S: TexlangState> VM<S> {
pub fn push_source<T1: Into<PathBuf>, T2: Into<String>>(
&mut self,
file_name: T1,
source_code: T2,
) -> Result<(), Box<error::Error>> {
self.internal
.push_source(None, file_name.into(), source_code.into())
}
}
impl<S> VM<S> {
pub fn clear_sources(&mut self) {
self.internal.clear_sources()
}
pub fn get_commands_as_map_slow(&self) -> HashMap<String, BuiltIn<S>> {
let map_1: HashMap<CsName, BuiltIn<S>> = self.commands_map.to_hash_map_slow();
let mut map = HashMap::new();
for (cs_name, cmd) in map_1 {
let cs_name_str = match self.internal.cs_name_interner.resolve(cs_name) {
None => continue,
Some(cs_name_str) => cs_name_str,
};
map.insert(cs_name_str.to_string(), cmd);
}
map
}
#[inline]
pub fn cs_name_interner(&self) -> &CsNameInterner {
&self.internal.cs_name_interner
}
fn begin_group(&mut self) {
self.commands_map.begin_group();
self.internal.groups.push(Default::default());
}
fn end_group(&mut self, token: token::Token) -> Result<(), Box<error::Error>> {
match self.commands_map.end_group() {
Ok(()) => (),
Err(_) => {
return Err(EndOfGroupError {
trace: self.trace(token),
}
.into())
}
}
let group = self.internal.groups.pop().unwrap();
group.restore(&mut self.state);
Ok(())
}
pub fn trace(&self, token: Token) -> trace::SourceCodeTrace {
self.internal
.tracer
.trace(token, &self.internal.cs_name_interner)
}
pub fn trace_end_of_input(&self) -> trace::SourceCodeTrace {
self.internal.tracer.trace_end_of_input()
}
}
#[cfg_attr(
feature = "serde",
derive(::serde::Serialize, ::serde::Deserialize),
serde(bound = "")
)]
struct Internal<S> {
current_source: Source,
sources: Vec<Source>,
cs_name_interner: CsNameInterner,
tracer: trace::Tracer,
#[cfg_attr(feature = "serde", serde(skip))]
token_buffers: std::collections::BinaryHeap<TokenBuffer>,
#[cfg_attr(feature = "serde", serde(skip))]
groups: Vec<variable::SaveStackElement<S>>,
}
impl<S> Internal<S> {
fn new(cs_name_interner: CsNameInterner) -> Self {
Internal {
current_source: Default::default(),
sources: Default::default(),
cs_name_interner,
tracer: Default::default(),
token_buffers: Default::default(),
groups: Default::default(),
}
}
}
impl<S: TexlangState> Internal<S> {
fn push_source(
&mut self,
token: Option<Token>,
file_name: PathBuf,
source_code: String,
) -> Result<(), Box<error::Error>> {
let source_code: std::rc::Rc<str> = source_code.into();
S::pre_source_code_addition_hook(token, self.sources.len())?;
let trace_key_range = self
.tracer
.register_source_code(token, file_name, &source_code);
let mut new_source = Source::new(source_code, trace_key_range);
std::mem::swap(&mut new_source, &mut self.current_source);
self.sources.push(new_source);
Ok(())
}
}
impl<S> Internal<S> {
fn clear_sources(&mut self) {
self.current_source = Default::default();
self.sources.clear();
}
#[inline]
fn push_expansion(&mut self, expansion: &[Token]) {
self.current_source
.expansions
.extend(expansion.iter().rev());
}
#[inline]
fn expansions(&self) -> &Vec<Token> {
&self.current_source.expansions
}
#[inline]
fn expansions_mut(&mut self) -> &mut Vec<Token> {
&mut self.current_source.expansions
}
fn pop_source(&mut self) -> bool {
match self.sources.pop() {
None => false,
Some(source) => {
self.current_source = source;
true
}
}
}
}
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
struct Source {
expansions: Vec<Token>,
root: lexer::Lexer,
}
impl Source {
pub fn new(source_code: std::rc::Rc<str>, trace_key_range: trace::KeyRange) -> Source {
Source {
expansions: Vec::with_capacity(32),
root: lexer::Lexer::new(source_code, trace_key_range),
}
}
}
impl Default for Source {
fn default() -> Self {
Source::new("".into(), trace::KeyRange::empty())
}
}
#[derive(Default)]
struct TokenBuffer(Vec<Token>);
impl PartialEq for TokenBuffer {
fn eq(&self, other: &Self) -> bool {
self.0.capacity() == other.0.capacity()
}
}
impl Eq for TokenBuffer {}
impl PartialOrd for TokenBuffer {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.0.capacity().partial_cmp(&other.0.capacity())
}
}
impl Ord for TokenBuffer {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.capacity().cmp(&other.0.capacity())
}
}
pub trait HasComponent<C>: TexlangState {
fn component(&self) -> &C;
fn component_mut(&mut self) -> &mut C;
}
#[macro_export]
macro_rules! implement_has_component {
( $type: path, $component: path, $field: ident ) => {
implement_has_component![$type, ($component, $field),];
};
( $type: path, $(($component: path, $field: ident),)+) => {
$(
impl ::texlang::vm::HasComponent<$component> for $type {
#[inline]
fn component(&self) -> &$component {
&self.$field
}
#[inline]
fn component_mut(&mut self) -> &mut $component {
&mut self.$field
}
}
)*
};
}
pub use implement_has_component;