diff --git a/plugins/tiddlywiki/railroad/components.js b/plugins/tiddlywiki/railroad/components.js index fed6f82c7..85d0d16e2 100644 --- a/plugins/tiddlywiki/railroad/components.js +++ b/plugins/tiddlywiki/railroad/components.js @@ -236,9 +236,11 @@ var Root = function(content) { Root.prototype = new Component(); -Root.prototype.toSvg = function() { - // Call Diagram(component1,component2,...) - return railroad.Diagram.apply(null, this.getSvgOfChildren()); +Root.prototype.toSvg = function(options) { + var args = this.getSvgOfChildren(); + args.unshift(options); + // Call Diagram(options,component1,component2,...) + return railroad.Diagram.apply(null,args); } var Sequence = function(content) { @@ -249,7 +251,7 @@ Sequence.prototype = new Component(); Sequence.prototype.toSvg = function() { // Call Sequence(component1,component2,...) - return railroad.Sequence.apply(null, this.getSvgOfChildren()); + return railroad.Sequence.apply(null,this.getSvgOfChildren()); } var Choice = function(content,normal) { @@ -266,7 +268,7 @@ Choice.prototype.toSvg = function() { // Call Choice(normal,component1,component2,...) var args = this.getSvgOfChildren(); args.unshift(this.normal); - return railroad.Choice.apply(null, args); + return railroad.Choice.apply(null,args); } /////////////////////////// Exports diff --git a/plugins/tiddlywiki/railroad/doc/example-source.tid b/plugins/tiddlywiki/railroad/doc/example-source.tid index dcca701db..8efc6b536 100644 --- a/plugins/tiddlywiki/railroad/doc/example-source.tid +++ b/plugins/tiddlywiki/railroad/doc/example-source.tid @@ -1,9 +1,9 @@ created: 20150103184022184 -modified: 20150103185522184 +modified: 20150119214125000 tags: title: $:/plugins/tiddlywiki/railroad/example-source -type: text/plain +type: text/vnd.tiddlywiki.railroad ["+"] ({ [[digit|GettingStarted]] } | "#" <'escape sequence'>) -[{("@" name-char | :"--" )}] \ No newline at end of file +[{("@" name-char | :"--" )}] diff --git a/plugins/tiddlywiki/railroad/doc/example.tid b/plugins/tiddlywiki/railroad/doc/example.tid index b64199a44..c4bcff126 100644 --- a/plugins/tiddlywiki/railroad/doc/example.tid +++ b/plugins/tiddlywiki/railroad/doc/example.tid @@ -1,16 +1,12 @@ created: 20150102165032410 -modified: 20150102172010663 +modified: 20150119215720000 tags: title: $:/plugins/tiddlywiki/railroad/example -<$railroad text={{$:/plugins/tiddlywiki/railroad/example-source}}/> +Transcluding from [[$:/plugins/tiddlywiki/railroad/example-source]]: -``` -<$railroad text=""" -["+"] -({ [[digit|GettingStarted]] } | "#" <'escape sequence'>) -[{("@" name-char | :"--" )}] -"""/> -``` +{{$:/plugins/tiddlywiki/railroad/example-source}} -<$railroad mode="debug" text={{$:/plugins/tiddlywiki/railroad/example-source}}/> \ No newline at end of file +Debug mode: + +<$railroad debug="yes" text={{$:/plugins/tiddlywiki/railroad/example-source}}/> diff --git a/plugins/tiddlywiki/railroad/doc/readme.tid b/plugins/tiddlywiki/railroad/doc/readme.tid index 2f55fd375..e7642b2d1 100644 --- a/plugins/tiddlywiki/railroad/doc/readme.tid +++ b/plugins/tiddlywiki/railroad/doc/readme.tid @@ -1,20 +1,20 @@ created: 20150102163222184 -modified: 20150102172016663 +modified: 20150119220023000 title: $:/plugins/tiddlywiki/railroad/readme -This plugin provides a `<$railroad>` widget for generating railroad syntax diagrams as SVG images. It is based on [[a library by Tab Atkins|https://github.com/tabatkins/railroad-diagrams]], and has been extended to allow components of a diagram to function as links. +This plugin provides a `<$railroad>` widget for generating railroad diagrams as SVG images. + +Alternatively, the [[diagram notation|$:/plugins/tiddlywiki/railroad/syntax]] can be stored in a dedicated tiddler with its `type` field set to `text/vnd.tiddlywiki.railroad`, and that tiddler can simply be transcluded to wherever it is needed. + +The plugin is based on [[a library by Tab Atkins|https://github.com/tabatkins/railroad-diagrams]], and has been extended to make it more flexible, including allowing components of a diagram to function as links or be transcluded from other tiddlers. The content of the `<$railroad>` widget is ignored. -|!Attribute |!Description | -|text |Text in a special syntax that defines the diagram's layout | -|arrow |If set to `no`, repeat paths do not have an arrow on them. The default is `yes` | -|mode |If set to `debug`, the diagram will display its internal tree structure. The default mode is `svg` | +|!Attribute |!Description |!Default | +|text |Text in a special notation that defines the diagram's layout |-- | +|arrow |If set to `no`, repeat paths do not have an arrow on them |`yes` | +|start |Style of the startpoint: `single`, `double`, `none` |`single` | +|end |Style of the endpoint: `single`, `double`, `none` |`single` | +|debug |If set to `yes`, the diagram displays its parse tree |`no` | -The entire `text` can be transcluded from another tiddler: - -``` -<$railroad tiddler={{diagram}}> -``` - -Alternatively, the diagram syntax allows specific parts of the `text` to be transcluded from other tiddlers. \ No newline at end of file +These options can also be specified via pragmas in the diagram notation. diff --git a/plugins/tiddlywiki/railroad/doc/syntax.tid b/plugins/tiddlywiki/railroad/doc/syntax.tid index d99e3bb80..fc0d8d87f 100644 --- a/plugins/tiddlywiki/railroad/doc/syntax.tid +++ b/plugins/tiddlywiki/railroad/doc/syntax.tid @@ -1,8 +1,8 @@ created: 20150103184022184 -modified: 20150103184022184 +modified: 20150119220342000 title: $:/plugins/tiddlywiki/railroad/syntax -The railroad widget uses a special ''diagram syntax'' to construct the components defined below. +The railroad widget uses a special notation to construct the components defined below. `x` and `y` here stand for any component. @@ -78,3 +78,22 @@ Names (as opposed to quoted strings) are available when a value starts with a le ; transclusion : <$railroad text=""" "{{" (name|string) "}}" """/> * Treats the content of another tiddler as diagram syntax and transcludes it into the current diagram + +--- + +; arrow pragma +: <$railroad text=""" "\arrow" ("yes" | "no") """/> +* Controls whether repeat paths have an arrow on them +* Can be toggled on and off in mid-diagram, if desired + +--- + +; debug pragma +: <$railroad text=""" "\debug" """/> +* Causes the diagram to display its parse tree + +--- + +; start/end pragma +: <$railroad text=""" ("\start" |: "\end") ("none" |: "single" | "double") """/> +* Controls the style of the diagram's startpoint or endpoint diff --git a/plugins/tiddlywiki/railroad/files/railroad-diagrams.js b/plugins/tiddlywiki/railroad/files/railroad-diagrams.js index 09bbac171..dfdcbe7ff 100644 --- a/plugins/tiddlywiki/railroad/files/railroad-diagrams.js +++ b/plugins/tiddlywiki/railroad/files/railroad-diagrams.js @@ -162,16 +162,17 @@ var temp = (function(options) { return this; } - function Diagram(items) { - if(!(this instanceof Diagram)) return new Diagram([].slice.call(arguments)); +/* TiddlyWiki: added twOptions parameter, passing it to Start() and End() */ + function Diagram(twOptions, items) { + if(!(this instanceof Diagram)) return new Diagram(twOptions, [].slice.call(arguments,1)); FakeSVG.call(this, 'svg', {class: Diagram.DIAGRAM_CLASS}); this.items = items.map(wrapString); - this.items.unshift(new Start); - this.items.push(new End); + this.items.unshift(new Start(twOptions.start)); + this.items.push(new End(twOptions.end)); this.width = this.items.reduce(function(sofar, el) { return sofar + el.width + (el.needsSpace?20:0)}, 0)+1; this.up = Math.max.apply(null, this.items.map(function (x) { return x.up; })); this.down = Math.max.apply(null, this.items.map(function (x) { return x.down; })); - this.formatted = false; + this.formatted = false; } subclassOf(Diagram, FakeSVG); for(var option in options) { @@ -386,29 +387,47 @@ var temp = (function(options) { return Optional(OneOrMore(item, rep, wantArrow), skip); } - function Start() { - if(!(this instanceof Start)) return new Start(); +/* TiddlyWiki: added type parameter */ + function Start(type) { + if(!(this instanceof Start)) return new Start(type); FakeSVG.call(this, 'path'); - this.width = 20; + this.type = type || 'single' + this.width = (this.type === 'double') ? 20 : 10; this.up = 10; this.down = 10; } subclassOf(Start, FakeSVG); Start.prototype.format = function(x,y) { - this.attrs.d = 'M '+x+' '+(y-10)+' v 20 m 10 -20 v 20 m -10 -10 h 20.5'; +/* TiddlyWiki: added types */ + if(this.type === 'single') { + this.attrs.d = 'M '+x+' '+(y-10)+' v 20 m 0 -10 h 10.5'; + } else if(this.type === 'double') { + this.attrs.d = 'M '+x+' '+(y-10)+' v 20 m 10 -20 v 20 m -10 -10 h 20.5'; + } else { // 'none' + this.attrs.d = 'M '+x+' '+y+' h 10.5'; + } return this; } - function End() { - if(!(this instanceof End)) return new End(); +/* TiddlyWiki: added type parameter */ + function End(type) { + if(!(this instanceof End)) return new End(type); FakeSVG.call(this, 'path'); - this.width = 20; + this.type = type || 'double'; + this.width = (this.type === 'double') ? 20 : 10; this.up = 10; this.down = 10; } subclassOf(End, FakeSVG); End.prototype.format = function(x,y) { - this.attrs.d = 'M '+x+' '+y+' h 20 m -10 -10 v 20 m 10 -20 v 20'; +/* TiddlyWiki: added types */ + if(this.type === 'single') { + this.attrs.d = 'M '+x+' '+y+' h 10 m 0 -10 v 20'; + } else if(this.type === 'double') { + this.attrs.d = 'M '+x+' '+y+' h 20 m -10 -10 v 20 m 10 -20 v 20'; + } else { // 'none' + this.attrs.d = 'M '+x+' '+y+' h 10'; + } return this; } diff --git a/plugins/tiddlywiki/railroad/parser.js b/plugins/tiddlywiki/railroad/parser.js index 18db133e7..bb55b57b8 100644 --- a/plugins/tiddlywiki/railroad/parser.js +++ b/plugins/tiddlywiki/railroad/parser.js @@ -26,6 +26,12 @@ x y z sequence "x" can also be written 'x' or """x""" +pragmas: + \arrow yes|no + \debug yes|no + \start single|double|none + \end single|double|none + \*/ (function(){ @@ -35,10 +41,10 @@ x y z sequence var components = require("$:/plugins/tiddlywiki/railroad/components.js").components; -var Parser = function(widget,source,wantArrow) { +var Parser = function(widget,source,options) { this.widget = widget; this.source = source; - this.wantArrow = wantArrow; + this.options = options; this.tokens = this.tokenise(source); this.tokenPos = 0; this.advance(); @@ -57,7 +63,9 @@ Parser.prototype.parseContent = function() { if(!component) { break; } - content.push(component); + if(!component.isPragma) { + content.push(component); + } } return content; }; @@ -69,6 +77,8 @@ Parser.prototype.parseComponent = function() { component = this.parseTerminal(); } else if(this.at("name")) { component = this.parseName(); + } else if(this.at("pragma")) { + component = this.parsePragma(); } else { switch(this.token.value) { case "[": @@ -183,6 +193,7 @@ Parser.prototype.parseNonterminal = function() { }; Parser.prototype.parseOptional = function() { + var wantArrow = this.options.arrow; // Consume the [ this.advance(); // Consume the { if there is one @@ -202,11 +213,12 @@ Parser.prototype.parseOptional = function() { } this.close("]"); // Create a component - return repeated ? new components.OptionalRepeated(content,separator,normal,this.wantArrow) + return repeated ? new components.OptionalRepeated(content,separator,normal,wantArrow) : new components.Optional(content,normal); }; Parser.prototype.parseRepeated = function() { + var wantArrow = this.options.arrow; // Consume the { this.advance(); // Parse the content @@ -219,7 +231,7 @@ Parser.prototype.parseRepeated = function() { // Consume the closing bracket this.close("}"); // Create a component - return new components.Repeated(content,separator,this.wantArrow); + return new components.Repeated(content,separator,wantArrow); }; Parser.prototype.parseSequence = function() { @@ -255,6 +267,44 @@ Parser.prototype.parseTransclusion = function() { return new components.Transclusion(content); }; +/////////////////////////// Pragmas + +Parser.prototype.parsePragma = function() { + // Create a dummy component + var component = { isPragma: true }; + // Consume the pragma + var pragma = this.token.value; + this.advance(); + // Apply the setting + if(pragma === "arrow") { + this.options.arrow = this.parseYesNo(pragma); + } else if(pragma === "debug") { + this.options.debug = true; + } else if(pragma === "start") { + this.options.start = this.parseTerminusStyle(pragma); + } else if(pragma === "end") { + this.options.end = this.parseTerminusStyle(pragma); + } else { + throw "Invalid pragma"; + } + return component; +}; + +Parser.prototype.parseYesNo = function(pragma) { + return this.parseSetting(["yes","no"],pragma) === "yes"; +} + +Parser.prototype.parseTerminusStyle = function(pragma) { + return this.parseSetting(["single","double","none"],pragma); +} + +Parser.prototype.parseSetting = function(options,pragma) { + if(this.at("name") && options.indexOf(this.token.value) !== -1) { + return this.tokenValueEaten(); + } + throw options.join(" or ") + " expected after \\" + pragma; +} + /////////////////////////// Token manipulation Parser.prototype.advance = function() { @@ -276,7 +326,7 @@ Parser.prototype.eat = function(token) { return at; }; -Parser.prototype.tokenValue = function() { +Parser.prototype.tokenValueEaten = function() { var output = this.token.value; this.advance(); return output; @@ -305,12 +355,12 @@ Parser.prototype.expectString = function(context,token) { token = token || "String"; throw token + " expected " + context; } - return this.tokenValue(); + return this.tokenValueEaten(); }; Parser.prototype.expectNameOrString = function(context) { if(this.at("name")) { - return this.tokenValue(); + return this.tokenValueEaten(); } return this.expectString(context,"Name or string"); }; @@ -353,6 +403,9 @@ Parser.prototype.tokenise = function(source) { } else if(c.match(/[a-zA-Z]/)) { // Name token = this.readName(source,pos); + } else if(c.match(/\\/)) { + // Pragma + token = this.readPragma(source,pos); } else { throw "Syntax error at " + c; } @@ -380,6 +433,18 @@ Parser.prototype.readName = function(source,pos) { } }; +Parser.prototype.readPragma = function(source,pos) { + var re = /([a-z]+)/g; + pos++; + re.lastIndex = pos; + var match = re.exec(source); + if(match && match.index === pos) { + return {type: "pragma", value: match[1], start: pos, end: pos+match[1].length}; + } else { + throw "Invalid pragma"; + } +}; + /////////////////////////// Exports exports.parser = Parser; diff --git a/plugins/tiddlywiki/railroad/wrapper.js b/plugins/tiddlywiki/railroad/wrapper.js index a6e6d5a4d..af8bd42eb 100644 --- a/plugins/tiddlywiki/railroad/wrapper.js +++ b/plugins/tiddlywiki/railroad/wrapper.js @@ -33,18 +33,21 @@ RailroadWidget.prototype.render = function(parent,nextSibling) { this.computeAttributes(); this.execute(); // Get the source text -console.log('getAttribute(text)', this.getAttribute("text", 'not found')); -$tw.utils.each(this.parseTreeNode,function(element,title,object) { -console.log(':', element, title); -}); var source = this.getAttribute("text",this.parseTreeNode.text || ""); // Create a div to contain the SVG or error message var div = this.document.createElement("div"); try { + // Initialise options from widget attributes + var options = { + arrow: this.getAttribute("arrow","yes") === "yes", + debug: this.getAttribute("debug","no") === "yes", + start: this.getAttribute("start","single"), + end: this.getAttribute("end","single") + }; // Parse the source - var parser = new Parser(this,source,this.getAttribute("arrow","yes") === "yes"); + var parser = new Parser(this,source,options); // Generate content into the div - if(this.getAttribute("mode","svg") === "debug") { + if(parser.options.debug) { this.renderDebug(parser,div); } else { this.renderSvg(parser,div); @@ -67,7 +70,7 @@ RailroadWidget.prototype.renderDebug = function(parser,div) { RailroadWidget.prototype.renderSvg = function(parser,div) { // Generate a model of the diagram - var fakeSvg = parser.root.toSvg(); + var fakeSvg = parser.root.toSvg(parser.options); // Render the model into a tree of SVG DOM nodes var svg = fakeSvg.toSVG(); // Fill in the remaining attributes of any link nodes @@ -111,7 +114,7 @@ RailroadWidget.prototype.patchLinks = function(node) { RailroadWidget.prototype.refresh = function(changedTiddlers) { var changedAttributes = this.computeAttributes(); - if(changedAttributes.text || changedAttributes.mode || changedAttributes.arrow) { + if(changedAttributes.text) { this.refreshSelf(); return true; }