Code Monkey home page Code Monkey logo

railroad-diagrams's Introduction

Railroad-diagram Generator

Diagram(Stack('Generate', 'some'), OneOrMore(NonTerminal('railroad diagrams'), Comment('and more')))

This is a small library for generating railroad diagrams (like what JSON.org uses) using SVG, with both JS and Python ports.

Railroad diagrams are a way of visually representing a grammar in a form that is more readable than using regular expressions or BNF. They can easily represent any context-free grammar, and some more powerful grammars. There are several railroad-diagram generators out there, but none of them had the visual appeal I wanted, so I wrote my own.

Here's an online dingus for you to play with and get SVG code from!

Versions

This library is supported both as a JS module and a Python module, and the install and use instructions for each are in their lang-specific READMEs.

Caveats

SVG can't actually respond to the sizes of content; in particular, there's no way to make SVG adjust sizing/positioning based on the length of some text. Instead, I guess at some font metrics, which mostly work as long as you're using a fairly standard monospace font. This works pretty well, but long text inside of a construct might eventually overflow the construct.

License

Standard MIT license; see LICENSE.

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. If you're doing something cool with it, again I'd appreciate it if you let me know, but that's not required either.

railroad-diagrams's People

Contributors

adamshaylor avatar aeurielesn avatar bkiers avatar dundalek avatar enplotz avatar jonathankingston avatar larshp avatar lucaswiman avatar lurch avatar mebeim avatar mfederczuk avatar prantlf avatar ragtime avatar rosspatterson avatar rparree avatar rspeele avatar sebastien-f avatar simonsapin avatar spencerpark avatar tabatkins avatar thatch avatar yengip avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

railroad-diagrams's Issues

ease the adoption of this npm module by enterprises

The CC0 license can “weaken patent infringement defenses available to users of software released under CC0” https://opensource.org/faq#cc-zero

The affirmers may not have “all the necessary rights to grant permission to use the CC0’d work” https://wiki.creativecommons.org/wiki/CC0_FAQ#How_can_I_be_sure_that_I_have_all_the_rights_I_need_to_use_the_work.3F

I wonder if MIT or another top-5 OSI approved license could help in easing the legal worries in adopters.

Python port problem?

Rendering an SVG image generated by the Python port of railroad-diagrams, using librsvg causes this error:

gi.repository.GLib.Error: rsvg-error-quark: Error domain 1 code 9 on line 32 column 158 of data: xmlParseCharRef: invalid xmlChar value 0
 (0)

The file, at this point, contains:

     370  3d 22 32 30 22 3e 3c 2f  72 65 63 74 3e 3c 74 65  ="20"></rect><te
     380  78 74 20 78 3d 22 37 34  2e 32 35 22 20 79 3d 22  xt x="74.25" y="
     390  33 35 22 3e 26 23 39 31  2d 39 26 23 39 33 3c 2f  35">&#91-9&#93</                                   
     3a0  74 65 78 74 3e 3c 2f 67  3e 3c 70 61 74 68 20 64  text></g><path d                                   

or, in pain text:
<text x="74.25" y="35">&#91-9&#93</text>
which I suspect is missing the ';' after the entities (at least I suspect the entity syntax is the same as HTML's).

railroads.py, on line 22:
..., lambda c: "&#{0}".format(ord(c.group(0))), unicode(text))
seems to lack the ';' (I had to remove the initial characters of the line to avoid Markdown issues)

No expert in SVG - less in parsing. Am I correct here?

railroad.svg.txt

Please create git tags for releases

We packagers find git tags very convenient for fetching release versions from GitHub. Could you please create and push tags corresponding to releases (or at least the most recent release)?

Options to colour the Terminals?

I've got a pretty complicated "syntax grammar" made up of a whole bunch of railroad diagrams (25 of them!). I'm using the Python version of railroad-diagrams to generate all the diagrams in SVG format which I then display on an autogenerated HTML page.

Some of the "tokens" are keywords with no arguments, some of them are keywords which take a single numeric argument, some of them are keywords which take a single-word argument, and some of them are keywords which take a double-quoted string argument.
Currently, I differentiate between these different types with e.g.:

Terminal('high')
Sequence(Terminal('count'), NonTerminal('count_number'))
Sequence(Terminal('name'), NonTerminal('name_string'))
Sequence(Terminal('description'), NonTerminal('"description_string"'))

This all works fine, but I was wondering if it would be possible for the NonTerminal and Terminal objects to have an optional background_colour= parameter, so that I could give each of these different "types" of keywords / arguments a different background colour (rather than just the default light green), to make them easier to differentiate?

I didn't design this grammar, I "inherited" it on a big project, and I'm using railroad-diagrams to help me reverse-engineer the syntax and fully document it. Thanks for such a useful project 👍

Give the dingus the ability to set the global options

Currently each item in a "Choice" is always centered. I'd like the option to left justify the items. Sometimes left justification makes the syntax easier to understand.

Below is an example. The first image is generated by "railroad-diagrams" (with some additional coloring). The second is a photoshopped example of the same SVG with the "Choice" items left justified.

Image 1
image
Image 2
image

Escape backtick characters

When entering ` backtick characters into the terminals the SVG output in Chrome doesn't render completely (I didn't test any other browsers).

Replacing the backtick with ` in the SVG output works however entering that into the terminal comes out as a literal &#96; rather than the backtick.

HorizontalChoice does not support Stack as last element.

Diagram(
HorizontalChoice(
    Stack(
      NonTerminal('name char'),
      NonTerminal('name char'),
      NonTerminal('name char'),
      NonTerminal('escape')
   ),
    Stack(
      NonTerminal('name char'),
      NonTerminal('escape')),
    Stack(
      NonTerminal('name char'),
      NonTerminal('name char'),
      NonTerminal('name char'),
      NonTerminal('escape'))
))

Stack() nodes in Python port do not connect correctly to next node

Using the Python port, if you use a Stack(...), the path doesn't connect to the next DiagramItem in the sequence (such as the End). This is because the next path is pushed down farther than it should be.

misaligned

This is a really simple bug, len(self.items) just needs to be len(self.items) - 1 to match the JS version. PR for this coming in a second.

Have addTo() default to the <script>’s parent, not document.body

The example file contains a bunch of <script> element, each with Diagram(…).addTo();. This suggests that the diagram will be added at the "current position", just after the <script> element. But this is not the case: addTo() default to something like document.body.appendChild(…).

A test case:

<!DOCTYPE html>
<body>
  <p>
    a
    <script>
      var el = document.createElement("span");
      el.textContent = 'b';
      document.body.appendChild(el);
    </script>
    c
  </p>
  <p>
    d
  </p>
</body>

Here, the span element is appended to body, after the child of <body> that is being parsed. (The paragraph, here.) The final DOM order is a c b d. Is it possible in JS to get the <script> element being executed? The goal is to append just after that, so that the DOM order becomes a b c d.

Missing files in sdist

It appears that the manifest is missing at least one file necessary to build
from the sdist for version 1.0.3. You're in good company, about 5% of other
projects updated in the last year are also missing files.

+ /tmp/venv/bin/pip3 wheel --no-binary railroad-diagrams -w /tmp/ext railroad-diagrams==1.0.3
Looking in indexes: http://10.10.0.139:9191/root/pypi/+simple/
Collecting railroad-diagrams==1.0.3
  Downloading http://10.10.0.139:9191/root/pypi/%2Bf/342/d1b9e4bfc335d/railroad-diagrams-1.0.3.tar.gz (19 kB)
    ERROR: Command errored out with exit status 1:
     command: /tmp/venv/bin/python3 -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-wheel-6_btpu_e/railroad-diagrams/setup.py'"'"'; __file__='"'"'/tmp/pip-wheel-6_btpu_e/railroad-diagrams/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /tmp/pip-wheel-6_btpu_e/railroad-diagrams/pip-egg-info
         cwd: /tmp/pip-wheel-6_btpu_e/railroad-diagrams/
    Complete output (5 lines):
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-wheel-6_btpu_e/railroad-diagrams/setup.py", line 3, in <module>
        with open("README-py.md", "r") as fh:
    FileNotFoundError: [Errno 2] No such file or directory: 'README-py.md'
    ----------------------------------------
ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.

PYPI for python library

Would be very nice if the python library could be put on Python Package Index (PYPI) for usual method of downloading modules and including in other applications.

latest version to npm

Hi,
Is it possible to publish the latest version of railroad-diagrams to npm?

On npm version 1.0.0, the NonTerminal function is

function NonTerminal(text)

however in the git repository it is

function NonTerminal(text, href)

Python port leaves gap after OptionalSequence

Hi Tab,
Love the diagrams, thanks for porting to Python.
There seems to be an anomaly when drawing OptionalSequence containers where there's a break in the main line which grows the more items you have in the sequence.

I have tried to find out why it's happening to see if I could fix it and contribute the fix, but the logic in the .format() method (where I think it might be happening) is beyond my capability to track I'm afraid. I think the value of the x coordinate is getting out of step with the width of the items somewhere in there but I can't see through all the needsSpace, itemSpace, itemWidth and AR adjustments to work it out.

I don't know if you're still interested in this project but I thought I'd mention it to see if you can fix it or perhaps point me in the right direction. It may be something in my environment I guess but I would have thought that would cause problems with the other containers, and I haven't seen any of that.

I've attached an image of the Chrome rendering python generated by:
Diagram(
OptionalSequence(
"Optional", "Optional",
"Optional",
),
)

Plus, the svg code snipped from the html page and the same from the js generator web page.
os_from_generator.txt
os_from_python.txt
OptionalSequence python example

Cheers,
IP

How to use options

I am not a JS dev by nature, and only understand basic functions.

How to actually pass options like DIAGRAM_CLASS?

Diagram(
Terminal("Default Job","FLOW-COM-JOB1-147"),
Terminal("App 1","FLOW-COM-APP1-147"),
options={DIAGRAM_CLASS:"foo"}
).addTo();
Diagram({items:[
Terminal("Default Job","FLOW-COM-JOB1-147"),
Terminal("App 1","FLOW-COM-APP1-147")
],

options:[DIAGRAM_CLASS:"foo"]
).addTo();

Error "Cannot read property 'format' of undefined "

The following JS piece of code, on execution, fails:

window.setTimeout(function () {
    var strict = Optional(Terminal("strict"));
    var graph = Choice(Terminal("graph"), Terminal("digraph"));
    var id = Optional(NonTerminal("ID"));
    var stmtList = NonTerminal("stmt_list");
    var seq = Sequence(strict, graph, id, Terminal("{"), stmtList, Terminal("}"));  
    var o = Diagram(seq);
    o.addTo(window.document.body);
}, 1000);

And, this other one (is an equivalent of the preceding) also:

Diagram(
    Sequence(
        Optional(Terminal("strict")),
        Choice(Terminal("graph"), Terminal("digraph")),
        Optional(NonTerminal("ID")),
        Terminal("{"),
        NonTerminal("stmt_list"),
        Terminal("}")))
.addTo();   

Error details:

Message:
    Uncaught TypeError: Cannot read property 'format' of undefined 
File:
    railroad-diagrams.js
Line:
    291

Support an "alternating sequence" node

Originally reported in speced/bikeshed#1184.

It's currently hard to express the concept of "an alternating sequence of two nodes, which can start and end with either node". You can get close with a Plus(MultipleChoice("all", ...)) node, but it doesn't express the same constraints (it allows the same node to be adjacent to itself, if it's the end of one repetition and the beginning of the next). The only ways to express it correctly with the existing constructs are hard to read and write, and require duplicating one of the nodes at least 3 times.

This seems like a useful and common sort of construct, and should be easier to express.

Suggested rendering:

crossover node

Add a container : Group()

It would be great to add a new container, Group(child, label), allowing to render Regular expression groups, as in the following diagram for the RegExp /a(b)*c/:
image

For diagrams not about regexps, it may be used to comment parts of the diagram.

It would be great that the label had the same width as the box, and that there both label and box had classes, for easy styling.

ComplexDiagrams are actually start symbols, no?

Alternately, you can call ComplexDiagram(); it's identical to Diagram(), but has slightly different start/end shapes, same as what JSON.org does to distinguish between "leaf" types like number (ordinary Diagram()) and "container" types like Array (ComplexDiagram()).

I think on json.org the different start/end shapes indicate what are valid start symbols. They are used for value too by now.

Doc confusion with branching logic

My sincere apologies if this isn't an actual issue. I can not figure out how to accomplish the following branching logic based on the documentation provided in the README:

|-Promise--then-----------------|
         \     \             /
          \     throw---catch
           \__________/

Any help appreciated.

unexpected behaviour using Stack()

Whilst messing about with railroad-diagrams I ended doing a diagram essentially for the regex (A B? C?)+
When the components of the regex are Stack()ed everything seems fine, when not stacked C is missing.
The regex (A B? C?) renders as I would expect in both cases. HTML illustrating this below:

<!DOCTYPE html>
<style>
body {
    background-color: hsl(30,20%, 95%);
}
h1 {
    font-family: sans-serif;
    font-size: 1em;
}
script {
    display: none;
    white-space: pre;
    margin-top: -1em;
}
</style>
<link rel='stylesheet' href='railroad-diagrams.css'>
<script src='railroad-diagrams.js'></script>
<title>bug?</title><body>
<h2><pre>(A B? C?)+</pre></h2>

<p>stacked </p>
<script>
Diagram(
    OneOrMore
    (
        Stack
        (
            NonTerminal('A'),
            Optional
            (
                NonTerminal('B')
            ), 
            Optional
            (
                NonTerminal('C')
            )
        )
    )
).addTo();
</script>

<p>not stacked. huh?</p>
<script>
Diagram(
    OneOrMore
    (
        NonTerminal('A'),
        Optional
        (
            NonTerminal('B')
        ), 
        Optional
        (
            NonTerminal('C')
        )
    )
).addTo();
</script>

<hr>

<h2><pre>A B? C?</pre></h2>

<p>stacked </p>
<script>
Diagram(
    NonTerminal('A'),
    Optional
    (
        NonTerminal('B')
    ), 
    Optional
    (
        NonTerminal('C')
    )
).addTo();
</script>

<p>not stacked. fine.</p>
<script>
Diagram(
    Stack(
        NonTerminal('A'),
        Optional
        (
            NonTerminal('B')
        ), 
        Optional
        (
            NonTerminal('C')
        )
    )
).addTo();
</script>

</body>
</html>

Please advise.

node.js compatibility

I’m working on a Python port of this, to use in Bikeshed. I’d like to use node.js for testing (comparing the output of the two implementations), which currently fails because window is undefined.

It would be nice to use some kind of "universal module" pattern, but I don’t know which flavor to pick.

Mixed Sequences and non-Sequences in a Choice are not aligned

In the following diagram, choice 1 & 2 are aligned while 3 is not.

Diagram.INTERNAL_ALIGNMENT = 'left';
Diagram(
  Choice(0,
    Terminal('choice 1'),
    Terminal('choice 2'),
    Sequence(
      Terminal('choice 3'),
      Terminal('and more')
    )
  )
)

Same problem with right alignment.

Maybe Sequence should not add leading and trailing paths (but only between 2 consecutive elements in a Sequence).

[question]: text output?

Hello!

I'm interested in the possibility of rendering railroad diagrams in text for use in neovim or other terminal-bound editors. Is that something that could be done with this library? Would it involve subclassing the various entities?

Cheers

F# Port

Hello, I have made an F# port to the Python version of the library. I would like to make a PR to add it to this repo, so people can use this library in .Net and see how useful it would be. If I have time, I will rework it for WebSharper :D but that is not sure yet.
Have a nice day!

Extend to use as process flow

Are you aware of work to extend this library into a simple workflow diagram generator?

I've always liked railroad diagrams for their cleannes and would love to use them as a basis for simple flowchart-type process maps.

The only problem is that railroad diagrams have a couple of assumptions and clash with diagram flows:

  • They assume you'll always reach a single end rather than having multiple outcomes
  • They don't contemplate going "back"

Of the two the second poses a visual challenge, and probably would require being able to optionally reference individual sections to be able to point to them from other points.

Other limitations are more aesthetic (diferentiating a decision point visually, for example). And other requirements from a workflow are already covered (repetition, selection, etc.)

Cross reference diagrams from non-terminal

(This might be a feature for Bikeshed rather than for the diagram renderer.)

Many non-terminals in CSS Syntax diagrams refer to other diagrams. I’d like to have links so that eg. clicking on "escape" in the diagram shows the escape diagram.

http://dev.w3.org/csswg/css-syntax/#ident-token-diagram
http://dev.w3.org/csswg/css-syntax/#escape-diagram

Preferably, these links would be generated automatically or with minimal markup. (Maybe NT: <a>escape</a> and hooking into Bikeshed’s auto-linking?)

OptionalSequence.Width bug

Commit: aaa704d

Test code:
Diagram(
OptionalSequence(
NonTerminal('TestBlock1'),
NonTerminal('TestBlock2')
)
).addTo();

Result:
default

Possible solution:
File: railroad-diagrams.js line: 382
Change this.width = Diagram.ARC_RADIUS*4; to this.width = Diagram.ARC_RADIUS*2;

Can't make .svg file

I wanted to take the generated svgs and place them in .svg files. Putting the XML in a file an referencing it from the browser just causes a broken image icon. Additionally, trying to open the .svg file with InkScape (an open source svg editor), does open it, but everything is black and messed up. I tried comparing the generated svgs to svgs saved by InkScape and other examples I found online. It was missing an xml declaration like <?xml version="1.0" encoding="UTF-8"?> but that didn't fix the problem. Adding version="1.0" xmlns="http://www.w3.org/2000/svg" attributes to the <svg> element made the browser render them the same messed up thing as InkScape did.

Seems like there is something not quite compliant about the generated svg, but I don't know svg well enough to say what it is.

No AlternatingSequence example in README

While working on a recent PR, I noticed that there was an AlternatingSequence class, but there's no mention of this in the README file. Could you add an example with a matching screenshot please? 🙂

example.html has bit-rotted

Hi - it looks like when you retired the non-modular version of railroad-diagram.js in Jan 2022, it broke the example.html file (last updated in Feb 2018). It would be great to have that file updated. The immediate issue is that it refers to the retired versions rather than the current versions. But it also needs to be adapted to import the module appropriately.

FYI we are using it in our our project work at https://github.com/GetPoplog/Grammar

Steve

Bug in AlternatingSequence exception

class AlternatingSequence(DiagramMultiContainer):
	def __new__(cls, *items):
		if len(items) == 2:
			return super(AlternatingSequence, cls).__new__(cls)
		else:
			raise Exception("AlternatingSequence takes exactly two arguments got " + len(items))

raises:

TypeError: can only concatenate str (not "int") to str

Last line should use "str(len(items))" (or do proper format call:

raise Exception("AlternatingSequence takes exactly two arguments got {0}".format(len(items)))

Support a horizontal choice

( I will try to create a PR for this myself later)

Currently choices are always vertical, but if you have an EBNF that has a choice for all 'letters' (upcase and downcase) you get a really long vertical list of elements (52 of them)

If we support horizontal choices as well, you can mix horizontal and vertical choices and make this more into a grid.

img_9813

I was thinking about the following syntax:

HorizontalChoice(...children)

so without the first argument of what should be considered as the default path.

Allow to select single/double lines at start and end

It would be great if you could select whether to have single or doulbe lines as the begin and end indicators.
I am not completely sure but I think that the authors of json.org used this to highlight which of the non-terminals can have whitespace between the components.

sometime links not working

I did some research and this comes from the fact Stack "returns" hide some underlying nodes.
Correction I made into the Stack.prototype.format, where I add path bit by bits instead of the full "return".
I changed

Path(x, y)
	.arc('ne').down(Math.max(0, item.down + Diagram.VERTICAL_SEPARATION - Diagram.ARC_RADIUS*2))
	.arc('es').left(innerWidth)
	.arc('nw').down(Math.max(0, this.items[i+1].up + Diagram.VERTICAL_SEPARATION - Diagram.ARC_RADIUS*2))
	.arc('ws').addTo(this);

With

var temp =y;
Path(x, y).arc('ne').down(Math.max(0, item.down + Diagram.VERTICAL_SEPARATION - Diagram.ARC_RADIUS*2)).addTo(this);
y+=Diagram.ARC_RADIUS+Math.max(0, item.down + Diagram.VERTICAL_SEPARATION - Diagram.ARC_RADIUS*2); 
x+=Diagram.ARC_RADIUS;
Path(x, y).arc('es').left(innerWidth).addTo(this);
y+=Diagram.ARC_RADIUS;x=x-innerWidth-Diagram.ARC_RADIUS;;
Path(x, y).arc('nw').down(Math.max(0, this.items[i+1].up + Diagram.VERTICAL_SEPARATION - Diagram.ARC_RADIUS*2)).addTo(this);
y+=Diagram.ARC_RADIUS+Math.max(0, this.items[i+1].up + Diagram.VERTICAL_SEPARATION - Diagram.ARC_RADIUS*2);
x=x-Diagram.ARC_RADIUS;
Path(x, y).arc('ws').addTo(this);
y = temp + Math.max(item.down + Diagram.VERTICAL_SEPARATION, Diagram.ARC_RADIUS*2) + Math.max(this.items[i+1].up + Diagram.VERTICAL_SEPARATION, Diagram.ARC_RADIUS*2);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.