mirror of
https://github.com/Dummi26/mers.git
synced 2025-03-10 14:13:52 +01:00
added macro support, syntax is !(macro-type ...), i.e. !(mers my_file.mers) or !(mers { "value to compute at compile-time".regex("\\S+").assume_no_enum() })
This commit is contained in:
parent
0a2c67c2d5
commit
09d0d29138
@ -121,7 +121,10 @@ impl Lib {
|
||||
fn_signature.next();
|
||||
} else {
|
||||
loop {
|
||||
let mut t = match parse::parse_type_adv(&mut fn_signature, true) {
|
||||
let mut t = match parse::implementation::parse_type_adv(
|
||||
&mut fn_signature,
|
||||
true,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("{e}"),
|
||||
};
|
||||
@ -132,7 +135,8 @@ impl Lib {
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut fn_out = match parse::parse_type(&mut fn_signature) {
|
||||
let mut fn_out = match parse::implementation::parse_type(&mut fn_signature)
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("{e}"),
|
||||
};
|
||||
|
@ -1,16 +1,20 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn path_from_string(path: &str, script_directory: &PathBuf) -> Option<PathBuf> {
|
||||
pub fn path_from_string(path: &str, script_path: &PathBuf) -> Option<PathBuf> {
|
||||
let path = PathBuf::from(path);
|
||||
if path.is_absolute() {
|
||||
return Some(path);
|
||||
}
|
||||
if let Some(p) = script_directory
|
||||
if let Some(p) = script_path
|
||||
.canonicalize()
|
||||
.unwrap_or_else(|_| script_directory.clone())
|
||||
.unwrap_or_else(|_| script_path.clone())
|
||||
.parent()
|
||||
{
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("path: parent path: {p:?}");
|
||||
let p = p.join(&path);
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("path: joined: {p:?}");
|
||||
if p.exists() {
|
||||
return Some(p);
|
||||
}
|
||||
|
@ -108,12 +108,27 @@ impl File {
|
||||
}
|
||||
pub fn skip_whitespaces(&mut self) {
|
||||
loop {
|
||||
match self.chars.get(self.pos.current_char_index) {
|
||||
Some(ch) if ch.1.is_whitespace() => _ = self.next(),
|
||||
match self.peek() {
|
||||
Some(ch) if ch.is_whitespace() => _ = self.next(),
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn collect_to_whitespace(&mut self) -> String {
|
||||
let mut o = String::new();
|
||||
loop {
|
||||
if let Some(ch) = self.next() {
|
||||
if ch.is_whitespace() {
|
||||
self.set_pos(*self.get_ppos());
|
||||
break;
|
||||
}
|
||||
o.push(ch);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
o
|
||||
}
|
||||
pub fn path(&self) -> &PathBuf {
|
||||
&self.path
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ use std::{process::Command, sync::Arc};
|
||||
use crate::{
|
||||
libs,
|
||||
script::{
|
||||
code_macro::MacroError,
|
||||
code_parsed::*,
|
||||
code_runnable::RScript,
|
||||
global_info::GSInfo,
|
||||
@ -239,6 +240,7 @@ pub enum ParseErrors {
|
||||
CannotUseFixedIndexingWithThisType(VType),
|
||||
CannotWrapWithThisStatement(SStatementEnum),
|
||||
ErrorParsingFunctionArgs(Box<ParseError>),
|
||||
MacroError(MacroError),
|
||||
}
|
||||
impl ParseErrors {
|
||||
fn fmtgs(
|
||||
@ -285,20 +287,27 @@ impl ParseErrors {
|
||||
write!(f, "error parsing function args: ")?;
|
||||
parse_error.fmt_custom(f, file)
|
||||
}
|
||||
Self::MacroError(e) => write!(f, "error in macro: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_block(file: &mut File) -> Result<SBlock, ParseError> {
|
||||
use implementation::*;
|
||||
|
||||
pub mod implementation {
|
||||
|
||||
use super::*;
|
||||
|
||||
fn parse_block(file: &mut File) -> Result<SBlock, ParseError> {
|
||||
parse_block_advanced(file, None, true, false, false)
|
||||
}
|
||||
fn parse_block_advanced(
|
||||
}
|
||||
pub(crate) fn parse_block_advanced(
|
||||
file: &mut File,
|
||||
assume_single_statement: Option<bool>,
|
||||
treat_closed_block_bracket_as_closing_delimeter: bool,
|
||||
treat_eof_as_closing_delimeter: bool,
|
||||
treat_closed_normal_bracket_as_closing_delimeter: bool,
|
||||
) -> Result<SBlock, ParseError> {
|
||||
) -> Result<SBlock, ParseError> {
|
||||
file.skip_whitespaces();
|
||||
// if true, parse exactly one statement. unless assume_single_statement.is_some(), this depends on whether the code block starts with { or not.
|
||||
let single_statement = if let Some(v) = assume_single_statement {
|
||||
@ -374,15 +383,80 @@ fn parse_block_advanced(
|
||||
}
|
||||
}
|
||||
Ok(SBlock::new(statements))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_statement(file: &mut File) -> Result<SStatement, ParseError> {
|
||||
/// convenience function. If you require a string, use this. If the first character is a " it will parse
|
||||
/// until it finds a closing ". If the first character is anything else it will parse until it finds a character matching closing_char.
|
||||
/// Escape sequences (like \n) will only be interpreted as such if "" is used.
|
||||
pub(crate) fn parse_string_val<F>(file: &mut File, closing_char: F) -> String
|
||||
where
|
||||
F: Fn(char) -> bool,
|
||||
{
|
||||
match file.peek() {
|
||||
Some('"') => {
|
||||
file.next();
|
||||
parse_string(file).unwrap_or(String::new())
|
||||
}
|
||||
_ => {
|
||||
let mut buf = String::new();
|
||||
loop {
|
||||
if let Some(ch) = file.next() {
|
||||
if closing_char(ch) {
|
||||
file.set_pos(*file.get_ppos());
|
||||
break;
|
||||
}
|
||||
buf.push(ch);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
buf
|
||||
}
|
||||
}
|
||||
}
|
||||
/// assumes the starting " has already been consumed.
|
||||
pub(crate) fn parse_string(file: &mut File) -> Result<String, ParseError> {
|
||||
let mut buf = String::new();
|
||||
let err_start_of_statement = *file.get_pos();
|
||||
loop {
|
||||
match file.next() {
|
||||
Some('\\') => {
|
||||
if let Some(ch) = file.next() {
|
||||
buf.push(match ch {
|
||||
'\\' => '\\',
|
||||
'n' => '\n',
|
||||
't' => '\t',
|
||||
'"' => '"',
|
||||
ch => {
|
||||
eprintln!("Warn: Weird char escape \"\\{ch}\", will be replaced with \"{ch}\".");
|
||||
ch
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
Some('"') => break,
|
||||
Some(ch) => buf.push(ch),
|
||||
None => {
|
||||
return Err(ParseError {
|
||||
err: ParseErrors::FoundEofInString,
|
||||
location: err_start_of_statement,
|
||||
location_end: Some(*file.get_pos()),
|
||||
context: vec![],
|
||||
info: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
pub(crate) fn parse_statement(file: &mut File) -> Result<SStatement, ParseError> {
|
||||
parse_statement_adv(file, false)
|
||||
}
|
||||
fn parse_statement_adv(
|
||||
}
|
||||
pub(crate) fn parse_statement_adv(
|
||||
file: &mut File,
|
||||
is_part_of_chain_already: bool,
|
||||
) -> Result<SStatement, ParseError> {
|
||||
) -> Result<SStatement, ParseError> {
|
||||
file.skip_whitespaces();
|
||||
let err_start_of_statement = *file.get_pos();
|
||||
let out = match file.peek() {
|
||||
@ -415,37 +489,7 @@ fn parse_statement_adv(
|
||||
}
|
||||
Some('"') => {
|
||||
file.next();
|
||||
let mut buf = String::new();
|
||||
loop {
|
||||
match file.next() {
|
||||
Some('\\') => {
|
||||
if let Some(ch) = file.next() {
|
||||
buf.push(match ch {
|
||||
'\\' => '\\',
|
||||
'n' => '\n',
|
||||
't' => '\t',
|
||||
'"' => '"',
|
||||
ch => {
|
||||
eprintln!("Warn: Weird char escape \"\\{ch}\", will be replaced with \"{ch}\".");
|
||||
ch
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
Some('"') => break,
|
||||
Some(ch) => buf.push(ch),
|
||||
None => {
|
||||
return Err(ParseError {
|
||||
err: ParseErrors::FoundEofInString,
|
||||
location: err_start_of_statement,
|
||||
location_end: Some(*file.get_pos()),
|
||||
context: vec![],
|
||||
info: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(SStatementEnum::Value(VDataEnum::String(buf).to()).to())
|
||||
Some(SStatementEnum::Value(VDataEnum::String(parse_string(file)?).to()).to())
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
@ -643,7 +687,9 @@ fn parse_statement_adv(
|
||||
break SStatementEnum::Match(match_what, cases).to();
|
||||
}
|
||||
"true" => break SStatementEnum::Value(VDataEnum::Bool(true).to()).to(),
|
||||
"false" => break SStatementEnum::Value(VDataEnum::Bool(false).to()).to(),
|
||||
"false" => {
|
||||
break SStatementEnum::Value(VDataEnum::Bool(false).to()).to()
|
||||
}
|
||||
_ => {
|
||||
// int, float, var
|
||||
break {
|
||||
@ -672,7 +718,8 @@ fn parse_statement_adv(
|
||||
// SStatementEnum::Value(VDataEnum::Float(v).to()).to()
|
||||
} else {
|
||||
if start.starts_with('&') {
|
||||
SStatementEnum::Variable(start[1..].to_string(), true).to()
|
||||
SStatementEnum::Variable(start[1..].to_string(), true)
|
||||
.to()
|
||||
} else {
|
||||
SStatementEnum::Variable(start.to_string(), false).to()
|
||||
}
|
||||
@ -684,21 +731,48 @@ fn parse_statement_adv(
|
||||
Some('(') => {
|
||||
// parse_block_advanced: only treat ) as closing delimeter, don't use single-statement (missing {, so would be assumed otherwise)
|
||||
let name = start.trim();
|
||||
if name.is_empty() {
|
||||
match name {
|
||||
"" => {
|
||||
break SStatementEnum::FunctionDefinition(
|
||||
None,
|
||||
parse_function(file, Some(err_start_of_statement))?,
|
||||
)
|
||||
.to();
|
||||
} else {
|
||||
}
|
||||
"!" => {
|
||||
break SStatementEnum::Macro(
|
||||
match crate::script::code_macro::parse_macro(file) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return Err(ParseError {
|
||||
err: ParseErrors::MacroError(e),
|
||||
location: err_start_of_statement,
|
||||
location_end: Some(*file.get_pos()),
|
||||
context: vec![],
|
||||
info: None,
|
||||
});
|
||||
}
|
||||
},
|
||||
)
|
||||
.to()
|
||||
}
|
||||
_ => {
|
||||
break SStatementEnum::FunctionCall(
|
||||
name.to_string(),
|
||||
match parse_block_advanced(file, Some(false), false, false, true) {
|
||||
match parse_block_advanced(
|
||||
file,
|
||||
Some(false),
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
) {
|
||||
Ok(block) => block.statements,
|
||||
Err(e) => {
|
||||
// NOTE: Alternatively, just add an entry to the original error's context.
|
||||
return Err(ParseError {
|
||||
err: ParseErrors::ErrorParsingFunctionArgs(Box::new(e)),
|
||||
err: ParseErrors::ErrorParsingFunctionArgs(
|
||||
Box::new(e),
|
||||
),
|
||||
location: err_start_of_statement,
|
||||
location_end: Some(*file.get_pos()),
|
||||
context: vec![],
|
||||
@ -710,6 +784,7 @@ fn parse_statement_adv(
|
||||
.to();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(ch) => start.push(ch),
|
||||
None => {
|
||||
return Err(ParseError {
|
||||
@ -734,7 +809,8 @@ fn parse_statement_adv(
|
||||
if !is_part_of_chain_already {
|
||||
let mut chain_length = 0;
|
||||
let mut err_end_of_prev = err_end_of_original_statement;
|
||||
while let Some('.') = file.get_char(file.get_pos().current_char_index.saturating_sub(1))
|
||||
while let Some('.') =
|
||||
file.get_char(file.get_pos().current_char_index.saturating_sub(1))
|
||||
{
|
||||
let err_start_of_wrapper = *file.get_pos();
|
||||
let wrapper = parse_statement_adv(file, true)?;
|
||||
@ -810,13 +886,13 @@ fn parse_statement_adv(
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
/// Assumes the function name and opening bracket have already been parsed. File should continue like "name type name type ...) <statement>"
|
||||
fn parse_function(
|
||||
/// Assumes the function name and opening bracket have already been parsed. File should continue like "name type name type ...) <statement>"
|
||||
fn parse_function(
|
||||
file: &mut File,
|
||||
err_fn_start: Option<super::file::FilePosition>,
|
||||
) -> Result<SFunction, ParseError> {
|
||||
err_fn_start: Option<crate::parse::file::FilePosition>,
|
||||
) -> Result<SFunction, ParseError> {
|
||||
file.skip_whitespaces();
|
||||
// find the arguments to the function
|
||||
let mut args = Vec::new();
|
||||
@ -856,18 +932,18 @@ fn parse_function(
|
||||
}
|
||||
}
|
||||
Ok(SFunction::new(args, parse_block(file)?))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse_type(file: &mut File) -> Result<VType, ParseError> {
|
||||
pub(crate) fn parse_type(file: &mut File) -> Result<VType, ParseError> {
|
||||
match parse_type_adv(file, false) {
|
||||
Ok((v, _)) => Ok(v),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
pub(crate) fn parse_type_adv(
|
||||
}
|
||||
pub(crate) fn parse_type_adv(
|
||||
file: &mut File,
|
||||
in_fn_args: bool,
|
||||
) -> Result<(VType, bool), ParseError> {
|
||||
) -> Result<(VType, bool), ParseError> {
|
||||
let mut types = vec![];
|
||||
let mut closed_fn_args = false;
|
||||
loop {
|
||||
@ -893,17 +969,17 @@ pub(crate) fn parse_type_adv(
|
||||
}
|
||||
}
|
||||
Ok((VType { types }, closed_fn_args))
|
||||
}
|
||||
fn parse_single_type(file: &mut File) -> Result<VSingleType, ParseError> {
|
||||
}
|
||||
fn parse_single_type(file: &mut File) -> Result<VSingleType, ParseError> {
|
||||
match parse_single_type_adv(file, false) {
|
||||
Ok((v, _)) => Ok(v),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
fn parse_single_type_adv(
|
||||
}
|
||||
fn parse_single_type_adv(
|
||||
file: &mut File,
|
||||
in_fn_args: bool,
|
||||
) -> Result<(VSingleType, bool), ParseError> {
|
||||
) -> Result<(VSingleType, bool), ParseError> {
|
||||
file.skip_whitespaces();
|
||||
let mut closed_bracket_in_fn_args = false;
|
||||
let err_start_of_single_type = *file.get_pos();
|
||||
@ -1097,4 +1173,5 @@ fn parse_single_type_adv(
|
||||
},
|
||||
closed_bracket_in_fn_args,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::fmt::{self, Display, Formatter, Pointer};
|
||||
|
||||
use super::{global_info::GSInfo, val_data::VData, val_type::VType};
|
||||
use super::{code_macro::Macro, global_info::GSInfo, val_data::VData, val_type::VType};
|
||||
|
||||
pub enum SStatementEnum {
|
||||
Value(VData),
|
||||
@ -17,6 +17,7 @@ pub enum SStatementEnum {
|
||||
Match(String, Vec<(SStatement, SStatement)>),
|
||||
IndexFixed(SStatement, usize),
|
||||
EnumVariant(String, SStatement),
|
||||
Macro(Macro),
|
||||
}
|
||||
impl SStatementEnum {
|
||||
pub fn to(self) -> SStatement {
|
||||
@ -172,6 +173,9 @@ impl SStatementEnum {
|
||||
write!(f, "{variant}: ")?;
|
||||
inner.fmtgs(f, info)
|
||||
}
|
||||
Self::Macro(m) => {
|
||||
write!(f, "!({m})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
pub mod builtins;
|
||||
pub mod code_macro;
|
||||
pub mod code_parsed;
|
||||
pub mod code_runnable;
|
||||
pub mod global_info;
|
||||
|
@ -16,6 +16,7 @@ use crate::{
|
||||
|
||||
use super::{
|
||||
builtins::BuiltinFunction,
|
||||
code_macro::Macro,
|
||||
code_parsed::{SBlock, SFunction, SStatement, SStatementEnum},
|
||||
code_runnable::{RBlock, RFunction, RScript, RStatement, RStatementEnum},
|
||||
};
|
||||
@ -565,6 +566,9 @@ fn statement(
|
||||
v
|
||||
}
|
||||
}, statement(s, ginfo, linfo)?),
|
||||
SStatementEnum::Macro(m) => match m {
|
||||
Macro::StaticMers(val) => RStatementEnum::Value(val.clone()),
|
||||
},
|
||||
}
|
||||
.to();
|
||||
// if force_output_type is set, verify that the real output type actually fits in the forced one.
|
||||
|
@ -47,6 +47,9 @@ impl PartialEq for VDataEnum {
|
||||
}
|
||||
|
||||
impl VData {
|
||||
pub fn safe_to_share(&self) -> bool {
|
||||
self.data.safe_to_share()
|
||||
}
|
||||
pub fn out(&self) -> VType {
|
||||
VType {
|
||||
types: vec![self.out_single()],
|
||||
@ -82,6 +85,15 @@ impl VDataEnum {
|
||||
|
||||
// get()
|
||||
impl VDataEnum {
|
||||
pub fn safe_to_share(&self) -> bool {
|
||||
match self {
|
||||
Self::Bool(_) | Self::Int(_) | Self::Float(_) | Self::String(_) | Self::Function(_) => {
|
||||
true
|
||||
}
|
||||
Self::Tuple(v) | Self::List(_, v) => v.iter().all(|v| v.safe_to_share()),
|
||||
Self::Thread(..) | Self::Reference(..) | Self::EnumVariant(..) => false,
|
||||
}
|
||||
}
|
||||
pub fn noenum(self) -> VData {
|
||||
match self {
|
||||
Self::EnumVariant(_, v) => *v,
|
||||
|
Loading…
Reference in New Issue
Block a user