improving error messages

This commit is contained in:
Parker TenBroeck 2026-01-11 15:58:23 -05:00
parent 2e6330196d
commit 90d96f0738
10 changed files with 680 additions and 435 deletions

View file

@ -2,260 +2,376 @@ use std::collections::HashSet;
use super::*; use super::*;
use crate::loader::{ use crate::{
Context, DELTA_LOWER, GAMMA_UPPER, SIGMA_UPPER, Spanned, delta_lower, dual_struct_serde, epsilon, loader::{
ast::{self, Symbol as Sym}, Context, INITIAL_STATE, Spanned,
ast::{self, Symbol as Sym, TopLevel},
log::LogSink,
}, sigma_upper
}; };
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] dual_struct_serde! {
#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub struct TransitionFrom<'a> { pub struct TransitionFrom<'a> {
#[serde(borrow)]
pub state: State<'a>, pub state: State<'a>,
pub letter: Option<Letter<'a>>, pub letter: Option<Letter<'a>>,
}
} }
#[derive(Debug, PartialEq, Eq, Clone, Hash)] dual_struct_serde! {
#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct TransitionTo<'a> { pub struct TransitionTo<'a> {
#[serde(borrow)]
pub state: State<'a>, pub state: State<'a>,
pub transition: Span, pub transition: Span,
pub function: Span, pub function: Span,
}
} }
#[derive(Clone, Debug)] dual_struct_serde! { {#[serde_with::serde_as]}
#[allow(unused)] #[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", serde_with::serde_as)] pub struct Fa<'a> {
#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[serde(borrow)]
pub struct Fa<'a> {
pub initial_state: State<'a>, pub initial_state: State<'a>,
#[serde(borrow)]
pub states: HashMap<State<'a>, StateInfo>, pub states: HashMap<State<'a>, StateInfo>,
#[serde(borrow)]
pub alphabet: HashMap<Letter<'a>, LetterInfo>, pub alphabet: HashMap<Letter<'a>, LetterInfo>,
#[serde(borrow)]
pub final_states: HashMap<State<'a>, StateInfo>, pub final_states: HashMap<State<'a>, StateInfo>,
#[cfg(feature = "serde")] #[serde(borrow)]
#[serde_as(as = "serde_with::Seq<(_, _)>")] #[serde_as(as = "serde_with::Seq<(_, _)>")]
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>, pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
#[cfg(not(feature = "serde"))] }
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
} }
impl<'a> Fa<'a> { impl<'a> Fa<'a> {
pub fn parse( pub fn compile(
items: impl Iterator<Item = Spanned<ast::TopLevel<'a>>>, items: impl Iterator<Item = Spanned<ast::TopLevel<'a>>>,
ctx: &mut Context<'a>, ctx: &mut Context<'a>,
options: Options, options: Options,
) -> Option<Fa<'a>> { ) -> Option<Fa<'a>> {
FaCompiler::new(ctx, options).compile(items)
}
}
let mut initial_state = None; pub struct FaCompiler<'a, 'b> {
ctx: &'b mut Context<'a>,
options: Options,
let mut states = HashMap::new(); initial_state: Option<(State<'a>, Span)>,
let mut alphabet = HashMap::new();
let mut final_states = HashMap::new();
let mut transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>> = states: HashMap<State<'a>, StateInfo>,
HashMap::new(); states_def: Option<Span>,
alphabet: HashMap<Letter<'a>, LetterInfo>,
alphabet_def: Option<Span>,
final_states: HashMap<State<'a>, StateInfo>,
final_states_def: Option<Span>,
transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
}
impl<'a, 'b> FaCompiler<'a, 'b> {
pub fn new(ctx: &'b mut Context<'a>, options: Options) -> Self {
Self {
ctx,
options,
initial_state: Default::default(),
states: Default::default(),
states_def: Default::default(),
alphabet: Default::default(),
alphabet_def: Default::default(),
final_states: Default::default(),
final_states_def: Default::default(),
transitions: Default::default(),
}
}
pub fn compile(
mut self,
items: impl Iterator<Item = Spanned<ast::TopLevel<'a>>>,
) -> Option<Fa<'a>> {
for Spanned(element, span) in items { for Spanned(element, span) in items {
self.compile_top_level(element, span);
}
if self.alphabet_def.is_none() {
self.ctx
.emit_error_locless("alphabet never defined")
.emit_help_logless("add: E = {...}")
.emit_info_logless(concat!("E can be ", sigma_upper!(str)));
}
if self.states_def.is_none() {
self.ctx
.emit_error_locless("states never defined")
.emit_help_logless("add: Q = {...}");
}
if self.final_states_def.is_none() {
self.ctx
.emit_error_locless("final states never defined")
.emit_help_logless("add: F = {...}");
}
let initial_state = match self.initial_state {
Some(some) => some.0,
None => {
if self.states.contains_key(&State("q0")) {
self.ctx
.emit_warning_locless("initial state not defined, defaulting to 'q0'")
.emit_help_logless(format!("add: {INITIAL_STATE} = q0"));
} else {
self.ctx
.emit_error_locless("initial state not defined")
.emit_help_logless(format!("add: {INITIAL_STATE} = ..."));
}
State("q0")
}
};
if self.transitions.is_empty(){
self.ctx.emit_warning_locless("no transitions defined")
.emit_help_logless("consider defining one: d(state, letter|epsilon) = state | {state, state, ...}")
.emit_info_logless(concat!("d can be ", delta_lower!(str)))
.emit_info_logless(concat!("epsilon can be ", epsilon!(str)));
}
if self.ctx.contains_errors() {
return None;
}
Some(Fa {
initial_state,
states: self.states,
alphabet: self.alphabet,
final_states: self.final_states,
transitions: self.transitions,
})
}
fn compile_top_level(&mut self, element: TopLevel<'a>, span: Span) {
use Spanned as S; use Spanned as S;
use ast::TopLevel as TL; use ast::TopLevel as TL;
match element { match element {
TL::Item(S("Q", _), list) => { TL::Item(S("Q", _), list) => self.compile_states(list, span),
if !states.is_empty() { TL::Item(S(sigma_upper!(pat), _), list) => self.compile_alphabet(list, span),
ctx.emit_error("states already set", span); TL::Item(S("F", _), list) => self.compile_final_states(list, span),
} TL::Item(S(INITIAL_STATE, _), item) => self.compile_initial_state(item, span),
let Some(list) = list.expect_set(ctx) else {
continue;
};
for item in list {
let Some(ident) = item.expect_ident(ctx) else {
continue;
};
if states
.insert(State(ident), StateInfo { definition: item.1 })
.is_some()
{
ctx.emit_error("state redefined", item.1);
}
}
if list.is_empty() {
ctx.emit_error("states cannot be empty", span);
}
}
TL::Item(S("E" | SIGMA_UPPER | "sigma", _), list) => {
if !alphabet.is_empty() {
ctx.emit_error("alphabet already set", span);
}
let Some(list) = list.expect_set(ctx) else {
continue;
};
for item in list {
let Some(ident) = item.expect_ident(ctx) else {
continue;
};
if ident.chars().count() != 1 {
ctx.emit_error("letter cannot be longer than one char", item.1);
}
if alphabet
.insert(Letter(ident), LetterInfo { definition: item.1 })
.is_some()
{
ctx.emit_error("letter redefined", item.1);
}
}
if list.is_empty() {
ctx.emit_error("alphabet cannot be empty", span);
}
}
TL::Item(S("F", _), list) => {
if !final_states.is_empty() {
ctx.emit_error("final states already set", span);
}
let Some(list) = list.expect_set(ctx) else {
continue;
};
for item in list {
let Some(ident) = item.expect_ident(ctx) else {
continue;
};
if states.contains_key(&State(ident)) {
if final_states
.insert(State(ident), StateInfo { definition: item.1 })
.is_some()
{
ctx.emit_error("final state redefined", item.1);
}
} else {
ctx.emit_error("final state not defined in set of states", item.1);
}
}
}
TL::Item(S("I" | "q0", _), S(src, src_d)) => match src {
ast::Item::Symbol(Sym::Ident(ident)) => {
if initial_state.is_some() {
ctx.emit_error("initial state already set", span);
}
if states.contains_key(&State(ident)) {
initial_state = Some(State(ident))
} else {
ctx.emit_error("initial state symbol not defined as a state", src_d);
}
}
_ => ctx.emit_error("expected ident", src_d),
},
TL::Item(S(name, dest_s), _) => { TL::Item(S(name, dest_s), _) => {
ctx.emit_error(format!("unknown item {name:?}, expected 'Q' | 'E' | '{SIGMA_UPPER}' | 'sigma' | 'F' | 'T' | '{GAMMA_UPPER}' | 'gamma' | 'I' | 'q0' | 'S' | 'z0'"), dest_s); self.ctx.emit_error(format!("unknown item {name:?}, expected states, alphabet, final states, initial state"), dest_s);
} }
TL::TransitionFunc(S((S("d" | DELTA_LOWER | "delta", _), tuple), _), list) => { TL::TransitionFunc(S((S(delta_lower!(pat), _), args), _), list) => {
let list = list.set_weak(); self.compile_transition_function(args, list)
let Some((state, letter)) = tuple.as_ref().expect_fa_transition_function(ctx)
else {
continue;
};
if !states.contains_key(&State(state.0)) {
ctx.emit_error("transition state not defined as state", state.1);
continue;
};
let letter: Option<Letter<'_>> = match letter.0 {
Sym::Epsilon => {
if !options.epsilon_moves {
ctx.emit_error("epsilon moves not permitted", letter.1);
}
None
}
Sym::Ident(val) => {
if !alphabet.contains_key(&Letter(val)) {
ctx.emit_error(
"transition letter not defined in alphabet",
letter.1,
);
}
Some(Letter(val))
}
};
for item in list {
let Some(next_state) = item.expect_ident(ctx) else {
continue;
};
let next_state = Spanned(next_state, item.1);
if !states.contains_key(&State(next_state.0)) {
ctx.emit_error("transition state not defined as state", next_state.1);
continue;
};
let entry: &mut _ = transitions
.entry(TransitionFrom {
letter,
state: State(state.0),
})
.or_default();
if !entry.is_empty() && !options.non_deterministic {
ctx.emit_error("transition already defined for this starting point (non determinism not permitted)", item.1);
}
if !entry.insert(TransitionTo {
state: State(next_state.0),
function: tuple.1,
transition: item.1,
}) {
ctx.emit_warning("duplicate transition", item.1);
}
}
} }
TL::TransitionFunc(S((S(name, _), _), dest_s), _) => { TL::TransitionFunc(S((S(name, _), _), dest_s), _) => {
ctx.emit_error( self.ctx.emit_error(
format!( format!(
"unknown function {name:?}, expected 'd' | 'delta' | '{DELTA_LOWER}'" "unknown function {name:?}, expected transition function ( {} )",
delta_lower!(str)
), ),
dest_s, dest_s,
); );
} }
TL::ProductionRule(_, _) => { TL::ProductionRule(_, _) => {
ctx.emit_error("unexpected production rule", span); self.ctx.emit_error("unexpected production rule", span);
} }
TL::Table() => ctx.emit_error("unexpected table", span), TL::Table() => _ = self.ctx.emit_error("unexpected table", span),
} }
} }
if alphabet.is_empty() { fn compile_states(&mut self, list: Spanned<ast::Item<'a>>, top_level: Span) {
ctx.emit_error_locless("alphabet never defined"); if let Some(previous) = self.states_def {
self.ctx
.emit_error("states already set", top_level)
.emit_info("previously defined here", previous);
}
let Some(list) = list.expect_set(self.ctx) else {
return;
};
for item in list {
let Some(ident) = item.expect_ident(self.ctx) else {
continue;
};
if let Some(previous) = self
.states
.insert(State(ident), StateInfo { definition: item.1 })
{
self.ctx
.emit_error("state redefined", item.1)
.emit_info("previously defined here", previous.definition);
}
} }
if states.is_empty() { if list.is_empty() {
ctx.emit_error_locless("states never defined"); self.ctx.emit_error("states cannot be empty", top_level);
}
self.states_def = Some(top_level);
} }
let initial_state = match initial_state { fn compile_alphabet(&mut self, list: Spanned<ast::Item<'a>>, top_level: Span) {
Some(some) => some, if let Some(previous) = self.alphabet_def {
None => { self.ctx
if states.contains_key(&State("q0")) { .emit_error("alphabet already set", top_level)
ctx.emit_warning_locless("initial state not defined, defaulting to 'q0'"); .emit_info("previously defined here", previous);
}
let Some(list) = list.expect_set(self.ctx) else {
return;
};
for item in list {
let Some(ident) = item.expect_ident(self.ctx) else {
continue;
};
if ident.chars().count() != 1 {
self.ctx
.emit_error("letter cannot be longer than one char", item.1);
}
if let Some(previous) = self
.alphabet
.insert(Letter(ident), LetterInfo { definition: item.1 })
{
self.ctx
.emit_error("letter redefined", item.1)
.emit_help("previously defined here", previous.definition);
}
}
if list.is_empty() {
self.ctx.emit_error("alphabet cannot be empty", top_level);
}
self.alphabet_def = Some(top_level);
}
fn compile_final_states(&mut self, list: Spanned<ast::Item<'a>>, top_level: Span) {
if let Some(previous) = self.final_states_def {
self.ctx
.emit_error("final states already set", top_level)
.emit_help("previously defined here", previous);
}
let Some(list) = list.expect_set(self.ctx) else {
return;
};
for item in list {
let Some(ident) = item.expect_ident(self.ctx) else {
continue;
};
if self.states.contains_key(&State(ident)) {
if self
.final_states
.insert(State(ident), StateInfo { definition: item.1 })
.is_some()
{
self.ctx.emit_error("final state redefined", item.1);
}
} else { } else {
ctx.emit_error_locless("initial state not defined"); self.ctx
.emit_error("final state not defined in set of states", item.1);
} }
State("q0") }
self.final_states_def = Some(top_level);
}
fn compile_initial_state(
&mut self,
Spanned(src, src_d): Spanned<ast::Item<'a>>,
top_level: Span,
) {
match src {
ast::Item::Symbol(Sym::Ident(ident)) => {
if let Some((_, previous)) = self.initial_state {
self.ctx
.emit_error("initial state already set", top_level)
.emit_help("previously defined here", previous);
}
if self.states.contains_key(&State(ident)) {
self.initial_state = Some((State(ident), top_level))
} else {
self.ctx
.emit_error("initial state symbol not defined as a state", src_d);
}
}
_ => _ = self.ctx.emit_error("expected ident", src_d),
}
}
fn compile_transition_function(
&mut self,
args: Spanned<ast::Tuple<'a>>,
list: Spanned<ast::Item<'a>>,
) {
let list = list.set_weak();
let Some((state, letter)) = args.as_ref().expect_fa_transition_function(self.ctx) else {
return;
};
if !self.states.contains_key(&State(state.0)) {
self.ctx
.emit_error("transition state not defined as state", state.1);
return;
};
let letter: Option<Letter<'_>> = match letter.0 {
Sym::Epsilon(_) => {
if !self.options.epsilon_moves {
self.ctx.emit_error("epsilon moves not permitted", letter.1);
}
None
}
Sym::Ident(val) => {
if !self.alphabet.contains_key(&Letter(val)) {
self.ctx
.emit_error("transition letter not defined in alphabet", letter.1);
}
Some(Letter(val))
} }
}; };
if ctx.contains_errors() { for item in list {
return None; let Some(next_state) = item.expect_ident(self.ctx) else {
} continue;
};
let next_state = Spanned(next_state, item.1);
Some(Fa { if !self.states.contains_key(&State(next_state.0)) {
initial_state, self.ctx
states, .emit_error("transition state not defined as state", next_state.1);
alphabet, continue;
final_states, };
transitions,
let entry: &mut _ = self
.transitions
.entry(TransitionFrom {
letter,
state: State(state.0),
}) })
.or_default();
if let Some(entry) = entry.iter().next()
&& !self.options.non_deterministic
{
self.ctx.emit_error("transition already defined for this starting point (non determinism not permitted)", item.1)
.emit_info("previously defined here", entry.transition);
}
if let Some(previous) = entry.replace(TransitionTo {
state: State(next_state.0),
function: args.1,
transition: item.1,
}) {
self.ctx
.emit_warning("duplicate transition", item.1)
.emit_info("previously defined here", previous.transition);
}
}
} }
} }
@ -271,7 +387,12 @@ impl<'a> Spanned<&ast::Tuple<'a>> {
] => { ] => {
return Some((Spanned(state, *state_span), Spanned(*letter, *letter_span))); return Some((Spanned(state, *state_span), Spanned(*letter, *letter_span)));
} }
_ => ctx.emit_error("expected FA transition function (ident, ident|~)", self.1), _ => {
_ = ctx.emit_error(
"expected FA transition function (state, letter|epsilon)",
self.1,
)
}
} }
None None
} }

View file

@ -14,31 +14,31 @@ pub struct Options {
} }
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
pub struct State<'a>(pub &'a str); pub struct State<'a>(pub &'a str);
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
pub struct Symbol<'a>(pub &'a str); pub struct Symbol<'a>(pub &'a str);
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(transparent))]
pub struct Letter<'a>(pub &'a str); pub struct Letter<'a>(pub &'a str);
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StateInfo { pub struct StateInfo {
pub definition: Span, pub definition: Span,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SymbolInfo { pub struct SymbolInfo {
pub definition: Span, pub definition: Span,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LetterInfo { pub struct LetterInfo {
pub definition: Span, pub definition: Span,
} }

View file

@ -2,10 +2,9 @@ use std::collections::HashSet;
use super::*; use super::*;
use crate::loader::{ use crate::{delta_lower, gamma_upper, loader::{
Context, DELTA_LOWER, GAMMA_UPPER, SIGMA_UPPER, Spanned, Context, INITIAL_STACK, INITIAL_STATE, Spanned, ast::{self, Symbol as Sym}, log::LogSink
ast::{self, Symbol as Sym}, }, sigma_upper};
};
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize))]
@ -90,7 +89,7 @@ impl<'a> Pda<'a> {
ctx.emit_error("states cannot be empty", span); ctx.emit_error("states cannot be empty", span);
} }
} }
TL::Item(S("E" | SIGMA_UPPER | "sigma", _), list) => { TL::Item(S(sigma_upper!(pat), _), list) => {
if !alphabet.is_empty() { if !alphabet.is_empty() {
ctx.emit_error("alphabet already set", span); ctx.emit_error("alphabet already set", span);
} }
@ -142,7 +141,7 @@ impl<'a> Pda<'a> {
} }
final_states = Some(map); final_states = Some(map);
} }
TL::Item(S("T" | GAMMA_UPPER | "gamma", _), list) => { TL::Item(S(gamma_upper!(pat), _), list) => {
if !symbols.is_empty() { if !symbols.is_empty() {
ctx.emit_error("stack symbols already set", span); ctx.emit_error("stack symbols already set", span);
} }
@ -166,7 +165,7 @@ impl<'a> Pda<'a> {
ctx.emit_error("stack symbols cannot be empty", span); ctx.emit_error("stack symbols cannot be empty", span);
} }
} }
TL::Item(S("I" | "q0", _), S(src, src_d)) => match src { TL::Item(S(INITIAL_STATE, _), S(src, src_d)) => match src {
ast::Item::Symbol(Sym::Ident(ident)) => { ast::Item::Symbol(Sym::Ident(ident)) => {
if initial_state.is_some() { if initial_state.is_some() {
ctx.emit_error("initial state already set", span); ctx.emit_error("initial state already set", span);
@ -177,9 +176,9 @@ impl<'a> Pda<'a> {
ctx.emit_error("initial state symbol not defined as a state", src_d); ctx.emit_error("initial state symbol not defined as a state", src_d);
} }
} }
_ => ctx.emit_error("expected ident", src_d), _ => _ = ctx.emit_error("expected ident", src_d),
}, },
TL::Item(S("S" | "z0", _), S(src, src_d)) => match src { TL::Item(S(INITIAL_STACK, _), S(src, src_d)) => match src {
ast::Item::Symbol(Sym::Ident(ident)) => { ast::Item::Symbol(Sym::Ident(ident)) => {
if initial_stack.is_some() { if initial_stack.is_some() {
ctx.emit_error("initial stack already set", span); ctx.emit_error("initial stack already set", span);
@ -193,13 +192,13 @@ impl<'a> Pda<'a> {
); );
} }
} }
_ => ctx.emit_error("expected ident", src_d), _ => _ = ctx.emit_error("expected ident", src_d),
}, },
TL::Item(S(name, dest_s), _) => { TL::Item(S(name, dest_s), _) => {
ctx.emit_error(format!("unknown item {name:?}, expected 'Q' | 'E' | '{SIGMA_UPPER}' | 'sigma' | 'F' | 'T' | '{GAMMA_UPPER}' | 'gamma' | 'I' | 'q0' | 'S' | 'z0'"), dest_s); ctx.emit_error(format!("unknown item {name:?}, expected states, alphabet, symbols, final states, initial state, initial stack"), dest_s);
} }
TL::TransitionFunc(S((S("d" | DELTA_LOWER | "delta", _), tuple), _), list) => { TL::TransitionFunc(S((S(delta_lower!(pat), _), tuple), _), list) => {
let list = list.set_weak(); let list = list.set_weak();
let Some((state, letter, stack_symbol)) = let Some((state, letter, stack_symbol)) =
tuple.as_ref().expect_pda_transition_function(ctx) tuple.as_ref().expect_pda_transition_function(ctx)
@ -219,7 +218,7 @@ impl<'a> Pda<'a> {
}; };
let letter: Option<Letter<'_>> = match letter.0 { let letter: Option<Letter<'_>> = match letter.0 {
Sym::Epsilon => { Sym::Epsilon(_) => {
if !options.epsilon_moves { if !options.epsilon_moves {
ctx.emit_error("epsilon moves not permitted", letter.1); ctx.emit_error("epsilon moves not permitted", letter.1);
} }
@ -253,7 +252,7 @@ impl<'a> Pda<'a> {
.iter() .iter()
.rev() .rev()
.filter_map(|symbol| { .filter_map(|symbol| {
if matches!(symbol.0, ast::Item::Symbol(Sym::Epsilon)) { if matches!(symbol.0, ast::Item::Symbol(Sym::Epsilon(_))) {
return None; return None;
} }
let ident = symbol.expect_ident(ctx)?; let ident = symbol.expect_ident(ctx)?;
@ -290,7 +289,7 @@ impl<'a> Pda<'a> {
TL::TransitionFunc(S((S(name, _), _), dest_s), _) => { TL::TransitionFunc(S((S(name, _), _), dest_s), _) => {
ctx.emit_error( ctx.emit_error(
format!( format!(
"unknown function {name:?}, expected 'd' | 'delta' | '{DELTA_LOWER}'" "unknown function {name:?}, expected transition function ( {} )", delta_lower!(str)
), ),
dest_s, dest_s,
); );
@ -299,7 +298,7 @@ impl<'a> Pda<'a> {
TL::ProductionRule(_, _) => { TL::ProductionRule(_, _) => {
ctx.emit_error("unexpected production rule", span); ctx.emit_error("unexpected production rule", span);
} }
TL::Table() => ctx.emit_error("unexpected table", span), TL::Table() => _ = ctx.emit_error("unexpected table", span),
} }
} }
@ -318,14 +317,14 @@ impl<'a> Pda<'a> {
let initial_stack = match initial_stack { let initial_stack = match initial_stack {
Some(some) => some, Some(some) => some,
None => { None => {
if symbols.contains_key(&Symbol("z0")) { if symbols.contains_key(&Symbol("Z0")) {
ctx.emit_warning_locless( ctx.emit_warning_locless(
"initial stack symbol not defined, defaulting to 'z0'", "initial stack symbol not defined, defaulting to 'Z0'",
); );
} else { } else {
ctx.emit_error_locless("initial stack symbol not defined"); ctx.emit_error_locless("initial stack symbol not defined");
} }
Symbol("z0") Symbol("Z0")
} }
}; };
@ -374,8 +373,8 @@ impl<'a, 'b> Spanned<&'b ast::Tuple<'a>> {
Spanned(symbol, *symbol_span), Spanned(symbol, *symbol_span),
)); ));
} }
_ => ctx.emit_error( _ => _ = ctx.emit_error(
"expected PDA transition function (ident, ident|~, ident)", "expected PDA transition function (state, letter|epsilon, symbol)",
self.1, self.1,
), ),
} }
@ -392,7 +391,7 @@ impl<'a, 'b> Spanned<&'b ast::Tuple<'a>> {
] => { ] => {
return Some((Spanned(state, *state_span), list.list_weak())); return Some((Spanned(state, *state_span), list.list_weak()));
} }
_ => ctx.emit_error("expected PDA transition (ident, item|[item])", self.1), _ => _ = ctx.emit_error("expected PDA transition (state, symbol|[symbol])", self.1),
} }
None None
} }

View file

@ -2,10 +2,9 @@ use std::collections::HashSet;
use super::*; use super::*;
use crate::loader::{ use crate::{delta_lower, gamma_upper, loader::{
Context, DELTA_LOWER, GAMMA_UPPER, SIGMA_UPPER, Spanned, BLANK_SYMBOL, Context, Spanned, ast::{self, Symbol as Sym}, log::LogSink
ast::{self, Symbol as Sym}, }};
};
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize))]
@ -118,7 +117,7 @@ impl<'a> Tm<'a> {
} }
} }
} }
TL::Item(S("T" | GAMMA_UPPER | "gamma", _), list) => { TL::Item(S(gamma_upper!(pat), _), list) => {
if !symbols.is_empty() { if !symbols.is_empty() {
ctx.emit_error("tape symbols already set", span); ctx.emit_error("tape symbols already set", span);
} }
@ -142,7 +141,7 @@ impl<'a> Tm<'a> {
ctx.emit_error("tape symbols cannot be empty", span); ctx.emit_error("tape symbols cannot be empty", span);
} }
} }
TL::Item(S("I" | "q0", _), S(src, src_d)) => match src { TL::Item(S("q0", _), S(src, src_d)) => match src {
ast::Item::Symbol(Sym::Ident(ident)) => { ast::Item::Symbol(Sym::Ident(ident)) => {
if initial_state.is_some() { if initial_state.is_some() {
ctx.emit_error("initial state already set", span); ctx.emit_error("initial state already set", span);
@ -153,9 +152,9 @@ impl<'a> Tm<'a> {
ctx.emit_error("initial state symbol not defined as a state", src_d); ctx.emit_error("initial state symbol not defined as a state", src_d);
} }
} }
_ => ctx.emit_error("expected ident", src_d), _ => _ = ctx.emit_error("expected ident", src_d),
}, },
TL::Item(S("S" | "z0", _), S(src, src_d)) => match src { TL::Item(S(BLANK_SYMBOL, _), S(src, src_d)) => match src {
ast::Item::Symbol(Sym::Ident(ident)) => { ast::Item::Symbol(Sym::Ident(ident)) => {
if initial_tape.is_some() { if initial_tape.is_some() {
ctx.emit_error("initial tape symbol already set", span); ctx.emit_error("initial tape symbol already set", span);
@ -169,13 +168,13 @@ impl<'a> Tm<'a> {
); );
} }
} }
_ => ctx.emit_error("expected ident", src_d), _ => _ = ctx.emit_error("expected ident", src_d),
}, },
TL::Item(S(name, dest_s), _) => { TL::Item(S(name, dest_s), _) => {
ctx.emit_error(format!("unknown item {name:?}, expected 'Q' | 'E' | '{SIGMA_UPPER}' | 'sigma' | 'F' | 'T' | '{GAMMA_UPPER}' | 'gamma' | 'I' | 'q0' | 'S' | 'z0'"), dest_s); ctx.emit_error(format!("unknown item {name:?}, expected states, symbols, final states, initial state, blank symbol"), dest_s);
} }
TL::TransitionFunc(S((S("d" | DELTA_LOWER | "delta", _), tuple), _), list) => { TL::TransitionFunc(S((S(delta_lower!(pat), _), tuple), _), list) => {
let list = list.set_weak(); let list = list.set_weak();
let Some((from_state, from_tape)) = let Some((from_state, from_tape)) =
tuple.as_ref().expect_tm_transition_function(ctx) tuple.as_ref().expect_tm_transition_function(ctx)
@ -231,7 +230,7 @@ impl<'a> Tm<'a> {
TL::TransitionFunc(S((S(name, _), _), dest_s), _) => { TL::TransitionFunc(S((S(name, _), _), dest_s), _) => {
ctx.emit_error( ctx.emit_error(
format!( format!(
"unknown function {name:?}, expected 'd' | 'delta' | '{DELTA_LOWER}'" "unknown function {name:?}, expected transition function ( {} )", delta_lower!(str)
), ),
dest_s, dest_s,
); );
@ -240,7 +239,7 @@ impl<'a> Tm<'a> {
TL::ProductionRule(_, _) => { TL::ProductionRule(_, _) => {
ctx.emit_error("unexpected production rule", span); ctx.emit_error("unexpected production rule", span);
} }
TL::Table() => ctx.emit_error("unexpected table", span), TL::Table() => _ = ctx.emit_error("unexpected table", span),
} }
} }
@ -303,7 +302,7 @@ impl<'a> Spanned<&ast::Tuple<'a>> {
] => { ] => {
return Some((Spanned(state, *state_span), Spanned(*tape, *tape_span))); return Some((Spanned(state, *state_span), Spanned(*tape, *tape_span)));
} }
_ => ctx.emit_error("expected TM transition function (ident, ident)", self.1), _ => _ = ctx.emit_error("expected TM transition function (state, symbol)", self.1),
} }
None None
} }
@ -321,7 +320,7 @@ impl<'a> Spanned<&ast::Tuple<'a>> {
let direction = match direction { let direction = match direction {
ast::Symbol::Ident("left" | "L" | "<") => Direction::Left, ast::Symbol::Ident("left" | "L" | "<") => Direction::Left,
ast::Symbol::Ident("right" | "R" | ">") => Direction::Right, ast::Symbol::Ident("right" | "R" | ">") => Direction::Right,
ast::Symbol::Epsilon | ast::Symbol::Ident("~") => Direction::None, ast::Symbol::Epsilon(_) | ast::Symbol::Ident("~") => Direction::None,
ast::Symbol::Ident(ident) => { ast::Symbol::Ident(ident) => {
ctx.emit_error( ctx.emit_error(
format!("invalid direction specified '{ident}'"), format!("invalid direction specified '{ident}'"),
@ -336,8 +335,8 @@ impl<'a> Spanned<&ast::Tuple<'a>> {
Spanned(direction, *direction_span), Spanned(direction, *direction_span),
)); ));
} }
_ => ctx.emit_error( _ => _ = ctx.emit_error(
"expected TM transition function (ident, ident, ident)", "expected TM transition function (state, symbol, direction)",
self.1, self.1,
), ),
} }

View file

@ -1,2 +1,36 @@
pub mod automatan; pub mod automatan;
pub mod loader; pub mod loader;
#[macro_export]
macro_rules! dual_struct_serde {
($({$(#[$serde_specific:meta])*})?
$(#[$struct_meta:meta])*
$vis:vis struct $Name:ident $(<$($gen:tt),*>)?
{
$(
$(#[$field_meta:meta])*
$fvis:vis $fname:ident : $fty:ty
),* $(,)?
}
) => {
#[cfg(feature = "serde")]
$(#[$struct_meta])*
$( $(#[$serde_specific])* )?
#[derive(serde::Serialize, serde::Deserialize)]
$vis struct $Name $(<$($gen)*>)? {
$(
$(#[$field_meta])*
$fvis $fname: $fty
),*
}
#[cfg(not(feature = "serde"))]
$(#[$struct_meta])*
$vis struct $Name $(<$($gen)*>)? {
$(
$fvis $fname: $fty
),*
}
};
}

View file

@ -16,7 +16,7 @@ pub struct Tuple<'a>(pub Vec<Spanned<Item<'a>>>);
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum Symbol<'a> { pub enum Symbol<'a> {
Epsilon, Epsilon(&'a str),
Ident(&'a str), Ident(&'a str),
} }
@ -62,14 +62,14 @@ pub enum TopLevel<'a> {
Table(), Table(),
} }
use crate::loader::Context; use crate::loader::{Context, log::LogSink};
impl<'a> Spanned<Item<'a>> { impl<'a> Spanned<Item<'a>> {
pub fn expect_symbol(&self, ctx: &mut Context<'a>) -> Option<Symbol<'a>> { pub fn expect_symbol(&self, ctx: &mut Context<'a>) -> Option<Symbol<'a>> {
match &self.0 { match &self.0 {
Item::Symbol(sym) => return Some(*sym), Item::Symbol(sym) => return Some(*sym),
Item::Tuple(_) => ctx.emit_error("expected ident found tuple", self.1), Item::Tuple(_) => _ = ctx.emit_error("expected ident found tuple", self.1),
Item::List(_) => ctx.emit_error("expected ident found list", self.1), Item::List(_) => _ = ctx.emit_error("expected ident found list", self.1),
} }
None None
} }
@ -77,18 +77,18 @@ impl<'a> Spanned<Item<'a>> {
pub fn expect_ident(&self, ctx: &mut Context<'a>) -> Option<&'a str> { pub fn expect_ident(&self, ctx: &mut Context<'a>) -> Option<&'a str> {
match &self.0 { match &self.0 {
Item::Symbol(Symbol::Ident(ident)) => return Some(ident), Item::Symbol(Symbol::Ident(ident)) => return Some(ident),
Item::Symbol(Symbol::Epsilon) => ctx.emit_error("expected ident found epsilon", self.1), Item::Symbol(Symbol::Epsilon(_)) => _ = ctx.emit_error("expected ident found epsilon", self.1),
Item::Tuple(_) => ctx.emit_error("expected ident found tuple", self.1), Item::Tuple(_) => _ = ctx.emit_error("expected ident found tuple", self.1),
Item::List(_) => ctx.emit_error("expected ident found list", self.1), Item::List(_) => _ = ctx.emit_error("expected ident found list", self.1),
} }
None None
} }
pub fn expect_set(&self, ctx: &mut Context<'a>) -> Option<&[Spanned<Item<'a>>]> { pub fn expect_set(&self, ctx: &mut Context<'a>) -> Option<&[Spanned<Item<'a>>]> {
match &self.0 { match &self.0 {
Item::Symbol(Symbol::Ident(_)) => ctx.emit_error("expected set found ident", self.1), Item::Symbol(Symbol::Ident(_)) => _ = ctx.emit_error("expected set found ident", self.1),
Item::Symbol(Symbol::Epsilon) => ctx.emit_error("expected set found epsilon", self.1), Item::Symbol(Symbol::Epsilon(_)) => _ = ctx.emit_error("expected set found epsilon", self.1),
Item::Tuple(_) => ctx.emit_error("expected set found tuple", self.1), Item::Tuple(_) => _ = ctx.emit_error("expected set found tuple", self.1),
Item::List(list) => return Some(&list.0), Item::List(list) => return Some(&list.0),
} }
None None
@ -96,9 +96,9 @@ impl<'a> Spanned<Item<'a>> {
pub fn expect_list(&self, ctx: &mut Context<'a>) -> Option<&[Spanned<Item<'a>>]> { pub fn expect_list(&self, ctx: &mut Context<'a>) -> Option<&[Spanned<Item<'a>>]> {
match &self.0 { match &self.0 {
Item::Symbol(Symbol::Ident(_)) => ctx.emit_error("expected list found ident", self.1), Item::Symbol(Symbol::Ident(_)) => _ = ctx.emit_error("expected list found ident", self.1),
Item::Symbol(Symbol::Epsilon) => ctx.emit_error("expected list found epsilon", self.1), Item::Symbol(Symbol::Epsilon(_)) => _ = ctx.emit_error("expected list found epsilon", self.1),
Item::Tuple(_) => ctx.emit_error("expected list found tuple", self.1), Item::Tuple(_) => _ = ctx.emit_error("expected list found tuple", self.1),
Item::List(list) => return Some(&list.0), Item::List(list) => return Some(&list.0),
} }
None None
@ -120,10 +120,10 @@ impl<'a> Spanned<Item<'a>> {
pub fn expect_tuple(&self, ctx: &mut Context<'a>) -> Option<Spanned<&Tuple<'a>>> { pub fn expect_tuple(&self, ctx: &mut Context<'a>) -> Option<Spanned<&Tuple<'a>>> {
match &self.0 { match &self.0 {
Item::Symbol(Symbol::Ident(_)) => ctx.emit_error("expected tuple found ident", self.1), Item::Symbol(Symbol::Ident(_)) => _ = ctx.emit_error("expected tuple found ident", self.1),
Item::Symbol(Symbol::Epsilon) => ctx.emit_error("expected tuple found epsilon", self.1), Item::Symbol(Symbol::Epsilon(_)) => _ = ctx.emit_error("expected tuple found epsilon", self.1),
Item::Tuple(tuple) => return Some(Spanned(tuple, self.1)), Item::Tuple(tuple) => return Some(Spanned(tuple, self.1)),
Item::List(_) => ctx.emit_error("expected tuple found list", self.1), Item::List(_) => _ = ctx.emit_error("expected tuple found list", self.1),
} }
None None
} }

View file

@ -2,11 +2,97 @@ use std::fmt::Display;
use crate::loader::Span; use crate::loader::Span;
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Logs { pub struct Logs {
logs: Vec<LogEntry>, logs: Vec<LogEntry>,
has_error: bool, has_error: bool,
} }
pub trait LogSink {
fn emit(&mut self, entry: LogEntry) -> &mut LogEntry;
fn emit_error_locless(&mut self, msg: impl Into<String>) -> &mut LogEntry {
self.emit(LogEntry {
message: msg.into(),
span: None,
level: LogLevel::Error,
child: None,
})
}
fn emit_error(&mut self, msg: impl Into<String>, span: Span) -> &mut LogEntry {
self.emit(LogEntry {
message: msg.into(),
span: Some(span),
level: LogLevel::Error,
child: None,
})
}
fn emit_warning(&mut self, msg: impl Into<String>, span: Span) -> &mut LogEntry {
self.emit(LogEntry {
message: msg.into(),
span: Some(span),
level: LogLevel::Warning,
child: None,
})
}
fn emit_warning_locless(&mut self, msg: impl Into<String>) -> &mut LogEntry {
self.emit(LogEntry {
message: msg.into(),
span: None,
level: LogLevel::Warning,
child: None,
})
}
fn emit_info(&mut self, msg: impl Into<String>, span: Span) -> &mut LogEntry {
self.emit(LogEntry {
message: msg.into(),
span: Some(span),
level: LogLevel::Info,
child: None,
})
}
fn emit_info_logless(&mut self, msg: impl Into<String>) -> &mut LogEntry {
self.emit(LogEntry {
message: msg.into(),
span: None,
level: LogLevel::Info,
child: None,
})
}
fn emit_help(&mut self, msg: impl Into<String>, span: Span) -> &mut LogEntry {
self.emit(LogEntry {
message: msg.into(),
span: Some(span),
level: LogLevel::Help,
child: None,
})
}
fn emit_help_logless(&mut self, msg: impl Into<String>) -> &mut LogEntry {
self.emit(LogEntry {
message: msg.into(),
span: None,
level: LogLevel::Help,
child: None,
})
}
}
impl LogSink for Logs {
fn emit(&mut self, entry: LogEntry) -> &mut LogEntry {
self.has_error |= matches!(entry.level, LogLevel::Error);
self.logs.push(entry);
self.logs.last_mut().unwrap()
}
}
impl Logs { impl Logs {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
@ -19,51 +105,6 @@ impl Logs {
self.has_error self.has_error
} }
pub fn emit(&mut self, entry: LogEntry) {
self.has_error |= matches!(entry.level, LogLevel::Error);
self.logs.push(entry);
}
pub fn emit_error_locless(&mut self, msg: impl Into<String>) {
self.emit(LogEntry {
message: msg.into(),
span: None,
level: LogLevel::Error,
});
}
pub fn emit_error(&mut self, msg: impl Into<String>, span: Span) {
self.emit(LogEntry {
message: msg.into(),
span: Some(span),
level: LogLevel::Error,
});
}
pub fn emit_warning(&mut self, msg: impl Into<String>, span: Span) {
self.emit(LogEntry {
message: msg.into(),
span: Some(span),
level: LogLevel::Warning,
});
}
pub fn emit_warning_locless(&mut self, msg: impl Into<String>) {
self.emit(LogEntry {
message: msg.into(),
span: None,
level: LogLevel::Warning,
});
}
pub fn emit_info(&mut self, msg: impl Into<String>, span: Span) {
self.emit(LogEntry {
message: msg.into(),
span: Some(span),
level: LogLevel::Info,
});
}
pub fn displayable_with<'a>( pub fn displayable_with<'a>(
&'a self, &'a self,
src: &'a str, src: &'a str,
@ -86,16 +127,27 @@ impl Default for Logs {
} }
} }
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum LogLevel { pub enum LogLevel {
Info, Info,
Warning, Warning,
Error, Error,
Help,
} }
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct LogEntry { pub struct LogEntry {
pub message: String, pub message: String,
pub span: Option<Span>, pub span: Option<Span>,
pub level: LogLevel, pub level: LogLevel,
pub child: Option<Box<LogEntry>>,
}
impl LogSink for LogEntry {
fn emit(&mut self, entry: LogEntry) -> &mut LogEntry {
self.child = Some(Box::new(entry));
self.child.as_mut().unwrap()
}
} }
pub struct LogEntryDisplay<'a> { pub struct LogEntryDisplay<'a> {
@ -109,23 +161,31 @@ impl<'a> Display for LogEntryDisplay<'a> {
pub const BOLD: &str = "\x1b[1m"; pub const BOLD: &str = "\x1b[1m";
// pub const UNDERLINE: &str = "\x1b[4m"; // pub const UNDERLINE: &str = "\x1b[4m";
pub const RED: &str = "\x1b[31m"; pub const RED: &str = "\x1b[31m";
// pub const GREEN: &str = "\x1b[32m"; pub const GREEN: &str = "\x1b[32m";
pub const YELLOW: &str = "\x1b[33m"; pub const YELLOW: &str = "\x1b[33m";
// pub const BLUE: &str = "\x1b[34m"; // pub const BLUE: &str = "\x1b[34m";
pub const CYAN: &str = "\x1b[36m"; pub const CYAN: &str = "\x1b[36m";
match self.entry.level { let mut next_entry = Some(self.entry);
while let Some(entry) = next_entry {
match entry.level {
LogLevel::Help => write!(f, "{BOLD}{GREEN}help{RESET}{BOLD}: ")?,
LogLevel::Info => write!(f, "{BOLD}{CYAN}info{RESET}{BOLD}: ")?, LogLevel::Info => write!(f, "{BOLD}{CYAN}info{RESET}{BOLD}: ")?,
LogLevel::Warning => write!(f, "{BOLD}{YELLOW}warning{RESET}{BOLD}: ")?, LogLevel::Warning => write!(f, "{BOLD}{YELLOW}warning{RESET}{BOLD}: ")?,
LogLevel::Error => write!(f, "{BOLD}{RED}error{RESET}{BOLD}: ")?, LogLevel::Error => write!(f, "{BOLD}{RED}error{RESET}{BOLD}: ")?,
} }
writeln!(f, "{}{RESET}", self.entry.message)?; writeln!(f, "{}{RESET}", entry.message)?;
if let Some(span) = self.entry.span { if let Some(span) = entry.span {
let line_start = self.src.get(..=span.0).unwrap_or("").lines().count(); let line_start = self.src.get(..=span.0).unwrap_or("").lines().count();
let line_end = self.src.get(..span.1).unwrap_or("").lines().count(); let line_end = self.src.get(..span.1).unwrap_or("").lines().count();
let padding = if line_end == 0 {1} else {line_end.ilog10() as usize}; let padding = if line_end == 0 {
1
} else {
line_end.ilog10() as usize
};
let start = self let start = self
.src .src
@ -180,6 +240,8 @@ impl<'a> Display for LogEntryDisplay<'a> {
writeln!(f)?; writeln!(f)?;
} }
} }
next_entry = entry.child.as_deref()
}
Ok(()) Ok(())
} }

View file

@ -1,23 +1,67 @@
use crate::{automatan::*, loader::ast::TopLevel}; use crate::{
automatan::*,
loader::{
ast::TopLevel,
log::{LogEntry, LogSink},
},
};
pub mod ast; pub mod ast;
pub mod lexer; pub mod lexer;
pub mod log; pub mod log;
pub mod parser; pub mod parser;
pub const EPSILON_LOWER: &str = "Ɛ"; #[macro_export]
pub const EPSILON_LOWER_MATH: &str = "𝛆"; macro_rules! maker {
pub const DELTA_LOWER: &str = "δ"; (pat: $($pat:pat),*) => {
pub const SIGMA_UPPER: &str = "Σ"; $($pat)|*
pub const GAMMA_UPPER: &str = "Γ"; };
pub const GAMMA_LOWER: &str = "γ"; (arr: $($expr:expr),*) => {
[$($expr),*]
};
(str: $first:literal, $($remainder:literal),+) => {
concat!($crate::maker!(str: $first), " | ", $crate::maker!(str: $($remainder),*))
};
(str: $first:literal) => {
concat!("'",$first,"'")
};
}
pub const INITIAL_STATE: &str = "q0";
pub const INITIAL_STACK: &str = "z0";
pub const BLANK_SYMBOL: &str = "B";
#[macro_export]
macro_rules! epsilon {
($ident: ident) => {
$crate::maker!($ident: "epsilon","~", "Ɛ", "ε", "ϵ", "𝛆", "𝛜", "𝜀", "𝜖", "𝜺", "𝝐", "𝝴", "𝞊", "𝞮", "𝟄")
};
}
#[macro_export]
macro_rules! delta_lower {
($ident: ident) => {
$crate::maker!($ident: "delta","D","d","","δ", "𝛅", "𝛿", "𝜹", "𝝳", "𝞭")
};
}
#[macro_export]
macro_rules! sigma_upper {
($ident: ident) => {
$crate::maker!($ident: "E","S", "sigma","Σ","𝚺", "𝛴", "𝜮", "𝝨", "𝞢")
};
}
#[macro_export]
macro_rules! gamma_upper {
($ident: ident) => {
$crate::maker!($ident: "T","G","gamma","Γ","", "𝚪", "𝛤", "𝜞", "𝝘", "𝞒")
};
}
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)] #[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Span( pub struct Span(pub usize, pub usize);
#[cfg_attr(feature = "serde", serde(rename = "start"))] pub usize,
#[cfg_attr(feature = "serde", serde(rename = "end"))] pub usize,
);
impl Span { impl Span {
pub fn join(&self, end: Span) -> Span { pub fn join(&self, end: Span) -> Span {
Span(self.0, end.1) Span(self.0, end.1)
@ -41,6 +85,12 @@ pub struct Context<'a> {
src: &'a str, src: &'a str,
} }
impl<'a> LogSink for Context<'a> {
fn emit(&mut self, entry: log::LogEntry) -> &mut LogEntry {
self.logs.emit(entry)
}
}
impl<'a> Context<'a> { impl<'a> Context<'a> {
pub fn new(src: &'a str) -> Self { pub fn new(src: &'a str) -> Self {
Self { Self {
@ -61,30 +111,6 @@ impl<'a> Context<'a> {
Span(self.src.len(), self.src.len()) Span(self.src.len(), self.src.len())
} }
pub fn emit(&mut self, entry: log::LogEntry) {
self.logs.emit(entry);
}
pub fn emit_error_locless(&mut self, msg: impl Into<String>) {
self.logs.emit_error_locless(msg);
}
pub fn emit_error(&mut self, msg: impl Into<String>, span: Span) {
self.logs.emit_error(msg, span);
}
pub fn emit_warning(&mut self, msg: impl Into<String>, span: Span) {
self.logs.emit_warning(msg, span);
}
pub fn emit_warning_locless(&mut self, msg: impl Into<String>) {
self.logs.emit_warning_locless(msg);
}
pub fn emit_info(&mut self, msg: impl Into<String>, span: Span) {
self.logs.emit_info(msg, span);
}
pub fn contains_errors(&self) -> bool { pub fn contains_errors(&self) -> bool {
self.logs.contains_errors() self.logs.contains_errors()
} }
@ -127,11 +153,13 @@ pub fn parse_universal<'a>(ctx: &mut Context<'a>) -> Option<Machine<'a>> {
(item.expect_ident(ctx)?, span) (item.expect_ident(ctx)?, span)
} }
Some(S(_, span)) => { Some(S(_, span)) => {
ctx.emit_error("expected type=<type> as first item", span); ctx.emit_error("expected type=<type> as first item", span)
.emit_help_logless("add: type = ...");
return None; return None;
} }
None => { None => {
ctx.emit_error("expected type=<type> as first item", ctx.eof()); ctx.emit_error("expected type=<type> as first item", ctx.eof())
.emit_help_logless("add: type = ...");
return None; return None;
} }
}; };
@ -164,8 +192,8 @@ pub fn parse_universal<'a>(ctx: &mut Context<'a>) -> Option<Machine<'a>> {
}; };
Some(match parse_type(items.next(), ctx)? { Some(match parse_type(items.next(), ctx)? {
Type::Dfa => Machine::Fa(fa::Fa::parse(items, ctx, D)?), Type::Dfa => Machine::Fa(fa::Fa::compile(items, ctx, D)?),
Type::Nfa => Machine::Fa(fa::Fa::parse(items, ctx, N)?), Type::Nfa => Machine::Fa(fa::Fa::compile(items, ctx, N)?),
Type::Dpda => Machine::Pda(pda::Pda::parse(items, ctx, D)?), Type::Dpda => Machine::Pda(pda::Pda::parse(items, ctx, D)?),
Type::Npda => Machine::Pda(pda::Pda::parse(items, ctx, N)?), Type::Npda => Machine::Pda(pda::Pda::parse(items, ctx, N)?),
Type::Tm => Machine::Tm(tm::Tm::parse(items, ctx, D)?), Type::Tm => Machine::Tm(tm::Tm::parse(items, ctx, D)?),

View file

@ -1,3 +1,5 @@
use crate::epsilon;
use crate::loader::log::LogSink;
use crate::loader::{Context, Span}; use crate::loader::{Context, Span};
use super::lexer::Token as T; use super::lexer::Token as T;
@ -49,7 +51,7 @@ impl<'a, 'b> Parser<'a, 'b> {
return self.peek; return self.peek;
} }
Some(S(Ok(ok), r)) => return Some(S(ok, r)), Some(S(Ok(ok), r)) => return Some(S(ok, r)),
Some(S(Err(err), span)) => self.ctx.emit_error(format!("lexer: {err:?}"), span), Some(S(Err(err), span)) => _ = self.ctx.emit_error(format!("lexer: {err:?}"), span),
None => return None, None => return None,
} }
} }
@ -90,9 +92,8 @@ impl<'a, 'b> Parser<'a, 'b> {
fn parse_as_symbol(&mut self, tok: S<T<'a>>) -> S<Symbol<'a>> { fn parse_as_symbol(&mut self, tok: S<T<'a>>) -> S<Symbol<'a>> {
match tok { match tok {
S(T::Tilde, r) => S(Symbol::Epsilon, r), S(T::Tilde, r) => S(Symbol::Epsilon("~"), r),
S(T::Ident("epsilon"), r) => S(Symbol::Epsilon, r), S(T::Ident(repr@ epsilon!(pat)), r) => S(Symbol::Epsilon(repr), r),
S(T::Ident(super::EPSILON_LOWER), r) => S(Symbol::Epsilon, r),
S(T::Ident(ident), r) => S(Symbol::Ident(ident), r), S(T::Ident(ident), r) => S(Symbol::Ident(ident), r),
S(got, span) => { S(got, span) => {
self.ctx.emit_error( self.ctx.emit_error(

View file

@ -1,6 +1,10 @@
use std::collections::HashMap; use std::collections::HashMap;
use automata::loader::{self, Context, Span, Spanned, lexer::Lexer}; use automata::{
delta_lower, epsilon, gamma_upper,
loader::{self, Context, Span, Spanned, lexer::Lexer},
sigma_upper,
};
use serde::Serialize; use serde::Serialize;
use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::prelude::wasm_bindgen;
@ -88,16 +92,11 @@ pub fn lex(input: &str) -> Vec<Tok> {
{ {
Kind::Keyword Kind::Keyword
} }
// ugly hack to keep single ascii letters non keyworded for user
Token::Ident(ident) if ident.is_ascii() && ident.len()==1 => Kind::Ident,
Token::Ident( Token::Ident(
loader::EPSILON_LOWER epsilon!(pat) | delta_lower!(pat) | sigma_upper!(pat) | gamma_upper!(pat),
| "epsilon"
| loader::DELTA_LOWER
| "delta"
| loader::GAMMA_UPPER
| "gamma"
| loader::GAMMA_LOWER
| loader::SIGMA_UPPER
| "sigma",
) => Kind::Keyword, ) => Kind::Keyword,
Token::Ident(_) => Kind::Ident, Token::Ident(_) => Kind::Ident,
Token::LineEnd => Kind::Punc, Token::LineEnd => Kind::Punc,
@ -127,6 +126,7 @@ pub fn lex(input: &str) -> Vec<Tok> {
#[wasm_bindgen] #[wasm_bindgen]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum LogLevel { pub enum LogLevel {
Help = "help",
Info = "info", Info = "info",
Warning = "warning", Warning = "warning",
Error = "error", Error = "error",
@ -165,7 +165,7 @@ pub fn compile(input: &str) -> CompileResult {
use std::fmt::Write; use std::fmt::Write;
let ansi_log = ctx.logs_display().fold(String::new(), |mut s, e| { let ansi_log = ctx.logs_display().fold(String::new(), |mut s, e| {
write!(&mut s, "{e}").unwrap(); writeln!(&mut s, "{e}").unwrap();
s s
}); });
@ -174,6 +174,7 @@ pub fn compile(input: &str) -> CompileResult {
.into_entries() .into_entries()
.map(|e| CompileLog { .map(|e| CompileLog {
level: match e.level { level: match e.level {
loader::log::LogLevel::Help => LogLevel::Help,
loader::log::LogLevel::Info => LogLevel::Info, loader::log::LogLevel::Info => LogLevel::Info,
loader::log::LogLevel::Warning => LogLevel::Warning, loader::log::LogLevel::Warning => LogLevel::Warning,
loader::log::LogLevel::Error => LogLevel::Error, loader::log::LogLevel::Error => LogLevel::Error,