mirror of
https://github.com/ParkerTenBroeck/automata.git
synced 2026-06-06 21:24:06 -04:00
moved to deno to bundle website
This commit is contained in:
parent
c35d7a9192
commit
7629bdab6d
28 changed files with 1534 additions and 41961 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
.vscode/launch.json
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
# wasm-bindgen-cli_0_2_100
|
# wasm-bindgen-cli_0_2_100
|
||||||
wasm-pack
|
wasm-pack
|
||||||
binaryen
|
binaryen
|
||||||
|
deno
|
||||||
simple-http-server
|
simple-http-server
|
||||||
];
|
];
|
||||||
RUSTC_VERSION = "nightly";
|
RUSTC_VERSION = "nightly";
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ struct To(State, Vec<Symbol>);
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub struct TransitionTable {
|
pub struct Npda {
|
||||||
initial_state: State,
|
initial_state: State,
|
||||||
initial_stack: Symbol,
|
initial_stack: Symbol,
|
||||||
state_names: StateMap<String>,
|
state_names: StateMap<String>,
|
||||||
|
|
@ -19,7 +19,7 @@ pub struct TransitionTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct NPDA {
|
pub struct NpdaState {
|
||||||
pub state: State,
|
pub state: State,
|
||||||
pub stack: Vec<Symbol>,
|
pub stack: Vec<Symbol>,
|
||||||
pub position: usize,
|
pub position: usize,
|
||||||
|
|
@ -27,26 +27,26 @@ pub struct NPDA {
|
||||||
|
|
||||||
pub struct Simulator {
|
pub struct Simulator {
|
||||||
input: String,
|
input: String,
|
||||||
table: TransitionTable,
|
machine: Npda,
|
||||||
running: Vec<NPDA>,
|
running: Vec<NpdaState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum SimulatorResult {
|
pub enum SimulatorResult {
|
||||||
Pending,
|
Pending,
|
||||||
Reject,
|
Reject,
|
||||||
Accept(NPDA),
|
Accept(NpdaState),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Simulator {
|
impl Simulator {
|
||||||
pub fn begin(input: impl Into<String>, table: TransitionTable) -> Self {
|
pub fn begin(input: impl Into<String>, machine: Npda) -> Self {
|
||||||
Self {
|
Self {
|
||||||
input: input.into(),
|
input: input.into(),
|
||||||
running: vec![NPDA {
|
running: vec![NpdaState {
|
||||||
state: table.initial_state,
|
state: machine.initial_state,
|
||||||
stack: vec![table.initial_stack],
|
stack: vec![machine.initial_stack],
|
||||||
position: 0,
|
position: 0,
|
||||||
}],
|
}],
|
||||||
table,
|
machine,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ impl Simulator {
|
||||||
};
|
};
|
||||||
|
|
||||||
for to in self
|
for to in self
|
||||||
.table
|
.machine
|
||||||
.transitions
|
.transitions
|
||||||
.get((npda.state, top))
|
.get((npda.state, top))
|
||||||
.and_then(|t| t.get(None))
|
.and_then(|t| t.get(None))
|
||||||
|
|
@ -68,7 +68,7 @@ impl Simulator {
|
||||||
{
|
{
|
||||||
let mut stack = npda.stack.clone();
|
let mut stack = npda.stack.clone();
|
||||||
stack.extend_from_slice(&to.1);
|
stack.extend_from_slice(&to.1);
|
||||||
new.push(NPDA {
|
new.push(NpdaState {
|
||||||
state: to.0,
|
state: to.0,
|
||||||
stack,
|
stack,
|
||||||
position: npda.position,
|
position: npda.position,
|
||||||
|
|
@ -80,11 +80,11 @@ impl Simulator {
|
||||||
.get(npda.position..)
|
.get(npda.position..)
|
||||||
.and_then(|c| c.chars().next())
|
.and_then(|c| c.chars().next())
|
||||||
else {
|
else {
|
||||||
if let Some(final_states) = &self.table.final_states
|
if let Some(final_states) = &self.machine.final_states
|
||||||
&& final_states.get(npda.state).copied().unwrap_or_default()
|
&& final_states.get(npda.state).copied().unwrap_or_default()
|
||||||
{
|
{
|
||||||
return SimulatorResult::Accept(npda.clone());
|
return SimulatorResult::Accept(npda.clone());
|
||||||
} else if npda.stack == [self.table.initial_stack] {
|
} else if npda.stack == [self.machine.initial_stack] {
|
||||||
return SimulatorResult::Accept(npda.clone());
|
return SimulatorResult::Accept(npda.clone());
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -92,7 +92,7 @@ impl Simulator {
|
||||||
};
|
};
|
||||||
|
|
||||||
for to in self
|
for to in self
|
||||||
.table
|
.machine
|
||||||
.transitions
|
.transitions
|
||||||
.get((npda.state, top))
|
.get((npda.state, top))
|
||||||
.and_then(|t| t.get(Some(next)))
|
.and_then(|t| t.get(Some(next)))
|
||||||
|
|
@ -101,7 +101,7 @@ impl Simulator {
|
||||||
{
|
{
|
||||||
let mut stack = npda.stack.clone();
|
let mut stack = npda.stack.clone();
|
||||||
stack.extend_from_slice(&to.1);
|
stack.extend_from_slice(&to.1);
|
||||||
new.push(NPDA {
|
new.push(NpdaState {
|
||||||
state: to.0,
|
state: to.0,
|
||||||
stack,
|
stack,
|
||||||
position: npda.position + next.len_utf8(),
|
position: npda.position + next.len_utf8(),
|
||||||
|
|
@ -120,26 +120,14 @@ impl Simulator {
|
||||||
// ------ parser/semantics
|
// ------ parser/semantics
|
||||||
|
|
||||||
use crate::loader::{
|
use crate::loader::{
|
||||||
DELTA_LOWER, GAMMA_UPPER, SIGMA_UPPER, Spanned,
|
Context, DELTA_LOWER, GAMMA_UPPER, SIGMA_UPPER, Spanned, ast::{self, Symbol as Sym}
|
||||||
ast::{self, Symbol as Sym},
|
|
||||||
lexer::Lexer,
|
|
||||||
log::Logs,
|
|
||||||
parser::Parser,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
impl TransitionTable {
|
impl Npda {
|
||||||
pub fn load_table<'a>(input: &'a str) -> Result<(TransitionTable, Logs<'a>), Logs<'a>> {
|
|
||||||
let (ast, logs) = Parser::new(Lexer::new(input)).parse_elements();
|
|
||||||
if logs.contains_errors() {
|
|
||||||
return Err(logs);
|
|
||||||
}
|
|
||||||
Self::load_from_ast(&ast, logs)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_from_ast<'a>(
|
pub fn load_from_ast<'a>(
|
||||||
ast: &Vec<Spanned<ast::TopLevel<'a>>>,
|
items: impl Iterator<Item = Spanned<ast::TopLevel<'a>>>,
|
||||||
mut logs: Logs<'a>,
|
ctx: &mut Context<'a>
|
||||||
) -> Result<(TransitionTable, Logs<'a>), Logs<'a>> {
|
) -> Option<Npda> {
|
||||||
let mut initial_state = None;
|
let mut initial_state = None;
|
||||||
let mut initial_stack = None;
|
let mut initial_stack = None;
|
||||||
|
|
||||||
|
|
@ -150,141 +138,141 @@ impl TransitionTable {
|
||||||
|
|
||||||
let mut transitions_map = HashMap::new();
|
let mut transitions_map = HashMap::new();
|
||||||
|
|
||||||
for Spanned(element, span) in ast {
|
for Spanned(element, span) in items {
|
||||||
use Spanned as S;
|
use Spanned as S;
|
||||||
use ast::TopLevel as TL;
|
use ast::TopLevel as TL;
|
||||||
match element {
|
match element {
|
||||||
TL::Item(S("Q", _), list) => {
|
TL::Item(S("Q", _), list) => {
|
||||||
if !states.is_empty() {
|
if !states.is_empty() {
|
||||||
logs.emit_error("states already set", *span);
|
ctx.emit_error("states already set", span);
|
||||||
}
|
}
|
||||||
let Some(list) = list.expect_set(&mut logs) else {
|
let Some(list) = list.expect_set(ctx) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
for item in list {
|
for item in list {
|
||||||
let Some(ident) = item.expect_ident(&mut logs) else {
|
let Some(ident) = item.expect_ident(ctx) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let state = match states.len().try_into() {
|
let state = match states.len().try_into() {
|
||||||
Ok(ok) => State(ok),
|
Ok(ok) => State(ok),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
logs.emit_error("too many states defined", item.1);
|
ctx.emit_error("too many states defined", item.1);
|
||||||
State(0)
|
State(0)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if states.insert(ident, state).is_some() {
|
if states.insert(ident, state).is_some() {
|
||||||
logs.emit_error("state redefined", item.1);
|
ctx.emit_error("state redefined", item.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if list.is_empty() {
|
if list.is_empty() {
|
||||||
logs.emit_error("states cannot be empty", *span);
|
ctx.emit_error("states cannot be empty", span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TL::Item(S("E" | SIGMA_UPPER | "sigma", _), list) => {
|
TL::Item(S("E" | SIGMA_UPPER | "sigma", _), list) => {
|
||||||
if !alphabet.is_empty() {
|
if !alphabet.is_empty() {
|
||||||
logs.emit_error("alphabet already set", *span);
|
ctx.emit_error("alphabet already set", span);
|
||||||
}
|
}
|
||||||
let Some(list) = list.expect_set(&mut logs) else {
|
let Some(list) = list.expect_set(ctx) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
for item in list {
|
for item in list {
|
||||||
let Some(ident) = item.expect_ident(&mut logs) else {
|
let Some(ident) = item.expect_ident(ctx) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if ident.chars().count() != 1 {
|
if ident.chars().count() != 1 {
|
||||||
logs.emit_error("letter cannot be longer than one char", item.1);
|
ctx.emit_error("letter cannot be longer than one char", item.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !alphabet.insert(ident.chars().next().unwrap_or_default()) {
|
if !alphabet.insert(ident.chars().next().unwrap_or_default()) {
|
||||||
logs.emit_error("letter redefined", item.1);
|
ctx.emit_error("letter redefined", item.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if list.is_empty() {
|
if list.is_empty() {
|
||||||
logs.emit_error("alphabet cannot be empty", *span);
|
ctx.emit_error("alphabet cannot be empty", span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TL::Item(S("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);
|
ctx.emit_error("final states already set", span);
|
||||||
}
|
}
|
||||||
let mut map = HashSet::new();
|
let mut map = HashSet::new();
|
||||||
let Some(list) = list.expect_set(&mut logs) else {
|
let Some(list) = list.expect_set(ctx) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
for item in list {
|
for item in list {
|
||||||
let Some(ident) = item.expect_ident(&mut logs) else {
|
let Some(ident) = item.expect_ident(ctx) 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);
|
ctx.emit_error("final state redefined", item.1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logs.emit_error("final state not defined in set of states", item.1);
|
ctx.emit_error("final state not defined in set of states", item.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final_states = Some(map);
|
final_states = Some(map);
|
||||||
}
|
}
|
||||||
TL::Item(S("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);
|
ctx.emit_error("stack symbols already set", span);
|
||||||
}
|
}
|
||||||
let Some(list) = list.expect_set(&mut logs) else {
|
let Some(list) = list.expect_set(ctx) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
for item in list {
|
for item in list {
|
||||||
let Some(ident) = item.expect_ident(&mut logs) else {
|
let Some(ident) = item.expect_ident(ctx) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let symbol = match stack_symbols.len().try_into() {
|
let symbol = match stack_symbols.len().try_into() {
|
||||||
Ok(ok) => Symbol(ok),
|
Ok(ok) => Symbol(ok),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
logs.emit_error("too many stack symbols defined", item.1);
|
ctx.emit_error("too many stack symbols defined", item.1);
|
||||||
Symbol(0)
|
Symbol(0)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if stack_symbols.insert(ident, symbol).is_some() {
|
if stack_symbols.insert(ident, symbol).is_some() {
|
||||||
logs.emit_error("stack symbol redefined", item.1);
|
ctx.emit_error("stack symbol redefined", item.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if list.is_empty() {
|
if list.is_empty() {
|
||||||
logs.emit_error("stack symbols cannot be empty", *span);
|
ctx.emit_error("stack symbols cannot be empty", span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TL::Item(S("I" | "q0", _), S(src, src_d)) => match src {
|
TL::Item(S("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);
|
ctx.emit_error("initial state already set", span);
|
||||||
}
|
}
|
||||||
if let Some(initial) = states.get(ident) {
|
if let Some(initial) = states.get(ident) {
|
||||||
initial_state = Some(*initial)
|
initial_state = Some(*initial)
|
||||||
} else {
|
} else {
|
||||||
logs.emit_error("initial state symbol not defined as a state", *src_d);
|
ctx.emit_error("initial state symbol not defined as a state", src_d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => logs.emit_error("expected ident", *src_d),
|
_ => ctx.emit_error("expected ident", src_d),
|
||||||
},
|
},
|
||||||
TL::Item(S("S" | "z0", _), S(src, src_d)) => match src {
|
TL::Item(S("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);
|
ctx.emit_error("initial stack already set", span);
|
||||||
}
|
}
|
||||||
if let Some(initial) = stack_symbols.get(ident) {
|
if let Some(initial) = stack_symbols.get(ident) {
|
||||||
initial_stack = Some(*initial)
|
initial_stack = Some(*initial)
|
||||||
} else {
|
} else {
|
||||||
logs.emit_error(
|
ctx.emit_error(
|
||||||
"initial stack symbol not defined as a stack symbol",
|
"initial stack symbol not defined as a stack symbol",
|
||||||
*src_d,
|
src_d,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => logs.emit_error("expected ident", *src_d),
|
_ => ctx.emit_error("expected ident", src_d),
|
||||||
},
|
},
|
||||||
TL::Item(S(name, dest_s), _) => {
|
TL::Item(S(name, dest_s), _) => {
|
||||||
logs.emit_error(format!("unknown item {name:?}, expected 'Q' | 'E' | '{SIGMA_UPPER}' | 'sigma' | 'F' | 'T' | '{GAMMA_UPPER}' | 'gamma' | 'I' | 'q0' | 'S' | 'z0'"), *dest_s);
|
ctx.emit_error(format!("unknown item {name:?}, expected 'Q' | 'E' | '{SIGMA_UPPER}' | 'sigma' | 'F' | 'T' | '{GAMMA_UPPER}' | 'gamma' | 'I' | 'q0' | 'S' | 'z0'"), dest_s);
|
||||||
}
|
}
|
||||||
|
|
||||||
TL::TransitionFunc(
|
TL::TransitionFunc(
|
||||||
|
|
@ -293,16 +281,16 @@ impl TransitionTable {
|
||||||
) => {
|
) => {
|
||||||
let list = list.set_weak();
|
let list = list.set_weak();
|
||||||
let Some((state, letter, stack_symbol)) =
|
let Some((state, letter, stack_symbol)) =
|
||||||
tuple.as_ref().expect_npda_transition_function(&mut logs)
|
tuple.as_ref().expect_npda_transition_function(ctx)
|
||||||
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);
|
ctx.emit_error("transition state not defined as state", state.1);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(stack_symbol) = stack_symbols.get(stack_symbol.0).copied() else {
|
let Some(stack_symbol) = stack_symbols.get(stack_symbol.0).copied() else {
|
||||||
logs.emit_error(
|
ctx.emit_error(
|
||||||
"transition stack symbol not defined as stack symbol",
|
"transition stack symbol not defined as stack symbol",
|
||||||
stack_symbol.1,
|
stack_symbol.1,
|
||||||
);
|
);
|
||||||
|
|
@ -316,14 +304,14 @@ impl TransitionTable {
|
||||||
&& val.chars().count() == 1
|
&& val.chars().count() == 1
|
||||||
{
|
{
|
||||||
if !alphabet.contains(&char) {
|
if !alphabet.contains(&char) {
|
||||||
logs.emit_error(
|
ctx.emit_error(
|
||||||
"transition letter not defined in alphabet",
|
"transition letter not defined in alphabet",
|
||||||
letter.1,
|
letter.1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Some(char)
|
Some(char)
|
||||||
} else {
|
} else {
|
||||||
logs.emit_error(
|
ctx.emit_error(
|
||||||
"transition letter can only be single character",
|
"transition letter can only be single character",
|
||||||
letter.1,
|
letter.1,
|
||||||
);
|
);
|
||||||
|
|
@ -334,14 +322,14 @@ impl TransitionTable {
|
||||||
|
|
||||||
for item in list {
|
for item in list {
|
||||||
let Some((next_state, stack)) = item
|
let Some((next_state, stack)) = item
|
||||||
.expect_tuple(&mut logs)
|
.expect_tuple(ctx)
|
||||||
.and_then(|item| item.expect_npda_transition(&mut logs))
|
.and_then(|item| item.expect_npda_transition(ctx))
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(next_state) = states.get(next_state.0).copied() else {
|
let Some(next_state) = states.get(next_state.0).copied() else {
|
||||||
logs.emit_error("transition state not defined as state", next_state.1);
|
ctx.emit_error("transition state not defined as state", next_state.1);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -352,10 +340,10 @@ impl TransitionTable {
|
||||||
if matches!(symbol.0, ast::Item::Symbol(Sym::Epsilon)) {
|
if matches!(symbol.0, ast::Item::Symbol(Sym::Epsilon)) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let ident = symbol.expect_ident(&mut logs)?;
|
let ident = symbol.expect_ident(ctx)?;
|
||||||
|
|
||||||
let Some(symbol) = stack_symbols.get(ident).copied() else {
|
let Some(symbol) = stack_symbols.get(ident).copied() else {
|
||||||
logs.emit_error(
|
ctx.emit_error(
|
||||||
"transition stack symbol not defined",
|
"transition stack symbol not defined",
|
||||||
symbol.1,
|
symbol.1,
|
||||||
);
|
);
|
||||||
|
|
@ -370,46 +358,46 @@ impl TransitionTable {
|
||||||
.or_insert(HashSet::new())
|
.or_insert(HashSet::new())
|
||||||
.insert((next_state, stack))
|
.insert((next_state, stack))
|
||||||
{
|
{
|
||||||
logs.emit_warning("duplicate transition", item.1);
|
ctx.emit_warning("duplicate transition", item.1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TL::TransitionFunc(S((S(name, _), _), dest_s), _) => {
|
TL::TransitionFunc(S((S(name, _), _), dest_s), _) => {
|
||||||
logs.emit_error(
|
ctx.emit_error(
|
||||||
format!("unknown function {name:?}, expected 'd' | 'delta' | '{DELTA_LOWER}'"),
|
format!("unknown function {name:?}, expected 'd' | 'delta' | '{DELTA_LOWER}'"),
|
||||||
*dest_s,
|
dest_s,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TL::ProductionRule(_, _) => {
|
TL::ProductionRule(_, _) => {
|
||||||
logs.emit_error("unexpected production rule", *span);
|
ctx.emit_error("unexpected production rule", span);
|
||||||
}
|
}
|
||||||
TL::Table() => logs.emit_error("unexpected table", *span),
|
TL::Table() => ctx.emit_error("unexpected table", span),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if stack_symbols.is_empty() {
|
if stack_symbols.is_empty() {
|
||||||
logs.emit_error_locless("stack symbols never defined");
|
ctx.emit_error_locless("stack symbols never defined");
|
||||||
}
|
}
|
||||||
|
|
||||||
if alphabet.is_empty() {
|
if alphabet.is_empty() {
|
||||||
logs.emit_error_locless("alphabet never defined");
|
ctx.emit_error_locless("alphabet never defined");
|
||||||
}
|
}
|
||||||
|
|
||||||
if states.is_empty() {
|
if states.is_empty() {
|
||||||
logs.emit_error_locless("states never defined");
|
ctx.emit_error_locless("states never defined");
|
||||||
}
|
}
|
||||||
|
|
||||||
let initial_stack = match initial_stack {
|
let initial_stack = match initial_stack {
|
||||||
Some(some) => some,
|
Some(some) => some,
|
||||||
None => {
|
None => {
|
||||||
if let Some(initial) = stack_symbols.get("z0") {
|
if let Some(initial) = stack_symbols.get("z0") {
|
||||||
logs.emit_warning_locless(
|
ctx.emit_warning_locless(
|
||||||
"initial stack symbol not defined, defaulting to 'z0'",
|
"initial stack symbol not defined, defaulting to 'z0'",
|
||||||
);
|
);
|
||||||
*initial
|
*initial
|
||||||
} else {
|
} else {
|
||||||
logs.emit_error_locless("initial stack symbol not defined");
|
ctx.emit_error_locless("initial stack symbol not defined");
|
||||||
Symbol(0)
|
Symbol(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -419,10 +407,10 @@ impl TransitionTable {
|
||||||
Some(some) => some,
|
Some(some) => some,
|
||||||
None => {
|
None => {
|
||||||
if let Some(initial) = states.get("q0") {
|
if let Some(initial) = states.get("q0") {
|
||||||
logs.emit_warning_locless("initial state not defined, defaulting to 'q0'");
|
ctx.emit_warning_locless("initial state not defined, defaulting to 'q0'");
|
||||||
*initial
|
*initial
|
||||||
} else {
|
} else {
|
||||||
logs.emit_error_locless("initial state not defined");
|
ctx.emit_error_locless("initial state not defined");
|
||||||
State(0)
|
State(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -461,7 +449,11 @@ impl TransitionTable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let table = TransitionTable {
|
if ctx.contains_errors(){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Npda {
|
||||||
initial_state,
|
initial_state,
|
||||||
initial_stack,
|
initial_stack,
|
||||||
state_names,
|
state_names,
|
||||||
|
|
@ -469,8 +461,6 @@ impl TransitionTable {
|
||||||
alphabet,
|
alphabet,
|
||||||
final_states,
|
final_states,
|
||||||
transitions,
|
transitions,
|
||||||
};
|
})
|
||||||
|
|
||||||
Ok((table, logs))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,36 +59,36 @@ pub enum TopLevel<'a> {
|
||||||
Table(),
|
Table(),
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::loader::log::Logs;
|
use crate::loader::Context;
|
||||||
|
|
||||||
impl<'a> Spanned<Item<'a>> {
|
impl<'a> Spanned<Item<'a>> {
|
||||||
pub fn expect_ident(&self, logs: &mut Logs<'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) => {
|
Item::Symbol(Symbol::Epsilon) => {
|
||||||
logs.emit_error("expected ident found epsilon", self.1)
|
ctx.emit_error("expected ident found epsilon", self.1)
|
||||||
}
|
}
|
||||||
Item::Tuple(_) => logs.emit_error("expected ident found tuple", self.1),
|
Item::Tuple(_) => ctx.emit_error("expected ident found tuple", self.1),
|
||||||
Item::List(_) => logs.emit_error("expected ident found list", self.1),
|
Item::List(_) => ctx.emit_error("expected ident found list", self.1),
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expect_set(&self, logs: &mut Logs<'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(_)) => logs.emit_error("expected set found ident", self.1),
|
Item::Symbol(Symbol::Ident(_)) => ctx.emit_error("expected set found ident", self.1),
|
||||||
Item::Symbol(Symbol::Epsilon) => logs.emit_error("expected set found epsilon", self.1),
|
Item::Symbol(Symbol::Epsilon) => ctx.emit_error("expected set found epsilon", self.1),
|
||||||
Item::Tuple(_) => logs.emit_error("expected set found tuple", self.1),
|
Item::Tuple(_) => ctx.emit_error("expected set found tuple", self.1),
|
||||||
Item::List(list) => return Some(&list.0),
|
Item::List(list) => return Some(&list.0),
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expect_list(&self, logs: &mut Logs<'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(_)) => logs.emit_error("expected list found ident", self.1),
|
Item::Symbol(Symbol::Ident(_)) => ctx.emit_error("expected list found ident", self.1),
|
||||||
Item::Symbol(Symbol::Epsilon) => logs.emit_error("expected list found epsilon", self.1),
|
Item::Symbol(Symbol::Epsilon) => ctx.emit_error("expected list found epsilon", self.1),
|
||||||
Item::Tuple(_) => logs.emit_error("expected list found tuple", self.1),
|
Item::Tuple(_) => ctx.emit_error("expected list found tuple", self.1),
|
||||||
Item::List(list) => return Some(&list.0),
|
Item::List(list) => return Some(&list.0),
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|
@ -108,34 +108,34 @@ impl<'a> Spanned<Item<'a>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expect_tuple(&self, logs: &mut Logs<'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(_)) => logs.emit_error("expected tuple found ident", self.1),
|
Item::Symbol(Symbol::Ident(_)) => ctx.emit_error("expected tuple found ident", self.1),
|
||||||
Item::Symbol(Symbol::Epsilon) => {
|
Item::Symbol(Symbol::Epsilon) => {
|
||||||
logs.emit_error("expected tuple found epsilon", self.1)
|
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(_) => logs.emit_error("expected tuple found list", self.1),
|
Item::List(_) => ctx.emit_error("expected tuple found list", self.1),
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Spanned<&'b Tuple<'a>> {
|
impl<'a, 'b> Spanned<&'b Tuple<'a>> {
|
||||||
pub fn expect_dfa_transition(&self, _: &mut Logs<'a>) -> ! {
|
pub fn expect_dfa_transition(&self, _: &mut Context<'a>) -> ! {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
pub fn expect_nfa_transition(&self, _: &mut Logs<'a>) -> ! {
|
pub fn expect_nfa_transition(&self, _: &mut Context<'a>) -> ! {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expect_dpda_transition(&self, _: &mut Logs<'a>) -> ! {
|
pub fn expect_dpda_transition(&self, _: &mut Context<'a>) -> ! {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expect_npda_transition_function(
|
pub fn expect_npda_transition_function(
|
||||||
&self,
|
&self,
|
||||||
logs: &mut Logs<'a>,
|
ctx: &mut Context<'a>,
|
||||||
) -> Option<(Spanned<&'a str>, Spanned<Symbol<'a>>, Spanned<&'a str>)> {
|
) -> Option<(Spanned<&'a str>, Spanned<Symbol<'a>>, Spanned<&'a str>)> {
|
||||||
match &self.0.0[..] {
|
match &self.0.0[..] {
|
||||||
[
|
[
|
||||||
|
|
@ -149,7 +149,7 @@ impl<'a, 'b> Spanned<&'b Tuple<'a>> {
|
||||||
Spanned(symbol, *symbol_span),
|
Spanned(symbol, *symbol_span),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
_ => logs.emit_error(
|
_ => ctx.emit_error(
|
||||||
"expected NPDA transition function (ident, ident|~, ident)",
|
"expected NPDA transition function (ident, ident|~, ident)",
|
||||||
self.1,
|
self.1,
|
||||||
),
|
),
|
||||||
|
|
@ -158,7 +158,7 @@ impl<'a, 'b> Spanned<&'b Tuple<'a>> {
|
||||||
}
|
}
|
||||||
pub fn expect_npda_transition(
|
pub fn expect_npda_transition(
|
||||||
&self,
|
&self,
|
||||||
logs: &mut Logs<'a>,
|
ctx: &mut Context<'a>,
|
||||||
) -> Option<(Spanned<&'a str>, &'b [Spanned<Item<'a>>])> {
|
) -> Option<(Spanned<&'a str>, &'b [Spanned<Item<'a>>])> {
|
||||||
match &self.0.0[..] {
|
match &self.0.0[..] {
|
||||||
[
|
[
|
||||||
|
|
@ -167,15 +167,15 @@ impl<'a, 'b> Spanned<&'b Tuple<'a>> {
|
||||||
] => {
|
] => {
|
||||||
return Some((Spanned(state, *state_span), list.list_weak()));
|
return Some((Spanned(state, *state_span), list.list_weak()));
|
||||||
}
|
}
|
||||||
_ => logs.emit_error("expected NPDA transition (ident, item|[item])", self.1),
|
_ => ctx.emit_error("expected NPDA transition (ident, item|[item])", self.1),
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expect_tm_transition(&self, _: &mut Logs<'a>) -> ! {
|
pub fn expect_tm_transition(&self, _: &Context<'a>) -> ! {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
pub fn expect_ntm_transition(&self, _: &mut Logs<'a>) -> ! {
|
pub fn expect_ntm_transition(&self, _: &Context<'a>) -> ! {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,6 @@ impl<'a> std::fmt::Display for Token<'a> {
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Lexer<'a> {
|
pub struct Lexer<'a> {
|
||||||
input: &'a str,
|
input: &'a str,
|
||||||
|
|
||||||
start: usize,
|
|
||||||
position: usize,
|
position: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,7 +71,6 @@ impl<'a> Lexer<'a> {
|
||||||
pub fn new(input: &'a str) -> Self {
|
pub fn new(input: &'a str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
input,
|
input,
|
||||||
start: 0,
|
|
||||||
position: 0,
|
position: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -96,13 +93,6 @@ impl<'a> Lexer<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eof_span(&self) -> Span {
|
|
||||||
Span(self.input.len(), self.input.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn input(&self) -> &'a str {
|
|
||||||
self.input
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn begin_ident(c: char) -> bool {
|
fn begin_ident(c: char) -> bool {
|
||||||
|
|
@ -121,16 +111,15 @@ impl<'a> std::iter::Iterator for Lexer<'a> {
|
||||||
&& c.is_whitespace()
|
&& c.is_whitespace()
|
||||||
{
|
{
|
||||||
if c == '\n'{
|
if c == '\n'{
|
||||||
self.start = self.position;
|
let start = self.position;
|
||||||
self.consume();
|
self.consume();
|
||||||
let res = Some(Spanned(Ok(Token::LineEnd), Span(self.start, self.position)));
|
let res = Some(Spanned(Ok(Token::LineEnd), Span(start, self.position)));
|
||||||
self.start = self.position;
|
|
||||||
return res;
|
return res;
|
||||||
}else{
|
}else{
|
||||||
self.consume();
|
self.consume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.start = self.position;
|
let start = self.position;
|
||||||
|
|
||||||
let res = match self.consume()? {
|
let res = match self.consume()? {
|
||||||
'(' => Ok(Token::LPar),
|
'(' => Ok(Token::LPar),
|
||||||
|
|
@ -163,8 +152,7 @@ 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() {
|
||||||
self.backtrack();
|
break Ok(Token::Comment(&self.input[start + 2..self.position]));
|
||||||
break Ok(Token::Comment(&self.input[self.start + 2..=self.position]));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some('*') => loop {
|
Some('*') => loop {
|
||||||
|
|
@ -172,7 +160,7 @@ impl<'a> std::iter::Iterator for Lexer<'a> {
|
||||||
Some('*') if self.peek() == Some('/') => {
|
Some('*') if self.peek() == Some('/') => {
|
||||||
self.consume();
|
self.consume();
|
||||||
break Ok(Token::Comment(
|
break Ok(Token::Comment(
|
||||||
&self.input[self.start + 2..self.position - 2],
|
&self.input[start + 2..self.position - 2],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Some(_) => {}
|
Some(_) => {}
|
||||||
|
|
@ -191,16 +179,15 @@ impl<'a> std::iter::Iterator for Lexer<'a> {
|
||||||
Some(c) if continue_ident(c) => {}
|
Some(c) if continue_ident(c) => {}
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
self.backtrack();
|
self.backtrack();
|
||||||
break Ok(Token::Ident(&self.input[self.start..self.position]));
|
break Ok(Token::Ident(&self.input[start..self.position]));
|
||||||
}
|
}
|
||||||
None => break Ok(Token::Ident(&self.input[self.start..self.position])),
|
None => break Ok(Token::Ident(&self.input[start..self.position])),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
c => Err(Error::InvalidChar(c)),
|
c => Err(Error::InvalidChar(c)),
|
||||||
};
|
};
|
||||||
let span = Span(self.start, self.position);
|
let span = Span(start, self.position);
|
||||||
self.start = self.position;
|
|
||||||
Some(Spanned(res, span))
|
Some(Spanned(res, span))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
use std::{borrow::Cow, fmt::Display};
|
use std::{fmt::Display};
|
||||||
|
|
||||||
use crate::loader::Span;
|
use crate::loader::Span;
|
||||||
|
|
||||||
pub struct Logs<'a> {
|
pub struct Logs {
|
||||||
logs: Vec<LogEntry>,
|
logs: Vec<LogEntry>,
|
||||||
src: Cow<'a, str>,
|
|
||||||
has_error: bool,
|
has_error: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Logs<'a> {
|
impl Logs {
|
||||||
pub fn new(src: impl Into<Cow<'a, str>>) -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
logs: Vec::new(),
|
logs: Vec::new(),
|
||||||
src: src.into(),
|
|
||||||
has_error: false,
|
has_error: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -66,9 +64,9 @@ impl<'a> Logs<'a> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn displayable(&self) -> impl Iterator<Item = LogEntryDisplay<'_>> {
|
pub fn displayable_with<'a>(&'a self, src: &'a str) -> impl Iterator<Item = LogEntryDisplay<'a>> {
|
||||||
self.logs.iter().map(|entry| LogEntryDisplay {
|
self.logs.iter().map(|entry| LogEntryDisplay {
|
||||||
src: &self.src,
|
src,
|
||||||
entry,
|
entry,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -80,9 +78,11 @@ impl<'a> Logs<'a> {
|
||||||
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 {
|
impl Default for Logs {
|
||||||
&self.src
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
|
use crate::{automata::npda, loader::ast::TopLevel};
|
||||||
|
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
pub mod lexer;
|
pub mod lexer;
|
||||||
pub mod log;
|
pub mod log;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
|
|
||||||
pub const EPSILON_LOWER: &str = "Ɛ";
|
pub const EPSILON_LOWER: &str = "Ɛ";
|
||||||
|
pub const EPSILON_LOWER_MATH: &str = "𝛆";
|
||||||
pub const DELTA_LOWER: &str = "δ";
|
pub const DELTA_LOWER: &str = "δ";
|
||||||
pub const SIGMA_UPPER: &str = "Σ";
|
pub const SIGMA_UPPER: &str = "Σ";
|
||||||
pub const GAMMA_UPPER: &str = "Γ";
|
pub const GAMMA_UPPER: &str = "Γ";
|
||||||
|
|
@ -28,3 +31,118 @@ impl<T> Spanned<T> {
|
||||||
Spanned(&self.0, self.1)
|
Spanned(&self.0, self.1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Context<'a>{
|
||||||
|
logs: log::Logs,
|
||||||
|
src: &'a str
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Context<'a>{
|
||||||
|
pub fn new(src: &'a str) -> Self{
|
||||||
|
Self { logs: log::Logs::new(), src }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn src(&self) -> &'a str{
|
||||||
|
self.src
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn logs_display(&self) -> impl Iterator<Item = log::LogEntryDisplay<'_>>{
|
||||||
|
self.logs.displayable_with(self.src)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eof(&self) -> Span{
|
||||||
|
Span(self.src.len(), self.src.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit(&mut self, entry: log::LogEntry) {
|
||||||
|
self.logs.emit(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit_error_locless(&mut self, msg: impl Into<String>) {
|
||||||
|
self.logs.emit_error_locless(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit_error(&mut self, msg: impl Into<String>, span: Span) {
|
||||||
|
self.logs.emit_error(msg, span);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit_warning(&mut self, msg: impl Into<String>, span: Span) {
|
||||||
|
self.logs.emit_warning(msg, span);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit_warning_locless(&mut self, msg: impl Into<String>) {
|
||||||
|
self.logs.emit_warning_locless(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit_info(&mut self, msg: impl Into<String>, span: Span) {
|
||||||
|
self.logs.emit_info(msg, span);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains_errors(&self) -> bool {
|
||||||
|
self.logs.contains_errors()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_logs(self) -> log::Logs{
|
||||||
|
self.logs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub enum Machine{
|
||||||
|
Npda(npda::Npda)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_universal(ctx: &mut Context<'_>) -> Option<Machine> {
|
||||||
|
let mut items = parser::Parser::new(ctx).collect::<Vec<_>>().into_iter();
|
||||||
|
if ctx.logs.contains_errors(){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
use Spanned as S;
|
||||||
|
|
||||||
|
enum Type{
|
||||||
|
Dfa,
|
||||||
|
Nfa,
|
||||||
|
Dpda,
|
||||||
|
Npda,
|
||||||
|
Tm,
|
||||||
|
Ntm
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_type<'a>(item: Option<S<TopLevel<'a>>>, ctx: &mut Context<'a>) -> Option<Type>{
|
||||||
|
let (str, span) = match item{
|
||||||
|
Some(S(TopLevel::Item(S("type", _), item@S(_,span)), _)) => (item.expect_ident(ctx)?, span),
|
||||||
|
Some(S(_, span)) => {
|
||||||
|
ctx.emit_error("expected type=<type> as first item", span);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
ctx.emit_error("expected type=<type> as first item", ctx.eof());
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(match str{
|
||||||
|
"dfa"|"DFA" => Type::Dfa,
|
||||||
|
"nfa"|"NFA" => Type::Nfa,
|
||||||
|
"dpda"|"DPDA" => Type::Dpda,
|
||||||
|
"npdaA"|"NPDA" => Type::Npda,
|
||||||
|
"tm"|"TM" => Type::Tm,
|
||||||
|
"ntm"|"NTM" => Type::Ntm,
|
||||||
|
_ => {
|
||||||
|
ctx.emit_error("unknown type, expected 'DFA' | 'NFA' | 'DPDA' | 'NPDA' | 'TM' | 'NTM'", span);
|
||||||
|
return None;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(match parse_type(items.next(), ctx)?{
|
||||||
|
Type::Dfa => todo!(),
|
||||||
|
Type::Nfa => todo!(),
|
||||||
|
Type::Dpda => todo!(),
|
||||||
|
Type::Npda => Machine::Npda(npda::Npda::load_from_ast(items, ctx)?),
|
||||||
|
Type::Tm => todo!(),
|
||||||
|
Type::Ntm => todo!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -1,283 +1,242 @@
|
||||||
use crate::loader::log::{LogEntryDisplay, Logs};
|
use crate::loader::{Context, Span};
|
||||||
use crate::loader::{EPSILON_LOWER, Span, Spanned};
|
|
||||||
|
use super::lexer::Token as T;
|
||||||
|
use crate::loader::Spanned as S;
|
||||||
|
|
||||||
use super::ast::*;
|
use super::ast::*;
|
||||||
use super::lexer::{Lexer, Token};
|
use super::lexer::Lexer;
|
||||||
|
|
||||||
pub struct Parser<'a> {
|
pub struct Parser<'a, 'b> {
|
||||||
lexer: Lexer<'a>,
|
lexer: Lexer<'a>,
|
||||||
peek: Option<Spanned<Token<'a>>>,
|
peek: Option<S<T<'a>>>,
|
||||||
logs: Logs<'a>,
|
ctx: &'b mut Context<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Parser<'a> {
|
impl<'a, 'b> Iterator for Parser<'a, 'b> {
|
||||||
pub fn new(lexer: Lexer<'a>) -> Self {
|
type Item = S<TopLevel<'a>>;
|
||||||
Parser {
|
|
||||||
logs: Logs::new(lexer.input()),
|
|
||||||
peek: None,
|
|
||||||
lexer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eof(&self) -> Span {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
self.lexer.eof_span()
|
self.next_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> Parser<'a, 'b> {
|
||||||
|
pub fn new(ctx: &'b mut Context<'a>) -> Self {
|
||||||
|
Parser {
|
||||||
|
lexer: Lexer::new(ctx.src()),
|
||||||
|
ctx,
|
||||||
|
peek: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn advance_line(&mut self) {
|
fn advance_line(&mut self) {
|
||||||
if self.peek_token().is_none(){
|
if self.expect_token(T::LineEnd).0 {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.expect_token(Token::LineEnd).0 {
|
|
||||||
self.peek = None;
|
self.peek = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_token(&mut self) -> Option<Spanned<Token<'a>>> {
|
fn next_token_optional(&mut self) -> Option<S<T<'a>>> {
|
||||||
match self.peek {
|
match self.peek {
|
||||||
Some(Spanned(Token::LineEnd, _)) => return self.peek,
|
Some(S(T::LineEnd, _)) => return self.peek,
|
||||||
Some(_) => return self.peek.take(),
|
Some(_) => return self.peek.take(),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
loop {
|
loop {
|
||||||
match self.lexer.next() {
|
match self.lexer.next() {
|
||||||
Some(Spanned(Ok(Token::Comment(_)), _)) => {}
|
Some(S(Ok(T::Comment(_)), _)) => {}
|
||||||
Some(Spanned(Ok(Token::LineEnd), span)) => {
|
Some(S(Ok(T::LineEnd), span)) => {
|
||||||
self.peek = Some(Spanned(Token::LineEnd, span));
|
self.peek = Some(S(T::LineEnd, span));
|
||||||
return self.peek;
|
return self.peek;
|
||||||
}
|
}
|
||||||
Some(Spanned(Ok(ok), r)) => return Some(Spanned(ok, r)),
|
Some(S(Ok(ok), r)) => return Some(S(ok, r)),
|
||||||
Some(Spanned(Err(err), span)) => {
|
Some(S(Err(err), span)) => self.ctx.emit_error(format!("lexer: {err:?}"), span),
|
||||||
self.logs.emit_error(format!("lexer: {err:?}"), span)
|
|
||||||
}
|
|
||||||
None => return None,
|
None => return None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn peek_token(&mut self) -> Option<Spanned<Token<'a>>> {
|
fn peek_token_optional(&mut self) -> Option<S<T<'a>>> {
|
||||||
if self.peek.is_none() {
|
if self.peek.is_none() {
|
||||||
self.peek = self.next_token();
|
self.peek = self.next_token_optional();
|
||||||
}
|
}
|
||||||
self.peek
|
self.peek
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expect_token(&mut self, expected: Token<'a>) -> (bool, Span) {
|
fn next_token(&mut self) -> S<T<'a>> {
|
||||||
if let Some(Spanned(token, span)) = self.peek_token() {
|
self.next_token_optional()
|
||||||
if token != expected {
|
.unwrap_or(S(T::LineEnd, self.ctx.eof()))
|
||||||
self.logs.emit_error(
|
}
|
||||||
format!("unexpected {:#}, expected {:}", token, expected),
|
|
||||||
span,
|
fn peek_token(&mut self) -> S<T<'a>> {
|
||||||
);
|
self.peek_token_optional()
|
||||||
(false, span)
|
.unwrap_or(S(T::LineEnd, self.ctx.eof()))
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
fn expect_token(&mut self, expected: T<'a>) -> (bool, Span) {
|
||||||
|
match self.peek_token() {
|
||||||
|
S(token, span) if token == expected => {
|
||||||
self.next_token();
|
self.next_token();
|
||||||
(true, span)
|
(true, span)
|
||||||
}
|
}
|
||||||
} else {
|
S(token, span) => {
|
||||||
self.logs.emit_error(
|
self.ctx.emit_error(
|
||||||
format!("unexpected eof expected {:#}", expected),
|
format!("unexpected {:#} expected {:}", token, expected),
|
||||||
self.eof(),
|
span,
|
||||||
);
|
);
|
||||||
(false, self.eof())
|
(false, span)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_symbol(&mut self) -> Spanned<Symbol<'a>> {
|
fn parse_as_symbol(&mut self, tok: S<T<'a>>) -> S<Symbol<'a>> {
|
||||||
match self.next_token() {
|
match tok {
|
||||||
Some(Spanned(Token::Tilde, r)) => Spanned(Symbol::Epsilon, r),
|
S(T::Tilde, r) => S(Symbol::Epsilon, r),
|
||||||
Some(Spanned(Token::Ident("epsilon"), r)) => Spanned(Symbol::Epsilon, r),
|
S(T::Ident("epsilon"), r) => S(Symbol::Epsilon, r),
|
||||||
Some(Spanned(Token::Ident(super::EPSILON_LOWER), r)) => Spanned(Symbol::Epsilon, r),
|
S(T::Ident(super::EPSILON_LOWER), r) => S(Symbol::Epsilon, r),
|
||||||
Some(Spanned(Token::Ident(ident), r)) => Spanned(Symbol::Ident(ident), r),
|
S(T::Ident(ident), r) => S(Symbol::Ident(ident), r),
|
||||||
Some(Spanned(got, span)) => {
|
S(got, span) => {
|
||||||
self.logs.emit_error(
|
self.ctx.emit_error(
|
||||||
format!(
|
format!(
|
||||||
"unexpected {:#}, expected {:} | {:} (symbol)",
|
"unexpected {:#} expected symbol ( {:} | {:} )",
|
||||||
got,
|
got,
|
||||||
Token::Tilde,
|
T::Tilde,
|
||||||
Token::Ident("")
|
T::Ident("")
|
||||||
),
|
),
|
||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
Spanned(Symbol::Ident("<INVALID>"), span)
|
S(Symbol::Ident("<INVALID>"), span)
|
||||||
}
|
|
||||||
None => {
|
|
||||||
self.logs.emit_error(
|
|
||||||
format!(
|
|
||||||
"unexpected eof expected {:} | {:} (symbol)",
|
|
||||||
Token::Tilde,
|
|
||||||
Token::Ident("")
|
|
||||||
),
|
|
||||||
self.eof(),
|
|
||||||
);
|
|
||||||
Spanned(Symbol::Ident("<INVALID>"), self.eof())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_tupple(&mut self) -> Spanned<Tuple<'a>> {
|
fn parse_symbol(&mut self) -> S<Symbol<'a>> {
|
||||||
|
let next = self.next_token();
|
||||||
|
self.parse_as_symbol(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_tupple(&mut self) -> S<Tuple<'a>> {
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
let (matched, start) = self.expect_token(Token::LPar);
|
let (matched, start) = self.expect_token(T::LPar);
|
||||||
if !matched {
|
if !matched {
|
||||||
return Spanned(Tuple(Vec::new()), start);
|
return S(Tuple(Vec::new()), start);
|
||||||
}
|
}
|
||||||
|
|
||||||
while !matches!(self.peek_token(), Some(Spanned(Token::RPar, _))) {
|
while !matches!(self.peek_token().0, T::RPar) {
|
||||||
items.push(self.parse_item());
|
items.push(self.parse_item());
|
||||||
if matches!(self.peek_token(), Some(Spanned(Token::Comma, _))) {
|
if matches!(self.peek_token().0, T::Comma) {
|
||||||
self.next_token();
|
self.next_token();
|
||||||
}
|
}
|
||||||
match self.peek_token() {
|
if let S(T::LineEnd, span) = self.peek_token() {
|
||||||
None => {
|
self.ctx
|
||||||
self.logs.emit_error(
|
.emit_error(format!("unexpected eol expected {:}", T::RPar), span);
|
||||||
format!("unexpected eof expected {:}", Token::RPar),
|
return S(Tuple(items), start.join(span));
|
||||||
self.eof(),
|
|
||||||
);
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (_, end) = self.expect_token(Token::RPar);
|
let (_, end) = self.expect_token(T::RPar);
|
||||||
|
|
||||||
Spanned(Tuple(items), start.join(end))
|
S(Tuple(items), start.join(end))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_item(&mut self) -> Spanned<Item<'a>> {
|
fn parse_item(&mut self) -> S<Item<'a>> {
|
||||||
match self.peek_token() {
|
match self.peek_token().0 {
|
||||||
Some(Spanned(Token::Ident(_) | Token::Tilde, _)) => {
|
T::Ident(_) | T::Tilde => self.parse_symbol().map(Item::Symbol),
|
||||||
self.parse_symbol().map(Item::Symbol)
|
T::LPar => self.parse_tupple().map(Item::Tuple),
|
||||||
}
|
T::LBrace | T::LBracket => self.parse_list().map(Item::List),
|
||||||
Some(Spanned(Token::LPar, _)) => self.parse_tupple().map(Item::Tuple),
|
_ => {
|
||||||
Some(Spanned(Token::LBrace | Token::LBracket, _)) => self.parse_list().map(Item::List),
|
let S(got, span) = self.next_token();
|
||||||
Some(Spanned(got, span)) => {
|
self.ctx.emit_error(
|
||||||
self.next_token();
|
|
||||||
self.logs.emit_error(
|
|
||||||
format!(
|
format!(
|
||||||
"unexpected {:#}, expected {:} | {:} | {:} | {:} | {:} (item)",
|
"unexpected {:#} expected item ( {:} | {:} | {:} | {:} | {:} )",
|
||||||
got,
|
got,
|
||||||
Token::Tilde,
|
T::Tilde,
|
||||||
Token::Ident(""),
|
T::Ident(""),
|
||||||
Token::LPar,
|
T::LPar,
|
||||||
Token::LBrace,
|
T::LBrace,
|
||||||
Token::LBracket,
|
T::LBracket,
|
||||||
),
|
),
|
||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
Spanned(Item::Symbol(Symbol::Ident("<INVALID>")), span)
|
S(Item::Symbol(Symbol::Ident("<INVALID>")), span)
|
||||||
}
|
|
||||||
None => {
|
|
||||||
self.logs.emit_error(
|
|
||||||
format!(
|
|
||||||
"unexpected eof expected {:} | {:} | {:} | {:} | {:} (item)",
|
|
||||||
Token::Tilde,
|
|
||||||
Token::Ident(""),
|
|
||||||
Token::LPar,
|
|
||||||
Token::LBrace,
|
|
||||||
Token::LBracket,
|
|
||||||
),
|
|
||||||
self.eof(),
|
|
||||||
);
|
|
||||||
Spanned(Item::Symbol(Symbol::Ident("<INVALID>")), self.eof())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_list(&mut self) -> Spanned<List<'a>> {
|
fn parse_list(&mut self) -> S<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() {
|
||||||
Some(Spanned(Token::LBrace, r)) => (r, Token::RBrace),
|
S(T::LBrace, span) => (span, T::RBrace),
|
||||||
Some(Spanned(Token::LBracket, r)) => (r, Token::RBracket),
|
S(T::LBracket, span) => (span, T::RBracket),
|
||||||
Some(Spanned(got, span)) => {
|
S(got, span) => {
|
||||||
self.logs.emit_error(
|
self.ctx.emit_error(
|
||||||
format!(
|
format!(
|
||||||
"unexpected {:#}, expected {:} | {:}",
|
"unexpected {:#} expected list start ( {:} | {:} )",
|
||||||
got,
|
got,
|
||||||
Token::RBrace,
|
T::RBrace,
|
||||||
Token::RBracket
|
T::RBracket
|
||||||
),
|
),
|
||||||
span,
|
span,
|
||||||
);
|
);
|
||||||
return Spanned(List(Vec::new(), ListKind::BracketComma), span);
|
return S(List(Vec::new(), ListKind::BracketComma), span);
|
||||||
}
|
|
||||||
None => {
|
|
||||||
self.logs.emit_error(
|
|
||||||
format!(
|
|
||||||
"unexpected eof expected {:} | {:}",
|
|
||||||
Token::RBrace,
|
|
||||||
Token::RBracket
|
|
||||||
),
|
|
||||||
self.eof(),
|
|
||||||
);
|
|
||||||
return Spanned(List(Vec::new(), ListKind::BracketComma), self.eof());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut comma = false;
|
let mut comma = false;
|
||||||
while self.peek_token().map(|t| t.0) != Some(match_end) {
|
while self.peek_token().0 != match_end {
|
||||||
list.push(self.parse_item());
|
list.push(self.parse_item());
|
||||||
if matches!(self.peek_token(), Some(Spanned(Token::Comma, _))) {
|
|
||||||
|
if list.len() != 1
|
||||||
|
&& self.peek_token().0 != match_end
|
||||||
|
&& !matches!(self.peek_token().0, T::LineEnd)
|
||||||
|
&& matches!(self.peek_token().0, T::Comma) != comma
|
||||||
|
{
|
||||||
|
let span = self.peek_token().1;
|
||||||
|
self.ctx.emit_warning(
|
||||||
|
"inconsistent comma delimiting. use commas to delimit all or no items",
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if matches!(self.peek_token().0, T::Comma) {
|
||||||
comma = true;
|
comma = true;
|
||||||
self.next_token();
|
self.next_token();
|
||||||
}
|
}
|
||||||
match self.peek_token() {
|
if let S(T::LineEnd, span) = self.peek_token() {
|
||||||
None => {
|
self.ctx.emit_error(
|
||||||
self.logs.emit_error(
|
format!("unexpected eol expected list close ( {:} )", match_end),
|
||||||
format!("unexpected eof expected {:}", match_end),
|
span,
|
||||||
self.eof(),
|
);
|
||||||
);
|
return S(List(list, ListKind::BraceComma), start.join(span));
|
||||||
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);
|
||||||
let kind = match (comma, match_end) {
|
let kind = match (comma, match_end) {
|
||||||
(true, Token::RBrace) => ListKind::BraceComma,
|
(true, T::RBrace) => ListKind::BraceComma,
|
||||||
(false, Token::RBrace) => ListKind::Brace,
|
(false, T::RBrace) => ListKind::Brace,
|
||||||
(true, Token::RBracket) => ListKind::BracketComma,
|
(true, T::RBracket) => ListKind::BracketComma,
|
||||||
(false, Token::RBracket) => ListKind::Bracket,
|
(false, T::RBracket) => ListKind::Bracket,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
Spanned(List(list, kind), start.join(end))
|
S(List(list, kind), start.join(end))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_regex(&mut self) -> Spanned<Regex<'a>> {
|
fn parse_regex(&mut self) -> S<Regex<'a>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_production_rule(
|
fn parse_production_rule(&mut self, S(sym, start): S<Symbol<'a>>) -> Option<S<TopLevel<'a>>> {
|
||||||
&mut self,
|
let mut lhs_group = ProductionGroup(vec![S(sym, start)]);
|
||||||
sym: Symbol<'a>,
|
|
||||||
start: Span,
|
|
||||||
) -> Option<Spanned<TopLevel<'a>>> {
|
|
||||||
let mut lhs_group = ProductionGroup(vec![Spanned(sym, start)]);
|
|
||||||
let mut lhs_group_end = start;
|
let mut lhs_group_end = start;
|
||||||
while !matches!(
|
while !matches!(self.peek_token().0, T::LSmallArrow | T::LineEnd) {
|
||||||
self.peek_token(),
|
|
||||||
None | Some(Spanned(Token::LSmallArrow | Token::LineEnd, _))
|
|
||||||
) {
|
|
||||||
let sym = self.parse_symbol();
|
let sym = self.parse_symbol();
|
||||||
lhs_group_end = sym.1;
|
lhs_group_end = sym.1;
|
||||||
lhs_group.0.push(sym);
|
lhs_group.0.push(sym);
|
||||||
}
|
}
|
||||||
if !self.expect_token(Token::LSmallArrow).0 {
|
if !self.expect_token(T::LSmallArrow).0 {
|
||||||
return Some(Spanned(
|
return Some(S(
|
||||||
TopLevel::ProductionRule(
|
TopLevel::ProductionRule(
|
||||||
Spanned(lhs_group, start.join(lhs_group_end)),
|
S(lhs_group, start.join(lhs_group_end)),
|
||||||
Spanned(vec![], lhs_group_end),
|
S(vec![], lhs_group_end),
|
||||||
),
|
),
|
||||||
start.join(lhs_group_end),
|
start.join(lhs_group_end),
|
||||||
));
|
));
|
||||||
|
|
@ -287,25 +246,21 @@ impl<'a> Parser<'a> {
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut group = ProductionGroup(vec![]);
|
let mut group = ProductionGroup(vec![]);
|
||||||
while !matches!(
|
while !matches!(self.peek_token().0, T::LineEnd | T::Or) {
|
||||||
self.peek_token(),
|
|
||||||
None | Some(Spanned(Token::LineEnd | Token::Or, _))
|
|
||||||
) {
|
|
||||||
group.0.push(self.parse_symbol());
|
group.0.push(self.parse_symbol());
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.0.is_empty() {
|
if group.0.is_empty() {
|
||||||
let eof = self.eof();
|
let span = self.peek_token().1;
|
||||||
let span = self.peek_token().map(|t| t.1).unwrap_or(eof);
|
self.ctx
|
||||||
self.logs
|
|
||||||
.emit_error("cannot have empty production group", span);
|
.emit_error("cannot have empty production group", span);
|
||||||
}
|
}
|
||||||
|
|
||||||
let group_start = group.0.first().map(|g| g.1).unwrap_or(start);
|
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);
|
let group_end = group.0.last().map(|g| g.1).unwrap_or(start);
|
||||||
groups.push(Spanned(group, group_start.join(group_end)));
|
groups.push(S(group, group_start.join(group_end)));
|
||||||
|
|
||||||
if matches!(self.peek_token(), Some(Spanned(Token::Or, _))) {
|
if matches!(self.peek_token().0, T::Or) {
|
||||||
self.next_token();
|
self.next_token();
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
|
|
@ -313,7 +268,7 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if groups.is_empty() {
|
if groups.is_empty() {
|
||||||
self.logs.emit_error(
|
self.ctx.emit_error(
|
||||||
"cannot have empty production rule",
|
"cannot have empty production rule",
|
||||||
start.join(lhs_group_end),
|
start.join(lhs_group_end),
|
||||||
);
|
);
|
||||||
|
|
@ -322,10 +277,10 @@ impl<'a> Parser<'a> {
|
||||||
let rules_start = groups.first().map(|f| f.1).unwrap_or(start);
|
let rules_start = groups.first().map(|f| f.1).unwrap_or(start);
|
||||||
let rules_end = groups.last().map(|f| f.1).unwrap_or(start);
|
let rules_end = groups.last().map(|f| f.1).unwrap_or(start);
|
||||||
|
|
||||||
Some(Spanned(
|
Some(S(
|
||||||
TopLevel::ProductionRule(
|
TopLevel::ProductionRule(
|
||||||
Spanned(lhs_group, start.join(lhs_group_end)),
|
S(lhs_group, start.join(lhs_group_end)),
|
||||||
Spanned(groups, rules_start.join(rules_end)),
|
S(groups, rules_start.join(rules_end)),
|
||||||
),
|
),
|
||||||
start.join(rules_end),
|
start.join(rules_end),
|
||||||
))
|
))
|
||||||
|
|
@ -335,106 +290,73 @@ impl<'a> Parser<'a> {
|
||||||
&mut self,
|
&mut self,
|
||||||
ident: &'a str,
|
ident: &'a str,
|
||||||
start: Span,
|
start: Span,
|
||||||
) -> Option<Spanned<TopLevel<'a>>> {
|
) -> Option<S<TopLevel<'a>>> {
|
||||||
let tuple = self.parse_tupple();
|
let tuple = self.parse_tupple();
|
||||||
let span = start.join(tuple.1);
|
let span = start.join(tuple.1);
|
||||||
let dest = Spanned((Spanned(ident, start), tuple), span);
|
let dest = S((S(ident, start), tuple), span);
|
||||||
if !self.expect_token(Token::Eq).0 {
|
if !self.expect_token(T::Eq).0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let item = self.parse_item();
|
let item = self.parse_item();
|
||||||
let span = start.join(item.1);
|
let span = start.join(item.1);
|
||||||
Some(Spanned(TopLevel::TransitionFunc(dest, item), span))
|
Some(S(TopLevel::TransitionFunc(dest, item), span))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_element(&mut self) -> Option<Spanned<TopLevel<'a>>> {
|
pub fn next_element(&mut self) -> Option<S<TopLevel<'a>>> {
|
||||||
let result = loop {
|
let result = loop {
|
||||||
let next = self.next_token()?;
|
let next = self.next_token_optional()?;
|
||||||
match (next, self.peek_token()) {
|
match (next, self.peek_token()) {
|
||||||
(Spanned(Token::LineEnd, _), _) => self.advance_line(),
|
// empty
|
||||||
(Spanned(Token::Ident(ident), start), Some(Spanned(Token::LPar, _))) => {
|
(S(T::LineEnd, _), _) => self.advance_line(),
|
||||||
|
// transition function
|
||||||
|
(S(T::Ident(ident), start), S(T::LPar, _)) => {
|
||||||
if let Some(tf) = self.parse_transition_function(ident, start) {
|
if let Some(tf) = self.parse_transition_function(ident, start) {
|
||||||
break Some(tf);
|
break Some(tf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(
|
// item
|
||||||
Spanned(
|
(S(T::Ident(ident), start), S(T::Eq, _)) => {
|
||||||
Token::Ident(EPSILON_LOWER) | Token::Ident("epsilon") | Token::Tilde,
|
let name = S(ident, start);
|
||||||
start,
|
if !self.expect_token(T::Eq).0 {
|
||||||
),
|
|
||||||
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), Some(Spanned(Token::Eq, _))) => {
|
|
||||||
let name = Spanned(ident, start);
|
|
||||||
if !self.expect_token(Token::Eq).0 {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let item = self.parse_item();
|
let item = self.parse_item();
|
||||||
let span = start.join(item.1);
|
let span = start.join(item.1);
|
||||||
break Some(Spanned(TopLevel::Item(name, item), span));
|
break Some(S(TopLevel::Item(name, item), span));
|
||||||
}
|
}
|
||||||
(Spanned(Token::Ident(_), _), after) => {
|
// production rule
|
||||||
match after {
|
(
|
||||||
Some(Spanned(tok, span)) => {
|
sym @ S(T::Ident(_) | T::Tilde, _),
|
||||||
self.logs.emit_error(
|
S(T::LSmallArrow | T::Ident(_) | T::Tilde, _),
|
||||||
format!(
|
) => {
|
||||||
"unexpected {:#}, expected {:} | {:}",
|
let sym = self.parse_as_symbol(sym);
|
||||||
tok,
|
if let Some(pr) = self.parse_production_rule(sym) {
|
||||||
Token::Eq,
|
break Some(pr);
|
||||||
Token::LSmallArrow
|
|
||||||
),
|
|
||||||
span,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
self.logs.emit_error(
|
|
||||||
format!(
|
|
||||||
"unexpected eof, expected {:} | {:}",
|
|
||||||
Token::Eq,
|
|
||||||
Token::LSmallArrow
|
|
||||||
),
|
|
||||||
self.eof(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
while !matches!(self.next_token(), None | Some(Spanned(Token::LineEnd, _))) {}
|
}
|
||||||
|
|
||||||
|
(S(T::Ident(_), _), S(tok, span)) => {
|
||||||
|
self.ctx.emit_error(
|
||||||
|
format!(
|
||||||
|
"unexpected {:#} expected {:} | {:}",
|
||||||
|
tok,
|
||||||
|
T::Eq,
|
||||||
|
T::LSmallArrow
|
||||||
|
),
|
||||||
|
span,
|
||||||
|
);
|
||||||
|
while !matches!(self.next_token().0, T::LineEnd) {}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.logs.emit_error(
|
self.ctx.emit_error(
|
||||||
format!("unexpected {:#}, expected {:}", next.0, Token::Ident("")),
|
format!("unexpected {:#} expected {:}", next.0, T::Ident("")),
|
||||||
next.1,
|
next.1,
|
||||||
);
|
);
|
||||||
while !matches!(self.next_token(), None | Some(Spanned(Token::LineEnd, _))) {}
|
while !matches!(self.next_token().0, T::LineEnd) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.advance_line();
|
self.advance_line();
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_elements(mut self) -> (Vec<Spanned<TopLevel<'a>>>, Logs<'a>) {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
|
|
||||||
while let Some(next) = self.next_element() {
|
|
||||||
result.push(next)
|
|
||||||
}
|
|
||||||
|
|
||||||
(result, self.logs)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn logs(&self) -> impl Iterator<Item = LogEntryDisplay<'_>> {
|
|
||||||
self.logs.displayable()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28
src/main.rs
28
src/main.rs
|
|
@ -1,26 +1,24 @@
|
||||||
use automata::automata::npda;
|
use automata::{automata::npda, loader::Context};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let input = include_str!("../example.npda");
|
let input = include_str!("../example.npda");
|
||||||
|
let mut ctx = Context::new(input);
|
||||||
|
|
||||||
let table = match npda::TransitionTable::load_table(input) {
|
let machine = automata::loader::parse_universal(&mut ctx);
|
||||||
Ok((ok, logs)) => {
|
for log in ctx.logs_display(){
|
||||||
for log in logs.displayable() {
|
println!("{log}")
|
||||||
println!("{log}")
|
}
|
||||||
}
|
|
||||||
ok
|
let machine = match machine{
|
||||||
}
|
Some(automata::loader::Machine::Npda(npda)) => {
|
||||||
Err(logs) => {
|
npda
|
||||||
for log in logs.displayable() {
|
},
|
||||||
println!("{log}")
|
None => return,
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let input = "aababaaba";
|
let input = "aababaaba";
|
||||||
println!("running on: '{input}'");
|
println!("running on: '{input}'");
|
||||||
let mut simulator = npda::Simulator::begin(input, table);
|
let mut simulator = npda::Simulator::begin(input, machine);
|
||||||
loop {
|
loop {
|
||||||
match simulator.step() {
|
match simulator.step() {
|
||||||
npda::SimulatorResult::Pending => {}
|
npda::SimulatorResult::Pending => {}
|
||||||
|
|
|
||||||
13
web/deno.json
Normal file
13
web/deno.json
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["deno.window", "dom", "dom.iterable", "esnext"]
|
||||||
|
},
|
||||||
|
"tasks": {
|
||||||
|
"dev": "deno run -A tools/dev.ts",
|
||||||
|
"build": "deno run -A tools/build.ts",
|
||||||
|
"preview": "deno run -ENR jsr:@std/http/file-server dist root/index.html"
|
||||||
|
},
|
||||||
|
"imports": {
|
||||||
|
"sass": "npm:sass@^1.97.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
369
web/deno.lock
generated
Normal file
369
web/deno.lock
generated
Normal file
|
|
@ -0,0 +1,369 @@
|
||||||
|
{
|
||||||
|
"version": "5",
|
||||||
|
"specifiers": {
|
||||||
|
"jsr:@minify-html/deno@~0.18.1": "0.18.1",
|
||||||
|
"npm:@codemirror/autocomplete@*": "6.20.0",
|
||||||
|
"npm:@codemirror/commands@*": "6.10.1",
|
||||||
|
"npm:@codemirror/language@*": "6.12.1",
|
||||||
|
"npm:@codemirror/state@*": "6.5.3",
|
||||||
|
"npm:@codemirror/theme-one-dark@*": "6.1.3",
|
||||||
|
"npm:@codemirror/view@*": "6.39.9",
|
||||||
|
"npm:sass@*": "1.97.2",
|
||||||
|
"npm:sass@^1.97.2": "1.97.2",
|
||||||
|
"npm:vis-network@*": "10.0.2_@egjs+hammerjs@2.0.17_component-emitter@2.0.0_keycharm@0.4.0_uuid@13.0.0_vis-data@8.0.3__uuid@13.0.0__vis-util@6.0.0___@egjs+hammerjs@2.0.17___component-emitter@2.0.0__@egjs+hammerjs@2.0.17__component-emitter@2.0.0_vis-util@6.0.0__@egjs+hammerjs@2.0.17__component-emitter@2.0.0"
|
||||||
|
},
|
||||||
|
"jsr": {
|
||||||
|
"@minify-html/deno@0.18.1": {
|
||||||
|
"integrity": "1af76557adea9ee1e28759f653ec5a35e9c006e2abe73b65e4944aa821711ed1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"npm": {
|
||||||
|
"@codemirror/autocomplete@6.20.0": {
|
||||||
|
"integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==",
|
||||||
|
"dependencies": [
|
||||||
|
"@codemirror/language",
|
||||||
|
"@codemirror/state",
|
||||||
|
"@codemirror/view",
|
||||||
|
"@lezer/common"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@codemirror/commands@6.10.1": {
|
||||||
|
"integrity": "sha512-uWDWFypNdQmz2y1LaNJzK7fL7TYKLeUAU0npEC685OKTF3KcQ2Vu3klIM78D7I6wGhktme0lh3CuQLv0ZCrD9Q==",
|
||||||
|
"dependencies": [
|
||||||
|
"@codemirror/language",
|
||||||
|
"@codemirror/state",
|
||||||
|
"@codemirror/view",
|
||||||
|
"@lezer/common"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@codemirror/language@6.12.1": {
|
||||||
|
"integrity": "sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"@codemirror/state",
|
||||||
|
"@codemirror/view",
|
||||||
|
"@lezer/common",
|
||||||
|
"@lezer/highlight",
|
||||||
|
"@lezer/lr",
|
||||||
|
"style-mod"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@codemirror/state@6.5.3": {
|
||||||
|
"integrity": "sha512-MerMzJzlXogk2fxWFU1nKp36bY5orBG59HnPiz0G9nLRebWa0zXuv2siH6PLIHBvv5TH8CkQRqjBs0MlxCZu+A==",
|
||||||
|
"dependencies": [
|
||||||
|
"@marijn/find-cluster-break"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@codemirror/theme-one-dark@6.1.3": {
|
||||||
|
"integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==",
|
||||||
|
"dependencies": [
|
||||||
|
"@codemirror/language",
|
||||||
|
"@codemirror/state",
|
||||||
|
"@codemirror/view",
|
||||||
|
"@lezer/highlight"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@codemirror/view@6.39.9": {
|
||||||
|
"integrity": "sha512-miGSIfBOKC1s2oHoa80dp+BjtsL8sXsrgGlQnQuOcfvaedcQUtqddTmKbJSDkLl4mkgPvZyXuKic2HDNYcJLYA==",
|
||||||
|
"dependencies": [
|
||||||
|
"@codemirror/state",
|
||||||
|
"crelt",
|
||||||
|
"style-mod",
|
||||||
|
"w3c-keyname"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@egjs/hammerjs@2.0.17": {
|
||||||
|
"integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/hammerjs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@lezer/common@1.5.0": {
|
||||||
|
"integrity": "sha512-PNGcolp9hr4PJdXR4ix7XtixDrClScvtSCYW3rQG106oVMOOI+jFb+0+J3mbeL/53g1Zd6s0kJzaw6Ri68GmAA=="
|
||||||
|
},
|
||||||
|
"@lezer/highlight@1.2.3": {
|
||||||
|
"integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
|
||||||
|
"dependencies": [
|
||||||
|
"@lezer/common"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@lezer/lr@1.4.7": {
|
||||||
|
"integrity": "sha512-wNIFWdSUfX9Jc6ePMzxSPVgTVB4EOfDIwLQLWASyiUdHKaMsiilj9bYiGkGQCKVodd0x6bgQCV207PILGFCF9Q==",
|
||||||
|
"dependencies": [
|
||||||
|
"@lezer/common"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@marijn/find-cluster-break@1.0.2": {
|
||||||
|
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="
|
||||||
|
},
|
||||||
|
"@parcel/watcher-android-arm64@2.5.1": {
|
||||||
|
"integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
|
||||||
|
"os": ["android"],
|
||||||
|
"cpu": ["arm64"]
|
||||||
|
},
|
||||||
|
"@parcel/watcher-darwin-arm64@2.5.1": {
|
||||||
|
"integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
|
||||||
|
"os": ["darwin"],
|
||||||
|
"cpu": ["arm64"]
|
||||||
|
},
|
||||||
|
"@parcel/watcher-darwin-x64@2.5.1": {
|
||||||
|
"integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
|
||||||
|
"os": ["darwin"],
|
||||||
|
"cpu": ["x64"]
|
||||||
|
},
|
||||||
|
"@parcel/watcher-freebsd-x64@2.5.1": {
|
||||||
|
"integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
|
||||||
|
"os": ["freebsd"],
|
||||||
|
"cpu": ["x64"]
|
||||||
|
},
|
||||||
|
"@parcel/watcher-linux-arm-glibc@2.5.1": {
|
||||||
|
"integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
|
||||||
|
"os": ["linux"],
|
||||||
|
"cpu": ["arm"]
|
||||||
|
},
|
||||||
|
"@parcel/watcher-linux-arm-musl@2.5.1": {
|
||||||
|
"integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
|
||||||
|
"os": ["linux"],
|
||||||
|
"cpu": ["arm"]
|
||||||
|
},
|
||||||
|
"@parcel/watcher-linux-arm64-glibc@2.5.1": {
|
||||||
|
"integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
|
||||||
|
"os": ["linux"],
|
||||||
|
"cpu": ["arm64"]
|
||||||
|
},
|
||||||
|
"@parcel/watcher-linux-arm64-musl@2.5.1": {
|
||||||
|
"integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
|
||||||
|
"os": ["linux"],
|
||||||
|
"cpu": ["arm64"]
|
||||||
|
},
|
||||||
|
"@parcel/watcher-linux-x64-glibc@2.5.1": {
|
||||||
|
"integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
|
||||||
|
"os": ["linux"],
|
||||||
|
"cpu": ["x64"]
|
||||||
|
},
|
||||||
|
"@parcel/watcher-linux-x64-musl@2.5.1": {
|
||||||
|
"integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
|
||||||
|
"os": ["linux"],
|
||||||
|
"cpu": ["x64"]
|
||||||
|
},
|
||||||
|
"@parcel/watcher-win32-arm64@2.5.1": {
|
||||||
|
"integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
|
||||||
|
"os": ["win32"],
|
||||||
|
"cpu": ["arm64"]
|
||||||
|
},
|
||||||
|
"@parcel/watcher-win32-ia32@2.5.1": {
|
||||||
|
"integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
|
||||||
|
"os": ["win32"],
|
||||||
|
"cpu": ["ia32"]
|
||||||
|
},
|
||||||
|
"@parcel/watcher-win32-x64@2.5.1": {
|
||||||
|
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
|
||||||
|
"os": ["win32"],
|
||||||
|
"cpu": ["x64"]
|
||||||
|
},
|
||||||
|
"@parcel/watcher@2.5.1": {
|
||||||
|
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
|
||||||
|
"dependencies": [
|
||||||
|
"detect-libc",
|
||||||
|
"is-glob",
|
||||||
|
"micromatch",
|
||||||
|
"node-addon-api"
|
||||||
|
],
|
||||||
|
"optionalDependencies": [
|
||||||
|
"@parcel/watcher-android-arm64",
|
||||||
|
"@parcel/watcher-darwin-arm64",
|
||||||
|
"@parcel/watcher-darwin-x64",
|
||||||
|
"@parcel/watcher-freebsd-x64",
|
||||||
|
"@parcel/watcher-linux-arm-glibc",
|
||||||
|
"@parcel/watcher-linux-arm-musl",
|
||||||
|
"@parcel/watcher-linux-arm64-glibc",
|
||||||
|
"@parcel/watcher-linux-arm64-musl",
|
||||||
|
"@parcel/watcher-linux-x64-glibc",
|
||||||
|
"@parcel/watcher-linux-x64-musl",
|
||||||
|
"@parcel/watcher-win32-arm64",
|
||||||
|
"@parcel/watcher-win32-ia32",
|
||||||
|
"@parcel/watcher-win32-x64"
|
||||||
|
],
|
||||||
|
"scripts": true
|
||||||
|
},
|
||||||
|
"@types/hammerjs@2.0.46": {
|
||||||
|
"integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw=="
|
||||||
|
},
|
||||||
|
"braces@3.0.3": {
|
||||||
|
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||||
|
"dependencies": [
|
||||||
|
"fill-range"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"chokidar@4.0.3": {
|
||||||
|
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||||
|
"dependencies": [
|
||||||
|
"readdirp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"component-emitter@2.0.0": {
|
||||||
|
"integrity": "sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw=="
|
||||||
|
},
|
||||||
|
"crelt@1.0.6": {
|
||||||
|
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
|
||||||
|
},
|
||||||
|
"detect-libc@1.0.3": {
|
||||||
|
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||||
|
"bin": true
|
||||||
|
},
|
||||||
|
"fill-range@7.1.1": {
|
||||||
|
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||||
|
"dependencies": [
|
||||||
|
"to-regex-range"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"immutable@5.1.4": {
|
||||||
|
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA=="
|
||||||
|
},
|
||||||
|
"is-extglob@2.1.1": {
|
||||||
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
|
||||||
|
},
|
||||||
|
"is-glob@4.0.3": {
|
||||||
|
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||||
|
"dependencies": [
|
||||||
|
"is-extglob"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"is-number@7.0.0": {
|
||||||
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
|
||||||
|
},
|
||||||
|
"keycharm@0.4.0": {
|
||||||
|
"integrity": "sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ=="
|
||||||
|
},
|
||||||
|
"micromatch@4.0.8": {
|
||||||
|
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||||
|
"dependencies": [
|
||||||
|
"braces",
|
||||||
|
"picomatch"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node-addon-api@7.1.1": {
|
||||||
|
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="
|
||||||
|
},
|
||||||
|
"picomatch@2.3.1": {
|
||||||
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
|
||||||
|
},
|
||||||
|
"readdirp@4.1.2": {
|
||||||
|
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="
|
||||||
|
},
|
||||||
|
"sass@1.97.2": {
|
||||||
|
"integrity": "sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw==",
|
||||||
|
"dependencies": [
|
||||||
|
"chokidar",
|
||||||
|
"immutable",
|
||||||
|
"source-map-js"
|
||||||
|
],
|
||||||
|
"optionalDependencies": [
|
||||||
|
"@parcel/watcher"
|
||||||
|
],
|
||||||
|
"bin": true
|
||||||
|
},
|
||||||
|
"source-map-js@1.2.1": {
|
||||||
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
|
||||||
|
},
|
||||||
|
"style-mod@4.1.3": {
|
||||||
|
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="
|
||||||
|
},
|
||||||
|
"to-regex-range@5.0.1": {
|
||||||
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"is-number"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"uuid@13.0.0": {
|
||||||
|
"integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
|
||||||
|
"bin": true
|
||||||
|
},
|
||||||
|
"vis-data@8.0.3_uuid@13.0.0_vis-util@6.0.0__@egjs+hammerjs@2.0.17__component-emitter@2.0.0_@egjs+hammerjs@2.0.17_component-emitter@2.0.0": {
|
||||||
|
"integrity": "sha512-jhnb6rJNqkKR1Qmlay0VuDXY9ZlvAnYN1udsrP4U+krgZEq7C0yNSKdZqmnCe13mdnf9AdVcdDGFOzy2mpPoqw==",
|
||||||
|
"dependencies": [
|
||||||
|
"uuid",
|
||||||
|
"vis-util"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"vis-network@10.0.2_@egjs+hammerjs@2.0.17_component-emitter@2.0.0_keycharm@0.4.0_uuid@13.0.0_vis-data@8.0.3__uuid@13.0.0__vis-util@6.0.0___@egjs+hammerjs@2.0.17___component-emitter@2.0.0__@egjs+hammerjs@2.0.17__component-emitter@2.0.0_vis-util@6.0.0__@egjs+hammerjs@2.0.17__component-emitter@2.0.0": {
|
||||||
|
"integrity": "sha512-qPl8GLYBeHEFqiTqp4VBbYQIJ2EA8KLr7TstA2E8nJxfEHaKCU81hQLz7hhq11NUpHbMaRzBjW5uZpVKJ45/wA==",
|
||||||
|
"dependencies": [
|
||||||
|
"@egjs/hammerjs",
|
||||||
|
"component-emitter",
|
||||||
|
"keycharm",
|
||||||
|
"uuid",
|
||||||
|
"vis-data",
|
||||||
|
"vis-util"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"vis-util@6.0.0_@egjs+hammerjs@2.0.17_component-emitter@2.0.0": {
|
||||||
|
"integrity": "sha512-qtpts3HRma0zPe4bO7t9A2uejkRNj8Z2Tb6do6lN85iPNWExFkUiVhdAq5uLGIUqBFduyYeqWJKv/jMkxX0R5g==",
|
||||||
|
"dependencies": [
|
||||||
|
"@egjs/hammerjs",
|
||||||
|
"component-emitter"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"w3c-keyname@2.2.8": {
|
||||||
|
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"redirects": {
|
||||||
|
"https://esm.sh/@codemirror/autocomplete": "https://esm.sh/@codemirror/autocomplete@6.20.0",
|
||||||
|
"https://esm.sh/@codemirror/commands": "https://esm.sh/@codemirror/commands@6.10.1",
|
||||||
|
"https://esm.sh/@codemirror/language": "https://esm.sh/@codemirror/language@6.12.1",
|
||||||
|
"https://esm.sh/@codemirror/language@^6.0.0?target=denonext": "https://esm.sh/@codemirror/language@6.12.1?target=denonext",
|
||||||
|
"https://esm.sh/@codemirror/state": "https://esm.sh/@codemirror/state@6.5.3",
|
||||||
|
"https://esm.sh/@codemirror/state@^6.0.0?target=denonext": "https://esm.sh/@codemirror/state@6.5.3?target=denonext",
|
||||||
|
"https://esm.sh/@codemirror/state@^6.4.0?target=denonext": "https://esm.sh/@codemirror/state@6.5.3?target=denonext",
|
||||||
|
"https://esm.sh/@codemirror/state@^6.5.0?target=denonext": "https://esm.sh/@codemirror/state@6.5.3?target=denonext",
|
||||||
|
"https://esm.sh/@codemirror/theme-one-dark": "https://esm.sh/@codemirror/theme-one-dark@6.1.3",
|
||||||
|
"https://esm.sh/@codemirror/view": "https://esm.sh/@codemirror/view@6.39.9",
|
||||||
|
"https://esm.sh/@codemirror/view@^6.0.0?target=denonext": "https://esm.sh/@codemirror/view@6.39.9?target=denonext",
|
||||||
|
"https://esm.sh/@codemirror/view@^6.17.0?target=denonext": "https://esm.sh/@codemirror/view@6.39.9?target=denonext",
|
||||||
|
"https://esm.sh/@codemirror/view@^6.23.0?target=denonext": "https://esm.sh/@codemirror/view@6.39.9?target=denonext",
|
||||||
|
"https://esm.sh/@codemirror/view@^6.27.0?target=denonext": "https://esm.sh/@codemirror/view@6.39.9?target=denonext",
|
||||||
|
"https://esm.sh/@lezer/common@^1.1.0?target=denonext": "https://esm.sh/@lezer/common@1.5.0?target=denonext",
|
||||||
|
"https://esm.sh/@lezer/common@^1.3.0?target=denonext": "https://esm.sh/@lezer/common@1.5.0?target=denonext",
|
||||||
|
"https://esm.sh/@lezer/common@^1.5.0?target=denonext": "https://esm.sh/@lezer/common@1.5.0?target=denonext",
|
||||||
|
"https://esm.sh/@lezer/highlight@^1.0.0?target=denonext": "https://esm.sh/@lezer/highlight@1.2.3?target=denonext",
|
||||||
|
"https://esm.sh/@marijn/find-cluster-break@^1.0.0?target=denonext": "https://esm.sh/@marijn/find-cluster-break@1.0.2?target=denonext",
|
||||||
|
"https://esm.sh/crelt@^1.0.6?target=denonext": "https://esm.sh/crelt@1.0.6?target=denonext",
|
||||||
|
"https://esm.sh/style-mod@^4.0.0?target=denonext": "https://esm.sh/style-mod@4.1.3?target=denonext",
|
||||||
|
"https://esm.sh/style-mod@^4.1.0?target=denonext": "https://esm.sh/style-mod@4.1.3?target=denonext",
|
||||||
|
"https://esm.sh/w3c-keyname@^2.2.4?target=denonext": "https://esm.sh/w3c-keyname@2.2.8?target=denonext"
|
||||||
|
},
|
||||||
|
"remote": {
|
||||||
|
"https://esm.sh/@codemirror/autocomplete@6.20.0": "2374e2aac7fbf79969fe0557b2b4dbd442e6dc3a3975a4e59a0ab8cb2a06aaf2",
|
||||||
|
"https://esm.sh/@codemirror/autocomplete@6.20.0/denonext/autocomplete.mjs": "9a8472ea9c2f078cf7c36b5d84d86668421708fa8a5c61aaf776d20f745e8425",
|
||||||
|
"https://esm.sh/@codemirror/commands@6.10.1": "ddc0ecb0619eda5d51139da9d67dcb3a0f3458d6db3fb9a8c0b8175d4418ec88",
|
||||||
|
"https://esm.sh/@codemirror/commands@6.10.1/denonext/commands.mjs": "8c634ba35ad66aa6aa6abed1f4cb7c75434750123e745bb2ce8a704aef8bfdcb",
|
||||||
|
"https://esm.sh/@codemirror/language@6.12.1": "b3729707a0665bd514c6f0d6e6d04a934379df9851e5634dc3e1370528c05372",
|
||||||
|
"https://esm.sh/@codemirror/language@6.12.1/denonext/language.mjs": "0a8d58b557b489c799a90b580161bfc08b64e1dbf7fe1eb38a1138818fab418a",
|
||||||
|
"https://esm.sh/@codemirror/language@6.12.1?target=denonext": "b3729707a0665bd514c6f0d6e6d04a934379df9851e5634dc3e1370528c05372",
|
||||||
|
"https://esm.sh/@codemirror/state@6.5.3": "701ab7a48adb966990f78084a89148f58c454f20eb774fb4df4eb32692984c2c",
|
||||||
|
"https://esm.sh/@codemirror/state@6.5.3/denonext/state.mjs": "a1504952fd381a39be5d911e7ecc9121fa6c00c25a33c39bb4b03ff5c921b87e",
|
||||||
|
"https://esm.sh/@codemirror/state@6.5.3?target=denonext": "701ab7a48adb966990f78084a89148f58c454f20eb774fb4df4eb32692984c2c",
|
||||||
|
"https://esm.sh/@codemirror/theme-one-dark@6.1.3": "c53e61336a1da62912730710222fa0168418c6d699e9f187eb2069c0a72c7ccf",
|
||||||
|
"https://esm.sh/@codemirror/theme-one-dark@6.1.3/denonext/theme-one-dark.mjs": "2d277cf317df96dde41e2a6abd1b9f475e3896201df5bb47e268a33593b65d75",
|
||||||
|
"https://esm.sh/@codemirror/view@6.39.9": "629498953f3aa24f64fee4877edb0113093cdba44f9a61a1a3488f240e89f618",
|
||||||
|
"https://esm.sh/@codemirror/view@6.39.9/denonext/view.mjs": "cd8c45e126263a9bb55a6a69fe923e342197e593147a463ea6ac8d337a27be0d",
|
||||||
|
"https://esm.sh/@codemirror/view@6.39.9?target=denonext": "629498953f3aa24f64fee4877edb0113093cdba44f9a61a1a3488f240e89f618",
|
||||||
|
"https://esm.sh/@lezer/common@1.5.0/denonext/common.mjs": "c02bee82fffdc0ab555fc712c53c8d8a6258be07a59d1c2343f999e04a5ff87d",
|
||||||
|
"https://esm.sh/@lezer/common@1.5.0?target=denonext": "93895c0d96f3796848a0d8fb76518a16e617a4fc2c7aa5889cdefa4f75efe03c",
|
||||||
|
"https://esm.sh/@lezer/highlight@1.2.3/denonext/highlight.mjs": "e4e2ed7b14be7f9e93d70848ff127fe143fb251b16ba17a19d609b2f2ccd9ffc",
|
||||||
|
"https://esm.sh/@lezer/highlight@1.2.3?target=denonext": "39396ec28c65dd80ebe903d9df1cfdc735cbfc8c71927d66f7a986b7a411babc",
|
||||||
|
"https://esm.sh/@marijn/find-cluster-break@1.0.2/denonext/find-cluster-break.mjs": "740e4a2ff6fbcd7f619e556d06b4ab21bfafc6c69472ee84999739c3e26c9e46",
|
||||||
|
"https://esm.sh/@marijn/find-cluster-break@1.0.2?target=denonext": "8ee05755b904d18303ebb157ad8f96a86de0e20738e0836493defd76a62898dc",
|
||||||
|
"https://esm.sh/crelt@1.0.6/denonext/crelt.mjs": "b19810b35976ac8cf83e6337f73e119c7b90f5332a604f352349229adfa205a9",
|
||||||
|
"https://esm.sh/crelt@1.0.6?target=denonext": "eb6b6f73c05f35edfe2c89c49a1532f695941231d7b661df7f9cced089fa6390",
|
||||||
|
"https://esm.sh/style-mod@4.1.3/denonext/style-mod.mjs": "9ac9e681c3a810eac8ce11ec40cecb9ee8cc92b436856cadabe77a9939f57f1c",
|
||||||
|
"https://esm.sh/style-mod@4.1.3?target=denonext": "643b4ab89cbbe694d05a229c0c351ac7207873f146c01df2562f0e57272803a0",
|
||||||
|
"https://esm.sh/w3c-keyname@2.2.8/denonext/w3c-keyname.mjs": "99a47c27230eb51799d1b8dc53b17f50b46aae275423c9a19e5a1dbb0cce787a",
|
||||||
|
"https://esm.sh/w3c-keyname@2.2.8?target=denonext": "5e785342c86c345144e65df42516d2d9b149191458169748770625ffc9df5c6c"
|
||||||
|
},
|
||||||
|
"workspace": {
|
||||||
|
"dependencies": [
|
||||||
|
"npm:sass@^1.97.2"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,371 +0,0 @@
|
||||||
html,
|
|
||||||
body {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
font-family: system-ui, sans-serif;
|
|
||||||
background: #909090;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wrap {
|
|
||||||
height: 100vh;
|
|
||||||
width: 100vw;
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: var(--topH, 50vh) 8px 1fr;
|
|
||||||
/* top pane, splitter, editor */
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Top pane: terminal area */
|
|
||||||
.topPane {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: var(--termW, 50vw) 8px 1fr;
|
|
||||||
/* terminal, splitter, filler */
|
|
||||||
min-height: 80px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Bottom pane: editor */
|
|
||||||
.bottomPane {
|
|
||||||
min-height: 120px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make editor fill its pane */
|
|
||||||
#editor {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.terminal {
|
|
||||||
background: #0b0f14;
|
|
||||||
color: #c9d1d9;
|
|
||||||
padding: 1em;
|
|
||||||
margin: 0px;
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
||||||
font-size: 12.5px;
|
|
||||||
line-height: 1.35;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ANSI text styles */
|
|
||||||
.ansi-bold { font-weight: 700; }
|
|
||||||
.ansi-dim { opacity: 0.7; }
|
|
||||||
|
|
||||||
/* Foreground colors (standard + bright) */
|
|
||||||
.ansi-fg-30 { color: #0b0f14; } /* black */
|
|
||||||
.ansi-fg-31 { color: #ff7b72; } /* red */
|
|
||||||
.ansi-fg-32 { color: #7ee787; } /* green */
|
|
||||||
.ansi-fg-33 { color: #f2cc60; } /* yellow */
|
|
||||||
.ansi-fg-34 { color: #79c0ff; } /* blue */
|
|
||||||
.ansi-fg-35 { color: #d2a8ff; } /* magenta */
|
|
||||||
.ansi-fg-36 { color: #a5d6ff; } /* cyan */
|
|
||||||
.ansi-fg-37 { color: #c9d1d9; } /* white */
|
|
||||||
|
|
||||||
.ansi-fg-90 { color: #6e7681; } /* bright black / gray */
|
|
||||||
.ansi-fg-91 { color: #ffa198; }
|
|
||||||
.ansi-fg-92 { color: #a6f3a6; }
|
|
||||||
.ansi-fg-93 { color: #ffe082; }
|
|
||||||
.ansi-fg-94 { color: #a5d6ff; }
|
|
||||||
.ansi-fg-95 { color: #e3b8ff; }
|
|
||||||
.ansi-fg-96 { color: #c7f0ff; }
|
|
||||||
.ansi-fg-97 { color: #ffffff; }
|
|
||||||
|
|
||||||
/* Background colors (optional) */
|
|
||||||
.ansi-bg-40 { background: #0b0f14; }
|
|
||||||
.ansi-bg-41 { background: rgba(255, 123, 114, 0.22); }
|
|
||||||
.ansi-bg-42 { background: rgba(126, 231, 135, 0.18); }
|
|
||||||
.ansi-bg-43 { background: rgba(242, 204, 96, 0.18); }
|
|
||||||
.ansi-bg-44 { background: rgba(121, 192, 255, 0.18); }
|
|
||||||
.ansi-bg-45 { background: rgba(210, 168, 255, 0.18); }
|
|
||||||
.ansi-bg-46 { background: rgba(165, 214, 255, 0.18); }
|
|
||||||
.ansi-bg-47 { background: rgba(201, 209, 217, 0.10); }
|
|
||||||
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* App layout */
|
|
||||||
.app {
|
|
||||||
height: 100vh;
|
|
||||||
width: 100vw;
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: var(--canvasH, 35vh) 8px 1fr;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------- Canvas ---------- */
|
|
||||||
.canvasPane {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #111;
|
|
||||||
}
|
|
||||||
|
|
||||||
.graph {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------- Bottom area (terminal + editor) ---------- */
|
|
||||||
/* Bottom area (terminal + editor) */
|
|
||||||
.bottomPane {
|
|
||||||
height: 100%;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 8px var(--termW, 40vw);
|
|
||||||
overflow: hidden;
|
|
||||||
/* IMPORTANT */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Terminal side */
|
|
||||||
.terminalPane {
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.terminal {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
/* terminal scrolls */
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Editor side */
|
|
||||||
.editorPane {
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
/* let CodeMirror scroll */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* CodeMirror mount point */
|
|
||||||
#editor {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* VERY IMPORTANT: force CodeMirror to respect container height */
|
|
||||||
.cm-editor {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* CodeMirror’s internal scroller (this is where the scrollbar lives) */
|
|
||||||
.cm-scroller {
|
|
||||||
overflow-y: auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------- Splitters ---------- */
|
|
||||||
.hSplit {
|
|
||||||
cursor: row-resize;
|
|
||||||
background: rgba(255, 255, 255, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vSplit {
|
|
||||||
cursor: col-resize;
|
|
||||||
background: rgba(255, 255, 255, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hSplit:hover,
|
|
||||||
.vSplit:hover {
|
|
||||||
background: rgba(121, 192, 255, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.diag {
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diag li {
|
|
||||||
margin: 6px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Syntax colors via CSS classes applied by decorations --- */
|
|
||||||
.tok-comment {
|
|
||||||
color: #1a7b24;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tok-keyword {
|
|
||||||
color: #b99400;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tok-error {
|
|
||||||
color: #ff0505;
|
|
||||||
font-weight: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tok-ident {
|
|
||||||
color: #90d4e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tok-brace {
|
|
||||||
color: #d73a49;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tok-punc {
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tok-string {
|
|
||||||
color: #03621e;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Rainbow bracket depth classes */
|
|
||||||
.rb-0 {
|
|
||||||
color: #a35;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rb-1 {
|
|
||||||
color: #ed0;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rb-2 {
|
|
||||||
color: #9d5;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rb-3 {
|
|
||||||
color: #2cb;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rb-4 {
|
|
||||||
color: #36b;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rb-5 {
|
|
||||||
color: #639;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Severity underline styles */
|
|
||||||
.cm-diag-error {
|
|
||||||
text-decoration: underline wavy #d73a49;
|
|
||||||
/* red */
|
|
||||||
text-underline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-diag-warning {
|
|
||||||
text-decoration: underline wavy #ffd33d;
|
|
||||||
/* yellow */
|
|
||||||
text-underline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cm-diag-info {
|
|
||||||
text-decoration: underline wavy #79c0ff;
|
|
||||||
/* cyan-ish */
|
|
||||||
text-underline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tooltip title coloring by severity */
|
|
||||||
.tipTitle.error {
|
|
||||||
color: #d73a49;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tipTitle.warning {
|
|
||||||
color: #ffd33d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tipTitle.info {
|
|
||||||
color: #79c0ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Optional: diagnostics panel coloring */
|
|
||||||
.diag li.error {
|
|
||||||
color: #d73a49;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diag li.warning {
|
|
||||||
color: #b08800;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diag li.info {
|
|
||||||
color: #0366d6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tooltip styling */
|
|
||||||
.cm-tooltip.cm-tooltip-hover {
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
background: black;
|
|
||||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 8px 10px;
|
|
||||||
max-width: 420px;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1.35;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tipTitle {
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tipBody {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Loading screen */
|
|
||||||
|
|
||||||
.centered {
|
|
||||||
margin-right: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
color: #f0f0f0;
|
|
||||||
font-size: 24px;
|
|
||||||
font-family: Ubuntu-Light, Helvetica, sans-serif;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lds-dual-ring {
|
|
||||||
display: inline-block;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lds-dual-ring:after {
|
|
||||||
content: " ";
|
|
||||||
display: block;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
margin: 0px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 3px solid #fff;
|
|
||||||
border-color: #fff transparent #fff transparent;
|
|
||||||
animation: lds-dual-ring 1.2s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes lds-dual-ring {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,10 +6,7 @@
|
||||||
<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="style.css" rel="stylesheet">
|
||||||
|
|
||||||
|
|
||||||
<link href="editor.css" rel="stylesheet">
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -46,7 +43,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<script type="module" src="index.js"></script>
|
<script type="module" src="src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
|
|
||||||
import wasm from "./wasm.js"
|
|
||||||
import "./editor.js"
|
|
||||||
|
|
||||||
File diff suppressed because one or more lines are too long
27
web/root/js/vis-network.min.js
vendored
27
web/root/js/vis-network.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"name": "automata-web",
|
|
||||||
"type": "module",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"files": [
|
|
||||||
"automata_bg.wasm",
|
|
||||||
"automata.js",
|
|
||||||
"automata_bg.js"
|
|
||||||
],
|
|
||||||
"main": "automata.js",
|
|
||||||
"sideEffects": [
|
|
||||||
"./automata.js",
|
|
||||||
"./snippets/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +1,26 @@
|
||||||
import { EditorView, keymap, hoverTooltip, Decoration, ViewPlugin } from "https://esm.sh/@codemirror/view";
|
// deno-lint-ignore-file
|
||||||
import { EditorState, StateField } from "https://esm.sh/@codemirror/state";
|
|
||||||
import { defaultKeymap, history, historyKeymap } from "https://esm.sh/@codemirror/commands";
|
|
||||||
import { lineNumbers, highlightActiveLineGutter } from "https://esm.sh/@codemirror/view";
|
|
||||||
import { bracketMatching, indentOnInput } from "https://esm.sh/@codemirror/language";
|
|
||||||
import { closeBrackets } from "https://esm.sh/@codemirror/autocomplete";
|
|
||||||
import { oneDark } from "https://esm.sh/@codemirror/theme-one-dark";
|
|
||||||
|
|
||||||
import wasm from "./wasm.js"
|
import {
|
||||||
|
EditorView,
|
||||||
|
keymap,
|
||||||
|
hoverTooltip,
|
||||||
|
Decoration,
|
||||||
|
ViewPlugin,
|
||||||
|
lineNumbers,
|
||||||
|
highlightActiveLineGutter,
|
||||||
|
} from "npm:@codemirror/view";
|
||||||
|
|
||||||
import * as vis from "./js/vis-network.js"
|
import { EditorState, StateField, Text } from "npm:@codemirror/state";
|
||||||
|
import { defaultKeymap, history, historyKeymap } from "npm:@codemirror/commands";
|
||||||
|
import { bracketMatching, indentOnInput } from "npm:@codemirror/language";
|
||||||
|
import { closeBrackets } from "npm:@codemirror/autocomplete";
|
||||||
|
import { oneDark } from "npm:@codemirror/theme-one-dark";
|
||||||
|
|
||||||
|
|
||||||
function tokenize(text) {
|
import wasm from "./wasm.ts"
|
||||||
|
|
||||||
|
|
||||||
|
function tokenize(text: string) {
|
||||||
try {
|
try {
|
||||||
return wasm.lex(text);
|
return wasm.lex(text);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -20,16 +29,17 @@ function tokenize(text) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function compile(text) {
|
function compile(text: string): wasm.CompileResult {
|
||||||
try {
|
try {
|
||||||
return wasm.compile(text);
|
return wasm.compile(text);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e);
|
||||||
return []
|
// @ts-expect-error wasm defines extra cleanup
|
||||||
|
return {log: [], log_formatted: ""};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenClass = (t) =>
|
const tokenClass = (t: string) =>
|
||||||
({
|
({
|
||||||
comment: "tok-comment",
|
comment: "tok-comment",
|
||||||
keyword: "tok-keyword",
|
keyword: "tok-keyword",
|
||||||
|
|
@ -47,20 +57,20 @@ const tokenClass = (t) =>
|
||||||
}[t] || "tok-ident");
|
}[t] || "tok-ident");
|
||||||
|
|
||||||
|
|
||||||
function severityClass(sev) {
|
function severityClass(sev: string) {
|
||||||
const s = (sev || "error").toLowerCase();
|
const s = (sev || "error").toLowerCase();
|
||||||
if (s === "warning") return "cm-diag-warning";
|
if (s === "warning") return "cm-diag-warning";
|
||||||
if (s === "info") return "cm-diag-info";
|
if (s === "info") return "cm-diag-info";
|
||||||
return "cm-diag-error";
|
return "cm-diag-error";
|
||||||
}
|
}
|
||||||
function sevRank(sev) {
|
function sevRank(sev: string) {
|
||||||
if (sev === "error") return 3;
|
if (sev === "error") return 3;
|
||||||
if (sev === "warning") return 2;
|
if (sev === "warning") return 2;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function buildAnalysis(text, doc) {
|
function buildAnalysis(text: string, doc: Text) {
|
||||||
const tokens = tokenize(text);
|
const tokens = tokenize(text);
|
||||||
const { log, log_formatted } = compile(text);
|
const { log, log_formatted } = compile(text);
|
||||||
|
|
||||||
|
|
@ -71,7 +81,7 @@ function buildAnalysis(text, doc) {
|
||||||
for (const tok of tokens) {
|
for (const tok of tokens) {
|
||||||
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);
|
let tc = tokenClass(tok.kind);
|
||||||
if (tc === "rb-") {
|
if (tc === "rb-") {
|
||||||
tc += tok.scope_level.toString();
|
tc += tok.scope_level.toString();
|
||||||
}
|
}
|
||||||
|
|
@ -114,7 +124,7 @@ const analysisField = StateField.define({
|
||||||
// ===================== Hover tooltip (uses cached diags) =====================
|
// ===================== Hover tooltip (uses cached diags) =====================
|
||||||
const diagHover = hoverTooltip((view, pos) => {
|
const diagHover = hoverTooltip((view, pos) => {
|
||||||
const { log } = view.state.field(analysisField);
|
const { log } = view.state.field(analysisField);
|
||||||
const hits = log.filter((d) => pos >= d.start && pos <= d.end);
|
const hits = log.filter((d) => d.start !== undefined && d.end !== undefined && pos >= d.start && pos <= d.end);
|
||||||
if (hits.length === 0) return null;
|
if (hits.length === 0) return null;
|
||||||
|
|
||||||
const top = hits.reduce((a, b) => (sevRank(b.level) > sevRank(a.level) ? b : a), hits[0]);
|
const top = hits.reduce((a, b) => (sevRank(b.level) > sevRank(a.level) ? b : a), hits[0]);
|
||||||
|
|
@ -148,27 +158,28 @@ const diagHover = hoverTooltip((view, pos) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function escapeHtml(s) {
|
function escapeHtml(s: string) {
|
||||||
return String(s)
|
return s
|
||||||
.replace(/&/g, "&")
|
.replace(/&/g, "&")
|
||||||
.replace(/</g, "<")
|
.replace(/</g, "<")
|
||||||
.replace(/>/g, ">");
|
.replace(/>/g, ">");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function ansiToHtml(input) {
|
function ansiToHtml(input: string) {
|
||||||
|
// deno-lint-ignore no-control-regex
|
||||||
const ESC_RE = /\x1b\[([0-9;]*)m/g;
|
const ESC_RE = /\x1b\[([0-9;]*)m/g;
|
||||||
|
|
||||||
let out = "";
|
let out = "";
|
||||||
let lastIndex = 0;
|
let lastIndex = 0;
|
||||||
|
|
||||||
// current style state
|
// current style state
|
||||||
let fg = null; // e.g. 31, 92
|
let fg: number|null = null; // e.g. 31, 92
|
||||||
let bg = null; // e.g. 41
|
let bg: number|null = null; // e.g. 41
|
||||||
let bold = false;
|
let bold = false;
|
||||||
let dim = false;
|
let dim = false;
|
||||||
|
|
||||||
function openSpanIfNeeded(text) {
|
function openSpanIfNeeded(text: string) {
|
||||||
if (text.length === 0) return "";
|
if (text.length === 0) return "";
|
||||||
const classes = [];
|
const classes = [];
|
||||||
if (bold) classes.push("ansi-bold");
|
if (bold) classes.push("ansi-bold");
|
||||||
|
|
@ -179,8 +190,8 @@ function ansiToHtml(input) {
|
||||||
return `<span class="${classes.join(" ")}">${escapeHtml(text)}</span>`;
|
return `<span class="${classes.join(" ")}">${escapeHtml(text)}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyCodes(codes) {
|
function applyCodes(codes: string[]) {
|
||||||
if (codes.length === 0) codes = [0];
|
if (codes.length === 0) codes = ["0"];
|
||||||
for (const c of codes) {
|
for (const c of codes) {
|
||||||
const code = Number(c);
|
const code = Number(c);
|
||||||
if (Number.isNaN(code)) continue;
|
if (Number.isNaN(code)) continue;
|
||||||
|
|
@ -220,6 +231,7 @@ function ansiToHtml(input) {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error bad library
|
||||||
function formatTerminal(view) {
|
function formatTerminal(view) {
|
||||||
const term = document.getElementById("terminal");
|
const term = document.getElementById("terminal");
|
||||||
if (!term) return;
|
if (!term) return;
|
||||||
|
|
@ -234,10 +246,14 @@ function formatTerminal(view) {
|
||||||
|
|
||||||
const terminalPlugin = ViewPlugin.fromClass(
|
const terminalPlugin = ViewPlugin.fromClass(
|
||||||
class {
|
class {
|
||||||
|
|
||||||
|
// @ts-expect-error bad library
|
||||||
constructor(view) {
|
constructor(view) {
|
||||||
|
// @ts-expect-error bad library
|
||||||
this.view = view;
|
this.view = view;
|
||||||
formatTerminal(view);
|
formatTerminal(view);
|
||||||
}
|
}
|
||||||
|
// @ts-expect-error bad library
|
||||||
update(update) {
|
update(update) {
|
||||||
if (update.docChanged) formatTerminal(update.view);
|
if (update.docChanged) formatTerminal(update.view);
|
||||||
}
|
}
|
||||||
|
|
@ -288,15 +304,15 @@ const state = EditorState.create({
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
window.editor = new EditorView({
|
const editor = new EditorView({
|
||||||
state,
|
state,
|
||||||
parent: document.getElementById("editor"),
|
parent: document.getElementById("editor")!,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function setDefaultLayoutWeights() {
|
function setDefaultLayoutWeights() {
|
||||||
const vh = window.innerHeight;
|
const vh = globalThis.window.innerHeight;
|
||||||
const vw = window.innerWidth;
|
const vw = globalThis.window.innerWidth;
|
||||||
|
|
||||||
// Canvas: 30% of screen height
|
// Canvas: 30% of screen height
|
||||||
const canvasH = Math.round(vh * 0.60);
|
const canvasH = Math.round(vh * 0.60);
|
||||||
|
|
@ -304,7 +320,7 @@ function setDefaultLayoutWeights() {
|
||||||
// Terminal: 35% of width
|
// Terminal: 35% of width
|
||||||
const termW = Math.round(vw * 0.30);
|
const termW = Math.round(vw * 0.30);
|
||||||
|
|
||||||
const app = document.getElementById("app");
|
const app = document.getElementById("app")!;
|
||||||
app.style.setProperty("--canvasH", `${canvasH}px`);
|
app.style.setProperty("--canvasH", `${canvasH}px`);
|
||||||
app.style.setProperty("--termW", `${termW}px`);
|
app.style.setProperty("--termW", `${termW}px`);
|
||||||
}
|
}
|
||||||
|
|
@ -313,9 +329,9 @@ setDefaultLayoutWeights();
|
||||||
|
|
||||||
|
|
||||||
(function enableLayoutSplitters() {
|
(function enableLayoutSplitters() {
|
||||||
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");
|
||||||
|
|
||||||
|
|
@ -334,7 +350,7 @@ setDefaultLayoutWeights();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener("mousemove", (e) => {
|
globalThis.window.addEventListener("mousemove", (e) => {
|
||||||
const rect = app.getBoundingClientRect();
|
const rect = app.getBoundingClientRect();
|
||||||
|
|
||||||
if (draggingH) {
|
if (draggingH) {
|
||||||
|
|
@ -347,7 +363,7 @@ setDefaultLayoutWeights();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (draggingV) {
|
if (draggingV) {
|
||||||
const bottomPane = document.getElementById("bottomPane");
|
const bottomPane = document.getElementById("bottomPane")!;
|
||||||
const r = bottomPane.getBoundingClientRect();
|
const r = bottomPane.getBoundingClientRect();
|
||||||
const x = e.clientX - r.left;
|
const x = e.clientX - r.left;
|
||||||
const minTerm = 220;
|
const minTerm = 220;
|
||||||
|
|
@ -357,256 +373,9 @@ setDefaultLayoutWeights();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener("mouseup", () => {
|
globalThis.window.addEventListener("mouseup", () => {
|
||||||
draggingH = false;
|
draggingH = false;
|
||||||
draggingV = false;
|
draggingV = false;
|
||||||
document.body.style.cursor = "";
|
document.body.style.cursor = "";
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
4
web/root/src/main.ts
Normal file
4
web/root/src/main.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
import "./editor.ts"
|
||||||
|
import "./visualizer.ts"
|
||||||
|
|
||||||
187
web/root/src/visualizer.ts
Normal file
187
web/root/src/visualizer.ts
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
// deno-lint-ignore-file no-unversioned-import
|
||||||
|
|
||||||
|
// deno-lint-ignore no-import-prefix
|
||||||
|
import * as vis from "npm:vis-network/standalone";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const nodes = new vis.DataSet<vis.Node>();
|
||||||
|
const edges = new vis.DataSet<vis.Edge>();
|
||||||
|
|
||||||
|
|
||||||
|
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 → ε"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
function renderNode({
|
||||||
|
ctx,
|
||||||
|
id,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
state: { selected, hover },
|
||||||
|
style,
|
||||||
|
label,
|
||||||
|
}: any) {
|
||||||
|
return {
|
||||||
|
drawNode() {
|
||||||
|
ctx.save();
|
||||||
|
const 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
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function chosen_edge(_: vis.ChosenNodeValues, id: vis.IdType,selected: boolean, hovered: boolean) {
|
||||||
|
console.log("edge", id, selected, hovered)
|
||||||
|
}
|
||||||
|
|
||||||
|
function chosen_node(_: vis.ChosenNodeValues, id: vis.IdType,selected: boolean, hovered: boolean) {
|
||||||
|
console.log("node", id, selected, hovered)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const network: vis.Network = createGraph();
|
||||||
|
|
||||||
|
function createGraph(): vis.Network {
|
||||||
|
|
||||||
|
const container = document.getElementById("graph")!;
|
||||||
|
|
||||||
|
const 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,
|
||||||
|
hoverConnectedEdges: false,
|
||||||
|
selectConnectedEdges: false,
|
||||||
|
},
|
||||||
|
nodes: {
|
||||||
|
font: { color: "#c9d1d9" },
|
||||||
|
color: {
|
||||||
|
background: "#1f6feb",
|
||||||
|
border: "#79c0ff",
|
||||||
|
highlight: { background: "#388bfd", border: "#a5d6ff" }
|
||||||
|
},
|
||||||
|
// @ts-expect-error bad library
|
||||||
|
chosen: {
|
||||||
|
node: chosen_node,
|
||||||
|
},
|
||||||
|
shape: "custom",
|
||||||
|
ctxRenderer: renderNode,
|
||||||
|
size: 18,
|
||||||
|
},
|
||||||
|
edges: {
|
||||||
|
chosen: {
|
||||||
|
// @ts-expect-error bad library
|
||||||
|
edge: chosen_edge
|
||||||
|
},
|
||||||
|
arrowStrikethrough: false,
|
||||||
|
font: { align: "middle", color: "#000000ff" },
|
||||||
|
color: { color: "rgba(201,209,217,0.35)", highlight: "#c9d1d9" },
|
||||||
|
// @ts-expect-error bad library
|
||||||
|
smooth: { type: "dynamic" },
|
||||||
|
arrows: "to",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
vis.DataSet
|
||||||
|
|
||||||
|
network.on("doubleClick", (params: any) => {
|
||||||
|
|
||||||
|
for (const node_id of params.nodes){
|
||||||
|
// @ts-expect-error bad library
|
||||||
|
const node: vis.Node = nodes.get(node_id)!;
|
||||||
|
node.physics = !node.physics;
|
||||||
|
nodes.update(node)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return network;
|
||||||
|
}
|
||||||
|
|
@ -1,22 +1,21 @@
|
||||||
console.debug("Loading wasm…");
|
console.debug("Loading wasm…");
|
||||||
import init, * as wasm from "./automata/automata_web.js";
|
import init, * as wasm from "../../wasm/automata_web.js";
|
||||||
try{
|
try{
|
||||||
console.debug("Wasm loaded. Starting app…");
|
console.debug("Wasm loaded. Starting app…");
|
||||||
window.wasm = wasm;
|
|
||||||
await init();
|
await init();
|
||||||
console.debug("App started.");
|
console.debug("App started.");
|
||||||
document.getElementById("center_text").innerHTML = '';
|
document.getElementById("center_text")!.innerHTML = '';
|
||||||
document.getElementById("app").style.display = '';
|
document.getElementById("app")!.style.display = '';
|
||||||
wasm.init();
|
wasm.init();
|
||||||
}catch(e){
|
}catch(e){
|
||||||
console.error("Failed to start: " + error);
|
console.error("Failed to start: " + e);
|
||||||
document.getElementById("the_canvas_id").remove();
|
document.getElementById("the_canvas_id")!.remove();
|
||||||
document.getElementById("center_text").innerHTML = `
|
document.getElementById("center_text")!.innerHTML = `
|
||||||
<p>
|
<p>
|
||||||
An error occurred during loading:
|
An error occurred during loading:
|
||||||
</p>
|
</p>
|
||||||
<p style="font-family:Courier New">
|
<p style="font-family:Courier New">
|
||||||
${error}
|
${e}
|
||||||
</p>
|
</p>
|
||||||
<p style="font-size:14px">
|
<p style="font-size:14px">
|
||||||
Make sure you use a modern browser with WebGL and WASM enabled.
|
Make sure you use a modern browser with WebGL and WASM enabled.
|
||||||
127
web/root/style/editor.scss
Normal file
127
web/root/style/editor.scss
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
@import url("tooltip.scss");
|
||||||
|
|
||||||
|
/* CodeMirror mount point */
|
||||||
|
#editor {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* VERY IMPORTANT: force CodeMirror to respect container height */
|
||||||
|
.cm-editor {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CodeMirror’s internal scroller (this is where the scrollbar lives) */
|
||||||
|
.cm-scroller {
|
||||||
|
overflow-y: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.diag {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diag li {
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Syntax colors via CSS classes applied by decorations --- */
|
||||||
|
.tok-comment {
|
||||||
|
color: #1a7b24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tok-keyword {
|
||||||
|
color: #b99400;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tok-error {
|
||||||
|
color: #ff0505;
|
||||||
|
font-weight: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tok-ident {
|
||||||
|
color: #90d4e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tok-brace {
|
||||||
|
color: #d73a49;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tok-punc {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tok-string {
|
||||||
|
color: #03621e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rainbow bracket depth classes */
|
||||||
|
.rb-0 {
|
||||||
|
color: #a35;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rb-1 {
|
||||||
|
color: #ed0;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rb-2 {
|
||||||
|
color: #9d5;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rb-3 {
|
||||||
|
color: #2cb;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rb-4 {
|
||||||
|
color: #36b;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rb-5 {
|
||||||
|
color: #639;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Optional: diagnostics panel coloring */
|
||||||
|
.diag li.error {
|
||||||
|
color: #d73a49;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diag li.warning {
|
||||||
|
color: #b08800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diag li.info {
|
||||||
|
color: #0366d6;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Severity underline styles */
|
||||||
|
.cm-diag-error {
|
||||||
|
text-decoration: underline wavy #d73a49;
|
||||||
|
/* red */
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-diag-warning {
|
||||||
|
text-decoration: underline wavy #ffd33d;
|
||||||
|
/* yellow */
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-diag-info {
|
||||||
|
text-decoration: underline wavy #79c0ff;
|
||||||
|
/* cyan-ish */
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
}
|
||||||
140
web/root/style/style.scss
Normal file
140
web/root/style/style.scss
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
@import url("./editor.scss");;
|
||||||
|
@import url("./terminal.scss");
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
font-family: system-ui, sans-serif;
|
||||||
|
background: #909090;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: var(--topH, 50vh) 8px 1fr;
|
||||||
|
/* top pane, splitter, editor */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Top pane: terminal area */
|
||||||
|
.topPane {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: var(--termW, 50vw) 8px 1fr;
|
||||||
|
/* terminal, splitter, filler */
|
||||||
|
min-height: 80px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bottom pane: editor */
|
||||||
|
.bottomPane {
|
||||||
|
min-height: 120px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* App layout */
|
||||||
|
.app {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: var(--canvasH, 35vh) 8px 1fr;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Canvas ---------- */
|
||||||
|
.canvasPane {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
.graph {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Bottom area (terminal + editor) ---------- */
|
||||||
|
/* Bottom area (terminal + editor) */
|
||||||
|
.bottomPane {
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 8px var(--termW, 40vw);
|
||||||
|
overflow: hidden;
|
||||||
|
/* IMPORTANT */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Terminal side */
|
||||||
|
.terminalPane {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Editor side */
|
||||||
|
.editorPane {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
/* let CodeMirror scroll */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Splitters ---------- */
|
||||||
|
.hSplit {
|
||||||
|
cursor: row-resize;
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vSplit {
|
||||||
|
cursor: col-resize;
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hSplit:hover,
|
||||||
|
.vSplit:hover {
|
||||||
|
background: rgba(121, 192, 255, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Loading screen */
|
||||||
|
.centered {
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
color: #f0f0f0;
|
||||||
|
font-size: 24px;
|
||||||
|
font-family: Ubuntu-Light, Helvetica, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lds-dual-ring {
|
||||||
|
display: inline-block;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lds-dual-ring:after {
|
||||||
|
content: " ";
|
||||||
|
display: block;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin: 0px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 3px solid #fff;
|
||||||
|
border-color: #fff transparent #fff transparent;
|
||||||
|
animation: lds-dual-ring 1.2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes lds-dual-ring {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
web/root/style/terminal.scss
Normal file
48
web/root/style/terminal.scss
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
.terminal {
|
||||||
|
background: #0b0f14;
|
||||||
|
color: #c9d1d9;
|
||||||
|
padding: 1em;
|
||||||
|
margin: 0px;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||||
|
font-size: 12.5px;
|
||||||
|
line-height: 1.35;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ANSI text styles */
|
||||||
|
.ansi-bold { font-weight: 700; }
|
||||||
|
.ansi-dim { opacity: 0.7; }
|
||||||
|
|
||||||
|
/* Foreground colors (standard + bright) */
|
||||||
|
.ansi-fg-30 { color: #0b0f14; } /* black */
|
||||||
|
.ansi-fg-31 { color: #ff7b72; } /* red */
|
||||||
|
.ansi-fg-32 { color: #7ee787; } /* green */
|
||||||
|
.ansi-fg-33 { color: #f2cc60; } /* yellow */
|
||||||
|
.ansi-fg-34 { color: #79c0ff; } /* blue */
|
||||||
|
.ansi-fg-35 { color: #d2a8ff; } /* magenta */
|
||||||
|
.ansi-fg-36 { color: #a5d6ff; } /* cyan */
|
||||||
|
.ansi-fg-37 { color: #c9d1d9; } /* white */
|
||||||
|
|
||||||
|
.ansi-fg-90 { color: #6e7681; } /* bright black / gray */
|
||||||
|
.ansi-fg-91 { color: #ffa198; }
|
||||||
|
.ansi-fg-92 { color: #a6f3a6; }
|
||||||
|
.ansi-fg-93 { color: #ffe082; }
|
||||||
|
.ansi-fg-94 { color: #a5d6ff; }
|
||||||
|
.ansi-fg-95 { color: #e3b8ff; }
|
||||||
|
.ansi-fg-96 { color: #c7f0ff; }
|
||||||
|
.ansi-fg-97 { color: #ffffff; }
|
||||||
|
|
||||||
|
/* Background colors (optional) */
|
||||||
|
.ansi-bg-40 { background: #0b0f14; }
|
||||||
|
.ansi-bg-41 { background: rgba(255, 123, 114, 0.22); }
|
||||||
|
.ansi-bg-42 { background: rgba(126, 231, 135, 0.18); }
|
||||||
|
.ansi-bg-43 { background: rgba(242, 204, 96, 0.18); }
|
||||||
|
.ansi-bg-44 { background: rgba(121, 192, 255, 0.18); }
|
||||||
|
.ansi-bg-45 { background: rgba(210, 168, 255, 0.18); }
|
||||||
|
.ansi-bg-46 { background: rgba(165, 214, 255, 0.18); }
|
||||||
|
.ansi-bg-47 { background: rgba(201, 209, 217, 0.10); }
|
||||||
32
web/root/style/tooltip.scss
Normal file
32
web/root/style/tooltip.scss
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
.tipTitle.error {
|
||||||
|
color: #d73a49;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tipTitle.warning {
|
||||||
|
color: #ffd33d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tipTitle.info {
|
||||||
|
color: #79c0ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.cm-tooltip.cm-tooltip-hover {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
background: black;
|
||||||
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
max-width: 420px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tipTitle {
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tipBody {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use automata::{
|
use automata::{
|
||||||
automata::npda::{self, NPDA},
|
loader::{self, Context, Span, Spanned, lexer::Lexer},
|
||||||
loader::{self, Span, Spanned, lexer::Lexer},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
|
|
@ -18,40 +17,6 @@ pub fn init() {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn silly(machine: &str, input: &str) {
|
|
||||||
let table = match npda::TransitionTable::load_table(machine) {
|
|
||||||
Ok((ok, logs)) => {
|
|
||||||
for log in logs.displayable() {
|
|
||||||
println!("{log}")
|
|
||||||
}
|
|
||||||
ok
|
|
||||||
}
|
|
||||||
Err(logs) => {
|
|
||||||
for log in logs.displayable() {
|
|
||||||
println!("{log}")
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("running on: '{input}'");
|
|
||||||
let mut simulator = npda::Simulator::begin(input, table);
|
|
||||||
loop {
|
|
||||||
match simulator.step() {
|
|
||||||
npda::SimulatorResult::Pending => {}
|
|
||||||
npda::SimulatorResult::Reject => {
|
|
||||||
println!("REJECTED");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
npda::SimulatorResult::Accept(npda) => {
|
|
||||||
println!("ACCEPT: {npda:?}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum Kind {
|
pub enum Kind {
|
||||||
|
|
@ -191,18 +156,16 @@ pub struct CompileResult {
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn compile(input: &str) -> CompileResult {
|
pub fn compile(input: &str) -> CompileResult {
|
||||||
let log = match npda::TransitionTable::load_table(input) {
|
let mut ctx = Context::new(input);
|
||||||
Ok((_, logs)) => logs,
|
_ = automata::loader::parse_universal(&mut ctx);
|
||||||
Err(logs) => logs,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let log_formatted = log.displayable().fold(String::new(), |mut s, e| {
|
let log_formatted = ctx.logs_display().fold(String::new(), |mut s, e| {
|
||||||
write!(&mut s, "{e}").unwrap();
|
write!(&mut s, "{e}").unwrap();
|
||||||
s
|
s
|
||||||
});
|
});
|
||||||
|
|
||||||
let log = log
|
let log = ctx.into_logs()
|
||||||
.into_entries()
|
.into_entries()
|
||||||
.map(|e| CompileLog {
|
.map(|e| CompileLog {
|
||||||
level: match e.level {
|
level: match e.level {
|
||||||
|
|
|
||||||
44
web/tools/build.ts
Normal file
44
web/tools/build.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import * as sass from "sass";
|
||||||
|
|
||||||
|
// tools/build.ts
|
||||||
|
const ROOT = new URL("../root/", import.meta.url);
|
||||||
|
const WASM = new URL("../wasm/", import.meta.url);
|
||||||
|
const DIST = new URL("../dist/", import.meta.url);
|
||||||
|
|
||||||
|
async function run(cmd: string[], cwd?: string) {
|
||||||
|
const p = new Deno.Command(cmd[0], { args: cmd.slice(1), cwd });
|
||||||
|
const out = await p.output();
|
||||||
|
if (!out.success) {
|
||||||
|
console.error(new TextDecoder().decode(out.stderr));
|
||||||
|
Deno.exit(out.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean dist
|
||||||
|
await Deno.remove(DIST, { recursive: true }).catch(() => {});
|
||||||
|
await Deno.mkdir(DIST, { recursive: true });
|
||||||
|
|
||||||
|
|
||||||
|
console.log("compiling scss...");
|
||||||
|
|
||||||
|
const result = sass.compile(String(new URL("style/style.scss", ROOT).pathname), {
|
||||||
|
style: "compressed",
|
||||||
|
});
|
||||||
|
await Deno.writeTextFile(new URL("style.css", DIST), result.css);
|
||||||
|
|
||||||
|
console.log("Compiling wasm lib...");
|
||||||
|
await run(["wasm-pack", "build", "--target", "web", "--release", "--out-dir", "wasm"], "");
|
||||||
|
await Deno.copyFile(new URL("automata_web_bg.wasm", WASM), new URL("automata_web_bg.wasm", DIST));
|
||||||
|
|
||||||
|
|
||||||
|
console.log("Compiling bundle...");
|
||||||
|
const bundle = new Deno.Command(Deno.execPath(), {
|
||||||
|
args: ["bundle", "--platform=browser", "--outdir", "dist", "root/index.html", "--minify",],
|
||||||
|
});
|
||||||
|
const bundleRes = await bundle.output();
|
||||||
|
if (!bundleRes.success) {
|
||||||
|
console.error(new TextDecoder().decode(bundleRes.stderr));
|
||||||
|
Deno.exit(bundleRes.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Build complete: dist/");
|
||||||
58
web/tools/dev.ts
Normal file
58
web/tools/dev.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
// tools/dev.ts
|
||||||
|
const BUILD_CMD = ["deno", "run", "-A", "tools/build.ts"];
|
||||||
|
const SERVE_CMD = [
|
||||||
|
"deno",
|
||||||
|
"run",
|
||||||
|
"--allow-net",
|
||||||
|
"--allow-read",
|
||||||
|
"--allow-sys",
|
||||||
|
"jsr:@std/http/file-server",
|
||||||
|
"dist",
|
||||||
|
];
|
||||||
|
|
||||||
|
let building = false;
|
||||||
|
let server: Deno.ChildProcess | null = null;
|
||||||
|
|
||||||
|
async function runBuild() {
|
||||||
|
if (building) return;
|
||||||
|
building = true;
|
||||||
|
|
||||||
|
console.log("🔨 building…");
|
||||||
|
const p = new Deno.Command(BUILD_CMD[0], {
|
||||||
|
args: BUILD_CMD.slice(1),
|
||||||
|
});
|
||||||
|
const r = await p.output();
|
||||||
|
if (!r.success) {
|
||||||
|
console.error(new TextDecoder().decode(r.stderr));
|
||||||
|
} else {
|
||||||
|
console.log("✅ build complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
building = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startServer() {
|
||||||
|
if (server) return;
|
||||||
|
const p = new Deno.Command(SERVE_CMD[0], {
|
||||||
|
args: SERVE_CMD.slice(1),
|
||||||
|
stdout: "inherit",
|
||||||
|
stderr: "inherit",
|
||||||
|
});
|
||||||
|
server = p.spawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
await runBuild();
|
||||||
|
await startServer();
|
||||||
|
|
||||||
|
console.log("👀 watching for changes…");
|
||||||
|
|
||||||
|
const watcher = Deno.watchFs(["root", "../src"]);
|
||||||
|
for await (const event of watcher) {
|
||||||
|
if (
|
||||||
|
event.kind === "modify" ||
|
||||||
|
event.kind === "create" ||
|
||||||
|
event.kind === "remove"
|
||||||
|
) {
|
||||||
|
await runBuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue