mirror of
https://github.com/ParkerTenBroeck/automata.git
synced 2026-06-07 05:28:45 -04:00
reorganized project
This commit is contained in:
parent
34b20ec1fe
commit
c57a95b7b5
29 changed files with 34 additions and 43 deletions
12
automata/Cargo.toml
Normal file
12
automata/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "automata"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"], optional = true}
|
||||
serde_with = { version = "3.0", features = ["default"], optional = true}
|
||||
|
||||
[features]
|
||||
default = []
|
||||
serde = ["dep:serde", "dep:serde_with"]
|
||||
278
automata/src/automatan/fa.rs
Normal file
278
automata/src/automatan/fa.rs
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::loader::{
|
||||
Context, DELTA_LOWER, GAMMA_UPPER, SIGMA_UPPER, Spanned,
|
||||
ast::{self, Symbol as Sym},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct TransitionFrom<'a> {
|
||||
pub state: State<'a>,
|
||||
pub letter: Option<Letter<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct TransitionTo<'a> {
|
||||
pub state: State<'a>,
|
||||
|
||||
pub transition: Span,
|
||||
pub function: Span,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(unused)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde_with::serde_as)]
|
||||
pub struct Fa<'a> {
|
||||
pub initial_state: State<'a>,
|
||||
pub states: HashMap<State<'a>, StateInfo>,
|
||||
pub alphabet: HashMap<Letter<'a>, LetterInfo>,
|
||||
pub final_states: HashMap<State<'a>, StateInfo>,
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[serde_as(as = "serde_with::Seq<(_, _)>")]
|
||||
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
|
||||
#[cfg(not(feature = "serde"))]
|
||||
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
|
||||
}
|
||||
|
||||
impl<'a> Fa<'a> {
|
||||
pub fn parse(
|
||||
items: impl Iterator<Item = Spanned<ast::TopLevel<'a>>>,
|
||||
ctx: &mut Context<'a>,
|
||||
options: Options,
|
||||
) -> Option<Fa<'a>> {
|
||||
|
||||
let mut initial_state = None;
|
||||
|
||||
let mut states = HashMap::new();
|
||||
let mut alphabet = HashMap::new();
|
||||
let mut final_states = HashMap::new();
|
||||
|
||||
let mut transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>> =
|
||||
HashMap::new();
|
||||
|
||||
for Spanned(element, span) in items {
|
||||
use Spanned as S;
|
||||
use ast::TopLevel as TL;
|
||||
match element {
|
||||
TL::Item(S("Q", _), list) => {
|
||||
if !states.is_empty() {
|
||||
ctx.emit_error("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
|
||||
.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_none()
|
||||
{
|
||||
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), _) => {
|
||||
ctx.emit_error(format!("unknown item {name:?}, expected 'Q' | 'E' | '{SIGMA_UPPER}' | 'sigma' | 'F' | 'T' | '{GAMMA_UPPER}' | 'gamma' | 'I' | 'q0' | 'S' | 'z0'"), dest_s);
|
||||
}
|
||||
|
||||
TL::TransitionFunc(S((S("d" | DELTA_LOWER | "delta", _), tuple), _), list) => {
|
||||
let list = list.set_weak();
|
||||
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), _) => {
|
||||
ctx.emit_error(
|
||||
format!(
|
||||
"unknown function {name:?}, expected 'd' | 'delta' | '{DELTA_LOWER}'"
|
||||
),
|
||||
dest_s,
|
||||
);
|
||||
}
|
||||
|
||||
TL::ProductionRule(_, _) => {
|
||||
ctx.emit_error("unexpected production rule", span);
|
||||
}
|
||||
TL::Table() => ctx.emit_error("unexpected table", span),
|
||||
}
|
||||
}
|
||||
|
||||
if alphabet.is_empty() {
|
||||
ctx.emit_error_locless("alphabet never defined");
|
||||
}
|
||||
|
||||
if states.is_empty() {
|
||||
ctx.emit_error_locless("states never defined");
|
||||
}
|
||||
|
||||
let initial_state = match initial_state {
|
||||
Some(some) => some,
|
||||
None => {
|
||||
if states.contains_key(&State("q0")) {
|
||||
ctx.emit_warning_locless("initial state not defined, defaulting to 'q0'");
|
||||
} else {
|
||||
ctx.emit_error_locless("initial state not defined");
|
||||
}
|
||||
State("q0")
|
||||
}
|
||||
};
|
||||
|
||||
if ctx.contains_errors() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Fa {
|
||||
initial_state,
|
||||
states,
|
||||
alphabet,
|
||||
final_states,
|
||||
transitions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Spanned<&ast::Tuple<'a>> {
|
||||
fn expect_fa_transition_function(
|
||||
&self,
|
||||
ctx: &mut Context<'a>,
|
||||
) -> Option<(Spanned<&'a str>, Spanned<ast::Symbol<'a>>)> {
|
||||
match &self.0.0[..] {
|
||||
[
|
||||
Spanned(ast::Item::Symbol(ast::Symbol::Ident(state)), state_span),
|
||||
Spanned(ast::Item::Symbol(letter), letter_span),
|
||||
] => {
|
||||
return Some((Spanned(state, *state_span), Spanned(*letter, *letter_span)));
|
||||
}
|
||||
_ => ctx.emit_error("expected FA transition function (ident, ident|~)", self.1),
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
43
automata/src/automatan/mod.rs
Normal file
43
automata/src/automatan/mod.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::loader::Span;
|
||||
|
||||
pub mod fa;
|
||||
pub mod pda;
|
||||
pub mod tm;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Options {
|
||||
pub non_deterministic: bool,
|
||||
pub epsilon_moves: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
|
||||
pub struct State<'a>(pub &'a str);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
|
||||
pub struct Symbol<'a>(pub &'a str);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
|
||||
pub struct Letter<'a>(pub &'a str);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct StateInfo {
|
||||
pub definition: Span,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct SymbolInfo {
|
||||
pub definition: Span,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct LetterInfo {
|
||||
pub definition: Span,
|
||||
}
|
||||
399
automata/src/automatan/pda.rs
Normal file
399
automata/src/automatan/pda.rs
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::loader::{
|
||||
Context, DELTA_LOWER, GAMMA_UPPER, SIGMA_UPPER, Spanned,
|
||||
ast::{self, Symbol as Sym},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct TransitionFrom<'a> {
|
||||
pub state: State<'a>,
|
||||
pub letter: Option<Letter<'a>>,
|
||||
pub symbol: Symbol<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct TransitionTo<'a> {
|
||||
pub state: State<'a>,
|
||||
pub stack: Vec<Symbol<'a>>,
|
||||
|
||||
pub transition: Span,
|
||||
pub function: Span,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(unused)]
|
||||
#[cfg_attr(feature = "serde", serde_with::serde_as)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct Pda<'a> {
|
||||
pub initial_state: State<'a>,
|
||||
pub initial_stack: Symbol<'a>,
|
||||
pub states: HashMap<State<'a>, StateInfo>,
|
||||
pub symbols: HashMap<Symbol<'a>, SymbolInfo>,
|
||||
pub alphabet: HashMap<Letter<'a>, LetterInfo>,
|
||||
|
||||
pub final_states: Option<HashMap<State<'a>, StateInfo>>,
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[serde_as(as = "serde_with::Seq<(_, _)>")]
|
||||
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
|
||||
|
||||
#[cfg(not(feature = "serde"))]
|
||||
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
|
||||
}
|
||||
|
||||
impl<'a> Pda<'a> {
|
||||
pub fn parse(
|
||||
items: impl Iterator<Item = Spanned<ast::TopLevel<'a>>>,
|
||||
ctx: &mut Context<'a>,
|
||||
options: Options,
|
||||
) -> Option<Pda<'a>> {
|
||||
let mut initial_state = None;
|
||||
let mut initial_stack = None;
|
||||
|
||||
let mut states = HashMap::new();
|
||||
let mut symbols = HashMap::new();
|
||||
let mut alphabet = HashMap::new();
|
||||
let mut final_states = None;
|
||||
|
||||
let mut transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>> =
|
||||
HashMap::new();
|
||||
|
||||
for Spanned(element, span) in items {
|
||||
use Spanned as S;
|
||||
use ast::TopLevel as TL;
|
||||
match element {
|
||||
TL::Item(S("Q", _), list) => {
|
||||
if !states.is_empty() {
|
||||
ctx.emit_error("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
|
||||
.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_some() {
|
||||
ctx.emit_error("final states already set", span);
|
||||
}
|
||||
let mut map = HashMap::new();
|
||||
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 map
|
||||
.insert(State(ident), StateInfo { definition: item.1 })
|
||||
.is_none()
|
||||
{
|
||||
ctx.emit_error("final state redefined", item.1);
|
||||
}
|
||||
} else {
|
||||
ctx.emit_error("final state not defined in set of states", item.1);
|
||||
}
|
||||
}
|
||||
final_states = Some(map);
|
||||
}
|
||||
TL::Item(S("T" | GAMMA_UPPER | "gamma", _), list) => {
|
||||
if !symbols.is_empty() {
|
||||
ctx.emit_error("stack symbols 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 symbols
|
||||
.insert(Symbol(ident), SymbolInfo { definition: item.1 })
|
||||
.is_some()
|
||||
{
|
||||
ctx.emit_error("stack symbol redefined", item.1);
|
||||
}
|
||||
}
|
||||
|
||||
if list.is_empty() {
|
||||
ctx.emit_error("stack symbols cannot be empty", span);
|
||||
}
|
||||
}
|
||||
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("S" | "z0", _), S(src, src_d)) => match src {
|
||||
ast::Item::Symbol(Sym::Ident(ident)) => {
|
||||
if initial_stack.is_some() {
|
||||
ctx.emit_error("initial stack already set", span);
|
||||
}
|
||||
if symbols.contains_key(&Symbol(ident)) {
|
||||
initial_stack = Some(Symbol(ident));
|
||||
} else {
|
||||
ctx.emit_error(
|
||||
"initial stack symbol not defined as a stack symbol",
|
||||
src_d,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => ctx.emit_error("expected ident", src_d),
|
||||
},
|
||||
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);
|
||||
}
|
||||
|
||||
TL::TransitionFunc(S((S("d" | DELTA_LOWER | "delta", _), tuple), _), list) => {
|
||||
let list = list.set_weak();
|
||||
let Some((state, letter, stack_symbol)) =
|
||||
tuple.as_ref().expect_pda_transition_function(ctx)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if !states.contains_key(&State(state.0)) {
|
||||
ctx.emit_error("transition state not defined as state", state.1);
|
||||
continue;
|
||||
};
|
||||
if !symbols.contains_key(&Symbol(stack_symbol.0)) {
|
||||
ctx.emit_error(
|
||||
"transition stack symbol not defined as stack symbol",
|
||||
stack_symbol.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, stack)) = item
|
||||
.expect_tuple(ctx)
|
||||
.and_then(|item| item.expect_pda_transition(ctx))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !states.contains_key(&State(next_state.0)) {
|
||||
ctx.emit_error("transition state not defined as state", next_state.1);
|
||||
continue;
|
||||
};
|
||||
|
||||
let stack: Vec<_> = stack
|
||||
.iter()
|
||||
.rev()
|
||||
.filter_map(|symbol| {
|
||||
if matches!(symbol.0, ast::Item::Symbol(Sym::Epsilon)) {
|
||||
return None;
|
||||
}
|
||||
let ident = symbol.expect_ident(ctx)?;
|
||||
|
||||
if !symbols.contains_key(&Symbol(ident)) {
|
||||
ctx.emit_error("transition stack symbol not defined", symbol.1);
|
||||
return None;
|
||||
};
|
||||
Some(Symbol(ident))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let entry: &mut _ = transitions
|
||||
.entry(TransitionFrom {
|
||||
letter,
|
||||
state: State(state.0),
|
||||
symbol: Symbol(stack_symbol.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),
|
||||
stack,
|
||||
|
||||
function: tuple.1,
|
||||
transition: item.1,
|
||||
}) {
|
||||
ctx.emit_warning("duplicate transition", item.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
TL::TransitionFunc(S((S(name, _), _), dest_s), _) => {
|
||||
ctx.emit_error(
|
||||
format!(
|
||||
"unknown function {name:?}, expected 'd' | 'delta' | '{DELTA_LOWER}'"
|
||||
),
|
||||
dest_s,
|
||||
);
|
||||
}
|
||||
|
||||
TL::ProductionRule(_, _) => {
|
||||
ctx.emit_error("unexpected production rule", span);
|
||||
}
|
||||
TL::Table() => ctx.emit_error("unexpected table", span),
|
||||
}
|
||||
}
|
||||
|
||||
if symbols.is_empty() {
|
||||
ctx.emit_error_locless("stack symbols never defined");
|
||||
}
|
||||
|
||||
if alphabet.is_empty() {
|
||||
ctx.emit_error_locless("alphabet never defined");
|
||||
}
|
||||
|
||||
if states.is_empty() {
|
||||
ctx.emit_error_locless("states never defined");
|
||||
}
|
||||
|
||||
let initial_stack = match initial_stack {
|
||||
Some(some) => some,
|
||||
None => {
|
||||
if symbols.contains_key(&Symbol("z0")) {
|
||||
ctx.emit_warning_locless(
|
||||
"initial stack symbol not defined, defaulting to 'z0'",
|
||||
);
|
||||
} else {
|
||||
ctx.emit_error_locless("initial stack symbol not defined");
|
||||
}
|
||||
Symbol("z0")
|
||||
}
|
||||
};
|
||||
|
||||
let initial_state = match initial_state {
|
||||
Some(some) => some,
|
||||
None => {
|
||||
if states.contains_key(&State("q0")) {
|
||||
ctx.emit_warning_locless("initial state not defined, defaulting to 'q0'");
|
||||
} else {
|
||||
ctx.emit_error_locless("initial state not defined");
|
||||
}
|
||||
State("q0")
|
||||
}
|
||||
};
|
||||
|
||||
if ctx.contains_errors() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Pda {
|
||||
initial_state,
|
||||
initial_stack,
|
||||
states,
|
||||
symbols,
|
||||
alphabet,
|
||||
final_states,
|
||||
transitions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Spanned<&'b ast::Tuple<'a>> {
|
||||
fn expect_pda_transition_function(
|
||||
&self,
|
||||
ctx: &mut Context<'a>,
|
||||
) -> Option<(Spanned<&'a str>, Spanned<ast::Symbol<'a>>, Spanned<&'a str>)> {
|
||||
match &self.0.0[..] {
|
||||
[
|
||||
Spanned(ast::Item::Symbol(ast::Symbol::Ident(state)), state_span),
|
||||
Spanned(ast::Item::Symbol(letter), letter_span),
|
||||
Spanned(ast::Item::Symbol(ast::Symbol::Ident(symbol)), symbol_span),
|
||||
] => {
|
||||
return Some((
|
||||
Spanned(state, *state_span),
|
||||
Spanned(*letter, *letter_span),
|
||||
Spanned(symbol, *symbol_span),
|
||||
));
|
||||
}
|
||||
_ => ctx.emit_error(
|
||||
"expected PDA transition function (ident, ident|~, ident)",
|
||||
self.1,
|
||||
),
|
||||
}
|
||||
None
|
||||
}
|
||||
fn expect_pda_transition(
|
||||
&self,
|
||||
ctx: &mut Context<'a>,
|
||||
) -> Option<(Spanned<&'a str>, &'b [Spanned<ast::Item<'a>>])> {
|
||||
match &self.0.0[..] {
|
||||
[
|
||||
Spanned(ast::Item::Symbol(ast::Symbol::Ident(state)), state_span),
|
||||
list,
|
||||
] => {
|
||||
return Some((Spanned(state, *state_span), list.list_weak()));
|
||||
}
|
||||
_ => ctx.emit_error("expected PDA transition (ident, item|[item])", self.1),
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
346
automata/src/automatan/tm.rs
Normal file
346
automata/src/automatan/tm.rs
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::loader::{
|
||||
Context, DELTA_LOWER, GAMMA_UPPER, SIGMA_UPPER, Spanned,
|
||||
ast::{self, Symbol as Sym},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct TransitionFrom<'a> {
|
||||
pub state: State<'a>,
|
||||
pub symbol: Symbol<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub enum Direction {
|
||||
Left,
|
||||
Right,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct TransitionTo<'a> {
|
||||
pub state: State<'a>,
|
||||
pub symbol: Symbol<'a>,
|
||||
pub direction: Direction,
|
||||
|
||||
pub transition: Span,
|
||||
pub function: Span,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(unused)]
|
||||
#[cfg_attr(feature = "serde", serde_with::serde_as)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct Tm<'a> {
|
||||
pub initial_state: State<'a>,
|
||||
pub initial_tape: Symbol<'a>,
|
||||
pub states: HashMap<State<'a>, StateInfo>,
|
||||
pub symbols: HashMap<Symbol<'a>, SymbolInfo>,
|
||||
|
||||
pub final_states: HashMap<State<'a>, StateInfo>,
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[serde_as(as = "serde_with::Seq<(_, _)>")]
|
||||
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
|
||||
#[cfg(not(feature = "serde"))]
|
||||
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
|
||||
}
|
||||
|
||||
impl<'a> Tm<'a> {
|
||||
pub fn parse(
|
||||
items: impl Iterator<Item = Spanned<ast::TopLevel<'a>>>,
|
||||
ctx: &mut Context<'a>,
|
||||
options: Options,
|
||||
) -> Option<Tm<'a>> {
|
||||
let mut initial_state = None;
|
||||
let mut initial_tape = None;
|
||||
|
||||
let mut states = HashMap::new();
|
||||
let mut symbols = HashMap::new();
|
||||
let mut final_states = HashMap::new();
|
||||
|
||||
let mut transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>> =
|
||||
HashMap::new();
|
||||
|
||||
for Spanned(element, span) in items {
|
||||
use Spanned as S;
|
||||
use ast::TopLevel as TL;
|
||||
match element {
|
||||
TL::Item(S("Q", _), list) => {
|
||||
if !states.is_empty() {
|
||||
ctx.emit_error("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
|
||||
.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("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_none()
|
||||
{
|
||||
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("T" | GAMMA_UPPER | "gamma", _), list) => {
|
||||
if !symbols.is_empty() {
|
||||
ctx.emit_error("tape symbols 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 symbols
|
||||
.insert(Symbol(ident), SymbolInfo { definition: item.1 })
|
||||
.is_some()
|
||||
{
|
||||
ctx.emit_error("tape symbol redefined", item.1);
|
||||
}
|
||||
}
|
||||
|
||||
if list.is_empty() {
|
||||
ctx.emit_error("tape symbols cannot be empty", span);
|
||||
}
|
||||
}
|
||||
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("S" | "z0", _), S(src, src_d)) => match src {
|
||||
ast::Item::Symbol(Sym::Ident(ident)) => {
|
||||
if initial_tape.is_some() {
|
||||
ctx.emit_error("initial tape symbol already set", span);
|
||||
}
|
||||
if symbols.contains_key(&Symbol(ident)) {
|
||||
initial_tape = Some(Symbol(ident));
|
||||
} else {
|
||||
ctx.emit_error(
|
||||
"initial tape symbol not defined as a tape symbol",
|
||||
src_d,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => ctx.emit_error("expected ident", src_d),
|
||||
},
|
||||
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);
|
||||
}
|
||||
|
||||
TL::TransitionFunc(S((S("d" | DELTA_LOWER | "delta", _), tuple), _), list) => {
|
||||
let list = list.set_weak();
|
||||
let Some((from_state, from_tape)) =
|
||||
tuple.as_ref().expect_tm_transition_function(ctx)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if !states.contains_key(&State(from_state.0)) {
|
||||
ctx.emit_error("transition state not defined as state", from_state.1);
|
||||
continue;
|
||||
};
|
||||
if !symbols.contains_key(&Symbol(from_tape.0)) {
|
||||
ctx.emit_error(
|
||||
"transition tape symbol not defined as tape symbol",
|
||||
from_tape.1,
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
for item in list {
|
||||
let Some((to_state, to_tape, direction)) = item
|
||||
.expect_tuple(ctx)
|
||||
.and_then(|item| item.expect_tm_transition(ctx))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !states.contains_key(&State(to_state.0)) {
|
||||
ctx.emit_error("transition state not defined as state", to_state.1);
|
||||
continue;
|
||||
};
|
||||
|
||||
let entry: &mut _ = transitions
|
||||
.entry(TransitionFrom {
|
||||
state: State(from_state.0),
|
||||
symbol: Symbol(from_tape.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(to_state.0),
|
||||
symbol: Symbol(to_tape.0),
|
||||
direction: direction.0,
|
||||
|
||||
function: tuple.1,
|
||||
transition: item.1,
|
||||
}) {
|
||||
ctx.emit_warning("duplicate transition", item.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
TL::TransitionFunc(S((S(name, _), _), dest_s), _) => {
|
||||
ctx.emit_error(
|
||||
format!(
|
||||
"unknown function {name:?}, expected 'd' | 'delta' | '{DELTA_LOWER}'"
|
||||
),
|
||||
dest_s,
|
||||
);
|
||||
}
|
||||
|
||||
TL::ProductionRule(_, _) => {
|
||||
ctx.emit_error("unexpected production rule", span);
|
||||
}
|
||||
TL::Table() => ctx.emit_error("unexpected table", span),
|
||||
}
|
||||
}
|
||||
|
||||
if symbols.is_empty() {
|
||||
ctx.emit_error_locless("tape symbols never defined");
|
||||
}
|
||||
|
||||
if states.is_empty() {
|
||||
ctx.emit_error_locless("states never defined");
|
||||
}
|
||||
|
||||
let initial_tape = match initial_tape {
|
||||
Some(some) => some,
|
||||
None => {
|
||||
if symbols.contains_key(&Symbol("z0")) {
|
||||
ctx.emit_warning_locless("initial tape symbol not defined, defaulting to 'z0'");
|
||||
} else {
|
||||
ctx.emit_error_locless("initial tape symbol not defined");
|
||||
}
|
||||
Symbol("z0")
|
||||
}
|
||||
};
|
||||
|
||||
let initial_state = match initial_state {
|
||||
Some(some) => some,
|
||||
None => {
|
||||
if states.contains_key(&State("q0")) {
|
||||
ctx.emit_warning_locless("initial state not defined, defaulting to 'q0'");
|
||||
} else {
|
||||
ctx.emit_error_locless("initial state not defined");
|
||||
}
|
||||
State("q0")
|
||||
}
|
||||
};
|
||||
|
||||
if ctx.contains_errors() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Tm {
|
||||
initial_state,
|
||||
initial_tape,
|
||||
states,
|
||||
symbols,
|
||||
final_states,
|
||||
transitions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Spanned<&ast::Tuple<'a>> {
|
||||
fn expect_tm_transition_function(
|
||||
&self,
|
||||
ctx: &mut Context<'a>,
|
||||
) -> Option<(Spanned<&'a str>, Spanned<&'a str>)> {
|
||||
match &self.0.0[..] {
|
||||
[
|
||||
Spanned(ast::Item::Symbol(ast::Symbol::Ident(state)), state_span),
|
||||
Spanned(ast::Item::Symbol(ast::Symbol::Ident(tape)), tape_span),
|
||||
] => {
|
||||
return Some((Spanned(state, *state_span), Spanned(*tape, *tape_span)));
|
||||
}
|
||||
_ => ctx.emit_error("expected TM transition function (ident, ident)", self.1),
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn expect_tm_transition(
|
||||
&self,
|
||||
ctx: &mut Context<'a>,
|
||||
) -> Option<(Spanned<&'a str>, Spanned<&'a str>, Spanned<Direction>)> {
|
||||
match &self.0.0[..] {
|
||||
[
|
||||
Spanned(ast::Item::Symbol(ast::Symbol::Ident(state)), state_span),
|
||||
Spanned(ast::Item::Symbol(ast::Symbol::Ident(tape)), tape_span),
|
||||
Spanned(ast::Item::Symbol(direction), direction_span),
|
||||
] => {
|
||||
let direction = match direction {
|
||||
ast::Symbol::Ident("left" | "L" | "<") => Direction::Left,
|
||||
ast::Symbol::Ident("right" | "R" | ">") => Direction::Right,
|
||||
ast::Symbol::Epsilon | ast::Symbol::Ident("~") => Direction::None,
|
||||
ast::Symbol::Ident(ident) => {
|
||||
ctx.emit_error(
|
||||
format!("invalid direction specified '{ident}'"),
|
||||
*direction_span,
|
||||
);
|
||||
Direction::None
|
||||
}
|
||||
};
|
||||
return Some((
|
||||
Spanned(state, *state_span),
|
||||
Spanned(*tape, *tape_span),
|
||||
Spanned(direction, *direction_span),
|
||||
));
|
||||
}
|
||||
_ => ctx.emit_error(
|
||||
"expected TM transition function (ident, ident, ident)",
|
||||
self.1,
|
||||
),
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
2
automata/src/lib.rs
Normal file
2
automata/src/lib.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub mod automatan;
|
||||
pub mod loader;
|
||||
130
automata/src/loader/ast.rs
Normal file
130
automata/src/loader/ast.rs
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use super::Spanned;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ListKind {
|
||||
Brace,
|
||||
Bracket,
|
||||
|
||||
BraceComma,
|
||||
BracketComma,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Tuple<'a>(pub Vec<Spanned<Item<'a>>>);
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Symbol<'a> {
|
||||
Epsilon,
|
||||
Ident(&'a str),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Item<'a> {
|
||||
Symbol(Symbol<'a>),
|
||||
Tuple(Tuple<'a>),
|
||||
List(List<'a>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Regex<'a> {
|
||||
Terminal(&'a str),
|
||||
Match {
|
||||
complement: bool,
|
||||
patterns: Vec<Range<char>>,
|
||||
},
|
||||
Concat(Vec<Regex<'a>>),
|
||||
Star(Box<Regex<'a>>),
|
||||
Plus(Box<Regex<'a>>),
|
||||
Union(Vec<Regex<'a>>),
|
||||
Intersection(Vec<Regex<'a>>),
|
||||
Complement(Box<Regex<'a>>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct List<'a>(pub Vec<Spanned<Item<'a>>>, pub ListKind);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ProductionGroup<'a>(pub Vec<Spanned<Symbol<'a>>>);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TopLevel<'a> {
|
||||
Item(Spanned<&'a str>, Spanned<Item<'a>>),
|
||||
TransitionFunc(
|
||||
Spanned<(Spanned<&'a str>, Spanned<Tuple<'a>>)>,
|
||||
Spanned<Item<'a>>,
|
||||
),
|
||||
ProductionRule(
|
||||
Spanned<ProductionGroup<'a>>,
|
||||
Spanned<Vec<Spanned<ProductionGroup<'a>>>>,
|
||||
),
|
||||
Table(),
|
||||
}
|
||||
|
||||
use crate::loader::Context;
|
||||
|
||||
impl<'a> Spanned<Item<'a>> {
|
||||
pub fn expect_symbol(&self, ctx: &mut Context<'a>) -> Option<Symbol<'a>> {
|
||||
match &self.0 {
|
||||
Item::Symbol(sym) => return Some(*sym),
|
||||
Item::Tuple(_) => ctx.emit_error("expected ident found tuple", self.1),
|
||||
Item::List(_) => ctx.emit_error("expected ident found list", self.1),
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn expect_ident(&self, ctx: &mut Context<'a>) -> Option<&'a str> {
|
||||
match &self.0 {
|
||||
Item::Symbol(Symbol::Ident(ident)) => return Some(ident),
|
||||
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::List(_) => ctx.emit_error("expected ident found list", self.1),
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn expect_set(&self, ctx: &mut Context<'a>) -> Option<&[Spanned<Item<'a>>]> {
|
||||
match &self.0 {
|
||||
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::Tuple(_) => ctx.emit_error("expected set found tuple", self.1),
|
||||
Item::List(list) => return Some(&list.0),
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn expect_list(&self, ctx: &mut Context<'a>) -> Option<&[Spanned<Item<'a>>]> {
|
||||
match &self.0 {
|
||||
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::Tuple(_) => ctx.emit_error("expected list found tuple", self.1),
|
||||
Item::List(list) => return Some(&list.0),
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn list_weak(&self) -> &[Spanned<Item<'a>>] {
|
||||
match &self.0 {
|
||||
Item::List(list) => &list.0,
|
||||
_ => std::slice::from_ref(self),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_weak(&self) -> &[Spanned<Item<'a>>] {
|
||||
match &self.0 {
|
||||
Item::List(list) => &list.0,
|
||||
_ => std::slice::from_ref(self),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_tuple(&self, ctx: &mut Context<'a>) -> Option<Spanned<&Tuple<'a>>> {
|
||||
match &self.0 {
|
||||
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::Tuple(tuple) => return Some(Spanned(tuple, self.1)),
|
||||
Item::List(_) => ctx.emit_error("expected tuple found list", self.1),
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
212
automata/src/loader/lexer.rs
Normal file
212
automata/src/loader/lexer.rs
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
use crate::loader::{Span, Spanned};
|
||||
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
|
||||
pub enum Token<'a> {
|
||||
LPar,
|
||||
RPar,
|
||||
|
||||
LBrace,
|
||||
RBrace,
|
||||
|
||||
LBracket,
|
||||
RBracket,
|
||||
|
||||
Tilde,
|
||||
Eq,
|
||||
Comma,
|
||||
|
||||
Or,
|
||||
Plus,
|
||||
Star,
|
||||
And,
|
||||
|
||||
LSmallArrow,
|
||||
LBigArrow,
|
||||
|
||||
Comment(&'a str),
|
||||
|
||||
Ident(&'a str),
|
||||
LineEnd,
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Display for Token<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Token::LPar => write!(f, "'('"),
|
||||
Token::RPar => write!(f, "')'"),
|
||||
Token::LBrace => write!(f, "'{{'"),
|
||||
Token::RBrace => write!(f, "'}}'"),
|
||||
Token::LBracket => write!(f, "'['"),
|
||||
Token::RBracket => write!(f, "']'"),
|
||||
Token::Tilde => write!(f, "'~'"),
|
||||
Token::Eq => write!(f, "'='"),
|
||||
Token::Comma => write!(f, "','"),
|
||||
Token::Or => write!(f, "'|'"),
|
||||
Token::Plus => write!(f, "'+'"),
|
||||
Token::Star => write!(f, "'*'"),
|
||||
Token::And => write!(f, "'&'"),
|
||||
Token::LSmallArrow => write!(f, "'->'"),
|
||||
Token::LBigArrow => write!(f, "'=>'"),
|
||||
Token::Comment(_) => write!(f, "<comment>"),
|
||||
Token::Ident(ident) if f.alternate() => write!(f, "{ident:?}"),
|
||||
Token::Ident(_) => write!(f, "ident"),
|
||||
Token::LineEnd => write!(f, "eol"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Lexer<'a> {
|
||||
input: &'a str,
|
||||
position: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
|
||||
pub enum Error {
|
||||
InvalidChar(char),
|
||||
UnclosedMultiLine,
|
||||
}
|
||||
|
||||
impl<'a> Lexer<'a> {
|
||||
pub fn new(input: &'a str) -> Self {
|
||||
Self { input, position: 0 }
|
||||
}
|
||||
|
||||
fn consume(&mut self) -> Option<char> {
|
||||
let next = self.input.get(self.position..)?.chars().next()?;
|
||||
self.position += next.len_utf8();
|
||||
Some(next)
|
||||
}
|
||||
|
||||
fn peek(&mut self) -> Option<char> {
|
||||
self.input.get(self.position..)?.chars().next()
|
||||
}
|
||||
|
||||
fn backtrack(&mut self) {
|
||||
if let Some(consumed) = self.input.get(..self.position)
|
||||
&& let Some(previous) = consumed.chars().next_back()
|
||||
{
|
||||
self.position -= previous.len_utf8();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn begin_ident(c: char) -> bool {
|
||||
c.is_alphabetic() || c == '_' || (!c.is_ascii() && !c.is_control() && !c.is_whitespace())
|
||||
}
|
||||
|
||||
fn continue_ident(c: char) -> bool {
|
||||
c.is_alphanumeric() || c == '_' || (!c.is_ascii() && !c.is_control() && !c.is_whitespace())
|
||||
}
|
||||
|
||||
impl<'a> std::iter::Iterator for Lexer<'a> {
|
||||
type Item = Spanned<Result<Token<'a>, Error>>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while let Some(c) = self.peek()
|
||||
&& c.is_whitespace()
|
||||
{
|
||||
if c == '\n' {
|
||||
let start = self.position;
|
||||
self.consume();
|
||||
let res = Some(Spanned(Ok(Token::LineEnd), Span(start, self.position)));
|
||||
return res;
|
||||
} else {
|
||||
self.consume();
|
||||
}
|
||||
}
|
||||
let start = self.position;
|
||||
|
||||
let res = match self.consume()? {
|
||||
'(' => Ok(Token::LPar),
|
||||
')' => Ok(Token::RPar),
|
||||
'{' => Ok(Token::LBrace),
|
||||
'}' => Ok(Token::RBrace),
|
||||
'[' => Ok(Token::LBracket),
|
||||
']' => Ok(Token::RBracket),
|
||||
'~' => Ok(Token::Tilde),
|
||||
'+' => Ok(Token::Plus),
|
||||
'*' => Ok(Token::Star),
|
||||
'&' => Ok(Token::And),
|
||||
',' => Ok(Token::Comma),
|
||||
'|' => Ok(Token::Or),
|
||||
'=' => match self.peek() {
|
||||
Some('>') => {
|
||||
self.consume();
|
||||
Ok(Token::LBigArrow)
|
||||
}
|
||||
_ => Ok(Token::Eq),
|
||||
},
|
||||
'-' => match self.peek() {
|
||||
Some('>') => {
|
||||
self.consume();
|
||||
Ok(Token::LSmallArrow)
|
||||
}
|
||||
_ => Err(Error::InvalidChar('-')),
|
||||
},
|
||||
|
||||
'/' => match self.consume() {
|
||||
Some('/') => loop {
|
||||
match self.consume() {
|
||||
Some('\n') => {
|
||||
self.backtrack();
|
||||
break Ok(Token::Comment(&self.input[start + 2..=self.position]));
|
||||
}
|
||||
None => {
|
||||
break Ok(Token::Comment(&self.input[start + 2..=self.position]));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
},
|
||||
Some('*') => loop {
|
||||
match self.consume() {
|
||||
Some('*') if self.peek() == Some('/') => {
|
||||
self.consume();
|
||||
break Ok(Token::Comment(&self.input[start + 2..self.position - 2]));
|
||||
}
|
||||
Some(_) => {}
|
||||
None => break Err(Error::UnclosedMultiLine),
|
||||
}
|
||||
},
|
||||
Some(_) => {
|
||||
self.backtrack();
|
||||
Err(Error::InvalidChar('/'))
|
||||
}
|
||||
None => Err(Error::InvalidChar('/')),
|
||||
},
|
||||
|
||||
c if begin_ident(c) => loop {
|
||||
match self.consume() {
|
||||
Some(c) if continue_ident(c) => {}
|
||||
Some(_) => {
|
||||
self.backtrack();
|
||||
break Ok(Token::Ident(&self.input[start..self.position]));
|
||||
}
|
||||
None => break Ok(Token::Ident(&self.input[start..self.position])),
|
||||
}
|
||||
},
|
||||
|
||||
c => Err(Error::InvalidChar(c)),
|
||||
};
|
||||
let span = Span(start, self.position);
|
||||
Some(Spanned(res, span))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokenizer() {
|
||||
let tests = [
|
||||
"",
|
||||
"/*",
|
||||
"/**",
|
||||
"/*/",
|
||||
"/**/",
|
||||
"/",
|
||||
"//",
|
||||
"()[]{}~=>==>->-+*&|, hello _th012is__ a wondweful",
|
||||
];
|
||||
|
||||
for test in tests {
|
||||
println!("'{test}': {:?}", Lexer::new(test).collect::<Vec<_>>())
|
||||
}
|
||||
}
|
||||
186
automata/src/loader/log.rs
Normal file
186
automata/src/loader/log.rs
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use crate::loader::Span;
|
||||
|
||||
pub struct Logs {
|
||||
logs: Vec<LogEntry>,
|
||||
has_error: bool,
|
||||
}
|
||||
|
||||
impl Logs {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
logs: Vec::new(),
|
||||
has_error: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains_errors(&self) -> bool {
|
||||
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>(
|
||||
&'a self,
|
||||
src: &'a str,
|
||||
) -> impl Iterator<Item = LogEntryDisplay<'a>> {
|
||||
self.logs.iter().map(|entry| LogEntryDisplay { src, entry })
|
||||
}
|
||||
|
||||
pub fn entries(&self) -> &[LogEntry] {
|
||||
&self.logs
|
||||
}
|
||||
|
||||
pub fn into_entries(self) -> impl Iterator<Item = LogEntry> {
|
||||
self.logs.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Logs {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum LogLevel {
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
|
||||
pub struct LogEntry {
|
||||
pub message: String,
|
||||
pub span: Option<Span>,
|
||||
pub level: LogLevel,
|
||||
}
|
||||
|
||||
pub struct LogEntryDisplay<'a> {
|
||||
src: &'a str,
|
||||
entry: &'a LogEntry,
|
||||
}
|
||||
|
||||
impl<'a> Display for LogEntryDisplay<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
pub const RESET: &str = "\x1b[0;22m";
|
||||
pub const BOLD: &str = "\x1b[1m";
|
||||
// pub const UNDERLINE: &str = "\x1b[4m";
|
||||
pub const RED: &str = "\x1b[31m";
|
||||
// pub const GREEN: &str = "\x1b[32m";
|
||||
pub const YELLOW: &str = "\x1b[33m";
|
||||
// pub const BLUE: &str = "\x1b[34m";
|
||||
pub const CYAN: &str = "\x1b[36m";
|
||||
|
||||
match self.entry.level {
|
||||
LogLevel::Info => write!(f, "{BOLD}{CYAN}info{RESET}{BOLD}: ")?,
|
||||
LogLevel::Warning => write!(f, "{BOLD}{YELLOW}warning{RESET}{BOLD}: ")?,
|
||||
LogLevel::Error => write!(f, "{BOLD}{RED}error{RESET}{BOLD}: ")?,
|
||||
}
|
||||
writeln!(f, "{}{RESET}", self.entry.message)?;
|
||||
|
||||
if let Some(span) = self.entry.span {
|
||||
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 padding = line_end.ilog10() as usize;
|
||||
|
||||
let start = self
|
||||
.src
|
||||
.get(..span.0)
|
||||
.and_then(|s| s.rfind('\n'))
|
||||
.map(|v| v + 1)
|
||||
.unwrap_or(0);
|
||||
|
||||
let end = if self.src.get(..span.1).unwrap_or("").ends_with("\n") {
|
||||
span.1
|
||||
} else {
|
||||
self.src
|
||||
.get(span.1..)
|
||||
.and_then(|s| s.find('\n'))
|
||||
.map(|v| v + span.1)
|
||||
.unwrap_or(self.src.len())
|
||||
};
|
||||
|
||||
let mut index = start;
|
||||
for (i, line) in self
|
||||
.src
|
||||
.get(start..end)
|
||||
.unwrap_or("")
|
||||
.split_inclusive("\n")
|
||||
.enumerate()
|
||||
{
|
||||
write!(f, "{BOLD}{CYAN}{:>padding$}: {RESET}", i + line_start)?;
|
||||
for char in line.chars() {
|
||||
if char == '\t' {
|
||||
write!(f, " ")?
|
||||
} else {
|
||||
write!(f, "{char}")?
|
||||
}
|
||||
}
|
||||
if !line.ends_with("\n") {
|
||||
writeln!(f)?;
|
||||
}
|
||||
write!(f, "{BOLD}{CYAN}")?;
|
||||
for _ in 0..padding + 3 {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
for char in line.chars() {
|
||||
if (span.0..span.1).contains(&index) {
|
||||
write!(f, "~")?;
|
||||
} else {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
index += char.len_utf8();
|
||||
}
|
||||
write!(f, "{RESET}")?;
|
||||
index += '\n'.len_utf8();
|
||||
writeln!(f)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
171
automata/src/loader/mod.rs
Normal file
171
automata/src/loader/mod.rs
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
use crate::{automatan::*, loader::ast::TopLevel};
|
||||
|
||||
pub mod ast;
|
||||
pub mod lexer;
|
||||
pub mod log;
|
||||
pub mod parser;
|
||||
|
||||
pub const EPSILON_LOWER: &str = "Ɛ";
|
||||
pub const EPSILON_LOWER_MATH: &str = "𝛆";
|
||||
pub const DELTA_LOWER: &str = "δ";
|
||||
pub const SIGMA_UPPER: &str = "Σ";
|
||||
pub const GAMMA_UPPER: &str = "Γ";
|
||||
pub const GAMMA_LOWER: &str = "γ";
|
||||
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
pub struct Span(
|
||||
#[cfg_attr(feature = "serde", serde(rename = "start"))] pub usize,
|
||||
#[cfg_attr(feature = "serde", serde(rename = "end"))] pub usize,
|
||||
);
|
||||
impl Span {
|
||||
pub fn join(&self, end: Span) -> Span {
|
||||
Span(self.0, end.1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
|
||||
pub struct Spanned<T>(pub T, pub Span);
|
||||
impl<T> Spanned<T> {
|
||||
pub fn map<R>(self, map: impl Fn(T) -> R) -> Spanned<R> {
|
||||
Spanned(map(self.0), self.1)
|
||||
}
|
||||
|
||||
pub fn as_ref(&self) -> Spanned<&T> {
|
||||
Spanned(&self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Context<'a> {
|
||||
logs: log::Logs,
|
||||
src: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Context<'a> {
|
||||
pub fn new(src: &'a str) -> Self {
|
||||
Self {
|
||||
logs: log::Logs::new(),
|
||||
src,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn src(&self) -> &'a str {
|
||||
self.src
|
||||
}
|
||||
|
||||
pub fn logs_display(&self) -> impl Iterator<Item = log::LogEntryDisplay<'_>> {
|
||||
self.logs.displayable_with(self.src)
|
||||
}
|
||||
|
||||
pub fn eof(&self) -> Span {
|
||||
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 {
|
||||
self.logs.contains_errors()
|
||||
}
|
||||
|
||||
pub fn into_logs(self) -> log::Logs {
|
||||
self.logs
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Machine<'a> {
|
||||
Fa(fa::Fa<'a>),
|
||||
Pda(pda::Pda<'a>),
|
||||
Tm(tm::Tm<'a>),
|
||||
}
|
||||
|
||||
pub fn parse_universal<'a>(ctx: &mut Context<'a>) -> Option<Machine<'a>> {
|
||||
let mut items = parser::Parser::new(ctx).collect::<Vec<_>>().into_iter();
|
||||
if ctx.logs.contains_errors() {
|
||||
return None;
|
||||
}
|
||||
|
||||
use Spanned as S;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Type {
|
||||
Dfa,
|
||||
Nfa,
|
||||
Dpda,
|
||||
Npda,
|
||||
Tm,
|
||||
Ntm,
|
||||
}
|
||||
|
||||
fn parse_type<'a>(item: Option<S<TopLevel<'a>>>, ctx: &mut Context<'a>) -> Option<Type> {
|
||||
let (str, span) = match item {
|
||||
Some(S(TopLevel::Item(S("type", _), item @ S(_, span)), _)) => {
|
||||
(item.expect_ident(ctx)?, span)
|
||||
}
|
||||
Some(S(_, span)) => {
|
||||
ctx.emit_error("expected type=<type> as first item", span);
|
||||
return None;
|
||||
}
|
||||
None => {
|
||||
ctx.emit_error("expected type=<type> as first item", ctx.eof());
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
Some(match str {
|
||||
"dfa" | "DFA" => Type::Dfa,
|
||||
"nfa" | "NFA" => Type::Nfa,
|
||||
"dpda" | "DPDA" => Type::Dpda,
|
||||
"npdaA" | "NPDA" => Type::Npda,
|
||||
"tm" | "TM" => Type::Tm,
|
||||
"ntm" | "NTM" => Type::Ntm,
|
||||
_ => {
|
||||
ctx.emit_error(
|
||||
"unknown type, expected 'DFA' | 'NFA' | 'DPDA' | 'NPDA' | 'TM' | 'NTM'",
|
||||
span,
|
||||
);
|
||||
return None;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const D: Options = Options {
|
||||
non_deterministic: false,
|
||||
epsilon_moves: false,
|
||||
};
|
||||
|
||||
const N: Options = Options {
|
||||
non_deterministic: true,
|
||||
epsilon_moves: true,
|
||||
};
|
||||
|
||||
Some(match parse_type(items.next(), ctx)? {
|
||||
Type::Dfa => Machine::Fa(fa::Fa::parse(items, ctx, D)?),
|
||||
Type::Nfa => Machine::Fa(fa::Fa::parse(items, ctx, N)?),
|
||||
Type::Dpda => Machine::Pda(pda::Pda::parse(items, ctx, D)?),
|
||||
Type::Npda => Machine::Pda(pda::Pda::parse(items, ctx, N)?),
|
||||
Type::Tm => Machine::Tm(tm::Tm::parse(items, ctx, D)?),
|
||||
Type::Ntm => Machine::Tm(tm::Tm::parse(items, ctx, N)?),
|
||||
})
|
||||
}
|
||||
362
automata/src/loader/parser.rs
Normal file
362
automata/src/loader/parser.rs
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
use crate::loader::{Context, Span};
|
||||
|
||||
use super::lexer::Token as T;
|
||||
use crate::loader::Spanned as S;
|
||||
|
||||
use super::ast::*;
|
||||
use super::lexer::Lexer;
|
||||
|
||||
pub struct Parser<'a, 'b> {
|
||||
lexer: Lexer<'a>,
|
||||
peek: Option<S<T<'a>>>,
|
||||
ctx: &'b mut Context<'a>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Iterator for Parser<'a, 'b> {
|
||||
type Item = S<TopLevel<'a>>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.next_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Parser<'a, 'b> {
|
||||
pub fn new(ctx: &'b mut Context<'a>) -> Self {
|
||||
Parser {
|
||||
lexer: Lexer::new(ctx.src()),
|
||||
ctx,
|
||||
peek: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_line(&mut self) {
|
||||
if self.expect_token(T::LineEnd).0 {
|
||||
self.peek = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn next_token_optional(&mut self) -> Option<S<T<'a>>> {
|
||||
match self.peek {
|
||||
Some(S(T::LineEnd, _)) => return self.peek,
|
||||
Some(_) => return self.peek.take(),
|
||||
_ => {}
|
||||
}
|
||||
loop {
|
||||
match self.lexer.next() {
|
||||
Some(S(Ok(T::Comment(_)), _)) => {}
|
||||
Some(S(Ok(T::LineEnd), span)) => {
|
||||
self.peek = Some(S(T::LineEnd, span));
|
||||
return self.peek;
|
||||
}
|
||||
Some(S(Ok(ok), r)) => return Some(S(ok, r)),
|
||||
Some(S(Err(err), span)) => self.ctx.emit_error(format!("lexer: {err:?}"), span),
|
||||
None => return None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn peek_token_optional(&mut self) -> Option<S<T<'a>>> {
|
||||
if self.peek.is_none() {
|
||||
self.peek = self.next_token_optional();
|
||||
}
|
||||
self.peek
|
||||
}
|
||||
|
||||
fn next_token(&mut self) -> S<T<'a>> {
|
||||
self.next_token_optional()
|
||||
.unwrap_or(S(T::LineEnd, self.ctx.eof()))
|
||||
}
|
||||
|
||||
fn peek_token(&mut self) -> S<T<'a>> {
|
||||
self.peek_token_optional()
|
||||
.unwrap_or(S(T::LineEnd, self.ctx.eof()))
|
||||
}
|
||||
|
||||
fn expect_token(&mut self, expected: T<'a>) -> (bool, Span) {
|
||||
match self.peek_token() {
|
||||
S(token, span) if token == expected => {
|
||||
self.next_token();
|
||||
(true, span)
|
||||
}
|
||||
S(token, span) => {
|
||||
self.ctx.emit_error(
|
||||
format!("unexpected {:#} expected {:}", token, expected),
|
||||
span,
|
||||
);
|
||||
(false, span)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_as_symbol(&mut self, tok: S<T<'a>>) -> S<Symbol<'a>> {
|
||||
match tok {
|
||||
S(T::Tilde, r) => S(Symbol::Epsilon, r),
|
||||
S(T::Ident("epsilon"), r) => S(Symbol::Epsilon, r),
|
||||
S(T::Ident(super::EPSILON_LOWER), r) => S(Symbol::Epsilon, r),
|
||||
S(T::Ident(ident), r) => S(Symbol::Ident(ident), r),
|
||||
S(got, span) => {
|
||||
self.ctx.emit_error(
|
||||
format!(
|
||||
"unexpected {:#} expected symbol ( {:} | {:} )",
|
||||
got,
|
||||
T::Tilde,
|
||||
T::Ident("")
|
||||
),
|
||||
span,
|
||||
);
|
||||
S(Symbol::Ident("<INVALID>"), span)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_symbol(&mut self) -> S<Symbol<'a>> {
|
||||
let next = self.next_token();
|
||||
self.parse_as_symbol(next)
|
||||
}
|
||||
|
||||
fn parse_tupple(&mut self) -> S<Tuple<'a>> {
|
||||
let mut items = Vec::new();
|
||||
let (matched, start) = self.expect_token(T::LPar);
|
||||
if !matched {
|
||||
return S(Tuple(Vec::new()), start);
|
||||
}
|
||||
|
||||
while !matches!(self.peek_token().0, T::RPar) {
|
||||
items.push(self.parse_item());
|
||||
if matches!(self.peek_token().0, T::Comma) {
|
||||
self.next_token();
|
||||
}
|
||||
if let S(T::LineEnd, span) = self.peek_token() {
|
||||
self.ctx
|
||||
.emit_error(format!("unexpected eol expected {:}", T::RPar), span);
|
||||
return S(Tuple(items), start.join(span));
|
||||
}
|
||||
}
|
||||
|
||||
let (_, end) = self.expect_token(T::RPar);
|
||||
|
||||
S(Tuple(items), start.join(end))
|
||||
}
|
||||
|
||||
fn parse_item(&mut self) -> S<Item<'a>> {
|
||||
match self.peek_token().0 {
|
||||
T::Ident(_) | T::Tilde => self.parse_symbol().map(Item::Symbol),
|
||||
T::LPar => self.parse_tupple().map(Item::Tuple),
|
||||
T::LBrace | T::LBracket => self.parse_list().map(Item::List),
|
||||
_ => {
|
||||
let S(got, span) = self.next_token();
|
||||
self.ctx.emit_error(
|
||||
format!(
|
||||
"unexpected {:#} expected item ( {:} | {:} | {:} | {:} | {:} )",
|
||||
got,
|
||||
T::Tilde,
|
||||
T::Ident(""),
|
||||
T::LPar,
|
||||
T::LBrace,
|
||||
T::LBracket,
|
||||
),
|
||||
span,
|
||||
);
|
||||
S(Item::Symbol(Symbol::Ident("<INVALID>")), span)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_list(&mut self) -> S<List<'a>> {
|
||||
let mut list = Vec::new();
|
||||
|
||||
let (start, match_end) = match self.next_token() {
|
||||
S(T::LBrace, span) => (span, T::RBrace),
|
||||
S(T::LBracket, span) => (span, T::RBracket),
|
||||
S(got, span) => {
|
||||
self.ctx.emit_error(
|
||||
format!(
|
||||
"unexpected {:#} expected list start ( {:} | {:} )",
|
||||
got,
|
||||
T::RBrace,
|
||||
T::RBracket
|
||||
),
|
||||
span,
|
||||
);
|
||||
return S(List(Vec::new(), ListKind::BracketComma), span);
|
||||
}
|
||||
};
|
||||
|
||||
let mut comma = false;
|
||||
while self.peek_token().0 != match_end {
|
||||
list.push(self.parse_item());
|
||||
|
||||
if list.len() != 1
|
||||
&& self.peek_token().0 != match_end
|
||||
&& !matches!(self.peek_token().0, T::LineEnd)
|
||||
&& matches!(self.peek_token().0, T::Comma) != comma
|
||||
{
|
||||
let span = self.peek_token().1;
|
||||
self.ctx.emit_warning(
|
||||
"inconsistent comma delimiting. use commas to delimit all or no items",
|
||||
span,
|
||||
);
|
||||
}
|
||||
if matches!(self.peek_token().0, T::Comma) {
|
||||
comma = true;
|
||||
self.next_token();
|
||||
}
|
||||
if let S(T::LineEnd, span) = self.peek_token() {
|
||||
self.ctx.emit_error(
|
||||
format!("unexpected eol expected list close ( {:} )", match_end),
|
||||
span,
|
||||
);
|
||||
return S(List(list, ListKind::BraceComma), start.join(span));
|
||||
}
|
||||
}
|
||||
let (_, end) = self.expect_token(match_end);
|
||||
let kind = match (comma, match_end) {
|
||||
(true, T::RBrace) => ListKind::BraceComma,
|
||||
(false, T::RBrace) => ListKind::Brace,
|
||||
(true, T::RBracket) => ListKind::BracketComma,
|
||||
(false, T::RBracket) => ListKind::Bracket,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
S(List(list, kind), start.join(end))
|
||||
}
|
||||
|
||||
fn parse_regex(&mut self) -> S<Regex<'a>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn parse_production_rule(&mut self, S(sym, start): S<Symbol<'a>>) -> Option<S<TopLevel<'a>>> {
|
||||
let mut lhs_group = ProductionGroup(vec![S(sym, start)]);
|
||||
let mut lhs_group_end = start;
|
||||
while !matches!(self.peek_token().0, T::LSmallArrow | T::LineEnd) {
|
||||
let sym = self.parse_symbol();
|
||||
lhs_group_end = sym.1;
|
||||
lhs_group.0.push(sym);
|
||||
}
|
||||
if !self.expect_token(T::LSmallArrow).0 {
|
||||
return Some(S(
|
||||
TopLevel::ProductionRule(
|
||||
S(lhs_group, start.join(lhs_group_end)),
|
||||
S(vec![], lhs_group_end),
|
||||
),
|
||||
start.join(lhs_group_end),
|
||||
));
|
||||
}
|
||||
|
||||
let mut groups = Vec::new();
|
||||
|
||||
loop {
|
||||
let mut group = ProductionGroup(vec![]);
|
||||
while !matches!(self.peek_token().0, T::LineEnd | T::Or) {
|
||||
group.0.push(self.parse_symbol());
|
||||
}
|
||||
|
||||
if group.0.is_empty() {
|
||||
let span = self.peek_token().1;
|
||||
self.ctx
|
||||
.emit_error("cannot have empty production group", span);
|
||||
}
|
||||
|
||||
let group_start = group.0.first().map(|g| g.1).unwrap_or(start);
|
||||
let group_end = group.0.last().map(|g| g.1).unwrap_or(start);
|
||||
groups.push(S(group, group_start.join(group_end)));
|
||||
|
||||
if matches!(self.peek_token().0, T::Or) {
|
||||
self.next_token();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if groups.is_empty() {
|
||||
self.ctx.emit_error(
|
||||
"cannot have empty production rule",
|
||||
start.join(lhs_group_end),
|
||||
);
|
||||
}
|
||||
|
||||
let rules_start = groups.first().map(|f| f.1).unwrap_or(start);
|
||||
let rules_end = groups.last().map(|f| f.1).unwrap_or(start);
|
||||
|
||||
Some(S(
|
||||
TopLevel::ProductionRule(
|
||||
S(lhs_group, start.join(lhs_group_end)),
|
||||
S(groups, rules_start.join(rules_end)),
|
||||
),
|
||||
start.join(rules_end),
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_transition_function(
|
||||
&mut self,
|
||||
ident: &'a str,
|
||||
start: Span,
|
||||
) -> Option<S<TopLevel<'a>>> {
|
||||
let tuple = self.parse_tupple();
|
||||
let span = start.join(tuple.1);
|
||||
let dest = S((S(ident, start), tuple), span);
|
||||
if !self.expect_token(T::Eq).0 {
|
||||
return None;
|
||||
}
|
||||
let item = self.parse_item();
|
||||
let span = start.join(item.1);
|
||||
Some(S(TopLevel::TransitionFunc(dest, item), span))
|
||||
}
|
||||
|
||||
pub fn next_element(&mut self) -> Option<S<TopLevel<'a>>> {
|
||||
let result = loop {
|
||||
let next = self.next_token_optional()?;
|
||||
match (next, self.peek_token()) {
|
||||
// empty
|
||||
(S(T::LineEnd, _), _) => self.advance_line(),
|
||||
// transition function
|
||||
(S(T::Ident(ident), start), S(T::LPar, _)) => {
|
||||
if let Some(tf) = self.parse_transition_function(ident, start) {
|
||||
break Some(tf);
|
||||
}
|
||||
}
|
||||
// item
|
||||
(S(T::Ident(ident), start), S(T::Eq, _)) => {
|
||||
let name = S(ident, start);
|
||||
if !self.expect_token(T::Eq).0 {
|
||||
continue;
|
||||
}
|
||||
let item = self.parse_item();
|
||||
let span = start.join(item.1);
|
||||
break Some(S(TopLevel::Item(name, item), span));
|
||||
}
|
||||
// production rule
|
||||
(
|
||||
sym @ S(T::Ident(_) | T::Tilde, _),
|
||||
S(T::LSmallArrow | T::Ident(_) | T::Tilde, _),
|
||||
) => {
|
||||
let sym = self.parse_as_symbol(sym);
|
||||
if let Some(pr) = self.parse_production_rule(sym) {
|
||||
break Some(pr);
|
||||
}
|
||||
}
|
||||
|
||||
(S(T::Ident(_), _), S(tok, span)) => {
|
||||
self.ctx.emit_error(
|
||||
format!(
|
||||
"unexpected {:#} expected {:} | {:}",
|
||||
tok,
|
||||
T::Eq,
|
||||
T::LSmallArrow
|
||||
),
|
||||
span,
|
||||
);
|
||||
while !matches!(self.next_token().0, T::LineEnd) {}
|
||||
}
|
||||
_ => {
|
||||
self.ctx.emit_error(
|
||||
format!("unexpected {:#} expected {:}", next.0, T::Ident("")),
|
||||
next.1,
|
||||
);
|
||||
while !matches!(self.next_token().0, T::LineEnd) {}
|
||||
}
|
||||
}
|
||||
};
|
||||
self.advance_line();
|
||||
result
|
||||
}
|
||||
}
|
||||
10
automata/src/sim/dfa.rs
Normal file
10
automata/src/sim/dfa.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// pub struct TransitionTable {
|
||||
// initial: State,
|
||||
// state_names: Vec<String>,
|
||||
// // transitions: Vec<SymbolMap<State>>,
|
||||
// final_states: Vec<bool>,
|
||||
// }
|
||||
|
||||
// pub struct DFA {
|
||||
// state: State,
|
||||
// }
|
||||
1
automata/src/sim/dpda.rs
Normal file
1
automata/src/sim/dpda.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
159
automata/src/sim/mod.rs
Normal file
159
automata/src/sim/mod.rs
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
pub mod dfa;
|
||||
pub mod dpda;
|
||||
pub mod nfa;
|
||||
pub mod npda;
|
||||
pub mod ntm;
|
||||
pub mod tm;
|
||||
|
||||
pub trait Get<Idx> {
|
||||
type Output;
|
||||
fn get(&self, idx: Idx) -> Option<&Self::Output>;
|
||||
fn get_mut(&mut self, idx: Idx) -> Option<&mut Self::Output>;
|
||||
}
|
||||
|
||||
pub trait GetDefault<Idx> {
|
||||
type Output: Default;
|
||||
fn get_or_insert_default(&mut self, idx: Idx) -> &Self::Output;
|
||||
fn get_mut_or_insert_default(&mut self, idx: Idx) -> &mut Self::Output;
|
||||
}
|
||||
|
||||
macro_rules! index {
|
||||
($ty: ident, $self:ident, $collection: expr, $index_calc: expr, $index: pat = $index_ty: ty $(, $default: expr)?) => {
|
||||
impl<T> Get<$index_ty> for $ty<T> {
|
||||
type Output = T;
|
||||
fn get(&$self, $index: $index_ty) -> Option<&T>{
|
||||
$collection.get($index_calc)
|
||||
}
|
||||
|
||||
fn get_mut(&mut $self, $index: $index_ty) -> Option<&mut T>{
|
||||
$collection.get_mut($index_calc)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Index<$index_ty> for $ty<T>{
|
||||
type Output = T;
|
||||
|
||||
fn index(& $self, $index: $index_ty) -> &T{
|
||||
$collection.get($index_calc).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::IndexMut<$index_ty> for $ty<T>{
|
||||
fn index_mut(&mut $self, $index: $index_ty) -> &mut T{
|
||||
$collection.get_mut($index_calc).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
impl<T: Default> GetDefault<$index_ty> for $ty<T> {
|
||||
type Output = T;
|
||||
fn get_or_insert_default(&mut $self, $index: $index_ty) -> &T{
|
||||
$default
|
||||
}
|
||||
|
||||
fn get_mut_or_insert_default(&mut $self, $index: $index_ty) -> &mut T{
|
||||
$default
|
||||
}
|
||||
}
|
||||
)?
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct State(u16);
|
||||
|
||||
#[derive(Clone, Debug, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct Symbol(u16);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StateMap<T>(Vec<T>);
|
||||
|
||||
index!(StateMap, self, self.0, index.0 as usize, index = State);
|
||||
|
||||
impl<T> StateMap<T>{
|
||||
pub fn entries(&self) -> impl Iterator<Item = (State, &T)>{
|
||||
self.0.iter().enumerate().map(|(i, v)|(State(i as u16), v))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SymbolMap<T>(Vec<T>);
|
||||
|
||||
impl<T> SymbolMap<T>{
|
||||
pub fn entries(&self) -> impl Iterator<Item = (Symbol, &T)>{
|
||||
self.0.iter().enumerate().map(|(i, v)|(Symbol(i as u16), v))
|
||||
}
|
||||
}
|
||||
|
||||
index!(SymbolMap, self, self.0, index.0 as usize, index = Symbol);
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct StateSymbolMap<T> {
|
||||
map: Vec<T>,
|
||||
max_state: u16,
|
||||
}
|
||||
|
||||
impl<T> StateSymbolMap<T>{
|
||||
pub fn entries(&self) -> impl Iterator<Item = ((State, Symbol), &T)>{
|
||||
self.map.iter().enumerate().map(|(i, v)|{
|
||||
let state = State((i % self.max_state as usize) as u16);
|
||||
let symbol = Symbol((i / self.max_state as usize) as u16);
|
||||
((state, symbol), v)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
index!(
|
||||
StateSymbolMap,
|
||||
self,
|
||||
self.map,
|
||||
state.0 as usize + self.max_state as usize * symbol.0 as usize,
|
||||
(state, symbol) = (State, Symbol)
|
||||
);
|
||||
index!(
|
||||
StateSymbolMap,
|
||||
self,
|
||||
self.map,
|
||||
state.0 as usize + self.max_state as usize * symbol.0 as usize,
|
||||
(symbol, state) = (Symbol, State)
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct CharMap<T>(HashMap<char, T>);
|
||||
|
||||
impl<T> CharMap<T>{
|
||||
pub fn entries(&self) -> impl Iterator<Item = (char, &T)>{
|
||||
self.0.iter().map(|(k, v)|(*k, v))
|
||||
}
|
||||
}
|
||||
|
||||
index!(
|
||||
CharMap,
|
||||
self,
|
||||
self.0,
|
||||
&char,
|
||||
char = char,
|
||||
self.0.entry(char).or_default()
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct CharEpsilonMap<T>(HashMap<Option<char>, T>);
|
||||
|
||||
impl<T> CharEpsilonMap<T>{
|
||||
pub fn entries(&self) -> impl Iterator<Item = (Option<char>, &T)>{
|
||||
self.0.iter().map(|(k, v)|(*k, v))
|
||||
}
|
||||
}
|
||||
|
||||
index!(
|
||||
CharEpsilonMap,
|
||||
self,
|
||||
self.0,
|
||||
&Some(char),
|
||||
char = char,
|
||||
self.0.entry(Some(char)).or_default()
|
||||
);
|
||||
index!(CharEpsilonMap, self, self.0, &char, char = Option<char>, self.0.entry(char).or_default());
|
||||
|
||||
1
automata/src/sim/nfa.rs
Normal file
1
automata/src/sim/nfa.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
516
automata/src/sim/npda.rs
Normal file
516
automata/src/sim/npda.rs
Normal file
|
|
@ -0,0 +1,516 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct To(State, Vec<Symbol>);
|
||||
|
||||
impl To{
|
||||
pub fn state(&self) -> State{
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn stack(&self) -> &[Symbol]{
|
||||
&self.1
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(unused)]
|
||||
pub struct Npda {
|
||||
initial_state: State,
|
||||
initial_stack: Symbol,
|
||||
state_names: StateMap<String>,
|
||||
symbol_names: SymbolMap<String>,
|
||||
alphabet: HashSet<char>,
|
||||
|
||||
final_states: Option<StateMap<bool>>,
|
||||
transitions: StateSymbolMap<CharEpsilonMap<Vec<To>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct StateTransition<T> {
|
||||
pub from: T,
|
||||
pub to: T,
|
||||
}
|
||||
|
||||
impl Npda {
|
||||
pub fn get_state_name(&self, state: State) -> Option<&str>{
|
||||
self.state_names.get(state).map(String::as_str)
|
||||
}
|
||||
|
||||
pub fn get_symbol_name(&self, symbol: Symbol) -> Option<&str>{
|
||||
self.symbol_names.get(symbol).map(String::as_str)
|
||||
}
|
||||
|
||||
pub fn initial_state(&self) -> State{
|
||||
self.initial_state
|
||||
}
|
||||
|
||||
pub fn initial_stack(&self) -> Symbol{
|
||||
self.initial_stack
|
||||
}
|
||||
|
||||
pub fn final_states(&self) -> Option<impl Iterator<Item = State>>{
|
||||
Some(self.final_states.as_ref()?.entries().filter(|&(_, f)| *f).map(|(s, _)| s))
|
||||
}
|
||||
|
||||
pub fn states(&self) -> impl Iterator<Item = (State, &str)>{
|
||||
self.state_names.entries().map(|s|(s.0, s.1.as_str()))
|
||||
}
|
||||
|
||||
pub fn symbols(&self) -> impl Iterator<Item = (Symbol, &str)>{
|
||||
self.symbol_names.entries().map(|s|(s.0, s.1.as_str()))
|
||||
}
|
||||
|
||||
pub fn transitions(&self) -> &StateSymbolMap<CharEpsilonMap<Vec<To>>>{
|
||||
&self.transitions
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct NpdaState {
|
||||
pub state: State,
|
||||
pub stack: Vec<Symbol>,
|
||||
pub position: usize,
|
||||
}
|
||||
|
||||
pub struct Simulator {
|
||||
input: String,
|
||||
machine: Npda,
|
||||
running: Vec<NpdaState>,
|
||||
}
|
||||
|
||||
pub enum SimulatorResult {
|
||||
Pending,
|
||||
Reject,
|
||||
Accept(NpdaState),
|
||||
}
|
||||
|
||||
impl Simulator {
|
||||
pub fn begin(input: impl Into<String>, machine: Npda) -> Self {
|
||||
Self {
|
||||
input: input.into(),
|
||||
running: vec![NpdaState {
|
||||
state: machine.initial_state,
|
||||
stack: vec![machine.initial_stack],
|
||||
position: 0,
|
||||
}],
|
||||
machine,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn step(&mut self) -> SimulatorResult {
|
||||
println!("step, ({}) paths", self.running.len());
|
||||
let mut new = Vec::new();
|
||||
for mut npda in self.running.drain(..) {
|
||||
let Some(top) = npda.stack.pop() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for to in self
|
||||
.machine
|
||||
.transitions
|
||||
.get((npda.state, top))
|
||||
.and_then(|t| t.get(None))
|
||||
.iter()
|
||||
.flat_map(|t| t.iter())
|
||||
{
|
||||
let mut stack = npda.stack.clone();
|
||||
stack.extend_from_slice(&to.1);
|
||||
new.push(NpdaState {
|
||||
state: to.0,
|
||||
stack,
|
||||
position: npda.position,
|
||||
});
|
||||
}
|
||||
|
||||
let Some(next) = self
|
||||
.input
|
||||
.get(npda.position..)
|
||||
.and_then(|c| c.chars().next())
|
||||
else {
|
||||
if let Some(final_states) = &self.machine.final_states
|
||||
&& final_states.get(npda.state).copied().unwrap_or_default()
|
||||
{
|
||||
return SimulatorResult::Accept(npda.clone());
|
||||
} else if npda.stack == [self.machine.initial_stack] {
|
||||
return SimulatorResult::Accept(npda.clone());
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
for to in self
|
||||
.machine
|
||||
.transitions
|
||||
.get((npda.state, top))
|
||||
.and_then(|t| t.get(Some(next)))
|
||||
.iter()
|
||||
.flat_map(|t| t.iter())
|
||||
{
|
||||
let mut stack = npda.stack.clone();
|
||||
stack.extend_from_slice(&to.1);
|
||||
new.push(NpdaState {
|
||||
state: to.0,
|
||||
stack,
|
||||
position: npda.position + next.len_utf8(),
|
||||
});
|
||||
}
|
||||
}
|
||||
self.running = new;
|
||||
if self.running.is_empty() {
|
||||
SimulatorResult::Reject
|
||||
} else {
|
||||
SimulatorResult::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------ parser/semantics
|
||||
|
||||
use crate::loader::{
|
||||
Context, DELTA_LOWER, GAMMA_UPPER, SIGMA_UPPER, Spanned,
|
||||
ast::{self, Symbol as Sym},
|
||||
};
|
||||
|
||||
impl Npda {
|
||||
pub fn load_from_ast<'a>(
|
||||
items: impl Iterator<Item = Spanned<ast::TopLevel<'a>>>,
|
||||
ctx: &mut Context<'a>,
|
||||
) -> Option<Npda> {
|
||||
let mut initial_state = None;
|
||||
let mut initial_stack = None;
|
||||
|
||||
let mut states = HashMap::new();
|
||||
let mut stack_symbols = HashMap::new();
|
||||
let mut alphabet = HashSet::new();
|
||||
let mut final_states = None;
|
||||
|
||||
let mut transitions_map = HashMap::new();
|
||||
|
||||
for Spanned(element, span) in items {
|
||||
use Spanned as S;
|
||||
use ast::TopLevel as TL;
|
||||
match element {
|
||||
TL::Item(S("Q", _), list) => {
|
||||
if !states.is_empty() {
|
||||
ctx.emit_error("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;
|
||||
};
|
||||
let state = match states.len().try_into() {
|
||||
Ok(ok) => State(ok),
|
||||
Err(_) => {
|
||||
ctx.emit_error("too many states defined", item.1);
|
||||
State(0)
|
||||
}
|
||||
};
|
||||
if let Some(old) = states.insert(ident, state) {
|
||||
ctx.emit_error("state redefined", item.1);
|
||||
states.insert(ident, old);
|
||||
}
|
||||
}
|
||||
|
||||
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(ident.chars().next().unwrap_or_default()) {
|
||||
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_some() {
|
||||
ctx.emit_error("final states already set", span);
|
||||
}
|
||||
let mut map = HashSet::new();
|
||||
let Some(list) = list.expect_set(ctx) else {
|
||||
continue;
|
||||
};
|
||||
for item in list {
|
||||
let Some(ident) = item.expect_ident(ctx) else {
|
||||
continue;
|
||||
};
|
||||
if let Some(state) = states.get(ident) {
|
||||
if !map.insert(*state) {
|
||||
ctx.emit_error("final state redefined", item.1);
|
||||
}
|
||||
} else {
|
||||
ctx.emit_error("final state not defined in set of states", item.1);
|
||||
}
|
||||
}
|
||||
final_states = Some(map);
|
||||
}
|
||||
TL::Item(S("T" | GAMMA_UPPER | "gamma", _), list) => {
|
||||
if !stack_symbols.is_empty() {
|
||||
ctx.emit_error("stack symbols 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;
|
||||
};
|
||||
let symbol = match stack_symbols.len().try_into() {
|
||||
Ok(ok) => Symbol(ok),
|
||||
Err(_) => {
|
||||
ctx.emit_error("too many stack symbols defined", item.1);
|
||||
Symbol(0)
|
||||
}
|
||||
};
|
||||
if let Some(old) = stack_symbols.insert(ident, symbol) {
|
||||
ctx.emit_error("stack symbol redefined", item.1);
|
||||
stack_symbols.insert(ident, old);
|
||||
}
|
||||
}
|
||||
|
||||
if list.is_empty() {
|
||||
ctx.emit_error("stack symbols cannot be empty", span);
|
||||
}
|
||||
}
|
||||
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 let Some(initial) = states.get(ident) {
|
||||
initial_state = Some(*initial)
|
||||
} else {
|
||||
ctx.emit_error("initial state symbol not defined as a state", src_d);
|
||||
}
|
||||
}
|
||||
_ => ctx.emit_error("expected ident", src_d),
|
||||
},
|
||||
TL::Item(S("S" | "z0", _), S(src, src_d)) => match src {
|
||||
ast::Item::Symbol(Sym::Ident(ident)) => {
|
||||
if initial_stack.is_some() {
|
||||
ctx.emit_error("initial stack already set", span);
|
||||
}
|
||||
if let Some(initial) = stack_symbols.get(ident) {
|
||||
initial_stack = Some(*initial)
|
||||
} else {
|
||||
ctx.emit_error(
|
||||
"initial stack symbol not defined as a stack symbol",
|
||||
src_d,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => ctx.emit_error("expected ident", src_d),
|
||||
},
|
||||
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);
|
||||
}
|
||||
|
||||
TL::TransitionFunc(S((S("d" | DELTA_LOWER | "delta", _), tuple), _), list) => {
|
||||
let list = list.set_weak();
|
||||
let Some((state, letter, stack_symbol)) =
|
||||
tuple.as_ref().expect_npda_transition_function(ctx)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(state) = states.get(state.0).copied() else {
|
||||
ctx.emit_error("transition state not defined as state", state.1);
|
||||
continue;
|
||||
};
|
||||
let Some(stack_symbol) = stack_symbols.get(stack_symbol.0).copied() else {
|
||||
ctx.emit_error(
|
||||
"transition stack symbol not defined as stack symbol",
|
||||
stack_symbol.1,
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
let char = match letter.0 {
|
||||
Sym::Epsilon => None,
|
||||
Sym::Ident(val) => {
|
||||
if let Some(char) = val.chars().next()
|
||||
&& val.chars().count() == 1
|
||||
{
|
||||
if !alphabet.contains(&char) {
|
||||
ctx.emit_error(
|
||||
"transition letter not defined in alphabet",
|
||||
letter.1,
|
||||
);
|
||||
}
|
||||
Some(char)
|
||||
} else {
|
||||
ctx.emit_error(
|
||||
"transition letter can only be single character",
|
||||
letter.1,
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for item in list {
|
||||
let Some((next_state, stack)) = item
|
||||
.expect_tuple(ctx)
|
||||
.and_then(|item| item.expect_npda_transition(ctx))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(next_state) = states.get(next_state.0).copied() else {
|
||||
ctx.emit_error("transition state not defined as state", next_state.1);
|
||||
continue;
|
||||
};
|
||||
|
||||
let stack: Vec<_> = stack
|
||||
.iter()
|
||||
.rev()
|
||||
.filter_map(|symbol| {
|
||||
if matches!(symbol.0, ast::Item::Symbol(Sym::Epsilon)) {
|
||||
return None;
|
||||
}
|
||||
let ident = symbol.expect_ident(ctx)?;
|
||||
|
||||
let Some(symbol) = stack_symbols.get(ident).copied() else {
|
||||
ctx.emit_error("transition stack symbol not defined", symbol.1);
|
||||
return None;
|
||||
};
|
||||
Some(symbol)
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !transitions_map
|
||||
.entry((state, char, stack_symbol))
|
||||
.or_insert(HashSet::new())
|
||||
.insert((next_state, stack))
|
||||
{
|
||||
ctx.emit_warning("duplicate transition", item.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
TL::TransitionFunc(S((S(name, _), _), dest_s), _) => {
|
||||
ctx.emit_error(
|
||||
format!(
|
||||
"unknown function {name:?}, expected 'd' | 'delta' | '{DELTA_LOWER}'"
|
||||
),
|
||||
dest_s,
|
||||
);
|
||||
}
|
||||
|
||||
TL::ProductionRule(_, _) => {
|
||||
ctx.emit_error("unexpected production rule", span);
|
||||
}
|
||||
TL::Table() => ctx.emit_error("unexpected table", span),
|
||||
}
|
||||
}
|
||||
|
||||
if stack_symbols.is_empty() {
|
||||
ctx.emit_error_locless("stack symbols never defined");
|
||||
}
|
||||
|
||||
if alphabet.is_empty() {
|
||||
ctx.emit_error_locless("alphabet never defined");
|
||||
}
|
||||
|
||||
if states.is_empty() {
|
||||
ctx.emit_error_locless("states never defined");
|
||||
}
|
||||
|
||||
let initial_stack = match initial_stack {
|
||||
Some(some) => some,
|
||||
None => {
|
||||
if let Some(initial) = stack_symbols.get("z0") {
|
||||
ctx.emit_warning_locless(
|
||||
"initial stack symbol not defined, defaulting to 'z0'",
|
||||
);
|
||||
*initial
|
||||
} else {
|
||||
ctx.emit_error_locless("initial stack symbol not defined");
|
||||
Symbol(0)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let initial_state = match initial_state {
|
||||
Some(some) => some,
|
||||
None => {
|
||||
if let Some(initial) = states.get("q0") {
|
||||
ctx.emit_warning_locless("initial state not defined, defaulting to 'q0'");
|
||||
*initial
|
||||
} else {
|
||||
ctx.emit_error_locless("initial state not defined");
|
||||
State(0)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let state_names = StateMap(states.iter().fold(
|
||||
vec![String::new(); states.len()],
|
||||
|mut a, (k, v)| {
|
||||
a[v.0 as usize] = k.to_string();
|
||||
a
|
||||
},
|
||||
));
|
||||
let symbol_names = SymbolMap(stack_symbols.iter().fold(
|
||||
vec![String::new(); stack_symbols.len()],
|
||||
|mut a, (k, v)| {
|
||||
a[v.0 as usize] = k.to_string();
|
||||
a
|
||||
},
|
||||
));
|
||||
|
||||
let final_states = final_states.map(|f| {
|
||||
StateMap(f.iter().fold(vec![false; states.len()], |mut a, k| {
|
||||
a[k.0 as usize] = true;
|
||||
a
|
||||
}))
|
||||
});
|
||||
|
||||
let mut transitions: StateSymbolMap<CharEpsilonMap<Vec<To>>> = StateSymbolMap {
|
||||
map: vec![CharEpsilonMap::default(); stack_symbols.len() * states.len()],
|
||||
max_state: states.len() as u16,
|
||||
};
|
||||
|
||||
for ((q, c, s), to) in transitions_map {
|
||||
let from = &mut transitions[(q, s)];
|
||||
for (n, ss) in to {
|
||||
from.get_mut_or_insert_default(c).push(To(n, ss));
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.contains_errors() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Npda {
|
||||
initial_state,
|
||||
initial_stack,
|
||||
state_names,
|
||||
symbol_names,
|
||||
alphabet,
|
||||
final_states,
|
||||
transitions,
|
||||
})
|
||||
}
|
||||
}
|
||||
1
automata/src/sim/ntm.rs
Normal file
1
automata/src/sim/ntm.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
1
automata/src/sim/tm.rs
Normal file
1
automata/src/sim/tm.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
Loading…
Add table
Add a link
Reference in a new issue