mirror of
https://github.com/ParkerTenBroeck/automata.git
synced 2026-06-07 05:28:45 -04:00
Merge branch 'main' into gh-pages
This commit is contained in:
commit
d91768a5e6
24 changed files with 1764 additions and 958 deletions
|
|
@ -3,11 +3,13 @@ use std::collections::HashSet;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
delta_lower, dual_struct_serde, epsilon, loader::{
|
delta_lower, dual_struct_serde, epsilon,
|
||||||
|
loader::{
|
||||||
Context, INITIAL_STATE, Spanned,
|
Context, INITIAL_STATE, Spanned,
|
||||||
ast::{self, Symbol as Sym, TopLevel},
|
ast::{self, Symbol as Sym, TopLevel},
|
||||||
log::LogSink,
|
log::LogSink,
|
||||||
}, sigma_upper
|
},
|
||||||
|
sigma_upper,
|
||||||
};
|
};
|
||||||
|
|
||||||
dual_struct_serde! {
|
dual_struct_serde! {
|
||||||
|
|
@ -104,6 +106,12 @@ impl<'a, 'b> FaCompiler<'a, 'b> {
|
||||||
self.compile_top_level(element, span);
|
self.compile_top_level(element, span);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.states_def.is_none() {
|
||||||
|
self.ctx
|
||||||
|
.emit_error_locless("states never defined")
|
||||||
|
.emit_help_logless("add: Q = {...}");
|
||||||
|
}
|
||||||
|
|
||||||
if self.alphabet_def.is_none() {
|
if self.alphabet_def.is_none() {
|
||||||
self.ctx
|
self.ctx
|
||||||
.emit_error_locless("alphabet never defined")
|
.emit_error_locless("alphabet never defined")
|
||||||
|
|
@ -111,12 +119,6 @@ impl<'a, 'b> FaCompiler<'a, 'b> {
|
||||||
.emit_info_logless(concat!("E can be ", sigma_upper!(str)));
|
.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() {
|
if self.final_states_def.is_none() {
|
||||||
self.ctx
|
self.ctx
|
||||||
.emit_error_locless("final states never defined")
|
.emit_error_locless("final states never defined")
|
||||||
|
|
@ -139,9 +141,12 @@ impl<'a, 'b> FaCompiler<'a, 'b> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.transitions.is_empty(){
|
if self.transitions.is_empty() {
|
||||||
self.ctx.emit_warning_locless("no transitions defined")
|
self.ctx
|
||||||
.emit_help_logless("consider defining one: d(state, letter|epsilon) = state | {state, state, ...}")
|
.emit_warning_locless("no transitions defined")
|
||||||
|
.emit_help_logless(
|
||||||
|
"consider defining one: d(state, letter|epsilon) = state | {state, ...}",
|
||||||
|
)
|
||||||
.emit_info_logless(concat!("d can be ", delta_lower!(str)))
|
.emit_info_logless(concat!("d can be ", delta_lower!(str)))
|
||||||
.emit_info_logless(concat!("epsilon can be ", epsilon!(str)));
|
.emit_info_logless(concat!("epsilon can be ", epsilon!(str)));
|
||||||
}
|
}
|
||||||
|
|
@ -168,11 +173,11 @@ impl<'a, 'b> FaCompiler<'a, 'b> {
|
||||||
TL::Item(S("F", _), list) => self.compile_final_states(list, span),
|
TL::Item(S("F", _), list) => self.compile_final_states(list, span),
|
||||||
TL::Item(S(INITIAL_STATE, _), item) => self.compile_initial_state(item, span),
|
TL::Item(S(INITIAL_STATE, _), item) => self.compile_initial_state(item, span),
|
||||||
TL::Item(S(name, dest_s), _) => {
|
TL::Item(S(name, dest_s), _) => {
|
||||||
self.ctx.emit_error(format!("unknown item {name:?}, expected states, alphabet, final states, initial state"), dest_s);
|
self.ctx.emit_error(format!("unknown item {name:?}, expected states | alphabet | final states | initial state"), dest_s);
|
||||||
}
|
}
|
||||||
|
|
||||||
TL::TransitionFunc(S((S(delta_lower!(pat), _), args), _), list) => {
|
TL::TransitionFunc(S((S(delta_lower!(pat), _), args), func), list) => {
|
||||||
self.compile_transition_function(args, list)
|
self.compile_transition_function(args, func, list)
|
||||||
}
|
}
|
||||||
TL::TransitionFunc(S((S(name, _), _), dest_s), _) => {
|
TL::TransitionFunc(S((S(name, _), _), dest_s), _) => {
|
||||||
self.ctx.emit_error(
|
self.ctx.emit_error(
|
||||||
|
|
@ -308,6 +313,7 @@ impl<'a, 'b> FaCompiler<'a, 'b> {
|
||||||
fn compile_transition_function(
|
fn compile_transition_function(
|
||||||
&mut self,
|
&mut self,
|
||||||
args: Spanned<ast::Tuple<'a>>,
|
args: Spanned<ast::Tuple<'a>>,
|
||||||
|
function: Span,
|
||||||
list: Spanned<ast::Item<'a>>,
|
list: Spanned<ast::Item<'a>>,
|
||||||
) {
|
) {
|
||||||
let list = list.set_weak();
|
let list = list.set_weak();
|
||||||
|
|
@ -363,8 +369,7 @@ impl<'a, 'b> FaCompiler<'a, 'b> {
|
||||||
}
|
}
|
||||||
if let Some(previous) = entry.replace(TransitionTo {
|
if let Some(previous) = entry.replace(TransitionTo {
|
||||||
state: State(next_state.0),
|
state: State(next_state.0),
|
||||||
|
function,
|
||||||
function: args.1,
|
|
||||||
transition: item.1,
|
transition: item.1,
|
||||||
}) {
|
}) {
|
||||||
self.ctx
|
self.ctx
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ pub mod fa;
|
||||||
pub mod pda;
|
pub mod pda;
|
||||||
pub mod tm;
|
pub mod tm;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
pub non_deterministic: bool,
|
pub non_deterministic: bool,
|
||||||
|
|
@ -14,15 +13,27 @@ 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::Deserialize), 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::Deserialize), 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::Deserialize), 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)]
|
||||||
|
|
|
||||||
|
|
@ -2,358 +2,572 @@ use std::collections::HashSet;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::{delta_lower, gamma_upper, loader::{
|
use crate::{
|
||||||
Context, INITIAL_STACK, INITIAL_STATE, Spanned, ast::{self, Symbol as Sym}, log::LogSink
|
delta_lower, dual_struct_serde, epsilon, gamma_upper,
|
||||||
}, sigma_upper};
|
loader::{
|
||||||
|
Context, INITIAL_STACK, INITIAL_STATE, Spanned,
|
||||||
|
ast::{self, Symbol as Sym},
|
||||||
|
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, Hash)]
|
||||||
pub struct TransitionFrom<'a> {
|
pub struct TransitionFrom<'a> {
|
||||||
pub state: State<'a>,
|
#[serde(borrow)]
|
||||||
pub letter: Option<Letter<'a>>,
|
pub state: State<'a>,
|
||||||
pub symbol: Symbol<'a>,
|
#[serde(borrow)]
|
||||||
|
pub letter: Option<Letter<'a>>,
|
||||||
|
#[serde(borrow)]
|
||||||
|
pub symbol: Symbol<'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> {
|
||||||
pub state: State<'a>,
|
#[serde(borrow)]
|
||||||
pub stack: Vec<Symbol<'a>>,
|
pub state: State<'a>,
|
||||||
|
#[serde(borrow)]
|
||||||
|
pub stack: Vec<Symbol<'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 Pda<'a> {
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
#[serde(borrow)]
|
||||||
pub struct Pda<'a> {
|
pub initial_state: State<'a>,
|
||||||
pub initial_state: State<'a>,
|
#[serde(borrow)]
|
||||||
pub initial_stack: Symbol<'a>,
|
pub initial_stack: Symbol<'a>,
|
||||||
pub states: HashMap<State<'a>, StateInfo>,
|
#[serde(borrow)]
|
||||||
pub symbols: HashMap<Symbol<'a>, SymbolInfo>,
|
pub states: HashMap<State<'a>, StateInfo>,
|
||||||
pub alphabet: HashMap<Letter<'a>, LetterInfo>,
|
#[serde(borrow)]
|
||||||
|
pub symbols: HashMap<Symbol<'a>, SymbolInfo>,
|
||||||
|
#[serde(borrow)]
|
||||||
|
pub alphabet: HashMap<Letter<'a>, LetterInfo>,
|
||||||
|
|
||||||
pub final_states: Option<HashMap<State<'a>, StateInfo>>,
|
#[serde(borrow)]
|
||||||
|
pub final_states: Option<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"))]
|
#[derive(Clone, Copy)]
|
||||||
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
|
enum AcceptBy {
|
||||||
|
EmptyStack,
|
||||||
|
FinalState,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PdaCompiler<'a, 'b> {
|
||||||
|
ctx: &'b mut Context<'a>,
|
||||||
|
options: Options,
|
||||||
|
|
||||||
|
initial_state: Option<(State<'a>, Span)>,
|
||||||
|
initial_stack: Option<(Symbol<'a>, Span)>,
|
||||||
|
accept_by: Option<(AcceptBy, Span)>,
|
||||||
|
|
||||||
|
states: HashMap<State<'a>, StateInfo>,
|
||||||
|
states_def: Option<Span>,
|
||||||
|
|
||||||
|
symbols: HashMap<Symbol<'a>, SymbolInfo>,
|
||||||
|
symbols_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> Pda<'a> {
|
impl<'a> Pda<'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<Pda<'a>> {
|
) -> Option<Pda<'a>> {
|
||||||
let mut initial_state = None;
|
PdaCompiler::new(ctx, options).compile(items)
|
||||||
let mut initial_stack = None;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut states = HashMap::new();
|
macro_rules! accept_empty {
|
||||||
let mut symbols = HashMap::new();
|
($ident: ident) => {
|
||||||
let mut alphabet = HashMap::new();
|
$crate::maker!($ident: "N","n","null","empty","E","Z0","z0")
|
||||||
let mut final_states = None;
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let mut transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>> =
|
macro_rules! accept_final {
|
||||||
HashMap::new();
|
($ident: ident) => {
|
||||||
|
$crate::maker!($ident: "F","final")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> PdaCompiler<'a, 'b> {
|
||||||
|
pub fn new(ctx: &'b mut Context<'a>, options: Options) -> Self {
|
||||||
|
Self {
|
||||||
|
ctx,
|
||||||
|
options,
|
||||||
|
|
||||||
|
initial_state: Default::default(),
|
||||||
|
initial_stack: Default::default(),
|
||||||
|
accept_by: Default::default(),
|
||||||
|
states: Default::default(),
|
||||||
|
states_def: Default::default(),
|
||||||
|
symbols: Default::default(),
|
||||||
|
symbols_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<Pda<'a>> {
|
||||||
for Spanned(element, span) in items {
|
for Spanned(element, span) in items {
|
||||||
use Spanned as S;
|
self.compile_top_level(element, span);
|
||||||
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() {
|
if self.states_def.is_none() {
|
||||||
ctx.emit_error("states cannot be empty", span);
|
self.ctx
|
||||||
}
|
.emit_error_locless("states never defined")
|
||||||
}
|
.emit_help_logless("add: Q = {...}");
|
||||||
TL::Item(S(sigma_upper!(pat), _), 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 {
|
if self.alphabet_def.is_none() {
|
||||||
ctx.emit_error("letter cannot be longer than one char", item.1);
|
self.ctx
|
||||||
}
|
.emit_error_locless("alphabet never defined")
|
||||||
|
.emit_help_logless("add: E = {...}")
|
||||||
|
.emit_info_logless(concat!("E can be ", sigma_upper!(str)));
|
||||||
|
}
|
||||||
|
|
||||||
if alphabet
|
if self.symbols_def.is_none() {
|
||||||
.insert(Letter(ident), LetterInfo { definition: item.1 })
|
self.ctx
|
||||||
.is_some()
|
.emit_error_locless("stack symbols never defined")
|
||||||
{
|
.emit_help_logless("add: G = {...}")
|
||||||
ctx.emit_error("letter redefined", item.1);
|
.emit_info_logless(concat!("G can be ", gamma_upper!(str)));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
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_some()
|
|
||||||
{
|
|
||||||
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(gamma_upper!(pat), _), 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
|
if self.accept_by.is_none() {
|
||||||
.insert(Symbol(ident), SymbolInfo { definition: item.1 })
|
self.ctx
|
||||||
.is_some()
|
.emit_error_locless("accept by never defined")
|
||||||
{
|
.emit_help_logless("add: accept = N|F")
|
||||||
ctx.emit_error("stack symbol redefined", item.1);
|
.emit_info_logless(concat!(
|
||||||
}
|
"accept by empty stack N can be ",
|
||||||
}
|
accept_empty!(str)
|
||||||
|
))
|
||||||
|
.emit_info_logless(concat!(
|
||||||
|
"accept by final state F can be ",
|
||||||
|
accept_final!(str)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
if list.is_empty() {
|
if self.final_states_def.is_none()
|
||||||
ctx.emit_error("stack symbols cannot be empty", span);
|
&& matches!(self.accept_by, Some((AcceptBy::FinalState, _)))
|
||||||
}
|
{
|
||||||
}
|
self.ctx
|
||||||
TL::Item(S(INITIAL_STATE, _), S(src, src_d)) => match src {
|
.emit_error_locless("final states never defined")
|
||||||
ast::Item::Symbol(Sym::Ident(ident)) => {
|
.emit_help_logless("add: F = {...}");
|
||||||
if initial_state.is_some() {
|
}else if let (Some((AcceptBy::EmptyStack, empty)), Some(states)) = (self.accept_by, self.final_states_def){
|
||||||
ctx.emit_error("initial state already set", span);
|
self.ctx
|
||||||
}
|
.emit_error_locless("final states defined alongside accept by empty stack")
|
||||||
if states.contains_key(&State(ident)) {
|
.emit_help("either remote to accept by empty stack", states)
|
||||||
initial_state = Some(State(ident))
|
.emit_help("or remote to accept by final state", empty);
|
||||||
} else {
|
|
||||||
ctx.emit_error("initial state symbol not defined as a state", src_d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => _ = ctx.emit_error("expected ident", src_d),
|
|
||||||
},
|
|
||||||
TL::Item(S(INITIAL_STACK, _), 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 states, alphabet, symbols, final states, initial state, initial stack"), dest_s);
|
|
||||||
}
|
|
||||||
|
|
||||||
TL::TransitionFunc(S((S(delta_lower!(pat), _), 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 transition function ( {} )", delta_lower!(str)
|
|
||||||
),
|
|
||||||
dest_s,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TL::ProductionRule(_, _) => {
|
|
||||||
ctx.emit_error("unexpected production rule", span);
|
|
||||||
}
|
|
||||||
TL::Table() => _ = ctx.emit_error("unexpected table", span),
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if symbols.is_empty() {
|
let initial_state = match self.initial_state {
|
||||||
ctx.emit_error_locless("stack symbols never defined");
|
Some(some) => some.0,
|
||||||
}
|
|
||||||
|
|
||||||
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 => {
|
None => {
|
||||||
if symbols.contains_key(&Symbol("Z0")) {
|
if self.states.contains_key(&State("q0")) {
|
||||||
ctx.emit_warning_locless(
|
self.ctx
|
||||||
"initial stack symbol not defined, defaulting to 'Z0'",
|
.emit_warning_locless("initial state not defined, defaulting to 'q0'")
|
||||||
);
|
.emit_help_logless(format!("add: {INITIAL_STATE} = q0"));
|
||||||
} else {
|
} else {
|
||||||
ctx.emit_error_locless("initial stack symbol not defined");
|
self.ctx
|
||||||
}
|
.emit_error_locless("initial state not defined")
|
||||||
Symbol("Z0")
|
.emit_help_logless(format!("add: {INITIAL_STATE} = ..."));
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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")
|
State("q0")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if ctx.contains_errors() {
|
let initial_stack = match self.initial_stack {
|
||||||
|
Some(some) => some.0,
|
||||||
|
None => {
|
||||||
|
if self.symbols.contains_key(&Symbol("Z0")) {
|
||||||
|
self.ctx
|
||||||
|
.emit_warning_locless(
|
||||||
|
"initial stack symbol not defined, defaulting to 'Z0'",
|
||||||
|
)
|
||||||
|
.emit_help_logless(format!("add: {INITIAL_STACK} = Z0"));
|
||||||
|
} else {
|
||||||
|
self.ctx
|
||||||
|
.emit_error_locless("initial stack symbol not defined")
|
||||||
|
.emit_help_logless(format!("add: {INITIAL_STACK} = ..."));
|
||||||
|
}
|
||||||
|
Symbol("Z0")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.transitions.is_empty() {
|
||||||
|
self.ctx
|
||||||
|
.emit_warning_locless("no transitions defined")
|
||||||
|
.emit_help_logless(
|
||||||
|
"consider defining one: d(state, letter|epsilon, symbol) = (state, [symbol]) | {(state, [symbol]), ...}",
|
||||||
|
)
|
||||||
|
.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;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let final_states =
|
||||||
|
matches!(self.accept_by, Some((AcceptBy::FinalState, _))).then_some(self.final_states);
|
||||||
|
|
||||||
Some(Pda {
|
Some(Pda {
|
||||||
initial_state,
|
initial_state,
|
||||||
initial_stack,
|
initial_stack,
|
||||||
states,
|
states: self.states,
|
||||||
symbols,
|
symbols: self.symbols,
|
||||||
alphabet,
|
alphabet: self.alphabet,
|
||||||
final_states,
|
final_states,
|
||||||
transitions,
|
transitions: self.transitions,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compile_top_level(&mut self, element: ast::TopLevel<'a>, span: Span) {
|
||||||
|
use Spanned as S;
|
||||||
|
use ast::TopLevel as TL;
|
||||||
|
match element {
|
||||||
|
TL::Item(S("accept", _), item) => self.compile_accept_by(item, span),
|
||||||
|
TL::Item(S("Q", _), list) => self.compile_states(list, span),
|
||||||
|
TL::Item(S(gamma_upper!(pat), _), list) => self.compile_symbols(list, span),
|
||||||
|
TL::Item(S(sigma_upper!(pat), _), list) => self.compile_alphabet(list, span),
|
||||||
|
TL::Item(S("F", _), list) => self.compile_final_states(list, span),
|
||||||
|
TL::Item(S(INITIAL_STATE, _), item) => self.compile_initial_state(item, span),
|
||||||
|
TL::Item(S(INITIAL_STACK, _), item) => self.compile_initial_stack(item, span),
|
||||||
|
TL::Item(S(name, dest_s), _) => {
|
||||||
|
self.ctx.emit_error(format!("unknown item {name:?}, expected states | stack symbols | alphabet | accept by | final states | initial state | initial stack"), dest_s);
|
||||||
|
}
|
||||||
|
|
||||||
|
TL::TransitionFunc(S((S(delta_lower!(pat), _), args), func), list) => {
|
||||||
|
self.compile_transition_function(args, func, list)
|
||||||
|
}
|
||||||
|
TL::TransitionFunc(S((S(name, _), _), dest_s), _) => {
|
||||||
|
self.ctx.emit_error(
|
||||||
|
format!(
|
||||||
|
"unknown function {name:?}, expected transition function ( {} )",
|
||||||
|
delta_lower!(str)
|
||||||
|
),
|
||||||
|
dest_s,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TL::ProductionRule(_, _) => {
|
||||||
|
self.ctx.emit_error("unexpected production rule", span);
|
||||||
|
}
|
||||||
|
TL::Table() => _ = self.ctx.emit_error("unexpected table", span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_accept_by(&mut self, item: Spanned<ast::Item<'a>>, top_level: Span) {
|
||||||
|
if let Some((_, previous)) = self.accept_by {
|
||||||
|
self.ctx
|
||||||
|
.emit_error("accept by already set", top_level)
|
||||||
|
.emit_info("previously defined here", previous);
|
||||||
|
}
|
||||||
|
let Some(by) = item.expect_ident(self.ctx) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let by = match by {
|
||||||
|
accept_empty!(pat) => AcceptBy::EmptyStack,
|
||||||
|
accept_final!(pat) => AcceptBy::FinalState,
|
||||||
|
_ => {
|
||||||
|
self.ctx.emit_error("invalid accept by", item.1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.accept_by = Some((by, top_level));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_states(&mut self, list: Spanned<ast::Item<'a>>, top_level: Span) {
|
||||||
|
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 list.is_empty() {
|
||||||
|
self.ctx.emit_error("states cannot be empty", top_level);
|
||||||
|
}
|
||||||
|
self.states_def = Some(top_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_symbols(&mut self, list: Spanned<ast::Item<'a>>, top_level: Span) {
|
||||||
|
if let Some(previous) = self.symbols_def {
|
||||||
|
self.ctx
|
||||||
|
.emit_error("stack symbols 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
|
||||||
|
.symbols
|
||||||
|
.insert(Symbol(ident), SymbolInfo { definition: item.1 })
|
||||||
|
{
|
||||||
|
self.ctx
|
||||||
|
.emit_error("stack symbol redefined", item.1)
|
||||||
|
.emit_info("previously defined here", previous.definition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if list.is_empty() {
|
||||||
|
self.ctx.emit_error("states cannot be empty", top_level);
|
||||||
|
}
|
||||||
|
self.symbols_def = Some(top_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_alphabet(&mut self, list: Spanned<ast::Item<'a>>, top_level: Span) {
|
||||||
|
if let Some(previous) = self.alphabet_def {
|
||||||
|
self.ctx
|
||||||
|
.emit_error("alphabet 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 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 {
|
||||||
|
self.ctx
|
||||||
|
.emit_error("final state not defined in set of states", item.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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_initial_stack(
|
||||||
|
&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_stack {
|
||||||
|
self.ctx
|
||||||
|
.emit_error("initial stack symbol already set", top_level)
|
||||||
|
.emit_help("previously defined here", previous);
|
||||||
|
}
|
||||||
|
if self.symbols.contains_key(&Symbol(ident)) {
|
||||||
|
self.initial_stack = Some((Symbol(ident), top_level))
|
||||||
|
} else {
|
||||||
|
self.ctx
|
||||||
|
.emit_error("initial stack 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>>,
|
||||||
|
function: Span,
|
||||||
|
list: Spanned<ast::Item<'a>>,
|
||||||
|
) {
|
||||||
|
let list = list.set_weak();
|
||||||
|
let Some((state, letter, stack_symbol)) =
|
||||||
|
args.as_ref().expect_pda_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;
|
||||||
|
};
|
||||||
|
if !self.symbols.contains_key(&Symbol(stack_symbol.0)) {
|
||||||
|
self.ctx.emit_error(
|
||||||
|
"transition stack symbol not defined as stack symbol",
|
||||||
|
stack_symbol.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))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for item in list {
|
||||||
|
let Some((next_state, stack)) = item
|
||||||
|
.expect_tuple(self.ctx)
|
||||||
|
.and_then(|item| item.expect_pda_transition(self.ctx))
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !self.states.contains_key(&State(next_state.0)) {
|
||||||
|
self.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(self.ctx)?;
|
||||||
|
|
||||||
|
if !self.symbols.contains_key(&Symbol(ident)) {
|
||||||
|
self.ctx
|
||||||
|
.emit_error("transition stack symbol not defined", symbol.1);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(Symbol(ident))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let entry: &mut _ = self
|
||||||
|
.transitions
|
||||||
|
.entry(TransitionFrom {
|
||||||
|
letter,
|
||||||
|
state: State(state.0),
|
||||||
|
symbol: Symbol(stack_symbol.0),
|
||||||
|
})
|
||||||
|
.or_default();
|
||||||
|
if !entry.is_empty() && !self.options.non_deterministic {
|
||||||
|
self.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,
|
||||||
|
transition: item.1,
|
||||||
|
}) {
|
||||||
|
self.ctx.emit_warning("duplicate transition", item.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Spanned<&'b ast::Tuple<'a>> {
|
impl<'a, 'b> Spanned<&'b ast::Tuple<'a>> {
|
||||||
|
|
@ -373,10 +587,12 @@ impl<'a, 'b> Spanned<&'b ast::Tuple<'a>> {
|
||||||
Spanned(symbol, *symbol_span),
|
Spanned(symbol, *symbol_span),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
_ => _ = ctx.emit_error(
|
_ => {
|
||||||
"expected PDA transition function (state, letter|epsilon, symbol)",
|
_ = ctx.emit_error(
|
||||||
self.1,
|
"expected PDA transition function (state, letter|epsilon, symbol)",
|
||||||
),
|
self.1,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,292 +2,412 @@ use std::collections::HashSet;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::{delta_lower, gamma_upper, loader::{
|
use crate::{
|
||||||
BLANK_SYMBOL, Context, Spanned, ast::{self, Symbol as Sym}, log::LogSink
|
delta_lower, dual_struct_serde, gamma_upper, loader::{
|
||||||
}};
|
BLANK_SYMBOL, Context, INITIAL_STATE, Spanned,
|
||||||
|
ast::{self, Symbol as Sym},
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
log::LogSink,
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
}
|
||||||
pub struct TransitionFrom<'a> {
|
};
|
||||||
pub state: State<'a>,
|
dual_struct_serde! {
|
||||||
pub symbol: Symbol<'a>,
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||||
|
pub struct TransitionFrom<'a> {
|
||||||
|
#[serde(borrow)]
|
||||||
|
pub state: State<'a>,
|
||||||
|
#[serde(borrow)]
|
||||||
|
pub symbol: Symbol<'a>,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
|
#[serde(rename = "<")]
|
||||||
Left,
|
Left,
|
||||||
|
#[serde(rename = ">")]
|
||||||
Right,
|
Right,
|
||||||
|
#[serde(rename = "_")]
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
dual_struct_serde! {
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||||
pub struct TransitionTo<'a> {
|
pub struct TransitionTo<'a> {
|
||||||
pub state: State<'a>,
|
#[serde(borrow)]
|
||||||
pub symbol: Symbol<'a>,
|
pub state: State<'a>,
|
||||||
pub direction: Direction,
|
#[serde(borrow)]
|
||||||
|
pub symbol: Symbol<'a>,
|
||||||
|
pub direction: Direction,
|
||||||
|
|
||||||
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 Tm<'a> {
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
#[serde(borrow)]
|
||||||
pub struct Tm<'a> {
|
pub initial_state: State<'a>,
|
||||||
pub initial_state: State<'a>,
|
#[serde(borrow)]
|
||||||
pub initial_tape: Symbol<'a>,
|
pub blank_symbol: Symbol<'a>,
|
||||||
pub states: HashMap<State<'a>, StateInfo>,
|
#[serde(borrow)]
|
||||||
pub symbols: HashMap<Symbol<'a>, SymbolInfo>,
|
pub states: HashMap<State<'a>, StateInfo>,
|
||||||
|
#[serde(borrow)]
|
||||||
|
pub symbols: HashMap<Symbol<'a>, SymbolInfo>,
|
||||||
|
|
||||||
pub final_states: HashMap<State<'a>, StateInfo>,
|
#[serde(borrow)]
|
||||||
|
pub final_states: HashMap<State<'a>, StateInfo>,
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
#[serde_as(as = "serde_with::Seq<(_, _)>")]
|
#[serde(borrow)]
|
||||||
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
|
#[serde_as(as = "serde_with::Seq<(_, _)>")]
|
||||||
#[cfg(not(feature = "serde"))]
|
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
|
||||||
pub transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Tm<'a> {
|
impl<'a> Tm<'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<Tm<'a>> {
|
) -> Option<Tm<'a>> {
|
||||||
let mut initial_state = None;
|
TmCompiler::new(ctx, options).compile(items)
|
||||||
let mut initial_tape = None;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut states = HashMap::new();
|
pub struct TmCompiler<'a, 'b> {
|
||||||
let mut symbols = HashMap::new();
|
ctx: &'b mut Context<'a>,
|
||||||
let mut final_states = HashMap::new();
|
options: Options,
|
||||||
|
|
||||||
let mut transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>> =
|
initial_state: Option<(State<'a>, Span)>,
|
||||||
HashMap::new();
|
blank_symbol: Option<(Symbol<'a>, Span)>,
|
||||||
|
|
||||||
|
states: HashMap<State<'a>, StateInfo>,
|
||||||
|
states_def: Option<Span>,
|
||||||
|
|
||||||
|
symbols: HashMap<Symbol<'a>, SymbolInfo>,
|
||||||
|
symbols_def: Option<Span>,
|
||||||
|
|
||||||
|
final_states: HashMap<State<'a>, StateInfo>,
|
||||||
|
final_states_def: Option<Span>,
|
||||||
|
|
||||||
|
transitions: HashMap<TransitionFrom<'a>, HashSet<TransitionTo<'a>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> TmCompiler<'a, 'b> {
|
||||||
|
pub fn new(ctx: &'b mut Context<'a>, options: Options) -> Self {
|
||||||
|
Self {
|
||||||
|
ctx,
|
||||||
|
options,
|
||||||
|
|
||||||
|
initial_state: Default::default(),
|
||||||
|
blank_symbol: Default::default(),
|
||||||
|
states: Default::default(),
|
||||||
|
states_def: Default::default(),
|
||||||
|
symbols: Default::default(),
|
||||||
|
symbols_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<Tm<'a>> {
|
||||||
for Spanned(element, span) in items {
|
for Spanned(element, span) in items {
|
||||||
use Spanned as S;
|
self.compile_top_level(element, span);
|
||||||
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(gamma_upper!(pat), _), 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("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(BLANK_SYMBOL, _), 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 states, symbols, final states, initial state, blank symbol"), dest_s);
|
|
||||||
}
|
|
||||||
|
|
||||||
TL::TransitionFunc(S((S(delta_lower!(pat), _), 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 transition function ( {} )", delta_lower!(str)
|
|
||||||
),
|
|
||||||
dest_s,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TL::ProductionRule(_, _) => {
|
|
||||||
ctx.emit_error("unexpected production rule", span);
|
|
||||||
}
|
|
||||||
TL::Table() => _ = ctx.emit_error("unexpected table", span),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if symbols.is_empty() {
|
if self.final_states_def.is_none() {
|
||||||
ctx.emit_error_locless("tape symbols never defined");
|
self.ctx
|
||||||
|
.emit_error_locless("final states never defined")
|
||||||
|
.emit_help_logless("add: F = {...}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if states.is_empty() {
|
let initial_state = match self.initial_state {
|
||||||
ctx.emit_error_locless("states never defined");
|
Some(some) => some.0,
|
||||||
}
|
|
||||||
|
|
||||||
let initial_tape = match initial_tape {
|
|
||||||
Some(some) => some,
|
|
||||||
None => {
|
None => {
|
||||||
if symbols.contains_key(&Symbol("z0")) {
|
if self.states.contains_key(&State("q0")) {
|
||||||
ctx.emit_warning_locless("initial tape symbol not defined, defaulting to 'z0'");
|
self.ctx
|
||||||
|
.emit_warning_locless("initial state not defined, defaulting to 'q0'")
|
||||||
|
.emit_help_logless(format!("add: {INITIAL_STATE} = q0"));
|
||||||
} else {
|
} else {
|
||||||
ctx.emit_error_locless("initial tape symbol not defined");
|
self.ctx
|
||||||
}
|
.emit_error_locless("initial state not defined")
|
||||||
Symbol("z0")
|
.emit_help_logless(format!("add: {BLANK_SYMBOL} = ..."));
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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")
|
State("q0")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if ctx.contains_errors() {
|
let blank_symbol = match self.blank_symbol {
|
||||||
|
Some(some) => some.0,
|
||||||
|
None => {
|
||||||
|
if self.symbols.contains_key(&Symbol("B")) {
|
||||||
|
self.ctx
|
||||||
|
.emit_warning_locless("blank symbol not defined, defaulting to 'B'")
|
||||||
|
.emit_help_logless(format!("add: {BLANK_SYMBOL} = B"));
|
||||||
|
} else {
|
||||||
|
self.ctx
|
||||||
|
.emit_error_locless("blank symbol not defined")
|
||||||
|
.emit_help_logless(format!("add: {BLANK_SYMBOL} = ..."));
|
||||||
|
}
|
||||||
|
Symbol("B")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.transitions.is_empty() {
|
||||||
|
self.ctx
|
||||||
|
.emit_warning_locless("no transitions defined")
|
||||||
|
.emit_help_logless(
|
||||||
|
"consider defining one: d(state, symbol) = (state, symbol, direction) | {(state, symbol, direction), ...}",
|
||||||
|
)
|
||||||
|
.emit_info_logless(concat!("d can be ", delta_lower!(str)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.ctx.contains_errors() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Tm {
|
Some(Tm {
|
||||||
initial_state,
|
initial_state,
|
||||||
initial_tape,
|
blank_symbol,
|
||||||
states,
|
states: self.states,
|
||||||
symbols,
|
symbols: self.symbols,
|
||||||
final_states,
|
final_states: self.final_states,
|
||||||
transitions,
|
transitions: self.transitions,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compile_top_level(&mut self, element: ast::TopLevel<'a>, span: Span) {
|
||||||
|
use Spanned as S;
|
||||||
|
use ast::TopLevel as TL;
|
||||||
|
match element {
|
||||||
|
TL::Item(S("Q", _), list) => self.compile_states(list, span),
|
||||||
|
TL::Item(S(gamma_upper!(pat), _), list) => self.compile_symbols(list, span),
|
||||||
|
TL::Item(S("F", _), list) => self.compile_final_states(list, span),
|
||||||
|
TL::Item(S(INITIAL_STATE, _), item) => self.compile_initial_state(item, span),
|
||||||
|
TL::Item(S(BLANK_SYMBOL, _), item) => self.compile_blank_symbol(item, span),
|
||||||
|
TL::Item(S(name, dest_s), _) => {
|
||||||
|
self.ctx.emit_error(format!("unknown item {name:?}, expected states | symbols | final states | initial state | blank symbol"), dest_s);
|
||||||
|
}
|
||||||
|
|
||||||
|
TL::TransitionFunc(S((S(delta_lower!(pat), _), args), func), list) => {
|
||||||
|
self.compile_transition_function(args, func, list)
|
||||||
|
}
|
||||||
|
TL::TransitionFunc(S((S(name, _), _), dest_s), _) => {
|
||||||
|
self.ctx.emit_error(
|
||||||
|
format!(
|
||||||
|
"unknown function {name:?}, expected transition function ( {} )",
|
||||||
|
delta_lower!(str)
|
||||||
|
),
|
||||||
|
dest_s,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TL::ProductionRule(_, _) => {
|
||||||
|
self.ctx.emit_error("unexpected production rule", span);
|
||||||
|
}
|
||||||
|
TL::Table() => _ = self.ctx.emit_error("unexpected table", span),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_states(&mut self, list: Spanned<ast::Item<'a>>, top_level: Span) {
|
||||||
|
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 list.is_empty() {
|
||||||
|
self.ctx.emit_error("states cannot be empty", top_level);
|
||||||
|
}
|
||||||
|
self.states_def = Some(top_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_symbols(&mut self, list: Spanned<ast::Item<'a>>, top_level: Span) {
|
||||||
|
if let Some(previous) = self.symbols_def {
|
||||||
|
self.ctx
|
||||||
|
.emit_error("stack symbols 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
|
||||||
|
.symbols
|
||||||
|
.insert(Symbol(ident), SymbolInfo { definition: item.1 })
|
||||||
|
{
|
||||||
|
self.ctx
|
||||||
|
.emit_error("stack symbol redefined", item.1)
|
||||||
|
.emit_info("previously defined here", previous.definition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if list.is_empty() {
|
||||||
|
self.ctx.emit_error("states cannot be empty", top_level);
|
||||||
|
}
|
||||||
|
self.symbols_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 {
|
||||||
|
self.ctx
|
||||||
|
.emit_error("final state not defined in set of states", item.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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_blank_symbol(
|
||||||
|
&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.blank_symbol {
|
||||||
|
self.ctx
|
||||||
|
.emit_error("blank symbol already set", top_level)
|
||||||
|
.emit_help("previously defined here", previous);
|
||||||
|
}
|
||||||
|
if self.symbols.contains_key(&Symbol(ident)) {
|
||||||
|
self.blank_symbol = Some((Symbol(ident), top_level))
|
||||||
|
} else {
|
||||||
|
self.ctx
|
||||||
|
.emit_error("blank symbol not defined as a symbol", src_d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => _ = self.ctx.emit_error("expected ident", src_d),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_transition_function(
|
||||||
|
&mut self,
|
||||||
|
args: Spanned<ast::Tuple<'a>>,
|
||||||
|
function: Span,
|
||||||
|
list: Spanned<ast::Item<'a>>,
|
||||||
|
) {
|
||||||
|
let list = list.set_weak();
|
||||||
|
let Some((from_state, from_tape)) = args.as_ref().expect_tm_transition_function(self.ctx)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if !self.states.contains_key(&State(from_state.0)) {
|
||||||
|
self.ctx
|
||||||
|
.emit_error("transition state not defined as state", from_state.1);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if !self.symbols.contains_key(&Symbol(from_tape.0)) {
|
||||||
|
self.ctx.emit_error(
|
||||||
|
"transition tape symbol not defined as tape symbol",
|
||||||
|
from_tape.1,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
for item in list {
|
||||||
|
let Some((to_state, to_tape, direction)) = item
|
||||||
|
.expect_tuple(self.ctx)
|
||||||
|
.and_then(|item| item.expect_tm_transition(self.ctx))
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !self.states.contains_key(&State(to_state.0)) {
|
||||||
|
self.ctx
|
||||||
|
.emit_error("transition state not defined as state", to_state.1);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let entry: &mut _ = self
|
||||||
|
.transitions
|
||||||
|
.entry(TransitionFrom {
|
||||||
|
state: State(from_state.0),
|
||||||
|
symbol: Symbol(from_tape.0),
|
||||||
|
})
|
||||||
|
.or_default();
|
||||||
|
if !entry.is_empty() && !self.options.non_deterministic {
|
||||||
|
self.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,
|
||||||
|
transition: item.1,
|
||||||
|
}) {
|
||||||
|
self.ctx.emit_warning("duplicate transition", item.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Spanned<&ast::Tuple<'a>> {
|
impl<'a> Spanned<&ast::Tuple<'a>> {
|
||||||
|
|
@ -335,10 +455,12 @@ impl<'a> Spanned<&ast::Tuple<'a>> {
|
||||||
Spanned(direction, *direction_span),
|
Spanned(direction, *direction_span),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
_ => _ = ctx.emit_error(
|
_ => {
|
||||||
"expected TM transition function (state, symbol, direction)",
|
_ = ctx.emit_error(
|
||||||
self.1,
|
"expected TM transition function (state, symbol, direction)",
|
||||||
),
|
self.1,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
pub mod automatan;
|
pub mod automatan;
|
||||||
pub mod loader;
|
pub mod loader;
|
||||||
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! dual_struct_serde {
|
macro_rules! dual_struct_serde {
|
||||||
($({$(#[$serde_specific:meta])*})?
|
($({$(#[$serde_specific:meta])*})?
|
||||||
|
|
@ -33,4 +32,83 @@ macro_rules! dual_struct_serde {
|
||||||
),*
|
),*
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! dual_enum_serde {
|
||||||
|
(
|
||||||
|
$( {$(#[$serde_specific:meta])*} )?
|
||||||
|
$(#[$enum_meta:meta])*
|
||||||
|
$vis:vis enum $Name:ident $(<$($gen:tt),*>)?
|
||||||
|
{
|
||||||
|
$(
|
||||||
|
$(#[$variant_meta:meta])*
|
||||||
|
$Variant:ident
|
||||||
|
$(
|
||||||
|
// Tuple variant: Variant(T1, T2, ...)
|
||||||
|
( $(
|
||||||
|
$(#[$tfield_meta:meta])*
|
||||||
|
$tfield_ty:ty
|
||||||
|
),* $(,)? )
|
||||||
|
)?
|
||||||
|
$(
|
||||||
|
// Struct variant: Variant { a: T, b: U, ... }
|
||||||
|
{ $(
|
||||||
|
$(#[$sfield_meta:meta])*
|
||||||
|
$sfield_vis:vis $sfield_name:ident : $sfield_ty:ty
|
||||||
|
),* $(,)? }
|
||||||
|
)?
|
||||||
|
),* $(,)?
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
$(#[$enum_meta])*
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
$( $(#[$serde_specific])* )?
|
||||||
|
$vis enum $Name $(<$($gen),*>)? {
|
||||||
|
$(
|
||||||
|
$(#[$variant_meta])*
|
||||||
|
$Variant
|
||||||
|
$(
|
||||||
|
(
|
||||||
|
$(
|
||||||
|
$(#[$tfield_meta])*
|
||||||
|
$tfield_ty
|
||||||
|
),*
|
||||||
|
)
|
||||||
|
)?
|
||||||
|
$(
|
||||||
|
{
|
||||||
|
$(
|
||||||
|
$(#[$sfield_meta])*
|
||||||
|
$sfield_vis $sfield_name: $sfield_ty
|
||||||
|
),*
|
||||||
|
}
|
||||||
|
)?
|
||||||
|
),*
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "serde"))]
|
||||||
|
$(#[$enum_meta])*
|
||||||
|
$vis enum $Name $(<$($gen),*>)? {
|
||||||
|
$(
|
||||||
|
// strip variant + field attrs in non-serde version
|
||||||
|
$Variant
|
||||||
|
$(
|
||||||
|
(
|
||||||
|
$(
|
||||||
|
$tfield_ty
|
||||||
|
),*
|
||||||
|
)
|
||||||
|
)?
|
||||||
|
$(
|
||||||
|
{
|
||||||
|
$(
|
||||||
|
$sfield_vis $sfield_name: $sfield_ty
|
||||||
|
),*
|
||||||
|
}
|
||||||
|
)?
|
||||||
|
),*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,9 @@ 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),
|
||||||
}
|
}
|
||||||
|
|
@ -86,8 +88,12 @@ impl<'a> Spanned<Item<'a>> {
|
||||||
|
|
||||||
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(_)) => {
|
||||||
Item::Symbol(Symbol::Epsilon(_)) => _ = ctx.emit_error("expected set found epsilon", self.1),
|
_ = 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::Tuple(_) => _ = ctx.emit_error("expected set found tuple", self.1),
|
||||||
Item::List(list) => return Some(&list.0),
|
Item::List(list) => return Some(&list.0),
|
||||||
}
|
}
|
||||||
|
|
@ -96,8 +102,12 @@ 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(_)) => {
|
||||||
Item::Symbol(Symbol::Epsilon(_)) => _ = ctx.emit_error("expected list found epsilon", self.1),
|
_ = 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::Tuple(_) => _ = ctx.emit_error("expected list found tuple", self.1),
|
||||||
Item::List(list) => return Some(&list.0),
|
Item::List(list) => return Some(&list.0),
|
||||||
}
|
}
|
||||||
|
|
@ -120,8 +130,12 @@ 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(_)) => {
|
||||||
Item::Symbol(Symbol::Epsilon(_)) => _ = ctx.emit_error("expected tuple found epsilon", self.1),
|
_ = 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::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),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,10 @@ fn begin_ident(c: char) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn continue_ident(c: char) -> bool {
|
fn continue_ident(c: char) -> bool {
|
||||||
c.is_alphanumeric() || c == '_' || c=='\'' || (!c.is_ascii() && !c.is_control() && !c.is_whitespace())
|
c.is_alphanumeric()
|
||||||
|
|| c == '_'
|
||||||
|
|| c == '\''
|
||||||
|
|| (!c.is_ascii() && !c.is_control() && !c.is_whitespace())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> std::iter::Iterator for Lexer<'a> {
|
impl<'a> std::iter::Iterator for Lexer<'a> {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ use std::fmt::Display;
|
||||||
|
|
||||||
use crate::loader::Span;
|
use crate::loader::Span;
|
||||||
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||||
pub struct Logs {
|
pub struct Logs {
|
||||||
logs: Vec<LogEntry>,
|
logs: Vec<LogEntry>,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
automatan::*,
|
automatan::*,
|
||||||
|
dual_enum_serde,
|
||||||
loader::{
|
loader::{
|
||||||
ast::TopLevel,
|
ast::TopLevel,
|
||||||
log::{LogEntry, LogSink},
|
log::{LogEntry, LogSink},
|
||||||
|
|
@ -120,13 +121,14 @@ impl<'a> Context<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
dual_enum_serde! {
|
||||||
#[cfg_attr(feature = "serde", serde(tag = "type"))]
|
{#[serde(tag = "type")] #[serde(rename_all = "snake_case")]}
|
||||||
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Machine<'a> {
|
pub enum Machine<'a> {
|
||||||
Fa(fa::Fa<'a>),
|
Fa(#[serde(borrow)] fa::Fa<'a>),
|
||||||
Pda(pda::Pda<'a>),
|
Pda(#[serde(borrow)] pda::Pda<'a>),
|
||||||
Tm(tm::Tm<'a>),
|
Tm(#[serde(borrow)] tm::Tm<'a>),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_universal<'a>(ctx: &mut Context<'a>) -> Option<Machine<'a>> {
|
pub fn parse_universal<'a>(ctx: &mut Context<'a>) -> Option<Machine<'a>> {
|
||||||
|
|
@ -194,9 +196,9 @@ 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::compile(items, ctx, D)?),
|
Type::Dfa => Machine::Fa(fa::Fa::compile(items, ctx, D)?),
|
||||||
Type::Nfa => Machine::Fa(fa::Fa::compile(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::compile(items, ctx, D)?),
|
||||||
Type::Npda => Machine::Pda(pda::Pda::parse(items, ctx, N)?),
|
Type::Npda => Machine::Pda(pda::Pda::compile(items, ctx, N)?),
|
||||||
Type::Tm => Machine::Tm(tm::Tm::parse(items, ctx, D)?),
|
Type::Tm => Machine::Tm(tm::Tm::compile(items, ctx, D)?),
|
||||||
Type::Ntm => Machine::Tm(tm::Tm::parse(items, ctx, N)?),
|
Type::Ntm => Machine::Tm(tm::Tm::compile(items, ctx, N)?),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ 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(repr@ epsilon!(pat)), r) => S(Symbol::Epsilon(repr), r),
|
S(T::Ident(repr @ epsilon!(pat)), r) => S(Symbol::Epsilon(repr), 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(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1 @@
|
||||||
|
pub fn main() {}
|
||||||
|
|
||||||
pub fn main(){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -100,11 +100,11 @@ export type State = string;
|
||||||
export type Symbol = string;
|
export type Symbol = string;
|
||||||
export type Letter = string;
|
export type Letter = string;
|
||||||
|
|
||||||
export type Span = [number, number];
|
export type Span = readonly [number, number];
|
||||||
|
|
||||||
export type StateInfo = { definition: Span };
|
export type StateInfo = { readonly definition: Span };
|
||||||
export type LetterInfo = { definition: Span };
|
export type LetterInfo = { readonly definition: Span };
|
||||||
export type SymbolInfo = { definition: Span };
|
export type SymbolInfo = { readonly definition: Span };
|
||||||
|
|
||||||
export type FaTransFrom = {
|
export type FaTransFrom = {
|
||||||
state: State;
|
state: State;
|
||||||
|
|
@ -112,16 +112,16 @@ export type FaTransFrom = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FaTransTo = {
|
export type FaTransTo = {
|
||||||
state: State;
|
readonly state: State;
|
||||||
|
|
||||||
transition: Span;
|
readonly transition: Span;
|
||||||
function: Span;
|
readonly function: Span;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Edge = {
|
export type Edge = {
|
||||||
repr: string;
|
readonly repr: string;
|
||||||
function: Span;
|
readonly function: Span;
|
||||||
transition: Span;
|
readonly transition: Span;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Fa = {
|
export type Fa = {
|
||||||
|
|
@ -139,17 +139,17 @@ export type Fa = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PdaTransFrom = {
|
export type PdaTransFrom = {
|
||||||
state: State;
|
readonly state: State;
|
||||||
letter: Letter | null;
|
readonly letter: Letter | null;
|
||||||
symbol: Symbol;
|
readonly symbol: Symbol;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PdaTransTo = {
|
export type PdaTransTo = {
|
||||||
state: State;
|
readonly state: State;
|
||||||
stack: Symbol[];
|
readonly stack: readonly Symbol[];
|
||||||
|
|
||||||
transition: Span;
|
readonly transition: Span;
|
||||||
function: Span;
|
readonly function: Span;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Pda = {
|
export type Pda = {
|
||||||
|
|
@ -172,24 +172,24 @@ export type Pda = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TmTransFrom = {
|
export type TmTransFrom = {
|
||||||
state: State;
|
readonly state: State;
|
||||||
symbol: Symbol;
|
readonly symbol: Symbol;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TmTransTo = {
|
export type TmTransTo = {
|
||||||
state: State;
|
readonly state: State;
|
||||||
symbol: Symbol;
|
readonly symbol: Symbol;
|
||||||
direction: "L" | "R" | "N";
|
readonly direction: "<" | ">" | "_";
|
||||||
|
|
||||||
transition: Span;
|
readonly transition: Span;
|
||||||
function: Span;
|
readonly function: Span;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Tm = {
|
export type Tm = {
|
||||||
type: "tm";
|
type: "tm";
|
||||||
|
|
||||||
initial_state: State;
|
initial_state: State;
|
||||||
initial_tape: Symbol;
|
blank_symbol: Symbol;
|
||||||
states: Map<State, StateInfo>;
|
states: Map<State, StateInfo>;
|
||||||
symbols: Map<Symbol, SymbolInfo>;
|
symbols: Map<Symbol, SymbolInfo>;
|
||||||
alphabet: Map<Letter, LetterInfo>;
|
alphabet: Map<Letter, LetterInfo>;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
// deno-lint-ignore-file
|
// deno-lint-ignore-file
|
||||||
|
|
||||||
import type { Machine } from "./automata.ts";
|
import type { Machine, Span } from "./automata.ts";
|
||||||
import type { Example } from "./examples.ts";
|
import type { Example } from "./examples.ts";
|
||||||
import type { Sim, SimStepResult } from "./simulation.ts";
|
import type { Sim, SimStepResult } from "./simulation.ts";
|
||||||
import type wasm from "./wasm.ts";
|
import type wasm from "./wasm.ts";
|
||||||
import type { Text } from "npm:@codemirror/state";
|
import type { Text } from "npm:@codemirror/state";
|
||||||
|
import type { Highlight } from "./highlight.ts";
|
||||||
|
|
||||||
type Unsubscribe = () => void;
|
type Unsubscribe = () => void;
|
||||||
|
|
||||||
|
|
@ -69,14 +70,14 @@ type AppEvents = {
|
||||||
"editor/change": {text: string, doc: Text};
|
"editor/change": {text: string, doc: Text};
|
||||||
"compiled": {log: wasm.CompileLog[], ansi_log: string, machine: string|undefined};
|
"compiled": {log: wasm.CompileLog[], ansi_log: string, machine: string|undefined};
|
||||||
|
|
||||||
"automata/sim/update": { simulation: Sim|null };
|
"automata/sim/update": Sim|null;
|
||||||
"automata/sim/before_step": { simulation: Sim };
|
"automata/sim/before_step": { simulation: Sim };
|
||||||
"automata/sim/after_step": { simulation: Sim, result: SimStepResult };
|
"automata/sim/after_step": { simulation: Sim, result: SimStepResult };
|
||||||
"automata/update": { automaton: Machine };
|
"automata/update": Machine;
|
||||||
|
|
||||||
"example/selected": {example: Example};
|
"example/selected": Example;
|
||||||
|
|
||||||
"controls/editor/set_text": {text: string};
|
"controls/editor/set_text": string;
|
||||||
|
|
||||||
"controls/vis/physics": {enabled: boolean};
|
"controls/vis/physics": {enabled: boolean};
|
||||||
"controls/vis/reset_network": void;
|
"controls/vis/reset_network": void;
|
||||||
|
|
@ -85,6 +86,12 @@ type AppEvents = {
|
||||||
"controls/sim/reload": void;
|
"controls/sim/reload": void;
|
||||||
"controls/sim/clear": void;
|
"controls/sim/clear": void;
|
||||||
|
|
||||||
|
"highlight/one/add": Highlight;
|
||||||
|
"highlight/one/remove": Highlight;
|
||||||
|
"highlight/all/remove": void;
|
||||||
|
|
||||||
|
"highlight/update": void;
|
||||||
|
|
||||||
"theme/update": void;
|
"theme/update": void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ class Controls {
|
||||||
if (Controls.running) Controls.setRunning(false);
|
if (Controls.running) Controls.setRunning(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
bus.on("automata/sim/update", ({ simulation }) => {
|
bus.on("automata/sim/update", simulation => {
|
||||||
Controls.simulation_active = !!simulation;
|
Controls.simulation_active = !!simulation;
|
||||||
if (!simulation) Controls.stop();
|
if (!simulation) Controls.stop();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Decoration,
|
Decoration,
|
||||||
|
DecorationSet,
|
||||||
EditorView,
|
EditorView,
|
||||||
highlightActiveLine,
|
highlightActiveLine,
|
||||||
highlightActiveLineGutter,
|
highlightActiveLineGutter,
|
||||||
|
|
@ -10,7 +11,7 @@ import {
|
||||||
lineNumbers,
|
lineNumbers,
|
||||||
} from "npm:@codemirror/view";
|
} from "npm:@codemirror/view";
|
||||||
|
|
||||||
import { EditorState, StateField, Text } from "npm:@codemirror/state";
|
import { EditorState, RangeSetBuilder, StateEffect, StateField, Text } from "npm:@codemirror/state";
|
||||||
import {
|
import {
|
||||||
defaultKeymap,
|
defaultKeymap,
|
||||||
history,
|
history,
|
||||||
|
|
@ -24,6 +25,7 @@ import wasm from "./wasm.ts";
|
||||||
import { Share } from "./share.ts";
|
import { Share } from "./share.ts";
|
||||||
import { examples } from "./examples.ts";
|
import { examples } from "./examples.ts";
|
||||||
import { bus } from "./bus.ts";
|
import { bus } from "./bus.ts";
|
||||||
|
import { current, Highlight, HighlightKind } from "./highlight.ts";
|
||||||
|
|
||||||
function tokenize(text: string): wasm.Tok[] {
|
function tokenize(text: string): wasm.Tok[] {
|
||||||
try {
|
try {
|
||||||
|
|
@ -45,6 +47,48 @@ function compile(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function decoForKind(kind: HighlightKind) {
|
||||||
|
// Use a class per kind so each gets a distinct color via CSS
|
||||||
|
return Decoration.mark({ class: `cm-highlight cm-highlight-${kind}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.on("highlight/update", _ => {
|
||||||
|
const arr = current.values().toArray().sort((a, b) => a.span[0]-b.span[0]);
|
||||||
|
editor.dispatch({ effects: setHighlights.of(arr) });
|
||||||
|
});
|
||||||
|
export const setHighlights = StateEffect.define<Highlight[]>();
|
||||||
|
export const highlightsField = StateField.define<DecorationSet>({
|
||||||
|
create() {
|
||||||
|
return Decoration.none;
|
||||||
|
},
|
||||||
|
|
||||||
|
update(highlights, tr) {
|
||||||
|
// Keep highlights aligned with document edits
|
||||||
|
highlights = highlights.map(tr.changes);
|
||||||
|
|
||||||
|
for (const e of tr.effects) {
|
||||||
|
if (e.is(setHighlights)) {
|
||||||
|
const spans = e.value;
|
||||||
|
|
||||||
|
const builder = new RangeSetBuilder<Decoration>();
|
||||||
|
for (const s of spans) {
|
||||||
|
|
||||||
|
const from = Math.max(0, Math.min(s.span[0], tr.state.doc.length));
|
||||||
|
const to = Math.max(0, Math.min(s.span[1], tr.state.doc.length));
|
||||||
|
if (to > from) builder.add(from, to, decoForKind(s.kind));
|
||||||
|
}
|
||||||
|
highlights = builder.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return highlights;
|
||||||
|
},
|
||||||
|
|
||||||
|
provide: (f) => EditorView.decorations.from(f),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
const eventBusConnection = StateField.define({
|
const eventBusConnection = StateField.define({
|
||||||
create(state) {
|
create(state) {
|
||||||
const text = state.doc.toString();
|
const text = state.doc.toString();
|
||||||
|
|
@ -198,6 +242,7 @@ const state = EditorState.create({
|
||||||
keymap.of([...defaultKeymap, ...historyKeymap]),
|
keymap.of([...defaultKeymap, ...historyKeymap]),
|
||||||
|
|
||||||
eventBusConnection,
|
eventBusConnection,
|
||||||
|
highlightsField,
|
||||||
diagHover,
|
diagHover,
|
||||||
|
|
||||||
EditorView.lineWrapping,
|
EditorView.lineWrapping,
|
||||||
|
|
@ -211,15 +256,15 @@ const editor = new EditorView({
|
||||||
|
|
||||||
bus.on(
|
bus.on(
|
||||||
"begin",
|
"begin",
|
||||||
(_) => bus.emit("controls/editor/set_text", { text: defaultText() }),
|
(_) => bus.emit("controls/editor/set_text", defaultText()),
|
||||||
);
|
);
|
||||||
|
|
||||||
bus.on("controls/editor/set_text", ({ text }) => {
|
bus.on("controls/editor/set_text", text => {
|
||||||
editor.dispatch({
|
editor.dispatch({
|
||||||
changes: { from: 0, to: editor.state.doc.length, insert: text },
|
changes: { from: 0, to: editor.state.doc.length, insert: text },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
bus.on("example/selected", ({ example }) => {
|
bus.on("example/selected", example => {
|
||||||
bus.emit("controls/editor/set_text", { text: example.machine });
|
bus.emit("controls/editor/set_text", example.machine);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,11 @@ export const examples: readonly Example[] = [
|
||||||
"DFA",
|
"DFA",
|
||||||
`// strings over a,b which start and end with different letters
|
`// strings over a,b which start and end with different letters
|
||||||
|
|
||||||
type = DFA // type of machine DFA, NFA, DPDA, NPDA, DTM, NTM
|
type = DFA // type of machine DFA, NFA, DPDA, NPDA, DTM, NTM
|
||||||
Q = {q0, qa, qa', qb, qb'} // set of states
|
Q = {q0, qa, qa', qb, qb'} // set of states
|
||||||
E = {a, b} // alphabet
|
E = {a, b} // alphabet
|
||||||
F = {qa', qb'} // set of final states
|
F = {qa', qb'} // set of final states
|
||||||
q0 = q0 // initial state
|
q0 = q0 // initial state
|
||||||
|
|
||||||
// transition function (state, letter) -> state
|
// transition function (state, letter) -> state
|
||||||
d(q0, a) = qa
|
d(q0, a) = qa
|
||||||
|
|
@ -81,11 +81,12 @@ d(q4, 3) = q2`,
|
||||||
new Example(
|
new Example(
|
||||||
"DPDA",
|
"DPDA",
|
||||||
"unequal",
|
"unequal",
|
||||||
`type=DPDA
|
`type = DPDA
|
||||||
Q = {q0, qas, qeq, qmb, qlb} // states
|
Q = {q0, qas, qeq, qmb, qlb} // states
|
||||||
E = {a, b} // alphabet
|
E = {a, b} // alphabet
|
||||||
T = {z0, A} // stack
|
T = {z0, A} // stack
|
||||||
F = {qmb, qlb} // final states
|
F = {qmb, qlb} // final states
|
||||||
|
accept = F // accept by final state
|
||||||
q0 = q0
|
q0 = q0
|
||||||
z0 = z0
|
z0 = z0
|
||||||
|
|
||||||
|
|
@ -112,6 +113,7 @@ d(qmb, b, z0) = (qmb, z0)`,
|
||||||
Q = {q0, q1} // states
|
Q = {q0, q1} // states
|
||||||
E = {a, b} // alphabet
|
E = {a, b} // alphabet
|
||||||
T = {z0, A, B} // stack
|
T = {z0, A, B} // stack
|
||||||
|
accept = E // accept by empty stack
|
||||||
q0 = q0
|
q0 = q0
|
||||||
z0 = z0
|
z0 = z0
|
||||||
|
|
||||||
|
|
@ -142,6 +144,7 @@ d(q1, b, B) = { (q1, epsilon) }`,
|
||||||
Q = {q0, q1} // states
|
Q = {q0, q1} // states
|
||||||
E = {a, b} // alphabet
|
E = {a, b} // alphabet
|
||||||
T = {z0, A, B} // stack
|
T = {z0, A, B} // stack
|
||||||
|
accept = E // accept by empty stack
|
||||||
q0 = q0
|
q0 = q0
|
||||||
z0 = z0
|
z0 = z0
|
||||||
|
|
||||||
|
|
@ -160,6 +163,30 @@ d(q0, epsilon, B) = { (q1, B) }
|
||||||
d(q1, a, A) = { (q1, epsilon) }
|
d(q1, a, A) = { (q1, epsilon) }
|
||||||
d(q1, b, B) = { (q1, epsilon) }`,
|
d(q1, b, B) = { (q1, epsilon) }`,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
new Example("TM", "a^nb^n",
|
||||||
|
`// accepts all strings on {a,b}+ of the form anbn
|
||||||
|
|
||||||
|
type = TM
|
||||||
|
Q = { q0, q1, q2, q3, q4 } // set of internal states
|
||||||
|
F = { q4 } // set of final states
|
||||||
|
T = { a, b, X, Y, B } // tape alphabet
|
||||||
|
B = B // the blank symbol (tape initializer symbol)
|
||||||
|
q0 = q0 // initial state
|
||||||
|
|
||||||
|
d(q0,a)=(q1,x,R)
|
||||||
|
d(q1,a)=(q1,a,R)
|
||||||
|
d(q1,Y)=(q1,y,R)
|
||||||
|
d(q1,b)=(q2,y,L)
|
||||||
|
|
||||||
|
d(q2,Y)=(q2,y,L)
|
||||||
|
d(q2,a)=(q2,a,L)
|
||||||
|
d(q2,X)=(q0,x,R)
|
||||||
|
|
||||||
|
d(q0,Y)=(q3,y,R)
|
||||||
|
d(q3,Y)=(q3,y,R)
|
||||||
|
d(q3,B)=(q4,B,R)
|
||||||
|
`)
|
||||||
];
|
];
|
||||||
|
|
||||||
const CATEGORY_ORDER: Category[] = [
|
const CATEGORY_ORDER: Category[] = [
|
||||||
|
|
@ -242,5 +269,5 @@ function buildExamplesDropdown(
|
||||||
|
|
||||||
const selectEl = document.getElementById("exampleSelect") as HTMLSelectElement;
|
const selectEl = document.getElementById("exampleSelect") as HTMLSelectElement;
|
||||||
buildExamplesDropdown(selectEl, examples, (example) => {
|
buildExamplesDropdown(selectEl, examples, (example) => {
|
||||||
bus.emit("example/selected", {example});
|
bus.emit("example/selected", example);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
85
web/root/src/highlight.ts
Normal file
85
web/root/src/highlight.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
import type { Span } from "./automata.ts";
|
||||||
|
import { bus } from "./bus.ts";
|
||||||
|
import { automaton } from "./simulation.ts";
|
||||||
|
|
||||||
|
|
||||||
|
export type HighlightKind = "focus" | "error" | "warning" | "success";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export type Highlight = {
|
||||||
|
span: Span,
|
||||||
|
kind: HighlightKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
type HighlightEntry = {
|
||||||
|
span: Span,
|
||||||
|
kind: HighlightKind,
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const current: Map<string, HighlightEntry> = new Map();
|
||||||
|
|
||||||
|
|
||||||
|
function asKey(highlight: Highlight): string {
|
||||||
|
return `${highlight.span[0]}:${highlight.span[1]}:${highlight.kind}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function highlight_from_node_id(node_id: string) {
|
||||||
|
const state = automaton.states.get(node_id);
|
||||||
|
if (state) {
|
||||||
|
bus.emit("highlight/one/add", { kind: "success", span: state.definition })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dehighlight_from_node_id(node_id: string) {
|
||||||
|
const state = automaton.states.get(node_id);
|
||||||
|
if (state) {
|
||||||
|
bus.emit("highlight/one/remove", { kind: "success", span: state.definition })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function highlight_from_edge_id(node_id: string) {
|
||||||
|
for (const edge_value of automaton.edges.get(node_id)!) {
|
||||||
|
bus.emit("highlight/one/add", { kind: "focus", span: edge_value.function })
|
||||||
|
bus.emit("highlight/one/add", { kind: "warning", span: edge_value.transition })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dehighlight_from_edge_id(node_id: string) {
|
||||||
|
for (const edge_value of automaton.edges.get(node_id)!) {
|
||||||
|
bus.emit("highlight/one/remove", { kind: "focus", span: edge_value.function })
|
||||||
|
bus.emit("highlight/one/remove", { kind: "warning", span: edge_value.transition })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.on("automata/update", _ => {
|
||||||
|
bus.emit("highlight/all/remove", undefined);
|
||||||
|
})
|
||||||
|
|
||||||
|
bus.on("highlight/one/add", (highlight) => {
|
||||||
|
const key = asKey(highlight);
|
||||||
|
if (current.has(key)) {
|
||||||
|
current.get(key)!.count += 1;
|
||||||
|
} else {
|
||||||
|
current.set(key, { count: 1, ...highlight });
|
||||||
|
bus.emit("highlight/update", undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
bus.on("highlight/one/remove", (highlight) => {
|
||||||
|
const key = asKey(highlight);
|
||||||
|
if (current.has(key)) {
|
||||||
|
const value = current.get(key)!
|
||||||
|
value.count -= 1;
|
||||||
|
if (value.count === 0) {
|
||||||
|
current.delete(key);
|
||||||
|
bus.emit("highlight/update", undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
bus.on("highlight/all/remove", (_) => {
|
||||||
|
if (current.size !== 0) {
|
||||||
|
current.clear();
|
||||||
|
bus.emit("highlight/update", undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -1,18 +1,26 @@
|
||||||
import { bus } from "./bus.ts";
|
import { bus } from "./bus.ts";
|
||||||
import type {
|
import type {
|
||||||
Fa,
|
|
||||||
Machine,
|
Machine,
|
||||||
|
Fa,
|
||||||
Pda,
|
Pda,
|
||||||
State,
|
|
||||||
Symbol,
|
|
||||||
Tm,
|
Tm,
|
||||||
} from "./automata.ts";
|
} from "./automata.ts";
|
||||||
import {parse_machine_from_json} from "./automata.ts";
|
import {parse_machine_from_json} from "./automata.ts";
|
||||||
|
|
||||||
|
import { FaSim } from "./simulation/fa.ts";
|
||||||
|
export { FaSim } from "./simulation/fa.ts";
|
||||||
|
|
||||||
|
import { PdaSim } from "./simulation/pda.ts";
|
||||||
|
export { PdaSim } from "./simulation/pda.ts";
|
||||||
|
|
||||||
|
import { TmSim } from "./simulation/tm.ts";
|
||||||
|
export { TmSim } from "./simulation/tm.ts";
|
||||||
|
|
||||||
export type SimStepResult = "pending" | "accept" | "reject";
|
export type SimStepResult = "pending" | "accept" | "reject";
|
||||||
export type Sim = FaSim | PdaSim | TmSim;
|
export type Sim = FaSim | PdaSim | TmSim;
|
||||||
let simulation: Sim | null = null;
|
|
||||||
let automaton: Machine = {
|
export let simulation: Sim | null = null;
|
||||||
|
export let automaton: Machine = {
|
||||||
type: "fa",
|
type: "fa",
|
||||||
alphabet: new Map(),
|
alphabet: new Map(),
|
||||||
final_states: new Map(),
|
final_states: new Map(),
|
||||||
|
|
@ -28,7 +36,7 @@ bus.on("compiled", ({ machine }) => {
|
||||||
try {
|
try {
|
||||||
bus.emit("controls/sim/clear", undefined);
|
bus.emit("controls/sim/clear", undefined);
|
||||||
automaton = parse_machine_from_json(machine);
|
automaton = parse_machine_from_json(machine);
|
||||||
bus.emit("automata/update", { automaton });
|
bus.emit("automata/update", automaton);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +44,7 @@ bus.on("compiled", ({ machine }) => {
|
||||||
});
|
});
|
||||||
bus.on("controls/sim/clear", (_) => {
|
bus.on("controls/sim/clear", (_) => {
|
||||||
simulation = null;
|
simulation = null;
|
||||||
bus.emit("automata/sim/update", { simulation: null });
|
bus.emit("automata/sim/update", null);
|
||||||
});
|
});
|
||||||
bus.on("controls/sim/step", (_) => {
|
bus.on("controls/sim/step", (_) => {
|
||||||
if (simulation) {
|
if (simulation) {
|
||||||
|
|
@ -48,7 +56,7 @@ bus.on("controls/sim/step", (_) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const machineInput = document.getElementById("machineInput") as HTMLInputElement;
|
const machineInput = document.getElementById("machineInput") as HTMLInputElement;
|
||||||
machineInput.addEventListener("input", () => bus.emit("automata/sim/update", {simulation: null}));
|
machineInput.addEventListener("input", () => bus.emit("controls/sim/clear", undefined));
|
||||||
machineInput.addEventListener("keydown", (e) => {
|
machineInput.addEventListener("keydown", (e) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
bus.emit("controls/sim/reload", undefined)
|
bus.emit("controls/sim/reload", undefined)
|
||||||
|
|
@ -67,10 +75,10 @@ bus.on("controls/sim/reload", (_) => {
|
||||||
simulation = new TmSim(automaton as Tm, input);
|
simulation = new TmSim(automaton as Tm, input);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
bus.emit("automata/sim/update", { simulation });
|
bus.emit("automata/sim/update", simulation);
|
||||||
});
|
});
|
||||||
const simulationStatus = document.getElementById("simulationStatus") as HTMLInputElement;
|
const simulationStatus = document.getElementById("simulationStatus") as HTMLInputElement;
|
||||||
bus.on("automata/sim/update", ({simulation}) => {
|
bus.on("automata/sim/update", simulation => {
|
||||||
if (!simulation){
|
if (!simulation){
|
||||||
simulationStatus.innerText = "N/A"
|
simulationStatus.innerText = "N/A"
|
||||||
simulationStatus.style.color = "var(--fg-2)";
|
simulationStatus.style.color = "var(--fg-2)";
|
||||||
|
|
@ -92,234 +100,3 @@ bus.on("automata/sim/after_step", ({result}) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export class FaState {
|
|
||||||
readonly state: State;
|
|
||||||
|
|
||||||
readonly position: number;
|
|
||||||
readonly input: string;
|
|
||||||
readonly accepted: boolean = false;
|
|
||||||
private repr!: string;
|
|
||||||
|
|
||||||
constructor(state: State, position: number, input: string) {
|
|
||||||
this.state = state;
|
|
||||||
this.position = position;
|
|
||||||
this.input = input;
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
if (!this.repr) {
|
|
||||||
this.repr = this.state + " >" + this.input.substring(this.position);
|
|
||||||
}
|
|
||||||
return this.repr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FaSim {
|
|
||||||
machine: Fa;
|
|
||||||
paths: FaState[];
|
|
||||||
input: string;
|
|
||||||
|
|
||||||
current_states: Map<string, FaState[]> = new Map();
|
|
||||||
accepted: FaState[] = [];
|
|
||||||
|
|
||||||
constructor(machine: Fa, input: string) {
|
|
||||||
this.machine = machine;
|
|
||||||
this.paths = [new FaState(machine.initial_state, 0, input)];
|
|
||||||
this.current_states.set(machine.initial_state, [this.paths[0]]);
|
|
||||||
this.input = input;
|
|
||||||
}
|
|
||||||
|
|
||||||
step(): SimStepResult {
|
|
||||||
if (this.paths.length == 0) return "reject";
|
|
||||||
if (this.accepted.length != 0) return "accept";
|
|
||||||
|
|
||||||
const paths: FaState[] = [];
|
|
||||||
this.current_states.clear();
|
|
||||||
|
|
||||||
const push = (state: FaState) => {
|
|
||||||
paths.push(state);
|
|
||||||
if (!this.current_states.has(state.state)) {
|
|
||||||
this.current_states.set(state.state, []);
|
|
||||||
}
|
|
||||||
this.current_states.get(state.state)?.push(state);
|
|
||||||
|
|
||||||
if (
|
|
||||||
state.position == this.input.length &&
|
|
||||||
this.machine.final_states.has(state.state)
|
|
||||||
) {
|
|
||||||
// @ts-expect-error sillllyyyy
|
|
||||||
state.accepted = true;
|
|
||||||
this.accepted.push(state);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const path of this.paths) {
|
|
||||||
const letter_map = this.machine.transitions_components.get(path.state)!;
|
|
||||||
if (!letter_map) continue;
|
|
||||||
|
|
||||||
for (const to of letter_map.get(null) ?? []) {
|
|
||||||
push(new FaState(to.state, path.position, this.input));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.position >= this.input.length) continue;
|
|
||||||
|
|
||||||
const char = this.input.charAt(path.position);
|
|
||||||
|
|
||||||
for (const to of letter_map.get(char) ?? []) {
|
|
||||||
push(new FaState(to.state, path.position + 1, this.input));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.paths = paths;
|
|
||||||
|
|
||||||
if (this.paths.length == 0) return "reject";
|
|
||||||
if (this.accepted.length != 0) return "accept";
|
|
||||||
return "pending";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PdaState {
|
|
||||||
readonly state: State;
|
|
||||||
readonly stack: Symbol[];
|
|
||||||
|
|
||||||
readonly position: number;
|
|
||||||
readonly input: string;
|
|
||||||
readonly accepted: boolean = false;
|
|
||||||
private repr!: string;
|
|
||||||
|
|
||||||
constructor(state: State, stack: Symbol[], position: number, input: string) {
|
|
||||||
this.state = state;
|
|
||||||
this.stack = stack;
|
|
||||||
this.position = position;
|
|
||||||
this.input = input;
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
if (!this.repr) {
|
|
||||||
this.repr = this.state + " [" + this.stack + "]" + " >" +
|
|
||||||
this.input.substring(this.position);
|
|
||||||
}
|
|
||||||
return this.repr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PdaSim {
|
|
||||||
machine: Pda;
|
|
||||||
paths: PdaState[];
|
|
||||||
input: string;
|
|
||||||
|
|
||||||
current_states: Map<string, PdaState[]> = new Map();
|
|
||||||
accepted: PdaState[] = [];
|
|
||||||
|
|
||||||
constructor(machine: Pda, input: string) {
|
|
||||||
this.machine = machine;
|
|
||||||
this.paths = [
|
|
||||||
new PdaState(machine.initial_state, [machine.initial_stack], 0, input),
|
|
||||||
];
|
|
||||||
this.current_states.set(machine.initial_state, [this.paths[0]]);
|
|
||||||
this.input = input;
|
|
||||||
}
|
|
||||||
|
|
||||||
step(): SimStepResult {
|
|
||||||
if (this.paths.length == 0) return "reject";
|
|
||||||
if (this.accepted.length != 0) return "accept";
|
|
||||||
|
|
||||||
const paths: PdaState[] = [];
|
|
||||||
this.current_states.clear();
|
|
||||||
|
|
||||||
const push = (state: PdaState) => {
|
|
||||||
paths.push(state);
|
|
||||||
if (!this.current_states.has(state.state)) {
|
|
||||||
this.current_states.set(state.state, []);
|
|
||||||
}
|
|
||||||
this.current_states.get(state.state)?.push(state);
|
|
||||||
|
|
||||||
if (
|
|
||||||
state.position == this.input.length && this.machine.final_states &&
|
|
||||||
this.machine.final_states.has(state.state) ||
|
|
||||||
state.position == this.input.length && !this.machine.final_states &&
|
|
||||||
state.stack.length == 1 &&
|
|
||||||
state.stack[0] == this.machine.initial_stack
|
|
||||||
) {
|
|
||||||
// @ts-expect-error sillllyyyy
|
|
||||||
state.accepted = true;
|
|
||||||
this.accepted.push(state);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const path of this.paths) {
|
|
||||||
const stack = path.stack.pop()!;
|
|
||||||
const letter_map = this.machine.transitions_components.get(path.state)
|
|
||||||
?.get(stack);
|
|
||||||
if (!letter_map) continue;
|
|
||||||
|
|
||||||
for (const to of letter_map.get(null) ?? []) {
|
|
||||||
push(
|
|
||||||
new PdaState(
|
|
||||||
to.state,
|
|
||||||
path.stack.concat(to.stack),
|
|
||||||
path.position,
|
|
||||||
this.input,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.position >= this.input.length) continue;
|
|
||||||
|
|
||||||
const char = this.input.charAt(path.position);
|
|
||||||
|
|
||||||
for (const to of letter_map.get(char) ?? []) {
|
|
||||||
push(
|
|
||||||
new PdaState(
|
|
||||||
to.state,
|
|
||||||
path.stack.concat(to.stack),
|
|
||||||
path.position + 1,
|
|
||||||
this.input,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.paths = paths;
|
|
||||||
|
|
||||||
if (this.paths.length == 0) return "reject";
|
|
||||||
if (this.accepted.length != 0) return "accept";
|
|
||||||
return "pending";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TmState {
|
|
||||||
readonly state: State;
|
|
||||||
readonly tape: Symbol[];
|
|
||||||
|
|
||||||
readonly position: number;
|
|
||||||
readonly input: string;
|
|
||||||
readonly accepted: boolean = false;
|
|
||||||
private repr!: string;
|
|
||||||
|
|
||||||
constructor(state: State, tape: Symbol[], position: number, input: string) {
|
|
||||||
this.state = state;
|
|
||||||
this.tape = tape;
|
|
||||||
this.position = position;
|
|
||||||
this.input = input;
|
|
||||||
}
|
|
||||||
|
|
||||||
toString(): string {
|
|
||||||
if (!this.repr) this.repr = this.state + " " + this.position;
|
|
||||||
return this.repr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TmSim {
|
|
||||||
machine: Tm;
|
|
||||||
input: string;
|
|
||||||
current_states: Map<string, TmState[]> = new Map();
|
|
||||||
accepted: TmState[] = [];
|
|
||||||
|
|
||||||
constructor(machine: Tm, input: string) {
|
|
||||||
this.machine = machine;
|
|
||||||
this.input = input;
|
|
||||||
}
|
|
||||||
|
|
||||||
step(): SimStepResult {
|
|
||||||
return "pending";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
117
web/root/src/simulation/fa.ts
Normal file
117
web/root/src/simulation/fa.ts
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
import type {
|
||||||
|
Fa,
|
||||||
|
FaTransTo,
|
||||||
|
State,
|
||||||
|
} from "../automata.ts";
|
||||||
|
import { SimStepResult } from "../simulation.ts";
|
||||||
|
|
||||||
|
export type FaState = {
|
||||||
|
readonly state: State;
|
||||||
|
readonly position: number;
|
||||||
|
|
||||||
|
readonly accepted: boolean;
|
||||||
|
readonly repr: string;
|
||||||
|
|
||||||
|
readonly path: readonly FaTransTo[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type Initializer<T> = { -readonly [P in keyof T]?: T[P] | undefined };
|
||||||
|
|
||||||
|
export class FaSim {
|
||||||
|
readonly machine: Fa;
|
||||||
|
readonly input: string;
|
||||||
|
|
||||||
|
paths: FaState[] = [];
|
||||||
|
current_states: Map<string, FaState[]> = new Map();
|
||||||
|
accepted: FaState[] = [];
|
||||||
|
rejected: FaState[] = [];
|
||||||
|
|
||||||
|
constructor(machine: Fa, input: string) {
|
||||||
|
this.machine = machine;
|
||||||
|
this.input = input;
|
||||||
|
this.initial();
|
||||||
|
}
|
||||||
|
|
||||||
|
private accept(state: Initializer<FaState>): boolean {
|
||||||
|
const pos = state.position ?? 0;
|
||||||
|
const st = state.state!;
|
||||||
|
return pos === this.input.length && this.machine.final_states.has(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
private init_state(state: Initializer<FaState>) {
|
||||||
|
state.position ??= 0;
|
||||||
|
|
||||||
|
state.accepted = this.accept(state);
|
||||||
|
state.repr = state.state + " >" + this.input.substring(state.position);
|
||||||
|
|
||||||
|
const frozen = state as FaState;
|
||||||
|
|
||||||
|
if (frozen.accepted) this.accepted.push(frozen);
|
||||||
|
this.paths.push(frozen);
|
||||||
|
|
||||||
|
if (!this.current_states.has(frozen.state)) {
|
||||||
|
this.current_states.set(frozen.state, []);
|
||||||
|
}
|
||||||
|
this.current_states.get(frozen.state)!.push(frozen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initial() {
|
||||||
|
const state: Initializer<FaState> = {
|
||||||
|
state: this.machine.initial_state,
|
||||||
|
position: 0,
|
||||||
|
path: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
this.init_state(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private transition(from: FaState, to: FaTransTo, consume: boolean) {
|
||||||
|
const state: Initializer<FaState> = {
|
||||||
|
state: to.state,
|
||||||
|
position: from.position + (consume ? 1 : 0),
|
||||||
|
path: from.path.concat([to]),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.init_state(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
step(): SimStepResult {
|
||||||
|
if (this.accepted.length !== 0) return "accept";
|
||||||
|
if (this.paths.length === 0) return "reject";
|
||||||
|
|
||||||
|
const paths = this.paths;
|
||||||
|
this.paths = [];
|
||||||
|
this.current_states.clear();
|
||||||
|
|
||||||
|
for (const from of paths) {
|
||||||
|
const letterMap = this.machine.transitions_components.get(from.state);
|
||||||
|
|
||||||
|
if (!letterMap) {
|
||||||
|
this.rejected.push(from);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// epsilon transitions
|
||||||
|
const eps = letterMap.get(null) ?? [];
|
||||||
|
for (const to of eps) this.transition(from, to, false);
|
||||||
|
|
||||||
|
// consuming transitions
|
||||||
|
if (from.position >= this.input.length) {
|
||||||
|
if (eps.length === 0) this.rejected.push(from);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ch = this.input.charAt(from.position);
|
||||||
|
const trs = letterMap.get(ch) ?? [];
|
||||||
|
for (const to of trs) this.transition(from, to, true);
|
||||||
|
|
||||||
|
if (eps.length === 0 && trs.length === 0) {
|
||||||
|
this.rejected.push(from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.accepted.length !== 0) return "accept";
|
||||||
|
if (this.paths.length === 0) return "reject";
|
||||||
|
return "pending";
|
||||||
|
}
|
||||||
|
}
|
||||||
146
web/root/src/simulation/pda.ts
Normal file
146
web/root/src/simulation/pda.ts
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
import type {
|
||||||
|
Pda,
|
||||||
|
PdaTransTo,
|
||||||
|
State,
|
||||||
|
Symbol
|
||||||
|
} from "../automata.ts";
|
||||||
|
import { SimStepResult } from "../simulation.ts";
|
||||||
|
|
||||||
|
export type PdaState = {
|
||||||
|
readonly state: State;
|
||||||
|
readonly stack: Symbol[];
|
||||||
|
readonly position: number;
|
||||||
|
|
||||||
|
readonly accepted: boolean;
|
||||||
|
readonly repr: string;
|
||||||
|
|
||||||
|
readonly path: readonly PdaTransTo[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type Initializer<T> = { -readonly [P in keyof T]?: T[P] | undefined };
|
||||||
|
|
||||||
|
export class PdaSim {
|
||||||
|
readonly machine: Pda;
|
||||||
|
readonly input: string;
|
||||||
|
|
||||||
|
paths: PdaState[] = [];
|
||||||
|
current_states: Map<string, PdaState[]> = new Map();
|
||||||
|
accepted: PdaState[] = [];
|
||||||
|
rejected: PdaState[] = [];
|
||||||
|
|
||||||
|
constructor(machine: Pda, input: string) {
|
||||||
|
this.machine = machine;
|
||||||
|
this.input = input;
|
||||||
|
this.initial();
|
||||||
|
}
|
||||||
|
|
||||||
|
private accept(state: Initializer<PdaState>): boolean {
|
||||||
|
const pos = state.position ?? 0;
|
||||||
|
const st = state.state!;
|
||||||
|
const stack = state.stack ?? [];
|
||||||
|
|
||||||
|
//accept by final state
|
||||||
|
if (pos === this.input.length && this.machine.final_states && this.machine.final_states.has(st)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//accept by empty stack
|
||||||
|
if (pos === this.input.length && !this.machine.final_states && stack.length === 1 && stack[0] === this.machine.initial_stack) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private init_state(state: Initializer<PdaState>) {
|
||||||
|
state.stack ??= [this.machine.initial_stack];
|
||||||
|
state.position ??= 0;
|
||||||
|
|
||||||
|
state.accepted = this.accept(state);
|
||||||
|
state.repr = state.state + " [" + state.stack.join(",") + "] >" + this.input.substring(state.position);
|
||||||
|
|
||||||
|
const frozen = state as PdaState;
|
||||||
|
|
||||||
|
if (frozen.accepted) this.accepted.push(frozen);
|
||||||
|
this.paths.push(frozen);
|
||||||
|
|
||||||
|
if (!this.current_states.has(frozen.state)) {
|
||||||
|
this.current_states.set(frozen.state, []);
|
||||||
|
}
|
||||||
|
this.current_states.get(frozen.state)!.push(frozen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initial() {
|
||||||
|
const state: Initializer<PdaState> = {
|
||||||
|
state: this.machine.initial_state,
|
||||||
|
stack: [this.machine.initial_stack],
|
||||||
|
position: 0,
|
||||||
|
path: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
this.init_state(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private transition(from: PdaState, to: PdaTransTo, consume: boolean) {
|
||||||
|
const stackCopy = from.stack.slice(0, from.stack.length - 1); // pop off top
|
||||||
|
const nextStack = stackCopy.concat(to.stack);
|
||||||
|
if (nextStack.length == 0) {
|
||||||
|
this.rejected.push(from)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const state: Initializer<PdaState> = {
|
||||||
|
state: to.state,
|
||||||
|
stack: nextStack,
|
||||||
|
position: from.position + (consume ? 1 : 0),
|
||||||
|
path: from.path.concat([to]),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.init_state(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
step(): SimStepResult {
|
||||||
|
if (this.accepted.length !== 0) return "accept";
|
||||||
|
if (this.paths.length === 0) return "reject";
|
||||||
|
|
||||||
|
const paths = this.paths;
|
||||||
|
this.paths = [];
|
||||||
|
this.current_states.clear();
|
||||||
|
|
||||||
|
for (const from of paths) {
|
||||||
|
const top = from.stack[from.stack.length - 1];
|
||||||
|
|
||||||
|
const letterMap = this.machine.transitions_components.get(from.state)?.get(top);
|
||||||
|
if (!letterMap) {
|
||||||
|
this.rejected.push(from);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// epsilon transitions
|
||||||
|
const epsilon_transitions = letterMap.get(null) ?? [];
|
||||||
|
for (const to of epsilon_transitions) {
|
||||||
|
this.transition(from, to, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from.position >= this.input.length) {
|
||||||
|
if (epsilon_transitions.length == 0){
|
||||||
|
this.rejected.push(from);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// consuming transitions
|
||||||
|
const ch = this.input.charAt(from.position);
|
||||||
|
|
||||||
|
const transitions = letterMap.get(ch) ?? [];
|
||||||
|
for (const to of transitions) {
|
||||||
|
this.transition(from, to, true);
|
||||||
|
}
|
||||||
|
if (epsilon_transitions.length == 0 && transitions.length == 0){
|
||||||
|
this.rejected.push(from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.accepted.length !== 0) return "accept";
|
||||||
|
if (this.paths.length === 0) return "reject";
|
||||||
|
return "pending";
|
||||||
|
}
|
||||||
|
}
|
||||||
129
web/root/src/simulation/tm.ts
Normal file
129
web/root/src/simulation/tm.ts
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
import type {
|
||||||
|
State,
|
||||||
|
Symbol,
|
||||||
|
Tm,
|
||||||
|
TmTransTo
|
||||||
|
} from "../automata.ts";
|
||||||
|
import { SimStepResult } from "../simulation.ts";
|
||||||
|
|
||||||
|
|
||||||
|
export type TmState = {
|
||||||
|
readonly state: State;
|
||||||
|
readonly tape: Symbol[];
|
||||||
|
readonly head: number;
|
||||||
|
|
||||||
|
readonly accepted: boolean;
|
||||||
|
readonly repr: string;
|
||||||
|
|
||||||
|
readonly path: readonly TmTransTo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Initializer<T> = { -readonly [P in keyof T]?: T[P] | undefined };
|
||||||
|
|
||||||
|
export class TmSim {
|
||||||
|
readonly machine: Tm;
|
||||||
|
paths: TmState[] = [];
|
||||||
|
readonly input: string;
|
||||||
|
|
||||||
|
current_states: Map<string, TmState[]> = new Map();
|
||||||
|
accepted: TmState[] = [];
|
||||||
|
rejected: TmState[] = [];
|
||||||
|
|
||||||
|
constructor(machine: Tm, input: string) {
|
||||||
|
this.machine = machine;
|
||||||
|
this.input = input;
|
||||||
|
this.initial();
|
||||||
|
}
|
||||||
|
|
||||||
|
private init_state(state: Initializer<TmState>) {
|
||||||
|
state.repr = state.state + " [ " + this.machine.blank_symbol + " " + state.tape!.map((s, i, _) => i == state.head ? `[${s}]` : s).join(" ") + " " + this.machine.blank_symbol + " ]";
|
||||||
|
|
||||||
|
|
||||||
|
const frozen = state as TmState;
|
||||||
|
if (frozen.accepted) this.accepted.push(frozen);
|
||||||
|
this.paths.push(frozen);
|
||||||
|
if (!this.current_states.has(frozen.state)) {
|
||||||
|
this.current_states.set(frozen.state, []);
|
||||||
|
}
|
||||||
|
this.current_states.get(frozen.state)!.push(frozen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initial() {
|
||||||
|
const state: Initializer<TmState> = {
|
||||||
|
state: this.machine.initial_state,
|
||||||
|
accepted: this.machine.final_states.has(this.machine.initial_state),
|
||||||
|
tape: this.input.split(''),
|
||||||
|
head: 0,
|
||||||
|
|
||||||
|
path: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (state.tape!.length == 0) state.tape!.push(this.machine.blank_symbol)
|
||||||
|
|
||||||
|
this.init_state(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private transition(from: TmState, to: TmTransTo) {
|
||||||
|
const state: Initializer<TmState> = {
|
||||||
|
state: to.state,
|
||||||
|
accepted: this.machine.final_states.has(to.state),
|
||||||
|
|
||||||
|
path: from.path.concat([to]),
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (to.direction) {
|
||||||
|
case "_":
|
||||||
|
state.tape = from.tape.slice();
|
||||||
|
state.tape![from.head] = to.symbol;
|
||||||
|
state.head = from.head;
|
||||||
|
break;
|
||||||
|
case "<":
|
||||||
|
if (from.head == 0) {
|
||||||
|
state.tape = from.tape.splice(0, 0, to.symbol);
|
||||||
|
state.head = 0;
|
||||||
|
} else {
|
||||||
|
state.tape = from.tape.slice();
|
||||||
|
state.tape![from.head] = to.symbol;
|
||||||
|
state.head = from.head - 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ">":
|
||||||
|
state.head = from.head + 1;
|
||||||
|
state.tape = from.tape.slice();
|
||||||
|
state.tape![from.head] = to.symbol;
|
||||||
|
if (state.head == from.tape.length) {
|
||||||
|
state.tape!.push(this.machine.blank_symbol);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.init_state(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
step(): SimStepResult {
|
||||||
|
if (this.accepted.length != 0) return "accept";
|
||||||
|
if (this.paths.length == 0) return "reject";
|
||||||
|
|
||||||
|
const paths: TmState[] = this.paths;
|
||||||
|
this.paths = [];
|
||||||
|
this.current_states.clear();
|
||||||
|
|
||||||
|
for (const from of paths) {
|
||||||
|
const symbol = from.tape[from.head];
|
||||||
|
const transitions = this.machine.transitions_components.get(from.state)?.get(symbol) ?? [];
|
||||||
|
if (transitions.length == 0) {
|
||||||
|
this.rejected.push(from);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const to of transitions) {
|
||||||
|
this.transition(from, to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.accepted.length != 0) return "accept";
|
||||||
|
if (this.paths.length == 0) return "reject";
|
||||||
|
return "pending";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,46 +3,42 @@
|
||||||
import * as vis from "npm:vis-network/standalone";
|
import * as vis from "npm:vis-network/standalone";
|
||||||
|
|
||||||
import { bus } from "./bus.ts";
|
import { bus } from "./bus.ts";
|
||||||
import type { Sim } from "./simulation.ts";
|
import { automaton, simulation } from "./simulation.ts";
|
||||||
import type { Machine } from "./automata.ts";
|
import { dehighlight_from_edge_id, dehighlight_from_node_id, highlight_from_edge_id, highlight_from_node_id } from "./highlight.ts";
|
||||||
|
|
||||||
|
|
||||||
bus.on("controls/vis/physics", ({enabled}) => {
|
bus.on("controls/vis/physics", ({ enabled }) => {
|
||||||
network.setOptions({ physics: { enabled } });
|
network.setOptions({ physics: { enabled } });
|
||||||
network.setOptions({edges: {smooth: enabled}});
|
network.setOptions({ edges: { smooth: enabled } });
|
||||||
});
|
});
|
||||||
bus.on("controls/vis/reset_network", _ => {
|
bus.on("controls/vis/reset_network", _ => {
|
||||||
try {
|
try {
|
||||||
nodes.forEach((n) => {
|
nodes.forEach((n) => {
|
||||||
n.physics = true;
|
n.physics = true;
|
||||||
n.x = undefined;
|
n.x = undefined;
|
||||||
n.y = undefined;
|
n.y = undefined;
|
||||||
});
|
});
|
||||||
network.setData({ nodes, edges });
|
network.setData({ nodes, edges });
|
||||||
} catch {
|
} catch {
|
||||||
// Last resort
|
// Last resort
|
||||||
network.setData({ nodes, edges });
|
network.setData({ nodes, edges });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
bus.on("automata/sim/after_step", _ => {
|
bus.on("automata/sim/after_step", _ => {
|
||||||
network.redraw();
|
network.redraw();
|
||||||
});
|
});
|
||||||
|
|
||||||
let simulation: Sim | null = null;
|
bus.on("automata/sim/update", _ => {
|
||||||
bus.on("automata/sim/update", ({simulation: sim}) => {
|
|
||||||
simulation = sim;
|
|
||||||
network.redraw();
|
network.redraw();
|
||||||
});
|
});
|
||||||
|
|
||||||
let automaton: Machine
|
bus.on("automata/update", automaton => {
|
||||||
|
|
||||||
bus.on("automata/update", ({automaton: auto}) => {
|
|
||||||
automaton = auto;
|
|
||||||
// Populate nodes
|
// Populate nodes
|
||||||
for (const state of automaton.states.keys()) {
|
for (const state of automaton.states.keys()) {
|
||||||
|
|
||||||
const size = measureTextWidth(state, getGraphTheme().node_font)/2+10
|
const size = measureTextWidth(state, getGraphTheme().node_font) / 2 + 10
|
||||||
if (nodes.get(state)) {
|
if (nodes.get(state)) {
|
||||||
nodes.update({
|
nodes.update({
|
||||||
id: state,
|
id: state,
|
||||||
|
|
@ -62,20 +58,20 @@ bus.on("automata/update", ({automaton: auto}) => {
|
||||||
for (const [edge_id, transitions] of automaton.edges) {
|
for (const [edge_id, transitions] of automaton.edges) {
|
||||||
const to_from = edge_id.split("#");
|
const to_from = edge_id.split("#");
|
||||||
const vadjust = -getGraphTheme().edge_font_size *
|
const vadjust = -getGraphTheme().edge_font_size *
|
||||||
Math.floor(transitions.length / 2);
|
Math.floor(transitions.length / 2);
|
||||||
const font = {
|
const font = {
|
||||||
vadjust,
|
vadjust,
|
||||||
bold: {
|
bold: {
|
||||||
vadjust
|
vadjust
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (edges.get(edge_id)) {
|
if (edges.get(edge_id)) {
|
||||||
edges.update({
|
edges.update({
|
||||||
id: edge_id,
|
id: edge_id,
|
||||||
font,
|
font,
|
||||||
from: to_from[0],
|
from: to_from[0],
|
||||||
to: to_from[1],
|
to: to_from[1],
|
||||||
label: transitions.map(i => i.repr).join(automaton.type=="fa"?",":"\n"),
|
label: transitions.map(i => i.repr).join(automaton.type == "fa" ? "," : "\n"),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
edges.add({
|
edges.add({
|
||||||
|
|
@ -83,7 +79,7 @@ bus.on("automata/update", ({automaton: auto}) => {
|
||||||
font,
|
font,
|
||||||
from: to_from[0],
|
from: to_from[0],
|
||||||
to: to_from[1],
|
to: to_from[1],
|
||||||
label: transitions.map(i => i.repr).join(automaton.type=="fa"?",":"\n"),
|
label: transitions.map(i => i.repr).join(automaton.type == "fa" ? "," : "\n"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -238,22 +234,6 @@ function measureTextWidth(text: string, font: string): number {
|
||||||
return ctx.measureText(text).width;
|
return ctx.measureText(text).width;
|
||||||
}
|
}
|
||||||
|
|
||||||
function chosen_edge(
|
|
||||||
_: vis.ChosenNodeValues,
|
|
||||||
id: vis.IdType,
|
|
||||||
selected: boolean,
|
|
||||||
hovered: boolean,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
function chosen_node(
|
|
||||||
_: vis.ChosenNodeValues,
|
|
||||||
id: vis.IdType,
|
|
||||||
selected: boolean,
|
|
||||||
hovered: boolean,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
const network: vis.Network = createGraph();
|
const network: vis.Network = createGraph();
|
||||||
|
|
||||||
function createGraph(): vis.Network {
|
function createGraph(): vis.Network {
|
||||||
|
|
@ -286,16 +266,9 @@ function createGraph(): vis.Network {
|
||||||
shape: "custom",
|
shape: "custom",
|
||||||
size: 18,
|
size: 18,
|
||||||
// @ts-expect-error bad library
|
// @ts-expect-error bad library
|
||||||
chosen: {
|
|
||||||
node: chosen_node,
|
|
||||||
},
|
|
||||||
ctxRenderer: renderNode,
|
ctxRenderer: renderNode,
|
||||||
},
|
},
|
||||||
edges: {
|
edges: {
|
||||||
chosen: {
|
|
||||||
// @ts-expect-error bad library
|
|
||||||
edge: chosen_edge,
|
|
||||||
},
|
|
||||||
arrowStrikethrough: false,
|
arrowStrikethrough: false,
|
||||||
arrows: "to",
|
arrows: "to",
|
||||||
},
|
},
|
||||||
|
|
@ -303,7 +276,7 @@ function createGraph(): vis.Network {
|
||||||
);
|
);
|
||||||
vis.DataSet;
|
vis.DataSet;
|
||||||
|
|
||||||
network.on("doubleClick", (params: {nodes: string[]}) => {
|
network.on("doubleClick", (params: { nodes: string[] }) => {
|
||||||
for (const node_id of params.nodes) {
|
for (const node_id of params.nodes) {
|
||||||
const node: vis.Node = nodes.get(node_id)!;
|
const node: vis.Node = nodes.get(node_id)!;
|
||||||
node.physics = !node.physics;
|
node.physics = !node.physics;
|
||||||
|
|
@ -311,6 +284,49 @@ function createGraph(): vis.Network {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
network.on("hoverEdge", ({ edge }: { edge: string }) => {
|
||||||
|
highlight_from_edge_id(edge)
|
||||||
|
});
|
||||||
|
|
||||||
|
network.on('blurEdge', ({edge}: {edge: string}) => {
|
||||||
|
dehighlight_from_edge_id(edge)
|
||||||
|
});
|
||||||
|
|
||||||
|
network.on("hoverNode", ({ node }: { node: string }) => {
|
||||||
|
highlight_from_node_id(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
network.on('blurNode', ({ node }: { node: string }) => {
|
||||||
|
dehighlight_from_node_id(node)
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
network.on("selectEdge", item => {
|
||||||
|
const id = network.getEdgeAt(item.pointer.DOM);
|
||||||
|
if(id)highlight_from_edge_id(id as string);
|
||||||
|
});
|
||||||
|
|
||||||
|
network.on('deselectEdge', item => {
|
||||||
|
console.log(item);
|
||||||
|
for (const edge of item.previousSelection.edges){
|
||||||
|
console.log(edge);
|
||||||
|
dehighlight_from_edge_id(edge.id)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
network.on("selectNode", item => {
|
||||||
|
const id = network.getNodeAt(item.pointer.DOM);
|
||||||
|
if(id)highlight_from_node_id(id as string);
|
||||||
|
});
|
||||||
|
|
||||||
|
network.on('deselectNode', item => {
|
||||||
|
console.log(item);
|
||||||
|
for (const node of item.previousSelection.nodes){
|
||||||
|
console.log(node);
|
||||||
|
dehighlight_from_node_id(node.id)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return network;
|
return network;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -323,7 +339,7 @@ function renderNode({
|
||||||
state: { selected, hover },
|
state: { selected, hover },
|
||||||
style,
|
style,
|
||||||
label,
|
label,
|
||||||
}: {ctx: CanvasRenderingContext2D, id: string, x: number, y: number, state: {selected: boolean, hover: boolean}, style: vis.NodeOptions, label: string}) {
|
}: { ctx: CanvasRenderingContext2D, id: string, x: number, y: number, state: { selected: boolean, hover: boolean }, style: vis.NodeOptions, label: string }) {
|
||||||
return {
|
return {
|
||||||
drawNode() {
|
drawNode() {
|
||||||
const t = getGraphTheme();
|
const t = getGraphTheme();
|
||||||
|
|
@ -333,7 +349,7 @@ function renderNode({
|
||||||
const isFinal = automaton.final_states
|
const isFinal = automaton.final_states
|
||||||
? automaton.final_states.has(id)
|
? automaton.final_states.has(id)
|
||||||
: false;
|
: false;
|
||||||
const isActive = simulation?simulation.current_states.has(id):false;
|
const isActive = simulation ? simulation.current_states.has(id) : false;
|
||||||
|
|
||||||
const fill = selected ? t.bg_2 : hover ? t.bg_1 : t.bg_0;
|
const fill = selected ? t.bg_2 : hover ? t.bg_1 : t.bg_0;
|
||||||
const stroke = isActive ? t.current_node_border : t.node_border;
|
const stroke = isActive ? t.current_node_border : t.node_border;
|
||||||
|
|
@ -345,7 +361,7 @@ function renderNode({
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
|
|
||||||
ctx.font = hover||selected?t.node_font_bold:t.node_font;
|
ctx.font = hover || selected ? t.node_font_bold : t.node_font;
|
||||||
ctx.textAlign = "center";
|
ctx.textAlign = "center";
|
||||||
ctx.textBaseline = "middle";
|
ctx.textBaseline = "middle";
|
||||||
|
|
||||||
|
|
@ -382,7 +398,7 @@ function renderNode({
|
||||||
const lineH = 14;
|
const lineH = 14;
|
||||||
|
|
||||||
let w = 0;
|
let w = 0;
|
||||||
for (const ln of paths) w = Math.max(w, ctx.measureText(ln.toString()).width);
|
for (const ln of paths) w = Math.max(w, ctx.measureText(ln.repr).width);
|
||||||
const boxW = w + padX * 2;
|
const boxW = w + padX * 2;
|
||||||
const boxH = paths.length * lineH + padY * 2;
|
const boxH = paths.length * lineH + padY * 2;
|
||||||
|
|
||||||
|
|
@ -398,8 +414,8 @@ function renderNode({
|
||||||
|
|
||||||
ctx.textBaseline = "top";
|
ctx.textBaseline = "top";
|
||||||
for (let i = 0; i < paths.length; i++) {
|
for (let i = 0; i < paths.length; i++) {
|
||||||
ctx.fillStyle = paths[i].accepted?t.current_node_border:t.fg_0;
|
ctx.fillStyle = paths[i].accepted ? t.current_node_border : t.fg_0;
|
||||||
ctx.fillText(paths[i].toString(), x, by + padY + i * lineH);
|
ctx.fillText(paths[i].repr, x, by + padY + i * lineH);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -132,3 +132,14 @@
|
||||||
text-underline-offset: 2px;
|
text-underline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.cm-highlight {
|
||||||
|
border-radius: 4px;
|
||||||
|
// padding: 0 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-highlight-warning { background: color-mix(in srgb, var(--warning) 40%, var(--bg-0)); }
|
||||||
|
.cm-highlight-focus { background: color-mix(in srgb, var(--focus) 40%, var(--bg-0)); }
|
||||||
|
.cm-highlight-success { background: color-mix(in srgb, var(--success) 40%, var(--bg-0)); }
|
||||||
|
.cm-highlight-error { background: color-mix(in srgb, var(--error) 40%, var(--bg-0)); }
|
||||||
|
|
@ -94,7 +94,7 @@ pub fn lex(input: &str) -> Vec<Tok> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ugly hack to keep single ascii letters non keyworded for user
|
// 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(ident) if ident.is_ascii() && ident.len() == 1 => Kind::Ident,
|
||||||
Token::Ident(
|
Token::Ident(
|
||||||
epsilon!(pat) | delta_lower!(pat) | sigma_upper!(pat) | gamma_upper!(pat),
|
epsilon!(pat) | delta_lower!(pat) | sigma_upper!(pat) | gamma_upper!(pat),
|
||||||
) => Kind::Keyword,
|
) => Kind::Keyword,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue