mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-12-29 11:30:28 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
d19e2fa217
@ -3,7 +3,7 @@ title: $:/core/ui/SideBarLists
|
||||
<div class="tc-sidebar-lists">
|
||||
|
||||
<div class="tc-search">
|
||||
<$edit-text tiddler="$:/temp/search" type="search" tag="input"/>
|
||||
<$edit-text tiddler="$:/temp/search" type="search" tag="input" focus="true"/>
|
||||
<$reveal state="$:/temp/search" type="nomatch" text="">
|
||||
<$button tooltip={{$:/language/Buttons/AdvancedSearch/Hint}} aria-label={{$:/language/Buttons/AdvancedSearch/Caption}} class="tc-btn-invisible">
|
||||
<$action-setfield $tiddler="$:/temp/advancedsearch" text={{$:/temp/search}}/>
|
||||
|
@ -6,6 +6,6 @@ type: text/vnd.tiddlywiki
|
||||
|
||||
The TiddlyWiki community holds regular Google Hangouts, usually every Tuesday from 4pm to 6pm (UK time). They are announced in the [[TiddlyWiki Google group|https://groups.google.com/d/forum/tiddlywiki]] and on the [[TiddlyWiki Twitter account|https://twitter.com/TiddlyWiki]].
|
||||
|
||||
Past Hangouts are archived in this YouTube playlist:
|
||||
Past Hangouts are archived in this ~YouTube playlist:
|
||||
|
||||
<iframe width="560" height="315" src="http://www.youtube.com/embed/videoseries?list=PLVT_2PPd-1p34gGCQ5qpwC8QdykxVAI3u" frameborder="0" allowfullscreen></iframe>
|
||||
|
@ -7,7 +7,7 @@ type: text/vnd.tiddlywiki
|
||||
Filters are used in TiddlyWiki to choose tiddlers by specifying simple match criteria. They are used by widgets like the ListWidget and the CountWidget to perform operations on multiple tiddlers at once.
|
||||
|
||||
* [[Introduction to Filters]] is a step-by-step introduction to how filters are used
|
||||
* [[Filter Formal Grammar]] is a technical explanation of the filter syntax.
|
||||
* [[Filter Syntax]] explains the exact rules for writing filters
|
||||
|
||||
The most common filter operators are:
|
||||
|
||||
|
80
editions/tw5.com/tiddlers/filters/FilterSyntax.tid
Normal file
80
editions/tw5.com/tiddlers/filters/FilterSyntax.tid
Normal file
@ -0,0 +1,80 @@
|
||||
created: 20140210141217955
|
||||
modified: 20150102174633890
|
||||
tags: Filters
|
||||
title: Filter Syntax
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
[[Filter expressions|Filters]] follow a grammar that is presented here, using [[RailroadDiagrams]], for those who find formal syntax descriptions helpful. However, you can write your own filter expressions without needing to understand this tiddler.
|
||||
|
||||
---
|
||||
|
||||
;filter
|
||||
: <$railroad text="""
|
||||
{ [:whitespace] ("+" | :- | "-") run }
|
||||
"""/>
|
||||
|
||||
A sequence of runs is evaluated from left to right, as follows:
|
||||
|
||||
|!Sequence |!Interpretation |
|
||||
|`run1 run2` |union of the sets, i.e. the tiddlers in //either// run1 //or// run2 |
|
||||
|`run1 -run2` |difference of the sets, i.e. run1 but excluding any tiddlers in run2 |
|
||||
|`run1 +run2` |run2 takes run1 as its input |
|
||||
|
||||
The first run takes `[all[tiddlers]]` as its input, i.e. the set of all non-missing tiddlers.
|
||||
|
||||
---
|
||||
|
||||
;run
|
||||
: <$railroad text="""
|
||||
( "[" {step} "]"
|
||||
|
|
||||
'"' [{/'any character except "'/}] '"'
|
||||
|
|
||||
"'" [{/"any character except '"/}] '"'
|
||||
|
|
||||
[{/"any character except [ ] or whitespace"/}]
|
||||
)
|
||||
"""/>
|
||||
* The last three options are short for `[title[text]]`
|
||||
* A run evaluates each of its steps and returns the intersection of the results
|
||||
|
||||
---
|
||||
|
||||
;step
|
||||
: <$railroad text="""
|
||||
[:"!"] operator parameter
|
||||
"""/>
|
||||
* A step returns a set of tiddlers, in the form of a TitleList
|
||||
|
||||
---
|
||||
|
||||
;operator
|
||||
: <$railroad text="""
|
||||
( keyword [:":" fieldname] | fieldname )
|
||||
"""/>
|
||||
* Keywords are reserved names (<code>[[is|FilterOperator: is]]</code>, <code>[[has|FilterOperator: has]]</code>, <code>[[tag|FilterOperator: tag]]</code>, etc) of specific filtering functions
|
||||
* A fieldname on its own implies the keyword `field`
|
||||
* An entirely omitted operator defaults to `title`
|
||||
|
||||
---
|
||||
|
||||
;parameter
|
||||
: <$railroad text="""
|
||||
( "[" [{/"any character except ]"/}] "]"
|
||||
|
|
||||
"{" [{/"any character except }"/}] "}"
|
||||
|
|
||||
"<" [{/"any character except >"/}] ">"
|
||||
)
|
||||
"""/>
|
||||
* `[`...`]` encloses a literal parameter
|
||||
* `{`...`}` encloses a TextReference parameter
|
||||
* `<`...`>` encloses a [[variable|Variables]] parameter
|
||||
|
||||
---
|
||||
|
||||
;whitespace
|
||||
: <$railroad text="""
|
||||
{( "space" | "tab" | "linefeed" | "return" | "vertical tab" | "formfeed" )}
|
||||
"""/>
|
||||
* A match for the JavaScript regular expression `\s+`
|
@ -1,61 +0,0 @@
|
||||
created: 20140210141217955
|
||||
modified: 20140912145655663
|
||||
tags: Filters
|
||||
title: Filter Formal Grammar
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
[[Filter expressions|Filters]] follow a grammar that is presented here for those who find formal syntax descriptions helpful. However, you can write your own filter expressions without needing to understand this tiddler.
|
||||
|
||||
* [//x//] denotes an optional //x//
|
||||
* (//x//)... denotes 1 or more instances of //x//
|
||||
* Literal characters are `monospaced`
|
||||
* Top-level bullets indicate alternative possibilities
|
||||
* Second-level bullets are comments and clarifications
|
||||
|
||||
;filter
|
||||
* ( [//whitespace//] [`+`|`-`] //run// )...
|
||||
|
||||
;run
|
||||
* `[` (//operation//)... `]`
|
||||
* `"` //text// `"`
|
||||
** The text can contain anything but `"`
|
||||
* `'` //text// `'`
|
||||
** The text can contain anything but `'`
|
||||
* //text//
|
||||
** The text can contain anything but whitespace and `[` and `]`
|
||||
** These last three alternatives are short for `[title[text]]`
|
||||
|
||||
;operation
|
||||
* [`!`] //operator// //operand//
|
||||
|
||||
;operator
|
||||
* [//keyword//] [`:` //fieldname//]
|
||||
** Keywords (`is`, `has`, `tag`, etc) are reserved names that identify filter functions
|
||||
** A fieldname on its own implies the keyword `field`
|
||||
** An entirely omitted operator defaults to `title`
|
||||
|
||||
;operand
|
||||
* `[` //text// `]`
|
||||
** literal -- the text can contain anything but `]`
|
||||
* `{` //text// `}`
|
||||
** text reference -- the text can contain anything but `}`
|
||||
* `<` //text// `>`
|
||||
** variable -- the text can contain anything but `>`
|
||||
|
||||
;whitespace
|
||||
* One or more spaces, tabs or linefeeds, i.e. a match for the JavaScript regular expression `\s+`
|
||||
|
||||
!Evaluation
|
||||
|
||||
Each operation returns a set of tiddlers, in the form of a TitleList.
|
||||
|
||||
A run evaluates each of the operations it contains, and returns the intersection of the resulting sets.
|
||||
|
||||
A sequence of runs is evaluated from left to right, as follows:
|
||||
|
||||
|!Sequence |!Interpretation |
|
||||
|run1 run2 |union of the sets, i.e. the tiddlers in //either// run1 //or// run2 |
|
||||
|run1 -run2 |difference of the sets, i.e. run1 but excluding any tiddlers in run2 |
|
||||
|run1 +run2 |run2 takes run1 as its input |
|
||||
|
||||
The first run of a sequence takes `[all[tiddlers]]` as its input, i.e. the set of all non-missing tiddlers.
|
@ -22,6 +22,7 @@ The content of the `<$edit-text>` widget is ignored.
|
||||
|class |A CSS class to be assigned to the generated HTML editing element |
|
||||
|placeholder |Placeholder text to be displayed when the edit field is empty |
|
||||
|focusPopup |Title of a state tiddler for a popup that is displayed when the editing element has focus |
|
||||
|focus |Set to "true" to automatically focus the editor after creation |
|
||||
|tag |Overrides the generated HTML editing element tag. Use `textarea` for a multi-line editor |
|
||||
|type |Overrides the generated HTML editing element `type` attribute |
|
||||
|size |The size of the input field (in characters) |
|
||||
|
@ -4,7 +4,8 @@
|
||||
"tiddlywiki/googleanalytics",
|
||||
"tiddlywiki/nodewebkitsaver",
|
||||
"tiddlywiki/github-fork-ribbon",
|
||||
"tiddlywiki/browser-sniff"
|
||||
"tiddlywiki/browser-sniff",
|
||||
"tiddlywiki/railroad"
|
||||
],
|
||||
"themes": [
|
||||
"tiddlywiki/vanilla",
|
||||
|
@ -92,16 +92,17 @@ Given a tiddler title and an array of existing filenames, generate a new legal f
|
||||
*/
|
||||
FileSystemAdaptor.prototype.generateTiddlerFilename = function(title,extension,existingFilenames) {
|
||||
// First remove any of the characters that are illegal in Windows filenames
|
||||
var baseFilename = title.replace(/<|>|\:|\"|\/|\\|\||\?|\*|\^|\s/g,"_");
|
||||
var baseFilename = transliterate(title.replace(/<|>|\:|\"|\/|\\|\||\?|\*|\^|\s/g,"_"));
|
||||
// Truncate the filename if it is too long
|
||||
if(baseFilename.length > 200) {
|
||||
baseFilename = baseFilename.substr(0,200);
|
||||
}
|
||||
// Start with the base filename plus the extension
|
||||
var filename = transliterate(baseFilename) + extension,
|
||||
var filename = baseFilename + extension,
|
||||
count = 1;
|
||||
// Add a discriminator if we're clashing with an existing filename
|
||||
while(existingFilenames.indexOf(filename) !== -1) {
|
||||
// Add a discriminator if we're clashing with an existing filename while
|
||||
// handling case-insensitive filesystems (NTFS, FAT/FAT32, etc.)
|
||||
while(existingFilenames.some(function(value) {return value.toLocaleLowerCase() === filename.toLocaleLowerCase();})) {
|
||||
filename = baseFilename + " " + (count++) + extension;
|
||||
}
|
||||
return filename;
|
||||
|
264
plugins/tiddlywiki/railroad/components.js
Normal file
264
plugins/tiddlywiki/railroad/components.js
Normal file
@ -0,0 +1,264 @@
|
||||
/*\
|
||||
title: $:/plugins/tiddlywiki/railroad/components.js
|
||||
type: application/javascript
|
||||
module-type: library
|
||||
|
||||
Components of a railroad diagram.
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var railroad = require("$:/plugins/tiddlywiki/railroad/railroad-diagrams.js");
|
||||
|
||||
/////////////////////////// Base component
|
||||
|
||||
var Component = function() {
|
||||
this.type = "Component";
|
||||
};
|
||||
|
||||
// Set up a leaf component
|
||||
Component.prototype.initialiseLeaf = function(type,text) {
|
||||
this.type = type;
|
||||
this.text = text;
|
||||
};
|
||||
|
||||
// Set up a component with a single child
|
||||
Component.prototype.initialiseWithChild = function(type,content) {
|
||||
this.type = type;
|
||||
this.child = toSingleChild(content);
|
||||
};
|
||||
|
||||
// Set up a component with an array of children
|
||||
Component.prototype.initialiseWithChildren = function(type,content) {
|
||||
this.type = type;
|
||||
// Force the content to be an array
|
||||
this.children = $tw.utils.isArray(content) ? content : [content];
|
||||
}
|
||||
|
||||
// Return an array of the SVG strings of an array of children
|
||||
Component.prototype.getSvgOfChildren = function() {
|
||||
return this.children.map(function(child) {
|
||||
return child.toSvg();
|
||||
});
|
||||
}
|
||||
|
||||
Component.prototype.toSvg = function() {
|
||||
return "";
|
||||
}
|
||||
|
||||
Component.prototype.debug = function(output,indent) {
|
||||
output.push(indent);
|
||||
output.push(this.type);
|
||||
// Add the text of a leaf component
|
||||
if(this.text && this.text !== "") {
|
||||
output.push(": ");
|
||||
output.push(this.text);
|
||||
}
|
||||
// Flag the normal route
|
||||
if(this.normal !== undefined) {
|
||||
if(this.normal === true) {
|
||||
output.push(" (normal)");
|
||||
} else if(this.normal !== false) {
|
||||
output.push(" (normal: ");
|
||||
output.push(this.normal);
|
||||
output.push(")");
|
||||
}
|
||||
}
|
||||
output.push("\n");
|
||||
var contentIndent = indent + " ";
|
||||
// Add the one child
|
||||
if(this.child) {
|
||||
this.child.debug(output,contentIndent);
|
||||
}
|
||||
// Add the array of children
|
||||
if(this.children) {
|
||||
this.debugArray(this.children,output,contentIndent);
|
||||
}
|
||||
// Add the separator if there is one
|
||||
if(this.separator) {
|
||||
output.push(indent);
|
||||
output.push("(separator)\n");
|
||||
this.separator.debug(output,contentIndent);
|
||||
}
|
||||
};
|
||||
|
||||
Component.prototype.debugArray = function(array,output,indent) {
|
||||
for(var i=0; i<array.length; i++) {
|
||||
var item = array[i];
|
||||
// Choice content is a special case: an array of arrays
|
||||
if(item.isChoiceBranch) {
|
||||
output.push(indent);
|
||||
output.push("(");
|
||||
output.push(i);
|
||||
output.push(")\n");
|
||||
item.debug(output," " +indent);
|
||||
} else {
|
||||
item.debug(output,indent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var toSingleChild = function(content) {
|
||||
if($tw.utils.isArray(content)) {
|
||||
// Reduce an array of one child to just the child
|
||||
if(content.length === 1) {
|
||||
return content[0];
|
||||
} else {
|
||||
// Never allow an empty sequence
|
||||
if(content.length === 0) {
|
||||
content.push(new Dummy());
|
||||
}
|
||||
// Wrap multiple children into a single sequence component
|
||||
return new Sequence(content);
|
||||
}
|
||||
} else {
|
||||
// Already single
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////// Leaf components
|
||||
|
||||
var Comment = function(text) {
|
||||
this.initialiseLeaf("Comment",text);
|
||||
};
|
||||
|
||||
Comment.prototype = new Component();
|
||||
|
||||
Comment.prototype.toSvg = function() {
|
||||
return railroad.Comment(this.text);
|
||||
}
|
||||
|
||||
var Dummy = function() {
|
||||
this.initialiseLeaf("Dummy");
|
||||
};
|
||||
|
||||
Dummy.prototype = new Component();
|
||||
|
||||
Dummy.prototype.toSvg = function() {
|
||||
return railroad.Skip();
|
||||
}
|
||||
|
||||
var Nonterminal = function(text) {
|
||||
this.initialiseLeaf("Nonterminal",text);
|
||||
};
|
||||
|
||||
Nonterminal.prototype = new Component();
|
||||
|
||||
Nonterminal.prototype.toSvg = function() {
|
||||
return railroad.NonTerminal(this.text);
|
||||
}
|
||||
|
||||
var Terminal = function(text) {
|
||||
this.initialiseLeaf("Terminal",text);
|
||||
};
|
||||
|
||||
Terminal.prototype = new Component();
|
||||
|
||||
Terminal.prototype.toSvg = function() {
|
||||
return railroad.Terminal(this.text);
|
||||
}
|
||||
|
||||
/////////////////////////// Components with one child
|
||||
|
||||
var Optional = function(content,normal) {
|
||||
this.initialiseWithChild("Optional",content);
|
||||
this.normal = normal;
|
||||
};
|
||||
|
||||
Optional.prototype = new Component();
|
||||
|
||||
Optional.prototype.toSvg = function() {
|
||||
// Call Optional(component,"skip")
|
||||
return railroad.Optional(this.child.toSvg(), this.normal ? undefined : "skip");
|
||||
}
|
||||
|
||||
var OptionalRepeated = function(content,separator,normal) {
|
||||
this.initialiseWithChild("OptionalRepeated",content);
|
||||
this.separator = toSingleChild(separator);
|
||||
this.normal = normal;
|
||||
};
|
||||
|
||||
OptionalRepeated.prototype = new Component();
|
||||
|
||||
OptionalRepeated.prototype.toSvg = function() {
|
||||
// Call ZeroOrMore(component,separator,"skip")
|
||||
var separatorSvg = this.separator ? this.separator.toSvg() : null;
|
||||
var skip = this.normal ? undefined : "skip";
|
||||
return railroad.ZeroOrMore(this.child.toSvg(),separatorSvg,skip);
|
||||
}
|
||||
|
||||
var Repeated = function(content,separator) {
|
||||
this.initialiseWithChild("Repeated",content);
|
||||
this.separator = toSingleChild(separator);
|
||||
};
|
||||
|
||||
Repeated.prototype = new Component();
|
||||
|
||||
Repeated.prototype.toSvg = function() {
|
||||
// Call OneOrMore(component,separator)
|
||||
var separatorSvg = this.separator ? this.separator.toSvg() : null;
|
||||
return railroad.OneOrMore(this.child.toSvg(),separatorSvg);
|
||||
}
|
||||
|
||||
/////////////////////////// Components with an array of children
|
||||
|
||||
var Root = function(content) {
|
||||
this.initialiseWithChildren("Root",content);
|
||||
};
|
||||
|
||||
Root.prototype = new Component();
|
||||
|
||||
Root.prototype.toSvg = function() {
|
||||
// Call Diagram(component1,component2,...)
|
||||
return railroad.Diagram.apply(null, this.getSvgOfChildren());
|
||||
}
|
||||
|
||||
var Sequence = function(content) {
|
||||
this.initialiseWithChildren("Sequence",content);
|
||||
};
|
||||
|
||||
Sequence.prototype = new Component();
|
||||
|
||||
Sequence.prototype.toSvg = function() {
|
||||
// Call Sequence(component1,component2,...)
|
||||
return railroad.Sequence.apply(null, this.getSvgOfChildren());
|
||||
}
|
||||
|
||||
var Choice = function(content,normal) {
|
||||
this.initialiseWithChildren("Choice",content.map(toSingleChild));
|
||||
for(var i=0; i<this.children.length; i++) {
|
||||
this.children[i].isChoiceBranch = true;
|
||||
}
|
||||
this.normal = normal;
|
||||
};
|
||||
|
||||
Choice.prototype = new Component();
|
||||
|
||||
Choice.prototype.toSvg = function() {
|
||||
// Call Choice(normal,component1,component2,...)
|
||||
var args = this.getSvgOfChildren();
|
||||
args.unshift(this.normal);
|
||||
return railroad.Choice.apply(null, args);
|
||||
}
|
||||
|
||||
/////////////////////////// Exports
|
||||
|
||||
exports.components = {
|
||||
Choice: Choice,
|
||||
Comment: Comment,
|
||||
Dummy: Dummy,
|
||||
Nonterminal: Nonterminal,
|
||||
Optional: Optional,
|
||||
OptionalRepeated: OptionalRepeated,
|
||||
Repeated: Repeated,
|
||||
Root: Root,
|
||||
Sequence: Sequence,
|
||||
Terminal: Terminal
|
||||
};
|
||||
|
||||
})();
|
9
plugins/tiddlywiki/railroad/doc/example-source.tid
Normal file
9
plugins/tiddlywiki/railroad/doc/example-source.tid
Normal file
@ -0,0 +1,9 @@
|
||||
created: 20150103184022184
|
||||
modified: 20150103185522184
|
||||
tags:
|
||||
title: $:/plugins/tiddlywiki/railroad/example-source
|
||||
type: text/plain
|
||||
|
||||
["+"]
|
||||
({digit} | "#" <'escape sequence'>)
|
||||
[{("@" name-char | :"--" )}]
|
16
plugins/tiddlywiki/railroad/doc/example.tid
Normal file
16
plugins/tiddlywiki/railroad/doc/example.tid
Normal file
@ -0,0 +1,16 @@
|
||||
created: 20150102165032410
|
||||
modified: 20150102172010663
|
||||
tags:
|
||||
title: $:/plugins/tiddlywiki/railroad/example
|
||||
|
||||
<$railroad text={{$:/plugins/tiddlywiki/railroad/example-source}}/>
|
||||
|
||||
```
|
||||
<$railroad text="""
|
||||
["+"]
|
||||
({digit} | "#" <'escape sequence'>)
|
||||
[{("@" name-char | :"--" )}]
|
||||
"""/>
|
||||
```
|
||||
|
||||
<$railroad mode="debug" text={{$:/plugins/tiddlywiki/railroad/example-source}}/>
|
17
plugins/tiddlywiki/railroad/doc/readme.tid
Normal file
17
plugins/tiddlywiki/railroad/doc/readme.tid
Normal file
@ -0,0 +1,17 @@
|
||||
created: 20150102163222184
|
||||
modified: 20150102172016663
|
||||
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]].
|
||||
|
||||
The content of the `<$railroad>` widget is ignored.
|
||||
|
||||
|!Attribute |!Description |
|
||||
|text |Text in a special syntax that defines the diagram's layout |
|
||||
|mode |If set to `debug`, the diagram will display its internal tree structure. The default mode is `svg` |
|
||||
|
||||
The `text` can be transcluded from another tiddler:
|
||||
|
||||
```
|
||||
<$railroad tiddler={{diagram}}>
|
||||
```
|
5
plugins/tiddlywiki/railroad/doc/syntax-string.tid
Normal file
5
plugins/tiddlywiki/railroad/doc/syntax-string.tid
Normal file
@ -0,0 +1,5 @@
|
||||
created: 20150103184022184
|
||||
modified: 20150103184022184
|
||||
title: $:/plugins/tiddlywiki/railroad/syntax-string
|
||||
|
||||
('"' text '"' | "'" text "'" | '"""' text '"""')
|
67
plugins/tiddlywiki/railroad/doc/syntax.tid
Normal file
67
plugins/tiddlywiki/railroad/doc/syntax.tid
Normal file
@ -0,0 +1,67 @@
|
||||
created: 20150103184022184
|
||||
modified: 20150103184022184
|
||||
title: $:/plugins/tiddlywiki/railroad/syntax
|
||||
|
||||
The railroad widget constructs a diagram from the components defined below.
|
||||
|
||||
`x` and `y` here stand for any component.
|
||||
|
||||
---
|
||||
|
||||
; sequence
|
||||
: <$railroad text=""" ["<-"] {x} ["->"] """/>
|
||||
* A sequence of components
|
||||
* The `<-` and `->` delimiters allow you to force a single component to be treated as a sequence. This is occasionally useful for spacing a diagram out
|
||||
|
||||
---
|
||||
|
||||
; optional
|
||||
: <$railroad text=""" "[" [":"] x "]" """/>
|
||||
* A component that can be omitted
|
||||
* The colon makes `x` appear straight ahead
|
||||
|
||||
---
|
||||
|
||||
; repeated
|
||||
: <$railroad text=""" "{" x [:"+" y] "}" """/>
|
||||
* A list of one or more `x`
|
||||
* The `+` suffix adds `y` as a separator between each `x` and the next
|
||||
|
||||
---
|
||||
|
||||
; optional repeated
|
||||
: <$railroad text=""" "[{" [":"] x [:"+" y] "}]" """/>
|
||||
* An optional list of `x`, i.e. a list of zero or more `x`
|
||||
|
||||
---
|
||||
|
||||
; choice
|
||||
: <$railroad text=""" "(" {[:":"] x +"|"} ")" """/>
|
||||
* A set of alternatives
|
||||
* The colon indicates which branch appears straight ahead. By default, it's the first branch
|
||||
|
||||
---
|
||||
|
||||
; string / terminal
|
||||
: <$railroad text={{$:/plugins/tiddlywiki/railroad/syntax-string}}/>
|
||||
* A literal or terminal component
|
||||
* This follows the normal ~TiddlyWiki rules for quoted strings
|
||||
|
||||
---
|
||||
|
||||
; nonterminal
|
||||
: <$railroad text=""" (name | "<" string ">") """/>
|
||||
* A nonterminal component, i.e. the name of another diagram
|
||||
* The simple `name` option is available when the text starts with a letter and contains only letters, digits, underscores, dots and hyphens
|
||||
|
||||
---
|
||||
|
||||
; comment
|
||||
: <$railroad text=""" "/" string "/" """/>
|
||||
* A comment
|
||||
|
||||
---
|
||||
|
||||
; dummy
|
||||
: <$railroad text=""" "-" """/>
|
||||
* The absence of a component
|
23
plugins/tiddlywiki/railroad/files/railroad-diagrams.css
Normal file
23
plugins/tiddlywiki/railroad/files/railroad-diagrams.css
Normal file
@ -0,0 +1,23 @@
|
||||
svg.railroad-diagram {
|
||||
background-color: hsl(30,20%,95%);
|
||||
}
|
||||
svg.railroad-diagram path {
|
||||
stroke-width: 3;
|
||||
stroke: black;
|
||||
fill: rgba(0,0,0,0);
|
||||
}
|
||||
svg.railroad-diagram text {
|
||||
font: bold 14px monospace;
|
||||
text-anchor: middle;
|
||||
}
|
||||
svg.railroad-diagram text.label {
|
||||
text-anchor: start;
|
||||
}
|
||||
svg.railroad-diagram text.comment {
|
||||
font: italic 12px monospace;
|
||||
}
|
||||
svg.railroad-diagram rect {
|
||||
stroke-width: 3;
|
||||
stroke: black;
|
||||
fill: hsl(120,100%,90%);
|
||||
}
|
508
plugins/tiddlywiki/railroad/files/railroad-diagrams.js
Normal file
508
plugins/tiddlywiki/railroad/files/railroad-diagrams.js
Normal file
@ -0,0 +1,508 @@
|
||||
/* TiddlyWiki: modifications to the original library are commented like this */
|
||||
|
||||
/*
|
||||
Railroad Diagrams
|
||||
by Tab Atkins Jr. (and others)
|
||||
http://xanthir.com
|
||||
http://twitter.com/tabatkins
|
||||
http://github.com/tabatkins/railroad-diagrams
|
||||
|
||||
This document and all associated files in the github project are licensed under CC0: http://creativecommons.org/publicdomain/zero/1.0/
|
||||
This means you can reuse, remix, or otherwise appropriate this project for your own use WITHOUT RESTRICTION.
|
||||
(The actual legal meaning can be found at the above link.)
|
||||
Don't ask me for permission to use any part of this project, JUST USE IT.
|
||||
I would appreciate attribution, but that is not required by the license.
|
||||
*/
|
||||
|
||||
/*
|
||||
This file uses a module pattern to avoid leaking names into the global scope.
|
||||
The only accidental leakage is the name "temp".
|
||||
The exported names can be found at the bottom of this file;
|
||||
simply change the names in the array of strings to change what they are called in your application.
|
||||
|
||||
As well, several configuration constants are passed into the module function at the bottom of this file.
|
||||
At runtime, these constants can be found on the Diagram class.
|
||||
*/
|
||||
|
||||
var temp = (function(options) {
|
||||
function subclassOf(baseClass, superClass) {
|
||||
baseClass.prototype = Object.create(superClass.prototype);
|
||||
baseClass.prototype.$super = superClass.prototype;
|
||||
}
|
||||
|
||||
function unnull(/* children */) {
|
||||
return [].slice.call(arguments).reduce(function(sofar, x) { return sofar !== undefined ? sofar : x; });
|
||||
}
|
||||
|
||||
function determineGaps(outer, inner) {
|
||||
var diff = outer - inner;
|
||||
switch(Diagram.INTERNAL_ALIGNMENT) {
|
||||
case 'left': return [0, diff]; break;
|
||||
case 'right': return [diff, 0]; break;
|
||||
case 'center':
|
||||
default: return [diff/2, diff/2]; break;
|
||||
}
|
||||
}
|
||||
|
||||
function wrapString(value) {
|
||||
return ((typeof value) == 'string') ? new Terminal(value) : value;
|
||||
}
|
||||
|
||||
|
||||
function SVG(name, attrs, text) {
|
||||
attrs = attrs || {};
|
||||
text = text || '';
|
||||
var el = document.createElementNS("http://www.w3.org/2000/svg",name);
|
||||
for(var attr in attrs) {
|
||||
el.setAttribute(attr, attrs[attr]);
|
||||
}
|
||||
el.textContent = text;
|
||||
return el;
|
||||
}
|
||||
|
||||
function FakeSVG(tagName, attrs, text){
|
||||
if(!(this instanceof FakeSVG)) return new FakeSVG(tagName, attrs, text);
|
||||
if(text) this.children = text;
|
||||
else this.children = [];
|
||||
this.tagName = tagName;
|
||||
this.attrs = unnull(attrs, {});
|
||||
return this;
|
||||
};
|
||||
FakeSVG.prototype.format = function(x, y, width) {
|
||||
// Virtual
|
||||
};
|
||||
FakeSVG.prototype.addTo = function(parent) {
|
||||
if(parent instanceof FakeSVG) {
|
||||
parent.children.push(this);
|
||||
return this;
|
||||
} else {
|
||||
var svg = this.toSVG();
|
||||
parent.appendChild(svg);
|
||||
return svg;
|
||||
}
|
||||
};
|
||||
FakeSVG.prototype.toSVG = function() {
|
||||
var el = SVG(this.tagName, this.attrs);
|
||||
if(typeof this.children == 'string') {
|
||||
el.textContent = this.children;
|
||||
} else {
|
||||
this.children.forEach(function(e) {
|
||||
el.appendChild(e.toSVG());
|
||||
});
|
||||
}
|
||||
return el;
|
||||
};
|
||||
FakeSVG.prototype.toString = function() {
|
||||
var str = '<' + this.tagName;
|
||||
var group = this.tagName == "g" || this.tagName == "svg";
|
||||
for(var attr in this.attrs) {
|
||||
str += ' ' + attr + '="' + (this.attrs[attr]+'').replace(/&/g, '&').replace(/"/g, '"') + '"';
|
||||
}
|
||||
str += '>';
|
||||
if(group) str += "\n";
|
||||
if(typeof this.children == 'string') {
|
||||
str += this.children.replace(/&/g, '&').replace(/</g, '<');
|
||||
} else {
|
||||
this.children.forEach(function(e) {
|
||||
str += e;
|
||||
});
|
||||
}
|
||||
str += '</' + this.tagName + '>\n';
|
||||
return str;
|
||||
}
|
||||
|
||||
function Path(x,y) {
|
||||
if(!(this instanceof Path)) return new Path(x,y);
|
||||
FakeSVG.call(this, 'path');
|
||||
this.attrs.d = "M"+x+' '+y;
|
||||
}
|
||||
subclassOf(Path, FakeSVG);
|
||||
Path.prototype.m = function(x,y) {
|
||||
this.attrs.d += 'm'+x+' '+y;
|
||||
return this;
|
||||
}
|
||||
Path.prototype.h = function(val) {
|
||||
this.attrs.d += 'h'+val;
|
||||
return this;
|
||||
}
|
||||
Path.prototype.right = Path.prototype.h;
|
||||
Path.prototype.left = function(val) { return this.h(-val); }
|
||||
Path.prototype.v = function(val) {
|
||||
this.attrs.d += 'v'+val;
|
||||
return this;
|
||||
}
|
||||
Path.prototype.down = Path.prototype.v;
|
||||
Path.prototype.up = function(val) { return this.v(-val); }
|
||||
Path.prototype.arc = function(sweep){
|
||||
var x = Diagram.ARC_RADIUS;
|
||||
var y = Diagram.ARC_RADIUS;
|
||||
if(sweep[0] == 'e' || sweep[1] == 'w') {
|
||||
x *= -1;
|
||||
}
|
||||
if(sweep[0] == 's' || sweep[1] == 'n') {
|
||||
y *= -1;
|
||||
}
|
||||
if(sweep == 'ne' || sweep == 'es' || sweep == 'sw' || sweep == 'wn') {
|
||||
var cw = 1;
|
||||
} else {
|
||||
var cw = 0;
|
||||
}
|
||||
this.attrs.d += "a"+Diagram.ARC_RADIUS+" "+Diagram.ARC_RADIUS+" 0 0 "+cw+' '+x+' '+y;
|
||||
return this;
|
||||
}
|
||||
Path.prototype.format = function() {
|
||||
// All paths in this library start/end horizontally.
|
||||
// The extra .5 ensures a minor overlap, so there's no seams in bad rasterizers.
|
||||
this.attrs.d += 'h.5';
|
||||
return this;
|
||||
}
|
||||
|
||||
function Diagram(items) {
|
||||
if(!(this instanceof Diagram)) return new Diagram([].slice.call(arguments));
|
||||
FakeSVG.call(this, 'svg', {class: Diagram.DIAGRAM_CLASS});
|
||||
this.items = items.map(wrapString);
|
||||
this.items.unshift(new Start);
|
||||
this.items.push(new 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;
|
||||
}
|
||||
subclassOf(Diagram, FakeSVG);
|
||||
for(var option in options) {
|
||||
Diagram[option] = options[option];
|
||||
}
|
||||
Diagram.prototype.format = function(paddingt, paddingr, paddingb, paddingl) {
|
||||
paddingt = unnull(paddingt, 20);
|
||||
paddingr = unnull(paddingr, paddingt, 20);
|
||||
paddingb = unnull(paddingb, paddingt, 20);
|
||||
paddingl = unnull(paddingl, paddingr, 20);
|
||||
var x = paddingl;
|
||||
var y = paddingt;
|
||||
y += this.up;
|
||||
var g = FakeSVG('g', Diagram.STROKE_ODD_PIXEL_LENGTH ? {transform:'translate(.5 .5)'} : {});
|
||||
for(var i = 0; i < this.items.length; i++) {
|
||||
var item = this.items[i];
|
||||
if(item.needsSpace) {
|
||||
Path(x,y).h(10).addTo(g);
|
||||
x += 10;
|
||||
}
|
||||
item.format(x, y, item.width).addTo(g);
|
||||
x += item.width;
|
||||
if(item.needsSpace) {
|
||||
Path(x,y).h(10).addTo(g);
|
||||
x += 10;
|
||||
}
|
||||
}
|
||||
this.attrs.width = this.width + paddingl + paddingr;
|
||||
this.attrs.height = this.up + this.down + paddingt + paddingb;
|
||||
this.attrs.viewBox = "0 0 " + this.attrs.width + " " + this.attrs.height;
|
||||
g.addTo(this);
|
||||
this.formatted = true;
|
||||
return this;
|
||||
}
|
||||
Diagram.prototype.addTo = function(parent) {
|
||||
var scriptTag = document.getElementsByTagName('script');
|
||||
scriptTag = scriptTag[scriptTag.length - 1];
|
||||
var parentTag = scriptTag.parentNode;
|
||||
parent = parent || parentTag;
|
||||
return this.$super.addTo.call(this, parent);
|
||||
}
|
||||
Diagram.prototype.toSVG = function() {
|
||||
if (!this.formatted) {
|
||||
this.format();
|
||||
}
|
||||
return this.$super.toSVG.call(this);
|
||||
}
|
||||
Diagram.prototype.toString = function() {
|
||||
if (!this.formatted) {
|
||||
this.format();
|
||||
}
|
||||
return this.$super.toString.call(this);
|
||||
}
|
||||
|
||||
function Sequence(items) {
|
||||
if(!(this instanceof Sequence)) return new Sequence([].slice.call(arguments));
|
||||
FakeSVG.call(this, 'g');
|
||||
this.items = items.map(wrapString);
|
||||
this.width = this.items.reduce(function(sofar, el) { return sofar + el.width + (el.needsSpace?20:0)}, 0);
|
||||
this.up = this.items.reduce(function(sofar,el) { return Math.max(sofar, el.up)}, 0);
|
||||
this.down = this.items.reduce(function(sofar,el) { return Math.max(sofar, el.down)}, 0);
|
||||
}
|
||||
subclassOf(Sequence, FakeSVG);
|
||||
Sequence.prototype.format = function(x,y,width) {
|
||||
// Hook up the two sides if this is narrower than its stated width.
|
||||
var gaps = determineGaps(width, this.width);
|
||||
Path(x,y).h(gaps[0]).addTo(this);
|
||||
Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
|
||||
x += gaps[0];
|
||||
|
||||
for(var i = 0; i < this.items.length; i++) {
|
||||
var item = this.items[i];
|
||||
if(item.needsSpace) {
|
||||
Path(x,y).h(10).addTo(this);
|
||||
x += 10;
|
||||
}
|
||||
item.format(x, y, item.width).addTo(this);
|
||||
x += item.width;
|
||||
if(item.needsSpace) {
|
||||
Path(x,y).h(10).addTo(this);
|
||||
x += 10;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
function Choice(normal, items) {
|
||||
if(!(this instanceof Choice)) return new Choice(normal, [].slice.call(arguments,1));
|
||||
FakeSVG.call(this, 'g');
|
||||
if( typeof normal !== "number" || normal !== Math.floor(normal) ) {
|
||||
throw new TypeError("The first argument of Choice() must be an integer.");
|
||||
} else if(normal < 0 || normal >= items.length) {
|
||||
throw new RangeError("The first argument of Choice() must be an index for one of the items.");
|
||||
} else {
|
||||
this.normal = normal;
|
||||
}
|
||||
this.items = items.map(wrapString);
|
||||
this.width = this.items.reduce(function(sofar, el){return Math.max(sofar, el.width)},0) + Diagram.ARC_RADIUS*4;
|
||||
this.up = this.down = 0;
|
||||
for(var i = 0; i < this.items.length; i++) {
|
||||
var item = this.items[i];
|
||||
if(i < normal) { this.up += Math.max(Diagram.ARC_RADIUS,item.up + item.down + Diagram.VERTICAL_SEPARATION); }
|
||||
if(i == normal) { this.up += Math.max(Diagram.ARC_RADIUS, item.up); this.down += Math.max(Diagram.ARC_RADIUS, item.down); }
|
||||
if(i > normal) { this.down += Math.max(Diagram.ARC_RADIUS,Diagram.VERTICAL_SEPARATION + item.up + item.down); }
|
||||
}
|
||||
}
|
||||
subclassOf(Choice, FakeSVG);
|
||||
Choice.prototype.format = function(x,y,width) {
|
||||
// Hook up the two sides if this is narrower than its stated width.
|
||||
var gaps = determineGaps(width, this.width);
|
||||
Path(x,y).h(gaps[0]).addTo(this);
|
||||
Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
|
||||
x += gaps[0];
|
||||
|
||||
var last = this.items.length -1;
|
||||
var innerWidth = this.width - Diagram.ARC_RADIUS*4;
|
||||
|
||||
// Do the elements that curve above
|
||||
for(var i = this.normal - 1; i >= 0; i--) {
|
||||
var item = this.items[i];
|
||||
if( i == this.normal - 1 ) {
|
||||
var distanceFromY = Math.max(Diagram.ARC_RADIUS*2, this.items[i+1].up + Diagram.VERTICAL_SEPARATION + item.down);
|
||||
}
|
||||
Path(x,y).arc('se').up(distanceFromY - Diagram.ARC_RADIUS*2).arc('wn').addTo(this);
|
||||
item.format(x+Diagram.ARC_RADIUS*2,y - distanceFromY,innerWidth).addTo(this);
|
||||
Path(x+Diagram.ARC_RADIUS*2+innerWidth, y-distanceFromY).arc('ne').down(distanceFromY - Diagram.ARC_RADIUS*2).arc('ws').addTo(this);
|
||||
distanceFromY += Math.max(Diagram.ARC_RADIUS, item.up + Diagram.VERTICAL_SEPARATION + (i == 0 ? 0 : this.items[i-1].down));
|
||||
}
|
||||
|
||||
// Do the straight-line path.
|
||||
Path(x,y).right(Diagram.ARC_RADIUS*2).addTo(this);
|
||||
this.items[this.normal].format(x+Diagram.ARC_RADIUS*2, y, innerWidth).addTo(this);
|
||||
Path(x+Diagram.ARC_RADIUS*2+innerWidth, y).right(Diagram.ARC_RADIUS*2).addTo(this);
|
||||
|
||||
// Do the elements that curve below
|
||||
for(var i = this.normal+1; i <= last; i++) {
|
||||
var item = this.items[i];
|
||||
if( i == this.normal + 1 ) {
|
||||
var distanceFromY = Math.max(Diagram.ARC_RADIUS*2, this.items[i-1].down + Diagram.VERTICAL_SEPARATION + item.up);
|
||||
}
|
||||
Path(x,y).arc('ne').down(distanceFromY - Diagram.ARC_RADIUS*2).arc('ws').addTo(this);
|
||||
item.format(x+Diagram.ARC_RADIUS*2, y+distanceFromY, innerWidth).addTo(this);
|
||||
Path(x+Diagram.ARC_RADIUS*2+innerWidth, y+distanceFromY).arc('se').up(distanceFromY - Diagram.ARC_RADIUS*2).arc('wn').addTo(this);
|
||||
distanceFromY += Math.max(Diagram.ARC_RADIUS, item.down + Diagram.VERTICAL_SEPARATION + (i == last ? 0 : this.items[i+1].up));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
function Optional(item, skip) {
|
||||
if( skip === undefined )
|
||||
return Choice(1, Skip(), item);
|
||||
else if ( skip === "skip" )
|
||||
return Choice(0, Skip(), item);
|
||||
else
|
||||
throw "Unknown value for Optional()'s 'skip' argument.";
|
||||
}
|
||||
|
||||
function OneOrMore(item, rep) {
|
||||
if(!(this instanceof OneOrMore)) return new OneOrMore(item, rep);
|
||||
FakeSVG.call(this, 'g');
|
||||
rep = rep || (new Skip);
|
||||
this.item = wrapString(item);
|
||||
this.rep = wrapString(rep);
|
||||
this.width = Math.max(this.item.width, this.rep.width) + Diagram.ARC_RADIUS*2;
|
||||
this.up = this.item.up;
|
||||
this.down = Math.max(Diagram.ARC_RADIUS*2, this.item.down + Diagram.VERTICAL_SEPARATION + this.rep.up + this.rep.down);
|
||||
}
|
||||
subclassOf(OneOrMore, FakeSVG);
|
||||
OneOrMore.prototype.needsSpace = true;
|
||||
OneOrMore.prototype.format = function(x,y,width) {
|
||||
// Hook up the two sides if this is narrower than its stated width.
|
||||
var gaps = determineGaps(width, this.width);
|
||||
Path(x,y).h(gaps[0]).addTo(this);
|
||||
Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
|
||||
x += gaps[0];
|
||||
|
||||
// Draw item
|
||||
Path(x,y).right(Diagram.ARC_RADIUS).addTo(this);
|
||||
this.item.format(x+Diagram.ARC_RADIUS,y,this.width-Diagram.ARC_RADIUS*2).addTo(this);
|
||||
Path(x+this.width-Diagram.ARC_RADIUS,y).right(Diagram.ARC_RADIUS).addTo(this);
|
||||
|
||||
// Draw repeat arc
|
||||
var distanceFromY = Math.max(Diagram.ARC_RADIUS*2, this.item.down+Diagram.VERTICAL_SEPARATION+this.rep.up);
|
||||
Path(x+Diagram.ARC_RADIUS,y).arc('nw').down(distanceFromY-Diagram.ARC_RADIUS*2).arc('ws').addTo(this);
|
||||
this.rep.format(x+Diagram.ARC_RADIUS, y+distanceFromY, this.width - Diagram.ARC_RADIUS*2).addTo(this);
|
||||
Path(x+this.width-Diagram.ARC_RADIUS, y+distanceFromY).arc('se').up(distanceFromY-Diagram.ARC_RADIUS*2).arc('en').addTo(this);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
function ZeroOrMore(item, rep, skip) {
|
||||
return Optional(OneOrMore(item, rep), skip);
|
||||
}
|
||||
|
||||
function Start() {
|
||||
if(!(this instanceof Start)) return new Start();
|
||||
FakeSVG.call(this, 'path');
|
||||
this.width = 20;
|
||||
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';
|
||||
return this;
|
||||
}
|
||||
|
||||
function End() {
|
||||
if(!(this instanceof End)) return new End();
|
||||
FakeSVG.call(this, 'path');
|
||||
this.width = 20;
|
||||
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';
|
||||
return this;
|
||||
}
|
||||
|
||||
function Terminal(text) {
|
||||
if(!(this instanceof Terminal)) return new Terminal(text);
|
||||
FakeSVG.call(this, 'g');
|
||||
this.text = text;
|
||||
this.width = text.length * 8 + 20; /* Assume that each char is .5em, and that the em is 16px */
|
||||
this.up = 11;
|
||||
this.down = 11;
|
||||
}
|
||||
subclassOf(Terminal, FakeSVG);
|
||||
Terminal.prototype.needsSpace = true;
|
||||
Terminal.prototype.format = function(x, y, width) {
|
||||
// Hook up the two sides if this is narrower than its stated width.
|
||||
var gaps = determineGaps(width, this.width);
|
||||
Path(x,y).h(gaps[0]).addTo(this);
|
||||
Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
|
||||
x += gaps[0];
|
||||
|
||||
FakeSVG('rect', {x:x, y:y-11, width:this.width, height:this.up+this.down, rx:10, ry:10}).addTo(this);
|
||||
FakeSVG('text', {x:x+this.width/2, y:y+4}, this.text).addTo(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
function NonTerminal(text) {
|
||||
if(!(this instanceof NonTerminal)) return new NonTerminal(text);
|
||||
FakeSVG.call(this, 'g');
|
||||
this.text = text;
|
||||
this.width = text.length * 8 + 20;
|
||||
this.up = 11;
|
||||
this.down = 11;
|
||||
}
|
||||
subclassOf(NonTerminal, FakeSVG);
|
||||
NonTerminal.prototype.needsSpace = true;
|
||||
NonTerminal.prototype.format = function(x, y, width) {
|
||||
// Hook up the two sides if this is narrower than its stated width.
|
||||
var gaps = determineGaps(width, this.width);
|
||||
Path(x,y).h(gaps[0]).addTo(this);
|
||||
Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
|
||||
x += gaps[0];
|
||||
|
||||
FakeSVG('rect', {x:x, y:y-11, width:this.width, height:this.up+this.down}).addTo(this);
|
||||
FakeSVG('text', {x:x+this.width/2, y:y+4}, this.text).addTo(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
function Comment(text) {
|
||||
if(!(this instanceof Comment)) return new Comment(text);
|
||||
FakeSVG.call(this, 'g');
|
||||
this.text = text;
|
||||
this.width = text.length * 7 + 10;
|
||||
this.up = 11;
|
||||
this.down = 11;
|
||||
}
|
||||
subclassOf(Comment, FakeSVG);
|
||||
Comment.prototype.needsSpace = true;
|
||||
Comment.prototype.format = function(x, y, width) {
|
||||
// Hook up the two sides if this is narrower than its stated width.
|
||||
var gaps = determineGaps(width, this.width);
|
||||
Path(x,y).h(gaps[0]).addTo(this);
|
||||
Path(x+gaps[0]+this.width,y).h(gaps[1]).addTo(this);
|
||||
x += gaps[0];
|
||||
|
||||
FakeSVG('text', {x:x+this.width/2, y:y+5, class:'comment'}, this.text).addTo(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
function Skip() {
|
||||
if(!(this instanceof Skip)) return new Skip();
|
||||
FakeSVG.call(this, 'g');
|
||||
this.width = 0;
|
||||
this.up = 0;
|
||||
this.down = 0;
|
||||
}
|
||||
subclassOf(Skip, FakeSVG);
|
||||
Skip.prototype.format = function(x, y, width) {
|
||||
Path(x,y).right(width).addTo(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/* TiddlyWiki: added linking ability */
|
||||
function Link(target, item) {
|
||||
if(!(this instanceof Link)) return new Link(target, item);
|
||||
FakeSVG.call(this, 'a', {'xlink:href': target});
|
||||
this.item = item;
|
||||
this.width = item.width;
|
||||
this.up = item.up;
|
||||
this.down = item.down;
|
||||
}
|
||||
subclassOf(Link, FakeSVG);
|
||||
Link.prototype.format = function(x, y, width) {
|
||||
this.item.format(x,y,width).addTo(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/* TiddlyWiki: this block replaces the export mechanism in the original library */
|
||||
if (exports) {
|
||||
exports.Diagram = Diagram;
|
||||
exports.Sequence = Sequence;
|
||||
exports.Choice = Choice;
|
||||
exports.Optional = Optional;
|
||||
exports.OneOrMore = OneOrMore;
|
||||
exports.ZeroOrMore = ZeroOrMore;
|
||||
exports.Terminal = Terminal;
|
||||
exports.NonTerminal = NonTerminal;
|
||||
exports.Comment = Comment;
|
||||
exports.Skip = Skip;
|
||||
exports.Link = Link;
|
||||
};
|
||||
})(
|
||||
{
|
||||
VERTICAL_SEPARATION: 8,
|
||||
ARC_RADIUS: 10,
|
||||
DIAGRAM_CLASS: 'railroad-diagram',
|
||||
STROKE_ODD_PIXEL_LENGTH: true,
|
||||
INTERNAL_ALIGNMENT: 'center',
|
||||
}
|
||||
);
|
||||
|
||||
/* TiddlyWiki: removed assignments to properties of the window object */
|
21
plugins/tiddlywiki/railroad/files/tiddlywiki.files
Normal file
21
plugins/tiddlywiki/railroad/files/tiddlywiki.files
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"tiddlers": [
|
||||
{
|
||||
"file": "railroad-diagrams.css",
|
||||
"fields": {
|
||||
"type": "text/css",
|
||||
"title": "$:/plugins/tiddlywiki/railroad/railroad-diagrams.css",
|
||||
"tags": "$:/tags/Stylesheet"
|
||||
}
|
||||
},{
|
||||
"file": "railroad-diagrams.js",
|
||||
"fields": {
|
||||
"type": "application/javascript",
|
||||
"title": "$:/plugins/tiddlywiki/railroad/railroad-diagrams.js",
|
||||
"module-type": "library"
|
||||
},
|
||||
"prefix": "(function(document) {\n",
|
||||
"suffix": "\n})($tw.node ? $tw.fakeDocument : window.document)\n"
|
||||
}
|
||||
]
|
||||
}
|
334
plugins/tiddlywiki/railroad/parser.js
Normal file
334
plugins/tiddlywiki/railroad/parser.js
Normal file
@ -0,0 +1,334 @@
|
||||
/*\
|
||||
title: $:/plugins/tiddlywiki/railroad/parser.js
|
||||
type: application/javascript
|
||||
module-type: library
|
||||
|
||||
Parser for the source of a railroad diagram.
|
||||
|
||||
[:x] optional, normally included
|
||||
[x] optional, normally omitted
|
||||
{x} one or more
|
||||
{x +","} one or more, comma-separated
|
||||
[{:x}] zero or more, normally included
|
||||
[{:x +","}] zero or more, comma-separated, normally included
|
||||
[{x}] zero or more, normally omitted
|
||||
[{x +","}] zero or more, comma-separated, normally omitted
|
||||
x y z sequence
|
||||
<-x y z-> explicit sequence
|
||||
(x|y|z) alternatives
|
||||
(x|:y|z) alternatives, normally y
|
||||
"x" terminal
|
||||
<"x"> nonterminal
|
||||
/"blah"/ comment
|
||||
- dummy
|
||||
|
||||
"x" can also be written 'x' or """x"""
|
||||
|
||||
Future extensions:
|
||||
[[x|tiddler]] link
|
||||
{{tiddler}} transclusion
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var components = require("$:/plugins/tiddlywiki/railroad/components.js").components;
|
||||
|
||||
var Parser = function(source) {
|
||||
this.source = source;
|
||||
this.tokens = this.tokenise(source);
|
||||
this.tokenPos = 0;
|
||||
this.advance();
|
||||
this.root = new components.Root(this.parseContent());
|
||||
this.checkFinished();
|
||||
};
|
||||
|
||||
/////////////////////////// Parser dispatch
|
||||
|
||||
Parser.prototype.parseContent = function() {
|
||||
var content = [];
|
||||
// Parse zero or more components
|
||||
while(true) {
|
||||
var component = this.parseComponent();
|
||||
if(!component) {
|
||||
break;
|
||||
}
|
||||
content.push(component);
|
||||
}
|
||||
return content;
|
||||
};
|
||||
|
||||
Parser.prototype.parseComponent = function() {
|
||||
var component = null;
|
||||
if(this.token) {
|
||||
if(this.at("string")) {
|
||||
component = this.parseTerminal();
|
||||
} else if(this.at("identifier")) {
|
||||
component = this.parseIdentifier();
|
||||
} else {
|
||||
switch(this.token.value) {
|
||||
case "[":
|
||||
component = this.parseOptional();
|
||||
break;
|
||||
case "{":
|
||||
component = this.parseRepeated();
|
||||
break;
|
||||
case "<":
|
||||
component = this.parseNonterminal();
|
||||
break;
|
||||
case "(":
|
||||
component = this.parseChoice();
|
||||
break;
|
||||
case "/":
|
||||
component = this.parseComment();
|
||||
break;
|
||||
case "<-":
|
||||
component = this.parseSequence();
|
||||
break;
|
||||
case "-":
|
||||
component = this.parseDummy();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return component;
|
||||
};
|
||||
|
||||
/////////////////////////// Specific components
|
||||
|
||||
Parser.prototype.parseChoice = function() {
|
||||
// Consume the (
|
||||
this.advance();
|
||||
var content = [],
|
||||
colon = -1;
|
||||
do {
|
||||
// Allow at most one branch to be prefixed with a colon
|
||||
if(colon === -1 && this.eat(":")) {
|
||||
colon = content.length;
|
||||
}
|
||||
// Parse the next branch
|
||||
content.push(this.parseContent());
|
||||
} while(this.eat("|"));
|
||||
// Create a component
|
||||
var component = new components.Choice(content,colon === -1 ? 0 : colon);
|
||||
// Consume the closing bracket
|
||||
this.close(")");
|
||||
return component;
|
||||
};
|
||||
|
||||
Parser.prototype.parseComment = function() {
|
||||
// Consume the /
|
||||
this.advance();
|
||||
// The comment's content should be in a string literal
|
||||
this.expectStringLiteral("/");
|
||||
// Create a component
|
||||
var component = new components.Comment(this.token.value);
|
||||
// Consume the string literal
|
||||
this.advance();
|
||||
// Consume the closing /
|
||||
this.close("/");
|
||||
return component;
|
||||
};
|
||||
|
||||
Parser.prototype.parseDummy = function() {
|
||||
// Consume the -
|
||||
this.advance();
|
||||
// Create a component
|
||||
return new components.Dummy();
|
||||
};
|
||||
|
||||
Parser.prototype.parseIdentifier = function() {
|
||||
// Create a component
|
||||
var component = new components.Nonterminal(this.token.value);
|
||||
// Consume the identifier
|
||||
this.advance();
|
||||
return component;
|
||||
};
|
||||
|
||||
|
||||
Parser.prototype.parseNonterminal = function() {
|
||||
// Consume the <
|
||||
this.advance();
|
||||
// The nonterminal's name should be in a string literal
|
||||
this.expectStringLiteral("<");
|
||||
// Create a component
|
||||
var component = new components.Nonterminal(this.token.value);
|
||||
// Consume the string literal
|
||||
this.advance();
|
||||
// Consume the closing bracket
|
||||
this.close(">");
|
||||
return component;
|
||||
};
|
||||
|
||||
Parser.prototype.parseOptional = function() {
|
||||
// Consume the [
|
||||
this.advance();
|
||||
// Consume the { if there is one
|
||||
var repeated = this.eat("{");
|
||||
// Note whether omission is the normal route
|
||||
var normal = this.eat(":");
|
||||
// Parse the content
|
||||
var content = this.parseContent(),
|
||||
separator = null;
|
||||
// Parse the separator if there is one
|
||||
if(repeated && this.eat("+")) {
|
||||
separator = this.parseContent();
|
||||
}
|
||||
// Create a component
|
||||
var component = repeated ? new components.OptionalRepeated(content,separator,normal) : new components.Optional(content,normal);
|
||||
// Consume the closing brackets
|
||||
if(repeated) {
|
||||
this.close("}");
|
||||
}
|
||||
this.close("]");
|
||||
return component;
|
||||
};
|
||||
|
||||
Parser.prototype.parseRepeated = function() {
|
||||
// Consume the {
|
||||
this.advance();
|
||||
// Parse the content
|
||||
var content = this.parseContent(),
|
||||
separator = null;
|
||||
// Parse the separator if there is one
|
||||
if(this.eat("+")) {
|
||||
separator = this.parseContent();
|
||||
}
|
||||
// Create a component
|
||||
var component = new components.Repeated(content,separator);
|
||||
// Consume the closing bracket
|
||||
this.close("}");
|
||||
return component;
|
||||
};
|
||||
|
||||
Parser.prototype.parseSequence = function() {
|
||||
// Consume the ~
|
||||
this.advance();
|
||||
// Parse the content
|
||||
var content = this.parseContent();
|
||||
// Create a component
|
||||
var component = new components.Sequence(content);
|
||||
// Consume the closing ~
|
||||
this.close("->");
|
||||
return component;
|
||||
};
|
||||
|
||||
Parser.prototype.parseTerminal = function() {
|
||||
var component = new components.Terminal(this.token.value);
|
||||
// Consume the string literal
|
||||
this.advance();
|
||||
return component;
|
||||
};
|
||||
|
||||
/////////////////////////// Token manipulation
|
||||
|
||||
Parser.prototype.advance = function() {
|
||||
if(this.tokenPos >= this.tokens.length) {
|
||||
this.token = null;
|
||||
}
|
||||
this.token = this.tokens[this.tokenPos++];
|
||||
};
|
||||
|
||||
Parser.prototype.at = function(token) {
|
||||
return this.token && (this.token.type === token || this.token.type === "token" && this.token.value === token);
|
||||
};
|
||||
|
||||
Parser.prototype.eat = function(token) {
|
||||
var at = this.at(token);
|
||||
if(at) {
|
||||
this.advance();
|
||||
}
|
||||
return at;
|
||||
};
|
||||
|
||||
Parser.prototype.expectStringLiteral = function(preamble) {
|
||||
if(!this.at("string")) {
|
||||
throw "String expected after " + preamble;
|
||||
}
|
||||
};
|
||||
|
||||
Parser.prototype.close = function(token) {
|
||||
if(!this.eat(token)) {
|
||||
throw "Closing " + token + " expected";
|
||||
}
|
||||
};
|
||||
|
||||
Parser.prototype.checkFinished = function() {
|
||||
if(this.token) {
|
||||
throw "Syntax error at " + this.token.value;
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////// Tokenisation
|
||||
|
||||
Parser.prototype.tokenise = function(source) {
|
||||
var tokens = [],
|
||||
pos = 0,
|
||||
c, s, token;
|
||||
while(pos < source.length) {
|
||||
// Initialise this iteration
|
||||
s = token = null;
|
||||
// Skip whitespace
|
||||
pos = $tw.utils.skipWhiteSpace(source,pos);
|
||||
// Avoid falling off the end of the string
|
||||
if (pos >= source.length) {
|
||||
break;
|
||||
}
|
||||
// Examine the next character
|
||||
c = source.charAt(pos);
|
||||
if("\"'".indexOf(c) !== -1) {
|
||||
// String literal
|
||||
token = $tw.utils.parseStringLiteral(source,pos);
|
||||
if(!token) {
|
||||
throw "Unterminated string literal";
|
||||
}
|
||||
} else if("[]{}".indexOf(c) !== -1) {
|
||||
// Single or double character
|
||||
s = source.charAt(pos+1) === c ? c + c : c;
|
||||
} else if(c === "<") {
|
||||
// < or <-
|
||||
s = source.charAt(pos+1) === "-" ? "<-" : "<";
|
||||
} else if(c === "-") {
|
||||
// - or ->
|
||||
s = source.charAt(pos+1) === ">" ? "->" : "-";
|
||||
} else if("()>+|/:".indexOf(c) !== -1) {
|
||||
// Single character
|
||||
s = c;
|
||||
} else if(c.match(/[a-zA-Z]/)) {
|
||||
// Identifier
|
||||
token = this.readIdentifier(source,pos);
|
||||
} else {
|
||||
throw "Syntax error at " + c;
|
||||
}
|
||||
// Add our findings to the return array
|
||||
if(token) {
|
||||
tokens.push(token);
|
||||
} else {
|
||||
token = $tw.utils.parseTokenString(source,pos,s);
|
||||
tokens.push(token);
|
||||
}
|
||||
// Prepare for the next character
|
||||
pos = token.end;
|
||||
}
|
||||
return tokens;
|
||||
};
|
||||
|
||||
Parser.prototype.readIdentifier = function(source,pos) {
|
||||
var re = /([a-zA-Z0-9_.-]+)/g;
|
||||
re.lastIndex = pos;
|
||||
var match = re.exec(source);
|
||||
if(match && match.index === pos) {
|
||||
return {type: "identifier", value: match[1], start: pos, end: pos + match[1].length};
|
||||
} else {
|
||||
throw "Invalid identifier";
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////// Exports
|
||||
|
||||
exports.parser = Parser;
|
||||
|
||||
})();
|
7
plugins/tiddlywiki/railroad/plugin.info
Normal file
7
plugins/tiddlywiki/railroad/plugin.info
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"title": "$:/plugins/tiddlywiki/railroad",
|
||||
"description": "Plugin for generating SVG railroad diagrams",
|
||||
"author": "Astrid Elocson",
|
||||
"plugin-type": "plugin",
|
||||
"list": "readme syntax example"
|
||||
}
|
70
plugins/tiddlywiki/railroad/wrapper.js
Normal file
70
plugins/tiddlywiki/railroad/wrapper.js
Normal file
@ -0,0 +1,70 @@
|
||||
/*\
|
||||
title: $:/plugins/tiddlywiki/railroad/wrapper.js
|
||||
type: application/javascript
|
||||
module-type: widget
|
||||
|
||||
Wrapper for `railroad-diagrams.js` that provides a `<$railroad>` widget.
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var Parser = require("$:/plugins/tiddlywiki/railroad/parser.js").parser,
|
||||
Widget = require("$:/core/modules/widgets/widget.js").widget;
|
||||
|
||||
var RailroadWidget = function(parseTreeNode,options) {
|
||||
this.initialise(parseTreeNode,options);
|
||||
};
|
||||
|
||||
/*
|
||||
Inherit from the base widget class
|
||||
*/
|
||||
RailroadWidget.prototype = new Widget();
|
||||
|
||||
/*
|
||||
Render this widget into the DOM
|
||||
*/
|
||||
RailroadWidget.prototype.render = function(parent,nextSibling) {
|
||||
// Housekeeping
|
||||
this.parentDomNode = parent;
|
||||
this.computeAttributes();
|
||||
this.execute();
|
||||
// Get the source text
|
||||
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 {
|
||||
// Parse the source
|
||||
var parser = new Parser(source);
|
||||
if(this.getAttribute("mode","svg") === "debug") {
|
||||
var output = ["<pre>"];
|
||||
parser.root.debug(output, "");
|
||||
output.push("</pre>");
|
||||
div.innerHTML = output.join("");
|
||||
} else {
|
||||
div.innerHTML = parser.root.toSvg();
|
||||
}
|
||||
} catch(ex) {
|
||||
div.className = "tc-error";
|
||||
div.textContent = ex;
|
||||
}
|
||||
// Insert it into the DOM
|
||||
parent.insertBefore(div,nextSibling);
|
||||
this.domNodes.push(div);
|
||||
};
|
||||
|
||||
RailroadWidget.prototype.refresh = function(changedTiddlers) {
|
||||
var changedAttributes = this.computeAttributes();
|
||||
if(changedAttributes.text) {
|
||||
this.refreshSelf();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
exports.railroad = RailroadWidget;
|
||||
|
||||
})();
|
Loading…
Reference in New Issue
Block a user