use super::ProcessorState;
use crate::auth::{AuthState, AuthType};
#[cfg(feature = "debug")]
use crate::common::DiffOption;
#[cfg(feature = "signature")]
use crate::common::SignatureType;
use crate::common::{
CommentType, ErrorBehaviour, FlowControl, Hygiene, LocalMacro, MacroFragment, MacroType,
ProcessInput, ProcessType, RelayTarget, WriteOption,
};
#[cfg(feature = "debug")]
use crate::debugger::DebugSwitch;
#[cfg(feature = "debug")]
use crate::debugger::Debugger;
use crate::error::RadError;
use crate::extension::{ExtMacroBuilder, ExtMacroType};
#[cfg(feature = "hook")]
use crate::hookmap::{HookMap, HookType};
use crate::lexor::*;
use crate::logger::TrackType;
use crate::logger::{Logger, WarningType};
use crate::map::MacroMap;
use crate::package::StaticScript;
use crate::runtime_map::RuntimeMacro;
#[cfg(feature = "signature")]
use crate::sigmap::SignatureMap;
use crate::storage::{RadStorage, StorageOutput};
use crate::trim;
use crate::utils::Utils;
use crate::DefineParser;
use crate::{consts::*, RadResult};
use crate::{ArgParser, GreedyState};
#[cfg(feature = "cindex")]
use cindex::Indexer;
use lazy_static::lazy_static;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::{self, BufReader, Read, Write};
use std::path::{Path, PathBuf};
lazy_static! {
static ref MAC_NAME : Regex = Regex::new(r#"^[_a-zA-Z]\w*$"#).expect("Failed to create regex expression");
}
pub struct Processor<'processor> {
map: MacroMap,
define_parser: DefineParser,
pub(crate) write_option: WriteOption<'processor>,
cache_file: Option<File>,
logger: Logger<'processor>,
cache: String,
#[cfg(feature = "hook")]
pub(crate) hook_map: HookMap,
#[cfg(feature = "debug")]
debugger: Debugger,
checker: UnbalancedChecker,
pub(crate) state: ProcessorState,
pub(crate) storage: Option<Box<dyn RadStorage>>,
#[cfg(feature = "cindex")]
pub(crate) indexer: Indexer,
}
impl<'processor> Default for Processor<'processor> {
fn default() -> Self {
Self::new()
}
}
impl<'processor> Processor<'processor> {
pub fn new() -> Self {
Self::new_processor(true)
}
pub fn empty() -> Self {
Self::new_processor(false)
}
fn new_processor(use_default: bool) -> Self {
#[allow(unused_mut)] let mut state = ProcessorState::new();
let mut logger = Logger::new();
logger.set_write_option(Some(WriteOption::Terminal));
let map = if use_default {
MacroMap::new()
} else {
MacroMap::empty()
};
Self {
map,
cache: String::new(),
write_option: WriteOption::Terminal,
cache_file: None,
define_parser: DefineParser::new(),
logger,
state,
#[cfg(feature = "hook")]
hook_map: HookMap::new(),
#[cfg(feature = "debug")]
debugger: Debugger::new(),
checker: UnbalancedChecker::new(),
storage: None,
#[cfg(feature = "cindex")]
indexer: Indexer::new(),
}
}
pub fn write_to_file<P: AsRef<Path>>(mut self, target_file: P) -> RadResult<Self> {
let open_option = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.to_owned();
if let Ok(option) = WriteOption::file(target_file.as_ref(), open_option) {
self.write_option = option;
} else {
return Err(RadError::InvalidCommandOption(format!(
"Could not create file \"{}\"",
target_file.as_ref().display()
)));
}
Ok(self)
}
pub fn write_to_variable(mut self, value: &'processor mut String) -> Self {
self.write_option = WriteOption::Variable(value);
self
}
pub fn error_to_file<F: AsRef<Path>>(mut self, target_file: F) -> RadResult<Self> {
let file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.to_owned();
if let Ok(file) = WriteOption::file(target_file.as_ref(), file) {
self.logger.set_write_option(Some(file));
} else {
return Err(RadError::InvalidCommandOption(format!(
"Could not create file \"{}\"",
target_file.as_ref().display()
)));
}
Ok(self)
}
pub fn error_to_variable(mut self, value: &'processor mut String) -> Self {
self.logger
.set_write_option(Some(WriteOption::Variable(value)));
self
}
pub fn custom_comment_char(mut self, character: char) -> RadResult<Self> {
if UNALLOWED_CHARS.is_match(&character.to_string()) {
return Err(RadError::UnallowedChar(format!(
"\"{}\" is not allowed",
character
)));
} else if self.get_macro_char() == character {
return Err(RadError::UnallowedChar(format!(
"\"{}\" is already defined for macro character",
character
)));
}
self.state.comment_char.replace(character);
Ok(self)
}
pub fn custom_chars(mut self, macro_character: char, comment_char: char) -> RadResult<Self> {
if macro_character == comment_char {
return Err(RadError::UnallowedChar(
"Cannot set a same character for macro and comment".to_string(),
));
}
if UNALLOWED_CHARS.is_match(¯o_character.to_string())
|| UNALLOWED_CHARS.is_match(&comment_char.to_string())
{
return Err(RadError::UnallowedChar(format!(
"\"{}\" is not allowed",
macro_character
)));
} else {
self.state.macro_char.replace(macro_character);
self.state.comment_char.replace(comment_char);
}
Ok(self)
}
pub fn custom_macro_char(mut self, character: char) -> RadResult<Self> {
if UNALLOWED_CHARS.is_match(&character.to_string()) {
return Err(RadError::UnallowedChar(format!(
"\"{}\" is not allowed",
character
)));
} else if self.get_comment_char() == character {
return Err(RadError::UnallowedChar(format!(
"\"{}\" is already defined for comment character",
character
)));
}
self.state.macro_char.replace(character);
Ok(self)
}
pub fn unix_new_line(mut self, use_unix_new_line: bool) -> Self {
if use_unix_new_line {
self.state.newline = "\n".to_owned();
}
self
}
pub fn purge(mut self, purge: bool) -> Self {
if purge {
self.state.behaviour = ErrorBehaviour::Purge;
}
self
}
pub fn lenient(mut self, lenient: bool) -> Self {
if lenient {
self.state.behaviour = ErrorBehaviour::Lenient;
}
self
}
pub fn hygiene(mut self, hygiene: Hygiene) -> Self {
self.state.hygiene = hygiene;
self
}
pub fn pipe_truncate(mut self, truncate: bool) -> Self {
self.state.pipe_truncate = truncate;
self
}
pub fn set_comment_type(mut self, comment_type: CommentType) -> Self {
self.state.comment_type = comment_type;
self
}
pub fn silent(mut self, silent_type: WarningType) -> Self {
self.logger.suppress_warning(silent_type);
self
}
pub fn assert(mut self, assert: bool) -> Self {
if assert {
self.logger.set_assert();
self.state.behaviour = ErrorBehaviour::Purge; self.write_option = WriteOption::Discard;
}
self
}
#[cfg(feature = "debug")]
pub fn debug(mut self, debug: bool) -> Self {
self.debugger.debug = debug;
self
}
#[cfg(feature = "debug")]
pub fn log(mut self, log: bool) -> Self {
self.debugger.log = log;
self
}
#[cfg(feature = "debug")]
pub fn diff(mut self, diff: DiffOption) -> RadResult<Self> {
self.debugger.enable_diff(diff)?;
Ok(self)
}
#[cfg(feature = "debug")]
pub fn interactive(mut self, interactive: bool) -> Self {
if interactive {
self.debugger.set_interactive();
}
self
}
pub fn melt_files(mut self, paths: &[impl AsRef<Path>]) -> RadResult<Self> {
let mut rule_file = RuleFile::new(None);
for p in paths.iter() {
rule_file.melt(p.as_ref())?;
}
self.map.runtime.extend_map(rule_file.rules, Hygiene::None);
Ok(self)
}
pub fn allow(mut self, auth_types: &[AuthType]) -> Self {
for auth in auth_types {
self.state.auth_flags.set_state(auth, AuthState::Open)
}
self
}
pub fn allow_with_warning(mut self, auth_types: &[AuthType]) -> Self {
for auth in auth_types {
self.state.auth_flags.set_state(auth, AuthState::Warn)
}
self
}
pub fn discard(mut self, discard: bool) -> Self {
if discard {
self.write_option = WriteOption::Discard;
}
self
}
pub fn storage(mut self, storage: Box<dyn RadStorage>) -> Self {
self.storage.replace(storage);
self
}
pub fn melt_literal(&mut self, literal: &[u8]) -> RadResult<()> {
let mut rule_file = RuleFile::new(None);
rule_file.melt_literal(literal)?;
self.map.runtime.extend_map(rule_file.rules, Hygiene::None);
Ok(())
}
pub fn package_sources<T: AsRef<Path>>(
&self,
sources: &[T],
out_file: Option<T>,
) -> RadResult<()> {
let mut body = Vec::new();
for file in sources {
body.extend(std::fs::read(file)?);
}
let mut static_script = StaticScript::new(self, body)?;
let path = if let Some(file) = out_file {
file.as_ref().to_owned()
} else {
PathBuf::from("out.r4c")
};
static_script.package(Some(&path))?;
Ok(())
}
pub fn import_frozen_file(&mut self, path: &Path) -> RadResult<()> {
let mut rule_file = RuleFile::new(None);
rule_file.melt(path)?;
self.map.runtime.extend_map(rule_file.rules, Hygiene::None);
Ok(())
}
pub fn insert_queue(&mut self, item: &str) {
self.state.queued.push(item.to_owned());
}
pub fn set_hygiene(&mut self, hygiene: Hygiene) {
if !self.map.runtime.volatile.is_empty() {
self.map.clear_runtime_macros(true);
}
self.state.hygiene = hygiene;
}
pub fn set_dry_mode(&mut self) {
self.write_option = WriteOption::Discard;
self.state.process_type = ProcessType::Dry;
self.state.auth_flags.clear();
}
pub fn set_freeze_mode(&mut self) {
self.write_option = WriteOption::Discard;
self.state.process_type = ProcessType::Freeze;
self.state.auth_flags.clear();
}
pub fn clear_volatile(&mut self) {
if !self.map.runtime.volatile.is_empty() {
self.map.clear_runtime_macros(true);
}
}
pub(crate) fn toggle_hygiene(&mut self, toggle: bool) {
if toggle {
if !self.map.runtime.volatile.is_empty() {
self.map.clear_runtime_macros(true);
}
self.state.hygiene = Hygiene::Macro;
} else {
self.state.hygiene = Hygiene::None;
}
}
pub fn set_write_option(&mut self, write_option: WriteOption<'processor>) {
self.write_option = write_option;
}
pub fn reset_flow_control(&mut self) {
self.state.flow_control = FlowControl::None;
}
#[cfg(feature = "signature")]
pub(crate) fn get_signature_map(&self, sig_type: SignatureType) -> RadResult<SignatureMap> {
let signatures = match sig_type {
SignatureType::All => self.map.get_signatures(),
SignatureType::Default => self.map.get_default_signatures(),
SignatureType::Runtime => self.map.get_runtime_signatures(),
};
Ok(SignatureMap::new(signatures))
}
#[allow(dead_code)]
pub fn print_permission(&mut self) -> RadResult<()> {
if let Some(status) = self.state.auth_flags.get_status_string() {
let mut status_with_header = String::from("Permission granted");
status_with_header.push_str(&status);
self.log_warning_no_line(&status_with_header, WarningType::Security)?;
}
Ok(())
}
pub fn print_result(&mut self) -> RadResult<()> {
self.logger.print_result()?;
#[cfg(feature = "debug")]
self.debugger.yield_diff(&mut self.logger)?;
Ok(())
}
pub(crate) fn organize_and_clear_cache(&mut self) -> RadResult<Option<String>> {
if self.state.hygiene == Hygiene::Input {
self.map.clear_runtime_macros(true);
}
if self.state.input_stack.len() == 1 {
if !self.state.relay.is_empty() {
let relay = format!("{:?}", self.state.relay.last().unwrap());
self.log_warning(&format!("There is unterminated relay target : \"{}\" which might not be an intended behaviour.", relay), WarningType::Sanity)?;
}
match self.state.flow_control {
FlowControl::None => (),
FlowControl::Exit => self
.logger
.wlog_no_line("Process exited.", WarningType::Sanity)?,
FlowControl::Escape => self
.logger
.wlog_no_line("Process escaped.", WarningType::Sanity)?,
}
}
self.state.input_stack.clear();
self.logger.stop_last_tracker();
if self.cache.is_empty() {
Ok(None)
} else {
Ok(Some(std::mem::take(&mut self.cache)))
}
}
pub fn set_storage(&mut self, storage: Box<dyn RadStorage>) {
self.storage.replace(storage);
}
pub fn extract_storage(&mut self, serialize: bool) -> RadResult<Option<StorageOutput>> {
if let Some(storage) = self.storage.as_mut() {
match storage.extract(serialize) {
Err(err) => Err(RadError::StorageError(format!("Extract error : {}", err))),
Ok(value) => Ok(value),
}
} else {
Ok(None)
}
}
pub fn freeze_to_file(&mut self, path: impl AsRef<Path>) -> RadResult<()> {
RuleFile::new(Some(self.map.runtime.macros.clone())).freeze(path.as_ref())?;
Ok(())
}
pub fn serialize_rules(&self) -> RadResult<Vec<u8>> {
RuleFile::new(Some(self.map.runtime.macros.clone())).serialize()
}
pub fn add_ext_macro(&mut self, ext: ExtMacroBuilder) {
match ext.macro_type {
ExtMacroType::Function => self.map.function.new_ext_macro(ext),
ExtMacroType::Deterred => self.map.deterred.new_ext_macro(ext),
}
}
pub fn add_runtime_rules(&mut self, rules: &[(impl AsRef<str>, &str, &str)]) -> RadResult<()> {
if self.state.hygiene == Hygiene::Aseptic {
let err = RadError::UnallowedMacroExecution(format!(
"Cannot register macros : \"{:?}\" in aseptic mode",
rules.iter().map(|(s, _, _)| s.as_ref()).collect::<Vec<_>>()
));
if self.state.behaviour == ErrorBehaviour::Strict {
self.log_error(&err.to_string())?;
return Err(RadError::StrictPanic);
} else {
self.log_warning(&err.to_string(), WarningType::Security)?;
}
}
for (name, args, body) in rules {
let name = name.as_ref().trim();
if !MAC_NAME.is_match(name) {
let err = RadError::InvalidMacroName(format!(
"Name : \"{}\" is not a valid macro name",
name
));
return Err(err);
}
self.map.runtime.macros.insert(
name.to_owned(),
RuntimeMacro {
name: name.to_owned(),
args: args
.split_whitespace()
.map(|s| s.to_owned())
.collect::<Vec<String>>(),
body: body.to_string(),
desc: None,
is_static: false,
},
);
}
Ok(())
}
pub fn add_static_rules(
&mut self,
rules: &[(impl AsRef<str>, impl AsRef<str>)],
) -> RadResult<()> {
if self.state.hygiene == Hygiene::Aseptic {
let err = RadError::UnallowedMacroExecution(format!(
"Cannot register macros : \"{:?}\" in aseptic mode",
rules.iter().map(|(s, _)| s.as_ref()).collect::<Vec<_>>()
));
if self.state.behaviour == ErrorBehaviour::Strict {
self.log_error(&err.to_string())?;
return Err(err);
} else {
self.log_warning(&err.to_string(), WarningType::Sanity)?;
}
}
for (name, body) in rules {
let name = name.as_ref().trim();
if !MAC_NAME.is_match(name) {
let err = RadError::InvalidMacroName(format!(
"Name : \"{}\" is not a valid macro name",
name
));
return Err(err);
}
self.map.runtime.macros.insert(
name.to_owned(),
RuntimeMacro {
name: name.to_owned(),
args: vec![],
body: body.as_ref().to_owned(),
desc: None,
is_static: true,
},
);
}
Ok(())
}
#[cfg(feature = "hook")]
pub fn register_hook(
&mut self,
hook_type: HookType,
target_macro: &str,
invoke_macro: &str,
target_count: usize,
resetable: bool,
) -> RadResult<()> {
if target_macro.is_empty() {
let err = RadError::InvalidMacroName(format!(
"Cannot register hook for macro \"{}\"",
target_macro
));
return Err(err);
}
if invoke_macro.is_empty() {
let err = RadError::InvalidMacroName(format!(
"Cannot register hook which invokes a macro \"{}\"",
target_macro
));
return Err(err);
}
self.hook_map.add_hook(
hook_type,
target_macro,
invoke_macro,
target_count,
resetable,
)?;
Ok(())
}
#[cfg(feature = "hook")]
pub fn deregister_hook(&mut self, hook_type: HookType, target_macro: &str) -> RadResult<()> {
if target_macro.is_empty() {
let err = RadError::InvalidMacroName(format!(
"Cannot deregister hook for macro \"{}\"",
target_macro
));
return Err(err);
}
self.hook_map.del_hook(hook_type, target_macro)?;
Ok(())
}
pub fn execute_macro(
&mut self,
level: usize,
caller: &str,
macro_name: &str,
arguments: &str,
) -> RadResult<Option<String>> {
self.logger
.start_new_tracker(TrackType::Input("String".to_string()));
let mut frag = MacroFragment::new();
frag.name = macro_name.to_owned();
frag.args = arguments.to_owned();
let result = self.evaluate(level, caller, &mut frag);
self.organize_and_clear_cache()?;
result
}
pub fn process_string(&mut self, content: &str) -> RadResult<Option<String>> {
self.logger
.start_new_tracker(TrackType::Input("String".to_string()));
let mut reader = content.as_bytes();
self.process_buffer(&mut reader, None, false)?;
self.organize_and_clear_cache()
}
pub fn process_stdin(&mut self) -> RadResult<Option<String>> {
#[allow(unused_imports)]
use std::io::Read;
let stdin = io::stdin();
self.set_input_stdin()?;
#[cfg(feature = "debug")]
if self.is_debug() {
let mut input = String::new();
stdin.lock().read_to_string(&mut input)?;
self.process_buffer(&mut input.as_bytes(), None, false)?;
return self.organize_and_clear_cache();
}
let mut reader = stdin.lock();
self.process_buffer(&mut reader, None, false)?;
self.organize_and_clear_cache()
}
pub fn process_file(&mut self, path: impl AsRef<Path>) -> RadResult<Option<String>> {
if path.as_ref().is_dir() {
return Err(RadError::InvalidFile(format!(
"File \"{}\" is not a readable file",
path.as_ref().display()
)));
}
let backup = if self.state.sandbox {
Some(self.backup())
} else {
None
};
self.set_file(path.as_ref().to_str().unwrap())?;
let file_stream = File::open(path)?;
let mut reader = BufReader::new(file_stream);
self.process_buffer(&mut reader, backup, false)?;
self.organize_and_clear_cache()
}
pub fn process_static_script(&mut self, path: impl AsRef<Path>) -> RadResult<Option<String>> {
if path.as_ref().is_dir() {
return Err(RadError::InvalidFile(format!(
"File \"{}\" is not a readable file",
path.as_ref().display()
)));
}
let backup = if self.state.sandbox {
Some(self.backup())
} else {
None
};
self.set_file(path.as_ref().to_str().unwrap())?;
let file_stream = File::open(path)?;
let mut reader = BufReader::new(file_stream);
let mut source = vec![];
reader.read_to_end(&mut source)?;
let static_script = StaticScript::unpack(source)?;
let mut reader = BufReader::new(&static_script.body[..]);
self.melt_literal(&static_script.header[..])?;
self.process_buffer(&mut reader, backup, false)?;
self.organize_and_clear_cache()
}
pub(crate) fn process_file_as_chunk(
&mut self,
path: impl AsRef<Path>,
) -> RadResult<Option<String>> {
let backup = if self.state.sandbox {
Some(self.backup())
} else {
None
};
self.set_file(path.as_ref().to_str().unwrap())?;
let file_stream = File::open(path)?;
let mut reader = BufReader::new(file_stream);
self.process_buffer(&mut reader, backup, true)
}
fn process_buffer(
&mut self,
buffer: &mut impl std::io::BufRead,
backup: Option<SandboxBackup>,
use_container: bool,
) -> RadResult<Option<String>> {
let mut line_iter = Utils::full_lines(buffer).peekable();
let mut lexor = Lexor::new(
self.get_macro_char(),
self.get_comment_char(),
&self.state.comment_type,
);
let mut frag = MacroFragment::new();
let container = String::new();
let mut cont = if use_container { Some(container) } else { None };
#[cfg(feature = "debug")]
self.debugger.user_input_on_start(
&self.state.current_input.to_string(),
self.logger.get_last_line(),
)?;
loop {
#[cfg(feature = "debug")]
if let Some(line) = line_iter.peek() {
let line = line.as_ref().unwrap();
self.debugger.add_line_cache(line);
self.debugger
.user_input_on_line(&frag, self.logger.get_last_line())?;
}
let result = match self.process_line(&mut line_iter, &mut lexor, &mut frag) {
Ok(oo) => oo,
Err(err) => {
return Err(err);
}
};
match result {
ParseResult::Printable(remainder) => {
self.write_to(&remainder, &mut cont)?;
#[cfg(feature = "debug")]
self.debugger.clear_line_cache();
if !frag.whole_string.is_empty() {
frag = MacroFragment::new();
}
}
ParseResult::FoundMacro(remainder) => {
self.write_to(&remainder, &mut cont)?;
}
ParseResult::NoPrint => {}
ParseResult::Eoi => {
if use_container {
Utils::pop_newline(cont.as_mut().unwrap());
}
break;
}
}
#[cfg(feature = "debug")]
self.debugger.inc_line_number();
}
if let Some(backup) = backup {
self.recover(backup)?;
self.state.sandbox = false;
}
if use_container {
Ok(cont.filter(|t| !t.is_empty()))
} else {
Ok(None)
}
}
fn is_local_macro(&self, mut level: usize, name: &str) -> bool {
while level > 0 {
if self.map.local.contains_key(&Utils::local_name(level, name)) {
return true;
}
level -= 1;
}
false
}
#[cfg(feature = "debug")]
fn check_debug_macro(&mut self, frag: &mut MacroFragment, level: usize) -> RadResult<()> {
if !self.is_debug() {
return Ok(());
}
if level == 0 {
self.debugger
.user_input_on_macro(frag, self.logger.get_last_line())?;
} else {
self.debugger
.user_input_on_step(frag, self.logger.get_last_line())?;
}
if level == 0 {
self.debugger.clear_line_cache();
}
Ok(())
}
fn process_line(
&mut self,
lines: &mut impl std::iter::Iterator<Item = std::io::Result<String>>,
lexor: &mut Lexor,
frag: &mut MacroFragment,
) -> RadResult<ParseResult> {
if let Some(line) = lines.next() {
self.logger.inc_line_number();
let line = line?;
if self.state.deny_newline {
self.state.deny_newline = false;
if line == "\n" || line == "\r\n" {
return Ok(ParseResult::NoPrint);
}
}
match self.state.flow_control {
FlowControl::Escape => return Ok(ParseResult::Printable(line)),
FlowControl::Exit => {
return Ok(ParseResult::Eoi);
}
FlowControl::None => (),
}
#[cfg(feature = "debug")]
self.debugger.write_diff_original(&line)?;
let remainder = self.parse_line(lexor, frag, &line, 0, MAIN_CALLER)?;
self.map.clear_local();
self.state.error_cache.take();
if self.state.hygiene == Hygiene::Macro {
self.map.clear_runtime_macros(true);
}
if !remainder.is_empty() {
if !frag.is_empty() {
if self.state.consume_newline {
self.state.consume_newline = false;
}
Ok(ParseResult::FoundMacro(remainder))
} else {
Ok(ParseResult::Printable(remainder))
}
}
else {
Ok(ParseResult::NoPrint)
}
} else {
Ok(ParseResult::Eoi)
}
}
fn parse_chunk_body(&mut self, level: usize, caller: &str, chunk: &str) -> RadResult<String> {
let mut lexor = Lexor::new(
self.get_macro_char(),
self.get_comment_char(),
&self.state.comment_type,
);
let mut frag = MacroFragment::new();
let mut result = String::new();
self.logger
.start_new_tracker(TrackType::Body(caller.to_string()));
for line in Utils::full_lines(chunk.as_bytes()) {
let line = line?;
if self.state.deny_newline {
self.state.deny_newline = false;
if line == "\n" || line == "\r\n" {
continue;
}
}
let line_result = self.parse_line(&mut lexor, &mut frag, &line, level, caller)?;
self.logger.inc_line_number();
result.push_str(&line_result);
}
if !frag.is_empty() {
result.push_str(&frag.whole_string);
}
self.logger.stop_last_tracker();
Ok(result)
}
pub(crate) fn parse_chunk_args(
&mut self,
level: usize,
caller: &str,
chunk: &str,
) -> RadResult<String> {
let mut lexor = Lexor::new(
self.get_macro_char(),
self.get_comment_char(),
&self.state.comment_type,
);
lexor.set_inner();
let mut frag = MacroFragment::new();
let mut result = String::new();
self.logger
.start_new_tracker(TrackType::Argument(caller.to_owned()));
for line in Utils::full_lines(chunk.as_bytes()) {
let line = line?;
if self.state.deny_newline {
self.state.deny_newline = false;
if line == "\n" || line == "\r\n" {
continue;
}
}
let line_result = self.parse_line(&mut lexor, &mut frag, &line, level, caller)?;
result.push_str(&line_result);
self.logger.inc_line_number();
}
if !frag.whole_string.is_empty() {
result.push_str(&frag.whole_string);
}
self.logger.stop_last_tracker();
Ok(result)
}
fn parse_line(
&mut self,
lexor: &mut Lexor,
frag: &mut MacroFragment,
line: &str,
level: usize,
caller: &str,
) -> RadResult<String> {
let mut remainder = String::new();
if self.state.comment_type != CommentType::None
&& line.trim().starts_with(self.get_comment_char())
{
return Ok(String::new());
}
let mut ch_iter = line.chars().peekable();
while let Some(ch) = ch_iter.next() {
if self.state.escape_newline {
if ch == '\r' && ch_iter.peek().unwrap_or(&'0') == &'\n' {
continue;
} else if ch == '\n' {
self.state.escape_newline = false;
continue;
}
}
self.logger.inc_char_number();
let lex_result = lexor.lex(ch);
match lex_result {
LexResult::CommentExit => {
self.lex_branch_comment_exit(frag, &mut remainder);
return Ok(remainder);
}
LexResult::Ignore => frag.whole_string.push(ch),
LexResult::Literal(cursor) => {
self.lex_branch_literal(ch, frag, &mut remainder, cursor);
}
LexResult::StartFrag => {
self.lex_branch_start_frag(ch, frag, &mut remainder, lexor)?;
}
LexResult::RestartName => {
remainder.push_str(&frag.whole_string);
frag.clear();
frag.is_processed = true;
frag.whole_string.push(self.get_macro_char());
}
LexResult::EmptyName => {
self.lex_branch_empty_name(ch, frag, &mut remainder, lexor);
}
LexResult::AddToRemainder => {
self.lex_branch_add_to_remainder(ch, &mut remainder)?;
}
LexResult::AddToFrag(cursor) => {
self.lex_branch_add_to_frag(ch, frag, cursor)?;
}
LexResult::EndFrag => {
self.lex_branch_end_frag(ch, frag, &mut remainder, lexor, level, caller)?;
if self.state.lexor_escape_blanks {
lexor.consume_blank();
self.state.lexor_escape_blanks = false;
}
}
LexResult::ExitFrag => {
self.lex_branch_exit_frag(ch, frag, &mut remainder);
}
}
#[cfg(feature = "hook")]
if frag.is_empty() && level == 0 {
if let Some(mac_name) = self.hook_map.add_char_count(ch) {
let mut hook_frag = MacroFragment::new();
hook_frag.name = mac_name;
let mut hook_mainder = String::new();
let mut hook_lexor = Lexor::new(
self.get_macro_char(),
self.get_comment_char(),
&self.state.comment_type,
);
self.lex_branch_end_invoke(
&mut hook_lexor,
&mut hook_frag,
&mut hook_mainder,
level,
&frag.name,
)?;
if !hook_mainder.is_empty() {
remainder.push_str(&hook_mainder);
}
} }
if frag.is_processed {
self.logger.merge_track();
frag.is_processed = false;
}
}
if self.state.consume_newline && remainder.trim().is_empty() {
remainder.clear();
self.state.consume_newline = false;
} else if self.state.escape_newline {
if remainder.ends_with("\r\n") {
remainder = remainder.strip_suffix("\r\n").unwrap().to_string();
} else if remainder.ends_with('\n') {
remainder = remainder.strip_suffix('\n').unwrap().to_string();
}
self.state.escape_newline = false;
}
if self.state.consume_newline {
self.state.consume_newline = false;
}
Ok(remainder)
}
fn evaluate(
&mut self,
level: usize,
caller: &str,
frag: &mut MacroFragment,
) -> RadResult<Option<String>> {
let level = level + 1;
let mut skip_expansion = false;
if frag.pipe_input {
skip_expansion = true;
frag.args = self.state.get_pipe("-").unwrap_or_default();
}
let (name, mut raw_args) = (&frag.name, frag.args.clone());
if frag.trim_input {
raw_args = raw_args
.lines()
.map(|l| l.trim())
.fold(String::new(), |mut acc, l| {
acc.push_str(l);
acc.push_str(&self.state.newline);
acc
})
.strip_suffix(&self.state.newline)
.unwrap_or_default()
.to_string();
}
let args: String;
if skip_expansion
|| !self.map.is_deterred_macro(name)
|| self.state.process_type == ProcessType::Dry
{
args = self.parse_chunk_args(level, name, &raw_args)?;
#[cfg(feature = "debug")]
{
frag.processed_args = args.clone();
}
} else {
if self.state.process_type == ProcessType::Freeze {
self.log_warning(
"Deterred macro is not expanded in freeze mode.",
WarningType::Sanity,
)?;
frag.clear();
return Ok(None);
}
args = raw_args;
#[cfg(feature = "debug")]
{
frag.processed_args =
String::from("It is impossible to retrieve args from deterred macro")
}
}
let mut temp_level = level;
while temp_level > 0 {
if let Some(local) = self.map.local.get(&Utils::local_name(temp_level, name)) {
return Ok(Some(local.body.to_owned()));
}
temp_level -= 1;
}
if self.map.runtime.contains(name, self.state.hygiene) {
if caller == name {
self.log_warning(
&format!(
"Calling self, which is \"{}\", can possibly trigger infinite loop. This is also occured when argument's name is equal to macro's name.",
name,
),
WarningType::Sanity,
)?;
}
if let Some(RelayTarget::Macro(mac)) = &self.state.relay.last() {
if mac == name {
let err = RadError::UnallowedMacroExecution(format!(
"Cannot execute a macro \"{}\" when it is being relayed to",
mac
));
frag.whole_string.clear();
return Err(err);
}
}
let result = self.invoke_rule(level, name, &args)?;
return Ok(result);
}
else if self.map.is_deterred_macro(name) {
if let Some(func) = self.map.deterred.get_deterred_macro(name) {
if self.state.process_type == ProcessType::Dry {
return Ok(None);
}
let final_result = func(&args, level, self)?;
return Ok(final_result);
}
}
if self.map.function.contains(name) {
if self.state.process_type == ProcessType::Dry {
return Ok(None);
}
let func = self.map.function.get_func(name).unwrap();
let final_result = match func(&args, self) {
Ok(e) => e,
Err(err) => {
return Err(err);
}
};
Ok(final_result)
}
else {
let err = RadError::InvalidMacroName(format!("No such macro name : \"{}\"", &name));
if self.state.process_type == ProcessType::Dry {
self.log_warning(&err.to_string(), WarningType::Sanity)?;
Ok(None)
} else {
Err(err)
}
}
}
fn invoke_rule(
&mut self,
level: usize,
name: &str,
arg_values: &str,
) -> RadResult<Option<String>> {
let rule = self
.map
.runtime
.get(name, self.state.hygiene)
.unwrap()
.clone();
if rule.is_static {
return Ok(Some(rule.body));
}
let arg_types = &rule.args;
let args =
if let Some(content) = ArgParser::new().args_with_len(arg_values, arg_types.len()) {
content
} else {
let err = RadError::InvalidArgument(format!(
"{}'s arguments are not sufficient. Given {}, but needs {}",
name,
ArgParser::new()
.args_to_vec(arg_values, ',', GreedyState::Never)
.len(),
arg_types.len()
));
if self.state.process_type == ProcessType::Dry {
self.log_warning(&err.to_string(), WarningType::Sanity)?;
return Ok(None);
} else {
return Err(err);
}
};
for (idx, arg_type) in arg_types.iter().enumerate() {
self.map.add_local_macro(level + 1, arg_type, &args[idx]);
}
let result = self.parse_chunk_body(level, name, &rule.body)?;
self.map.clear_lower_locals(level);
Ok(Some(result))
}
fn add_rule(&mut self, frag: &MacroFragment) -> RadResult<()> {
if let Some((name, args, mut body)) = self.define_parser.parse_define(&frag.args) {
if name.is_empty() {
let err = RadError::InvalidMacroName("Cannot define a empty macro".to_string());
return Err(err);
}
if self.state.behaviour == ErrorBehaviour::Strict
&& self
.map
.contains_macro(&name, MacroType::Any, self.state.hygiene)
{
let mac_name = if frag.args.contains(',') {
frag.args.split(',').next().unwrap()
} else {
frag.args.split('=').next().unwrap()
};
let err = RadError::UnallowedMacroExecution(format!(
"Can't override exsiting macro : \"{}\"",
mac_name
));
self.log_error(&err.to_string())?;
return Err(RadError::StrictPanic);
}
if frag.trim_input {
body = trim!(&body
.lines()
.map(|l| trim!(l))
.fold(String::new(), |mut acc, l| {
acc.push_str(&l);
acc.push_str(&self.state.newline);
acc
}))
.to_string();
}
self.map
.register_runtime(&name, &args, &body, self.state.hygiene)?;
if self.state.process_type == ProcessType::Dry {
let err = RadError::InvalidArgument(format!("Macro \"{}\" has invalid body", name));
let res = self
.process_string(&format!("${}({})", name, args.replace(' ', ",")))
.map_err(|_| &err);
if res.is_err() {
self.log_warning(&err.to_string(), WarningType::Sanity)?;
}
}
} else {
let name = if frag.args.contains(',') {
frag.args.split(',').next().unwrap()
} else {
frag.args.split('=').next().unwrap()
};
let err = RadError::InvalidMacroName(format!(
"Invalid macro definition format for a macro : \"{}\"",
name
));
return Err(err);
}
Ok(())
}
fn write_to(&mut self, content: &str, container: &mut Option<String>) -> RadResult<()> {
if content.is_empty() {
return Ok(());
}
if let Some(cont) = container.as_mut() {
cont.push_str(content);
return Ok(());
}
if let Some(cache) = &mut self.cache_file {
cache.write_all(content.as_bytes())?;
return Ok(());
}
#[cfg(feature = "debug")]
self.debugger.write_diff_processed(content)?;
match self
.state
.relay
.last_mut()
.unwrap_or(&mut RelayTarget::None)
{
RelayTarget::Macro(mac) => {
if !self
.map
.contains_macro(mac, MacroType::Runtime, self.state.hygiene)
{
let err = RadError::InvalidMacroName(format!(
"Cannot relay to non-exsitent macro \"{}\"",
mac
));
return Err(err);
}
self.map.append(mac, content, self.state.hygiene);
}
RelayTarget::File(target) => {
target.inner().write_all(content.as_bytes())?;
}
#[cfg(not(feature = "wasm"))]
RelayTarget::Temp => {
if let Some(file) = self.get_temp_file() {
file.write_all(content.as_bytes())?;
}
}
RelayTarget::None => {
match &mut self.write_option {
WriteOption::File(f) => f.inner().write_all(content.as_bytes())?,
WriteOption::Terminal => std::io::stdout().write_all(content.as_bytes())?,
WriteOption::Variable(var) => var.push_str(content),
WriteOption::Return => self.cache.push_str(content),
WriteOption::Discard => (), }
}
}
Ok(())
}
fn lex_branch_comment_exit(&mut self, frag: &mut MacroFragment, remainder: &mut String) {
remainder.push_str(&frag.whole_string);
remainder.push_str(&self.state.newline);
frag.clear();
frag.is_processed = true;
}
fn lex_branch_literal(
&mut self,
ch: char,
frag: &mut MacroFragment,
remainder: &mut String,
cursor: Cursor,
) {
match cursor {
Cursor::Name => {
frag.whole_string.push(ch);
remainder.push_str(&frag.whole_string);
frag.clear();
frag.is_processed = true;
}
Cursor::None => {
remainder.push(ch);
}
Cursor::Arg => {
frag.args.push(ch);
frag.whole_string.push(ch);
}
}
}
fn lex_branch_start_frag(
&mut self,
ch: char,
frag: &mut MacroFragment,
remainder: &mut String,
lexor: &mut Lexor,
) -> RadResult<()> {
#[cfg(feature = "debug")]
self.debugger
.user_input_before_macro(frag, self.logger.get_last_line())?;
frag.whole_string.push(ch);
if self.state.paused && frag.name != "pause" {
lexor.reset();
remainder.push_str(&frag.whole_string);
frag.clear();
frag.is_processed = true;
}
Ok(())
}
fn lex_branch_empty_name(
&mut self,
ch: char,
frag: &mut MacroFragment,
remainder: &mut String,
lexor: &mut Lexor,
) {
frag.whole_string.push(ch);
self.logger.append_track(String::from("empty name"));
if self.state.paused {
lexor.reset();
remainder.push_str(&frag.whole_string);
frag.clear();
frag.is_processed = true;
}
}
fn lex_branch_add_to_remainder(&mut self, ch: char, remainder: &mut String) -> RadResult<()> {
if !self.checker.check(ch) && !self.state.paused {
self.logger
.append_track(String::from("Unbalanced parenthesis"));
self.log_warning("Unbalanced parenthesis detected.", WarningType::Sanity)?;
self.logger.merge_track();
}
remainder.push(ch);
Ok(())
}
fn lex_branch_add_to_frag(
&mut self,
ch: char,
frag: &mut MacroFragment,
cursor: Cursor,
) -> RadResult<()> {
match cursor {
Cursor::Name => {
if frag.name.is_empty() {
self.logger.append_track(String::from("Macro start"));
}
match ch {
'|' => frag.pipe_output = true,
'*' => frag.yield_literal = true,
'!' => frag.negate_result = true,
'=' => frag.trim_input = true,
'^' => frag.trim_output = true,
'-' => frag.pipe_input = true,
_ => {
if frag.has_attribute() {
let err = RadError::InvalidMacroName(format!(
"Invalid macro attribute : \"{}\"",
ch
));
return Err(err);
}
frag.name.push(ch);
}
}
}
Cursor::Arg => frag.args.push(ch),
_ => unreachable!(),
}
frag.whole_string.push(ch);
Ok(())
}
fn lex_branch_end_frag(
&mut self,
ch: char,
frag: &mut MacroFragment,
remainder: &mut String,
lexor: &mut Lexor,
level: usize,
caller: &str,
) -> RadResult<()> {
frag.whole_string.push(ch);
if frag.name == DEFINE_KEYWORD {
if self.state.hygiene == Hygiene::Aseptic {
frag.clear();
frag.is_processed = true;
self.state.consume_newline = true;
let err = RadError::UnallowedMacroExecution(format!(
"Cannot register a macro : \"{}\" in aseptic mode",
frag.name
));
if self.state.behaviour == ErrorBehaviour::Strict {
self.log_error(&err.to_string())?;
return Err(RadError::StrictPanic);
} else {
self.log_warning(&err.to_string(), WarningType::Security)?;
}
} else {
if level != 0 && self.state.process_type == ProcessType::Freeze {
self.log_warning(
"Only first level define is allowed in freeze mode",
WarningType::Sanity,
)?;
frag.clear();
remainder.clear();
return Ok(());
}
self.lex_branch_end_frag_define(
frag,
remainder,
#[cfg(feature = "debug")]
level,
)?;
}
} else {
self.lex_branch_end_invoke(lexor, frag, remainder, level, caller)?;
}
Ok(())
}
fn lex_branch_end_frag_define(
&mut self,
frag: &mut MacroFragment,
remainder: &mut String,
#[cfg(feature = "debug")] level: usize,
) -> RadResult<()> {
if let Err(err) = self.add_rule(frag) {
self.log_error(&err.to_string())?;
match self.state.behaviour {
ErrorBehaviour::Interrupt => return Err(err),
ErrorBehaviour::Assert => return Err(RadError::AssertFail),
ErrorBehaviour::Strict => {
return Err(RadError::StrictPanic);
}
ErrorBehaviour::Purge => (),
ErrorBehaviour::Lenient => remainder.push_str(&frag.whole_string),
}
}
self.state.consume_newline = true;
#[cfg(feature = "debug")]
self.check_debug_macro(frag, level)?;
frag.clear();
frag.is_processed = true;
Ok(())
}
fn lex_branch_end_invoke(
&mut self,
lexor: &mut Lexor,
frag: &mut MacroFragment,
remainder: &mut String,
level: usize,
caller: &str,
) -> RadResult<()> {
if frag.name.is_empty() {
let err =
RadError::InvalidMacroName("Cannot invoke a macro with empty name".to_string());
self.log_error(&err.to_string())?;
match self.state.behaviour {
ErrorBehaviour::Assert => return Err(RadError::AssertFail),
ErrorBehaviour::Strict | ErrorBehaviour::Interrupt => {
return Err(RadError::StrictPanic);
} ErrorBehaviour::Lenient => remainder.push_str(&frag.whole_string),
ErrorBehaviour::Purge => (),
}
frag.clear();
frag.is_processed = true;
}
#[cfg(feature = "debug")]
{
self.debugger
.print_log(&frag.name, &frag.args, frag, &mut self.logger)?;
self.debugger.break_point(frag)?;
if frag.name.is_empty() {
frag.clear();
frag.is_processed = true;
self.state.consume_newline = true;
self.debugger.set_prompt("\"BR\"");
return Ok(());
}
}
let evaluation_result = self.evaluate(level, caller, frag);
match evaluation_result {
Err(error) => {
self.lex_branch_end_frag_eval_result_error(error, frag, remainder)?;
}
Ok(eval_variant) => {
self.lex_branch_end_frag_eval_result_ok(
eval_variant,
frag,
remainder,
lexor,
level,
)?;
}
}
frag.clear();
frag.is_processed = true;
let queued = std::mem::take(&mut self.state.queued); for item in queued {
let result = self.parse_chunk_args(0, MAIN_CALLER, &item)?;
remainder.push_str(&result);
}
Ok(())
}
fn lex_branch_end_frag_eval_result_error(
&mut self,
error: RadError,
frag: &MacroFragment,
remainder: &mut String,
) -> RadResult<()> {
if let RadError::UnsoundExecution(_) = error {
return Err(error);
}
if self.state.error_cache.is_none() {
self.log_error(&error.to_string())?;
self.state.error_cache.replace(error);
}
match self.state.behaviour {
ErrorBehaviour::Interrupt => return Err(RadError::Interrupt),
ErrorBehaviour::Assert => return Err(RadError::AssertFail),
ErrorBehaviour::Strict => {
return Err(RadError::StrictPanic);
}
ErrorBehaviour::Purge => (),
ErrorBehaviour::Lenient => remainder.push_str(&frag.whole_string),
}
Ok(())
}
fn lex_branch_end_frag_eval_result_ok(
&mut self,
content: Option<String>,
frag: &mut MacroFragment,
remainder: &mut String,
_lexor: &mut Lexor,
#[allow(unused_variables)] level: usize,
) -> RadResult<()> {
#[cfg(feature = "debug")]
if !self.is_local_macro(level + 1, &frag.name) {
self.check_debug_macro(frag, level)?;
}
if let Some(mut content) = content {
if frag.trim_output {
content = trim!(&content).to_string();
if content.is_empty() {
self.state.consume_newline = true;
}
}
if frag.negate_result {
match Utils::is_arg_true(trim!(&content).as_ref()) {
Ok(boolean) => content = (!boolean).to_string(),
Err(_) => {
if self.state.behaviour == ErrorBehaviour::Strict {
return Err(RadError::StorageError(format!(
"Tried to negate value, \"{}\" which is not a boolean",
content
)));
} else {
self.log_warning(
&format!(
"Tried to negate value, \"{}\" which is not a boolean",
content
),
WarningType::Sanity,
)?;
}
}
}
}
if frag.yield_literal {
content = format!("\\*{}*\\", content);
}
#[cfg(feature = "hook")]
if let Some(mac_name) = self.hook_map.add_macro_count(&frag.name) {
let mut hook_frag = MacroFragment::new();
hook_frag.name = mac_name;
let mut hook_mainder = String::new();
let mut hook_lexor = Lexor::new(
self.get_macro_char(),
self.get_comment_char(),
&self.state.comment_type,
);
self.lex_branch_end_invoke(
&mut hook_lexor,
&mut hook_frag,
&mut hook_mainder,
level,
&frag.name,
)?;
if !hook_mainder.is_empty() {
content.push_str(&hook_mainder);
}
}
if frag.pipe_output {
self.state.add_pipe(None, content);
self.state.consume_newline = true;
} else {
remainder.push_str(&content);
}
} else {
self.state.consume_newline = true;
}
Ok(())
}
fn lex_branch_exit_frag(&mut self, ch: char, frag: &mut MacroFragment, remainder: &mut String) {
frag.whole_string.push(ch);
remainder.push_str(&frag.whole_string);
frag.clear();
frag.is_processed = true;
}
pub(crate) fn get_logger_write_option(&self) -> Option<&WriteOption> {
self.logger.write_option.as_ref()
}
pub(crate) fn get_comment_char(&self) -> char {
comment_start(self.state.comment_char)
}
pub(crate) fn get_macro_char(&self) -> char {
macro_start(self.state.macro_char)
}
pub(crate) fn get_auth_state(&self, auth_type: &AuthType) -> AuthState {
*self.state.auth_flags.get_state(auth_type)
}
#[cfg(not(feature = "wasm"))]
pub(crate) fn set_temp_file(&mut self, path: &Path) -> RadResult<()> {
self.state.set_temp_target(path)
}
#[allow(dead_code)]
pub(crate) fn set_pipe(&mut self, value: &str) {
self.state
.pipe_map
.insert("-".to_owned(), value.to_string());
}
#[cfg(feature = "debug")]
pub(crate) fn set_debug(&mut self, debug: bool) {
self.debugger.debug = debug;
}
pub(crate) fn set_sandbox(&mut self, sandbox: bool) {
self.state.sandbox = sandbox;
}
#[cfg(not(feature = "wasm"))]
pub(crate) fn get_temp_path(&self) -> &Path {
self.state.temp_target.name()
}
#[cfg(not(feature = "wasm"))]
pub(crate) fn get_temp_file(&mut self) -> Option<&mut File> {
Some(self.state.temp_target.inner())
}
fn backup(&self) -> SandboxBackup {
SandboxBackup {
current_input: self.state.current_input.clone(),
local_macro_map: self.map.local.clone(),
}
}
fn recover(&mut self, backup: SandboxBackup) -> RadResult<()> {
self.logger.set_input(&backup.current_input);
self.state.current_input = backup.current_input;
self.map.local = backup.local_macro_map;
self.logger.stop_last_tracker();
Ok(())
}
pub(crate) fn track_assertion(&mut self, success: bool) -> RadResult<()> {
self.logger.alog(success)?;
Ok(())
}
pub(crate) fn log_message(&mut self, log: &str) -> RadResult<()> {
self.logger.log(log)?;
Ok(())
}
pub(crate) fn log_error(&mut self, log: &str) -> RadResult<()> {
self.logger.elog(log)?;
Ok(())
}
pub(crate) fn log_warning_no_line(
&mut self,
log: &str,
warning_type: WarningType,
) -> RadResult<()> {
self.logger.wlog_no_line(log, warning_type)?;
Ok(())
}
pub(crate) fn log_warning(&mut self, log: &str, warning_type: WarningType) -> RadResult<()> {
self.logger.wlog(log, warning_type)?;
Ok(())
}
fn set_file(&mut self, file: &str) -> RadResult<()> {
let path = Path::new(file);
if !path.exists() {
Err(RadError::InvalidCommandOption(format!(
"File, \"{}\" doesn't exist, therefore cannot be read by r4d.",
path.display()
)))
} else {
let path = PathBuf::from(file);
self.state.input_stack.insert(path.canonicalize()?);
let input = ProcessInput::File(path);
self.state.current_input = input.clone();
self.logger.set_input(&input);
Ok(())
}
}
fn set_input_stdin(&mut self) -> RadResult<()> {
self.state.current_input = ProcessInput::Stdin;
self.logger.set_input(&ProcessInput::Stdin);
Ok(())
}
#[cfg(feature = "debug")]
pub(crate) fn is_debug(&self) -> bool {
self.debugger.debug
}
#[cfg(feature = "debug")]
pub(crate) fn get_debug_switch(&self) -> &DebugSwitch {
&self.debugger.debug_switch
}
#[cfg(feature = "debug")]
pub(crate) fn set_prompt(&mut self, prompt: &str) {
self.debugger.set_prompt(prompt);
}
pub fn set_documentation(&mut self, macro_name: &str, content: &str) -> bool {
if let Some(mac) = self.map.runtime.get_mut(macro_name, Hygiene::None) {
mac.desc = Some(content.to_owned());
true
} else {
false
}
}
#[cfg(not(feature = "wasm"))]
pub(crate) fn get_current_dir(&self) -> RadResult<PathBuf> {
let path = match &self.state.current_input {
ProcessInput::Stdin => std::env::current_dir()?,
ProcessInput::File(path) => {
let path = match path.parent() {
Some(empty) if empty == Path::new("") => PathBuf::from("./"),
Some(other) => other.to_owned(),
None => std::env::current_dir()?,
};
path
}
};
Ok(path)
}
pub(crate) fn parse_and_strip(
&mut self,
ap: &mut ArgParser,
level: usize,
caller: &str,
src: &str,
) -> RadResult<String> {
Ok(ap.strip(&self.parse_chunk_args(level, caller, src)?))
}
pub(crate) fn get_local_macro_body(&self, macro_name: &str) -> RadResult<&str> {
let body = &self
.map
.local
.get(macro_name)
.ok_or_else(|| RadError::InvalidMacroName("No such macro".to_string()))?
.body;
Ok(body)
}
pub(crate) fn get_runtime_macro_body(&self, macro_name: &str) -> RadResult<&str> {
let body = &self
.map
.runtime
.get(macro_name, self.state.hygiene)
.ok_or_else(|| RadError::InvalidMacroName("No such macro".to_string()))?
.body;
Ok(body)
}
#[cfg(feature = "signature")]
pub(crate) fn get_macro_manual(&self, macro_name: &str) -> Option<String> {
self.map.get_signature(macro_name).map(|s| s.to_string())
}
pub fn try_get_or_insert_regex(&mut self, expression: &str) -> RadResult<&Regex> {
if self.state.regex_cache.contains(expression) {
Ok(self.state.regex_cache.get(expression).unwrap())
} else {
self.state.regex_cache.append(expression)
}
}
pub fn expand(&mut self, level: usize, src: &str, strip: bool) -> RadResult<String> {
let parsed = self.parse_chunk_args(level, "", src)?;
if strip {
Ok(ArgParser::new().strip(&parsed))
} else {
Ok(parsed)
}
}
pub fn print_error_no_line(&mut self, error: &str) -> RadResult<()> {
self.logger.elog_no_line(error)?;
Ok(())
}
pub fn print_error(&mut self, error: &str) -> RadResult<()> {
self.log_error(error)?;
Ok(())
}
pub fn split_arguments(
&self,
source: &str,
target_length: usize,
no_strip: bool,
) -> RadResult<Vec<String>> {
let mut ap = ArgParser::new();
ap.set_strip(!no_strip);
if let Some(args) = ap.args_with_len(source, target_length) {
Ok(args)
} else {
Err(RadError::InvalidArgument(
"Insufficient arguments.".to_string(),
))
}
}
pub fn check_auth(&mut self, auth_type: AuthType) -> RadResult<bool> {
let variant = match self.state.auth_flags.get_state(&auth_type) {
AuthState::Warn => {
self.log_warning(
&format!("{} was enabled with warning", auth_type),
WarningType::Security,
)?;
true
}
AuthState::Open => true,
AuthState::Restricted => false,
};
Ok(variant)
}
pub fn contains_macro(&self, macro_name: &str, macro_type: MacroType) -> bool {
self.map
.contains_macro(macro_name, macro_type, self.state.hygiene)
}
pub fn contains_local_macro(&self, mut level: usize, macro_name: &str) -> Option<String> {
while level > 0 {
let local_name = Utils::local_name(level, macro_name);
if self.map.contains_local_macro(&local_name) {
return Some(local_name);
}
level -= 1;
}
None
}
pub fn undefine_macro(&mut self, macro_name: &str, macro_type: MacroType) {
self.map
.undefine(macro_name, macro_type, self.state.hygiene);
}
pub fn rename_macro(&mut self, macro_name: &str, target_name: &str, macro_type: MacroType) {
self.map
.rename(macro_name, target_name, macro_type, self.state.hygiene);
}
pub fn append_macro(&mut self, macro_name: &str, target: &str) {
self.map.append(macro_name, target, self.state.hygiene);
}
pub fn append_local_macro(&mut self, macro_name: &str, target: &str) {
self.map.append_local(macro_name, target);
}
pub fn replace_macro(&mut self, macro_name: &str, target: &str) -> bool {
self.map.replace(macro_name, target, self.state.hygiene)
}
pub fn add_new_local_macro(&mut self, level: usize, macro_name: &str, body: &str) {
self.map.add_local_macro(level, macro_name, body);
}
pub fn remove_local_macro(&mut self, level: usize, macro_name: &str) {
self.map.remove_local_macro(level, macro_name);
}
pub fn is_true(&self, src: &str) -> RadResult<bool> {
Utils::is_arg_true(src)
}
}
#[derive(Debug)]
enum ParseResult {
FoundMacro(String),
Printable(String),
NoPrint,
Eoi,
}
struct SandboxBackup {
current_input: ProcessInput,
local_macro_map: HashMap<String, LocalMacro>,
}
pub(crate) struct UnbalancedChecker {
paren: usize,
}
impl UnbalancedChecker {
pub fn new() -> Self {
Self { paren: 0 }
}
pub fn check(&mut self, ch: char) -> bool {
match ch {
'(' => self.paren += 1,
')' => {
if self.paren > 0 {
self.paren -= 1;
} else {
return false;
}
}
_ => {
return true;
}
}
true
}
}
#[derive(Serialize, Deserialize)]
pub struct RuleFile {
pub rules: HashMap<String, RuntimeMacro>,
}
impl RuleFile {
pub fn new(rules: Option<HashMap<String, RuntimeMacro>>) -> Self {
if let Some(content) = rules {
Self { rules: content }
} else {
Self {
rules: HashMap::new(),
}
}
}
pub fn melt(&mut self, path: &Path) -> RadResult<()> {
Utils::is_real_path(path)?;
let result = bincode::deserialize::<Self>(&std::fs::read(path)?);
if let Err(err) = result {
Err(RadError::BincodeError(format!(
"Failed to melt the file : {} \n {}",
path.display(),
err
)))
} else {
self.rules.extend(result.unwrap().rules.into_iter());
Ok(())
}
}
pub fn melt_literal(&mut self, literal: &[u8]) -> RadResult<()> {
let result = bincode::deserialize::<Self>(literal);
if let Ok(rule_file) = result {
self.rules.extend(rule_file.rules.into_iter());
Ok(())
} else {
Err(RadError::BincodeError(
"Failed to melt the literal value".to_string(),
))
}
}
pub(crate) fn freeze(&self, path: &std::path::Path) -> RadResult<()> {
let result = bincode::serialize(self);
if result.is_err() {
Err(RadError::BincodeError(format!(
"Failed to freeze to a file : {}",
path.display()
)))
} else if std::fs::write(path, result.unwrap()).is_err() {
Err(RadError::InvalidArgument(format!(
"Failed to create a file : {}",
path.display()
)))
} else {
Ok(())
}
}
pub(crate) fn serialize(&self) -> RadResult<Vec<u8>> {
let result = bincode::serialize(self);
if result.is_err() {
return Err(RadError::BincodeError(
"Failed to serialize a rule".to_string(),
));
}
Ok(result.unwrap())
}
}