mirror of
https://github.com/ParkerTenBroeck/automata.git
synced 2026-06-07 05:28:45 -04:00
sync
This commit is contained in:
parent
f1b8c08e8f
commit
7971c61c74
15 changed files with 41485 additions and 252 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
|
type=NPDA
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,13 @@ pub mod npda;
|
||||||
pub mod ntm;
|
pub mod ntm;
|
||||||
pub mod tm;
|
pub mod tm;
|
||||||
|
|
||||||
pub trait Get<Idx>{
|
pub trait Get<Idx> {
|
||||||
type Output;
|
type Output;
|
||||||
fn get(&self, idx: Idx) -> Option<&Self::Output>;
|
fn get(&self, idx: Idx) -> Option<&Self::Output>;
|
||||||
fn get_mut(&mut self, idx: Idx) -> Option<&mut Self::Output>;
|
fn get_mut(&mut self, idx: Idx) -> Option<&mut Self::Output>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait GetDefault<Idx>{
|
pub trait GetDefault<Idx> {
|
||||||
type Output: Default;
|
type Output: Default;
|
||||||
fn get_or_insert_default(&mut self, idx: Idx) -> &Self::Output;
|
fn get_or_insert_default(&mut self, idx: Idx) -> &Self::Output;
|
||||||
fn get_mut_or_insert_default(&mut self, idx: Idx) -> &mut Self::Output;
|
fn get_mut_or_insert_default(&mut self, idx: Idx) -> &mut Self::Output;
|
||||||
|
|
@ -67,7 +67,6 @@ pub struct State(u16);
|
||||||
#[derive(Clone, Debug, Copy, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Copy, Hash, PartialEq, Eq)]
|
||||||
pub struct Symbol(u16);
|
pub struct Symbol(u16);
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct StateMap<T>(Vec<T>);
|
pub struct StateMap<T>(Vec<T>);
|
||||||
|
|
||||||
|
|
@ -84,18 +83,42 @@ pub struct StateSymbolMap<T> {
|
||||||
max_state: u16,
|
max_state: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
index!(
|
||||||
index!(StateSymbolMap, self, self.map, state.0 as usize + self.max_state as usize * symbol.0 as usize, (state, symbol) = (State, Symbol));
|
StateSymbolMap,
|
||||||
index!(StateSymbolMap, self, self.map, state.0 as usize + self.max_state as usize * symbol.0 as usize, (symbol, state) = (Symbol, State));
|
self,
|
||||||
|
self.map,
|
||||||
|
state.0 as usize + self.max_state as usize * symbol.0 as usize,
|
||||||
|
(state, symbol) = (State, Symbol)
|
||||||
|
);
|
||||||
|
index!(
|
||||||
|
StateSymbolMap,
|
||||||
|
self,
|
||||||
|
self.map,
|
||||||
|
state.0 as usize + self.max_state as usize * symbol.0 as usize,
|
||||||
|
(symbol, state) = (Symbol, State)
|
||||||
|
);
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct CharMap<T>(HashMap<char, T>);
|
pub struct CharMap<T>(HashMap<char, T>);
|
||||||
|
|
||||||
index!(CharMap, self, self.0, &char, char = char, self.0.entry(char).or_default());
|
index!(
|
||||||
|
CharMap,
|
||||||
|
self,
|
||||||
|
self.0,
|
||||||
|
&char,
|
||||||
|
char = char,
|
||||||
|
self.0.entry(char).or_default()
|
||||||
|
);
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct CharEpsilonMap<T>(HashMap<Option<char>, T>);
|
pub struct CharEpsilonMap<T>(HashMap<Option<char>, T>);
|
||||||
|
|
||||||
index!(CharEpsilonMap, self, self.0, &Some(char), char = char, self.0.entry(Some(char)).or_default());
|
index!(
|
||||||
|
CharEpsilonMap,
|
||||||
|
self,
|
||||||
|
self.0,
|
||||||
|
&Some(char),
|
||||||
|
char = char,
|
||||||
|
self.0.entry(Some(char)).or_default()
|
||||||
|
);
|
||||||
index!(CharEpsilonMap, self, self.0, &char, char = Option<char>, self.0.entry(char).or_default());
|
index!(CharEpsilonMap, self, self.0, &char, char = Option<char>, self.0.entry(char).or_default());
|
||||||
|
|
@ -31,15 +31,14 @@ pub struct Simulator {
|
||||||
running: Vec<NPDA>,
|
running: Vec<NPDA>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum SimulatorResult{
|
pub enum SimulatorResult {
|
||||||
Pending,
|
Pending,
|
||||||
Reject,
|
Reject,
|
||||||
Accept(NPDA)
|
Accept(NPDA),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Simulator {
|
impl Simulator {
|
||||||
pub fn begin(input: impl Into<String>, table: TransitionTable) -> Self {
|
pub fn begin(input: impl Into<String>, table: TransitionTable) -> Self {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
input: input.into(),
|
input: input.into(),
|
||||||
running: vec![NPDA {
|
running: vec![NPDA {
|
||||||
|
|
@ -110,9 +109,9 @@ impl Simulator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.running = new;
|
self.running = new;
|
||||||
if self.running.is_empty(){
|
if self.running.is_empty() {
|
||||||
SimulatorResult::Reject
|
SimulatorResult::Reject
|
||||||
}else{
|
} else {
|
||||||
SimulatorResult::Pending
|
SimulatorResult::Pending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -153,10 +152,9 @@ impl TransitionTable {
|
||||||
|
|
||||||
for Spanned(element, span) in ast {
|
for Spanned(element, span) in ast {
|
||||||
use Spanned as S;
|
use Spanned as S;
|
||||||
use ast::Dest;
|
|
||||||
use ast::TopLevel as TL;
|
use ast::TopLevel as TL;
|
||||||
match element {
|
match element {
|
||||||
TL::Assignment(S(Dest::Ident("Q"), _), list) => {
|
TL::Item(S("Q", _), list) => {
|
||||||
if !states.is_empty() {
|
if !states.is_empty() {
|
||||||
logs.emit_error("states already set", *span);
|
logs.emit_error("states already set", *span);
|
||||||
}
|
}
|
||||||
|
|
@ -183,7 +181,7 @@ impl TransitionTable {
|
||||||
logs.emit_error("states cannot be empty", *span);
|
logs.emit_error("states cannot be empty", *span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TL::Assignment(S(Dest::Ident("E" | SIGMA_UPPER | "sigma"), _), list) => {
|
TL::Item(S("E" | SIGMA_UPPER | "sigma", _), list) => {
|
||||||
if !alphabet.is_empty() {
|
if !alphabet.is_empty() {
|
||||||
logs.emit_error("alphabet already set", *span);
|
logs.emit_error("alphabet already set", *span);
|
||||||
}
|
}
|
||||||
|
|
@ -207,7 +205,7 @@ impl TransitionTable {
|
||||||
logs.emit_error("alphabet cannot be empty", *span);
|
logs.emit_error("alphabet cannot be empty", *span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TL::Assignment(S(Dest::Ident("F"), _), list) => {
|
TL::Item(S("F", _), list) => {
|
||||||
if final_states.is_some() {
|
if final_states.is_some() {
|
||||||
logs.emit_error("final states already set", *span);
|
logs.emit_error("final states already set", *span);
|
||||||
}
|
}
|
||||||
|
|
@ -219,17 +217,17 @@ impl TransitionTable {
|
||||||
let Some(ident) = item.expect_ident(&mut logs) else {
|
let Some(ident) = item.expect_ident(&mut logs) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if let Some(state) = states.get(ident){
|
if let Some(state) = states.get(ident) {
|
||||||
if !map.insert(*state) {
|
if !map.insert(*state) {
|
||||||
logs.emit_error("final state redefined", item.1);
|
logs.emit_error("final state redefined", item.1);
|
||||||
}
|
}
|
||||||
} else{
|
} else {
|
||||||
logs.emit_error("final state not defined in set of states", item.1);
|
logs.emit_error("final state not defined in set of states", item.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final_states = Some(map);
|
final_states = Some(map);
|
||||||
}
|
}
|
||||||
TL::Assignment(S(Dest::Ident("T" | GAMMA_UPPER | "gamma"), _), list) => {
|
TL::Item(S("T" | GAMMA_UPPER | "gamma", _), list) => {
|
||||||
if !stack_symbols.is_empty() {
|
if !stack_symbols.is_empty() {
|
||||||
logs.emit_error("stack symbols already set", *span);
|
logs.emit_error("stack symbols already set", *span);
|
||||||
}
|
}
|
||||||
|
|
@ -256,7 +254,7 @@ impl TransitionTable {
|
||||||
logs.emit_error("stack symbols cannot be empty", *span);
|
logs.emit_error("stack symbols cannot be empty", *span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TL::Assignment(S(Dest::Ident("I" | "q0"), _), S(src, src_d)) => match src {
|
TL::Item(S("I" | "q0", _), S(src, src_d)) => match src {
|
||||||
ast::Item::Symbol(Sym::Ident(ident)) => {
|
ast::Item::Symbol(Sym::Ident(ident)) => {
|
||||||
if initial_state.is_some() {
|
if initial_state.is_some() {
|
||||||
logs.emit_error("initial state already set", *span);
|
logs.emit_error("initial state already set", *span);
|
||||||
|
|
@ -269,7 +267,7 @@ impl TransitionTable {
|
||||||
}
|
}
|
||||||
_ => logs.emit_error("expected ident", *src_d),
|
_ => logs.emit_error("expected ident", *src_d),
|
||||||
},
|
},
|
||||||
TL::Assignment(S(Dest::Ident("S" | "z0"), _), S(src, src_d)) => match src {
|
TL::Item(S("S" | "z0", _), S(src, src_d)) => match src {
|
||||||
ast::Item::Symbol(Sym::Ident(ident)) => {
|
ast::Item::Symbol(Sym::Ident(ident)) => {
|
||||||
if initial_stack.is_some() {
|
if initial_stack.is_some() {
|
||||||
logs.emit_error("initial stack already set", *span);
|
logs.emit_error("initial stack already set", *span);
|
||||||
|
|
@ -285,12 +283,12 @@ impl TransitionTable {
|
||||||
}
|
}
|
||||||
_ => logs.emit_error("expected ident", *src_d),
|
_ => logs.emit_error("expected ident", *src_d),
|
||||||
},
|
},
|
||||||
TL::Assignment(S(Dest::Ident(name), dest_s), _) => {
|
TL::Item(S(name, dest_s), _) => {
|
||||||
logs.emit_error(format!("unknown item {name:?}, expected 'Q'|'E'|'{SIGMA_UPPER}'|'sigma'|'F'|'T'|'{GAMMA_UPPER}'|'gamma'|'I'|'q0'|'S'|'z0'"), *dest_s);
|
logs.emit_error(format!("unknown item {name:?}, expected 'Q'|'E'|'{SIGMA_UPPER}'|'sigma'|'F'|'T'|'{GAMMA_UPPER}'|'gamma'|'I'|'q0'|'S'|'z0'"), *dest_s);
|
||||||
}
|
}
|
||||||
|
|
||||||
TL::Assignment(
|
TL::TransitionFunc(
|
||||||
S(Dest::Function(S("d" | DELTA_LOWER | "delta", _), tuple), _),
|
S((S("d" | DELTA_LOWER | "delta", _), tuple), _),
|
||||||
list,
|
list,
|
||||||
) => {
|
) => {
|
||||||
let list = list.set_weak();
|
let list = list.set_weak();
|
||||||
|
|
@ -299,7 +297,7 @@ impl TransitionTable {
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(state) = states.get(state.0).copied() else{
|
let Some(state) = states.get(state.0).copied() else {
|
||||||
logs.emit_error("transition state not defined as state", state.1);
|
logs.emit_error("transition state not defined as state", state.1);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
@ -313,18 +311,25 @@ impl TransitionTable {
|
||||||
|
|
||||||
let char = match letter.0 {
|
let char = match letter.0 {
|
||||||
Sym::Epsilon => None,
|
Sym::Epsilon => None,
|
||||||
Sym::Ident(val) => if let Some(char) = val.chars().next() && val.chars().count() == 1 {
|
Sym::Ident(val) => {
|
||||||
if !alphabet.contains(&char){
|
if let Some(char) = val.chars().next()
|
||||||
logs.emit_error("transition letter not defined in alphabet", letter.1);
|
&& val.chars().count() == 1
|
||||||
|
{
|
||||||
|
if !alphabet.contains(&char) {
|
||||||
|
logs.emit_error(
|
||||||
|
"transition letter not defined in alphabet",
|
||||||
|
letter.1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Some(char)
|
||||||
|
} else {
|
||||||
|
logs.emit_error(
|
||||||
|
"transition letter can only be single character",
|
||||||
|
letter.1,
|
||||||
|
);
|
||||||
|
None
|
||||||
}
|
}
|
||||||
Some(char)
|
}
|
||||||
}else{
|
|
||||||
logs.emit_error(
|
|
||||||
"transition letter can only be single character",
|
|
||||||
letter.1,
|
|
||||||
);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for item in list {
|
for item in list {
|
||||||
|
|
@ -340,28 +345,36 @@ impl TransitionTable {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let stack: Vec<_> = stack.iter().rev().filter_map(|symbol|{
|
let stack: Vec<_> = stack
|
||||||
if matches!(symbol.0, ast::Item::Symbol(Sym::Epsilon)) {
|
.iter()
|
||||||
return None;
|
.rev()
|
||||||
}
|
.filter_map(|symbol| {
|
||||||
let ident = symbol.expect_ident(&mut logs)?;
|
if matches!(symbol.0, ast::Item::Symbol(Sym::Epsilon)) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let ident = symbol.expect_ident(&mut logs)?;
|
||||||
|
|
||||||
let Some(symbol) = stack_symbols.get(ident).copied() else{
|
let Some(symbol) = stack_symbols.get(ident).copied() else {
|
||||||
logs.emit_error("transition stack symbol not defined", symbol.1);
|
logs.emit_error(
|
||||||
return None;
|
"transition stack symbol not defined",
|
||||||
};
|
symbol.1,
|
||||||
Some(symbol)
|
);
|
||||||
}).collect();
|
return None;
|
||||||
|
};
|
||||||
|
Some(symbol)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
if !transitions_map
|
if !transitions_map
|
||||||
.entry((state, char, stack_symbol))
|
.entry((state, char, stack_symbol))
|
||||||
.or_insert(HashSet::new())
|
.or_insert(HashSet::new())
|
||||||
.insert((next_state, stack)) {
|
.insert((next_state, stack))
|
||||||
logs.emit_warning("duplicate transition", item.1);
|
{
|
||||||
}
|
logs.emit_warning("duplicate transition", item.1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TL::Assignment(S(Dest::Function(S(name, _), _), dest_s), _) => {
|
TL::TransitionFunc(S((S(name, _), _), dest_s), _) => {
|
||||||
logs.emit_error(
|
logs.emit_error(
|
||||||
format!("unknown function {name:?}, expected 'd'|'delta'|'{DELTA_LOWER}'"),
|
format!("unknown function {name:?}, expected 'd'|'delta'|'{DELTA_LOWER}'"),
|
||||||
*dest_s,
|
*dest_s,
|
||||||
|
|
@ -429,21 +442,21 @@ impl TransitionTable {
|
||||||
a
|
a
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
let final_states = final_states.map(|f|{
|
let final_states = final_states.map(|f| {
|
||||||
StateMap(f.iter().fold(vec![false; states.len()], |mut a, k|{
|
StateMap(f.iter().fold(vec![false; states.len()], |mut a, k| {
|
||||||
a[k.0 as usize] = true;
|
a[k.0 as usize] = true;
|
||||||
a
|
a
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut transitions: StateSymbolMap<CharEpsilonMap<Vec<To>>> = StateSymbolMap{
|
let mut transitions: StateSymbolMap<CharEpsilonMap<Vec<To>>> = StateSymbolMap {
|
||||||
map: vec![CharEpsilonMap::default(); stack_symbols.len() * states.len()],
|
map: vec![CharEpsilonMap::default(); stack_symbols.len() * states.len()],
|
||||||
max_state: states.len() as u16,
|
max_state: states.len() as u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
for ((q, c, s), to) in transitions_map{
|
for ((q, c, s), to) in transitions_map {
|
||||||
let from = &mut transitions[(q, s)];
|
let from = &mut transitions[(q, s)];
|
||||||
for (n, ss) in to{
|
for (n, ss) in to {
|
||||||
from.get_mut_or_insert_default(c).push(To(n, ss));
|
from.get_mut_or_insert_default(c).push(To(n, ss));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,15 @@ use std::ops::Range;
|
||||||
|
|
||||||
use super::Spanned;
|
use super::Spanned;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum ListKind {
|
||||||
|
Brace,
|
||||||
|
Bracket,
|
||||||
|
|
||||||
|
BraceComma,
|
||||||
|
BracketComma,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Tuple<'a>(pub Vec<Spanned<Item<'a>>>);
|
pub struct Tuple<'a>(pub Vec<Spanned<Item<'a>>>);
|
||||||
|
|
||||||
|
|
@ -11,12 +20,6 @@ pub enum Symbol<'a> {
|
||||||
Ident(&'a str),
|
Ident(&'a str),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum Dest<'a> {
|
|
||||||
Ident(&'a str),
|
|
||||||
Function(Spanned<&'a str>, Spanned<Tuple<'a>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Item<'a> {
|
pub enum Item<'a> {
|
||||||
Symbol(Symbol<'a>),
|
Symbol(Symbol<'a>),
|
||||||
|
|
@ -40,12 +43,19 @@ pub enum Regex<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct List<'a>(pub Vec<Spanned<Item<'a>>>);
|
pub struct List<'a>(pub Vec<Spanned<Item<'a>>>, pub ListKind);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ProductionGroup<'a>(pub Vec<Spanned<Symbol<'a>>>);
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum TopLevel<'a> {
|
pub enum TopLevel<'a> {
|
||||||
Assignment(Spanned<Dest<'a>>, Spanned<Item<'a>>),
|
Item(Spanned<&'a str>, Spanned<Item<'a>>),
|
||||||
ProductionRule(Spanned<Symbol<'a>>, Spanned<Symbol<'a>>),
|
TransitionFunc(Spanned<(Spanned<&'a str>, Spanned<Tuple<'a>>)>, Spanned<Item<'a>>),
|
||||||
|
ProductionRule(
|
||||||
|
Spanned<ProductionGroup<'a>>,
|
||||||
|
Spanned<Vec<Spanned<ProductionGroup<'a>>>>,
|
||||||
|
),
|
||||||
Table(),
|
Table(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,14 @@ pub enum Token<'a> {
|
||||||
Comment(&'a str),
|
Comment(&'a str),
|
||||||
|
|
||||||
Ident(&'a str),
|
Ident(&'a str),
|
||||||
|
LineEnd,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> std::fmt::Display for Token<'a> {
|
impl<'a> std::fmt::Display for Token<'a> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Token::LPar => write!(f, "')'"),
|
Token::LPar => write!(f, "'('"),
|
||||||
Token::RPar => write!(f, "'('"),
|
Token::RPar => write!(f, "')'"),
|
||||||
Token::LBrace => write!(f, "'{{'"),
|
Token::LBrace => write!(f, "'{{'"),
|
||||||
Token::RBrace => write!(f, "'}}'"),
|
Token::RBrace => write!(f, "'}}'"),
|
||||||
Token::LBracket => write!(f, "'['"),
|
Token::LBracket => write!(f, "'['"),
|
||||||
|
|
@ -49,6 +50,7 @@ impl<'a> std::fmt::Display for Token<'a> {
|
||||||
Token::Comment(_) => write!(f, "<comment>"),
|
Token::Comment(_) => write!(f, "<comment>"),
|
||||||
Token::Ident(ident) if f.alternate() => write!(f, "{ident:?}"),
|
Token::Ident(ident) if f.alternate() => write!(f, "{ident:?}"),
|
||||||
Token::Ident(_) => write!(f, "ident"),
|
Token::Ident(_) => write!(f, "ident"),
|
||||||
|
Token::LineEnd => write!(f, "eol"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -118,7 +120,15 @@ impl<'a> std::iter::Iterator for Lexer<'a> {
|
||||||
while let Some(c) = self.peek()
|
while let Some(c) = self.peek()
|
||||||
&& c.is_whitespace()
|
&& c.is_whitespace()
|
||||||
{
|
{
|
||||||
self.consume();
|
if c == '\n'{
|
||||||
|
self.start = self.position;
|
||||||
|
self.consume();
|
||||||
|
let res = Some(Spanned(Ok(Token::LineEnd), Span(self.start, self.position)));
|
||||||
|
self.start = self.position;
|
||||||
|
return res;
|
||||||
|
}else{
|
||||||
|
self.consume();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.start = self.position;
|
self.start = self.position;
|
||||||
|
|
||||||
|
|
@ -153,7 +163,8 @@ impl<'a> std::iter::Iterator for Lexer<'a> {
|
||||||
'/' => match self.consume() {
|
'/' => match self.consume() {
|
||||||
Some('/') => loop {
|
Some('/') => loop {
|
||||||
if let Some('\n') | None = self.consume() {
|
if let Some('\n') | None = self.consume() {
|
||||||
break Ok(Token::Comment(&self.input[self.start + 2..self.position]));
|
self.backtrack();
|
||||||
|
break Ok(Token::Comment(&self.input[self.start + 2..=self.position]));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some('*') => loop {
|
Some('*') => loop {
|
||||||
|
|
|
||||||
|
|
@ -73,15 +73,15 @@ impl<'a> Logs<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn entries(&self) -> &[LogEntry]{
|
pub fn entries(&self) -> &[LogEntry] {
|
||||||
&self.logs
|
&self.logs
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_entries(self) -> impl Iterator<Item = LogEntry>{
|
pub fn into_entries(self) -> impl Iterator<Item = LogEntry> {
|
||||||
self.logs.into_iter()
|
self.logs.into_iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn src(&self) -> &str{
|
pub fn src(&self) -> &str {
|
||||||
&self.src
|
&self.src
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -134,15 +134,24 @@ impl<'a> Display for LogEntryDisplay<'a> {
|
||||||
.map(|v| v + 1)
|
.map(|v| v + 1)
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
let end = self
|
let end = if self.src.get(..span.1).unwrap_or("").ends_with("\n") {
|
||||||
.src
|
span.1
|
||||||
.get(span.1..)
|
} else {
|
||||||
.and_then(|s| s.find('\n'))
|
self.src
|
||||||
.map(|v| v + span.1)
|
.get(span.1..)
|
||||||
.unwrap_or(self.src.len());
|
.and_then(|s| s.find('\n'))
|
||||||
|
.map(|v| v + span.1)
|
||||||
|
.unwrap_or(self.src.len())
|
||||||
|
};
|
||||||
|
|
||||||
let mut index = start;
|
let mut index = start;
|
||||||
for (i, line) in self.src.get(start..end).unwrap_or("").lines().enumerate() {
|
for (i, line) in self
|
||||||
|
.src
|
||||||
|
.get(start..end)
|
||||||
|
.unwrap_or("")
|
||||||
|
.split_inclusive("\n")
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
write!(f, "{BOLD}{CYAN}{:>padding$}: {RESET}", i + line_start)?;
|
write!(f, "{BOLD}{CYAN}{:>padding$}: {RESET}", i + line_start)?;
|
||||||
for char in line.chars() {
|
for char in line.chars() {
|
||||||
if char == '\t' {
|
if char == '\t' {
|
||||||
|
|
@ -151,7 +160,9 @@ impl<'a> Display for LogEntryDisplay<'a> {
|
||||||
write!(f, "{char}")?
|
write!(f, "{char}")?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeln!(f)?;
|
if !line.ends_with("\n") {
|
||||||
|
writeln!(f)?;
|
||||||
|
}
|
||||||
write!(f, "{BOLD}{CYAN}")?;
|
write!(f, "{BOLD}{CYAN}")?;
|
||||||
for _ in 0..padding + 3 {
|
for _ in 0..padding + 3 {
|
||||||
write!(f, " ")?;
|
write!(f, " ")?;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use crate::loader::log::{LogEntryDisplay, Logs};
|
use crate::loader::log::{LogEntryDisplay, Logs};
|
||||||
use crate::loader::{Span, Spanned};
|
use crate::loader::{EPSILON_LOWER, Span, Spanned};
|
||||||
|
|
||||||
use super::ast::*;
|
use super::ast::*;
|
||||||
use super::lexer::{Lexer, Token};
|
use super::lexer::{Lexer, Token};
|
||||||
|
|
||||||
pub struct Parser<'a> {
|
pub struct Parser<'a> {
|
||||||
lexer: Lexer<'a>,
|
lexer: Lexer<'a>,
|
||||||
peek: Option<Spanned<Token<'a>>>,
|
peek: Spanned<Option<Token<'a>>>,
|
||||||
logs: Logs<'a>,
|
logs: Logs<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -14,30 +14,43 @@ impl<'a> Parser<'a> {
|
||||||
pub fn new(lexer: Lexer<'a>) -> Self {
|
pub fn new(lexer: Lexer<'a>) -> Self {
|
||||||
Parser {
|
Parser {
|
||||||
logs: Logs::new(lexer.input()),
|
logs: Logs::new(lexer.input()),
|
||||||
peek: None,
|
peek: Spanned(None, Span(0,0)),
|
||||||
lexer,
|
lexer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eof(&self) -> Span{
|
fn eof(&self) -> Span {
|
||||||
self.lexer.eof_span()
|
self.lexer.eof_span()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_token(&mut self) -> Option<Spanned<Token<'a>>> {
|
fn advance_line(&mut self) {
|
||||||
if self.peek.is_some(){
|
if self.expect_token(Token::LineEnd).0 {
|
||||||
return self.peek.take()
|
self.peek = Spanned(None, Span(0,0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_token(&mut self) -> Spanned<Option<Token<'a>>> {
|
||||||
|
match self.peek.0 {
|
||||||
|
Some(Token::LineEnd) => return self.peek,
|
||||||
|
Some(_) => return Spanned(self.peek.0.take(), self.peek.1),
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
loop {
|
loop {
|
||||||
match self.lexer.next()? {
|
match self.lexer.next() {
|
||||||
Spanned(Ok(Token::Comment(_)), _) => {}
|
Some(Spanned(Ok(Token::Comment(_)), _)) => {}
|
||||||
Spanned(Ok(ok), r) => return Some(Spanned(ok, r)),
|
Some(Spanned(Ok(Token::LineEnd), span)) => {
|
||||||
Spanned(Err(err), span) => self.logs.emit_error(format!("lexer: {err:?}"), span),
|
self.peek = Spanned(Some(Token::LineEnd), span);
|
||||||
|
return self.peek;
|
||||||
|
}
|
||||||
|
Some(Spanned(Ok(ok), r)) => return Spanned(Some(ok), r),
|
||||||
|
Some(Spanned(Err(err), span)) => self.logs.emit_error(format!("lexer: {err:?}"), span),
|
||||||
|
None => return Spanned(None, self.lexer.eof_span())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn peek_token(&mut self) -> Option<Spanned<Token<'a>>> {
|
fn peek_token(&mut self) -> Spanned<Option<Token<'a>>> {
|
||||||
if self.peek.is_none(){
|
if self.peek.0.is_none() {
|
||||||
self.peek = self.next_token();
|
self.peek = self.next_token();
|
||||||
}
|
}
|
||||||
self.peek
|
self.peek
|
||||||
|
|
@ -47,7 +60,7 @@ impl<'a> Parser<'a> {
|
||||||
if let Some(Spanned(token, span)) = self.peek_token() {
|
if let Some(Spanned(token, span)) = self.peek_token() {
|
||||||
if token != expected {
|
if token != expected {
|
||||||
self.logs.emit_error(
|
self.logs.emit_error(
|
||||||
format!("unexpected token {:#}, expected {:}", token, expected),
|
format!("unexpected {:#}, expected {:}", token, expected),
|
||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
(false, span)
|
(false, span)
|
||||||
|
|
@ -56,22 +69,24 @@ impl<'a> Parser<'a> {
|
||||||
(true, span)
|
(true, span)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.logs
|
self.logs.emit_error(
|
||||||
.emit_error(format!("unexpected eof expected {:#}", expected), self.eof());
|
format!("unexpected eof expected {:#}", expected),
|
||||||
|
self.eof(),
|
||||||
|
);
|
||||||
(false, self.eof())
|
(false, self.eof())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_symbol(&mut self) -> Spanned<Symbol<'a>> {
|
fn parse_symbol(&mut self) -> Spanned<Symbol<'a>> {
|
||||||
match self.next_token() {
|
match self.next_token() {
|
||||||
Some(Spanned(Token::Tilde, r)) => Spanned(Symbol::Epsilon, r),
|
Spanned(Some(Token::Tilde), r) => Spanned(Symbol::Epsilon, r),
|
||||||
Some(Spanned(Token::Ident("epsilon"), r)) => Spanned(Symbol::Epsilon, r),
|
Spanned(Some(Token::Ident("epsilon")), r) => Spanned(Symbol::Epsilon, r),
|
||||||
Some(Spanned(Token::Ident(super::EPSILON_LOWER), r)) => Spanned(Symbol::Epsilon, r),
|
Spanned(Some(Token::Ident(super::EPSILON_LOWER)), r) => Spanned(Symbol::Epsilon, r),
|
||||||
Some(Spanned(Token::Ident(ident), r)) => Spanned(Symbol::Ident(ident), r),
|
Spanned(Some(Token::Ident(ident)), r) => Spanned(Symbol::Ident(ident), r),
|
||||||
Some(Spanned(got, span)) => {
|
Spanned(Some(got), span) => {
|
||||||
self.logs.emit_error(
|
self.logs.emit_error(
|
||||||
format!(
|
format!(
|
||||||
"unexpected token {:#}, expected {:}|{:}",
|
"unexpected token {:#}, expected {:}|{:} (symbol)",
|
||||||
got,
|
got,
|
||||||
Token::Tilde,
|
Token::Tilde,
|
||||||
Token::Ident("")
|
Token::Ident("")
|
||||||
|
|
@ -80,21 +95,21 @@ impl<'a> Parser<'a> {
|
||||||
);
|
);
|
||||||
Spanned(Symbol::Ident("<INVALID>"), span)
|
Spanned(Symbol::Ident("<INVALID>"), span)
|
||||||
}
|
}
|
||||||
None => {
|
Spanned(None, span) => {
|
||||||
self.logs.emit_error(
|
self.logs.emit_error(
|
||||||
format!(
|
format!(
|
||||||
"unexpected eof expected {:}|{:}",
|
"unexpected eof expected {:}|{:} (symbol)",
|
||||||
Token::Tilde,
|
Token::Tilde,
|
||||||
Token::Ident("")
|
Token::Ident("")
|
||||||
),
|
),
|
||||||
self.eof(),
|
span,
|
||||||
);
|
);
|
||||||
Spanned(Symbol::Ident("<INVALID>"), self.eof())
|
Spanned(Symbol::Ident("<INVALID>"), self.eof())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_tupple(&mut self) -> Spanned<Tuple<'a>> {
|
fn parse_tupple(&mut self) -> Spanned<Tuple<'a>> {
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
let (matched, start) = self.expect_token(Token::LPar);
|
let (matched, start) = self.expect_token(Token::LPar);
|
||||||
if !matched {
|
if !matched {
|
||||||
|
|
@ -106,12 +121,20 @@ impl<'a> Parser<'a> {
|
||||||
if matches!(self.peek_token(), Some(Spanned(Token::Comma, _))) {
|
if matches!(self.peek_token(), Some(Spanned(Token::Comma, _))) {
|
||||||
self.next_token();
|
self.next_token();
|
||||||
}
|
}
|
||||||
if self.peek_token().is_none() {
|
match self.peek_token() {
|
||||||
self.logs.emit_error(
|
None => {
|
||||||
format!("unexpected eof expected {:}", Token::RPar),
|
self.logs.emit_error(
|
||||||
self.eof(),
|
format!("unexpected eof expected {:}", Token::RPar),
|
||||||
);
|
self.eof(),
|
||||||
break;
|
);
|
||||||
|
return Spanned(Tuple(items), start.join(self.eof()));
|
||||||
|
}
|
||||||
|
Some(Spanned(Token::LineEnd, span)) => {
|
||||||
|
self.logs
|
||||||
|
.emit_error(format!("unexpected eol expected {:}", Token::RPar), span);
|
||||||
|
return Spanned(Tuple(items), start.join(span));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,7 +143,7 @@ impl<'a> Parser<'a> {
|
||||||
Spanned(Tuple(items), start.join(end))
|
Spanned(Tuple(items), start.join(end))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_item(&mut self) -> Spanned<Item<'a>> {
|
fn parse_item(&mut self) -> Spanned<Item<'a>> {
|
||||||
match self.peek_token() {
|
match self.peek_token() {
|
||||||
Some(Spanned(Token::Ident(_) | Token::Tilde, _)) => {
|
Some(Spanned(Token::Ident(_) | Token::Tilde, _)) => {
|
||||||
self.parse_symbol().map(Item::Symbol)
|
self.parse_symbol().map(Item::Symbol)
|
||||||
|
|
@ -131,7 +154,7 @@ impl<'a> Parser<'a> {
|
||||||
self.next_token();
|
self.next_token();
|
||||||
self.logs.emit_error(
|
self.logs.emit_error(
|
||||||
format!(
|
format!(
|
||||||
"unexpected token {:#}, expected {:}|{:}|{:}|{:}|{:}",
|
"unexpected token {:#}, expected {:}|{:}|{:}|{:}|{:} (item)",
|
||||||
got,
|
got,
|
||||||
Token::Tilde,
|
Token::Tilde,
|
||||||
Token::Ident(""),
|
Token::Ident(""),
|
||||||
|
|
@ -146,7 +169,7 @@ impl<'a> Parser<'a> {
|
||||||
None => {
|
None => {
|
||||||
self.logs.emit_error(
|
self.logs.emit_error(
|
||||||
format!(
|
format!(
|
||||||
"unexpected eof expected {:}|{:}|{:}|{:}|{:}",
|
"unexpected eof expected {:}|{:}|{:}|{:}|{:} (item)",
|
||||||
Token::Tilde,
|
Token::Tilde,
|
||||||
Token::Ident(""),
|
Token::Ident(""),
|
||||||
Token::LPar,
|
Token::LPar,
|
||||||
|
|
@ -160,7 +183,7 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_list(&mut self) -> Spanned<List<'a>> {
|
fn parse_list(&mut self) -> Spanned<List<'a>> {
|
||||||
let mut list = Vec::new();
|
let mut list = Vec::new();
|
||||||
|
|
||||||
let (start, match_end) = match self.next_token() {
|
let (start, match_end) = match self.next_token() {
|
||||||
|
|
@ -176,7 +199,7 @@ impl<'a> Parser<'a> {
|
||||||
),
|
),
|
||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
return Spanned(List(Vec::new()), span);
|
return Spanned(List(Vec::new(), ListKind::BracketComma), span);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
self.logs.emit_error(
|
self.logs.emit_error(
|
||||||
|
|
@ -187,65 +210,179 @@ impl<'a> Parser<'a> {
|
||||||
),
|
),
|
||||||
self.eof(),
|
self.eof(),
|
||||||
);
|
);
|
||||||
return Spanned(List(Vec::new()), self.eof());
|
return Spanned(List(Vec::new(), ListKind::BracketComma), self.eof());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut comma = false;
|
||||||
while self.peek_token().map(|t| t.0) != Some(match_end) {
|
while self.peek_token().map(|t| t.0) != Some(match_end) {
|
||||||
list.push(self.parse_item());
|
list.push(self.parse_item());
|
||||||
if matches!(self.peek_token(), Some(Spanned(Token::Comma, _))) {
|
if matches!(self.peek_token(), Some(Spanned(Token::Comma, _))) {
|
||||||
|
comma = true;
|
||||||
self.next_token();
|
self.next_token();
|
||||||
}
|
}
|
||||||
if self.peek_token().is_none() {
|
match self.peek_token() {
|
||||||
self.logs
|
None => {
|
||||||
.emit_error(format!("unexpected eof expected {:}", match_end), self.eof());
|
self.logs.emit_error(
|
||||||
break;
|
format!("unexpected eof expected {:}", match_end),
|
||||||
|
self.eof(),
|
||||||
|
);
|
||||||
|
return Spanned(List(list, ListKind::BraceComma), start.join(self.eof()));
|
||||||
|
}
|
||||||
|
Some(Spanned(Token::LineEnd, span)) => {
|
||||||
|
self.logs
|
||||||
|
.emit_error(format!("unexpected eol expected {:}", match_end), span);
|
||||||
|
return Spanned(List(list, ListKind::BraceComma), start.join(span));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let (_, end) = self.expect_token(match_end);
|
let (_, end) = self.expect_token(match_end);
|
||||||
Spanned(List(list), start.join(end))
|
let kind = match (comma, match_end) {
|
||||||
|
(true, Token::RBrace) => ListKind::BraceComma,
|
||||||
|
(false, Token::RBrace) => ListKind::Brace,
|
||||||
|
(true, Token::RBracket) => ListKind::BracketComma,
|
||||||
|
(false, Token::RBracket) => ListKind::Bracket,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
Spanned(List(list, kind), start.join(end))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_regex(&mut self) -> Spanned<Regex<'a>> {
|
fn parse_regex(&mut self) -> Spanned<Regex<'a>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_production_rule(
|
||||||
|
&mut self,
|
||||||
|
sym: Symbol<'a>,
|
||||||
|
start: Span,
|
||||||
|
) -> Option<Spanned<TopLevel<'a>>> {
|
||||||
|
let mut lhs_group = ProductionGroup(vec![Spanned(sym, start)]);
|
||||||
|
let mut lhs_group_end = start;
|
||||||
|
while !matches!(
|
||||||
|
self.peek_token(),
|
||||||
|
None | Some(Spanned(Token::LSmallArrow | Token::LineEnd, _))
|
||||||
|
) {
|
||||||
|
let sym = self.parse_symbol();
|
||||||
|
lhs_group_end = sym.1;
|
||||||
|
lhs_group.0.push(sym);
|
||||||
|
}
|
||||||
|
if !self.expect_token(Token::LSmallArrow).0{
|
||||||
|
return Some(Spanned(TopLevel::ProductionRule(Spanned(lhs_group, start.join(lhs_group_end)), Spanned(vec![], lhs_group_end)), start.join(lhs_group_end)))
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut groups = Vec::new();
|
||||||
|
|
||||||
|
while !matches!(self.peek_token(), None | Some(Spanned(Token::LineEnd, _))){
|
||||||
|
let mut group = ProductionGroup(vec![]);
|
||||||
|
while !matches!(self.peek_token(), None | Some(Spanned(Token::LineEnd|Token::Or, _))){
|
||||||
|
group.0.push(self.parse_symbol());
|
||||||
|
}
|
||||||
|
if group.0.is_empty(){
|
||||||
|
let span = if let Some(Spanned(_, span)) = self.peek_token(){
|
||||||
|
span
|
||||||
|
}else{
|
||||||
|
self.eof()
|
||||||
|
};
|
||||||
|
self.logs.emit_error("cannot have empty production rule", span);
|
||||||
|
}
|
||||||
|
if matches!(self.peek_token(), Some(Spanned(Token::Or, _))){
|
||||||
|
self.next_token();
|
||||||
|
// if matches!(self.peek_token(), None|Spanned(Token::Or|Token::LineEnd))
|
||||||
|
}
|
||||||
|
let group_start = group.0.first().map(|g|g.1).unwrap_or(start);
|
||||||
|
let group_end = group.0.last().map(|g|g.1).unwrap_or(start);
|
||||||
|
groups.push(Spanned(group, group_start.join(group_end)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if groups.is_empty(){
|
||||||
|
self.logs.emit_error("cannot have empty production rule", start.join(lhs_group_end));
|
||||||
|
}
|
||||||
|
|
||||||
|
let rules_start = groups.first().map(|f|f.1).unwrap_or(start);
|
||||||
|
let rules_end = groups.last().map(|f|f.1).unwrap_or(start);
|
||||||
|
|
||||||
|
Some(Spanned(TopLevel::ProductionRule(Spanned(lhs_group, start.join(lhs_group_end)), Spanned(groups, rules_start.join(rules_end))), start.join(rules_end)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_transition_function(
|
||||||
|
&mut self,
|
||||||
|
ident: &'a str,
|
||||||
|
start: Span,
|
||||||
|
) -> Option<Spanned<TopLevel<'a>>> {
|
||||||
|
let tuple = self.parse_tupple();
|
||||||
|
let span = start.join(tuple.1);
|
||||||
|
let dest = Spanned((Spanned(ident, start), tuple), span);
|
||||||
|
if !self.expect_token(Token::Eq).0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let item = self.parse_item();
|
||||||
|
let span = start.join(item.1);
|
||||||
|
Some(Spanned(TopLevel::TransitionFunc(dest, item), span))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_element(&mut self) -> Option<Spanned<TopLevel<'a>>> {
|
||||||
|
let result = loop {
|
||||||
|
let next = self.next_token()?;
|
||||||
|
match (next, self.peek_token()) {
|
||||||
|
(Spanned(Token::LineEnd, _), _) => self.advance_line(),
|
||||||
|
(Spanned(Token::Ident(ident), start), Some(Spanned(Token::LPar, _))) => {
|
||||||
|
if let Some(tf) = self.parse_transition_function(ident, start) {
|
||||||
|
break Some(tf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(
|
||||||
|
Spanned(
|
||||||
|
Token::Ident(EPSILON_LOWER) | Token::Ident("epsilon") | Token::Tilde,
|
||||||
|
start,
|
||||||
|
),
|
||||||
|
Some(Spanned(Token::LSmallArrow | Token::Ident(_) | Token::Tilde, _)),
|
||||||
|
) => {
|
||||||
|
if let Some(pr) = self.parse_production_rule(Symbol::Epsilon, start) {
|
||||||
|
break Some(pr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(
|
||||||
|
Spanned(Token::Ident(ident), start),
|
||||||
|
Some(Spanned(Token::LSmallArrow | Token::Ident(_) | Token::Tilde, _)),
|
||||||
|
) => {
|
||||||
|
if let Some(pr) = self.parse_production_rule(Symbol::Ident(ident), start) {
|
||||||
|
break Some(pr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Spanned(Token::Ident(ident), start), _) => {
|
||||||
|
let name = Spanned(ident, start);
|
||||||
|
if !self.expect_token(Token::Eq).0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let item = self.parse_item();
|
||||||
|
let span = start.join(item.1);
|
||||||
|
break Some(Spanned(TopLevel::Item(name, item), span));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.logs.emit_error(
|
||||||
|
format!(
|
||||||
|
"unexpected token {:#}, expected {:}",
|
||||||
|
next.0,
|
||||||
|
Token::Ident("")
|
||||||
|
),
|
||||||
|
next.1,
|
||||||
|
);
|
||||||
|
while !matches!(self.next_token(), None|Some(Spanned(Token::LineEnd, _))){
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.advance_line();
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_elements(mut self) -> (Vec<Spanned<TopLevel<'a>>>, Logs<'a>) {
|
pub fn parse_elements(mut self) -> (Vec<Spanned<TopLevel<'a>>>, Logs<'a>) {
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
|
|
||||||
while let Some(next) = self.next_token() {
|
while let Some(next) = self.next_element() {
|
||||||
match (next, self.peek_token()) {
|
result.push(next)
|
||||||
(Spanned(Token::Ident(ident), start), Some(Spanned(Token::LPar, _))) => {
|
|
||||||
let tuple = self.parse_tupple();
|
|
||||||
let span = start.join(tuple.1);
|
|
||||||
let dest = Spanned(Dest::Function(Spanned(ident, start), tuple), span);
|
|
||||||
if !self.expect_token(Token::Eq).0{continue;}
|
|
||||||
let item = self.parse_item();
|
|
||||||
let span = start.join(item.1);
|
|
||||||
result.push(Spanned(TopLevel::Assignment(dest, item), span));
|
|
||||||
}
|
|
||||||
(
|
|
||||||
Spanned(Token::Ident(_), start),
|
|
||||||
Some(Spanned(Token::LSmallArrow, end)),
|
|
||||||
) => {
|
|
||||||
self.logs.emit_error("Production rules are not yet supported", start.join(end));
|
|
||||||
}
|
|
||||||
(Spanned(Token::Ident(ident), start), _) => {
|
|
||||||
let dest = Spanned(Dest::Ident(ident), start);
|
|
||||||
if !self.expect_token(Token::Eq).0{continue;}
|
|
||||||
let item = self.parse_item();
|
|
||||||
let span = start.join(item.1);
|
|
||||||
result.push(Spanned(TopLevel::Assignment(dest, item), span));
|
|
||||||
}
|
|
||||||
_ => self.logs.emit_error(
|
|
||||||
format!(
|
|
||||||
"unexpected token {:#}, expected {:}",
|
|
||||||
next.0,
|
|
||||||
Token::Ident("")
|
|
||||||
),
|
|
||||||
next.1,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(result, self.logs)
|
(result, self.logs)
|
||||||
|
|
|
||||||
|
|
@ -22,16 +22,16 @@ fn main() {
|
||||||
println!("running on: '{input}'");
|
println!("running on: '{input}'");
|
||||||
let mut simulator = npda::Simulator::begin(input, table);
|
let mut simulator = npda::Simulator::begin(input, table);
|
||||||
loop {
|
loop {
|
||||||
match simulator.step(){
|
match simulator.step() {
|
||||||
npda::SimulatorResult::Pending => {},
|
npda::SimulatorResult::Pending => {}
|
||||||
npda::SimulatorResult::Reject => {
|
npda::SimulatorResult::Reject => {
|
||||||
println!("REJECTED");
|
println!("REJECTED");
|
||||||
break;
|
break;
|
||||||
},
|
}
|
||||||
npda::SimulatorResult::Accept(npda) => {
|
npda::SimulatorResult::Accept(npda) => {
|
||||||
println!("ACCEPT: {npda:?}");
|
println!("ACCEPT: {npda:?}");
|
||||||
break;
|
break;
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -106,10 +106,9 @@ body {
|
||||||
background: #111;
|
background: #111;
|
||||||
}
|
}
|
||||||
|
|
||||||
#canvas {
|
.graph {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Bottom area (terminal + editor) ---------- */
|
/* ---------- Bottom area (terminal + editor) ---------- */
|
||||||
|
|
|
||||||
|
|
@ -8,44 +8,45 @@ import { oneDark } from "https://esm.sh/@codemirror/theme-one-dark";
|
||||||
|
|
||||||
import wasm from "./wasm.js"
|
import wasm from "./wasm.js"
|
||||||
|
|
||||||
|
import * as vis from "./js/vis-network.js"
|
||||||
|
|
||||||
|
|
||||||
function tokenize(text) {
|
function tokenize(text) {
|
||||||
try{
|
try {
|
||||||
return wasm.lex(text);
|
return wasm.lex(text);
|
||||||
}catch(e){
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function compile(text) {
|
function compile(text) {
|
||||||
try{
|
try {
|
||||||
return wasm.compile(text);
|
return wasm.compile(text);
|
||||||
}catch(e){
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* ===================================================================== */
|
|
||||||
|
|
||||||
// Map token types -> CSS classes
|
|
||||||
const tokenClass = (t) =>
|
const tokenClass = (t) =>
|
||||||
({
|
({
|
||||||
comment: "tok-comment",
|
comment: "tok-comment",
|
||||||
keyword: "tok-keyword",
|
keyword: "tok-keyword",
|
||||||
error: "tok-error",
|
error: "tok-error",
|
||||||
ident: "tok-ident",
|
ident: "tok-ident",
|
||||||
punc: "tok-punc",
|
punc: "tok-punc",
|
||||||
string: "tok-string",
|
string: "tok-string",
|
||||||
lpar: "rb-",
|
lpar: "rb-",
|
||||||
lbrace: "rb-",
|
lbrace: "rb-",
|
||||||
lbracket: "rb-",
|
lbracket: "rb-",
|
||||||
|
|
||||||
|
rpar: "rb-",
|
||||||
|
rbrace: "rb-",
|
||||||
|
rbracket: "rb-",
|
||||||
|
}[t] || "tok-ident");
|
||||||
|
|
||||||
rpar: "rb-",
|
|
||||||
rbrace: "rb-",
|
|
||||||
rbracket: "rb-",
|
|
||||||
}[t] || "tok-ident");
|
|
||||||
|
|
||||||
// ===================== Diagnostics helpers =====================
|
|
||||||
function severityClass(sev) {
|
function severityClass(sev) {
|
||||||
const s = (sev || "error").toLowerCase();
|
const s = (sev || "error").toLowerCase();
|
||||||
if (s === "warning") return "cm-diag-warning";
|
if (s === "warning") return "cm-diag-warning";
|
||||||
|
|
@ -57,21 +58,11 @@ function sevRank(sev) {
|
||||||
if (sev === "warning") return 2;
|
if (sev === "warning") return 2;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
function sevLabel(sev) {
|
|
||||||
if (sev === "warning") return "warning";
|
|
||||||
if (sev === "info") return "info";
|
|
||||||
return "error";
|
|
||||||
}
|
|
||||||
function sevAnsiColorClass(sev) {
|
|
||||||
if (sev === "warning") return "ansi-yellow";
|
|
||||||
if (sev === "info") return "ansi-cyan";
|
|
||||||
return "ansi-red";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function buildAnalysis(text, doc) {
|
function buildAnalysis(text, doc) {
|
||||||
const tokens = tokenize(text);
|
const tokens = tokenize(text);
|
||||||
const {log, log_formatted} = compile(text);
|
const { log, log_formatted } = compile(text);
|
||||||
|
|
||||||
// Build ONE Decoration set: syntax + diagnostics
|
// Build ONE Decoration set: syntax + diagnostics
|
||||||
const marks = [];
|
const marks = [];
|
||||||
|
|
@ -81,8 +72,8 @@ function buildAnalysis(text, doc) {
|
||||||
const start = Math.max(0, Math.min(docLen, tok.start));
|
const start = Math.max(0, Math.min(docLen, tok.start));
|
||||||
const end = Math.max(start, Math.min(docLen, tok.end));
|
const end = Math.max(start, Math.min(docLen, tok.end));
|
||||||
var tc = tokenClass(tok.kind);
|
var tc = tokenClass(tok.kind);
|
||||||
if (tc === "rb-"){
|
if (tc === "rb-") {
|
||||||
tc += tok.scope_level.toString();
|
tc += tok.scope_level.toString();
|
||||||
}
|
}
|
||||||
if (end > start) {
|
if (end > start) {
|
||||||
marks.push(Decoration.mark({ class: tc }).range(start, end));
|
marks.push(Decoration.mark({ class: tc }).range(start, end));
|
||||||
|
|
@ -90,7 +81,7 @@ function buildAnalysis(text, doc) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const d of log) {
|
for (const d of log) {
|
||||||
if (d.start === undefined || d.end === undefined)continue;
|
if (d.start === undefined || d.end === undefined) continue;
|
||||||
const start = Math.max(0, Math.min(docLen, d.start));
|
const start = Math.max(0, Math.min(docLen, d.start));
|
||||||
const endRaw = d.end == null ? d.start : d.end;
|
const endRaw = d.end == null ? d.start : d.end;
|
||||||
const end = Math.max(start, Math.min(docLen, endRaw));
|
const end = Math.max(start, Math.min(docLen, endRaw));
|
||||||
|
|
@ -238,7 +229,7 @@ function formatTerminal(view) {
|
||||||
let s = "";
|
let s = "";
|
||||||
s += `\x1b[90m[compile]\x1b[0m ${log.length} diagnostics\n`;
|
s += `\x1b[90m[compile]\x1b[0m ${log.length} diagnostics\n`;
|
||||||
|
|
||||||
term.innerHTML = ansiToHtml(s+log_formatted);
|
term.innerHTML = ansiToHtml(s + log_formatted);
|
||||||
}
|
}
|
||||||
|
|
||||||
const terminalPlugin = ViewPlugin.fromClass(
|
const terminalPlugin = ViewPlugin.fromClass(
|
||||||
|
|
@ -253,8 +244,8 @@ const terminalPlugin = ViewPlugin.fromClass(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// ===================== Build editor =====================
|
|
||||||
const initialText = `machine=NPDA
|
const initialText = `type=NPDA
|
||||||
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
|
||||||
|
|
@ -325,45 +316,24 @@ setDefaultLayoutWeights();
|
||||||
const app = document.getElementById("app");
|
const app = document.getElementById("app");
|
||||||
const hSplit = document.getElementById("hSplit");
|
const hSplit = document.getElementById("hSplit");
|
||||||
const vSplit = document.getElementById("vSplit");
|
const vSplit = document.getElementById("vSplit");
|
||||||
const canvas = document.getElementById("canvas");
|
// const canvas = document.getElementById("canvas");
|
||||||
const canvasPane = document.getElementById("canvasPane");
|
const canvasPane = document.getElementById("canvasPane");
|
||||||
|
|
||||||
let draggingH = false;
|
let draggingH = false;
|
||||||
let draggingV = false;
|
let draggingV = false;
|
||||||
|
|
||||||
// --- Canvas height splitter ---
|
|
||||||
hSplit.addEventListener("mousedown", (e) => {
|
hSplit.addEventListener("mousedown", (e) => {
|
||||||
draggingH = true;
|
draggingH = true;
|
||||||
document.body.style.cursor = "row-resize";
|
document.body.style.cursor = "row-resize";
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Terminal/editor width splitter ---
|
|
||||||
vSplit.addEventListener("mousedown", (e) => {
|
vSplit.addEventListener("mousedown", (e) => {
|
||||||
draggingV = true;
|
draggingV = true;
|
||||||
document.body.style.cursor = "col-resize";
|
document.body.style.cursor = "col-resize";
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
function resizeCanvasToPane() {
|
|
||||||
// Keep canvas resolution in sync with CSS size (crisp rendering)
|
|
||||||
const rect = canvasPane.getBoundingClientRect();
|
|
||||||
const dpr = window.devicePixelRatio || 1;
|
|
||||||
const w = Math.max(1, Math.floor(rect.width * dpr));
|
|
||||||
const h = Math.max(1, Math.floor(rect.height * dpr));
|
|
||||||
if (canvas.width !== w) canvas.width = w;
|
|
||||||
if (canvas.height !== h) canvas.height = h;
|
|
||||||
|
|
||||||
// Optional: demo draw
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
ctx.fillStyle = "#1f6feb";
|
|
||||||
ctx.fillRect(20 * dpr, 20 * dpr, 140 * dpr, 60 * dpr);
|
|
||||||
ctx.fillStyle = "#c9d1d9";
|
|
||||||
ctx.font = `${14 * dpr}px ui-monospace, monospace`;
|
|
||||||
ctx.fillText("Canvas area", 30 * dpr, 55 * dpr);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("mousemove", (e) => {
|
window.addEventListener("mousemove", (e) => {
|
||||||
const rect = app.getBoundingClientRect();
|
const rect = app.getBoundingClientRect();
|
||||||
|
|
||||||
|
|
@ -374,7 +344,6 @@ setDefaultLayoutWeights();
|
||||||
const maxCanvas = rect.height - 8 - minBottom;
|
const maxCanvas = rect.height - 8 - minBottom;
|
||||||
const canvasH = Math.max(minCanvas, Math.min(maxCanvas, y));
|
const canvasH = Math.max(minCanvas, Math.min(maxCanvas, y));
|
||||||
app.style.setProperty("--canvasH", `${canvasH}px`);
|
app.style.setProperty("--canvasH", `${canvasH}px`);
|
||||||
resizeCanvasToPane();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (draggingV) {
|
if (draggingV) {
|
||||||
|
|
@ -383,7 +352,7 @@ setDefaultLayoutWeights();
|
||||||
const x = e.clientX - r.left;
|
const x = e.clientX - r.left;
|
||||||
const minTerm = 220;
|
const minTerm = 220;
|
||||||
const maxTerm = r.width - 8 - 220;
|
const maxTerm = r.width - 8 - 220;
|
||||||
const termW = Math.max(minTerm, Math.min(maxTerm, r.width-x));
|
const termW = Math.max(minTerm, Math.min(maxTerm, r.width - x));
|
||||||
app.style.setProperty("--termW", `${termW}px`);
|
app.style.setProperty("--termW", `${termW}px`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -393,8 +362,251 @@ setDefaultLayoutWeights();
|
||||||
draggingV = false;
|
draggingV = false;
|
||||||
document.body.style.cursor = "";
|
document.body.style.cursor = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
// Keep canvas crisp on window resize too
|
|
||||||
window.addEventListener("resize", resizeCanvasToPane);
|
|
||||||
resizeCanvasToPane();
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
let network = null;
|
||||||
|
const nodes = new vis.DataSet();
|
||||||
|
const edges = new vis.DataSet();
|
||||||
|
|
||||||
|
|
||||||
|
const automaton = {
|
||||||
|
states: ["q0", "q1"],
|
||||||
|
initialState: "q0",
|
||||||
|
acceptStates: ["q1"],
|
||||||
|
|
||||||
|
transitions: [
|
||||||
|
{
|
||||||
|
from: "q0",
|
||||||
|
to: "q0",
|
||||||
|
label: "ε, z0 → A z0\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: "q0",
|
||||||
|
to: "q0",
|
||||||
|
label: "ε, z0 → B z0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: "q0",
|
||||||
|
to: "q1",
|
||||||
|
label: "ε, z0 → z0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: "q1",
|
||||||
|
to: "q1",
|
||||||
|
label: "a, A → ε"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: "q1",
|
||||||
|
to: "q1",
|
||||||
|
label: "b, B → ε"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
/**@param {{ctx: CanvasRenderingContext2D}} */
|
||||||
|
function renderNode({
|
||||||
|
ctx,
|
||||||
|
id,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
state: { selected, hover },
|
||||||
|
style,
|
||||||
|
label,
|
||||||
|
}) {
|
||||||
|
return {
|
||||||
|
drawNode() {
|
||||||
|
ctx.save();
|
||||||
|
var r = style.size;
|
||||||
|
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, r, 0, 2 * Math.PI);
|
||||||
|
ctx.fillStyle = "red";
|
||||||
|
ctx.fill();
|
||||||
|
ctx.lineWidth = 4;
|
||||||
|
ctx.strokeStyle = "blue";
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
ctx.fillStyle = "black";
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillText(label, x, y, r);
|
||||||
|
|
||||||
|
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.strokeStyle = 'white';
|
||||||
|
ctx.fillStyle = "black";
|
||||||
|
let cy = y - (r + 10);
|
||||||
|
for (const part of "meow[]\nbeeep".split("\n").reverse()) {
|
||||||
|
const metrics = ctx.measureText(part);
|
||||||
|
cy -= metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
|
||||||
|
ctx.strokeText(part, x, cy);
|
||||||
|
ctx.fillText(part, x, cy);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
},
|
||||||
|
nodeDimensions: { width: 20, height: 20 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Populate nodes
|
||||||
|
for (const state of automaton.states) {
|
||||||
|
nodes.add({
|
||||||
|
id: state,
|
||||||
|
label: state,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate edges
|
||||||
|
automaton.transitions.forEach((t, i) => {
|
||||||
|
edges.add({
|
||||||
|
id: `e${i}`,
|
||||||
|
from: t.from,
|
||||||
|
to: t.to,
|
||||||
|
label: t.label
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// updateGraphFromText();
|
||||||
|
ensureGraph();
|
||||||
|
function updateGraphFromText() {
|
||||||
|
ensureGraph();
|
||||||
|
|
||||||
|
const trans = []
|
||||||
|
|
||||||
|
// Collect state ids
|
||||||
|
const stateSet = new Set();
|
||||||
|
for (const tr of trans) {
|
||||||
|
stateSet.add(tr.from);
|
||||||
|
stateSet.add(tr.to);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update nodes (add missing, remove stale)
|
||||||
|
const existingNodeIds = new Set(nodes.getIds());
|
||||||
|
const desiredNodeIds = new Set([...stateSet]);
|
||||||
|
|
||||||
|
// remove stale
|
||||||
|
for (const id of existingNodeIds) {
|
||||||
|
if (!desiredNodeIds.has(id)) nodes.remove(id);
|
||||||
|
}
|
||||||
|
// add/update desired
|
||||||
|
for (const id of desiredNodeIds) {
|
||||||
|
const pos = pinnedPositions.get(id);
|
||||||
|
if (!existingNodeIds.has(id)) {
|
||||||
|
nodes.add({
|
||||||
|
id,
|
||||||
|
label: id,
|
||||||
|
...(pos ? { x: pos.x, y: pos.y, fixed: true } : {})
|
||||||
|
});
|
||||||
|
} else if (pos) {
|
||||||
|
nodes.update({ id, x: pos.x, y: pos.y, fixed: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update edges (stable IDs so edits don't flicker)
|
||||||
|
const desiredEdgeIds = new Set();
|
||||||
|
const nextEdges = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < trans.length; i++) {
|
||||||
|
const tr = trans[i];
|
||||||
|
const id = `${tr.from}::${tr.to}::${tr.label}::${i}`;
|
||||||
|
desiredEdgeIds.add(id);
|
||||||
|
nextEdges.push({ id, from: tr.from, to: tr.to, label: tr.label });
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingEdgeIds = new Set(edges.getIds());
|
||||||
|
for (const id of existingEdgeIds) {
|
||||||
|
if (!desiredEdgeIds.has(id)) edges.remove(id);
|
||||||
|
}
|
||||||
|
// add/update in batch
|
||||||
|
for (const e of nextEdges) {
|
||||||
|
if (!existingEdgeIds.has(e.id)) edges.add(e);
|
||||||
|
else edges.update(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If positions exist for all nodes, we can disable physics to “respect” manual layout
|
||||||
|
// Otherwise leave physics on to auto-layout new nodes.
|
||||||
|
const allPinned = [...desiredNodeIds].every((id) => pinnedPositions.has(id));
|
||||||
|
network.setOptions({ physics: { enabled: !allPinned } });
|
||||||
|
|
||||||
|
// Redraw nicely after updates
|
||||||
|
network.fit({ animation: { duration: 200, easingFunction: "easeInOutQuad" } });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 4) Hook graph updates into your existing single-pass analysis ----------
|
||||||
|
const graphPlugin = ViewPlugin.fromClass(class {
|
||||||
|
constructor(view) {
|
||||||
|
updateGraphFromText(view.state.doc.toString());
|
||||||
|
}
|
||||||
|
update(update) {
|
||||||
|
if (update.docChanged) {
|
||||||
|
updateGraphFromText(update.state.doc.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function chosen_node(values, id, selected, hovering) {
|
||||||
|
|
||||||
|
console.log(values, id, selected, hovering)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureGraph() {
|
||||||
|
if (network) return;
|
||||||
|
|
||||||
|
const container = document.getElementById("graph");
|
||||||
|
network = new vis.Network(
|
||||||
|
container,
|
||||||
|
{ nodes, edges },
|
||||||
|
{
|
||||||
|
layout: { improvedLayout: true },
|
||||||
|
physics: {
|
||||||
|
enabled: true,
|
||||||
|
solver: "barnesHut",
|
||||||
|
barnesHut: { gravitationalConstant: -8000, springLength: 120, springConstant: 0.04 },
|
||||||
|
stabilization: { iterations: 200 }
|
||||||
|
},
|
||||||
|
interaction: {
|
||||||
|
dragNodes: true,
|
||||||
|
hover: true,
|
||||||
|
multiselect: true
|
||||||
|
},
|
||||||
|
nodes: {
|
||||||
|
shape: 'dot',
|
||||||
|
size: 14,
|
||||||
|
font: { color: "#c9d1d9" },
|
||||||
|
color: {
|
||||||
|
background: "#1f6feb",
|
||||||
|
border: "#79c0ff",
|
||||||
|
highlight: { background: "#388bfd", border: "#a5d6ff" }
|
||||||
|
},
|
||||||
|
chosen: {
|
||||||
|
node: chosen_node
|
||||||
|
},
|
||||||
|
shape: "custom",
|
||||||
|
ctxRenderer: renderNode,
|
||||||
|
size: 18,
|
||||||
|
},
|
||||||
|
edges: {
|
||||||
|
arrows: { to: { enabled: true, scaleFactor: 0.8 } },
|
||||||
|
arrowStrikethrough: false,
|
||||||
|
font: { align: "middle", color: "#000000ff" },
|
||||||
|
color: { color: "rgba(201,209,217,0.35)", highlight: "#c9d1d9" },
|
||||||
|
smooth: { type: "dynamic" },
|
||||||
|
arrows: "to",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save positions when user drags nodes
|
||||||
|
|
||||||
|
network.on("dragEnd", (params) => {
|
||||||
|
const pos = network.getPositions(params.nodes);
|
||||||
|
for (const id of params.nodes) {
|
||||||
|
pinnedPositions.set(id, pos[id]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.network = network;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>Automata</title>
|
<title>Automata</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/vis-network/styles/vis-network.min.css">
|
||||||
|
|
||||||
|
|
||||||
<link href="editor.css" rel="stylesheet">
|
<link href="editor.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
@ -20,7 +24,10 @@
|
||||||
|
|
||||||
<div class="app" style="display:none" id="app">
|
<div class="app" style="display:none" id="app">
|
||||||
<section class="canvasPane" id="canvasPane">
|
<section class="canvasPane" id="canvasPane">
|
||||||
<canvas id="canvas"></canvas>
|
<div id="graph" class="graph"></div>
|
||||||
|
|
||||||
|
<button id="togglePhysics">Toggle Physics</button>
|
||||||
|
<button id="resetLayout">Reset Layout</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="hSplit" id="hSplit" title="Drag to resize canvas height"></div>
|
<div class="hSplit" id="hSplit" title="Drag to resize canvas height"></div>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
import wasm from "./wasm.js"
|
import wasm from "./wasm.js"
|
||||||
import "./editor.js"
|
import "./editor.js"
|
||||||
|
|
||||||
|
|
|
||||||
40777
web/root/js/vis-network.js
Normal file
40777
web/root/js/vis-network.js
Normal file
File diff suppressed because one or more lines are too long
27
web/root/js/vis-network.min.js
vendored
Normal file
27
web/root/js/vis-network.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -142,6 +142,7 @@ pub fn lex(input: &str) -> Vec<Tok> {
|
||||||
| "sigma",
|
| "sigma",
|
||||||
) => Kind::Keyword,
|
) => Kind::Keyword,
|
||||||
Token::Ident(_) => Kind::Ident,
|
Token::Ident(_) => Kind::Ident,
|
||||||
|
Token::LineEnd => Kind::Punc,
|
||||||
};
|
};
|
||||||
|
|
||||||
let scope_level = match kind {
|
let scope_level = match kind {
|
||||||
|
|
@ -183,7 +184,7 @@ pub struct CompileLog {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(getter_with_clone)]
|
#[wasm_bindgen(getter_with_clone)]
|
||||||
pub struct CompileResult{
|
pub struct CompileResult {
|
||||||
pub log: Vec<CompileLog>,
|
pub log: Vec<CompileLog>,
|
||||||
pub log_formatted: String,
|
pub log_formatted: String,
|
||||||
}
|
}
|
||||||
|
|
@ -196,9 +197,13 @@ pub fn compile(input: &str) -> CompileResult {
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let log_formatted = log.displayable().fold(String::new(), |mut s, e|{write!(&mut s, "{e}").unwrap(); s});
|
let log_formatted = log.displayable().fold(String::new(), |mut s, e| {
|
||||||
|
write!(&mut s, "{e}").unwrap();
|
||||||
|
s
|
||||||
|
});
|
||||||
|
|
||||||
let log = log.into_entries()
|
let log = log
|
||||||
|
.into_entries()
|
||||||
.map(|e| CompileLog {
|
.map(|e| CompileLog {
|
||||||
level: match e.level {
|
level: match e.level {
|
||||||
loader::log::LogLevel::Info => LogLevel::Info,
|
loader::log::LogLevel::Info => LogLevel::Info,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue