jssm
-
3.9.3
+
4.4.2
=6.0.0"
},
@@ -8,8 +8,6 @@
"main": "dist/jssm.es5.browserified.js",
"scripts": {
"nyc-test": "nyc ava src/js/tests/*.js",
- "test": "ava src/js/tests/*.js",
- "test-verbose": "ava src/js/tests/*.js -v",
"removedir": "rimraf build -f && rimraf dist -f && rimraf docs -f",
"createdir": "mkdir build && mkdir dist && mkdir docs && cd docs && mkdir docs && cd ..",
"clean": "npm run removedir && rm -f src/js/jssm-dot.js && npm run createdir",
@@ -20,20 +18,19 @@
"make": "npm run clean && npm run peg && npm run babel && npm run rename && npm run setver && npm run pack",
"flow": "flow",
"peg": "rm -f src/js/jssm-dot.js && pegjs src/js/jssm-dot.peg && cp src/js/jssm-dot.js build/",
- "eslint": "eslint src/js/jssm.js src/js/tests/*.js",
+ "eslint": "eslint src/js/jssm.js src/js/jssm-types.js src/js/tests/*.js",
"nyc-html": "nyc ava report --reporter=html",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"vet": "npm run flow && npm run eslint",
"audit": "echo \\\"major\\\" remaining. . : `grep major src/js/jssm*.js | wc -l`\\\\n\\\"wasteful\\\" remaining : `grep wasteful src/js/jssm*.js | wc -l`\\\\n\\\"any\\\" remaining. . . : `grep any src/js/jssm*.js | wc -l`\\\\n\\\"mixed\\\" remaining. . : `grep mixed src/js/jssm*.js | wc -l`\\\\n\\\"todo\\\" remaining . . : `grep todo src/js/jssm*.js | wc -l`\\\\n\\\"nextdo\\\" remaining . : `grep nextdo src/js/jssm*.js | wc -l`\\\\n\\\"whargarbl\\\" remaining: `grep whargarbl src/js/jssm*.js | wc -l`\\\\n\\\"comeback\\\" remaining : `grep comeback src/js/jssm*.js | wc -l`\\\\n\\\"fixme\\\" remaining. . : `grep fixme src/js/jssm*.js | wc -l`\\\\n\\\"stochable\\\" remaining: `grep stochable src/js/jssm*.js | wc -l`\\\\n\\\"checkme\\\" remaining. : `grep checkme src/js/jssm*.js | wc -l`",
- "qbuild": "npm run eslint && npm run make && npm run test-verbose && npm run minify && npm run docs && npm run site && npm run dist",
- "build": "npm run make && npm run vet && npm run test-verbose && npm run audit && npm run minify && npm run docs && npm run site && npm run dist",
- "nyc-build": "npm run vet && npm run make && npm run test-verbose && npm run audit && npm run nyc-test",
+ "qbuild": "npm run eslint && npm run make && npm run nyc-test && npm run minify && npm run docs && npm run site && npm run dist",
+ "build": "npm run make && npm run vet && npm run nyc-test && npm run audit && npm run minify && npm run docs && npm run site && npm run dist",
"minify": "uglifyjs ./build/jssm.es5.cjs.js -o ./build/jssm.es5.cjs.min.js --compress",
"pretest": "npm run build",
"dist": "cp build/jssm.es5.cjs.* dist/",
"site": "cp src/site/* docs/",
"docs": "documentation build src/js/** -f html -o docs/lib",
- "travis": "rm -f src/js/jssm-dot.js && npm run nyc-build && npm run nyc-test && npm run coverage"
+ "travis": "rm -f src/js/jssm-dot.js && npm run coverage"
},
"repository": {
"type": "git",
@@ -88,13 +85,15 @@
"del-cli": "^1.1.0",
"documentation": "^4.0.0",
"eslint": "^4.3.0",
- "eslint-config-stonecypher": "^1.12.3",
+ "eslint-config-stonecypher": "^1.13.1",
"eslint-plugin-ava": "^4.2.1",
"eslint-plugin-flowtype": "^2.35.0",
"eslint-plugin-fp": "^2.3.0",
"eslint-plugin-jsdoc": "^3.1.2",
+ "eslint-plugin-jsx-a11y": "^6.0.2",
"eslint-plugin-new-with-error": "^1.1.0",
"eslint-plugin-promise": "^3.5.0",
+ "eslint-plugin-react": "^7.1.0",
"eslint-plugin-unicorn": "^2.1.2",
"flow-bin": "^0.49.1",
"gulp-string-replace": "^0.4.0",
diff --git a/src/demo/index.html b/src/demo/index.html
index f8a4b79d..d044023b 100644
--- a/src/demo/index.html
+++ b/src/demo/index.html
@@ -4,18 +4,35 @@
+
-
+ Loading…
diff --git a/src/js/jssm-dot.peg b/src/js/jssm-dot.peg
index c0cd6583..180702fb 100644
--- a/src/js/jssm-dot.peg
+++ b/src/js/jssm-dot.peg
@@ -11,35 +11,70 @@ GvizShape
-ForwardLightArrow "forward light arrow"
+ForwardLightArrow "forward light arrow ->"
= "->"
-TwoWayLightArrow "two way light arrow"
+TwoWayLightArrow "two way light arrow <->"
= "<->"
-ForwardFatArrow "forward fat arrow"
+BackLightArrow "back light arrow <-"
+ = "<-"
+
+
+ForwardFatArrow "forward fat arrow =>"
= "=>"
-TwoWayFatArrow "two way fat arrow"
+TwoWayFatArrow "two way fat arrow <=>"
= "<=>"
-ForwardTildeArrow "forward tilde arrow"
+BackFatArrow "back fat arrow <="
+ = "<="
+
+
+ForwardTildeArrow "forward tilde arrow ~>"
= "~>"
-TwoWayTildeArrow "two way tilde arrow"
+TwoWayTildeArrow "two way tilde arrow <~>"
= "<~>"
+BackTildeArrow "back tilde arrow <~"
+ = "<~"
+
+
+LightFatArrow "light fat arrow <-=>"
+ = "<-=>"
+
+LightTildeArrow "light tilde arrow <-~>"
+ = "<-~>"
+
+FatLightArrow "fat light arrow <=->"
+ = "<=->"
+
+FatTildeArrow "fat tilde arrow <=~>"
+ = "<=~>"
+
+TildeLightArrow "tilde light arrow <~->"
+ = "<~->"
+
+TildeFatArrow "tilde fat arrow <~=>"
+ = "<~=>"
+
+
LightArrow "light arrow"
- = ForwardLightArrow / TwoWayLightArrow
+ = ForwardLightArrow / TwoWayLightArrow / BackLightArrow
FatArrow "fat arrow"
- = ForwardFatArrow / TwoWayFatArrow
+ = ForwardFatArrow / TwoWayFatArrow / BackFatArrow
TildeArrow "tilde arrow"
- = ForwardTildeArrow / TwoWayTildeArrow
+ = ForwardTildeArrow / TwoWayTildeArrow / BackTildeArrow
+
+MixedArrow "mixed arrow"
+ = LightFatArrow / LightTildeArrow / FatLightArrow / FatTildeArrow / TildeLightArrow / TildeFatArrow
+
Arrow "arrow"
- = LightArrow / FatArrow / TildeArrow
+ = MixedArrow / LightArrow / FatArrow / TildeArrow
@@ -71,7 +106,7 @@ Unescaped = [\x20-\x21\x23-\x5B\x5D-\u10FFFF] // explicitly omits "
ActionLabelChar
= ActionLabelUnescaped
/ Escape Sequence:(
- '`'
+ "'"
/ "\\"
/ "/"
/ "b" { return "\b"; }
@@ -86,12 +121,15 @@ ActionLabelChar
)
{ return Sequence; }
-ActionLabelQuoteMark = '`'
-ActionLabelUnescaped = [\x20-\x5B\x5D-\x5F\x61-\u10FFFF] // explicitly omits `
+ActionLabelQuoteMark = "'"
+ActionLabelUnescaped = [\x20-\x26\x28-\x5B\x5D-\u10FFFF] // explicitly omits ' which is hex 27
ActionLabel "action label"
= ActionLabelQuoteMark chars:ActionLabelChar* ActionLabelQuoteMark { return chars.join(""); }
+LineTerminator
+ = [\n\r\u2028\u2029]
+
Whitespace "whitespace"
= [ \t\n\r\v]*
@@ -144,25 +182,31 @@ LabelOrLabelList
Subexp
- = Whitespace lactl:ActionLabel?
- Whitespace ldesc:ArrowDesc?
- Whitespace arrow:Arrow
- Whitespace rdesc:ArrowDesc?
- Whitespace ractl:ActionLabel?
- Whitespace label:LabelOrLabelList
- Whitespace tail:Subexp? {
+ = Whitespace r_action : ActionLabel?
+ Whitespace l_desc : ArrowDesc?
+ Whitespace arrow : Arrow
+ Whitespace r_desc : ArrowDesc?
+ Whitespace l_action : ActionLabel?
+ Whitespace label : LabelOrLabelList
+ Whitespace tail : Subexp? {
+
const base = {kind: arrow, to: label};
- if (tail && (tail !== [])) { base.se = tail; }
- if (ldesc) { base.ldesc = ldesc; }
- if (rdesc) { base.rdesc = rdesc; }
+ if (tail && (tail !== [])) { base.se = tail; }
+ if (l_desc) { base.l_desc = l_desc; }
+ if (r_desc) { base.r_desc = r_desc; }
+ if (l_action) { base.l_action = l_action; }
+ if (r_action) { base.r_action = r_action; }
return base;
+
}
+
+
Exp
= label:LabelOrLabelList se:Subexp Whitespace ';' Whitespace {
- const base = {from: label};
+ const base = { key: 'transition', from: label };
if (se && (se !== [])) { base.se = se; }
return base;
}
@@ -189,6 +233,8 @@ ConfigValidation
GvizLayout
= "dot"
/ "circo"
+ / "fdp"
+ / "neato"
StateItemShapeKey
= "in_shape"
@@ -198,12 +244,8 @@ StateItemShapeKey
StateItemShape
= Whitespace key:StateItemShapeKey Whitespace ":" Whitespace value:GvizShape Whitespace ";" Whitespace { return {key:key, value:value}; }
-StateItemGraphLayout
- = Whitespace "graph_layout" Whitespace ":" Whitespace value:GvizLayout Whitespace ";" Whitespace { return {key:"graph_layout", value:value}; }
-
StateItem
= StateItemShape
- / StateItemGraphLayout
StateItems
= StateItem+
@@ -253,52 +295,29 @@ ConfigTransition
-GraphBg
- = Whitespace "graph_bg" Whitespace ":" Whitespace value:GvizShape Whitespace ";" Whitespace { return {key:"graph_bg", value:value}; }
-
-MinTransitionsPerState
- = Whitespace "min_transitions_per_state" Whitespace ":" Whitespace value:Label Whitespace ";" Whitespace { return {key:"min_transitions_per_state", value:value}; }
-
-MaxTransitionsPerState
- = Whitespace "max_transitions_per_state" Whitespace ":" Whitespace value:Label Whitespace ";" Whitespace { return {key:"max_transitions_per_state", value:value}; }
-
-GraphInputs
- = Whitespace "inputs" Whitespace ":" Whitespace value:LabelList Whitespace ";" Whitespace { return {key:"inputs", value:value}; }
-
-GraphOutputs
- = Whitespace "outputs" Whitespace ":" Whitespace value:LabelList Whitespace ";" Whitespace { return {key:"outputs", value:value}; }
-
-GraphStartNodes
- = Whitespace "start_nodes" Whitespace ":" Whitespace value:LabelList Whitespace ";" Whitespace { return {key:"start_nodes", value:value}; }
-
-GraphEndNodes
- = Whitespace "end_nodes" Whitespace ":" Whitespace value:LabelList Whitespace ";" Whitespace { return {key:"end_nodes", value:value}; }
+ConfigGraphLayout
+ = Whitespace "graph_layout" Whitespace ":" Whitespace value:GvizLayout Whitespace ";" Whitespace { return {key:"graph_layout", value:value}; }
-GraphItem
- = GraphBg
- / MinTransitionsPerState
- / MaxTransitionsPerState
- / GraphStartNodes
- / GraphEndNodes
- / GraphInputs
- / GraphOutputs
+ConfigStartNodes
+ = Whitespace "start_nodes" Whitespace ":" Whitespace value:LabelList Whitespace ";" Whitespace { return {key:"start_nodes", value:value}; }
-GraphItems
- = GraphItem+
+ConfigEndNodes
+ = Whitespace "end_nodes" Whitespace ":" Whitespace value:LabelList Whitespace ";" Whitespace { return {key:"end_nodes", value:value}; }
-ConfigGraph "graph configuration"
- = Whitespace "graph" Whitespace ":" Whitespace "{" Whitespace graph_items:GraphItems? Whitespace "};" Whitespace {
- return { config_kind: "graph", config_items: graph_items || [] };
- }
+ConfigGraphBgColor
+ = Whitespace "graph_bg_color" Whitespace ":" Whitespace value:Color Whitespace ";" Whitespace { return {key:"graph_bg_color", value:value}; }
Config "configuration"
- = ConfigGraph
+ = ConfigGraphLayout
+ / ConfigStartNodes
+ / ConfigEndNodes
/ ConfigTransition
/ ConfigAction
/ ConfigState
/ ConfigValidation
+ / ConfigGraphBgColor
@@ -317,10 +336,28 @@ StateDef "state definition"
+MachineName
+ = Whitespace "machine name" Whitespace ":" Whitespace name:Label Whitespace ";" Whitespace { return { key: "Machine name", value: name }; }
+
+
+
+StateGroupDef
+ = Whitespace "group" Whitespace name:Label Whitespace ":" Whitespace nl:LabelOrLabelList Whitespace ";" Whitespace { return { key: "group definition", value: { name: name, list: nl } }; }
+
+
+
+Comment
+ = Whitespace "/*" (!"*/" .)* "*/" Whitespace { return { key: 'comment' }; }
+ / Whitespace "//" (!LineTerminator .)* Whitespace
+
+
Term
= Exp
/ StateDef
+ / MachineName
+ / StateGroupDef
/ Config
+ / Comment
TermList
= term:Term*
diff --git a/src/js/jssm-types.js b/src/js/jssm-types.js
index a9978f23..051901c8 100644
--- a/src/js/jssm-types.js
+++ b/src/js/jssm-types.js
@@ -5,13 +5,22 @@
-type JssmSuccess = { success: true };
-type JssmFailure = { success: false, error: mixed };
-type JssmIncomplete = { success: 'incomplete' };
-type JssmResult = JssmSuccess | JssmFailure | JssmIncomplete;
+type JssmSuccess = { success: true };
+type JssmFailure = { success: false, error: mixed };
+type JssmIncomplete = { success: 'incomplete' };
+type JssmResult = JssmSuccess | JssmFailure | JssmIncomplete;
-type JssmPermitted = 'required' | 'disallowed';
-type JssmPermittedOpt = 'required' | 'disallowed' | 'optional';
+type JssmPermitted = 'required' | 'disallowed';
+type JssmPermittedOpt = 'required' | 'disallowed' | 'optional';
+
+type JssmArrow = '->' | '<->' | '<=->' | '<~->'
+ | '=>' | '<=>' | '<-=>' | '<~=>'
+ | '~>' | '<~>' | '<-~>' | '<=~>';
+
+type JssmArrowDirection = 'left' | 'right' | 'both';
+type JssmArrowKind = 'none' | 'legal' | 'main' | 'forced';
+
+type JssmLayout = 'dot' | 'circo' | 'twopi' | 'fdp';
@@ -43,7 +52,9 @@ type JssmGenericState
= {
type JssmTransitionPermitter = (OldState: NT, NewState: NT, OldData: DT, NewData: DT) => boolean;
-type JssmTransitionPermitterMaybeArray = JssmTransitionPermitter | Array< JssmTransitionPermitter >;
+
+type JssmTransitionPermitterMaybeArray = JssmTransitionPermitter
+ | Array< JssmTransitionPermitter >;
@@ -83,14 +94,16 @@ type JssmTransition = {
action? : NT,
check? : JssmTransitionPermitterMaybeArray, // validate this edge's transition; usually about data
probability? : number, // for stoch modelling, would like to constrain to [0..1], dunno how
- usual? : string // most common exit, for graphing; likelihood overrides
+ kind : JssmArrowKind,
+ forced_only : boolean,
+ main_path : boolean
};
type JssmTransitions = Array< JssmTransition >;
type JssmTransitionList = {
- entrances : Array,
- exits : Array
+ entrances : Array,
+ exits : Array
};
@@ -100,6 +113,8 @@ type JssmGenericConfig = {
initial_state : NT,
+ layout? : JssmLayout,
+
complete? : Array,
transitions : JssmTransitions,
@@ -117,7 +132,45 @@ type JssmGenericConfig = {
simplify_bidi? : boolean,
- auto_api? : boolean | string; // boolean false means don't; boolean true means do; string means do-with-this-prefix
+ auto_api? : boolean | string // boolean false means don't; boolean true means do; string means do-with-this-prefix
+
+};
+
+
+
+
+
+type JssmCompileRule = {
+
+ agg_as : string,
+ val : mixed
+
+};
+
+
+
+
+
+type JssmCompileSe = {
+
+ to : NT,
+ se : JssmCompileSe,
+ kind : JssmArrow,
+ l_action? : NT,
+ r_action? : NT
+
+};
+
+
+
+
+
+type JssmCompileSeStart = {
+
+ from : NT,
+ se : JssmCompileSe,
+ key : string,
+ value? : string | mixed | number
};
@@ -125,13 +178,35 @@ type JssmGenericConfig = {
+type JssmParseTree = Array< JssmCompileSeStart >;
+
+
+
+
+
export type {
JssmTransition,
+ JssmTransitions,
JssmTransitionList,
+ JssmArrow,
+ JssmArrowKind,
+ JssmArrowDirection,
+
JssmGenericConfig,
JssmGenericState,
+ JssmGenericMachine,
+
+ JssmParseTree,
+ JssmCompileSe,
+ JssmCompileSeStart,
+ JssmCompileRule,
+
+ JssmPermitted,
+ JssmResult,
+
+ JssmLayout,
JssmMachineInternalState
diff --git a/src/js/jssm.js b/src/js/jssm.js
index 9251c9a3..f0244f2a 100644
--- a/src/js/jssm.js
+++ b/src/js/jssm.js
@@ -4,26 +4,246 @@
// @flow
import type {
+
JssmGenericState, JssmGenericConfig,
JssmTransition, JssmTransitionList,
- JssmMachineInternalState
+ JssmMachineInternalState,
+ JssmParseTree,
+ JssmCompileSe, JssmCompileSeStart, JssmCompileRule,
+ JssmArrow, JssmArrowDirection, JssmArrowKind,
+ JssmLayout
+
} from './jssm-types';
+
+
+
+
+import { seq, weighted_rand_select, weighted_sample_select, histograph, weighted_histo_key } from './jssm-util.js';
+
+const parse : (string) => JssmParseTree = require('./jssm-dot.js').parse; // eslint-disable-line flowtype/no-weak-types // todo whargarbl remove any
+
const version : null = null; // replaced from package.js in build
-import { seq, weighted_rand_select, weighted_sample_select, histograph, weighted_histo_key } from './jssm-util.js';
+function arrow_direction(arrow : JssmArrow) : JssmArrowDirection {
+
+ switch ( String(arrow) ) {
+
+ case '->' : case '=>' : case '~>' :
+ return 'right';
+
+ case '<-' : case '<=' : case '<~' :
+ return 'left';
+
+ case '<->': case '<-=>': case '<-~>':
+ case '<=>': case '<=->': case '<=~>':
+ case '<~>': case '<~->': case '<~=>':
+ return 'both';
+
+ default:
+ throw new Error(`arrow_direction: unknown arrow type ${arrow}`);
+
+ }
+
+}
+
+
+
+
+
+function arrow_left_kind(arrow : JssmArrow) : JssmArrowKind {
+
+ switch ( String(arrow) ) {
+
+ case '->': case '=>' : case '~>':
+ return 'none';
+
+ case '<-': case '<->': case '<-=>': case '<-~>':
+ return 'legal';
+
+ case '<=': case '<=>': case '<=->': case '<=~>':
+ return 'main';
+
+ case '<~': case '<~>': case '<~->': case '<~=>':
+ return 'forced';
+
+ default:
+ throw new Error(`arrow_direction: unknown arrow type ${arrow}`);
+
+ }
+
+}
+
+
+
+
+
+function arrow_right_kind(arrow : JssmArrow) : JssmArrowKind {
+
+ switch ( String(arrow) ) {
+
+ case '<-': case '<=' : case '<~':
+ return 'none';
+
+ case '->': case '<->': case '<=->': case '<~->':
+ return 'legal';
+
+ case '=>': case '<=>': case '<-=>': case '<~=>':
+ return 'main';
+
+ case '~>': case '<~>': case '<-~>': case '<=~>':
+ return 'forced';
+
+ default:
+ throw new Error(`arrow_direction: unknown arrow type ${arrow}`);
+
+ }
+
+}
+
+
+
+
+
+function compile_rule_transition_step(
+ acc : Array< JssmTransition >,
+ from : mNT,
+ to : mNT,
+ this_se : JssmCompileSe,
+ next_se : JssmCompileSe
+ ) : Array< JssmTransition > { // todo flow describe the parser representation of a transition step extension
+
+ const edges : Array< JssmTransition > = [];
+
+ const uFrom : Array< mNT > = (Array.isArray(from)? from : [from]),
+ uTo : Array< mNT > = (Array.isArray(to)? to : [to] );
+
+ uFrom.map( (f:mNT) => {
+ uTo.map( (t:mNT) => {
+
+ const rk : JssmArrowKind = arrow_right_kind(this_se.kind),
+ lk : JssmArrowKind = arrow_left_kind(this_se.kind);
+
+
+ const right : JssmTransition = {
+ from : f,
+ to : t,
+ kind : rk,
+ forced_only : rk === 'forced',
+ main_path : rk === 'main'
+ };
+
+ if (this_se.r_action) { right.action = this_se.r_action; }
+ if (right.kind !== 'none') { edges.push(right); }
+
+
+ const left : JssmTransition = {
+ from : t,
+ to : f,
+ kind : lk,
+ forced_only : lk === 'forced',
+ main_path : lk === 'main'
+ };
+
+ if (this_se.l_action) { left.action = this_se.l_action; }
+ if (left.kind !== 'none') { edges.push(left); }
+
+ });
+ });
+
+ const new_acc : Array< JssmTransition > = acc.concat(edges);
+
+ if (next_se) {
+ return compile_rule_transition_step(new_acc, to, next_se.to, next_se, next_se.se);
+ } else {
+ return new_acc;
+ }
+
+}
+
-const parse : (string) => Array = require('./jssm-dot.js').parse; // todo burn out any
+
+function compile_rule_handle_transition(rule : JssmCompileSeStart) : mixed { // todo flow describe the parser representation of a transition
+ return compile_rule_transition_step([], rule.from, rule.se.to, rule.se, rule.se.se);
+}
+
+
+
+function compile_rule_handler(rule : JssmCompileSeStart) : JssmCompileRule { // todo flow describe the output of the parser
+
+ if (rule.key === 'transition') { return { agg_as: 'transition', val: compile_rule_handle_transition(rule) }; }
+
+ const tautologies : Array = ['graph_layout', 'start_nodes', 'end_nodes'];
+ if (tautologies.includes(rule.key)) {
+ return { agg_as: rule.key, val: rule.value };
+ }
+
+ throw new Error(`compile_rule_handler: Unknown rule: ${JSON.stringify(rule)}`);
+
+}
+
+
+
+function compile(tree : JssmParseTree) : JssmGenericConfig { // todo flow describe the output of the parser
+
+ const results : {
+ graph_layout : Array< JssmLayout >,
+ transition : Array< JssmTransition >,
+ start_nodes : Array< mNT >,
+ end_nodes : Array< mNT >,
+ initial_state : Array< mNT >
+ } = {
+ graph_layout : [],
+ transition : [],
+ start_nodes : [],
+ end_nodes : [],
+ initial_state : []
+ };
+
+ tree.map( (tr : JssmCompileSeStart) => {
+
+ const rule : JssmCompileRule = compile_rule_handler(tr),
+ agg_as : string = rule.agg_as,
+ val : mixed = rule.val; // todo better types
+
+ results[agg_as] = results[agg_as].concat(val);
+
+ });
+
+ ['graph_layout', 'initial_state'].map( (oneOnlyKey : string) => {
+ if (results[oneOnlyKey].length > 1) {
+ throw new Error(`May only have one ${oneOnlyKey} statement maximum: ${JSON.stringify(results[oneOnlyKey])}`);
+ }
+ });
+
+ const assembled_transitions : Array< JssmTransition > = [].concat(... results['transition']);
+
+ const result_cfg : JssmGenericConfig = {
+ initial_state : results.start_nodes.length? results.start_nodes[0] : assembled_transitions[0].from,
+ transitions : assembled_transitions
+ };
+
+ if (results.graph_layout.length) { result_cfg.layout = results.graph_layout[0]; }
+
+ return result_cfg;
+
+}
+function make(plan : string) : JssmGenericConfig {
+ return compile(parse(plan));
+}
+
+
-class machine {
+
+class Machine {
_state : mNT;
@@ -33,10 +253,12 @@ class machine {
_named_transitions : Map;
_actions : Map>;
_reverse_actions : Map>;
- _reverse_action_targets : Map>;
+ _reverse_action_targets : Map>;
+
+ _layout : JssmLayout;
// whargarbl this badly needs to be broken up, monolith master
- constructor({ initial_state, complete=[], transitions } : JssmGenericConfig) {
+ constructor({ initial_state, complete=[], transitions, layout = 'dot' } : JssmGenericConfig) {
this._state = initial_state;
this._states = new Map();
@@ -47,27 +269,33 @@ class machine {
this._reverse_actions = new Map();
this._reverse_action_targets = new Map(); // todo
- transitions.map( (tr:any) => { // whargarbl burn out any
+ this._layout = layout;
+
+ transitions.map( (tr:JssmTransition) => {
if (tr.from === undefined) { throw new Error(`transition must define 'from': ${JSON.stringify(tr)}`); }
if (tr.to === undefined) { throw new Error(`transition must define 'to': ${ JSON.stringify(tr)}`); }
// get the cursors. what a mess
- let cursor_from = this._states.get(tr.from);
- if (cursor_from === undefined) {
- this._new_state({name: tr.from, from: [], to: [], complete: complete.includes(tr.from) });
- cursor_from = (this._states.get(tr.from) : any);
+ const cursor_from : JssmGenericState
+ = this._states.get(tr.from)
+ || { name: tr.from, from: [], to: [], complete: complete.includes(tr.from) };
+
+ if (!(this._states.has(tr.from))) {
+ this._new_state(cursor_from);
}
- let cursor_to = this._states.get(tr.to);
- if (cursor_to === undefined) {
- this._new_state({name: tr.to, from: [], to: [], complete: complete.includes(tr.to) });
- cursor_to = (this._states.get(tr.to) : any);
+ const cursor_to : JssmGenericState
+ = this._states.get(tr.to)
+ || {name: tr.to, from: [], to: [], complete: complete.includes(tr.to) };
+
+ if (!(this._states.has(tr.to))) {
+ this._new_state(cursor_to);
}
// guard against existing connections being re-added
if (cursor_from.to.includes(tr.to)) {
- throw new Error(`already has ${tr.from} to ${tr.to}`);
+ throw new Error(`already has ${JSON.stringify(tr.from)} to ${JSON.stringify(tr.to)}`);
} else {
cursor_from.to.push(tr.to);
cursor_to.from.push(tr.from);
@@ -75,19 +303,21 @@ class machine {
// add the edge; note its id
this._edges.push(tr);
- const thisEdgeId = this._edges.length - 1;
+ const thisEdgeId : number = this._edges.length - 1;
// guard against repeating a transition name
if (tr.name) {
- if (this._named_transitions.has(tr.name)) { throw new Error(`named transition "${tr.name}" already created`); }
- else { this._named_transitions.set(tr.name, thisEdgeId); }
+ if (this._named_transitions.has(tr.name)) {
+ throw new Error(`named transition "${JSON.stringify(tr.name)}" already created`);
+ } else {
+ this._named_transitions.set(tr.name, thisEdgeId);
+ }
}
// set up the mapping, so that edges can be looked up by endpoint pairs
- let from_mapping = this._edge_map.get(tr.from);
- if (from_mapping === undefined) {
- this._edge_map.set(tr.from, new Map());
- from_mapping = (this._edge_map.get(tr.from) : any); // whargarbl burn out uses of any
+ const from_mapping : Map = this._edge_map.get(tr.from) || new Map();
+ if (!(this._edge_map.has(tr.from))) {
+ this._edge_map.set(tr.from, from_mapping);
}
// const to_mapping = from_mapping.get(tr.to);
@@ -98,21 +328,21 @@ class machine {
// forward mapping first by action name
- let actionMap = this._actions.get(tr.action);
+ let actionMap : ?Map = this._actions.get(tr.action);
if (!(actionMap)) {
actionMap = new Map();
this._actions.set(tr.action, actionMap);
}
if (actionMap.has(tr.from)) {
- throw new Error(`action ${tr.action} already attached to origin ${tr.from}`);
+ throw new Error(`action ${JSON.stringify(tr.action)} already attached to origin ${JSON.stringify(tr.from)}`);
} else {
actionMap.set(tr.from, thisEdgeId);
}
// reverse mapping first by state origin name
- let rActionMap = this._reverse_actions.get(tr.from);
+ let rActionMap : ?Map = this._reverse_actions.get(tr.from);
if (!(rActionMap)) {
rActionMap = new Map();
this._reverse_actions.set(tr.from, rActionMap);
@@ -150,7 +380,7 @@ class machine {
_new_state(state_config : JssmGenericState) : mNT { // whargarbl get that state_config any under control
if (this._states.has(state_config.name)) {
- throw new Error(`state ${(state_config.name:any)} already exists`);
+ throw new Error(`state ${JSON.stringify(state_config.name)} already exists`);
}
this._states.set(state_config.name, state_config);
@@ -182,6 +412,10 @@ class machine {
return this.state_is_final(this.state());
}
+ layout() : string {
+ return String(this._layout);
+ }
+
machine_state() : JssmMachineInternalState {
@@ -213,7 +447,7 @@ class machine {
}
state_for(whichState : mNT) : JssmGenericState {
- const state = this._states.get(whichState);
+ const state : ?JssmGenericState = this._states.get(whichState);
if (state) { return state; }
else { throw new Error(`no such state ${JSON.stringify(state)}`); }
}
@@ -234,13 +468,23 @@ class machine {
- get_transition_by_state_names(from: mNT, to: mNT) {
- return this._edge_map.has(from)? (this._edge_map.get(from) : any).get(to) : undefined;
+ get_transition_by_state_names(from: mNT, to: mNT) : ?number {
+
+ const emg : ?Map = this._edge_map.get(from);
+
+ if (emg) {
+ return emg.get(to);
+ } else {
+ return undefined;
+ }
+
}
+
+
lookup_transition_for(from: mNT, to: mNT) : ?JssmTransition {
- const id = this.get_transition_by_state_names(from, to);
- return (id === undefined)? undefined : this._edges[id];
+ const id : ?number = this.get_transition_by_state_names(from, to);
+ return ((id === undefined) || (id === null))? undefined : this._edges[id];
}
@@ -261,19 +505,22 @@ class machine {
probable_exits_for(whichState : mNT) : Array< JssmTransition > {
- const wstate = this._states.get(whichState);
+ const wstate : ?JssmGenericState = this._states.get(whichState);
if (!(wstate)) { throw new Error(`No such state ${JSON.stringify(whichState)} in probable_exits_for`); }
- const wstate_to = wstate.to,
- wtf = wstate_to.map(ws => this.lookup_transition_for(this.state(), ws)).filter(defined => defined);
+ const wstate_to : Array< mNT > = wstate.to,
+
+ wtf : Array< JssmTransition >
+ = wstate_to
+ .map( (ws) : ?JssmTransition => this.lookup_transition_for(this.state(), ws))
+ .filter(Boolean);
- return (wtf:any); // :any because it can't see that .filter(d => d) removes
- // the undefineds, and l_t_f returns ?jt, but this returns jt
+ return wtf;
}
probabilistic_transition() : boolean {
- const selected = weighted_rand_select(this.probable_exits_for(this.state()));
+ const selected : JssmTransition = weighted_rand_select(this.probable_exits_for(this.state()));
return this.transition( selected.to );
}
@@ -287,20 +534,20 @@ class machine {
.concat([this.state()]);
}
- probabilistic_histo_walk(n : number) : Map {
+ probabilistic_histo_walk(n : number) : Map {
return histograph(this.probabilistic_walk(n));
}
actions(whichState : mNT = this.state() ) : Array {
- const wstate = this._reverse_actions.get(whichState);
+ const wstate : ?Map = this._reverse_actions.get(whichState);
if (wstate) { return [... wstate.keys()]; }
else { throw new Error(`No such state ${JSON.stringify(whichState)}`); }
}
list_states_having_action(whichState : mNT) : Array {
- const wstate = this._actions.get(whichState);
+ const wstate : ?Map = this._actions.get(whichState);
if (wstate) { return [... wstate.keys()]; }
else { throw new Error(`No such state ${JSON.stringify(whichState)}`); }
}
@@ -314,26 +561,26 @@ class machine {
.map( filtered => filtered.from );
}
*/
- list_exit_actions(whichState : mNT = this.state() ) : Array { // these are mNT
- const ra_base = this._reverse_actions.get(whichState);
+ list_exit_actions(whichState : mNT = this.state() ) : Array { // these are mNT, not ?mNT
+ const ra_base : ?Map = this._reverse_actions.get(whichState);
if (!(ra_base)) { throw new Error(`No such state ${JSON.stringify(whichState)}`); }
return [... ra_base.values()]
- .map ( (edgeId:number) => this._edges[edgeId] )
- .filter ( (o:JssmTransition) => o.from === whichState )
- .map ( filtered => filtered.action );
+ .map ( (edgeId:number) : JssmTransition => this._edges[edgeId] )
+ .filter ( (o:JssmTransition) : boolean => o.from === whichState )
+ .map ( (filtered : JssmTransition) : ?mNT => filtered.action );
}
- probable_action_exits(whichState : mNT = this.state() ) : Array { // these are mNT
- const ra_base = this._reverse_actions.get(whichState);
+ probable_action_exits(whichState : mNT = this.state() ) : Array { // these are mNT
+ const ra_base : ?Map = this._reverse_actions.get(whichState);
if (!(ra_base)) { throw new Error(`No such state ${JSON.stringify(whichState)}`); }
return [... ra_base.values()]
- .map ( (edgeId:number) => this._edges[edgeId] )
- .filter ( (o:JssmTransition) => o.from === whichState )
- .map ( filtered => ( { action : filtered.action,
- probability : filtered.probability } )
- );
+ .map ( (edgeId:number) : JssmTransition => this._edges[edgeId] )
+ .filter ( (o:JssmTransition) : boolean => o.from === whichState )
+ .map ( (filtered) : mixed => ( { action : filtered.action,
+ probability : filtered.probability } )
+ );
}
@@ -344,7 +591,7 @@ class machine {
}
has_unenterables() : boolean {
- return this.states().some(x => this.is_unenterable(x));
+ return this.states().some( (x) : boolean => this.is_unenterable(x));
}
@@ -359,7 +606,7 @@ class machine {
}
has_terminals() : boolean {
- return this.states().some(x => this.state_is_terminal(x));
+ return this.states().some( (x) : boolean => this.state_is_terminal(x));
}
@@ -369,13 +616,13 @@ class machine {
}
state_is_complete(whichState : mNT) : boolean {
- const wstate = this._states.get(whichState);
+ const wstate : ?JssmGenericState = this._states.get(whichState);
if (wstate) { return wstate.complete; }
else { throw new Error(`No such state ${JSON.stringify(whichState)}`); }
}
has_completes() : boolean {
- return this.states().some(x => this.state_is_complete(x));
+ return this.states().some( (x) : boolean => this.state_is_complete(x) );
}
@@ -385,7 +632,7 @@ class machine {
// todo whargarbl implement data stuff
// todo major incomplete whargarbl comeback
if (this.valid_action(name, newData)) {
- const edge = this.current_action_edge_for(name);
+ const edge : JssmTransition = this.current_action_edge_for(name);
this._state = edge.to;
return true;
} else {
@@ -405,7 +652,6 @@ class machine {
}
}
-/* whargarbl reintroduce after valid_force_transition is re-enabled
// can leave machine in inconsistent state. generally do not use
force_transition(newState : mNT, newData? : mDT) : boolean {
// todo whargarbl implement hooks
@@ -418,18 +664,17 @@ class machine {
return false;
}
}
-*/
current_action_for(action : mNT) : number | void {
- const action_base = this._actions.get(action);
+ const action_base : ?Map = this._actions.get(action);
return action_base? action_base.get(this.state()) : undefined;
}
current_action_edge_for(action : mNT) : JssmTransition {
- const idx = this.current_action_for(action);
- if (idx === undefined) { throw new Error(`No such action ${JSON.stringify(action)}`); }
+ const idx : ?number = this.current_action_for(action);
+ if ((idx === undefined) || (idx === null)) { throw new Error(`No such action ${JSON.stringify(action)}`); }
return this._edges[idx];
}
@@ -444,14 +689,21 @@ class machine {
// todo whargarbl implement hooks
// todo whargarbl implement data stuff
// todo major incomplete whargarbl comeback
- return (this.lookup_transition_for(this.state(), newState) !== undefined);
+ const transition_for : ?JssmTransition = this.lookup_transition_for(this.state(), newState);
+
+ if (!(transition_for)) { return false; }
+ if (transition_for.forced_only) { return false; }
+
+ return true;
+
}
-/* todo whargarbl re-enable force_transition/1 after implementing this
- valid_force_transition(newState : mNT, newData? : mDT) : boolean {
- return false; // major todo whargarbl
+ valid_force_transition(newState : mNT, _newData? : mDT) : boolean { // todo comeback unignore newData
+ // todo whargarbl implement hooks
+ // todo whargarbl implement data stuff
+ // todo major incomplete whargarbl comeback
+ return (this.lookup_transition_for(this.state(), newState) !== undefined);
}
-*/
}
@@ -460,12 +712,47 @@ class machine {
+function sm(template_strings : Array /* , arguments */) : Machine {
+
+ // foo`a${1}b${2}c` will come in as (['a','b','c'],1,2)
+ // this includes when a and c are empty strings
+ // therefore template_strings will always have one more el than template_args
+ // therefore map the smaller container and toss the last one on on the way out
+
+ return new Machine(make(template_strings.reduce(
+
+ // in general avoiding `arguments` is smart. however with the template
+ // string notation, as designed, it's not really worth the hassle
+
+ /* eslint-disable fp/no-arguments */
+ /* eslint-disable prefer-rest-params */
+ (acc, val, idx) : string => `${acc}${arguments[idx]}${val}` // arguments[0] is never loaded, so args doesn't need to be gated
+ /* eslint-enable prefer-rest-params */
+ /* eslint-enable fp/no-arguments */
+
+ )));
+
+}
+
+
+
+
+
export {
version,
- machine,
- parse,
+ Machine,
+
+ make,
+ parse,
+ compile,
+
+ sm,
+
+ arrow_direction,
+ arrow_left_kind,
+ arrow_right_kind,
// todo whargarbl these should be exported to a utility library
seq, weighted_rand_select, histograph, weighted_sample_select, weighted_histo_key
diff --git a/src/js/tests/array_transitions.js b/src/js/tests/array_transitions.js
new file mode 100644
index 00000000..93a9af79
--- /dev/null
+++ b/src/js/tests/array_transitions.js
@@ -0,0 +1,54 @@
+
+/* eslint-disable max-len */
+
+import {describe} from 'ava-spec';
+
+const jssm = require('../../../build/jssm.es5.js');
+
+
+
+
+
+describe('array on left', async it => {
+
+ const aLeft = [{main_path: false,forced_only: false,"from":"a","to":"d","kind":"legal"},
+ {main_path: false,forced_only: false,"from":"b","to":"d","kind":"legal"},
+ {main_path: false,forced_only: false,"from":"c","to":"d","kind":"legal"}];
+
+ it('[a b c]->d;', t => t.deepEqual(aLeft, jssm.compile(jssm.parse('[a b c]->d;')).transitions ));
+
+});
+
+
+
+
+
+describe('array on right', async it => {
+
+ const aRight = [{main_path: false,forced_only: false,"from":"a","to":"b","kind":"legal"},
+ {main_path: false,forced_only: false,"from":"a","to":"c","kind":"legal"},
+ {main_path: false,forced_only: false,"from":"a","to":"d","kind":"legal"}];
+
+ it('a->[b c d];', t => t.deepEqual(aRight, jssm.compile(jssm.parse('a->[b c d];')).transitions ));
+
+});
+
+
+
+
+
+describe('array on both sides', async it => {
+
+ const aBoth = [{main_path: false,forced_only: false,"from":"a","to":"x","kind":"legal"},
+ {main_path: false,forced_only: false,"from":"a","to":"y","kind":"legal"},
+ {main_path: false,forced_only: false,"from":"a","to":"z","kind":"legal"},
+ {main_path: false,forced_only: false,"from":"b","to":"x","kind":"legal"},
+ {main_path: false,forced_only: false,"from":"b","to":"y","kind":"legal"},
+ {main_path: false,forced_only: false,"from":"b","to":"z","kind":"legal"},
+ {main_path: false,forced_only: false,"from":"c","to":"x","kind":"legal"},
+ {main_path: false,forced_only: false,"from":"c","to":"y","kind":"legal"},
+ {main_path: false,forced_only: false,"from":"c","to":"z","kind":"legal"}];
+
+ it('[a b c]->[x y z];', t => t.deepEqual(aBoth, jssm.compile(jssm.parse('[a b c]->[x y z];')).transitions ));
+
+});
diff --git a/src/js/tests/arrow.js b/src/js/tests/arrow.js
new file mode 100644
index 00000000..9ef7ebad
--- /dev/null
+++ b/src/js/tests/arrow.js
@@ -0,0 +1,109 @@
+
+/* eslint-disable max-len */
+
+import {describe} from 'ava-spec';
+
+const jssm = require('../../../build/jssm.es5.js');
+
+
+
+
+
+describe('arrow_direction', async it => {
+
+ it('<-', t => t.is('left', jssm.arrow_direction('<-') ) );
+ it('<=', t => t.is('left', jssm.arrow_direction('<=') ) );
+ it('<~', t => t.is('left', jssm.arrow_direction('<~') ) );
+
+ it('->', t => t.is('right', jssm.arrow_direction('->') ) );
+ it('=>', t => t.is('right', jssm.arrow_direction('=>') ) );
+ it('~>', t => t.is('right', jssm.arrow_direction('~>') ) );
+
+ it('<->', t => t.is('both', jssm.arrow_direction('<->') ) );
+ it('<=>', t => t.is('both', jssm.arrow_direction('<=>') ) );
+ it('<~>', t => t.is('both', jssm.arrow_direction('<~>') ) );
+
+ it('<-=>', t => t.is('both', jssm.arrow_direction('<-=>') ) );
+ it('<=->', t => t.is('both', jssm.arrow_direction('<=->') ) );
+ it('<-~>', t => t.is('both', jssm.arrow_direction('<-~>') ) );
+ it('<~->', t => t.is('both', jssm.arrow_direction('<~->') ) );
+ it('<=~>', t => t.is('both', jssm.arrow_direction('<=~>') ) );
+ it('<~=>', t => t.is('both', jssm.arrow_direction('<~=>') ) );
+
+});
+
+
+
+
+
+describe('arrow_left_kind', async it => {
+
+ it('->', t => t.is('none', jssm.arrow_left_kind('->') ) );
+ it('=>', t => t.is('none', jssm.arrow_left_kind('=>') ) );
+ it('~>', t => t.is('none', jssm.arrow_left_kind('~>') ) );
+
+ it('<-', t => t.is('legal', jssm.arrow_left_kind('<-') ) );
+ it('<->', t => t.is('legal', jssm.arrow_left_kind('<->') ) );
+ it('<-=>', t => t.is('legal', jssm.arrow_left_kind('<-=>') ) );
+ it('<-~>', t => t.is('legal', jssm.arrow_left_kind('<-~>') ) );
+
+ it('<=', t => t.is('main', jssm.arrow_left_kind('<=') ) );
+ it('<=>', t => t.is('main', jssm.arrow_left_kind('<=>') ) );
+ it('<=->', t => t.is('main', jssm.arrow_left_kind('<=->') ) );
+ it('<=~>', t => t.is('main', jssm.arrow_left_kind('<=~>') ) );
+
+ it('<~', t => t.is('forced', jssm.arrow_left_kind('<~') ) );
+ it('<~>', t => t.is('forced', jssm.arrow_left_kind('<~>') ) );
+ it('<~->', t => t.is('forced', jssm.arrow_left_kind('<~->') ) );
+ it('<~=>', t => t.is('forced', jssm.arrow_left_kind('<~=>') ) );
+
+});
+
+
+
+
+
+describe('arrow_right_kind', async it => {
+
+ it('<-', t => t.is('none', jssm.arrow_right_kind('<-') ) );
+ it('<=', t => t.is('none', jssm.arrow_right_kind('<=') ) );
+ it('<~', t => t.is('none', jssm.arrow_right_kind('<~') ) );
+
+ it('->', t => t.is('legal', jssm.arrow_right_kind('->') ) );
+ it('<->', t => t.is('legal', jssm.arrow_right_kind('<->') ) );
+ it('<=->', t => t.is('legal', jssm.arrow_right_kind('<=->') ) );
+ it('<~->', t => t.is('legal', jssm.arrow_right_kind('<~->') ) );
+
+ it('=>', t => t.is('main', jssm.arrow_right_kind('=>') ) );
+ it('<=>', t => t.is('main', jssm.arrow_right_kind('<=>') ) );
+ it('<-=>', t => t.is('main', jssm.arrow_right_kind('<-=>') ) );
+ it('<~=>', t => t.is('main', jssm.arrow_right_kind('<~=>') ) );
+
+ it('~>', t => t.is('forced', jssm.arrow_right_kind('~>') ) );
+ it('<~>', t => t.is('forced', jssm.arrow_right_kind('<~>') ) );
+ it('<-~>', t => t.is('forced', jssm.arrow_right_kind('<-~>') ) );
+ it('<=~>', t => t.is('forced', jssm.arrow_right_kind('<=~>') ) );
+
+});
+
+
+
+
+
+describe('error catchery', async _parse_it => {
+
+ describe('unknown arrow direction', async it => {
+ it('throws', t => t.throws( () => { jssm.arrow_direction('boop'); } ));
+ });
+
+ describe('unknown arrow left kind', async it => {
+ it('throws', t => t.throws( () => { jssm.arrow_left_kind('boop'); } ));
+ });
+
+ describe('unknown arrow right kind', async it => {
+ it('throws', t => t.throws( () => { jssm.arrow_right_kind('boop'); } ));
+ });
+
+});
+
+// stochable
diff --git a/src/js/tests/compile.js b/src/js/tests/compile.js
new file mode 100644
index 00000000..a07e0078
--- /dev/null
+++ b/src/js/tests/compile.js
@@ -0,0 +1,48 @@
+
+/* eslint-disable max-len */
+
+import {describe} from 'ava-spec';
+
+const jssm = require('../../../build/jssm.es5.js');
+
+
+
+
+
+describe('compile/1', async _parse_it => {
+
+ describe('a->b;', async it => {
+ const a_to_b_str = `a->b;`;
+ it('doesn\'t throw', t => t.notThrows(() => { jssm.compile(jssm.parse(a_to_b_str)); }) );
+ });
+
+ describe('a->b->c;', async it => {
+ const a_to_b_to_c_str = `a->b->c;`;
+ it('doesn\'t throw', t => t.notThrows(() => { jssm.compile(jssm.parse(a_to_b_to_c_str)); }) );
+ });
+
+ describe('template tokens', async it => {
+ const a_through_e_token_str = `a->${'b'}->c->${'d'}->e;`;
+ it('doesn\'t throw', t => t.notThrows(() => { jssm.compile(jssm.parse(a_through_e_token_str)); }) );
+ });
+
+ describe('all arrows', async it => {
+ const all_arrows = `a -> b => c ~> d <-> e <=> f <~> g <-=> h <=-> i <~-> j <-~> k <=~> l <~=> m <- n <= o <~ p;`;
+ it('doesn\'t throw', t => t.notThrows(() => { jssm.compile(jssm.parse(all_arrows)); }) );
+ });
+
+});
+
+
+
+
+
+describe('error catchery', async _parse_it => {
+
+ describe('unknown rule', async it => {
+ it('throws', t => t.throws( () => { jssm.compile( [{"key":"FAKE_RULE","from":"a","se":{"kind":"->","to":"b"}}] ); } ));
+ });
+
+});
+
+// stochable
diff --git a/src/js/tests/forced transitions.js b/src/js/tests/forced transitions.js
new file mode 100644
index 00000000..78a23b66
--- /dev/null
+++ b/src/js/tests/forced transitions.js
@@ -0,0 +1,29 @@
+
+/* eslint-disable max-len */
+
+import {describe} from 'ava-spec';
+
+const jssm = require('../../../build/jssm.es5.js'),
+ sm = jssm.sm;
+
+
+
+
+
+describe('reject and accept correctly', async it => {
+
+ const machine = sm` a ~> b -> c; `;
+
+ it('starts in a', t => t.is('a', machine.state() ));
+ it('rejects transition to b', t => t.is(false, machine.transition('b') ));
+ it('still in a', t => t.is('a', machine.state() ));
+ it('rejects transition to c', t => t.is(false, machine.transition('c') ));
+ it('still in a', t => t.is('a', machine.state() ));
+ it('rejects forced transition to c', t => t.is(false, machine.force_transition('c') ));
+ it('still in a', t => t.is('a', machine.state() ));
+ it('accepts forced transition to b', t => t.is(true, machine.force_transition('b') ));
+ it('now in b', t => t.is('b', machine.state() ));
+ it('accepts transition to c', t => t.is(true, machine.transition('c') ));
+ it('now in c', t => t.is('c', machine.state() ));
+
+});
diff --git a/src/js/tests/general.js b/src/js/tests/general.js
index 63d2d411..50ea8308 100644
--- a/src/js/tests/general.js
+++ b/src/js/tests/general.js
@@ -15,82 +15,9 @@ test('build-set version number is present', t => t.is(typeof jssm.version, 'stri
-describe('Simple stop light', async it => {
-
- const trs = [
- { name: 'SwitchToWarn', action: 'Proceed', from:'Green', to:'Yellow' },
- { name: 'SwitchToHalt', action: 'Proceed', from:'Yellow', to:'Red' },
- { name: 'SwitchToGo', action: 'Proceed', from:'Red', to:'Green' }
- ],
- light = new jssm.machine({
- initial_state : 'Red',
- transitions : trs
- });
-
- const r_states = light.states();
- it('has the right state count', t => t.is(r_states.length, 3));
- trs.map(t => t.to).map(c =>
- it(`has state "${c}"`, t => t.is(r_states.includes(c), true))
- );
-
- const r_names = light.list_named_transitions();
- it('has the right named transition count', t => t.is(r_names.size, 3));
- trs.map(t => t.name)
- .map(a =>
- it(`has named transition "${a}"`, t => t.is(r_names.has(a), true))
- );
-
- it.describe('- `proceed` walkthrough', async it2 => {
-
- it2('machine starts red', t => t.is("Red", light.state()));
- it2('proceed is true', t => t.is(true, light.action('Proceed')));
- it2('light is now green', t => t.is("Green", light.state()));
- it2('proceed is true', t => t.is(true, light.action('Proceed')));
- it2('light is now yellow', t => t.is("Yellow", light.state()));
- it2('proceed is true', t => t.is(true, light.action('Proceed')));
- it2('light is red again', t => t.is("Red", light.state()));
-
- });
-
- it.describe('- mixed - `proceed` and `transition`', async it3 => {
-
- it3('machine starts red', t => t.is("Red", light.state()));
- it3('proceed is true', t => t.is(true, light.action('Proceed')));
- it3('light is now green', t => t.is("Green", light.state()));
-
- it3('green refuses transition red', t => t.is(false, light.transition('Red')));
- it3('green still green', t => t.is("Green", light.state()));
- it3('green refuses transition green', t => t.is(false, light.transition('Green')));
- it3('green still green', t => t.is("Green", light.state()));
- it3('green accepts transition yellow', t => t.is(true, light.transition('Yellow')));
- it3('green now yellow', t => t.is("Yellow", light.state()));
-
- it3('proceed is true', t => t.is(true, light.action('Proceed')));
- it3('light is red again', t => t.is("Red", light.state()));
-
- it3('red refuses transition yellow', t => t.is(false, light.transition('Yellow')));
- it3('red still red', t => t.is("Red", light.state()));
- it3('red refuses transition red', t => t.is(false, light.transition('Red')));
- it3('red still red', t => t.is("Red", light.state()));
- it3('red accepts transition green', t => t.is(true, light.transition('Green')));
- it3('red now green', t => t.is("Green", light.state()));
-
- it3('proceed is true', t => t.is(true, light.action('Proceed')));
- it3('light is yellow again', t => t.is("Yellow", light.state()));
- it3('proceed is true', t => t.is(true, light.action('Proceed')));
- it3('light is finally red again', t => t.is("Red", light.state()));
-
- });
-
-});
-
-
-
-
-
describe('Stochastic weather', async _it => {
- new jssm.machine({
+ new jssm.Machine({
initial_state: 'breezy',
@@ -150,89 +77,9 @@ describe('Stochastic weather', async _it => {
-describe('Complex stop light', async it => {
-
- const light2 = new jssm.machine({
-
- initial_state: 'off',
-
- transitions:[
-
- { name:'turn_on', action:'power_on', from:'off', to:'red'},
-
- { action:'power_off', from:'red', to:'off' },
- { action:'power_off', from:'yellow', to:'off' },
- { action:'power_off', from:'green', to:'off' },
-
- { name:'switch_warn', action:'proceed', from:'green', to:'yellow' },
- { name:'switch_halt', action:'proceed', from:'yellow', to:'red' },
- { name:'switch_go', action:'proceed', from:'red', to:'green' }
-
- ]
-
- });
-
- const r_states = light2.states();
- it('has the right state count', t => t.is(r_states.length, 4));
- ['red', 'yellow', 'green', 'off'].map(c =>
- it(`has state "${c}"`, t => t.is(r_states.includes(c), true))
- );
-
- const r_names = light2.list_named_transitions();
- it('has the right named transition count', t => t.is(r_names.size, 4));
- ['turn_on', 'switch_warn', 'switch_halt', 'switch_go'].map(a =>
- it(`has named transition "${a}"`, t => t.is(r_names.has(a), true))
- );
-
- it('has the right exit actions for red', t => t.deepEqual(['power_off', 'proceed'], light2.list_exit_actions('red')));
-
-
- it.describe('- `transition` walkthrough', async it2 => {
-
- it2('machine starts off', t => t.is("off", light2.state()));
- it2('off refuses green', t => t.is(false, light2.transition('green')));
- it2('off refuses yellow', t => t.is(false, light2.transition('yellow')));
-
- it2('off refuses proceed', t => t.is(false, light2.action('proceed')));
-
- it2('off accepts red', t => t.is(true, light2.transition('red')));
- it2('off is now red', t => t.is("red", light2.state()));
- it2('red refuses yellow', t => t.is(false, light2.transition('yellow')));
- it2('red still red', t => t.is("red", light2.state()));
- it2('red refuses red', t => t.is(false, light2.transition('red')));
- it2('red still red', t => t.is("red", light2.state()));
-
- it2('red accepts green', t => t.is(true, light2.transition('green')));
- it2('red now green', t => t.is("green", light2.state()));
- it2('green refuses red', t => t.is(false, light2.transition('red')));
- it2('green still green', t => t.is("green", light2.state()));
- it2('green refuses green', t => t.is(false, light2.transition('green')));
- it2('green still green', t => t.is("green", light2.state()));
-
- it2('green accepts yellow', t => t.is(true, light2.transition('yellow')));
- it2('green now yellow', t => t.is("yellow", light2.state()));
- it2('yellow refuses green', t => t.is(false, light2.transition('green')));
- it2('yellow still yellow', t => t.is("yellow", light2.state()));
- it2('yellow refuses yellow', t => t.is(false, light2.transition('yellow')));
- it2('yellow still yellow', t => t.is("yellow", light2.state()));
-
- it2('yellow accepts red', t => t.is(true, light2.transition('red')));
- it2('back to red', t => t.is("red", light2.state()));
-
- it2('proceed is true', t => t.is(true, light2.action('proceed')));
- it2('light is now green', t => t.is("green", light2.state()));
-
- });
-
-});
-
-
-
-
-
describe('list exit actions', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { from:'off', to:'red', action:'on' }, { from:'red', to:'off',action:'off' } ]
});
@@ -249,7 +96,7 @@ describe('list exit actions', async it => {
describe('probable exits for', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { from:'off', to:'red' } ]
});
@@ -267,7 +114,7 @@ describe('probable exits for', async it => {
describe('probable action exits', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { from:'off', to:'red', action:'on' }, { from:'red', to:'off',action:'off' } ]
});
@@ -289,7 +136,7 @@ describe('probable action exits', async it => {
describe('probabilistic_transition', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { from:'off', to:'red' } ]
});
@@ -306,7 +153,7 @@ describe('probabilistic_transition', async it => {
describe('probabilistic_walk', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { from:'off', to:'red' }, { from:'red', to:'off' } ]
});
@@ -323,7 +170,7 @@ describe('probabilistic_walk', async it => {
describe('probabilistic_histo_walk', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { from:'off', to:'red' }, { from:'red', to:'off' } ]
});
@@ -342,7 +189,7 @@ describe('probabilistic_histo_walk', async it => {
describe('reports state_is_final', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[
{ from:'off', to:'red' },
@@ -365,7 +212,7 @@ describe('reports state_is_final', async it => {
describe('reports is_final', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[
{ from:'off', to:'red' }
@@ -390,7 +237,7 @@ describe('reports is_final', async it => {
describe('reports state_is_terminal', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { name:'turn_on', action:'power_on', from:'off', to:'red'} ]
});
@@ -406,7 +253,7 @@ describe('reports state_is_terminal', async it => {
describe('reports is_terminal', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { name:'turn_on', action:'power_on', from:'off', to:'red'} ]
});
@@ -429,7 +276,7 @@ describe('reports is_terminal', async it => {
describe('reports state_is_complete', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { name:'turn_on', action:'power_on', from:'off', to:'red'} ],
complete:['off'] // huhu
@@ -446,7 +293,7 @@ describe('reports state_is_complete', async it => {
describe('reports is_complete', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { name:'turn_on', action:'power_on', from:'off', to:'red'} ],
complete:['off'] // huhu
@@ -470,7 +317,7 @@ describe('reports is_complete', async it => {
describe('reports on actions', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { name:'turn_on', action:'power_on', from:'off', to:'red'} ]
});
@@ -490,7 +337,7 @@ describe('reports on actions', async it => {
describe('actions', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { from:'off', to:'red', action:'on' }, { from:'red', to:'off',action:'off' } ]
});
@@ -512,7 +359,7 @@ describe('actions', async it => {
describe('states having action', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { from:'off', to:'red', action:'on' }, { from:'red', to:'off',action:'off' } ]
});
@@ -528,7 +375,7 @@ describe('states having action', async it => {
describe('unenterables', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { name:'turn_on', action:'power_on', from:'off', to:'red'} ]
});
@@ -545,7 +392,7 @@ describe('unenterables', async it => {
describe('reports on action edges', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { name:'turn_on', action:'power_on', from:'off', to:'red'} ]
});
@@ -561,7 +408,7 @@ describe('reports on action edges', async it => {
describe('reports on states', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { name:'turn_on', action:'power_on', from:'off', to:'red'} ]
});
@@ -578,7 +425,7 @@ describe('reports on states', async it => {
describe('returns states', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { name:'turn_on', action:'power_on', from:'off', to:'red'} ]
});
@@ -593,7 +440,7 @@ describe('returns states', async it => {
describe('reports on transitions', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { name:'turn_on', action:'power_on', from:'off', to:'red'} ]
});
@@ -643,7 +490,7 @@ describe('reports on transitions', async it => {
describe('transition by state names', async it => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'off',
transitions:[ { name:'turn_on', action:'power_on', from:'off', to:'red'} ]
});
@@ -663,7 +510,7 @@ describe('Illegal machines', async it => {
it('catch repeated names', t => t.throws(() => {
- new jssm.machine({
+ new jssm.Machine({
initial_state: 'moot',
transitions:[
{ name:'identical', from:'1', to:'2' },
@@ -676,7 +523,7 @@ describe('Illegal machines', async it => {
it('must define from', t => t.throws(() => {
- new jssm.machine({
+ new jssm.Machine({
initial_state: 'moot',
transitions:[
{ name:'identical', to:'2' }
@@ -688,7 +535,7 @@ describe('Illegal machines', async it => {
it('must define to', t => t.throws(() => {
- new jssm.machine({
+ new jssm.Machine({
initial_state: 'moot',
transitions:[
{ name:'identical', from:'1' }
@@ -700,7 +547,7 @@ describe('Illegal machines', async it => {
it('must not have two identical edges', t => t.throws(() => {
- new jssm.machine({
+ new jssm.Machine({
initial_state: 'moot',
transitions:[
{ name:'id1', from:'1', to:'2' },
@@ -713,7 +560,7 @@ describe('Illegal machines', async it => {
it('must not have two of the same action from the same source', t => t.throws(() => {
- new jssm.machine({
+ new jssm.Machine({
initial_state: 'moot',
transitions:[
{ name:'id1', from:'1', to:'2', action:'identical' },
@@ -726,7 +573,7 @@ describe('Illegal machines', async it => {
it('must not have completion of non-state', t => t.throws(() => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'moot',
transitions:[
{ name:'id1', from:'1', to:'2', action:'identical' }
@@ -740,7 +587,7 @@ describe('Illegal machines', async it => {
it('internal state helper must not accept double states', t => t.throws(() => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: 'moot',
transitions:[
{ name:'id1', from:'1', to:'2', action:'identical' }
@@ -755,7 +602,7 @@ describe('Illegal machines', async it => {
it('can\'t get actions of non-state', t => t.throws(() => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: '1',
transitions:[
{ name:'id1', from:'1', to:'2', action:'identical' }
@@ -769,7 +616,7 @@ describe('Illegal machines', async it => {
it('can\'t get states having non-action', t => t.throws(() => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: '1',
transitions:[
{ name:'id1', from:'1', to:'2', action:'identical' }
@@ -783,7 +630,7 @@ describe('Illegal machines', async it => {
it('can\'t list exit states of non-action', t => t.throws(() => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: '1',
transitions:[
{ name:'id1', from:'1', to:'2', action:'identical' }
@@ -797,7 +644,7 @@ describe('Illegal machines', async it => {
it('probable exits for throws on non-state', t => t.throws(() => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: '1',
transitions:[
{ name:'id1', from:'1', to:'2', action:'identical' }
@@ -814,7 +661,7 @@ test(t => {
it('no probable action exits of non-action', t => t.throws(() => {
- const machine = new jssm.machine({
+ const machine = new jssm.Machine({
initial_state: '1',
transitions:[
{ name:'id1', from:'1', to:'2', action:'identical' }
diff --git a/src/js/tests/graph node lists.js b/src/js/tests/graph node lists.js
new file mode 100644
index 00000000..4db82fca
--- /dev/null
+++ b/src/js/tests/graph node lists.js
@@ -0,0 +1,16 @@
+
+/* eslint-disable max-len */
+
+import {describe} from 'ava-spec';
+
+const jssm = require('../../../build/jssm.es5.js'),
+ sm = jssm.sm;
+
+
+
+
+
+describe('graph node lists', async it => {
+ it('start nodes don\'t throw', t => t.notThrows(() => { const _a = sm`start_nodes: [a b c]; a->b->c->d;`; }) );
+ it('end nodes don\'t throw', t => t.notThrows(() => { const _a = sm`end_nodes: [a b c]; a->b->c->d;`; }) );
+});
diff --git a/src/js/tests/layout.js b/src/js/tests/layout.js
new file mode 100644
index 00000000..3d7d04ea
--- /dev/null
+++ b/src/js/tests/layout.js
@@ -0,0 +1,30 @@
+
+/* eslint-disable max-len */
+
+import {describe} from 'ava-spec';
+
+const jssm = require('../../../build/jssm.es5.js'),
+ sm = jssm.sm;
+
+
+
+
+
+describe('graph attributes don\'t throw', async it => {
+ const machine = sm`graph_layout: circo; a->b->c->d->e->f->a;`;
+ it('layout is circo', t => t.is('circo', machine.layout() ));
+});
+
+
+
+
+
+describe('error catchery', async _parse_it => {
+
+ describe('double graph_layout', async it => {
+ it('throws', t => t.throws( () => {
+ const _machine = sm`graph_layout: circo; graph_layout: circo; a->b->c->d->e->f->a;`;
+ } ));
+ });
+
+});
diff --git a/src/js/tests/parse actions.js b/src/js/tests/parse actions.js
new file mode 100644
index 00000000..175452d7
--- /dev/null
+++ b/src/js/tests/parse actions.js
@@ -0,0 +1,35 @@
+
+/* eslint-disable max-len */
+
+import {describe} from 'ava-spec';
+
+const jssm = require('../../../build/jssm.es5.js'),
+ sm = jssm.sm;
+
+
+
+
+
+describe('matter', async it => {
+
+ const matter = sm` Solid 'Heat' <-> 'Cool' Liquid 'Heat' <-> 'Cool' Gas 'Heat' <-> 'Cool' Plasma; `;
+
+ it( 'starts Solid', t => t.is('Solid', matter.state() ));
+ it( 'Heat is true', t => t.is(true, matter.action('Heat') ));
+ it( 'is now Liquid', t => t.is('Liquid', matter.state() ));
+ it( 'Heat is true', t => t.is(true, matter.action('Heat') ));
+ it( 'is now Gas', t => t.is('Gas', matter.state() ));
+ it( 'Heat is true', t => t.is(true, matter.action('Heat') ));
+ it( 'is now Plasma', t => t.is('Plasma', matter.state() ));
+ it( 'Heat is false', t => t.is(false, matter.action('Heat') ));
+ it( 'is now Plasma', t => t.is('Plasma', matter.state() ));
+ it( 'Cool is true', t => t.is(true, matter.action('Cool') ));
+ it( 'is now Gas', t => t.is('Gas', matter.state() ));
+ it( 'Cool is true', t => t.is(true, matter.action('Cool') ));
+ it( 'is now Liquid', t => t.is('Liquid', matter.state() ));
+ it( 'Cool is true', t => t.is(true, matter.action('Cool') ));
+ it( 'is now Solid', t => t.is('Solid', matter.state() ));
+ it( 'Cool is false', t => t.is(false, matter.action('Cool') ));
+ it( 'is now Solid', t => t.is('Solid', matter.state() ));
+
+});
diff --git a/src/js/tests/parse.js b/src/js/tests/parse.js
index cb376787..172394ba 100644
--- a/src/js/tests/parse.js
+++ b/src/js/tests/parse.js
@@ -13,10 +13,10 @@ describe('parse/1', async _parse_it => {
describe('forward arrow', async it => {
- const AtoB = [{"from": "a","se": {"kind": "->","to": "b"}}],
- AdB = [{"from": "a","se": {"kind": "->","to": "b","ldesc": [{"key":"arc_label","value":"d"}]}}],
- ABd = [{"from": "a","se": {"kind": "->","to": "b","rdesc": [{"key":"arc_label","value":"d"}]}}],
- AdBd = [{"from": "a","se": {"kind": "->","to": "b","ldesc": [{"key":"arc_label","value":"d"}],"rdesc": [{"key":"arc_label","value":"f"}]}}];
+ const AtoB = [{"key": "transition", "from": "a", "se": {"kind": "->","to": "b"}}],
+ AdB = [{"key": "transition", "from": "a", "se": {"kind": "->","to": "b","l_desc": [{"key":"arc_label","value":"d"}]}}],
+ ABd = [{"key": "transition", "from": "a", "se": {"kind": "->","to": "b","r_desc": [{"key":"arc_label","value":"d"}]}}],
+ AdBd = [{"key": "transition", "from": "a", "se": {"kind": "->","to": "b","l_desc": [{"key":"arc_label","value":"d"}],"r_desc": [{"key":"arc_label","value":"f"}]}}];
const echo_equal = (testt, validator) => it(test, t => t.deepEqual(validator, jssm.parse(testt)));
@@ -32,10 +32,10 @@ describe('parse/1', async _parse_it => {
describe('double arrow', async it => {
- const AtoB = [{"from": "a","se": {"kind": "<->","to": "b"}}],
- AdB = [{"from": "a","se": {"kind": "<->","to": "b","ldesc": [{"key":"arc_label","value":"d"}]}}],
- ABd = [{"from": "a","se": {"kind": "<->","to": "b","rdesc": [{"key":"arc_label","value":"d"}]}}],
- AdBd = [{"from": "a","se": {"kind": "<->","to": "b","ldesc": [{"key":"arc_label","value":"d"}],"rdesc": [{"key":"arc_label","value":"f"}]}}];
+ const AtoB = [{"key": "transition", "from": "a", "se": {"kind": "<->","to": "b"}}],
+ AdB = [{"key": "transition", "from": "a", "se": {"kind": "<->","to": "b","l_desc": [{"key":"arc_label","value":"d"}]}}],
+ ABd = [{"key": "transition", "from": "a", "se": {"kind": "<->","to": "b","r_desc": [{"key":"arc_label","value":"d"}]}}],
+ AdBd = [{"key": "transition", "from": "a", "se": {"kind": "<->","to": "b","l_desc": [{"key":"arc_label","value":"d"}],"r_desc": [{"key":"arc_label","value":"f"}]}}];
const echo_equal = (testt, validator) => it(test, t => t.deepEqual(validator, jssm.parse(testt)));
@@ -50,12 +50,12 @@ describe('parse/1', async _parse_it => {
});
describe('chain', async it => {
- const AtoBtoC = [{"from":"a","se":{"kind":"->","to":"b","se":{"kind":"->","to":"c"}}}];
+ const AtoBtoC = [{"key":"transition","from":"a","se":{"kind":"->","to":"b","se":{"kind":"->","to":"c"}}}];
it('a->b->c;', t => t.deepEqual(AtoBtoC, jssm.parse('a->b->c;') ));
});
describe('sequence', async it => {
- const AtoB_CtoD = [{"from":"a","se":{"kind":"->","to":"b"}},{"from":"c","se":{"kind":"->","to":"d"}}];
+ const AtoB_CtoD = [{"key":"transition","from":"a","se":{"kind":"->","to":"b"}},{"key":"transition","from":"c","se":{"kind":"->","to":"d"}}];
it('a->b;c->d;', t => t.deepEqual(AtoB_CtoD, jssm.parse('a->b;c->d;') ));
});
@@ -63,8 +63,14 @@ describe('parse/1', async _parse_it => {
// todo: graph: {outputs: [foo]}
describe('torture', async it => {
- const augh = `a->b-> c-> d -> e;`;
+
+ const augh = `
+ a->b-> c-> d -> e
+->
+f <- g <= h <-> i <=> j ~> k <~ l <~> m <~-> n <-~> o <=~> p <~=> q <-=> r <=-> s 'A' <= 'B' t;`;
+
it('doesn\'t throw', t => t.notThrows(() => { jssm.parse(augh); }) );
+
});
});
diff --git a/src/js/tests/sm_tag.js b/src/js/tests/sm_tag.js
new file mode 100644
index 00000000..1774b94e
--- /dev/null
+++ b/src/js/tests/sm_tag.js
@@ -0,0 +1,29 @@
+
+/* eslint-disable max-len */
+
+import {describe} from 'ava-spec';
+
+const jssm = require('../../../build/jssm.es5.js'),
+ sm = jssm.sm;
+
+
+
+
+
+describe('sm``', async _parse_it => {
+
+ describe('simple sm`a->b;`', async it => {
+ it('doesn\'t throw', t => t.notThrows(() => { const _foo = sm`a -> b;`; }) );
+ });
+
+ describe('long and chain sm`a->b;c->d;e->f->g;h->i;`', async it => {
+ it('doesn\'t throw', t => t.notThrows(() => { const _foo = sm`a->b;c->d;e->f->g;h->i;`; }) );
+ });
+
+ describe('template tags`', async it => {
+ it('doesn\'t throw', t => t.notThrows(() => { const bar = 'c->d', baz = 'b->h->i;f->h', _foo = sm`a->b;${bar};e->f->g;${baz};`; }) );
+ });
+
+});
+
+// stochable
diff --git a/src/js/tests/stop light.js b/src/js/tests/stop light.js
new file mode 100644
index 00000000..5cfe0284
--- /dev/null
+++ b/src/js/tests/stop light.js
@@ -0,0 +1,159 @@
+
+/* eslint-disable max-len */
+
+import {describe} from 'ava-spec';
+
+const jssm = require('../../../build/jssm.es5.js');
+
+
+
+
+
+describe('Simple stop light', async it => {
+
+ const trs = [
+ { name: 'SwitchToWarn', action: 'Proceed', from:'Green', to:'Yellow' },
+ { name: 'SwitchToHalt', action: 'Proceed', from:'Yellow', to:'Red' },
+ { name: 'SwitchToGo', action: 'Proceed', from:'Red', to:'Green' }
+ ],
+ light = new jssm.Machine({
+ initial_state : 'Red',
+ transitions : trs
+ });
+
+ const r_states = light.states();
+ it('has the right state count', t => t.is(r_states.length, 3));
+ trs.map(t => t.to).map(c =>
+ it(`has state "${c}"`, t => t.is(r_states.includes(c), true))
+ );
+
+ const r_names = light.list_named_transitions();
+ it('has the right named transition count', t => t.is(r_names.size, 3));
+ trs.map(t => t.name)
+ .map(a =>
+ it(`has named transition "${a}"`, t => t.is(r_names.has(a), true))
+ );
+
+ it.describe('- `proceed` walkthrough', async it2 => {
+
+ it2('machine starts red', t => t.is("Red", light.state()));
+ it2('proceed is true', t => t.is(true, light.action('Proceed')));
+ it2('light is now green', t => t.is("Green", light.state()));
+ it2('proceed is true', t => t.is(true, light.action('Proceed')));
+ it2('light is now yellow', t => t.is("Yellow", light.state()));
+ it2('proceed is true', t => t.is(true, light.action('Proceed')));
+ it2('light is red again', t => t.is("Red", light.state()));
+
+ });
+
+ it.describe('- mixed - `proceed` and `transition`', async it3 => {
+
+ it3('machine starts red', t => t.is("Red", light.state()));
+ it3('proceed is true', t => t.is(true, light.action('Proceed')));
+ it3('light is now green', t => t.is("Green", light.state()));
+
+ it3('green refuses transition red', t => t.is(false, light.transition('Red')));
+ it3('green still green', t => t.is("Green", light.state()));
+ it3('green refuses transition green', t => t.is(false, light.transition('Green')));
+ it3('green still green', t => t.is("Green", light.state()));
+ it3('green accepts transition yellow', t => t.is(true, light.transition('Yellow')));
+ it3('green now yellow', t => t.is("Yellow", light.state()));
+
+ it3('proceed is true', t => t.is(true, light.action('Proceed')));
+ it3('light is red again', t => t.is("Red", light.state()));
+
+ it3('red refuses transition yellow', t => t.is(false, light.transition('Yellow')));
+ it3('red still red', t => t.is("Red", light.state()));
+ it3('red refuses transition red', t => t.is(false, light.transition('Red')));
+ it3('red still red', t => t.is("Red", light.state()));
+ it3('red accepts transition green', t => t.is(true, light.transition('Green')));
+ it3('red now green', t => t.is("Green", light.state()));
+
+ it3('proceed is true', t => t.is(true, light.action('Proceed')));
+ it3('light is yellow again', t => t.is("Yellow", light.state()));
+ it3('proceed is true', t => t.is(true, light.action('Proceed')));
+ it3('light is finally red again', t => t.is("Red", light.state()));
+
+ });
+
+});
+
+
+
+
+
+describe('Complex stop light', async it => {
+
+ const light2 = new jssm.Machine({
+
+ initial_state: 'off',
+
+ transitions:[
+
+ { name:'turn_on', action:'power_on', from:'off', to:'red'},
+
+ { action:'power_off', from:'red', to:'off' },
+ { action:'power_off', from:'yellow', to:'off' },
+ { action:'power_off', from:'green', to:'off' },
+
+ { name:'switch_warn', action:'proceed', from:'green', to:'yellow' },
+ { name:'switch_halt', action:'proceed', from:'yellow', to:'red' },
+ { name:'switch_go', action:'proceed', from:'red', to:'green' }
+
+ ]
+
+ });
+
+ const r_states = light2.states();
+ it('has the right state count', t => t.is(r_states.length, 4));
+ ['red', 'yellow', 'green', 'off'].map(c =>
+ it(`has state "${c}"`, t => t.is(r_states.includes(c), true))
+ );
+
+ const r_names = light2.list_named_transitions();
+ it('has the right named transition count', t => t.is(r_names.size, 4));
+ ['turn_on', 'switch_warn', 'switch_halt', 'switch_go'].map(a =>
+ it(`has named transition "${a}"`, t => t.is(r_names.has(a), true))
+ );
+
+ it('has the right exit actions for red', t => t.deepEqual(['power_off', 'proceed'], light2.list_exit_actions('red')));
+
+
+ it.describe('- `transition` walkthrough', async it2 => {
+
+ it2('machine starts off', t => t.is("off", light2.state()));
+ it2('off refuses green', t => t.is(false, light2.transition('green')));
+ it2('off refuses yellow', t => t.is(false, light2.transition('yellow')));
+
+ it2('off refuses proceed', t => t.is(false, light2.action('proceed')));
+
+ it2('off accepts red', t => t.is(true, light2.transition('red')));
+ it2('off is now red', t => t.is("red", light2.state()));
+ it2('red refuses yellow', t => t.is(false, light2.transition('yellow')));
+ it2('red still red', t => t.is("red", light2.state()));
+ it2('red refuses red', t => t.is(false, light2.transition('red')));
+ it2('red still red', t => t.is("red", light2.state()));
+
+ it2('red accepts green', t => t.is(true, light2.transition('green')));
+ it2('red now green', t => t.is("green", light2.state()));
+ it2('green refuses red', t => t.is(false, light2.transition('red')));
+ it2('green still green', t => t.is("green", light2.state()));
+ it2('green refuses green', t => t.is(false, light2.transition('green')));
+ it2('green still green', t => t.is("green", light2.state()));
+
+ it2('green accepts yellow', t => t.is(true, light2.transition('yellow')));
+ it2('green now yellow', t => t.is("yellow", light2.state()));
+ it2('yellow refuses green', t => t.is(false, light2.transition('green')));
+ it2('yellow still yellow', t => t.is("yellow", light2.state()));
+ it2('yellow refuses yellow', t => t.is(false, light2.transition('yellow')));
+ it2('yellow still yellow', t => t.is("yellow", light2.state()));
+
+ it2('yellow accepts red', t => t.is(true, light2.transition('red')));
+ it2('back to red', t => t.is("red", light2.state()));
+
+ it2('proceed is true', t => t.is(true, light2.action('proceed')));
+ it2('light is now green', t => t.is("green", light2.state()));
+
+ });
+
+});