├── .eslintrc.json ├── .github ├── dependabot.yml └── workflows │ ├── publish.yml │ └── test.yml ├── .gitignore ├── CHANGES.md ├── Gruntfile.js ├── README.md ├── appveyor.yml ├── bin └── fbp ├── browser └── entry.js ├── grammar └── fbp.peg ├── karma.config.js ├── lib ├── fbp.js ├── index.js └── serialize.js ├── package.json ├── schema └── graph.json ├── schemata └── graph.yaml └── spec ├── .eslintrc.json ├── fbp.js ├── json.js └── utils └── inject.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base" 3 | } 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | versioning-strategy: increase-if-necessary 8 | - package-ecosystem: github-actions 9 | directory: "/" 10 | schedule: 11 | interval: weekly 12 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Node.js Package 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2.3.4 13 | - uses: actions/setup-node@v2.1.5 14 | with: 15 | node-version: 12 16 | - run: npm install 17 | - run: npm test 18 | 19 | publish-npm: 20 | needs: build 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v2.3.4 24 | - uses: actions/setup-node@v2.1.5 25 | with: 26 | node-version: 12 27 | registry-url: https://registry.npmjs.org/ 28 | - run: npm publish 29 | env: 30 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 31 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Run test suite 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | node-version: [12.x] 12 | steps: 13 | - uses: actions/checkout@v2.3.4 14 | - name: Use Node.js ${{ matrix.node-version }} 15 | uses: actions/setup-node@v2.1.5 16 | with: 17 | node-version: ${{ matrix.node-version }} 18 | - run: npm install 19 | - run: npm test 20 | env: 21 | CI: true 22 | merge-me: 23 | name: Auto-merge dependency updates 24 | needs: test 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: ridedott/merge-me-action@v2.2.18 28 | with: 29 | GITHUB_LOGIN: 'dependabot[bot]' 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | .DS_Store 3 | /browser/fbp.* 4 | /spec/result.xml 5 | /.nyc_output/ 6 | package-lock.json 7 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # fbp 1.8.0 - released 13.10.2020 2 | 3 | * Fixed availability of the JSON-to-FBP serializer 4 | 5 | # fbp 1.7.0 - released 17.11.2017 6 | 7 | * Added support for annotations like `# @runtime noflo-nodejs` or `# @name SomeComponent` 8 | * Added basic validation for parsed graphs to find issues with misnamed or misconfigured components 9 | * Fixed JSON-to-FBP serialization with case sensitive graphs 10 | 11 | # fbp 1.6.0 - released 03.11.2017 12 | 13 | * Removed support for deprecated `EXPORT` keyword 14 | 15 | # fbp 1.5.0 - released 06.07.2016 16 | 17 | * Add API for serializating back to FBP DSL: `fbp.serialize(graph)` 18 | * Let `fbp somegraph.json` serialize back to FBP DSL 19 | 20 | # fbp 1.4.0 - released 17.06.2016 21 | 22 | * Allow JSON as IIPs, `{ "foo": { "bar": { "baz": 1234 }}} -> IN Display(Output)` 23 | * Allow anonymous nodes `'foo' -> IN (Output) OUT -> myfoo(Component)` 24 | * Allow declaring components without making a connection `Display(Output)` 25 | * Allow not specifying ports. Will default to `IN` and `OUT` respectively. `(A) -> (B)` and `(A) OUT -> (B)` 26 | 27 | # fbp 1.3.0 - released 24.05.2016 28 | 29 | * Include JSON schema definition for graph output 30 | * Support enforcing the schema in parser via `validateSchema` option. Requires optional dependency `tv4`. 31 | 32 | # fbp 1.2.0 - released 23.05.2016 33 | 34 | * Support case-preserving in ports. Opt-in, via `caseSensitive` option. 35 | 36 | # fbp 1.1.4 - released 05.10.2015 37 | 38 | * Support for dashes `-` in node names 39 | 40 | # fbp 1.1.2 - released 25.04.2014 41 | 42 | * Support for array port index `[0]` in connections 43 | 44 | # fbp 1.1.0 - released 19.02.2014 45 | 46 | * Support for `INPORT` and `OUTPORT`, for exporting ports. 47 | 48 | # fbp 1.0.0 - released 30.05.2013 49 | 50 | * Initial release 51 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | // Project configuration 3 | this.initConfig({ 4 | pkg: this.file.readJSON('package.json'), 5 | 6 | // Generate library from Peg grammar 7 | peg: { 8 | fbp: { 9 | src: 'grammar/fbp.peg', 10 | dest: 'lib/fbp.js', 11 | }, 12 | }, 13 | 14 | yaml: { 15 | schemas: { 16 | files: [{ 17 | expand: true, 18 | cwd: 'schemata/', 19 | src: '*.yaml', 20 | dest: 'schema/', 21 | }, 22 | ], 23 | }, 24 | }, 25 | 26 | // Build the browser Component 27 | noflo_browser: { 28 | build: { 29 | files: { 30 | 'browser/fbp.js': ['browser/entry.js'], 31 | }, 32 | options: { 33 | manifest: { 34 | runtimes: ['noflo'], 35 | discover: true, 36 | recursive: true, 37 | silent: true, 38 | }, 39 | }, 40 | }, 41 | }, 42 | 43 | // Automated recompilation and testing when developing 44 | watch: { 45 | files: ['spec/*.js', 'grammar/*.peg'], 46 | tasks: ['test'], 47 | }, 48 | 49 | // BDD tests on Node.js 50 | mochaTest: { 51 | nodejs: { 52 | src: ['spec/*.js'], 53 | options: { 54 | reporter: 'spec', 55 | require: 'spec/utils/inject.js', 56 | }, 57 | }, 58 | }, 59 | 60 | // BDD tests on browser 61 | karma: { 62 | unit: { 63 | configFile: 'karma.config.js', 64 | }, 65 | }, 66 | }); 67 | 68 | // Grunt plugins used for building 69 | this.loadNpmTasks('grunt-yaml'); 70 | this.loadNpmTasks('grunt-peg'); 71 | this.loadNpmTasks('grunt-noflo-browser'); 72 | 73 | // Grunt plugins used for testing 74 | this.loadNpmTasks('grunt-mocha-test'); 75 | this.loadNpmTasks('grunt-karma'); 76 | this.loadNpmTasks('grunt-contrib-watch'); 77 | 78 | this.registerTask('build', ['peg', 'yaml', 'noflo_browser']); 79 | this.registerTask('test', ['build', 'mochaTest', 'karma']); 80 | this.registerTask('default', ['build']); 81 | }; 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FBP flow definition language parser 2 | =================================== 3 | 4 | The *fbp* library provides a parser for a domain-specific language for flow-based-programming (FBP), 5 | used for defining graphs for FBP programming environments like 6 | [NoFlo](https://noflojs.org), [MicroFlo](https://microflo.org) and [MsgFlo](http://msgflo.org). 7 | 8 | ## Usage 9 | 10 | You can use the FBP parser in your JavaScript code with the following: 11 | 12 | ```javascript 13 | var parser = require('fbp'); 14 | 15 | // Some FBP syntax code 16 | var fbpData = "'hello, world!' -> IN Display(Output)"; 17 | 18 | // Parse into a Graph definition JSON object 19 | var graphDefinition = parser.parse(fbpData, {caseSensitive: true}); 20 | ``` 21 | 22 | When `caseSensitive` is `false` the parser will convert port names to lowercase. This is currently the default behavior, but in future releases the default will change to preserve case. It is therefore recommended that you always specify the `caseSensitive` option to make your code future-proof. 23 | 24 | 25 | ### Command-line 26 | 27 | The *fbp* package also provides a command-line tool for converting FBP files into JSON: 28 | 29 | ``` 30 | $ fbp somefile.fbp [--case-sensitive] > somefile.json 31 | ``` 32 | 33 | And for converting JSON files into FBP: 34 | 35 | ``` 36 | $ fbp somefile.json [--case-sensitive] > somefile.fbp 37 | ``` 38 | 39 | 40 | ## Language for Flow-Based Programming 41 | 42 | FBP is a Domain-Specific Language (DSL) for easy graph definition. The syntax is the following: 43 | 44 | * `'somedata' -> PORT Process(Component)` sends initial data _somedata_ to port _PORT_ of process _Process_ that runs component _Component_ 45 | * `A(Component1) X -> Y B(Component2)` sets up a connection between port _X_ of process _A_ that runs component _Component1_ and port _Y_ of process _B_ that runs component _Component2_ 46 | 47 | You can connect multiple components and ports together on one line, and separate connection definitions with a newline or a comma (`,`). 48 | 49 | Components only have to be specified the first time you mention a new process. Afterwards, simply use the process name. 50 | 51 | Example: 52 | 53 | ```fbp 54 | 'somefile.txt' -> SOURCE Read(ReadFile) OUT -> IN Split(SplitStr) 55 | Split OUT -> IN Count(Counter) COUNT -> IN Display(Output) 56 | Read ERROR -> IN Display 57 | ``` 58 | 59 | The syntax also supports blank lines and comments. Comments start with the `#` character. 60 | 61 | Example with the same graph than above : 62 | 63 | ```fbp 64 | # Read the content of "somefile.txt" and split it by line 65 | 'somefile.txt' -> SOURCE Read(ReadFile) OUT -> IN Split(SplitStr) 66 | 67 | # Count the lines and display the result 68 | Split() OUT -> IN Count(Counter) COUNT -> IN Display(Output) 69 | 70 | # The read errors are also displayed 71 | Read() ERROR -> IN Display() 72 | ``` 73 | 74 | ### Exporting ports 75 | 76 | When FBP-defined graphs are used as subgraphs in other flows, it is often desirable to give more user-friendly names to their available ports. In the FBP language this is done by `INPORT` and `OUTPORT` statements. 77 | 78 | Example: 79 | 80 | ```fbp 81 | INPORT=Read.IN:FILENAME 82 | Read(ReadFile) OUT -> IN Display(Output) 83 | ``` 84 | 85 | This line would export the *IN* port of the *Read* node as *FILENAME*. 86 | 87 | ### Node metadata 88 | 89 | It is possible to append metadata to Nodes when declaring them by adding the metadata string to the Component part after a colon (`:`). 90 | 91 | Example: 92 | 93 | ```fbp 94 | 'somefile.txt' -> SOURCE Read(ReadFile:main) 95 | Read() OUT -> IN Split(SplitStr:main) 96 | Split() OUT -> IN Count(Counter:main) 97 | Count() COUNT -> IN Display(Output:main) 98 | Read() ERROR -> IN Display() 99 | ``` 100 | 101 | In this case the route leading from *Read* to *Display* through *Split* and *Count* would be identified with the string *main*. You can also provide arbitrary metadata keys with the `=` syntax: 102 | 103 | ```fbp 104 | Read() OUT -> IN Split(SplitStr:foo=bar,baz=123) 105 | ``` 106 | 107 | In this case the *Split* node would contain the metadata keys `foo` and `baz` with values `bar` and `123`. 108 | 109 | ### Annotations 110 | 111 | FBP graphs also support annotations for specifying things like graph name, description, icon, or the FBP runtime to be used for executing the graph. 112 | 113 | The syntax for annotations is `# @name value`, for example: 114 | 115 | ```fbp 116 | # @runtime noflo-nodejs 117 | # @name ReadSomefile 118 | 'somefile' -> SOURCE Read(ReadFile) 119 | ``` 120 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # appveyor file 2 | # http://www.appveyor.com/docs/appveyor-yml 3 | 4 | init: 5 | - git config --global core.autocrlf input 6 | 7 | environment: 8 | matrix: 9 | - nodejs_version: 12 10 | 11 | install: 12 | - ps: Install-Product node $env:nodejs_version 13 | - npm install -g grunt-cli 14 | - npm install 15 | - npm build 16 | 17 | build: off 18 | 19 | test_script: 20 | - node --version 21 | - npm --version 22 | - cmd: grunt test --no-color 23 | -------------------------------------------------------------------------------- /bin/fbp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // vim: set filetype=javascript: 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | var parser = require('../lib/index'); 6 | 7 | var caseFlag = '--case-sensitive', 8 | caseSensitive = false; 9 | 10 | if (process.argv.length < 3 || process.argv.length > 4 || (process.argv[4] && process.argv[4] !== caseFlag)) { 11 | console.log("Usage:\n$ fbp somefile.fbp [" + caseFlag + "]"); 12 | process.exit(0); 13 | } 14 | 15 | caseSensitive = process.argv[3] === caseFlag; 16 | 17 | var file = path.resolve(process.cwd(), process.argv[2]); 18 | if (!fs.existsSync(file)) { 19 | console.log("File " + file + " not found"); 20 | process.exit(1); 21 | } 22 | 23 | fileType = file.split('.').pop() 24 | if(fileType == 'json'){ 25 | return console.log(parser.serialize(fs.readFileSync(file, 'utf-8'))); 26 | } 27 | 28 | var result = parser.parse(fs.readFileSync(file, 'utf-8'), {caseSensitive: caseSensitive}); 29 | console.log(JSON.stringify(result, null, 2)); 30 | -------------------------------------------------------------------------------- /browser/entry.js: -------------------------------------------------------------------------------- 1 | var exported = { 2 | fbp: require('../lib/index.js') 3 | }; 4 | 5 | if (window) { 6 | window.require = function (moduleName) { 7 | if (exported[moduleName]) { 8 | return exported[moduleName]; 9 | } 10 | throw new Error('Module ' + moduleName + ' not available'); 11 | }; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /grammar/fbp.peg: -------------------------------------------------------------------------------- 1 | /* 2 | * Classic example grammar, which recognizes simple arithmetic expressions like 3 | * "2*(3+4)". The parser generated from this grammar then computes their value. 4 | */ 5 | 6 | { 7 | var parser, edges, nodes; 8 | 9 | var defaultInPort = "IN", defaultOutPort = "OUT"; 10 | 11 | parser = this; 12 | delete parser.properties; 13 | delete parser.inports; 14 | delete parser.outports; 15 | delete parser.groups; 16 | 17 | edges = parser.edges = []; 18 | 19 | nodes = {}; 20 | 21 | var serialize, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; 22 | 23 | parser.validateContents = function(graph, options) { 24 | // Ensure all nodes have a component 25 | if (graph.processes) { 26 | Object.keys(graph.processes).forEach(function (node) { 27 | if (!graph.processes[node].component) { 28 | throw new Error('Node "' + node + '" does not have a component defined'); 29 | } 30 | }); 31 | } 32 | // Ensure all inports point to existing nodes 33 | if (graph.inports) { 34 | Object.keys(graph.inports).forEach(function (port) { 35 | var portDef = graph.inports[port]; 36 | if (!graph.processes[portDef.process]) { 37 | throw new Error('Inport "' + port + '" is connected to an undefined target node "' + portDef.process + '"'); 38 | } 39 | }); 40 | } 41 | // Ensure all outports point to existing nodes 42 | if (graph.outports) { 43 | Object.keys(graph.outports).forEach(function (port) { 44 | var portDef = graph.outports[port]; 45 | if (!graph.processes[portDef.process]) { 46 | throw new Error('Outport "' + port + '" is connected to an undefined source node "' + portDef.process + '"'); 47 | } 48 | }); 49 | } 50 | // Ensure all edges have nodes defined 51 | if (graph.connections) { 52 | graph.connections.forEach(function (edge) { 53 | if (edge.tgt && !graph.processes[edge.tgt.process]) { 54 | if (edge.data) { 55 | throw new Error('IIP containing "' + edge.data + '" is connected to an undefined target node "' + edge.tgt.process + '"'); 56 | } 57 | throw new Error('Edge from "' + edge.src.process + '" port "' + edge.src.port + '" is connected to an undefined target node "' + edge.tgt.process + '"'); 58 | } 59 | if (edge.src && !graph.processes[edge.src.process]) { 60 | throw new Error('Edge to "' + edge.tgt.process + '" port "' + edge.tgt.port + '" is connected to an undefined source node "' + edge.src.process + '"'); 61 | } 62 | }); 63 | } 64 | }; 65 | 66 | parser.addNode = function (nodeName, comp) { 67 | if (!nodes[nodeName]) { 68 | nodes[nodeName] = {} 69 | } 70 | if (!!comp.comp) { 71 | nodes[nodeName].component = comp.comp; 72 | } 73 | if (!!comp.meta) { 74 | var metadata = {}; 75 | for (var i = 0; i < comp.meta.length; i++) { 76 | var item = comp.meta[i].split('='); 77 | if (item.length === 1) { 78 | item = ['routes', item[0]]; 79 | } 80 | var key = item[0]; 81 | var value = item[1]; 82 | if (key==='x' || key==='y') { 83 | value = parseFloat(value); 84 | } 85 | metadata[key] = value; 86 | } 87 | nodes[nodeName].metadata=metadata; 88 | } 89 | 90 | } 91 | 92 | var anonymousIndexes = {}; 93 | var anonymousNodeNames = {}; 94 | parser.addAnonymousNode = function(comp, offset) { 95 | if (!anonymousNodeNames[offset]) { 96 | var componentName = comp.comp.replace(/[^a-zA-Z0-9]+/, "_"); 97 | anonymousIndexes[componentName] = (anonymousIndexes[componentName] || 0) + 1; 98 | anonymousNodeNames[offset] = "_" + componentName + "_" + anonymousIndexes[componentName]; 99 | this.addNode(anonymousNodeNames[offset], comp); 100 | } 101 | return anonymousNodeNames[offset]; 102 | } 103 | 104 | parser.getResult = function () { 105 | var result = { 106 | inports: parser.inports || {}, 107 | outports: parser.outports || {}, 108 | groups: parser.groups || [], 109 | processes: nodes || {}, 110 | connections: parser.processEdges() 111 | }; 112 | 113 | if (parser.properties) { 114 | result.properties = parser.properties; 115 | } 116 | result.caseSensitive = options.caseSensitive || false; 117 | 118 | var validateSchema = parser.validateSchema; // default 119 | if (typeof(options.validateSchema) !== 'undefined') { validateSchema = options.validateSchema; } // explicit option 120 | if (validateSchema) { 121 | if (typeof(tv4) === 'undefined') { 122 | var tv4 = require("tv4"); 123 | } 124 | var schema = require("../schema/graph.json"); 125 | var validation = tv4.validateMultiple(result, schema); 126 | if (!validation.valid) { 127 | throw new Error("fbp: Did not validate againt graph schema:\n" + JSON.stringify(validation.errors, null, 2)); 128 | } 129 | } 130 | 131 | if (typeof options.validateContents === 'undefined' || options.validateContents) { 132 | parser.validateContents(result); 133 | } 134 | 135 | return result; 136 | } 137 | 138 | var flatten = function (array, isShallow) { 139 | var index = -1, 140 | length = array ? array.length : 0, 141 | result = []; 142 | 143 | while (++index < length) { 144 | var value = array[index]; 145 | 146 | if (value instanceof Array) { 147 | Array.prototype.push.apply(result, isShallow ? value : flatten(value)); 148 | } 149 | else { 150 | result.push(value); 151 | } 152 | } 153 | return result; 154 | } 155 | 156 | parser.registerAnnotation = function (key, value) { 157 | if (!parser.properties) { 158 | parser.properties = {}; 159 | } 160 | 161 | if (key === 'runtime') { 162 | parser.properties.environment = {}; 163 | parser.properties.environment.type = value; 164 | return; 165 | } 166 | 167 | parser.properties[key] = value; 168 | }; 169 | 170 | parser.registerInports = function (node, port, pub) { 171 | if (!parser.inports) { 172 | parser.inports = {}; 173 | } 174 | 175 | if (!options.caseSensitive) { 176 | pub = pub.toLowerCase(); 177 | port = port.toLowerCase(); 178 | } 179 | 180 | parser.inports[pub] = {process:node, port:port}; 181 | } 182 | parser.registerOutports = function (node, port, pub) { 183 | if (!parser.outports) { 184 | parser.outports = {}; 185 | } 186 | 187 | if (!options.caseSensitive) { 188 | pub = pub.toLowerCase(); 189 | port = port.toLowerCase(); 190 | } 191 | 192 | parser.outports[pub] = {process:node, port:port}; 193 | } 194 | 195 | parser.registerEdges = function (edges) { 196 | if (Array.isArray(edges)) { 197 | edges.forEach(function (o, i) { 198 | parser.edges.push(o); 199 | }); 200 | } 201 | } 202 | 203 | parser.processEdges = function () { 204 | var flats, grouped; 205 | flats = flatten(parser.edges); 206 | grouped = []; 207 | var current = {}; 208 | for (var i = 1; i < flats.length; i += 1) { 209 | // skip over default ports at the beginning of lines (could also handle this in grammar) 210 | if (("src" in flats[i - 1] || "data" in flats[i - 1]) && "tgt" in flats[i]) { 211 | flats[i - 1].tgt = flats[i].tgt; 212 | grouped.push(flats[i - 1]); 213 | i++; 214 | } 215 | } 216 | return grouped; 217 | } 218 | 219 | function makeName(s) { 220 | return s[0] + s[1].join(""); 221 | } 222 | 223 | function makePort(process, port, defaultPort) { 224 | if (!options.caseSensitive) { 225 | defaultPort = defaultPort.toLowerCase() 226 | } 227 | var p = { 228 | process: process, 229 | port: port ? port.port : defaultPort 230 | }; 231 | if (port && port.index != null) { 232 | p.index = port.index; 233 | } 234 | return p; 235 | } 236 | 237 | function makeInPort(process, port) { 238 | return makePort(process, port, defaultInPort); 239 | } 240 | function makeOutPort(process, port) { 241 | return makePort(process, port, defaultOutPort); 242 | } 243 | } 244 | 245 | start 246 | = (line)* { return parser.getResult(); } 247 | 248 | line 249 | = _ "INPORT=" node:node "." port:portName ":" pub:portName _ LineTerminator? {return parser.registerInports(node,port,pub)} 250 | / _ "OUTPORT=" node:node "." port:portName ":" pub:portName _ LineTerminator? {return parser.registerOutports(node,port,pub)} 251 | / _ "DEFAULT_INPORT=" name:portName _ LineTerminator? { defaultInPort = name} 252 | / _ "DEFAULT_OUTPORT=" name:portName _ LineTerminator? { defaultOutPort = name} 253 | / annotation:annotation newline { return parser.registerAnnotation(annotation[0], annotation[1]); } 254 | / comment newline? 255 | / _ newline 256 | / _ edges:connection _ LineTerminator? {return parser.registerEdges(edges);} 257 | 258 | LineTerminator 259 | = _ ","? comment? newline? 260 | 261 | newline 262 | = [\n\r\u2028\u2029] 263 | 264 | comment 265 | = _ "#" (anychar)* 266 | 267 | connection 268 | = x:source _ "->" _ y:connection { return [x,y]; } 269 | / destination 270 | 271 | source 272 | = bridge 273 | / outport 274 | / iip 275 | 276 | destination 277 | = inport 278 | / bridge 279 | 280 | bridge 281 | = x:port__ proc:node y:__port { return [{"tgt":makeInPort(proc, x)},{"src":makeOutPort(proc, y)}]; } 282 | / x:(port__)? proc:nodeWithComponent y:(__port)? { return [{"tgt":makeInPort(proc, x)},{"src":makeOutPort(proc, y)}]; } 283 | 284 | outport 285 | = proc:node port:(__port)? { return {"src":makeOutPort(proc, port)} } 286 | 287 | inport 288 | = port:(port__)? proc:node { return {"tgt":makeInPort(proc, port)} } 289 | 290 | iip 291 | = "'" iip:(iipchar)* "'" { return {"data":iip.join("")} } 292 | / iip:JSON_text { return {"data":iip} } 293 | 294 | node 295 | = name:nodeNameAndComponent { return name} 296 | / name:nodeName { return name} 297 | / name:nodeComponent { return name} 298 | 299 | nodeName 300 | = name:([a-zA-Z_][a-zA-Z0-9_\-]*) { return makeName(name)} 301 | 302 | nodeNameAndComponent 303 | = name:nodeName comp:component { parser.addNode(name,comp); return name} 304 | 305 | nodeComponent 306 | = comp:component { return parser.addAnonymousNode(comp, location().start.offset) } 307 | 308 | nodeWithComponent 309 | = nodeNameAndComponent 310 | / nodeComponent 311 | 312 | component 313 | = "(" comp:([a-zA-Z/\-0-9_]*)? meta:compMeta? ")" { var o = {}; comp ? o.comp = comp.join("") : o.comp = ''; meta ? o.meta = meta.join("").split(',') : null; return o; } 314 | 315 | compMeta 316 | = ":" meta:[a-zA-Z/=_,0-9]+ {return meta} 317 | 318 | annotation 319 | = "#" __ "@" key:[a-zA-Z0-9\-_]+ __ value:[a-zA-Z0-9\-_ \.]+ { return [key.join(''), value.join('')]; } 320 | 321 | port 322 | = portname:portName portindex:(portIndex)? {return { port: options.caseSensitive? portname : portname.toLowerCase(), index: portindex != null ? portindex : undefined }} 323 | 324 | port__ 325 | = port:port __ { return port; } 326 | 327 | __port 328 | = __ port:port { return port; } 329 | 330 | portName 331 | = portname:([a-zA-Z_][a-zA-Z.0-9_]*) {return makeName(portname)} 332 | 333 | portIndex 334 | = "[" portindex:[0-9]+ "]" {return parseInt(portindex.join(''))} 335 | 336 | anychar 337 | = [^\n\r\u2028\u2029] 338 | 339 | iipchar 340 | = [\\]['] { return "'"; } 341 | / [^'] 342 | 343 | _ 344 | = (" "*)? 345 | 346 | __ 347 | = " "+ 348 | 349 | /* 350 | * JSON Grammar 351 | * ============ 352 | * 353 | * Based on the grammar from RFC 7159 [1]. 354 | * 355 | * Note that JSON is also specified in ECMA-262 [2], ECMA-404 [3], and on the 356 | * JSON website [4] (somewhat informally). The RFC seems the most authoritative 357 | * source, which is confirmed e.g. by [5]. 358 | * 359 | * [1] http://tools.ietf.org/html/rfc7159 360 | * [2] http://www.ecma-international.org/publications/standards/Ecma-262.htm 361 | * [3] http://www.ecma-international.org/publications/standards/Ecma-404.htm 362 | * [4] http://json.org/ 363 | * [5] https://www.tbray.org/ongoing/When/201x/2014/03/05/RFC7159-JSON 364 | */ 365 | 366 | /* ----- 2. JSON Grammar ----- */ 367 | 368 | JSON_text 369 | = ws value:value ws { return value; } 370 | 371 | begin_array = ws "[" ws 372 | begin_object = ws "{" ws 373 | end_array = ws "]" ws 374 | end_object = ws "}" ws 375 | name_separator = ws ":" ws 376 | value_separator = ws "," ws 377 | 378 | ws "whitespace" = [ \t\n\r]* 379 | 380 | /* ----- 3. Values ----- */ 381 | 382 | value 383 | = false 384 | / null 385 | / true 386 | / object 387 | / array 388 | / number 389 | / string 390 | 391 | false = "false" { return false; } 392 | null = "null" { return null; } 393 | true = "true" { return true; } 394 | 395 | /* ----- 4. Objects ----- */ 396 | 397 | object 398 | = begin_object 399 | members:( 400 | head:member 401 | tail:(value_separator m:member { return m; })* 402 | { 403 | var result = {}, i; 404 | 405 | result[head.name] = head.value; 406 | 407 | for (i = 0; i < tail.length; i++) { 408 | result[tail[i].name] = tail[i].value; 409 | } 410 | 411 | return result; 412 | } 413 | )? 414 | end_object 415 | { return members !== null ? members: {}; } 416 | 417 | member 418 | = name:string name_separator value:value { 419 | return { name: name, value: value }; 420 | } 421 | 422 | /* ----- 5. Arrays ----- */ 423 | 424 | array 425 | = begin_array 426 | values:( 427 | head:value 428 | tail:(value_separator v:value { return v; })* 429 | { return [head].concat(tail); } 430 | )? 431 | end_array 432 | { return values !== null ? values : []; } 433 | 434 | /* ----- 6. Numbers ----- */ 435 | 436 | number "number" 437 | = minus? int frac? exp? { return parseFloat(text()); } 438 | 439 | decimal_point = "." 440 | digit1_9 = [1-9] 441 | e = [eE] 442 | exp = e (minus / plus)? DIGIT+ 443 | frac = decimal_point DIGIT+ 444 | int = zero / (digit1_9 DIGIT*) 445 | minus = "-" 446 | plus = "+" 447 | zero = "0" 448 | 449 | /* ----- 7. Strings ----- */ 450 | 451 | string "string" 452 | = quotation_mark chars:char* quotation_mark { return chars.join(""); } 453 | 454 | char 455 | = unescaped 456 | / escape 457 | sequence:( 458 | '"' 459 | / "\\" 460 | / "/" 461 | / "b" { return "\b"; } 462 | / "f" { return "\f"; } 463 | / "n" { return "\n"; } 464 | / "r" { return "\r"; } 465 | / "t" { return "\t"; } 466 | / "u" digits:$(HEXDIG HEXDIG HEXDIG HEXDIG) { 467 | return String.fromCharCode(parseInt(digits, 16)); 468 | } 469 | ) 470 | { return sequence; } 471 | 472 | escape = "\\" 473 | quotation_mark = '"' 474 | unescaped = [^\0-\x1F\x22\x5C] 475 | 476 | /* ----- Core ABNF Rules ----- */ 477 | 478 | /* See RFC 4234, Appendix B (http://tools.ietf.org/html/rfc4627). */ 479 | DIGIT = [0-9] 480 | HEXDIG = [0-9a-f]i 481 | -------------------------------------------------------------------------------- /karma.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (config) => { 2 | const configuration = { 3 | basePath: process.cwd(), 4 | frameworks: [ 5 | 'mocha', 6 | 'chai', 7 | ], 8 | reporters: [ 9 | 'mocha', 10 | ], 11 | files: [ 12 | 'browser/fbp.js', 13 | 'spec/utils/inject.js', 14 | 'spec/*.js', 15 | ], 16 | browsers: ['ChromeHeadless'], 17 | logLevel: config.LOG_WARN, 18 | singleRun: true, 19 | }; 20 | 21 | config.set(configuration); 22 | }; 23 | -------------------------------------------------------------------------------- /lib/fbp.js: -------------------------------------------------------------------------------- 1 | module.exports = (function() { 2 | "use strict"; 3 | 4 | /* 5 | * Generated by PEG.js 0.9.0. 6 | * 7 | * http://pegjs.org/ 8 | */ 9 | 10 | function peg$subclass(child, parent) { 11 | function ctor() { this.constructor = child; } 12 | ctor.prototype = parent.prototype; 13 | child.prototype = new ctor(); 14 | } 15 | 16 | function peg$SyntaxError(message, expected, found, location) { 17 | this.message = message; 18 | this.expected = expected; 19 | this.found = found; 20 | this.location = location; 21 | this.name = "SyntaxError"; 22 | 23 | if (typeof Error.captureStackTrace === "function") { 24 | Error.captureStackTrace(this, peg$SyntaxError); 25 | } 26 | } 27 | 28 | peg$subclass(peg$SyntaxError, Error); 29 | 30 | function peg$parse(input) { 31 | var options = arguments.length > 1 ? arguments[1] : {}, 32 | parser = this, 33 | 34 | peg$FAILED = {}, 35 | 36 | peg$startRuleFunctions = { start: peg$parsestart }, 37 | peg$startRuleFunction = peg$parsestart, 38 | 39 | peg$c0 = function() { return parser.getResult(); }, 40 | peg$c1 = "INPORT=", 41 | peg$c2 = { type: "literal", value: "INPORT=", description: "\"INPORT=\"" }, 42 | peg$c3 = ".", 43 | peg$c4 = { type: "literal", value: ".", description: "\".\"" }, 44 | peg$c5 = ":", 45 | peg$c6 = { type: "literal", value: ":", description: "\":\"" }, 46 | peg$c7 = function(node, port, pub) {return parser.registerInports(node,port,pub)}, 47 | peg$c8 = "OUTPORT=", 48 | peg$c9 = { type: "literal", value: "OUTPORT=", description: "\"OUTPORT=\"" }, 49 | peg$c10 = function(node, port, pub) {return parser.registerOutports(node,port,pub)}, 50 | peg$c11 = "DEFAULT_INPORT=", 51 | peg$c12 = { type: "literal", value: "DEFAULT_INPORT=", description: "\"DEFAULT_INPORT=\"" }, 52 | peg$c13 = function(name) { defaultInPort = name}, 53 | peg$c14 = "DEFAULT_OUTPORT=", 54 | peg$c15 = { type: "literal", value: "DEFAULT_OUTPORT=", description: "\"DEFAULT_OUTPORT=\"" }, 55 | peg$c16 = function(name) { defaultOutPort = name}, 56 | peg$c17 = function(annotation) { return parser.registerAnnotation(annotation[0], annotation[1]); }, 57 | peg$c18 = function(edges) {return parser.registerEdges(edges);}, 58 | peg$c19 = ",", 59 | peg$c20 = { type: "literal", value: ",", description: "\",\"" }, 60 | peg$c21 = /^[\n\r\u2028\u2029]/, 61 | peg$c22 = { type: "class", value: "[\\n\\r\\u2028\\u2029]", description: "[\\n\\r\\u2028\\u2029]" }, 62 | peg$c23 = "#", 63 | peg$c24 = { type: "literal", value: "#", description: "\"#\"" }, 64 | peg$c25 = "->", 65 | peg$c26 = { type: "literal", value: "->", description: "\"->\"" }, 66 | peg$c27 = function(x, y) { return [x,y]; }, 67 | peg$c28 = function(x, proc, y) { return [{"tgt":makeInPort(proc, x)},{"src":makeOutPort(proc, y)}]; }, 68 | peg$c29 = function(proc, port) { return {"src":makeOutPort(proc, port)} }, 69 | peg$c30 = function(port, proc) { return {"tgt":makeInPort(proc, port)} }, 70 | peg$c31 = "'", 71 | peg$c32 = { type: "literal", value: "'", description: "\"'\"" }, 72 | peg$c33 = function(iip) { return {"data":iip.join("")} }, 73 | peg$c34 = function(iip) { return {"data":iip} }, 74 | peg$c35 = function(name) { return name}, 75 | peg$c36 = /^[a-zA-Z_]/, 76 | peg$c37 = { type: "class", value: "[a-zA-Z_]", description: "[a-zA-Z_]" }, 77 | peg$c38 = /^[a-zA-Z0-9_\-]/, 78 | peg$c39 = { type: "class", value: "[a-zA-Z0-9_\\-]", description: "[a-zA-Z0-9_\\-]" }, 79 | peg$c40 = function(name) { return makeName(name)}, 80 | peg$c41 = function(name, comp) { parser.addNode(name,comp); return name}, 81 | peg$c42 = function(comp) { return parser.addAnonymousNode(comp, location().start.offset) }, 82 | peg$c43 = "(", 83 | peg$c44 = { type: "literal", value: "(", description: "\"(\"" }, 84 | peg$c45 = /^[a-zA-Z\/\-0-9_]/, 85 | peg$c46 = { type: "class", value: "[a-zA-Z/\\-0-9_]", description: "[a-zA-Z/\\-0-9_]" }, 86 | peg$c47 = ")", 87 | peg$c48 = { type: "literal", value: ")", description: "\")\"" }, 88 | peg$c49 = function(comp, meta) { var o = {}; comp ? o.comp = comp.join("") : o.comp = ''; meta ? o.meta = meta.join("").split(',') : null; return o; }, 89 | peg$c50 = /^[a-zA-Z\/=_,0-9]/, 90 | peg$c51 = { type: "class", value: "[a-zA-Z/=_,0-9]", description: "[a-zA-Z/=_,0-9]" }, 91 | peg$c52 = function(meta) {return meta}, 92 | peg$c53 = "@", 93 | peg$c54 = { type: "literal", value: "@", description: "\"@\"" }, 94 | peg$c55 = /^[a-zA-Z0-9\-_]/, 95 | peg$c56 = { type: "class", value: "[a-zA-Z0-9\\-_]", description: "[a-zA-Z0-9\\-_]" }, 96 | peg$c57 = /^[a-zA-Z0-9\-_ .]/, 97 | peg$c58 = { type: "class", value: "[a-zA-Z0-9\\-_ \\.]", description: "[a-zA-Z0-9\\-_ \\.]" }, 98 | peg$c59 = function(key, value) { return [key.join(''), value.join('')]; }, 99 | peg$c60 = function(portname, portindex) {return { port: options.caseSensitive? portname : portname.toLowerCase(), index: portindex != null ? portindex : undefined }}, 100 | peg$c61 = function(port) { return port; }, 101 | peg$c62 = /^[a-zA-Z.0-9_]/, 102 | peg$c63 = { type: "class", value: "[a-zA-Z.0-9_]", description: "[a-zA-Z.0-9_]" }, 103 | peg$c64 = function(portname) {return makeName(portname)}, 104 | peg$c65 = "[", 105 | peg$c66 = { type: "literal", value: "[", description: "\"[\"" }, 106 | peg$c67 = /^[0-9]/, 107 | peg$c68 = { type: "class", value: "[0-9]", description: "[0-9]" }, 108 | peg$c69 = "]", 109 | peg$c70 = { type: "literal", value: "]", description: "\"]\"" }, 110 | peg$c71 = function(portindex) {return parseInt(portindex.join(''))}, 111 | peg$c72 = /^[^\n\r\u2028\u2029]/, 112 | peg$c73 = { type: "class", value: "[^\\n\\r\\u2028\\u2029]", description: "[^\\n\\r\\u2028\\u2029]" }, 113 | peg$c74 = /^[\\]/, 114 | peg$c75 = { type: "class", value: "[\\\\]", description: "[\\\\]" }, 115 | peg$c76 = /^[']/, 116 | peg$c77 = { type: "class", value: "[']", description: "[']" }, 117 | peg$c78 = function() { return "'"; }, 118 | peg$c79 = /^[^']/, 119 | peg$c80 = { type: "class", value: "[^']", description: "[^']" }, 120 | peg$c81 = " ", 121 | peg$c82 = { type: "literal", value: " ", description: "\" \"" }, 122 | peg$c83 = function(value) { return value; }, 123 | peg$c84 = "{", 124 | peg$c85 = { type: "literal", value: "{", description: "\"{\"" }, 125 | peg$c86 = "}", 126 | peg$c87 = { type: "literal", value: "}", description: "\"}\"" }, 127 | peg$c88 = { type: "other", description: "whitespace" }, 128 | peg$c89 = /^[ \t\n\r]/, 129 | peg$c90 = { type: "class", value: "[ \\t\\n\\r]", description: "[ \\t\\n\\r]" }, 130 | peg$c91 = "false", 131 | peg$c92 = { type: "literal", value: "false", description: "\"false\"" }, 132 | peg$c93 = function() { return false; }, 133 | peg$c94 = "null", 134 | peg$c95 = { type: "literal", value: "null", description: "\"null\"" }, 135 | peg$c96 = function() { return null; }, 136 | peg$c97 = "true", 137 | peg$c98 = { type: "literal", value: "true", description: "\"true\"" }, 138 | peg$c99 = function() { return true; }, 139 | peg$c100 = function(head, m) { return m; }, 140 | peg$c101 = function(head, tail) { 141 | var result = {}, i; 142 | 143 | result[head.name] = head.value; 144 | 145 | for (i = 0; i < tail.length; i++) { 146 | result[tail[i].name] = tail[i].value; 147 | } 148 | 149 | return result; 150 | }, 151 | peg$c102 = function(members) { return members !== null ? members: {}; }, 152 | peg$c103 = function(name, value) { 153 | return { name: name, value: value }; 154 | }, 155 | peg$c104 = function(head, v) { return v; }, 156 | peg$c105 = function(head, tail) { return [head].concat(tail); }, 157 | peg$c106 = function(values) { return values !== null ? values : []; }, 158 | peg$c107 = { type: "other", description: "number" }, 159 | peg$c108 = function() { return parseFloat(text()); }, 160 | peg$c109 = /^[1-9]/, 161 | peg$c110 = { type: "class", value: "[1-9]", description: "[1-9]" }, 162 | peg$c111 = /^[eE]/, 163 | peg$c112 = { type: "class", value: "[eE]", description: "[eE]" }, 164 | peg$c113 = "-", 165 | peg$c114 = { type: "literal", value: "-", description: "\"-\"" }, 166 | peg$c115 = "+", 167 | peg$c116 = { type: "literal", value: "+", description: "\"+\"" }, 168 | peg$c117 = "0", 169 | peg$c118 = { type: "literal", value: "0", description: "\"0\"" }, 170 | peg$c119 = { type: "other", description: "string" }, 171 | peg$c120 = function(chars) { return chars.join(""); }, 172 | peg$c121 = "\"", 173 | peg$c122 = { type: "literal", value: "\"", description: "\"\\\"\"" }, 174 | peg$c123 = "\\", 175 | peg$c124 = { type: "literal", value: "\\", description: "\"\\\\\"" }, 176 | peg$c125 = "/", 177 | peg$c126 = { type: "literal", value: "/", description: "\"/\"" }, 178 | peg$c127 = "b", 179 | peg$c128 = { type: "literal", value: "b", description: "\"b\"" }, 180 | peg$c129 = function() { return "\b"; }, 181 | peg$c130 = "f", 182 | peg$c131 = { type: "literal", value: "f", description: "\"f\"" }, 183 | peg$c132 = function() { return "\f"; }, 184 | peg$c133 = "n", 185 | peg$c134 = { type: "literal", value: "n", description: "\"n\"" }, 186 | peg$c135 = function() { return "\n"; }, 187 | peg$c136 = "r", 188 | peg$c137 = { type: "literal", value: "r", description: "\"r\"" }, 189 | peg$c138 = function() { return "\r"; }, 190 | peg$c139 = "t", 191 | peg$c140 = { type: "literal", value: "t", description: "\"t\"" }, 192 | peg$c141 = function() { return "\t"; }, 193 | peg$c142 = "u", 194 | peg$c143 = { type: "literal", value: "u", description: "\"u\"" }, 195 | peg$c144 = function(digits) { 196 | return String.fromCharCode(parseInt(digits, 16)); 197 | }, 198 | peg$c145 = function(sequence) { return sequence; }, 199 | peg$c146 = /^[^\0-\x1F"\\]/, 200 | peg$c147 = { type: "class", value: "[^\\0-\\x1F\\x22\\x5C]", description: "[^\\0-\\x1F\\x22\\x5C]" }, 201 | peg$c148 = /^[0-9a-f]/i, 202 | peg$c149 = { type: "class", value: "[0-9a-f]i", description: "[0-9a-f]i" }, 203 | 204 | peg$currPos = 0, 205 | peg$savedPos = 0, 206 | peg$posDetailsCache = [{ line: 1, column: 1, seenCR: false }], 207 | peg$maxFailPos = 0, 208 | peg$maxFailExpected = [], 209 | peg$silentFails = 0, 210 | 211 | peg$result; 212 | 213 | if ("startRule" in options) { 214 | if (!(options.startRule in peg$startRuleFunctions)) { 215 | throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); 216 | } 217 | 218 | peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; 219 | } 220 | 221 | function text() { 222 | return input.substring(peg$savedPos, peg$currPos); 223 | } 224 | 225 | function location() { 226 | return peg$computeLocation(peg$savedPos, peg$currPos); 227 | } 228 | 229 | function expected(description) { 230 | throw peg$buildException( 231 | null, 232 | [{ type: "other", description: description }], 233 | input.substring(peg$savedPos, peg$currPos), 234 | peg$computeLocation(peg$savedPos, peg$currPos) 235 | ); 236 | } 237 | 238 | function error(message) { 239 | throw peg$buildException( 240 | message, 241 | null, 242 | input.substring(peg$savedPos, peg$currPos), 243 | peg$computeLocation(peg$savedPos, peg$currPos) 244 | ); 245 | } 246 | 247 | function peg$computePosDetails(pos) { 248 | var details = peg$posDetailsCache[pos], 249 | p, ch; 250 | 251 | if (details) { 252 | return details; 253 | } else { 254 | p = pos - 1; 255 | while (!peg$posDetailsCache[p]) { 256 | p--; 257 | } 258 | 259 | details = peg$posDetailsCache[p]; 260 | details = { 261 | line: details.line, 262 | column: details.column, 263 | seenCR: details.seenCR 264 | }; 265 | 266 | while (p < pos) { 267 | ch = input.charAt(p); 268 | if (ch === "\n") { 269 | if (!details.seenCR) { details.line++; } 270 | details.column = 1; 271 | details.seenCR = false; 272 | } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") { 273 | details.line++; 274 | details.column = 1; 275 | details.seenCR = true; 276 | } else { 277 | details.column++; 278 | details.seenCR = false; 279 | } 280 | 281 | p++; 282 | } 283 | 284 | peg$posDetailsCache[pos] = details; 285 | return details; 286 | } 287 | } 288 | 289 | function peg$computeLocation(startPos, endPos) { 290 | var startPosDetails = peg$computePosDetails(startPos), 291 | endPosDetails = peg$computePosDetails(endPos); 292 | 293 | return { 294 | start: { 295 | offset: startPos, 296 | line: startPosDetails.line, 297 | column: startPosDetails.column 298 | }, 299 | end: { 300 | offset: endPos, 301 | line: endPosDetails.line, 302 | column: endPosDetails.column 303 | } 304 | }; 305 | } 306 | 307 | function peg$fail(expected) { 308 | if (peg$currPos < peg$maxFailPos) { return; } 309 | 310 | if (peg$currPos > peg$maxFailPos) { 311 | peg$maxFailPos = peg$currPos; 312 | peg$maxFailExpected = []; 313 | } 314 | 315 | peg$maxFailExpected.push(expected); 316 | } 317 | 318 | function peg$buildException(message, expected, found, location) { 319 | function cleanupExpected(expected) { 320 | var i = 1; 321 | 322 | expected.sort(function(a, b) { 323 | if (a.description < b.description) { 324 | return -1; 325 | } else if (a.description > b.description) { 326 | return 1; 327 | } else { 328 | return 0; 329 | } 330 | }); 331 | 332 | while (i < expected.length) { 333 | if (expected[i - 1] === expected[i]) { 334 | expected.splice(i, 1); 335 | } else { 336 | i++; 337 | } 338 | } 339 | } 340 | 341 | function buildMessage(expected, found) { 342 | function stringEscape(s) { 343 | function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); } 344 | 345 | return s 346 | .replace(/\\/g, '\\\\') 347 | .replace(/"/g, '\\"') 348 | .replace(/\x08/g, '\\b') 349 | .replace(/\t/g, '\\t') 350 | .replace(/\n/g, '\\n') 351 | .replace(/\f/g, '\\f') 352 | .replace(/\r/g, '\\r') 353 | .replace(/[\x00-\x07\x0B\x0E\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) 354 | .replace(/[\x10-\x1F\x80-\xFF]/g, function(ch) { return '\\x' + hex(ch); }) 355 | .replace(/[\u0100-\u0FFF]/g, function(ch) { return '\\u0' + hex(ch); }) 356 | .replace(/[\u1000-\uFFFF]/g, function(ch) { return '\\u' + hex(ch); }); 357 | } 358 | 359 | var expectedDescs = new Array(expected.length), 360 | expectedDesc, foundDesc, i; 361 | 362 | for (i = 0; i < expected.length; i++) { 363 | expectedDescs[i] = expected[i].description; 364 | } 365 | 366 | expectedDesc = expected.length > 1 367 | ? expectedDescs.slice(0, -1).join(", ") 368 | + " or " 369 | + expectedDescs[expected.length - 1] 370 | : expectedDescs[0]; 371 | 372 | foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input"; 373 | 374 | return "Expected " + expectedDesc + " but " + foundDesc + " found."; 375 | } 376 | 377 | if (expected !== null) { 378 | cleanupExpected(expected); 379 | } 380 | 381 | return new peg$SyntaxError( 382 | message !== null ? message : buildMessage(expected, found), 383 | expected, 384 | found, 385 | location 386 | ); 387 | } 388 | 389 | function peg$parsestart() { 390 | var s0, s1, s2; 391 | 392 | s0 = peg$currPos; 393 | s1 = []; 394 | s2 = peg$parseline(); 395 | while (s2 !== peg$FAILED) { 396 | s1.push(s2); 397 | s2 = peg$parseline(); 398 | } 399 | if (s1 !== peg$FAILED) { 400 | peg$savedPos = s0; 401 | s1 = peg$c0(); 402 | } 403 | s0 = s1; 404 | 405 | return s0; 406 | } 407 | 408 | function peg$parseline() { 409 | var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; 410 | 411 | s0 = peg$currPos; 412 | s1 = peg$parse_(); 413 | if (s1 !== peg$FAILED) { 414 | if (input.substr(peg$currPos, 7) === peg$c1) { 415 | s2 = peg$c1; 416 | peg$currPos += 7; 417 | } else { 418 | s2 = peg$FAILED; 419 | if (peg$silentFails === 0) { peg$fail(peg$c2); } 420 | } 421 | if (s2 !== peg$FAILED) { 422 | s3 = peg$parsenode(); 423 | if (s3 !== peg$FAILED) { 424 | if (input.charCodeAt(peg$currPos) === 46) { 425 | s4 = peg$c3; 426 | peg$currPos++; 427 | } else { 428 | s4 = peg$FAILED; 429 | if (peg$silentFails === 0) { peg$fail(peg$c4); } 430 | } 431 | if (s4 !== peg$FAILED) { 432 | s5 = peg$parseportName(); 433 | if (s5 !== peg$FAILED) { 434 | if (input.charCodeAt(peg$currPos) === 58) { 435 | s6 = peg$c5; 436 | peg$currPos++; 437 | } else { 438 | s6 = peg$FAILED; 439 | if (peg$silentFails === 0) { peg$fail(peg$c6); } 440 | } 441 | if (s6 !== peg$FAILED) { 442 | s7 = peg$parseportName(); 443 | if (s7 !== peg$FAILED) { 444 | s8 = peg$parse_(); 445 | if (s8 !== peg$FAILED) { 446 | s9 = peg$parseLineTerminator(); 447 | if (s9 === peg$FAILED) { 448 | s9 = null; 449 | } 450 | if (s9 !== peg$FAILED) { 451 | peg$savedPos = s0; 452 | s1 = peg$c7(s3, s5, s7); 453 | s0 = s1; 454 | } else { 455 | peg$currPos = s0; 456 | s0 = peg$FAILED; 457 | } 458 | } else { 459 | peg$currPos = s0; 460 | s0 = peg$FAILED; 461 | } 462 | } else { 463 | peg$currPos = s0; 464 | s0 = peg$FAILED; 465 | } 466 | } else { 467 | peg$currPos = s0; 468 | s0 = peg$FAILED; 469 | } 470 | } else { 471 | peg$currPos = s0; 472 | s0 = peg$FAILED; 473 | } 474 | } else { 475 | peg$currPos = s0; 476 | s0 = peg$FAILED; 477 | } 478 | } else { 479 | peg$currPos = s0; 480 | s0 = peg$FAILED; 481 | } 482 | } else { 483 | peg$currPos = s0; 484 | s0 = peg$FAILED; 485 | } 486 | } else { 487 | peg$currPos = s0; 488 | s0 = peg$FAILED; 489 | } 490 | if (s0 === peg$FAILED) { 491 | s0 = peg$currPos; 492 | s1 = peg$parse_(); 493 | if (s1 !== peg$FAILED) { 494 | if (input.substr(peg$currPos, 8) === peg$c8) { 495 | s2 = peg$c8; 496 | peg$currPos += 8; 497 | } else { 498 | s2 = peg$FAILED; 499 | if (peg$silentFails === 0) { peg$fail(peg$c9); } 500 | } 501 | if (s2 !== peg$FAILED) { 502 | s3 = peg$parsenode(); 503 | if (s3 !== peg$FAILED) { 504 | if (input.charCodeAt(peg$currPos) === 46) { 505 | s4 = peg$c3; 506 | peg$currPos++; 507 | } else { 508 | s4 = peg$FAILED; 509 | if (peg$silentFails === 0) { peg$fail(peg$c4); } 510 | } 511 | if (s4 !== peg$FAILED) { 512 | s5 = peg$parseportName(); 513 | if (s5 !== peg$FAILED) { 514 | if (input.charCodeAt(peg$currPos) === 58) { 515 | s6 = peg$c5; 516 | peg$currPos++; 517 | } else { 518 | s6 = peg$FAILED; 519 | if (peg$silentFails === 0) { peg$fail(peg$c6); } 520 | } 521 | if (s6 !== peg$FAILED) { 522 | s7 = peg$parseportName(); 523 | if (s7 !== peg$FAILED) { 524 | s8 = peg$parse_(); 525 | if (s8 !== peg$FAILED) { 526 | s9 = peg$parseLineTerminator(); 527 | if (s9 === peg$FAILED) { 528 | s9 = null; 529 | } 530 | if (s9 !== peg$FAILED) { 531 | peg$savedPos = s0; 532 | s1 = peg$c10(s3, s5, s7); 533 | s0 = s1; 534 | } else { 535 | peg$currPos = s0; 536 | s0 = peg$FAILED; 537 | } 538 | } else { 539 | peg$currPos = s0; 540 | s0 = peg$FAILED; 541 | } 542 | } else { 543 | peg$currPos = s0; 544 | s0 = peg$FAILED; 545 | } 546 | } else { 547 | peg$currPos = s0; 548 | s0 = peg$FAILED; 549 | } 550 | } else { 551 | peg$currPos = s0; 552 | s0 = peg$FAILED; 553 | } 554 | } else { 555 | peg$currPos = s0; 556 | s0 = peg$FAILED; 557 | } 558 | } else { 559 | peg$currPos = s0; 560 | s0 = peg$FAILED; 561 | } 562 | } else { 563 | peg$currPos = s0; 564 | s0 = peg$FAILED; 565 | } 566 | } else { 567 | peg$currPos = s0; 568 | s0 = peg$FAILED; 569 | } 570 | if (s0 === peg$FAILED) { 571 | s0 = peg$currPos; 572 | s1 = peg$parse_(); 573 | if (s1 !== peg$FAILED) { 574 | if (input.substr(peg$currPos, 15) === peg$c11) { 575 | s2 = peg$c11; 576 | peg$currPos += 15; 577 | } else { 578 | s2 = peg$FAILED; 579 | if (peg$silentFails === 0) { peg$fail(peg$c12); } 580 | } 581 | if (s2 !== peg$FAILED) { 582 | s3 = peg$parseportName(); 583 | if (s3 !== peg$FAILED) { 584 | s4 = peg$parse_(); 585 | if (s4 !== peg$FAILED) { 586 | s5 = peg$parseLineTerminator(); 587 | if (s5 === peg$FAILED) { 588 | s5 = null; 589 | } 590 | if (s5 !== peg$FAILED) { 591 | peg$savedPos = s0; 592 | s1 = peg$c13(s3); 593 | s0 = s1; 594 | } else { 595 | peg$currPos = s0; 596 | s0 = peg$FAILED; 597 | } 598 | } else { 599 | peg$currPos = s0; 600 | s0 = peg$FAILED; 601 | } 602 | } else { 603 | peg$currPos = s0; 604 | s0 = peg$FAILED; 605 | } 606 | } else { 607 | peg$currPos = s0; 608 | s0 = peg$FAILED; 609 | } 610 | } else { 611 | peg$currPos = s0; 612 | s0 = peg$FAILED; 613 | } 614 | if (s0 === peg$FAILED) { 615 | s0 = peg$currPos; 616 | s1 = peg$parse_(); 617 | if (s1 !== peg$FAILED) { 618 | if (input.substr(peg$currPos, 16) === peg$c14) { 619 | s2 = peg$c14; 620 | peg$currPos += 16; 621 | } else { 622 | s2 = peg$FAILED; 623 | if (peg$silentFails === 0) { peg$fail(peg$c15); } 624 | } 625 | if (s2 !== peg$FAILED) { 626 | s3 = peg$parseportName(); 627 | if (s3 !== peg$FAILED) { 628 | s4 = peg$parse_(); 629 | if (s4 !== peg$FAILED) { 630 | s5 = peg$parseLineTerminator(); 631 | if (s5 === peg$FAILED) { 632 | s5 = null; 633 | } 634 | if (s5 !== peg$FAILED) { 635 | peg$savedPos = s0; 636 | s1 = peg$c16(s3); 637 | s0 = s1; 638 | } else { 639 | peg$currPos = s0; 640 | s0 = peg$FAILED; 641 | } 642 | } else { 643 | peg$currPos = s0; 644 | s0 = peg$FAILED; 645 | } 646 | } else { 647 | peg$currPos = s0; 648 | s0 = peg$FAILED; 649 | } 650 | } else { 651 | peg$currPos = s0; 652 | s0 = peg$FAILED; 653 | } 654 | } else { 655 | peg$currPos = s0; 656 | s0 = peg$FAILED; 657 | } 658 | if (s0 === peg$FAILED) { 659 | s0 = peg$currPos; 660 | s1 = peg$parseannotation(); 661 | if (s1 !== peg$FAILED) { 662 | s2 = peg$parsenewline(); 663 | if (s2 !== peg$FAILED) { 664 | peg$savedPos = s0; 665 | s1 = peg$c17(s1); 666 | s0 = s1; 667 | } else { 668 | peg$currPos = s0; 669 | s0 = peg$FAILED; 670 | } 671 | } else { 672 | peg$currPos = s0; 673 | s0 = peg$FAILED; 674 | } 675 | if (s0 === peg$FAILED) { 676 | s0 = peg$currPos; 677 | s1 = peg$parsecomment(); 678 | if (s1 !== peg$FAILED) { 679 | s2 = peg$parsenewline(); 680 | if (s2 === peg$FAILED) { 681 | s2 = null; 682 | } 683 | if (s2 !== peg$FAILED) { 684 | s1 = [s1, s2]; 685 | s0 = s1; 686 | } else { 687 | peg$currPos = s0; 688 | s0 = peg$FAILED; 689 | } 690 | } else { 691 | peg$currPos = s0; 692 | s0 = peg$FAILED; 693 | } 694 | if (s0 === peg$FAILED) { 695 | s0 = peg$currPos; 696 | s1 = peg$parse_(); 697 | if (s1 !== peg$FAILED) { 698 | s2 = peg$parsenewline(); 699 | if (s2 !== peg$FAILED) { 700 | s1 = [s1, s2]; 701 | s0 = s1; 702 | } else { 703 | peg$currPos = s0; 704 | s0 = peg$FAILED; 705 | } 706 | } else { 707 | peg$currPos = s0; 708 | s0 = peg$FAILED; 709 | } 710 | if (s0 === peg$FAILED) { 711 | s0 = peg$currPos; 712 | s1 = peg$parse_(); 713 | if (s1 !== peg$FAILED) { 714 | s2 = peg$parseconnection(); 715 | if (s2 !== peg$FAILED) { 716 | s3 = peg$parse_(); 717 | if (s3 !== peg$FAILED) { 718 | s4 = peg$parseLineTerminator(); 719 | if (s4 === peg$FAILED) { 720 | s4 = null; 721 | } 722 | if (s4 !== peg$FAILED) { 723 | peg$savedPos = s0; 724 | s1 = peg$c18(s2); 725 | s0 = s1; 726 | } else { 727 | peg$currPos = s0; 728 | s0 = peg$FAILED; 729 | } 730 | } else { 731 | peg$currPos = s0; 732 | s0 = peg$FAILED; 733 | } 734 | } else { 735 | peg$currPos = s0; 736 | s0 = peg$FAILED; 737 | } 738 | } else { 739 | peg$currPos = s0; 740 | s0 = peg$FAILED; 741 | } 742 | } 743 | } 744 | } 745 | } 746 | } 747 | } 748 | } 749 | 750 | return s0; 751 | } 752 | 753 | function peg$parseLineTerminator() { 754 | var s0, s1, s2, s3, s4; 755 | 756 | s0 = peg$currPos; 757 | s1 = peg$parse_(); 758 | if (s1 !== peg$FAILED) { 759 | if (input.charCodeAt(peg$currPos) === 44) { 760 | s2 = peg$c19; 761 | peg$currPos++; 762 | } else { 763 | s2 = peg$FAILED; 764 | if (peg$silentFails === 0) { peg$fail(peg$c20); } 765 | } 766 | if (s2 === peg$FAILED) { 767 | s2 = null; 768 | } 769 | if (s2 !== peg$FAILED) { 770 | s3 = peg$parsecomment(); 771 | if (s3 === peg$FAILED) { 772 | s3 = null; 773 | } 774 | if (s3 !== peg$FAILED) { 775 | s4 = peg$parsenewline(); 776 | if (s4 === peg$FAILED) { 777 | s4 = null; 778 | } 779 | if (s4 !== peg$FAILED) { 780 | s1 = [s1, s2, s3, s4]; 781 | s0 = s1; 782 | } else { 783 | peg$currPos = s0; 784 | s0 = peg$FAILED; 785 | } 786 | } else { 787 | peg$currPos = s0; 788 | s0 = peg$FAILED; 789 | } 790 | } else { 791 | peg$currPos = s0; 792 | s0 = peg$FAILED; 793 | } 794 | } else { 795 | peg$currPos = s0; 796 | s0 = peg$FAILED; 797 | } 798 | 799 | return s0; 800 | } 801 | 802 | function peg$parsenewline() { 803 | var s0; 804 | 805 | if (peg$c21.test(input.charAt(peg$currPos))) { 806 | s0 = input.charAt(peg$currPos); 807 | peg$currPos++; 808 | } else { 809 | s0 = peg$FAILED; 810 | if (peg$silentFails === 0) { peg$fail(peg$c22); } 811 | } 812 | 813 | return s0; 814 | } 815 | 816 | function peg$parsecomment() { 817 | var s0, s1, s2, s3, s4; 818 | 819 | s0 = peg$currPos; 820 | s1 = peg$parse_(); 821 | if (s1 !== peg$FAILED) { 822 | if (input.charCodeAt(peg$currPos) === 35) { 823 | s2 = peg$c23; 824 | peg$currPos++; 825 | } else { 826 | s2 = peg$FAILED; 827 | if (peg$silentFails === 0) { peg$fail(peg$c24); } 828 | } 829 | if (s2 !== peg$FAILED) { 830 | s3 = []; 831 | s4 = peg$parseanychar(); 832 | while (s4 !== peg$FAILED) { 833 | s3.push(s4); 834 | s4 = peg$parseanychar(); 835 | } 836 | if (s3 !== peg$FAILED) { 837 | s1 = [s1, s2, s3]; 838 | s0 = s1; 839 | } else { 840 | peg$currPos = s0; 841 | s0 = peg$FAILED; 842 | } 843 | } else { 844 | peg$currPos = s0; 845 | s0 = peg$FAILED; 846 | } 847 | } else { 848 | peg$currPos = s0; 849 | s0 = peg$FAILED; 850 | } 851 | 852 | return s0; 853 | } 854 | 855 | function peg$parseconnection() { 856 | var s0, s1, s2, s3, s4, s5; 857 | 858 | s0 = peg$currPos; 859 | s1 = peg$parsesource(); 860 | if (s1 !== peg$FAILED) { 861 | s2 = peg$parse_(); 862 | if (s2 !== peg$FAILED) { 863 | if (input.substr(peg$currPos, 2) === peg$c25) { 864 | s3 = peg$c25; 865 | peg$currPos += 2; 866 | } else { 867 | s3 = peg$FAILED; 868 | if (peg$silentFails === 0) { peg$fail(peg$c26); } 869 | } 870 | if (s3 !== peg$FAILED) { 871 | s4 = peg$parse_(); 872 | if (s4 !== peg$FAILED) { 873 | s5 = peg$parseconnection(); 874 | if (s5 !== peg$FAILED) { 875 | peg$savedPos = s0; 876 | s1 = peg$c27(s1, s5); 877 | s0 = s1; 878 | } else { 879 | peg$currPos = s0; 880 | s0 = peg$FAILED; 881 | } 882 | } else { 883 | peg$currPos = s0; 884 | s0 = peg$FAILED; 885 | } 886 | } else { 887 | peg$currPos = s0; 888 | s0 = peg$FAILED; 889 | } 890 | } else { 891 | peg$currPos = s0; 892 | s0 = peg$FAILED; 893 | } 894 | } else { 895 | peg$currPos = s0; 896 | s0 = peg$FAILED; 897 | } 898 | if (s0 === peg$FAILED) { 899 | s0 = peg$parsedestination(); 900 | } 901 | 902 | return s0; 903 | } 904 | 905 | function peg$parsesource() { 906 | var s0; 907 | 908 | s0 = peg$parsebridge(); 909 | if (s0 === peg$FAILED) { 910 | s0 = peg$parseoutport(); 911 | if (s0 === peg$FAILED) { 912 | s0 = peg$parseiip(); 913 | } 914 | } 915 | 916 | return s0; 917 | } 918 | 919 | function peg$parsedestination() { 920 | var s0; 921 | 922 | s0 = peg$parseinport(); 923 | if (s0 === peg$FAILED) { 924 | s0 = peg$parsebridge(); 925 | } 926 | 927 | return s0; 928 | } 929 | 930 | function peg$parsebridge() { 931 | var s0, s1, s2, s3; 932 | 933 | s0 = peg$currPos; 934 | s1 = peg$parseport__(); 935 | if (s1 !== peg$FAILED) { 936 | s2 = peg$parsenode(); 937 | if (s2 !== peg$FAILED) { 938 | s3 = peg$parse__port(); 939 | if (s3 !== peg$FAILED) { 940 | peg$savedPos = s0; 941 | s1 = peg$c28(s1, s2, s3); 942 | s0 = s1; 943 | } else { 944 | peg$currPos = s0; 945 | s0 = peg$FAILED; 946 | } 947 | } else { 948 | peg$currPos = s0; 949 | s0 = peg$FAILED; 950 | } 951 | } else { 952 | peg$currPos = s0; 953 | s0 = peg$FAILED; 954 | } 955 | if (s0 === peg$FAILED) { 956 | s0 = peg$currPos; 957 | s1 = peg$parseport__(); 958 | if (s1 === peg$FAILED) { 959 | s1 = null; 960 | } 961 | if (s1 !== peg$FAILED) { 962 | s2 = peg$parsenodeWithComponent(); 963 | if (s2 !== peg$FAILED) { 964 | s3 = peg$parse__port(); 965 | if (s3 === peg$FAILED) { 966 | s3 = null; 967 | } 968 | if (s3 !== peg$FAILED) { 969 | peg$savedPos = s0; 970 | s1 = peg$c28(s1, s2, s3); 971 | s0 = s1; 972 | } else { 973 | peg$currPos = s0; 974 | s0 = peg$FAILED; 975 | } 976 | } else { 977 | peg$currPos = s0; 978 | s0 = peg$FAILED; 979 | } 980 | } else { 981 | peg$currPos = s0; 982 | s0 = peg$FAILED; 983 | } 984 | } 985 | 986 | return s0; 987 | } 988 | 989 | function peg$parseoutport() { 990 | var s0, s1, s2; 991 | 992 | s0 = peg$currPos; 993 | s1 = peg$parsenode(); 994 | if (s1 !== peg$FAILED) { 995 | s2 = peg$parse__port(); 996 | if (s2 === peg$FAILED) { 997 | s2 = null; 998 | } 999 | if (s2 !== peg$FAILED) { 1000 | peg$savedPos = s0; 1001 | s1 = peg$c29(s1, s2); 1002 | s0 = s1; 1003 | } else { 1004 | peg$currPos = s0; 1005 | s0 = peg$FAILED; 1006 | } 1007 | } else { 1008 | peg$currPos = s0; 1009 | s0 = peg$FAILED; 1010 | } 1011 | 1012 | return s0; 1013 | } 1014 | 1015 | function peg$parseinport() { 1016 | var s0, s1, s2; 1017 | 1018 | s0 = peg$currPos; 1019 | s1 = peg$parseport__(); 1020 | if (s1 === peg$FAILED) { 1021 | s1 = null; 1022 | } 1023 | if (s1 !== peg$FAILED) { 1024 | s2 = peg$parsenode(); 1025 | if (s2 !== peg$FAILED) { 1026 | peg$savedPos = s0; 1027 | s1 = peg$c30(s1, s2); 1028 | s0 = s1; 1029 | } else { 1030 | peg$currPos = s0; 1031 | s0 = peg$FAILED; 1032 | } 1033 | } else { 1034 | peg$currPos = s0; 1035 | s0 = peg$FAILED; 1036 | } 1037 | 1038 | return s0; 1039 | } 1040 | 1041 | function peg$parseiip() { 1042 | var s0, s1, s2, s3; 1043 | 1044 | s0 = peg$currPos; 1045 | if (input.charCodeAt(peg$currPos) === 39) { 1046 | s1 = peg$c31; 1047 | peg$currPos++; 1048 | } else { 1049 | s1 = peg$FAILED; 1050 | if (peg$silentFails === 0) { peg$fail(peg$c32); } 1051 | } 1052 | if (s1 !== peg$FAILED) { 1053 | s2 = []; 1054 | s3 = peg$parseiipchar(); 1055 | while (s3 !== peg$FAILED) { 1056 | s2.push(s3); 1057 | s3 = peg$parseiipchar(); 1058 | } 1059 | if (s2 !== peg$FAILED) { 1060 | if (input.charCodeAt(peg$currPos) === 39) { 1061 | s3 = peg$c31; 1062 | peg$currPos++; 1063 | } else { 1064 | s3 = peg$FAILED; 1065 | if (peg$silentFails === 0) { peg$fail(peg$c32); } 1066 | } 1067 | if (s3 !== peg$FAILED) { 1068 | peg$savedPos = s0; 1069 | s1 = peg$c33(s2); 1070 | s0 = s1; 1071 | } else { 1072 | peg$currPos = s0; 1073 | s0 = peg$FAILED; 1074 | } 1075 | } else { 1076 | peg$currPos = s0; 1077 | s0 = peg$FAILED; 1078 | } 1079 | } else { 1080 | peg$currPos = s0; 1081 | s0 = peg$FAILED; 1082 | } 1083 | if (s0 === peg$FAILED) { 1084 | s0 = peg$currPos; 1085 | s1 = peg$parseJSON_text(); 1086 | if (s1 !== peg$FAILED) { 1087 | peg$savedPos = s0; 1088 | s1 = peg$c34(s1); 1089 | } 1090 | s0 = s1; 1091 | } 1092 | 1093 | return s0; 1094 | } 1095 | 1096 | function peg$parsenode() { 1097 | var s0, s1; 1098 | 1099 | s0 = peg$currPos; 1100 | s1 = peg$parsenodeNameAndComponent(); 1101 | if (s1 !== peg$FAILED) { 1102 | peg$savedPos = s0; 1103 | s1 = peg$c35(s1); 1104 | } 1105 | s0 = s1; 1106 | if (s0 === peg$FAILED) { 1107 | s0 = peg$currPos; 1108 | s1 = peg$parsenodeName(); 1109 | if (s1 !== peg$FAILED) { 1110 | peg$savedPos = s0; 1111 | s1 = peg$c35(s1); 1112 | } 1113 | s0 = s1; 1114 | if (s0 === peg$FAILED) { 1115 | s0 = peg$currPos; 1116 | s1 = peg$parsenodeComponent(); 1117 | if (s1 !== peg$FAILED) { 1118 | peg$savedPos = s0; 1119 | s1 = peg$c35(s1); 1120 | } 1121 | s0 = s1; 1122 | } 1123 | } 1124 | 1125 | return s0; 1126 | } 1127 | 1128 | function peg$parsenodeName() { 1129 | var s0, s1, s2, s3, s4; 1130 | 1131 | s0 = peg$currPos; 1132 | s1 = peg$currPos; 1133 | if (peg$c36.test(input.charAt(peg$currPos))) { 1134 | s2 = input.charAt(peg$currPos); 1135 | peg$currPos++; 1136 | } else { 1137 | s2 = peg$FAILED; 1138 | if (peg$silentFails === 0) { peg$fail(peg$c37); } 1139 | } 1140 | if (s2 !== peg$FAILED) { 1141 | s3 = []; 1142 | if (peg$c38.test(input.charAt(peg$currPos))) { 1143 | s4 = input.charAt(peg$currPos); 1144 | peg$currPos++; 1145 | } else { 1146 | s4 = peg$FAILED; 1147 | if (peg$silentFails === 0) { peg$fail(peg$c39); } 1148 | } 1149 | while (s4 !== peg$FAILED) { 1150 | s3.push(s4); 1151 | if (peg$c38.test(input.charAt(peg$currPos))) { 1152 | s4 = input.charAt(peg$currPos); 1153 | peg$currPos++; 1154 | } else { 1155 | s4 = peg$FAILED; 1156 | if (peg$silentFails === 0) { peg$fail(peg$c39); } 1157 | } 1158 | } 1159 | if (s3 !== peg$FAILED) { 1160 | s2 = [s2, s3]; 1161 | s1 = s2; 1162 | } else { 1163 | peg$currPos = s1; 1164 | s1 = peg$FAILED; 1165 | } 1166 | } else { 1167 | peg$currPos = s1; 1168 | s1 = peg$FAILED; 1169 | } 1170 | if (s1 !== peg$FAILED) { 1171 | peg$savedPos = s0; 1172 | s1 = peg$c40(s1); 1173 | } 1174 | s0 = s1; 1175 | 1176 | return s0; 1177 | } 1178 | 1179 | function peg$parsenodeNameAndComponent() { 1180 | var s0, s1, s2; 1181 | 1182 | s0 = peg$currPos; 1183 | s1 = peg$parsenodeName(); 1184 | if (s1 !== peg$FAILED) { 1185 | s2 = peg$parsecomponent(); 1186 | if (s2 !== peg$FAILED) { 1187 | peg$savedPos = s0; 1188 | s1 = peg$c41(s1, s2); 1189 | s0 = s1; 1190 | } else { 1191 | peg$currPos = s0; 1192 | s0 = peg$FAILED; 1193 | } 1194 | } else { 1195 | peg$currPos = s0; 1196 | s0 = peg$FAILED; 1197 | } 1198 | 1199 | return s0; 1200 | } 1201 | 1202 | function peg$parsenodeComponent() { 1203 | var s0, s1; 1204 | 1205 | s0 = peg$currPos; 1206 | s1 = peg$parsecomponent(); 1207 | if (s1 !== peg$FAILED) { 1208 | peg$savedPos = s0; 1209 | s1 = peg$c42(s1); 1210 | } 1211 | s0 = s1; 1212 | 1213 | return s0; 1214 | } 1215 | 1216 | function peg$parsenodeWithComponent() { 1217 | var s0; 1218 | 1219 | s0 = peg$parsenodeNameAndComponent(); 1220 | if (s0 === peg$FAILED) { 1221 | s0 = peg$parsenodeComponent(); 1222 | } 1223 | 1224 | return s0; 1225 | } 1226 | 1227 | function peg$parsecomponent() { 1228 | var s0, s1, s2, s3, s4; 1229 | 1230 | s0 = peg$currPos; 1231 | if (input.charCodeAt(peg$currPos) === 40) { 1232 | s1 = peg$c43; 1233 | peg$currPos++; 1234 | } else { 1235 | s1 = peg$FAILED; 1236 | if (peg$silentFails === 0) { peg$fail(peg$c44); } 1237 | } 1238 | if (s1 !== peg$FAILED) { 1239 | s2 = []; 1240 | if (peg$c45.test(input.charAt(peg$currPos))) { 1241 | s3 = input.charAt(peg$currPos); 1242 | peg$currPos++; 1243 | } else { 1244 | s3 = peg$FAILED; 1245 | if (peg$silentFails === 0) { peg$fail(peg$c46); } 1246 | } 1247 | while (s3 !== peg$FAILED) { 1248 | s2.push(s3); 1249 | if (peg$c45.test(input.charAt(peg$currPos))) { 1250 | s3 = input.charAt(peg$currPos); 1251 | peg$currPos++; 1252 | } else { 1253 | s3 = peg$FAILED; 1254 | if (peg$silentFails === 0) { peg$fail(peg$c46); } 1255 | } 1256 | } 1257 | if (s2 === peg$FAILED) { 1258 | s2 = null; 1259 | } 1260 | if (s2 !== peg$FAILED) { 1261 | s3 = peg$parsecompMeta(); 1262 | if (s3 === peg$FAILED) { 1263 | s3 = null; 1264 | } 1265 | if (s3 !== peg$FAILED) { 1266 | if (input.charCodeAt(peg$currPos) === 41) { 1267 | s4 = peg$c47; 1268 | peg$currPos++; 1269 | } else { 1270 | s4 = peg$FAILED; 1271 | if (peg$silentFails === 0) { peg$fail(peg$c48); } 1272 | } 1273 | if (s4 !== peg$FAILED) { 1274 | peg$savedPos = s0; 1275 | s1 = peg$c49(s2, s3); 1276 | s0 = s1; 1277 | } else { 1278 | peg$currPos = s0; 1279 | s0 = peg$FAILED; 1280 | } 1281 | } else { 1282 | peg$currPos = s0; 1283 | s0 = peg$FAILED; 1284 | } 1285 | } else { 1286 | peg$currPos = s0; 1287 | s0 = peg$FAILED; 1288 | } 1289 | } else { 1290 | peg$currPos = s0; 1291 | s0 = peg$FAILED; 1292 | } 1293 | 1294 | return s0; 1295 | } 1296 | 1297 | function peg$parsecompMeta() { 1298 | var s0, s1, s2, s3; 1299 | 1300 | s0 = peg$currPos; 1301 | if (input.charCodeAt(peg$currPos) === 58) { 1302 | s1 = peg$c5; 1303 | peg$currPos++; 1304 | } else { 1305 | s1 = peg$FAILED; 1306 | if (peg$silentFails === 0) { peg$fail(peg$c6); } 1307 | } 1308 | if (s1 !== peg$FAILED) { 1309 | s2 = []; 1310 | if (peg$c50.test(input.charAt(peg$currPos))) { 1311 | s3 = input.charAt(peg$currPos); 1312 | peg$currPos++; 1313 | } else { 1314 | s3 = peg$FAILED; 1315 | if (peg$silentFails === 0) { peg$fail(peg$c51); } 1316 | } 1317 | if (s3 !== peg$FAILED) { 1318 | while (s3 !== peg$FAILED) { 1319 | s2.push(s3); 1320 | if (peg$c50.test(input.charAt(peg$currPos))) { 1321 | s3 = input.charAt(peg$currPos); 1322 | peg$currPos++; 1323 | } else { 1324 | s3 = peg$FAILED; 1325 | if (peg$silentFails === 0) { peg$fail(peg$c51); } 1326 | } 1327 | } 1328 | } else { 1329 | s2 = peg$FAILED; 1330 | } 1331 | if (s2 !== peg$FAILED) { 1332 | peg$savedPos = s0; 1333 | s1 = peg$c52(s2); 1334 | s0 = s1; 1335 | } else { 1336 | peg$currPos = s0; 1337 | s0 = peg$FAILED; 1338 | } 1339 | } else { 1340 | peg$currPos = s0; 1341 | s0 = peg$FAILED; 1342 | } 1343 | 1344 | return s0; 1345 | } 1346 | 1347 | function peg$parseannotation() { 1348 | var s0, s1, s2, s3, s4, s5, s6, s7; 1349 | 1350 | s0 = peg$currPos; 1351 | if (input.charCodeAt(peg$currPos) === 35) { 1352 | s1 = peg$c23; 1353 | peg$currPos++; 1354 | } else { 1355 | s1 = peg$FAILED; 1356 | if (peg$silentFails === 0) { peg$fail(peg$c24); } 1357 | } 1358 | if (s1 !== peg$FAILED) { 1359 | s2 = peg$parse__(); 1360 | if (s2 !== peg$FAILED) { 1361 | if (input.charCodeAt(peg$currPos) === 64) { 1362 | s3 = peg$c53; 1363 | peg$currPos++; 1364 | } else { 1365 | s3 = peg$FAILED; 1366 | if (peg$silentFails === 0) { peg$fail(peg$c54); } 1367 | } 1368 | if (s3 !== peg$FAILED) { 1369 | s4 = []; 1370 | if (peg$c55.test(input.charAt(peg$currPos))) { 1371 | s5 = input.charAt(peg$currPos); 1372 | peg$currPos++; 1373 | } else { 1374 | s5 = peg$FAILED; 1375 | if (peg$silentFails === 0) { peg$fail(peg$c56); } 1376 | } 1377 | if (s5 !== peg$FAILED) { 1378 | while (s5 !== peg$FAILED) { 1379 | s4.push(s5); 1380 | if (peg$c55.test(input.charAt(peg$currPos))) { 1381 | s5 = input.charAt(peg$currPos); 1382 | peg$currPos++; 1383 | } else { 1384 | s5 = peg$FAILED; 1385 | if (peg$silentFails === 0) { peg$fail(peg$c56); } 1386 | } 1387 | } 1388 | } else { 1389 | s4 = peg$FAILED; 1390 | } 1391 | if (s4 !== peg$FAILED) { 1392 | s5 = peg$parse__(); 1393 | if (s5 !== peg$FAILED) { 1394 | s6 = []; 1395 | if (peg$c57.test(input.charAt(peg$currPos))) { 1396 | s7 = input.charAt(peg$currPos); 1397 | peg$currPos++; 1398 | } else { 1399 | s7 = peg$FAILED; 1400 | if (peg$silentFails === 0) { peg$fail(peg$c58); } 1401 | } 1402 | if (s7 !== peg$FAILED) { 1403 | while (s7 !== peg$FAILED) { 1404 | s6.push(s7); 1405 | if (peg$c57.test(input.charAt(peg$currPos))) { 1406 | s7 = input.charAt(peg$currPos); 1407 | peg$currPos++; 1408 | } else { 1409 | s7 = peg$FAILED; 1410 | if (peg$silentFails === 0) { peg$fail(peg$c58); } 1411 | } 1412 | } 1413 | } else { 1414 | s6 = peg$FAILED; 1415 | } 1416 | if (s6 !== peg$FAILED) { 1417 | peg$savedPos = s0; 1418 | s1 = peg$c59(s4, s6); 1419 | s0 = s1; 1420 | } else { 1421 | peg$currPos = s0; 1422 | s0 = peg$FAILED; 1423 | } 1424 | } else { 1425 | peg$currPos = s0; 1426 | s0 = peg$FAILED; 1427 | } 1428 | } else { 1429 | peg$currPos = s0; 1430 | s0 = peg$FAILED; 1431 | } 1432 | } else { 1433 | peg$currPos = s0; 1434 | s0 = peg$FAILED; 1435 | } 1436 | } else { 1437 | peg$currPos = s0; 1438 | s0 = peg$FAILED; 1439 | } 1440 | } else { 1441 | peg$currPos = s0; 1442 | s0 = peg$FAILED; 1443 | } 1444 | 1445 | return s0; 1446 | } 1447 | 1448 | function peg$parseport() { 1449 | var s0, s1, s2; 1450 | 1451 | s0 = peg$currPos; 1452 | s1 = peg$parseportName(); 1453 | if (s1 !== peg$FAILED) { 1454 | s2 = peg$parseportIndex(); 1455 | if (s2 === peg$FAILED) { 1456 | s2 = null; 1457 | } 1458 | if (s2 !== peg$FAILED) { 1459 | peg$savedPos = s0; 1460 | s1 = peg$c60(s1, s2); 1461 | s0 = s1; 1462 | } else { 1463 | peg$currPos = s0; 1464 | s0 = peg$FAILED; 1465 | } 1466 | } else { 1467 | peg$currPos = s0; 1468 | s0 = peg$FAILED; 1469 | } 1470 | 1471 | return s0; 1472 | } 1473 | 1474 | function peg$parseport__() { 1475 | var s0, s1, s2; 1476 | 1477 | s0 = peg$currPos; 1478 | s1 = peg$parseport(); 1479 | if (s1 !== peg$FAILED) { 1480 | s2 = peg$parse__(); 1481 | if (s2 !== peg$FAILED) { 1482 | peg$savedPos = s0; 1483 | s1 = peg$c61(s1); 1484 | s0 = s1; 1485 | } else { 1486 | peg$currPos = s0; 1487 | s0 = peg$FAILED; 1488 | } 1489 | } else { 1490 | peg$currPos = s0; 1491 | s0 = peg$FAILED; 1492 | } 1493 | 1494 | return s0; 1495 | } 1496 | 1497 | function peg$parse__port() { 1498 | var s0, s1, s2; 1499 | 1500 | s0 = peg$currPos; 1501 | s1 = peg$parse__(); 1502 | if (s1 !== peg$FAILED) { 1503 | s2 = peg$parseport(); 1504 | if (s2 !== peg$FAILED) { 1505 | peg$savedPos = s0; 1506 | s1 = peg$c61(s2); 1507 | s0 = s1; 1508 | } else { 1509 | peg$currPos = s0; 1510 | s0 = peg$FAILED; 1511 | } 1512 | } else { 1513 | peg$currPos = s0; 1514 | s0 = peg$FAILED; 1515 | } 1516 | 1517 | return s0; 1518 | } 1519 | 1520 | function peg$parseportName() { 1521 | var s0, s1, s2, s3, s4; 1522 | 1523 | s0 = peg$currPos; 1524 | s1 = peg$currPos; 1525 | if (peg$c36.test(input.charAt(peg$currPos))) { 1526 | s2 = input.charAt(peg$currPos); 1527 | peg$currPos++; 1528 | } else { 1529 | s2 = peg$FAILED; 1530 | if (peg$silentFails === 0) { peg$fail(peg$c37); } 1531 | } 1532 | if (s2 !== peg$FAILED) { 1533 | s3 = []; 1534 | if (peg$c62.test(input.charAt(peg$currPos))) { 1535 | s4 = input.charAt(peg$currPos); 1536 | peg$currPos++; 1537 | } else { 1538 | s4 = peg$FAILED; 1539 | if (peg$silentFails === 0) { peg$fail(peg$c63); } 1540 | } 1541 | while (s4 !== peg$FAILED) { 1542 | s3.push(s4); 1543 | if (peg$c62.test(input.charAt(peg$currPos))) { 1544 | s4 = input.charAt(peg$currPos); 1545 | peg$currPos++; 1546 | } else { 1547 | s4 = peg$FAILED; 1548 | if (peg$silentFails === 0) { peg$fail(peg$c63); } 1549 | } 1550 | } 1551 | if (s3 !== peg$FAILED) { 1552 | s2 = [s2, s3]; 1553 | s1 = s2; 1554 | } else { 1555 | peg$currPos = s1; 1556 | s1 = peg$FAILED; 1557 | } 1558 | } else { 1559 | peg$currPos = s1; 1560 | s1 = peg$FAILED; 1561 | } 1562 | if (s1 !== peg$FAILED) { 1563 | peg$savedPos = s0; 1564 | s1 = peg$c64(s1); 1565 | } 1566 | s0 = s1; 1567 | 1568 | return s0; 1569 | } 1570 | 1571 | function peg$parseportIndex() { 1572 | var s0, s1, s2, s3; 1573 | 1574 | s0 = peg$currPos; 1575 | if (input.charCodeAt(peg$currPos) === 91) { 1576 | s1 = peg$c65; 1577 | peg$currPos++; 1578 | } else { 1579 | s1 = peg$FAILED; 1580 | if (peg$silentFails === 0) { peg$fail(peg$c66); } 1581 | } 1582 | if (s1 !== peg$FAILED) { 1583 | s2 = []; 1584 | if (peg$c67.test(input.charAt(peg$currPos))) { 1585 | s3 = input.charAt(peg$currPos); 1586 | peg$currPos++; 1587 | } else { 1588 | s3 = peg$FAILED; 1589 | if (peg$silentFails === 0) { peg$fail(peg$c68); } 1590 | } 1591 | if (s3 !== peg$FAILED) { 1592 | while (s3 !== peg$FAILED) { 1593 | s2.push(s3); 1594 | if (peg$c67.test(input.charAt(peg$currPos))) { 1595 | s3 = input.charAt(peg$currPos); 1596 | peg$currPos++; 1597 | } else { 1598 | s3 = peg$FAILED; 1599 | if (peg$silentFails === 0) { peg$fail(peg$c68); } 1600 | } 1601 | } 1602 | } else { 1603 | s2 = peg$FAILED; 1604 | } 1605 | if (s2 !== peg$FAILED) { 1606 | if (input.charCodeAt(peg$currPos) === 93) { 1607 | s3 = peg$c69; 1608 | peg$currPos++; 1609 | } else { 1610 | s3 = peg$FAILED; 1611 | if (peg$silentFails === 0) { peg$fail(peg$c70); } 1612 | } 1613 | if (s3 !== peg$FAILED) { 1614 | peg$savedPos = s0; 1615 | s1 = peg$c71(s2); 1616 | s0 = s1; 1617 | } else { 1618 | peg$currPos = s0; 1619 | s0 = peg$FAILED; 1620 | } 1621 | } else { 1622 | peg$currPos = s0; 1623 | s0 = peg$FAILED; 1624 | } 1625 | } else { 1626 | peg$currPos = s0; 1627 | s0 = peg$FAILED; 1628 | } 1629 | 1630 | return s0; 1631 | } 1632 | 1633 | function peg$parseanychar() { 1634 | var s0; 1635 | 1636 | if (peg$c72.test(input.charAt(peg$currPos))) { 1637 | s0 = input.charAt(peg$currPos); 1638 | peg$currPos++; 1639 | } else { 1640 | s0 = peg$FAILED; 1641 | if (peg$silentFails === 0) { peg$fail(peg$c73); } 1642 | } 1643 | 1644 | return s0; 1645 | } 1646 | 1647 | function peg$parseiipchar() { 1648 | var s0, s1, s2; 1649 | 1650 | s0 = peg$currPos; 1651 | if (peg$c74.test(input.charAt(peg$currPos))) { 1652 | s1 = input.charAt(peg$currPos); 1653 | peg$currPos++; 1654 | } else { 1655 | s1 = peg$FAILED; 1656 | if (peg$silentFails === 0) { peg$fail(peg$c75); } 1657 | } 1658 | if (s1 !== peg$FAILED) { 1659 | if (peg$c76.test(input.charAt(peg$currPos))) { 1660 | s2 = input.charAt(peg$currPos); 1661 | peg$currPos++; 1662 | } else { 1663 | s2 = peg$FAILED; 1664 | if (peg$silentFails === 0) { peg$fail(peg$c77); } 1665 | } 1666 | if (s2 !== peg$FAILED) { 1667 | peg$savedPos = s0; 1668 | s1 = peg$c78(); 1669 | s0 = s1; 1670 | } else { 1671 | peg$currPos = s0; 1672 | s0 = peg$FAILED; 1673 | } 1674 | } else { 1675 | peg$currPos = s0; 1676 | s0 = peg$FAILED; 1677 | } 1678 | if (s0 === peg$FAILED) { 1679 | if (peg$c79.test(input.charAt(peg$currPos))) { 1680 | s0 = input.charAt(peg$currPos); 1681 | peg$currPos++; 1682 | } else { 1683 | s0 = peg$FAILED; 1684 | if (peg$silentFails === 0) { peg$fail(peg$c80); } 1685 | } 1686 | } 1687 | 1688 | return s0; 1689 | } 1690 | 1691 | function peg$parse_() { 1692 | var s0, s1; 1693 | 1694 | s0 = []; 1695 | if (input.charCodeAt(peg$currPos) === 32) { 1696 | s1 = peg$c81; 1697 | peg$currPos++; 1698 | } else { 1699 | s1 = peg$FAILED; 1700 | if (peg$silentFails === 0) { peg$fail(peg$c82); } 1701 | } 1702 | while (s1 !== peg$FAILED) { 1703 | s0.push(s1); 1704 | if (input.charCodeAt(peg$currPos) === 32) { 1705 | s1 = peg$c81; 1706 | peg$currPos++; 1707 | } else { 1708 | s1 = peg$FAILED; 1709 | if (peg$silentFails === 0) { peg$fail(peg$c82); } 1710 | } 1711 | } 1712 | if (s0 === peg$FAILED) { 1713 | s0 = null; 1714 | } 1715 | 1716 | return s0; 1717 | } 1718 | 1719 | function peg$parse__() { 1720 | var s0, s1; 1721 | 1722 | s0 = []; 1723 | if (input.charCodeAt(peg$currPos) === 32) { 1724 | s1 = peg$c81; 1725 | peg$currPos++; 1726 | } else { 1727 | s1 = peg$FAILED; 1728 | if (peg$silentFails === 0) { peg$fail(peg$c82); } 1729 | } 1730 | if (s1 !== peg$FAILED) { 1731 | while (s1 !== peg$FAILED) { 1732 | s0.push(s1); 1733 | if (input.charCodeAt(peg$currPos) === 32) { 1734 | s1 = peg$c81; 1735 | peg$currPos++; 1736 | } else { 1737 | s1 = peg$FAILED; 1738 | if (peg$silentFails === 0) { peg$fail(peg$c82); } 1739 | } 1740 | } 1741 | } else { 1742 | s0 = peg$FAILED; 1743 | } 1744 | 1745 | return s0; 1746 | } 1747 | 1748 | function peg$parseJSON_text() { 1749 | var s0, s1, s2, s3; 1750 | 1751 | s0 = peg$currPos; 1752 | s1 = peg$parsews(); 1753 | if (s1 !== peg$FAILED) { 1754 | s2 = peg$parsevalue(); 1755 | if (s2 !== peg$FAILED) { 1756 | s3 = peg$parsews(); 1757 | if (s3 !== peg$FAILED) { 1758 | peg$savedPos = s0; 1759 | s1 = peg$c83(s2); 1760 | s0 = s1; 1761 | } else { 1762 | peg$currPos = s0; 1763 | s0 = peg$FAILED; 1764 | } 1765 | } else { 1766 | peg$currPos = s0; 1767 | s0 = peg$FAILED; 1768 | } 1769 | } else { 1770 | peg$currPos = s0; 1771 | s0 = peg$FAILED; 1772 | } 1773 | 1774 | return s0; 1775 | } 1776 | 1777 | function peg$parsebegin_array() { 1778 | var s0, s1, s2, s3; 1779 | 1780 | s0 = peg$currPos; 1781 | s1 = peg$parsews(); 1782 | if (s1 !== peg$FAILED) { 1783 | if (input.charCodeAt(peg$currPos) === 91) { 1784 | s2 = peg$c65; 1785 | peg$currPos++; 1786 | } else { 1787 | s2 = peg$FAILED; 1788 | if (peg$silentFails === 0) { peg$fail(peg$c66); } 1789 | } 1790 | if (s2 !== peg$FAILED) { 1791 | s3 = peg$parsews(); 1792 | if (s3 !== peg$FAILED) { 1793 | s1 = [s1, s2, s3]; 1794 | s0 = s1; 1795 | } else { 1796 | peg$currPos = s0; 1797 | s0 = peg$FAILED; 1798 | } 1799 | } else { 1800 | peg$currPos = s0; 1801 | s0 = peg$FAILED; 1802 | } 1803 | } else { 1804 | peg$currPos = s0; 1805 | s0 = peg$FAILED; 1806 | } 1807 | 1808 | return s0; 1809 | } 1810 | 1811 | function peg$parsebegin_object() { 1812 | var s0, s1, s2, s3; 1813 | 1814 | s0 = peg$currPos; 1815 | s1 = peg$parsews(); 1816 | if (s1 !== peg$FAILED) { 1817 | if (input.charCodeAt(peg$currPos) === 123) { 1818 | s2 = peg$c84; 1819 | peg$currPos++; 1820 | } else { 1821 | s2 = peg$FAILED; 1822 | if (peg$silentFails === 0) { peg$fail(peg$c85); } 1823 | } 1824 | if (s2 !== peg$FAILED) { 1825 | s3 = peg$parsews(); 1826 | if (s3 !== peg$FAILED) { 1827 | s1 = [s1, s2, s3]; 1828 | s0 = s1; 1829 | } else { 1830 | peg$currPos = s0; 1831 | s0 = peg$FAILED; 1832 | } 1833 | } else { 1834 | peg$currPos = s0; 1835 | s0 = peg$FAILED; 1836 | } 1837 | } else { 1838 | peg$currPos = s0; 1839 | s0 = peg$FAILED; 1840 | } 1841 | 1842 | return s0; 1843 | } 1844 | 1845 | function peg$parseend_array() { 1846 | var s0, s1, s2, s3; 1847 | 1848 | s0 = peg$currPos; 1849 | s1 = peg$parsews(); 1850 | if (s1 !== peg$FAILED) { 1851 | if (input.charCodeAt(peg$currPos) === 93) { 1852 | s2 = peg$c69; 1853 | peg$currPos++; 1854 | } else { 1855 | s2 = peg$FAILED; 1856 | if (peg$silentFails === 0) { peg$fail(peg$c70); } 1857 | } 1858 | if (s2 !== peg$FAILED) { 1859 | s3 = peg$parsews(); 1860 | if (s3 !== peg$FAILED) { 1861 | s1 = [s1, s2, s3]; 1862 | s0 = s1; 1863 | } else { 1864 | peg$currPos = s0; 1865 | s0 = peg$FAILED; 1866 | } 1867 | } else { 1868 | peg$currPos = s0; 1869 | s0 = peg$FAILED; 1870 | } 1871 | } else { 1872 | peg$currPos = s0; 1873 | s0 = peg$FAILED; 1874 | } 1875 | 1876 | return s0; 1877 | } 1878 | 1879 | function peg$parseend_object() { 1880 | var s0, s1, s2, s3; 1881 | 1882 | s0 = peg$currPos; 1883 | s1 = peg$parsews(); 1884 | if (s1 !== peg$FAILED) { 1885 | if (input.charCodeAt(peg$currPos) === 125) { 1886 | s2 = peg$c86; 1887 | peg$currPos++; 1888 | } else { 1889 | s2 = peg$FAILED; 1890 | if (peg$silentFails === 0) { peg$fail(peg$c87); } 1891 | } 1892 | if (s2 !== peg$FAILED) { 1893 | s3 = peg$parsews(); 1894 | if (s3 !== peg$FAILED) { 1895 | s1 = [s1, s2, s3]; 1896 | s0 = s1; 1897 | } else { 1898 | peg$currPos = s0; 1899 | s0 = peg$FAILED; 1900 | } 1901 | } else { 1902 | peg$currPos = s0; 1903 | s0 = peg$FAILED; 1904 | } 1905 | } else { 1906 | peg$currPos = s0; 1907 | s0 = peg$FAILED; 1908 | } 1909 | 1910 | return s0; 1911 | } 1912 | 1913 | function peg$parsename_separator() { 1914 | var s0, s1, s2, s3; 1915 | 1916 | s0 = peg$currPos; 1917 | s1 = peg$parsews(); 1918 | if (s1 !== peg$FAILED) { 1919 | if (input.charCodeAt(peg$currPos) === 58) { 1920 | s2 = peg$c5; 1921 | peg$currPos++; 1922 | } else { 1923 | s2 = peg$FAILED; 1924 | if (peg$silentFails === 0) { peg$fail(peg$c6); } 1925 | } 1926 | if (s2 !== peg$FAILED) { 1927 | s3 = peg$parsews(); 1928 | if (s3 !== peg$FAILED) { 1929 | s1 = [s1, s2, s3]; 1930 | s0 = s1; 1931 | } else { 1932 | peg$currPos = s0; 1933 | s0 = peg$FAILED; 1934 | } 1935 | } else { 1936 | peg$currPos = s0; 1937 | s0 = peg$FAILED; 1938 | } 1939 | } else { 1940 | peg$currPos = s0; 1941 | s0 = peg$FAILED; 1942 | } 1943 | 1944 | return s0; 1945 | } 1946 | 1947 | function peg$parsevalue_separator() { 1948 | var s0, s1, s2, s3; 1949 | 1950 | s0 = peg$currPos; 1951 | s1 = peg$parsews(); 1952 | if (s1 !== peg$FAILED) { 1953 | if (input.charCodeAt(peg$currPos) === 44) { 1954 | s2 = peg$c19; 1955 | peg$currPos++; 1956 | } else { 1957 | s2 = peg$FAILED; 1958 | if (peg$silentFails === 0) { peg$fail(peg$c20); } 1959 | } 1960 | if (s2 !== peg$FAILED) { 1961 | s3 = peg$parsews(); 1962 | if (s3 !== peg$FAILED) { 1963 | s1 = [s1, s2, s3]; 1964 | s0 = s1; 1965 | } else { 1966 | peg$currPos = s0; 1967 | s0 = peg$FAILED; 1968 | } 1969 | } else { 1970 | peg$currPos = s0; 1971 | s0 = peg$FAILED; 1972 | } 1973 | } else { 1974 | peg$currPos = s0; 1975 | s0 = peg$FAILED; 1976 | } 1977 | 1978 | return s0; 1979 | } 1980 | 1981 | function peg$parsews() { 1982 | var s0, s1; 1983 | 1984 | peg$silentFails++; 1985 | s0 = []; 1986 | if (peg$c89.test(input.charAt(peg$currPos))) { 1987 | s1 = input.charAt(peg$currPos); 1988 | peg$currPos++; 1989 | } else { 1990 | s1 = peg$FAILED; 1991 | if (peg$silentFails === 0) { peg$fail(peg$c90); } 1992 | } 1993 | while (s1 !== peg$FAILED) { 1994 | s0.push(s1); 1995 | if (peg$c89.test(input.charAt(peg$currPos))) { 1996 | s1 = input.charAt(peg$currPos); 1997 | peg$currPos++; 1998 | } else { 1999 | s1 = peg$FAILED; 2000 | if (peg$silentFails === 0) { peg$fail(peg$c90); } 2001 | } 2002 | } 2003 | peg$silentFails--; 2004 | if (s0 === peg$FAILED) { 2005 | s1 = peg$FAILED; 2006 | if (peg$silentFails === 0) { peg$fail(peg$c88); } 2007 | } 2008 | 2009 | return s0; 2010 | } 2011 | 2012 | function peg$parsevalue() { 2013 | var s0; 2014 | 2015 | s0 = peg$parsefalse(); 2016 | if (s0 === peg$FAILED) { 2017 | s0 = peg$parsenull(); 2018 | if (s0 === peg$FAILED) { 2019 | s0 = peg$parsetrue(); 2020 | if (s0 === peg$FAILED) { 2021 | s0 = peg$parseobject(); 2022 | if (s0 === peg$FAILED) { 2023 | s0 = peg$parsearray(); 2024 | if (s0 === peg$FAILED) { 2025 | s0 = peg$parsenumber(); 2026 | if (s0 === peg$FAILED) { 2027 | s0 = peg$parsestring(); 2028 | } 2029 | } 2030 | } 2031 | } 2032 | } 2033 | } 2034 | 2035 | return s0; 2036 | } 2037 | 2038 | function peg$parsefalse() { 2039 | var s0, s1; 2040 | 2041 | s0 = peg$currPos; 2042 | if (input.substr(peg$currPos, 5) === peg$c91) { 2043 | s1 = peg$c91; 2044 | peg$currPos += 5; 2045 | } else { 2046 | s1 = peg$FAILED; 2047 | if (peg$silentFails === 0) { peg$fail(peg$c92); } 2048 | } 2049 | if (s1 !== peg$FAILED) { 2050 | peg$savedPos = s0; 2051 | s1 = peg$c93(); 2052 | } 2053 | s0 = s1; 2054 | 2055 | return s0; 2056 | } 2057 | 2058 | function peg$parsenull() { 2059 | var s0, s1; 2060 | 2061 | s0 = peg$currPos; 2062 | if (input.substr(peg$currPos, 4) === peg$c94) { 2063 | s1 = peg$c94; 2064 | peg$currPos += 4; 2065 | } else { 2066 | s1 = peg$FAILED; 2067 | if (peg$silentFails === 0) { peg$fail(peg$c95); } 2068 | } 2069 | if (s1 !== peg$FAILED) { 2070 | peg$savedPos = s0; 2071 | s1 = peg$c96(); 2072 | } 2073 | s0 = s1; 2074 | 2075 | return s0; 2076 | } 2077 | 2078 | function peg$parsetrue() { 2079 | var s0, s1; 2080 | 2081 | s0 = peg$currPos; 2082 | if (input.substr(peg$currPos, 4) === peg$c97) { 2083 | s1 = peg$c97; 2084 | peg$currPos += 4; 2085 | } else { 2086 | s1 = peg$FAILED; 2087 | if (peg$silentFails === 0) { peg$fail(peg$c98); } 2088 | } 2089 | if (s1 !== peg$FAILED) { 2090 | peg$savedPos = s0; 2091 | s1 = peg$c99(); 2092 | } 2093 | s0 = s1; 2094 | 2095 | return s0; 2096 | } 2097 | 2098 | function peg$parseobject() { 2099 | var s0, s1, s2, s3, s4, s5, s6, s7; 2100 | 2101 | s0 = peg$currPos; 2102 | s1 = peg$parsebegin_object(); 2103 | if (s1 !== peg$FAILED) { 2104 | s2 = peg$currPos; 2105 | s3 = peg$parsemember(); 2106 | if (s3 !== peg$FAILED) { 2107 | s4 = []; 2108 | s5 = peg$currPos; 2109 | s6 = peg$parsevalue_separator(); 2110 | if (s6 !== peg$FAILED) { 2111 | s7 = peg$parsemember(); 2112 | if (s7 !== peg$FAILED) { 2113 | peg$savedPos = s5; 2114 | s6 = peg$c100(s3, s7); 2115 | s5 = s6; 2116 | } else { 2117 | peg$currPos = s5; 2118 | s5 = peg$FAILED; 2119 | } 2120 | } else { 2121 | peg$currPos = s5; 2122 | s5 = peg$FAILED; 2123 | } 2124 | while (s5 !== peg$FAILED) { 2125 | s4.push(s5); 2126 | s5 = peg$currPos; 2127 | s6 = peg$parsevalue_separator(); 2128 | if (s6 !== peg$FAILED) { 2129 | s7 = peg$parsemember(); 2130 | if (s7 !== peg$FAILED) { 2131 | peg$savedPos = s5; 2132 | s6 = peg$c100(s3, s7); 2133 | s5 = s6; 2134 | } else { 2135 | peg$currPos = s5; 2136 | s5 = peg$FAILED; 2137 | } 2138 | } else { 2139 | peg$currPos = s5; 2140 | s5 = peg$FAILED; 2141 | } 2142 | } 2143 | if (s4 !== peg$FAILED) { 2144 | peg$savedPos = s2; 2145 | s3 = peg$c101(s3, s4); 2146 | s2 = s3; 2147 | } else { 2148 | peg$currPos = s2; 2149 | s2 = peg$FAILED; 2150 | } 2151 | } else { 2152 | peg$currPos = s2; 2153 | s2 = peg$FAILED; 2154 | } 2155 | if (s2 === peg$FAILED) { 2156 | s2 = null; 2157 | } 2158 | if (s2 !== peg$FAILED) { 2159 | s3 = peg$parseend_object(); 2160 | if (s3 !== peg$FAILED) { 2161 | peg$savedPos = s0; 2162 | s1 = peg$c102(s2); 2163 | s0 = s1; 2164 | } else { 2165 | peg$currPos = s0; 2166 | s0 = peg$FAILED; 2167 | } 2168 | } else { 2169 | peg$currPos = s0; 2170 | s0 = peg$FAILED; 2171 | } 2172 | } else { 2173 | peg$currPos = s0; 2174 | s0 = peg$FAILED; 2175 | } 2176 | 2177 | return s0; 2178 | } 2179 | 2180 | function peg$parsemember() { 2181 | var s0, s1, s2, s3; 2182 | 2183 | s0 = peg$currPos; 2184 | s1 = peg$parsestring(); 2185 | if (s1 !== peg$FAILED) { 2186 | s2 = peg$parsename_separator(); 2187 | if (s2 !== peg$FAILED) { 2188 | s3 = peg$parsevalue(); 2189 | if (s3 !== peg$FAILED) { 2190 | peg$savedPos = s0; 2191 | s1 = peg$c103(s1, s3); 2192 | s0 = s1; 2193 | } else { 2194 | peg$currPos = s0; 2195 | s0 = peg$FAILED; 2196 | } 2197 | } else { 2198 | peg$currPos = s0; 2199 | s0 = peg$FAILED; 2200 | } 2201 | } else { 2202 | peg$currPos = s0; 2203 | s0 = peg$FAILED; 2204 | } 2205 | 2206 | return s0; 2207 | } 2208 | 2209 | function peg$parsearray() { 2210 | var s0, s1, s2, s3, s4, s5, s6, s7; 2211 | 2212 | s0 = peg$currPos; 2213 | s1 = peg$parsebegin_array(); 2214 | if (s1 !== peg$FAILED) { 2215 | s2 = peg$currPos; 2216 | s3 = peg$parsevalue(); 2217 | if (s3 !== peg$FAILED) { 2218 | s4 = []; 2219 | s5 = peg$currPos; 2220 | s6 = peg$parsevalue_separator(); 2221 | if (s6 !== peg$FAILED) { 2222 | s7 = peg$parsevalue(); 2223 | if (s7 !== peg$FAILED) { 2224 | peg$savedPos = s5; 2225 | s6 = peg$c104(s3, s7); 2226 | s5 = s6; 2227 | } else { 2228 | peg$currPos = s5; 2229 | s5 = peg$FAILED; 2230 | } 2231 | } else { 2232 | peg$currPos = s5; 2233 | s5 = peg$FAILED; 2234 | } 2235 | while (s5 !== peg$FAILED) { 2236 | s4.push(s5); 2237 | s5 = peg$currPos; 2238 | s6 = peg$parsevalue_separator(); 2239 | if (s6 !== peg$FAILED) { 2240 | s7 = peg$parsevalue(); 2241 | if (s7 !== peg$FAILED) { 2242 | peg$savedPos = s5; 2243 | s6 = peg$c104(s3, s7); 2244 | s5 = s6; 2245 | } else { 2246 | peg$currPos = s5; 2247 | s5 = peg$FAILED; 2248 | } 2249 | } else { 2250 | peg$currPos = s5; 2251 | s5 = peg$FAILED; 2252 | } 2253 | } 2254 | if (s4 !== peg$FAILED) { 2255 | peg$savedPos = s2; 2256 | s3 = peg$c105(s3, s4); 2257 | s2 = s3; 2258 | } else { 2259 | peg$currPos = s2; 2260 | s2 = peg$FAILED; 2261 | } 2262 | } else { 2263 | peg$currPos = s2; 2264 | s2 = peg$FAILED; 2265 | } 2266 | if (s2 === peg$FAILED) { 2267 | s2 = null; 2268 | } 2269 | if (s2 !== peg$FAILED) { 2270 | s3 = peg$parseend_array(); 2271 | if (s3 !== peg$FAILED) { 2272 | peg$savedPos = s0; 2273 | s1 = peg$c106(s2); 2274 | s0 = s1; 2275 | } else { 2276 | peg$currPos = s0; 2277 | s0 = peg$FAILED; 2278 | } 2279 | } else { 2280 | peg$currPos = s0; 2281 | s0 = peg$FAILED; 2282 | } 2283 | } else { 2284 | peg$currPos = s0; 2285 | s0 = peg$FAILED; 2286 | } 2287 | 2288 | return s0; 2289 | } 2290 | 2291 | function peg$parsenumber() { 2292 | var s0, s1, s2, s3, s4; 2293 | 2294 | peg$silentFails++; 2295 | s0 = peg$currPos; 2296 | s1 = peg$parseminus(); 2297 | if (s1 === peg$FAILED) { 2298 | s1 = null; 2299 | } 2300 | if (s1 !== peg$FAILED) { 2301 | s2 = peg$parseint(); 2302 | if (s2 !== peg$FAILED) { 2303 | s3 = peg$parsefrac(); 2304 | if (s3 === peg$FAILED) { 2305 | s3 = null; 2306 | } 2307 | if (s3 !== peg$FAILED) { 2308 | s4 = peg$parseexp(); 2309 | if (s4 === peg$FAILED) { 2310 | s4 = null; 2311 | } 2312 | if (s4 !== peg$FAILED) { 2313 | peg$savedPos = s0; 2314 | s1 = peg$c108(); 2315 | s0 = s1; 2316 | } else { 2317 | peg$currPos = s0; 2318 | s0 = peg$FAILED; 2319 | } 2320 | } else { 2321 | peg$currPos = s0; 2322 | s0 = peg$FAILED; 2323 | } 2324 | } else { 2325 | peg$currPos = s0; 2326 | s0 = peg$FAILED; 2327 | } 2328 | } else { 2329 | peg$currPos = s0; 2330 | s0 = peg$FAILED; 2331 | } 2332 | peg$silentFails--; 2333 | if (s0 === peg$FAILED) { 2334 | s1 = peg$FAILED; 2335 | if (peg$silentFails === 0) { peg$fail(peg$c107); } 2336 | } 2337 | 2338 | return s0; 2339 | } 2340 | 2341 | function peg$parsedecimal_point() { 2342 | var s0; 2343 | 2344 | if (input.charCodeAt(peg$currPos) === 46) { 2345 | s0 = peg$c3; 2346 | peg$currPos++; 2347 | } else { 2348 | s0 = peg$FAILED; 2349 | if (peg$silentFails === 0) { peg$fail(peg$c4); } 2350 | } 2351 | 2352 | return s0; 2353 | } 2354 | 2355 | function peg$parsedigit1_9() { 2356 | var s0; 2357 | 2358 | if (peg$c109.test(input.charAt(peg$currPos))) { 2359 | s0 = input.charAt(peg$currPos); 2360 | peg$currPos++; 2361 | } else { 2362 | s0 = peg$FAILED; 2363 | if (peg$silentFails === 0) { peg$fail(peg$c110); } 2364 | } 2365 | 2366 | return s0; 2367 | } 2368 | 2369 | function peg$parsee() { 2370 | var s0; 2371 | 2372 | if (peg$c111.test(input.charAt(peg$currPos))) { 2373 | s0 = input.charAt(peg$currPos); 2374 | peg$currPos++; 2375 | } else { 2376 | s0 = peg$FAILED; 2377 | if (peg$silentFails === 0) { peg$fail(peg$c112); } 2378 | } 2379 | 2380 | return s0; 2381 | } 2382 | 2383 | function peg$parseexp() { 2384 | var s0, s1, s2, s3, s4; 2385 | 2386 | s0 = peg$currPos; 2387 | s1 = peg$parsee(); 2388 | if (s1 !== peg$FAILED) { 2389 | s2 = peg$parseminus(); 2390 | if (s2 === peg$FAILED) { 2391 | s2 = peg$parseplus(); 2392 | } 2393 | if (s2 === peg$FAILED) { 2394 | s2 = null; 2395 | } 2396 | if (s2 !== peg$FAILED) { 2397 | s3 = []; 2398 | s4 = peg$parseDIGIT(); 2399 | if (s4 !== peg$FAILED) { 2400 | while (s4 !== peg$FAILED) { 2401 | s3.push(s4); 2402 | s4 = peg$parseDIGIT(); 2403 | } 2404 | } else { 2405 | s3 = peg$FAILED; 2406 | } 2407 | if (s3 !== peg$FAILED) { 2408 | s1 = [s1, s2, s3]; 2409 | s0 = s1; 2410 | } else { 2411 | peg$currPos = s0; 2412 | s0 = peg$FAILED; 2413 | } 2414 | } else { 2415 | peg$currPos = s0; 2416 | s0 = peg$FAILED; 2417 | } 2418 | } else { 2419 | peg$currPos = s0; 2420 | s0 = peg$FAILED; 2421 | } 2422 | 2423 | return s0; 2424 | } 2425 | 2426 | function peg$parsefrac() { 2427 | var s0, s1, s2, s3; 2428 | 2429 | s0 = peg$currPos; 2430 | s1 = peg$parsedecimal_point(); 2431 | if (s1 !== peg$FAILED) { 2432 | s2 = []; 2433 | s3 = peg$parseDIGIT(); 2434 | if (s3 !== peg$FAILED) { 2435 | while (s3 !== peg$FAILED) { 2436 | s2.push(s3); 2437 | s3 = peg$parseDIGIT(); 2438 | } 2439 | } else { 2440 | s2 = peg$FAILED; 2441 | } 2442 | if (s2 !== peg$FAILED) { 2443 | s1 = [s1, s2]; 2444 | s0 = s1; 2445 | } else { 2446 | peg$currPos = s0; 2447 | s0 = peg$FAILED; 2448 | } 2449 | } else { 2450 | peg$currPos = s0; 2451 | s0 = peg$FAILED; 2452 | } 2453 | 2454 | return s0; 2455 | } 2456 | 2457 | function peg$parseint() { 2458 | var s0, s1, s2, s3; 2459 | 2460 | s0 = peg$parsezero(); 2461 | if (s0 === peg$FAILED) { 2462 | s0 = peg$currPos; 2463 | s1 = peg$parsedigit1_9(); 2464 | if (s1 !== peg$FAILED) { 2465 | s2 = []; 2466 | s3 = peg$parseDIGIT(); 2467 | while (s3 !== peg$FAILED) { 2468 | s2.push(s3); 2469 | s3 = peg$parseDIGIT(); 2470 | } 2471 | if (s2 !== peg$FAILED) { 2472 | s1 = [s1, s2]; 2473 | s0 = s1; 2474 | } else { 2475 | peg$currPos = s0; 2476 | s0 = peg$FAILED; 2477 | } 2478 | } else { 2479 | peg$currPos = s0; 2480 | s0 = peg$FAILED; 2481 | } 2482 | } 2483 | 2484 | return s0; 2485 | } 2486 | 2487 | function peg$parseminus() { 2488 | var s0; 2489 | 2490 | if (input.charCodeAt(peg$currPos) === 45) { 2491 | s0 = peg$c113; 2492 | peg$currPos++; 2493 | } else { 2494 | s0 = peg$FAILED; 2495 | if (peg$silentFails === 0) { peg$fail(peg$c114); } 2496 | } 2497 | 2498 | return s0; 2499 | } 2500 | 2501 | function peg$parseplus() { 2502 | var s0; 2503 | 2504 | if (input.charCodeAt(peg$currPos) === 43) { 2505 | s0 = peg$c115; 2506 | peg$currPos++; 2507 | } else { 2508 | s0 = peg$FAILED; 2509 | if (peg$silentFails === 0) { peg$fail(peg$c116); } 2510 | } 2511 | 2512 | return s0; 2513 | } 2514 | 2515 | function peg$parsezero() { 2516 | var s0; 2517 | 2518 | if (input.charCodeAt(peg$currPos) === 48) { 2519 | s0 = peg$c117; 2520 | peg$currPos++; 2521 | } else { 2522 | s0 = peg$FAILED; 2523 | if (peg$silentFails === 0) { peg$fail(peg$c118); } 2524 | } 2525 | 2526 | return s0; 2527 | } 2528 | 2529 | function peg$parsestring() { 2530 | var s0, s1, s2, s3; 2531 | 2532 | peg$silentFails++; 2533 | s0 = peg$currPos; 2534 | s1 = peg$parsequotation_mark(); 2535 | if (s1 !== peg$FAILED) { 2536 | s2 = []; 2537 | s3 = peg$parsechar(); 2538 | while (s3 !== peg$FAILED) { 2539 | s2.push(s3); 2540 | s3 = peg$parsechar(); 2541 | } 2542 | if (s2 !== peg$FAILED) { 2543 | s3 = peg$parsequotation_mark(); 2544 | if (s3 !== peg$FAILED) { 2545 | peg$savedPos = s0; 2546 | s1 = peg$c120(s2); 2547 | s0 = s1; 2548 | } else { 2549 | peg$currPos = s0; 2550 | s0 = peg$FAILED; 2551 | } 2552 | } else { 2553 | peg$currPos = s0; 2554 | s0 = peg$FAILED; 2555 | } 2556 | } else { 2557 | peg$currPos = s0; 2558 | s0 = peg$FAILED; 2559 | } 2560 | peg$silentFails--; 2561 | if (s0 === peg$FAILED) { 2562 | s1 = peg$FAILED; 2563 | if (peg$silentFails === 0) { peg$fail(peg$c119); } 2564 | } 2565 | 2566 | return s0; 2567 | } 2568 | 2569 | function peg$parsechar() { 2570 | var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; 2571 | 2572 | s0 = peg$parseunescaped(); 2573 | if (s0 === peg$FAILED) { 2574 | s0 = peg$currPos; 2575 | s1 = peg$parseescape(); 2576 | if (s1 !== peg$FAILED) { 2577 | if (input.charCodeAt(peg$currPos) === 34) { 2578 | s2 = peg$c121; 2579 | peg$currPos++; 2580 | } else { 2581 | s2 = peg$FAILED; 2582 | if (peg$silentFails === 0) { peg$fail(peg$c122); } 2583 | } 2584 | if (s2 === peg$FAILED) { 2585 | if (input.charCodeAt(peg$currPos) === 92) { 2586 | s2 = peg$c123; 2587 | peg$currPos++; 2588 | } else { 2589 | s2 = peg$FAILED; 2590 | if (peg$silentFails === 0) { peg$fail(peg$c124); } 2591 | } 2592 | if (s2 === peg$FAILED) { 2593 | if (input.charCodeAt(peg$currPos) === 47) { 2594 | s2 = peg$c125; 2595 | peg$currPos++; 2596 | } else { 2597 | s2 = peg$FAILED; 2598 | if (peg$silentFails === 0) { peg$fail(peg$c126); } 2599 | } 2600 | if (s2 === peg$FAILED) { 2601 | s2 = peg$currPos; 2602 | if (input.charCodeAt(peg$currPos) === 98) { 2603 | s3 = peg$c127; 2604 | peg$currPos++; 2605 | } else { 2606 | s3 = peg$FAILED; 2607 | if (peg$silentFails === 0) { peg$fail(peg$c128); } 2608 | } 2609 | if (s3 !== peg$FAILED) { 2610 | peg$savedPos = s2; 2611 | s3 = peg$c129(); 2612 | } 2613 | s2 = s3; 2614 | if (s2 === peg$FAILED) { 2615 | s2 = peg$currPos; 2616 | if (input.charCodeAt(peg$currPos) === 102) { 2617 | s3 = peg$c130; 2618 | peg$currPos++; 2619 | } else { 2620 | s3 = peg$FAILED; 2621 | if (peg$silentFails === 0) { peg$fail(peg$c131); } 2622 | } 2623 | if (s3 !== peg$FAILED) { 2624 | peg$savedPos = s2; 2625 | s3 = peg$c132(); 2626 | } 2627 | s2 = s3; 2628 | if (s2 === peg$FAILED) { 2629 | s2 = peg$currPos; 2630 | if (input.charCodeAt(peg$currPos) === 110) { 2631 | s3 = peg$c133; 2632 | peg$currPos++; 2633 | } else { 2634 | s3 = peg$FAILED; 2635 | if (peg$silentFails === 0) { peg$fail(peg$c134); } 2636 | } 2637 | if (s3 !== peg$FAILED) { 2638 | peg$savedPos = s2; 2639 | s3 = peg$c135(); 2640 | } 2641 | s2 = s3; 2642 | if (s2 === peg$FAILED) { 2643 | s2 = peg$currPos; 2644 | if (input.charCodeAt(peg$currPos) === 114) { 2645 | s3 = peg$c136; 2646 | peg$currPos++; 2647 | } else { 2648 | s3 = peg$FAILED; 2649 | if (peg$silentFails === 0) { peg$fail(peg$c137); } 2650 | } 2651 | if (s3 !== peg$FAILED) { 2652 | peg$savedPos = s2; 2653 | s3 = peg$c138(); 2654 | } 2655 | s2 = s3; 2656 | if (s2 === peg$FAILED) { 2657 | s2 = peg$currPos; 2658 | if (input.charCodeAt(peg$currPos) === 116) { 2659 | s3 = peg$c139; 2660 | peg$currPos++; 2661 | } else { 2662 | s3 = peg$FAILED; 2663 | if (peg$silentFails === 0) { peg$fail(peg$c140); } 2664 | } 2665 | if (s3 !== peg$FAILED) { 2666 | peg$savedPos = s2; 2667 | s3 = peg$c141(); 2668 | } 2669 | s2 = s3; 2670 | if (s2 === peg$FAILED) { 2671 | s2 = peg$currPos; 2672 | if (input.charCodeAt(peg$currPos) === 117) { 2673 | s3 = peg$c142; 2674 | peg$currPos++; 2675 | } else { 2676 | s3 = peg$FAILED; 2677 | if (peg$silentFails === 0) { peg$fail(peg$c143); } 2678 | } 2679 | if (s3 !== peg$FAILED) { 2680 | s4 = peg$currPos; 2681 | s5 = peg$currPos; 2682 | s6 = peg$parseHEXDIG(); 2683 | if (s6 !== peg$FAILED) { 2684 | s7 = peg$parseHEXDIG(); 2685 | if (s7 !== peg$FAILED) { 2686 | s8 = peg$parseHEXDIG(); 2687 | if (s8 !== peg$FAILED) { 2688 | s9 = peg$parseHEXDIG(); 2689 | if (s9 !== peg$FAILED) { 2690 | s6 = [s6, s7, s8, s9]; 2691 | s5 = s6; 2692 | } else { 2693 | peg$currPos = s5; 2694 | s5 = peg$FAILED; 2695 | } 2696 | } else { 2697 | peg$currPos = s5; 2698 | s5 = peg$FAILED; 2699 | } 2700 | } else { 2701 | peg$currPos = s5; 2702 | s5 = peg$FAILED; 2703 | } 2704 | } else { 2705 | peg$currPos = s5; 2706 | s5 = peg$FAILED; 2707 | } 2708 | if (s5 !== peg$FAILED) { 2709 | s4 = input.substring(s4, peg$currPos); 2710 | } else { 2711 | s4 = s5; 2712 | } 2713 | if (s4 !== peg$FAILED) { 2714 | peg$savedPos = s2; 2715 | s3 = peg$c144(s4); 2716 | s2 = s3; 2717 | } else { 2718 | peg$currPos = s2; 2719 | s2 = peg$FAILED; 2720 | } 2721 | } else { 2722 | peg$currPos = s2; 2723 | s2 = peg$FAILED; 2724 | } 2725 | } 2726 | } 2727 | } 2728 | } 2729 | } 2730 | } 2731 | } 2732 | } 2733 | if (s2 !== peg$FAILED) { 2734 | peg$savedPos = s0; 2735 | s1 = peg$c145(s2); 2736 | s0 = s1; 2737 | } else { 2738 | peg$currPos = s0; 2739 | s0 = peg$FAILED; 2740 | } 2741 | } else { 2742 | peg$currPos = s0; 2743 | s0 = peg$FAILED; 2744 | } 2745 | } 2746 | 2747 | return s0; 2748 | } 2749 | 2750 | function peg$parseescape() { 2751 | var s0; 2752 | 2753 | if (input.charCodeAt(peg$currPos) === 92) { 2754 | s0 = peg$c123; 2755 | peg$currPos++; 2756 | } else { 2757 | s0 = peg$FAILED; 2758 | if (peg$silentFails === 0) { peg$fail(peg$c124); } 2759 | } 2760 | 2761 | return s0; 2762 | } 2763 | 2764 | function peg$parsequotation_mark() { 2765 | var s0; 2766 | 2767 | if (input.charCodeAt(peg$currPos) === 34) { 2768 | s0 = peg$c121; 2769 | peg$currPos++; 2770 | } else { 2771 | s0 = peg$FAILED; 2772 | if (peg$silentFails === 0) { peg$fail(peg$c122); } 2773 | } 2774 | 2775 | return s0; 2776 | } 2777 | 2778 | function peg$parseunescaped() { 2779 | var s0; 2780 | 2781 | if (peg$c146.test(input.charAt(peg$currPos))) { 2782 | s0 = input.charAt(peg$currPos); 2783 | peg$currPos++; 2784 | } else { 2785 | s0 = peg$FAILED; 2786 | if (peg$silentFails === 0) { peg$fail(peg$c147); } 2787 | } 2788 | 2789 | return s0; 2790 | } 2791 | 2792 | function peg$parseDIGIT() { 2793 | var s0; 2794 | 2795 | if (peg$c67.test(input.charAt(peg$currPos))) { 2796 | s0 = input.charAt(peg$currPos); 2797 | peg$currPos++; 2798 | } else { 2799 | s0 = peg$FAILED; 2800 | if (peg$silentFails === 0) { peg$fail(peg$c68); } 2801 | } 2802 | 2803 | return s0; 2804 | } 2805 | 2806 | function peg$parseHEXDIG() { 2807 | var s0; 2808 | 2809 | if (peg$c148.test(input.charAt(peg$currPos))) { 2810 | s0 = input.charAt(peg$currPos); 2811 | peg$currPos++; 2812 | } else { 2813 | s0 = peg$FAILED; 2814 | if (peg$silentFails === 0) { peg$fail(peg$c149); } 2815 | } 2816 | 2817 | return s0; 2818 | } 2819 | 2820 | 2821 | var parser, edges, nodes; 2822 | 2823 | var defaultInPort = "IN", defaultOutPort = "OUT"; 2824 | 2825 | parser = this; 2826 | delete parser.properties; 2827 | delete parser.inports; 2828 | delete parser.outports; 2829 | delete parser.groups; 2830 | 2831 | edges = parser.edges = []; 2832 | 2833 | nodes = {}; 2834 | 2835 | var serialize, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; 2836 | 2837 | parser.validateContents = function(graph, options) { 2838 | // Ensure all nodes have a component 2839 | if (graph.processes) { 2840 | Object.keys(graph.processes).forEach(function (node) { 2841 | if (!graph.processes[node].component) { 2842 | throw new Error('Node "' + node + '" does not have a component defined'); 2843 | } 2844 | }); 2845 | } 2846 | // Ensure all inports point to existing nodes 2847 | if (graph.inports) { 2848 | Object.keys(graph.inports).forEach(function (port) { 2849 | var portDef = graph.inports[port]; 2850 | if (!graph.processes[portDef.process]) { 2851 | throw new Error('Inport "' + port + '" is connected to an undefined target node "' + portDef.process + '"'); 2852 | } 2853 | }); 2854 | } 2855 | // Ensure all outports point to existing nodes 2856 | if (graph.outports) { 2857 | Object.keys(graph.outports).forEach(function (port) { 2858 | var portDef = graph.outports[port]; 2859 | if (!graph.processes[portDef.process]) { 2860 | throw new Error('Outport "' + port + '" is connected to an undefined source node "' + portDef.process + '"'); 2861 | } 2862 | }); 2863 | } 2864 | // Ensure all edges have nodes defined 2865 | if (graph.connections) { 2866 | graph.connections.forEach(function (edge) { 2867 | if (edge.tgt && !graph.processes[edge.tgt.process]) { 2868 | if (edge.data) { 2869 | throw new Error('IIP containing "' + edge.data + '" is connected to an undefined target node "' + edge.tgt.process + '"'); 2870 | } 2871 | throw new Error('Edge from "' + edge.src.process + '" port "' + edge.src.port + '" is connected to an undefined target node "' + edge.tgt.process + '"'); 2872 | } 2873 | if (edge.src && !graph.processes[edge.src.process]) { 2874 | throw new Error('Edge to "' + edge.tgt.process + '" port "' + edge.tgt.port + '" is connected to an undefined source node "' + edge.src.process + '"'); 2875 | } 2876 | }); 2877 | } 2878 | }; 2879 | 2880 | parser.addNode = function (nodeName, comp) { 2881 | if (!nodes[nodeName]) { 2882 | nodes[nodeName] = {} 2883 | } 2884 | if (!!comp.comp) { 2885 | nodes[nodeName].component = comp.comp; 2886 | } 2887 | if (!!comp.meta) { 2888 | var metadata = {}; 2889 | for (var i = 0; i < comp.meta.length; i++) { 2890 | var item = comp.meta[i].split('='); 2891 | if (item.length === 1) { 2892 | item = ['routes', item[0]]; 2893 | } 2894 | var key = item[0]; 2895 | var value = item[1]; 2896 | if (key==='x' || key==='y') { 2897 | value = parseFloat(value); 2898 | } 2899 | metadata[key] = value; 2900 | } 2901 | nodes[nodeName].metadata=metadata; 2902 | } 2903 | 2904 | } 2905 | 2906 | var anonymousIndexes = {}; 2907 | var anonymousNodeNames = {}; 2908 | parser.addAnonymousNode = function(comp, offset) { 2909 | if (!anonymousNodeNames[offset]) { 2910 | var componentName = comp.comp.replace(/[^a-zA-Z0-9]+/, "_"); 2911 | anonymousIndexes[componentName] = (anonymousIndexes[componentName] || 0) + 1; 2912 | anonymousNodeNames[offset] = "_" + componentName + "_" + anonymousIndexes[componentName]; 2913 | this.addNode(anonymousNodeNames[offset], comp); 2914 | } 2915 | return anonymousNodeNames[offset]; 2916 | } 2917 | 2918 | parser.getResult = function () { 2919 | var result = { 2920 | inports: parser.inports || {}, 2921 | outports: parser.outports || {}, 2922 | groups: parser.groups || [], 2923 | processes: nodes || {}, 2924 | connections: parser.processEdges() 2925 | }; 2926 | 2927 | if (parser.properties) { 2928 | result.properties = parser.properties; 2929 | } 2930 | result.caseSensitive = options.caseSensitive || false; 2931 | 2932 | var validateSchema = parser.validateSchema; // default 2933 | if (typeof(options.validateSchema) !== 'undefined') { validateSchema = options.validateSchema; } // explicit option 2934 | if (validateSchema) { 2935 | if (typeof(tv4) === 'undefined') { 2936 | var tv4 = require("tv4"); 2937 | } 2938 | var schema = require("../schema/graph.json"); 2939 | var validation = tv4.validateMultiple(result, schema); 2940 | if (!validation.valid) { 2941 | throw new Error("fbp: Did not validate againt graph schema:\n" + JSON.stringify(validation.errors, null, 2)); 2942 | } 2943 | } 2944 | 2945 | if (typeof options.validateContents === 'undefined' || options.validateContents) { 2946 | parser.validateContents(result); 2947 | } 2948 | 2949 | return result; 2950 | } 2951 | 2952 | var flatten = function (array, isShallow) { 2953 | var index = -1, 2954 | length = array ? array.length : 0, 2955 | result = []; 2956 | 2957 | while (++index < length) { 2958 | var value = array[index]; 2959 | 2960 | if (value instanceof Array) { 2961 | Array.prototype.push.apply(result, isShallow ? value : flatten(value)); 2962 | } 2963 | else { 2964 | result.push(value); 2965 | } 2966 | } 2967 | return result; 2968 | } 2969 | 2970 | parser.registerAnnotation = function (key, value) { 2971 | if (!parser.properties) { 2972 | parser.properties = {}; 2973 | } 2974 | 2975 | if (key === 'runtime') { 2976 | parser.properties.environment = {}; 2977 | parser.properties.environment.type = value; 2978 | return; 2979 | } 2980 | 2981 | parser.properties[key] = value; 2982 | }; 2983 | 2984 | parser.registerInports = function (node, port, pub) { 2985 | if (!parser.inports) { 2986 | parser.inports = {}; 2987 | } 2988 | 2989 | if (!options.caseSensitive) { 2990 | pub = pub.toLowerCase(); 2991 | port = port.toLowerCase(); 2992 | } 2993 | 2994 | parser.inports[pub] = {process:node, port:port}; 2995 | } 2996 | parser.registerOutports = function (node, port, pub) { 2997 | if (!parser.outports) { 2998 | parser.outports = {}; 2999 | } 3000 | 3001 | if (!options.caseSensitive) { 3002 | pub = pub.toLowerCase(); 3003 | port = port.toLowerCase(); 3004 | } 3005 | 3006 | parser.outports[pub] = {process:node, port:port}; 3007 | } 3008 | 3009 | parser.registerEdges = function (edges) { 3010 | if (Array.isArray(edges)) { 3011 | edges.forEach(function (o, i) { 3012 | parser.edges.push(o); 3013 | }); 3014 | } 3015 | } 3016 | 3017 | parser.processEdges = function () { 3018 | var flats, grouped; 3019 | flats = flatten(parser.edges); 3020 | grouped = []; 3021 | var current = {}; 3022 | for (var i = 1; i < flats.length; i += 1) { 3023 | // skip over default ports at the beginning of lines (could also handle this in grammar) 3024 | if (("src" in flats[i - 1] || "data" in flats[i - 1]) && "tgt" in flats[i]) { 3025 | flats[i - 1].tgt = flats[i].tgt; 3026 | grouped.push(flats[i - 1]); 3027 | i++; 3028 | } 3029 | } 3030 | return grouped; 3031 | } 3032 | 3033 | function makeName(s) { 3034 | return s[0] + s[1].join(""); 3035 | } 3036 | 3037 | function makePort(process, port, defaultPort) { 3038 | if (!options.caseSensitive) { 3039 | defaultPort = defaultPort.toLowerCase() 3040 | } 3041 | var p = { 3042 | process: process, 3043 | port: port ? port.port : defaultPort 3044 | }; 3045 | if (port && port.index != null) { 3046 | p.index = port.index; 3047 | } 3048 | return p; 3049 | } 3050 | 3051 | function makeInPort(process, port) { 3052 | return makePort(process, port, defaultInPort); 3053 | } 3054 | function makeOutPort(process, port) { 3055 | return makePort(process, port, defaultOutPort); 3056 | } 3057 | 3058 | 3059 | peg$result = peg$startRuleFunction(); 3060 | 3061 | if (peg$result !== peg$FAILED && peg$currPos === input.length) { 3062 | return peg$result; 3063 | } else { 3064 | if (peg$result !== peg$FAILED && peg$currPos < input.length) { 3065 | peg$fail({ type: "end", description: "end of input" }); 3066 | } 3067 | 3068 | throw peg$buildException( 3069 | null, 3070 | peg$maxFailExpected, 3071 | peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, 3072 | peg$maxFailPos < input.length 3073 | ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) 3074 | : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) 3075 | ); 3076 | } 3077 | } 3078 | 3079 | return { 3080 | SyntaxError: peg$SyntaxError, 3081 | parse: peg$parse 3082 | }; 3083 | })(); -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const parser = require('./fbp'); 2 | const serialize = require('./serialize'); 3 | 4 | module.exports = { 5 | SyntaxError: parser.SyntaxError, 6 | parse: parser.parse, 7 | serialize, 8 | }; 9 | -------------------------------------------------------------------------------- /lib/serialize.js: -------------------------------------------------------------------------------- 1 | const indexOf = [].indexOf 2 | || function indexOf(item) { 3 | for (let i = 0, l = this.length; i < l; i += 1) { 4 | if (i in this && this[i] === item) return i; 5 | } 6 | return -1; 7 | }; 8 | 9 | module.exports = function serialize(graph) { 10 | let input; 11 | if (typeof (graph) === 'string') { 12 | input = JSON.parse(graph); 13 | } else { 14 | input = graph; 15 | } 16 | const namedComponents = []; 17 | let output = ''; 18 | function getName(n) { 19 | let name = n; 20 | if (input.processes[name].metadata != null) { 21 | name = input.processes[name].metadata.label; 22 | } 23 | if (name.indexOf('/') > -1) { 24 | name = name.split('/').pop(); 25 | } 26 | return name; 27 | } 28 | function getInOutName(n, data) { 29 | let name = n; 30 | if ((data.process != null) && (input.processes[data.process].metadata != null)) { 31 | name = input.processes[data.process].metadata.label; 32 | } else if (data.process != null) { 33 | name = data.process; 34 | } 35 | if (name.indexOf('/') > -1) { 36 | name = name.split('/').pop(); 37 | } 38 | return name; 39 | } 40 | if (input.properties) { 41 | if (input.properties.environment && input.properties.environment.type) { 42 | output += `# @runtime ${input.properties.environment.type}\n`; 43 | } 44 | Object.keys(input.properties).forEach((prop) => { 45 | if (!prop.match(/^[a-zA-Z0-9\-_]+$/)) { 46 | return; 47 | } 48 | const propval = input.properties[prop]; 49 | if (typeof propval !== 'string') { 50 | return; 51 | } 52 | if (!propval.match(/^[a-zA-Z0-9\-_\s.]+$/)) { 53 | return; 54 | } 55 | output += `# @${prop} ${propval}\n`; 56 | }); 57 | } 58 | Object.keys(input.inports).forEach((name) => { 59 | const inPort = input.inports[name]; 60 | const process = getInOutName(name, inPort); 61 | const publicName = input.caseSensitive ? name : name.toUpperCase(); 62 | inPort.port = input.caseSensitive ? inPort.port : inPort.port.toUpperCase(); 63 | output += `INPORT=${process}.${inPort.port}:${publicName}\n`; 64 | }); 65 | Object.keys(input.outports).forEach((name) => { 66 | const outPort = input.outports[name]; 67 | const process = getInOutName(name, outPort); 68 | const publicName = input.caseSensitive ? name : name.toUpperCase(); 69 | outPort.port = input.caseSensitive ? outPort.port : outPort.port.toUpperCase(); 70 | output += `OUTPORT=${process}.${outPort.port}:${publicName}\n`; 71 | }); 72 | output += '\n'; 73 | for (let i = 0; i < input.connections.length; i += 1) { 74 | const conn = input.connections[i]; 75 | if (conn.data != null) { 76 | const tgtPort = input.caseSensitive ? conn.tgt.port : conn.tgt.port.toUpperCase(); 77 | const tgtName = conn.tgt.process; 78 | const tgtProcess = input.processes[tgtName].component; 79 | let tgt = getName(tgtName); 80 | if (indexOf.call(namedComponents, tgtProcess) < 0) { 81 | tgt += `(${tgtProcess})`; 82 | namedComponents.push(tgtProcess); 83 | } 84 | output += `"${conn.data}" -> ${tgtPort} ${tgt}\n`; 85 | } else { 86 | const srcPort = input.caseSensitive ? conn.src.port : conn.src.port.toUpperCase(); 87 | const srcName = conn.src.process; 88 | const srcProcess = input.processes[srcName].component; 89 | let src = getName(srcName); 90 | if (indexOf.call(namedComponents, srcProcess) < 0) { 91 | src += `(${srcProcess})`; 92 | namedComponents.push(srcProcess); 93 | } 94 | const tgtPort = input.caseSensitive ? conn.tgt.port : conn.tgt.port.toUpperCase(); 95 | const tgtName = conn.tgt.process; 96 | const tgtProcess = input.processes[tgtName].component; 97 | let tgt = getName(tgtName); 98 | if (indexOf.call(namedComponents, tgtProcess) < 0) { 99 | tgt += `(${tgtProcess})`; 100 | namedComponents.push(tgtProcess); 101 | } 102 | output += `${src} ${srcPort} -> ${tgtPort} ${tgt}\n`; 103 | } 104 | } 105 | return output; 106 | }; 107 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fbp", 3 | "description": "Parser for the .fbp flow definition language", 4 | "version": "1.8.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/flowbased/fbp.git" 8 | }, 9 | "contributors": [ 10 | { 11 | "name": "James", 12 | "email": "aretecode@gmail.com" 13 | } 14 | ], 15 | "license": "MIT", 16 | "devDependencies": { 17 | "chai": "^4.0.2", 18 | "coveralls": "^3.0.0", 19 | "eslint": "^7.11.0", 20 | "eslint-config-airbnb-base": "^14.2.0", 21 | "eslint-plugin-chai": "0.0.1", 22 | "eslint-plugin-import": "^2.22.1", 23 | "eslint-plugin-mocha": "^8.0.0", 24 | "grunt": "^1.0.1", 25 | "grunt-contrib-watch": "^1.0.0", 26 | "grunt-karma": "^4.0.0", 27 | "grunt-mocha-test": "^0.13.2", 28 | "grunt-noflo-browser": "^2.0.2", 29 | "grunt-peg": "^2.0.1", 30 | "grunt-yaml": "^0.4.2", 31 | "json-loader": "^0.5.4", 32 | "karma": "^6.1.1", 33 | "karma-chai": "^0.1.0", 34 | "karma-chrome-launcher": "^3.1.0", 35 | "karma-mocha": "^2.0.1", 36 | "karma-mocha-reporter": "^2.2.5", 37 | "mocha": "^8.1.3", 38 | "noflo-component-loader": "^0.4.0", 39 | "noflo-webpack-config": "^2.0.2", 40 | "nyc": "^15.1.0", 41 | "tv4": "^1.2.7", 42 | "webpack": "^5.0.0", 43 | "webpack-cli": "^4.0.0" 44 | }, 45 | "keywords": [], 46 | "scripts": { 47 | "pretest": "eslint *.js lib/index.js lib/serialize.js spec/*.js", 48 | "test": "nyc grunt test" 49 | }, 50 | "main": "./lib/index", 51 | "bin": { 52 | "fbp": "./bin/fbp" 53 | }, 54 | "nyc": { 55 | "include": [ 56 | "lib/*.js" 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /schema/graph.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema", 3 | "id": "graph.json", 4 | "title": "FBP graph", 5 | "description": "A graph of FBP processes and connections between them.\nThis is the primary way of specifying FBP programs.\n", 6 | "name": "graph", 7 | "type": "object", 8 | "additionalProperties": false, 9 | "properties": { 10 | "caseSensitive": { 11 | "type": "boolean", 12 | "description": "Whether the graph port identifiers should be treated as case-sensitive" 13 | }, 14 | "properties": { 15 | "type": "object", 16 | "description": "User-defined properties attached to the graph.", 17 | "additionalProperties": true, 18 | "properties": { 19 | "name": { 20 | "type": "string", 21 | "description": "Name of the graph" 22 | }, 23 | "environment": { 24 | "type": "object", 25 | "description": "Information about the execution environment for the graph", 26 | "additionalProperties": true, 27 | "required": [ 28 | "type" 29 | ], 30 | "properties": { 31 | "type": { 32 | "type": "string", 33 | "description": "Runtime type the graph is for", 34 | "example": "noflo-nodejs" 35 | }, 36 | "content": { 37 | "type": "string", 38 | "description": "HTML fixture for browser-based graphs" 39 | } 40 | } 41 | }, 42 | "description": { 43 | "type": "string", 44 | "description": "Graph description" 45 | }, 46 | "icon": { 47 | "type": "string", 48 | "description": "Name of the icon that can be used for depicting the graph" 49 | } 50 | } 51 | }, 52 | "inports": { 53 | "type": [ 54 | "object", 55 | "undefined" 56 | ], 57 | "description": "Exported inports of the graph", 58 | "additionalProperties": true, 59 | "patternProperties": { 60 | "[a-z0-9]+": { 61 | "type": "object", 62 | "properties": { 63 | "process": { 64 | "type": "string" 65 | }, 66 | "port": { 67 | "type": "string" 68 | }, 69 | "metadata": { 70 | "type": "object", 71 | "additionalProperties": true, 72 | "required": [], 73 | "properties": { 74 | "x": { 75 | "type": "integer", 76 | "description": "X coordinate of a graph inport" 77 | }, 78 | "y": { 79 | "type": "integer", 80 | "description": "Y coordinate of a graph inport" 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | }, 88 | "outports": { 89 | "type": [ 90 | "object", 91 | "undefined" 92 | ], 93 | "description": "Exported outports of the graph", 94 | "additionalProperties": true, 95 | "patternProperties": { 96 | "[a-z0-9]+": { 97 | "type": "object", 98 | "properties": { 99 | "process": { 100 | "type": "string" 101 | }, 102 | "port": { 103 | "type": "string" 104 | }, 105 | "metadata": { 106 | "type": "object", 107 | "required": [], 108 | "additionalProperties": true, 109 | "properties": { 110 | "x": { 111 | "type": "integer", 112 | "description": "X coordinate of a graph outport" 113 | }, 114 | "y": { 115 | "type": "integer", 116 | "description": "Y coordinate of a graph outport" 117 | } 118 | } 119 | } 120 | } 121 | } 122 | } 123 | }, 124 | "groups": { 125 | "type": "array", 126 | "description": "List of groups of processes", 127 | "items": { 128 | "type": "object", 129 | "additionalProperties": false, 130 | "properties": { 131 | "name": { 132 | "type": "string" 133 | }, 134 | "nodes": { 135 | "type": "array", 136 | "items": { 137 | "type": "string" 138 | } 139 | }, 140 | "metadata": { 141 | "type": "object", 142 | "additionalProperties": true, 143 | "required": [], 144 | "properties": { 145 | "description": { 146 | "type": "string" 147 | } 148 | } 149 | } 150 | } 151 | } 152 | }, 153 | "processes": { 154 | "type": "object", 155 | "description": "The processes of this graph.\nEach process is an instance of a component.\n", 156 | "additionalProperties": false, 157 | "patternProperties": { 158 | "[a-zA-Z0-9_]+": { 159 | "type": "object", 160 | "properties": { 161 | "component": { 162 | "type": "string" 163 | }, 164 | "metadata": { 165 | "type": "object", 166 | "additionalProperties": true, 167 | "required": [], 168 | "properties": { 169 | "x": { 170 | "type": "integer", 171 | "description": "X coordinate of a graph node" 172 | }, 173 | "y": { 174 | "type": "integer", 175 | "description": "Y coordinate of a graph node" 176 | } 177 | } 178 | } 179 | } 180 | } 181 | } 182 | }, 183 | "connections": { 184 | "type": "array", 185 | "description": "Connections of the graph.\nA connection either connects ports of two processes, or specifices an IIP as initial input packet to a port.\n", 186 | "items": { 187 | "type": "object", 188 | "additionalProperties": false, 189 | "properties": { 190 | "src": { 191 | "type": "object", 192 | "additionalProperties": false, 193 | "properties": { 194 | "process": { 195 | "type": "string" 196 | }, 197 | "port": { 198 | "type": "string" 199 | }, 200 | "index": { 201 | "type": "integer" 202 | } 203 | } 204 | }, 205 | "tgt": { 206 | "type": "object", 207 | "additionalProperties": false, 208 | "properties": { 209 | "process": { 210 | "type": "string" 211 | }, 212 | "port": { 213 | "type": "string" 214 | }, 215 | "index": { 216 | "type": "integer" 217 | } 218 | } 219 | }, 220 | "data": {}, 221 | "metadata": { 222 | "type": "object", 223 | "additionalProperties": true, 224 | "required": [], 225 | "properties": { 226 | "route": { 227 | "type": "integer", 228 | "description": "Route identifier of a graph edge" 229 | }, 230 | "schema": { 231 | "type": "string", 232 | "format": "uri", 233 | "description": "JSON schema associated with a graph edge" 234 | }, 235 | "secure": { 236 | "type": "boolean", 237 | "description": "Whether edge data should be treated as secure" 238 | } 239 | } 240 | } 241 | } 242 | } 243 | } 244 | } 245 | } -------------------------------------------------------------------------------- /schemata/graph.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | "$schema": "http://json-schema.org/draft-04/schema" 3 | id: 'graph.json' 4 | title: 'FBP graph' 5 | description: | 6 | A graph of FBP processes and connections between them. 7 | This is the primary way of specifying FBP programs. 8 | name: "graph" 9 | type: "object" 10 | additionalProperties: false 11 | properties: 12 | caseSensitive: 13 | type: "boolean" 14 | description: "Whether the graph port identifiers should be treated as case-sensitive" 15 | properties: 16 | type: "object" 17 | description: "User-defined properties attached to the graph." 18 | additionalProperties: true 19 | properties: 20 | name: 21 | type: "string" 22 | description: "Name of the graph" 23 | environment: 24 | type: "object" 25 | description: "Information about the execution environment for the graph" 26 | additionalProperties: true 27 | required: ['type'] 28 | properties: 29 | type: 30 | type: "string" 31 | description: "Runtime type the graph is for" 32 | example: "noflo-nodejs" 33 | content: 34 | type: "string" 35 | description: "HTML fixture for browser-based graphs" 36 | description: 37 | type: "string" 38 | description: "Graph description" 39 | icon: 40 | type: "string" 41 | description: "Name of the icon that can be used for depicting the graph" 42 | inports: 43 | type: 44 | - object 45 | - undefined 46 | description: "Exported inports of the graph" 47 | additionalProperties: true 48 | patternProperties: 49 | "[a-z0-9]+": 50 | type: "object" 51 | properties: 52 | process: 53 | type: "string" 54 | port: 55 | type: "string" 56 | metadata: 57 | type: "object" 58 | additionalProperties: true 59 | required: [] 60 | properties: 61 | x: 62 | type: integer 63 | description: "X coordinate of a graph inport" 64 | y: 65 | type: integer 66 | description: "Y coordinate of a graph inport" 67 | outports: 68 | type: 69 | - object 70 | - undefined 71 | description: "Exported outports of the graph" 72 | additionalProperties: true 73 | patternProperties: 74 | "[a-z0-9]+": 75 | type: "object" 76 | properties: 77 | process: 78 | type: "string" 79 | port: 80 | type: "string" 81 | metadata: 82 | type: "object" 83 | required: [] 84 | additionalProperties: true 85 | properties: 86 | x: 87 | type: integer 88 | description: "X coordinate of a graph outport" 89 | y: 90 | type: integer 91 | description: "Y coordinate of a graph outport" 92 | groups: 93 | type: "array" 94 | description: "List of groups of processes" 95 | items: 96 | type: "object" 97 | additionalProperties: false 98 | properties: 99 | name: 100 | type: "string" 101 | nodes: 102 | type: "array" 103 | items: 104 | type: "string" 105 | metadata: 106 | type: object 107 | additionalProperties: true 108 | required: [] 109 | properties: 110 | description: 111 | type: string 112 | processes: 113 | type: "object" 114 | description: | 115 | The processes of this graph. 116 | Each process is an instance of a component. 117 | additionalProperties: false 118 | patternProperties: 119 | "[a-zA-Z0-9_]+": 120 | type: "object" 121 | properties: 122 | component: 123 | type: "string" 124 | metadata: 125 | type: "object" 126 | additionalProperties: true 127 | required: [] 128 | properties: 129 | x: 130 | type: integer 131 | description: "X coordinate of a graph node" 132 | y: 133 | type: integer 134 | description: "Y coordinate of a graph node" 135 | connections: 136 | type: "array" 137 | description: | 138 | Connections of the graph. 139 | A connection either connects ports of two processes, or specifices an IIP as initial input packet to a port. 140 | items: 141 | type: "object" 142 | additionalProperties: false 143 | properties: 144 | src: 145 | type: "object" 146 | additionalProperties: false 147 | properties: 148 | process: 149 | type: "string" 150 | port: 151 | type: "string" 152 | index: 153 | type: integer 154 | tgt: 155 | type: "object" 156 | additionalProperties: false 157 | properties: 158 | process: 159 | type: "string" 160 | port: 161 | type: "string" 162 | index: 163 | type: integer 164 | data: {} 165 | metadata: 166 | type: "object" 167 | additionalProperties: true 168 | required: [] 169 | properties: 170 | route: 171 | type: integer 172 | description: "Route identifier of a graph edge" 173 | schema: 174 | type: string 175 | format: "uri" 176 | description: "JSON schema associated with a graph edge" 177 | secure: 178 | type: boolean 179 | description: "Whether edge data should be treated as secure" 180 | -------------------------------------------------------------------------------- /spec/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "plugins": [ 4 | "chai", 5 | "mocha" 6 | ], 7 | "env": { 8 | "node": true, 9 | "mocha": true 10 | }, 11 | "rules": { 12 | "func-names": 0 13 | }, 14 | "globals": { 15 | "chai": false, 16 | "parser": false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spec/fbp.js: -------------------------------------------------------------------------------- 1 | if (typeof process !== 'undefined' && process.execPath && process.execPath.indexOf('node') !== -1) { 2 | // validate schema for every test on node.js. Don't have tv4 in the browser build 3 | parser.validateSchema = true; 4 | } 5 | 6 | describe('FBP parser', () => { 7 | it('should provide a parse method', () => chai.expect(parser.parse).to.be.a('function')); 8 | describe('with simple FBP string', () => { 9 | const fbpData = "'somefile' -> SOURCE Read(ReadFile)"; 10 | let graphData = null; 11 | it('should produce a graph JSON object', () => { 12 | graphData = parser.parse(fbpData, { 13 | caseSensitive: true, 14 | }); 15 | chai.expect(graphData).to.be.an('object'); 16 | chai.expect(graphData.caseSensitive).to.equal(true); 17 | }); 18 | describe('the generated graph', () => { 19 | it('should contain one node', () => chai.expect(graphData.processes).to.eql({ 20 | Read: { 21 | component: 'ReadFile', 22 | }, 23 | })); 24 | it('should contain an IIP', () => { 25 | chai.expect(graphData.connections).to.be.an('array'); 26 | chai.expect(graphData.connections.length).to.equal(1); 27 | chai.expect(graphData.connections[0]).to.eql({ 28 | data: 'somefile', 29 | tgt: { 30 | process: 'Read', 31 | port: 'SOURCE', 32 | }, 33 | }); 34 | }); 35 | }); 36 | }); 37 | describe('with three-statement FBP string', () => { 38 | const fbpData = '\'somefile.txt\' -> SOURCE Read(ReadFile) OUT -> IN Display(Output)'; 39 | let graphData = null; 40 | it('should produce a graph JSON object', () => { 41 | graphData = parser.parse(fbpData, { 42 | caseSensitive: true, 43 | }); 44 | chai.expect(graphData).to.be.an('object'); 45 | }); 46 | describe('the generated graph', () => { 47 | it('should contain two nodes', () => chai.expect(graphData.processes).to.eql({ 48 | Read: { 49 | component: 'ReadFile', 50 | }, 51 | Display: { 52 | component: 'Output', 53 | }, 54 | })); 55 | it('should contain an edge and an IIP', () => { 56 | chai.expect(graphData.connections).to.be.an('array'); 57 | chai.expect(graphData.connections.length).to.equal(2); 58 | }); 59 | it('should contain no exports', () => { 60 | chai.expect(graphData.exports).to.be.an('undefined'); 61 | chai.expect(graphData.inports).to.eql({}); 62 | chai.expect(graphData.outports).to.eql({}); 63 | }); 64 | }); 65 | }); 66 | describe('with three-statement FBP string without instantiation', () => it('should not fail', () => { 67 | const fbpData = `'db' -> KEY SetDb(Set) 68 | SplitDb(Split) OUT -> VALUE SetDb CONTEXT -> IN MergeContext(Merge)`; 69 | const graphData = parser.parse(fbpData, { 70 | caseSensitive: true, 71 | }); 72 | chai.expect(graphData.connections).to.have.length(3); 73 | })); 74 | describe('with no spaces around arrows', () => it('should not fail', () => { 75 | const fbpData = 'a(A)->b(B) ->c(C)-> d(D)->e(E)'; 76 | const graphData = parser.parse(fbpData, { 77 | caseSensitive: true, 78 | }); 79 | chai.expect(graphData.connections).to.have.length(4); 80 | })); 81 | describe('with anonymous nodes in an FBP string', () => { 82 | const fbpData = '(A) OUT -> IN (B) OUT -> IN (B)'; 83 | let graphData = null; 84 | it('should produce a graph JSON object', () => { 85 | graphData = parser.parse(fbpData, { 86 | caseSensitive: true, 87 | }); 88 | chai.expect(graphData).to.be.an('object'); 89 | }); 90 | describe('the generated graph', () => { 91 | it('should contain three nodes with unique names', () => chai.expect(graphData.processes).to.eql({ 92 | _A_1: { 93 | component: 'A', 94 | }, 95 | _B_1: { 96 | component: 'B', 97 | }, 98 | _B_2: { 99 | component: 'B', 100 | }, 101 | })); 102 | it('should contain two edges', () => chai.expect(graphData.connections).to.eql([ 103 | { 104 | src: { 105 | process: '_A_1', 106 | port: 'OUT', 107 | }, 108 | tgt: { 109 | process: '_B_1', 110 | port: 'IN', 111 | }, 112 | }, 113 | { 114 | src: { 115 | process: '_B_1', 116 | port: 'OUT', 117 | }, 118 | tgt: { 119 | process: '_B_2', 120 | port: 'IN', 121 | }, 122 | }, 123 | ])); 124 | it('should contain no exports', () => { 125 | chai.expect(graphData.exports).to.be.an('undefined'); 126 | chai.expect(graphData.inports).to.eql({}); 127 | chai.expect(graphData.outports).to.eql({}); 128 | }); 129 | }); 130 | }); 131 | describe('with default inport', () => { 132 | const fbpData = '(A) OUT -> (B)'; 133 | let graphData = null; 134 | it('should produce a graph JSON object', () => { 135 | graphData = parser.parse(fbpData, { 136 | caseSensitive: true, 137 | }); 138 | chai.expect(graphData).to.be.an('object'); 139 | }); 140 | describe('the generated graph', () => { 141 | it('should default port name to "IN"', () => chai.expect(graphData.connections).to.eql([ 142 | { 143 | src: { 144 | process: '_A_1', 145 | port: 'OUT', 146 | }, 147 | tgt: { 148 | process: '_B_1', 149 | port: 'IN', 150 | }, 151 | }, 152 | ])); 153 | it('should contain no exports', () => { 154 | chai.expect(graphData.exports).to.be.an('undefined'); 155 | chai.expect(graphData.inports).to.eql({}); 156 | chai.expect(graphData.outports).to.eql({}); 157 | }); 158 | }); 159 | }); 160 | describe('with default outport', () => { 161 | const fbpData = '(A) -> IN (B)'; 162 | let graphData = null; 163 | it('should produce a graph JSON object', () => { 164 | graphData = parser.parse(fbpData, { 165 | caseSensitive: true, 166 | }); 167 | chai.expect(graphData).to.be.an('object'); 168 | }); 169 | describe('the generated graph', () => { 170 | it('should default port name to "OUT"', () => chai.expect(graphData.connections).to.eql([ 171 | { 172 | src: { 173 | process: '_A_1', 174 | port: 'OUT', 175 | }, 176 | tgt: { 177 | process: '_B_1', 178 | port: 'IN', 179 | }, 180 | }, 181 | ])); 182 | it('should contain no exports', () => { 183 | chai.expect(graphData.exports).to.be.an('undefined'); 184 | chai.expect(graphData.inports).to.eql({}); 185 | chai.expect(graphData.outports).to.eql({}); 186 | }); 187 | }); 188 | }); 189 | describe('with default ports', () => { 190 | const fbpData = '(A) -> (B) -> (C)'; 191 | let graphData = null; 192 | it('should produce a graph JSON object', () => { 193 | graphData = parser.parse(fbpData, { 194 | caseSensitive: true, 195 | }); 196 | chai.expect(graphData).to.be.an('object'); 197 | }); 198 | describe('the generated graph', () => { 199 | it('should correctly use default ports', () => chai.expect(graphData.connections).to.eql([ 200 | { 201 | src: { 202 | process: '_A_1', 203 | port: 'OUT', 204 | }, 205 | tgt: { 206 | process: '_B_1', 207 | port: 'IN', 208 | }, 209 | }, 210 | { 211 | src: { 212 | process: '_B_1', 213 | port: 'OUT', 214 | }, 215 | tgt: { 216 | process: '_C_1', 217 | port: 'IN', 218 | }, 219 | }, 220 | ])); 221 | it('should contain no exports', () => { 222 | chai.expect(graphData.exports).to.be.an('undefined'); 223 | chai.expect(graphData.inports).to.eql({}); 224 | chai.expect(graphData.outports).to.eql({}); 225 | }); 226 | }); 227 | }); 228 | describe('with a more complex FBP string', () => { 229 | const fbpData = `'8003' -> LISTEN WebServer(HTTP/Server) REQUEST -> IN Profiler(HTTP/Profiler) OUT -> IN Authentication(HTTP/BasicAuth) 230 | Authentication() OUT -> IN GreetUser(HelloController) OUT[0] -> IN[0] WriteResponse(HTTP/WriteResponse) OUT -> IN Send(HTTP/SendResponse) 231 | 'hello.jade' -> SOURCE ReadTemplate(ReadFile) OUT -> TEMPLATE Render(Template) 232 | GreetUser() DATA -> OPTIONS Render() OUT -> STRING WriteResponse()`; 233 | let graphData = null; 234 | it('should produce a graph JSON object', () => { 235 | graphData = parser.parse(fbpData, { 236 | caseSensitive: true, 237 | }); 238 | chai.expect(graphData).to.be.an('object'); 239 | }); 240 | describe('the generated graph', () => { 241 | it('should contain eight nodes', () => { 242 | chai.expect(graphData.processes).to.be.an('object'); 243 | chai.expect(graphData.processes).to.have.keys(['WebServer', 'Profiler', 'Authentication', 'GreetUser', 'WriteResponse', 'Send', 'ReadTemplate', 'Render']); 244 | }); 245 | it('should contain ten edges and IIPs', () => { 246 | chai.expect(graphData.connections).to.be.an('array'); 247 | chai.expect(graphData.connections.length).to.equal(10); 248 | }); 249 | it('should contain no exports', () => chai.expect(graphData.exports).to.be.an('undefined')); 250 | }); 251 | }); 252 | describe('with multiple arrayport connections on same line', () => { 253 | const fbpData = `'test 1' -> IN[0] Mux(mux) OUT[0] -> IN Display(console) 254 | 'test 2' -> IN[1] Mux OUT[1] -> IN Display`; 255 | let graphData = null; 256 | it('should produce a graph JSON object', () => { 257 | graphData = parser.parse(fbpData, { 258 | caseSensitive: true, 259 | }); 260 | chai.expect(graphData).to.be.an('object'); 261 | chai.expect(graphData.processes).to.be.an('object'); 262 | chai.expect(graphData.connections).to.be.an('array'); 263 | }); 264 | describe('the generated graph', () => { 265 | it('should contain two nodes', () => chai.expect(graphData.processes).to.have.keys(['Mux', 'Display'])); 266 | it('should contain two IIPs', () => { 267 | const iips = graphData.connections.filter((conn) => conn.data); 268 | chai.expect(iips.length).to.equal(2); 269 | chai.expect(iips[0].data).to.eql('test 1'); 270 | chai.expect(iips[0].tgt).to.eql({ 271 | process: 'Mux', 272 | port: 'IN', 273 | index: 0, 274 | }); 275 | chai.expect(iips[1].data).to.eql('test 2'); 276 | chai.expect(iips[1].tgt).to.eql({ 277 | process: 'Mux', 278 | port: 'IN', 279 | index: 1, 280 | }); 281 | }); 282 | it('should contain two regular connections', () => { 283 | const connections = graphData.connections.filter((conn) => conn.src); 284 | chai.expect(connections.length).to.equal(2); 285 | chai.expect(connections[0].src).to.eql({ 286 | process: 'Mux', 287 | port: 'OUT', 288 | index: 0, 289 | }); 290 | chai.expect(connections[0].tgt).to.eql({ 291 | process: 'Display', 292 | port: 'IN', 293 | }); 294 | chai.expect(connections[1].src).to.eql({ 295 | process: 'Mux', 296 | port: 'OUT', 297 | index: 1, 298 | }); 299 | chai.expect(connections[1].tgt).to.eql({ 300 | process: 'Display', 301 | port: 'IN', 302 | }); 303 | }); 304 | it('should contain no exports', () => chai.expect(graphData.exports).to.be.an('undefined')); 305 | }); 306 | }); 307 | describe('with FBP string containing an IIP with whitespace', () => { 308 | const fbpData = '\'foo Bar BAZ\' -> IN Display(Output)'; 309 | let graphData = null; 310 | it('should produce a graph JSON object', () => { 311 | graphData = parser.parse(fbpData, { 312 | caseSensitive: true, 313 | }); 314 | chai.expect(graphData).to.be.an('object'); 315 | }); 316 | describe('the generated graph', () => { 317 | it('should contain a node', () => chai.expect(graphData.processes).to.eql({ 318 | Display: { 319 | component: 'Output', 320 | }, 321 | })); 322 | it('should contain an IIP', () => { 323 | chai.expect(graphData.connections).to.be.an('array'); 324 | chai.expect(graphData.connections.length).to.equal(1); 325 | chai.expect(graphData.connections[0].data).to.equal('foo Bar BAZ'); 326 | }); 327 | it('should contain no exports', () => { 328 | chai.expect(graphData.exports).to.be.an('undefined'); 329 | chai.expect(graphData.inports).to.eql({}); 330 | chai.expect(graphData.outports).to.eql({}); 331 | }); 332 | }); 333 | }); 334 | describe('with FBP string containing an empty IIP string', () => { 335 | const fbpData = '\'\' -> IN Display(Output)'; 336 | let graphData = null; 337 | it('should produce a graph JSON object', () => { 338 | graphData = parser.parse(fbpData, { 339 | caseSensitive: true, 340 | }); 341 | chai.expect(graphData).to.be.an('object'); 342 | }); 343 | describe('the generated graph', () => { 344 | it('should contain a node', () => chai.expect(graphData.processes).to.eql({ 345 | Display: { 346 | component: 'Output', 347 | }, 348 | })); 349 | it('should contain an IIP', () => { 350 | chai.expect(graphData.connections).to.be.an('array'); 351 | chai.expect(graphData.connections.length).to.equal(1); 352 | chai.expect(graphData.connections[0].data).to.equal(''); 353 | }); 354 | it('should contain no exports', () => { 355 | chai.expect(graphData.exports).to.be.an('undefined'); 356 | chai.expect(graphData.inports).to.eql({}); 357 | chai.expect(graphData.outports).to.eql({}); 358 | }); 359 | }); 360 | }); 361 | describe('with FBP string containing a JSON IIP string', () => { 362 | const fbpData = '{ "string": "s", "number": 123, "array": [1,2,3], "object": {}} -> IN Display(Output)'; 363 | let graphData = null; 364 | it('should produce a graph JSON object', () => { 365 | graphData = parser.parse(fbpData, { 366 | caseSensitive: true, 367 | }); 368 | chai.expect(graphData).to.be.an('object'); 369 | }); 370 | describe('the generated graph', () => { 371 | it('should contain a node', () => chai.expect(graphData.processes).to.eql({ 372 | Display: { 373 | component: 'Output', 374 | }, 375 | })); 376 | it('should contain an IIP', () => { 377 | chai.expect(graphData.connections).to.be.an('array'); 378 | chai.expect(graphData.connections.length).to.equal(1); 379 | chai.expect(graphData.connections[0].data).to.deep.equal({ 380 | string: 's', 381 | number: 123, 382 | array: [1, 2, 3], 383 | object: {}, 384 | }); 385 | }); 386 | it('should contain no exports', () => { 387 | chai.expect(graphData.exports).to.be.an('undefined'); 388 | chai.expect(graphData.inports).to.eql({}); 389 | chai.expect(graphData.outports).to.eql({}); 390 | }); 391 | }); 392 | }); 393 | describe('with FBP string containing comments', () => { 394 | const fbpData = `# Do stuff 395 | 'foo bar' -> IN Display(Output) # Here we show the string`; 396 | let graphData = null; 397 | it('should produce a graph JSON object', () => { 398 | graphData = parser.parse(fbpData, { 399 | caseSensitive: true, 400 | }); 401 | chai.expect(graphData).to.be.an('object'); 402 | }); 403 | describe('the generated graph', () => { 404 | it('should contain a node', () => chai.expect(graphData.processes).to.eql({ 405 | Display: { 406 | component: 'Output', 407 | }, 408 | })); 409 | it('should contain an IIP', () => { 410 | chai.expect(graphData.connections).to.be.an('array'); 411 | chai.expect(graphData.connections.length).to.equal(1); 412 | chai.expect(graphData.connections[0].data).to.equal('foo bar'); 413 | }); 414 | it('should contain no exports', () => { 415 | chai.expect(graphData.exports).to.be.an('undefined'); 416 | chai.expect(graphData.inports).to.eql({}); 417 | chai.expect(graphData.outports).to.eql({}); 418 | }); 419 | }); 420 | }); 421 | describe('with FBP string containing URL as IIP', () => { 422 | const fbpData = '\'http://localhost:5984/default\' -> URL Conn(couchdb/OpenDatabase)'; 423 | let graphData = null; 424 | it('should produce a graph JSON object', () => { 425 | graphData = parser.parse(fbpData, { 426 | caseSensitive: true, 427 | }); 428 | chai.expect(graphData).to.be.an('object'); 429 | }); 430 | describe('the generated graph', () => { 431 | it('should contain a node', () => chai.expect(graphData.processes).to.eql({ 432 | Conn: { 433 | component: 'couchdb/OpenDatabase', 434 | }, 435 | })); 436 | it('should contain an IIP', () => { 437 | chai.expect(graphData.connections).to.be.an('array'); 438 | chai.expect(graphData.connections.length).to.equal(1); 439 | chai.expect(graphData.connections[0].data).to.equal('http://localhost:5984/default'); 440 | }); 441 | it('should contain no exports', () => { 442 | chai.expect(graphData.exports).to.be.an('undefined'); 443 | chai.expect(graphData.inports).to.eql({}); 444 | chai.expect(graphData.outports).to.eql({}); 445 | }); 446 | }); 447 | }); 448 | describe('with FBP string containing RegExp as IIP', () => { 449 | const fbpData = `'_id=(d+.d+.d*)=http://iks-project.eu/%deliverable/$1' -> REGEXP MapDeliverableUri(MapPropertyValue) 450 | 'path=/_(?!(includes|layouts)' -> REGEXP MapDeliverableUri(MapPropertyValue) 451 | '@type=deliverable' -> PROPERTY SetDeliverableProps(SetProperty) 452 | '#foo' -> SELECTOR Get(dom/GetElement) 453 | 'Hi, {{ name }}' -> TEMPLATE Get`; 454 | let graphData = null; 455 | it('should produce a graph JSON object', () => { 456 | graphData = parser.parse(fbpData, { 457 | caseSensitive: true, 458 | }); 459 | chai.expect(graphData).to.be.an('object'); 460 | }); 461 | describe('the generated graph', () => { 462 | it('should contain two nodes', () => chai.expect(graphData.processes).to.eql({ 463 | MapDeliverableUri: { 464 | component: 'MapPropertyValue', 465 | }, 466 | SetDeliverableProps: { 467 | component: 'SetProperty', 468 | }, 469 | Get: { 470 | component: 'dom/GetElement', 471 | }, 472 | })); 473 | it('should contain IIPs', () => { 474 | chai.expect(graphData.connections).to.be.an('array'); 475 | chai.expect(graphData.connections.length).to.equal(5); 476 | chai.expect(graphData.connections[0].data).to.be.a('string'); 477 | }); 478 | it('should contain no exports', () => { 479 | chai.expect(graphData.exports).to.be.an('undefined'); 480 | chai.expect(graphData.inports).to.eql({}); 481 | chai.expect(graphData.outports).to.eql({}); 482 | }); 483 | }); 484 | }); 485 | describe('with FBP string with inports and outports', () => { 486 | const fbpData = `INPORT=Read.IN:FILENAME 487 | INPORT=Display.OPTIONS:OPTIONS 488 | OUTPORT=Display.OUT:OUT 489 | Read(ReadFile) OUT -> IN Display(Output)`; 490 | let graphData = null; 491 | it('should produce a graph JSON object', () => { 492 | graphData = parser.parse(fbpData, { 493 | caseSensitive: true, 494 | }); 495 | chai.expect(graphData).to.be.an('object'); 496 | }); 497 | describe('the generated graph', () => { 498 | it('should contain two nodes', () => chai.expect(graphData.processes).to.eql({ 499 | Read: { 500 | component: 'ReadFile', 501 | }, 502 | Display: { 503 | component: 'Output', 504 | }, 505 | })); 506 | it('should contain no legacy exports', () => chai.expect(graphData.exports).to.be.an('undefined')); 507 | it('should contain a single connection', () => { 508 | chai.expect(graphData.connections).to.be.an('array'); 509 | chai.expect(graphData.connections.length).to.equal(1); 510 | chai.expect(graphData.connections[0]).to.eql({ 511 | src: { 512 | process: 'Read', 513 | port: 'OUT', 514 | }, 515 | tgt: { 516 | process: 'Display', 517 | port: 'IN', 518 | }, 519 | }); 520 | }); 521 | it('should contain two inports', () => { 522 | chai.expect(graphData.inports).to.be.an('object'); 523 | chai.expect(graphData.inports.FILENAME).to.eql({ 524 | process: 'Read', 525 | port: 'IN', 526 | }); 527 | chai.expect(graphData.inports.OPTIONS).to.eql({ 528 | process: 'Display', 529 | port: 'OPTIONS', 530 | }); 531 | }); 532 | it('should contain an outport', () => { 533 | chai.expect(graphData.outports).to.be.an('object'); 534 | chai.expect(graphData.outports.OUT).to.eql({ 535 | process: 'Display', 536 | port: 'OUT', 537 | }); 538 | }); 539 | }); 540 | }); 541 | describe('with FBP string with legacy EXPORTs', () => { 542 | const fbpData = `EXPORT=Read.IN:FILENAME 543 | Read(ReadFile) OUT -> IN Display(Output)`; 544 | it('should fail', () => { 545 | chai.expect(() => parser.parse(fbpData, { 546 | caseSensitive: true, 547 | })).to.throw(Error); 548 | }); 549 | }); 550 | describe('with FBP string containing node metadata', () => { 551 | const fbpData = `Read(ReadFile) OUT -> IN Display(Output:foo=bar) 552 | 553 | # And we drop the rest 554 | Display OUT -> IN Drop(Drop:foo=baz,baz=/foo/bar)`; 555 | let graphData = null; 556 | it('should produce a graph JSON object', () => { 557 | graphData = parser.parse(fbpData, { 558 | caseSensitive: true, 559 | }); 560 | chai.expect(graphData).to.be.an('object'); 561 | }); 562 | it('should contain nodes with named routes', () => chai.expect(graphData.processes).to.eql({ 563 | Read: { 564 | component: 'ReadFile', 565 | }, 566 | Display: { 567 | component: 'Output', 568 | metadata: { 569 | foo: 'bar', 570 | }, 571 | }, 572 | Drop: { 573 | component: 'Drop', 574 | metadata: { 575 | foo: 'baz', 576 | baz: '/foo/bar', 577 | }, 578 | }, 579 | })); 580 | it('should contain two edges', () => { 581 | chai.expect(graphData.connections).to.be.an('array'); 582 | chai.expect(graphData.connections.length).to.equal(2); 583 | }); 584 | it('should contain no exports', () => { 585 | chai.expect(graphData.exports).to.be.an('undefined'); 586 | chai.expect(graphData.inports).to.eql({}); 587 | chai.expect(graphData.outports).to.eql({}); 588 | }); 589 | }); 590 | describe('with FBP string containing node x/y metadata', () => { 591 | const fbpData = 'Read(ReadFile) OUT -> IN Display(Output:foo=bar,x=17,y=42)'; 592 | let graphData = null; 593 | it('should produce a graph JSON object', () => { 594 | graphData = parser.parse(fbpData, { 595 | caseSensitive: true, 596 | }); 597 | chai.expect(graphData).to.be.an('object'); 598 | }); 599 | it('should contain nodes with numerical x/y metadata', () => chai.expect(graphData.processes).to.eql({ 600 | Read: { 601 | component: 'ReadFile', 602 | }, 603 | Display: { 604 | component: 'Output', 605 | metadata: { 606 | foo: 'bar', 607 | x: 17, 608 | y: 42, 609 | }, 610 | }, 611 | })); 612 | }); 613 | describe('with an invalid FBP string', () => { 614 | const fbpData = '\'foo\' --> Display(Output)'; 615 | it('should fail with an Exception', () => { 616 | chai.expect(() => parser.parse(fbpData, { 617 | caseSensitive: true, 618 | })).to.throw(Error); 619 | }); 620 | }); 621 | describe('with a component that contains dashes in name', () => { 622 | const fbpData = "'somefile' -> SOURCE Read(my-cool-component/ReadFile)"; 623 | let graphData = null; 624 | it('should produce a graph JSON object', () => { 625 | graphData = parser.parse(fbpData, { 626 | caseSensitive: true, 627 | }); 628 | chai.expect(graphData).to.be.an('object'); 629 | }); 630 | describe('the generated graph', () => { 631 | it('should contain one node', () => chai.expect(graphData.processes).to.eql({ 632 | Read: { 633 | component: 'my-cool-component/ReadFile', 634 | }, 635 | })); 636 | it('should contain an IIP', () => { 637 | chai.expect(graphData.connections).to.be.an('array'); 638 | chai.expect(graphData.connections.length).to.equal(1); 639 | }); 640 | }); 641 | }); 642 | describe('with commas to separate statements', () => { 643 | const fbpData = "'Hello' -> IN Foo(Component), 'World' -> IN Bar(OtherComponent), Foo OUT -> DATA Bar"; 644 | let graphData = null; 645 | it('should produce a graph JSON object', () => { 646 | graphData = parser.parse(fbpData, { 647 | caseSensitive: true, 648 | }); 649 | chai.expect(graphData).to.be.an('object'); 650 | }); 651 | describe('the generated graph', () => { 652 | it('should contain two nodes', () => chai.expect(graphData.processes).to.eql({ 653 | Foo: { 654 | component: 'Component', 655 | }, 656 | Bar: { 657 | component: 'OtherComponent', 658 | }, 659 | })); 660 | it('should contain two IIPs and one edge', () => chai.expect(graphData.connections).to.eql([ 661 | { 662 | data: 'Hello', 663 | tgt: { 664 | process: 'Foo', 665 | port: 'IN', 666 | }, 667 | }, 668 | { 669 | data: 'World', 670 | tgt: { 671 | process: 'Bar', 672 | port: 'IN', 673 | }, 674 | }, 675 | { 676 | src: { 677 | process: 'Foo', 678 | port: 'OUT', 679 | }, 680 | tgt: { 681 | process: 'Bar', 682 | port: 'DATA', 683 | }, 684 | }, 685 | ])); 686 | }); 687 | }); 688 | describe('with underscores and numbers in ports, nodes, and components', () => { 689 | const fbpData = "'Hello 09' -> IN_2 Foo_Node_42(Component_15)"; 690 | let graphData = null; 691 | it('should produce a graph JSON object', () => { 692 | graphData = parser.parse(fbpData, { 693 | caseSensitive: true, 694 | }); 695 | chai.expect(graphData).to.be.an('object'); 696 | }); 697 | describe('the generated graph', () => { 698 | it('should contain one node', () => chai.expect(graphData.processes).to.eql({ 699 | Foo_Node_42: { 700 | component: 'Component_15', 701 | }, 702 | })); 703 | it('should contain an IIP', () => { 704 | chai.expect(graphData.connections).to.be.an('array'); 705 | chai.expect(graphData.connections.length).to.equal(1); 706 | chai.expect(graphData.connections[0]).to.eql({ 707 | data: 'Hello 09', 708 | tgt: { 709 | process: 'Foo_Node_42', 710 | port: 'IN_2', 711 | }, 712 | }); 713 | }); 714 | }); 715 | }); 716 | describe('with dashes and numbers in nodes, and components', () => { 717 | const fbpData = "'Hello 09' -> IN_2 Foo-Node-42(Component-15)"; 718 | let graphData = null; 719 | it('should produce a graph JSON object', () => { 720 | graphData = parser.parse(fbpData, { 721 | caseSensitive: true, 722 | }); 723 | chai.expect(graphData).to.be.an('object'); 724 | }); 725 | describe('the generated graph', () => { 726 | it('should contain one node', () => chai.expect(graphData.processes).to.eql({ 727 | 'Foo-Node-42': { 728 | component: 'Component-15', 729 | }, 730 | })); 731 | it('should contain an IIP', () => { 732 | chai.expect(graphData.connections).to.be.an('array'); 733 | chai.expect(graphData.connections.length).to.equal(1); 734 | chai.expect(graphData.connections[0]).to.eql({ 735 | data: 'Hello 09', 736 | tgt: { 737 | process: 'Foo-Node-42', 738 | port: 'IN_2', 739 | }, 740 | }); 741 | }); 742 | }); 743 | }); 744 | describe('with FBP string containing port indexes', () => { 745 | const fbpData = `Read(ReadFile) OUT[1] -> IN Display(Output:foo=bar) 746 | 747 | # And we drop the rest 748 | Display OUT -> IN[0] Drop(Drop:foo=baz,baz=/foo/bar)`; 749 | let graphData = null; 750 | it('should produce a graph JSON object', () => { 751 | graphData = parser.parse(fbpData, { 752 | caseSensitive: true, 753 | }); 754 | chai.expect(graphData).to.be.an('object'); 755 | }); 756 | it('should contain nodes with named routes', () => chai.expect(graphData.processes).to.eql({ 757 | Read: { 758 | component: 'ReadFile', 759 | }, 760 | Display: { 761 | component: 'Output', 762 | metadata: { 763 | foo: 'bar', 764 | }, 765 | }, 766 | Drop: { 767 | component: 'Drop', 768 | metadata: { 769 | foo: 'baz', 770 | baz: '/foo/bar', 771 | }, 772 | }, 773 | })); 774 | it('should contain two edges', () => { 775 | chai.expect(graphData.connections).to.be.an('array'); 776 | chai.expect(graphData.connections).to.eql([ 777 | { 778 | src: { 779 | process: 'Read', 780 | port: 'OUT', 781 | index: 1, 782 | }, 783 | tgt: { 784 | process: 'Display', 785 | port: 'IN', 786 | }, 787 | }, 788 | { 789 | src: { 790 | process: 'Display', 791 | port: 'OUT', 792 | }, 793 | tgt: { 794 | process: 'Drop', 795 | port: 'IN', 796 | index: 0, 797 | }, 798 | }, 799 | ]); 800 | }); 801 | it('should contain no exports', () => { 802 | chai.expect(graphData.exports).to.be.an('undefined'); 803 | chai.expect(graphData.inports).to.eql({}); 804 | chai.expect(graphData.outports).to.eql({}); 805 | }); 806 | }); 807 | describe('with case-sensitive FBP string', () => { 808 | const fbpData = "'Hello' -> in Foo(Component), 'World' -> inPut Bar(OtherComponent), Foo outPut -> data Bar"; 809 | let graphData = null; 810 | it('should produce a graph JSON object', () => { 811 | graphData = parser.parse(fbpData, { 812 | caseSensitive: true, 813 | }); 814 | chai.expect(graphData).to.be.an('object'); 815 | }); 816 | describe('the generated graph', () => { 817 | it('should contain two nodes', () => chai.expect(graphData.processes).to.eql({ 818 | Foo: { 819 | component: 'Component', 820 | }, 821 | Bar: { 822 | component: 'OtherComponent', 823 | }, 824 | })); 825 | it('should contain two IIPs and one edge', () => chai.expect(graphData.connections).to.eql([ 826 | { 827 | data: 'Hello', 828 | tgt: { 829 | process: 'Foo', 830 | port: 'in', 831 | }, 832 | }, 833 | { 834 | data: 'World', 835 | tgt: { 836 | process: 'Bar', 837 | port: 'inPut', 838 | }, 839 | }, 840 | { 841 | src: { 842 | process: 'Foo', 843 | port: 'outPut', 844 | }, 845 | tgt: { 846 | process: 'Bar', 847 | port: 'data', 848 | }, 849 | }, 850 | ])); 851 | }); 852 | }); 853 | describe('should convert port names to lowercase by default', () => { 854 | const fbpData = `INPORT=Read.IN:FILENAME 855 | INPORT=Display.OPTIONS:OPTIONS 856 | OUTPORT=Display.OUT:OUT 857 | Read(ReadFile) OUT -> IN Display(Output) 858 | 859 | ReadIndexed(ReadFile) OUT[1] -> IN DisplayIndexed(Output:foo=bar) 860 | DisplayIndexed OUT -> IN[0] Drop(Drop:foo=baz,baz=/foo/bar)`; 861 | let graphData = null; 862 | beforeEach(() => { 863 | graphData = parser.parse(fbpData); 864 | }); 865 | it('should produce a graph JSON object', () => { 866 | chai.expect(graphData).to.be.an('object'); 867 | chai.expect(graphData.caseSensitive).to.equal(false); 868 | }); 869 | it('should contain connections', () => { 870 | chai.expect(graphData.connections).to.be.an('array'); 871 | chai.expect(graphData.connections.length).to.equal(3); 872 | chai.expect(graphData.connections).to.eql([ 873 | { 874 | src: { 875 | process: 'Read', 876 | port: 'out', 877 | }, 878 | tgt: { 879 | process: 'Display', 880 | port: 'in', 881 | }, 882 | }, 883 | { 884 | src: { 885 | process: 'ReadIndexed', 886 | port: 'out', 887 | index: 1, 888 | }, 889 | tgt: { 890 | process: 'DisplayIndexed', 891 | port: 'in', 892 | }, 893 | }, 894 | { 895 | src: { 896 | process: 'DisplayIndexed', 897 | port: 'out', 898 | }, 899 | tgt: { 900 | process: 'Drop', 901 | port: 'in', 902 | index: 0, 903 | }, 904 | }, 905 | ]); 906 | }); 907 | it('should contain two inports', () => { 908 | chai.expect(graphData.inports).to.be.an('object'); 909 | chai.expect(graphData.inports.filename).to.eql({ 910 | process: 'Read', 911 | port: 'in', 912 | }); 913 | chai.expect(graphData.inports.options).to.eql({ 914 | process: 'Display', 915 | port: 'options', 916 | }); 917 | }); 918 | it('should contain an outport', () => { 919 | chai.expect(graphData.outports).to.be.an('object'); 920 | chai.expect(graphData.outports.out).to.eql({ 921 | process: 'Display', 922 | port: 'out', 923 | }); 924 | }); 925 | }); 926 | describe('with FBP string with source node that doesn\'t have a component defined', () => { 927 | const fbpData = 'instanceMissingComponentName OUT -> (core/Output)'; 928 | it('should fail', () => { 929 | chai.expect(() => parser.parse(fbpData, { 930 | caseSensitive: true, 931 | })).to.throw(Error, 'Edge to "_core_Output_1" port "IN" is connected to an undefined source node "instanceMissingComponentName"'); 932 | }); 933 | }); 934 | describe('with FBP string with IIP sent to node that doesn\'t have a component defined', () => { 935 | const fbpData = '\'localhost\' -> IN instanceMissingComponentName'; 936 | it('should fail', () => { 937 | chai.expect(() => parser.parse(fbpData, { 938 | caseSensitive: true, 939 | })).to.throw(Error, 'IIP containing "localhost" is connected to an undefined target node "instanceMissingComponentName"'); 940 | }); 941 | }); 942 | describe('with FBP string with target node that doesn\'t have a component defined', () => { 943 | const fbpData = 'a(A)->b(B) ->c(C)-> d(D)->e'; 944 | it('should fail', () => { 945 | chai.expect(() => parser.parse(fbpData, { 946 | caseSensitive: true, 947 | })).to.throw(Error, 'Edge from "d" port "OUT" is connected to an undefined target node "e"'); 948 | }); 949 | }); 950 | describe('with FBP string with exported inport pointing to non-existing node', () => { 951 | const fbpData = `INPORT=noexist.IN:broken 952 | INPORT=exist.IN:works 953 | exist(foo/Bar)`; 954 | it('should fail', () => { 955 | chai.expect(() => parser.parse(fbpData, { 956 | caseSensitive: true, 957 | })).to.throw(Error, 'Inport "broken" is connected to an undefined target node "noexist"'); 958 | }); 959 | }); 960 | describe('with FBP string with exported outport pointing to non-existing node', () => { 961 | const fbpData = `INPORT=exist.IN:works 962 | OUTPORT=noexist.OUT:broken 963 | exist(foo/Bar)`; 964 | it('should fail', () => { 965 | chai.expect(() => parser.parse(fbpData, { 966 | caseSensitive: true, 967 | })).to.throw(Error, 'Outport "broken" is connected to an undefined source node "noexist"'); 968 | }); 969 | }); 970 | describe('with FBP string containing a runtime annotation', () => { 971 | const fbpData = `# @runtime foo 972 | 'somefile' -> SOURCE Read(ReadFile)`; 973 | let graphData = null; 974 | it('should produce a graph JSON object', () => { 975 | graphData = parser.parse(fbpData, { 976 | caseSensitive: true, 977 | }); 978 | chai.expect(graphData).to.be.an('object'); 979 | chai.expect(graphData.caseSensitive).to.equal(true); 980 | }); 981 | it('should contain the runtime type property', () => { 982 | chai.expect(graphData.properties.environment.type).to.equal('foo'); 983 | }); 984 | }); 985 | describe('with FBP string containing a name annotation', () => { 986 | const fbpData = `# @name ReadSomefile 987 | 'somefile' -> SOURCE Read(ReadFile)`; 988 | let graphData = null; 989 | it('should produce a graph JSON object', () => { 990 | graphData = parser.parse(fbpData, { 991 | caseSensitive: true, 992 | }); 993 | chai.expect(graphData).to.be.an('object'); 994 | chai.expect(graphData.caseSensitive).to.equal(true); 995 | }); 996 | it('should contain the name', () => chai.expect(graphData.properties.name).to.equal('ReadSomefile')); 997 | }); 998 | describe('with FBP string containing two annotations', () => { 999 | const fbpData = `# @runtime foo 1000 | # @name ReadSomefile 1001 | 'somefile' -> SOURCE Read(ReadFile)`; 1002 | let graphData = null; 1003 | it('should produce a graph JSON object', () => { 1004 | graphData = parser.parse(fbpData, { 1005 | caseSensitive: true, 1006 | }); 1007 | chai.expect(graphData).to.be.an('object'); 1008 | chai.expect(graphData.caseSensitive).to.equal(true); 1009 | }); 1010 | it('should contain the runtime type property', () => chai.expect(graphData.properties.environment.type).to.equal('foo')); 1011 | it('should contain the name', () => chai.expect(graphData.properties.name).to.equal('ReadSomefile')); 1012 | }); 1013 | }); 1014 | -------------------------------------------------------------------------------- /spec/json.js: -------------------------------------------------------------------------------- 1 | if (typeof process !== 'undefined' && process.execPath && process.execPath.indexOf('node') !== -1) { 2 | // validate schema for every test on node.js. Don't have tv4 in the browser build 3 | parser.validateSchema = true; 4 | } 5 | 6 | describe('JSON to FBP parser', () => { 7 | it('should provide a parse method', () => { 8 | chai.expect(parser.parse).to.be.a('function'); 9 | chai.expect(parser.serialize).to.be.a('function'); 10 | }); 11 | describe('roundtrip', () => describe('with simple FBP string', () => { 12 | const fbpData = `'8003' -> LISTEN WebServer(HTTP/Server) REQUEST -> IN Profiler(HTTP/Profiler) OUT -> IN Authentication(HTTP/BasicAuth) 13 | Authentication() OUT -> IN GreetUser(HelloController) OUT[0] -> IN[0] WriteResponse(HTTP/WriteResponse) OUT -> IN Send(HTTP/SendResponse) 14 | 'hello.jade' -> SOURCE ReadTemplate(ReadFile) OUT -> TEMPLATE Render(Template) 15 | GreetUser() DATA -> OPTIONS Render() OUT -> STRING WriteResponse()`; 16 | let roundTrippedFbpData = ''; 17 | let graphData = null; 18 | let graphData2 = null; 19 | it('should produce a graph JSON object', () => { 20 | // fbp -> json 21 | graphData = parser.parse(fbpData, { 22 | caseSensitive: true, 23 | }); 24 | chai.expect(graphData).to.be.an('object'); 25 | chai.expect(graphData.caseSensitive).to.equal(true); 26 | // json -> fbp 27 | roundTrippedFbpData = parser.serialize(graphData); 28 | // fbp -> json 29 | graphData2 = parser.parse(roundTrippedFbpData, { 30 | caseSensitive: true, 31 | }); 32 | // json -> fbp, to test 33 | const fbp2 = parser.serialize(graphData2); 34 | chai.expect(roundTrippedFbpData).to.equal(fbp2); 35 | }); 36 | describe('the generated graph', () => { 37 | it('should contain eight nodes', () => { 38 | chai.expect(graphData2.processes).to.be.an('object'); 39 | chai.expect(graphData2.processes).to.have.keys(['WebServer', 'Profiler', 'Authentication', 'GreetUser', 'WriteResponse', 'Send', 'ReadTemplate', 'Render']); 40 | }); 41 | it('should contain ten edges and IIPs', () => { 42 | chai.expect(graphData2.connections).to.be.an('array'); 43 | chai.expect(graphData2.connections.length).to.equal(10); 44 | }); 45 | it('should contain no exports', () => chai.expect(graphData2.exports).to.be.an('undefined')); 46 | }); 47 | })); 48 | describe('with flowhub json graph (from ingress c-base table)', () => { 49 | const jsonData = `{ 50 | "properties": { 51 | "name": "ConfigPaths", 52 | "environment": { 53 | "type": "noflo-nodejs" 54 | }, 55 | "description": "Read Ingress Table configuration files", 56 | "icon": "file" 57 | }, 58 | "inports": { 59 | "envvar": { 60 | "process": "core/ReadEnv_2mde7", 61 | "port": "key", 62 | "metadata": { 63 | "x": -612, 64 | "y": 432, 65 | "width": 72, 66 | "height": 72 67 | } 68 | }, 69 | "serverfile": { 70 | "process": "core/Repeat_ktvob", 71 | "port": "in", 72 | "metadata": { 73 | "x": -612, 74 | "y": 324, 75 | "width": 72, 76 | "height": 72 77 | } 78 | }, 79 | "portalfile": { 80 | "process": "core/Repeat_r65df", 81 | "port": "in", 82 | "metadata": { 83 | "x": -612, 84 | "y": 540, 85 | "width": 72, 86 | "height": 72 87 | } 88 | } 89 | }, 90 | "outports": { 91 | "error": { 92 | "process": "core/ReadEnv_2mde7", 93 | "port": "error", 94 | "metadata": { 95 | "x": -324, 96 | "y": 684, 97 | "width": 72, 98 | "height": 72 99 | } 100 | }, 101 | "serverfile": { 102 | "process": "strings/CompileString_pjcg2", 103 | "port": "out", 104 | "metadata": { 105 | "x": 0, 106 | "y": 432, 107 | "width": 72, 108 | "height": 72 109 | } 110 | }, 111 | "portalfile": { 112 | "process": "strings/CompileString_667tt", 113 | "port": "out", 114 | "metadata": { 115 | "x": 0, 116 | "y": 540, 117 | "width": 72, 118 | "height": 72 119 | } 120 | } 121 | }, 122 | "groups": [], 123 | "processes": { 124 | "core/ReadEnv_2mde7": { 125 | "component": "core/ReadEnv", 126 | "metadata": { 127 | "label": "core/ReadEnv", 128 | "x": -468, 129 | "y": 432, 130 | "width": 72, 131 | "height": 72 132 | } 133 | }, 134 | "core/Repeat_ktvob": { 135 | "component": "core/Repeat", 136 | "metadata": { 137 | "label": "core/Repeat", 138 | "x": -324, 139 | "y": 324, 140 | "width": 72, 141 | "height": 72 142 | } 143 | }, 144 | "packets/DoNotDisconnect_uev3m": { 145 | "component": "packets/DoNotDisconnect", 146 | "metadata": { 147 | "label": "packets/DoNotDisconnect", 148 | "x": -324, 149 | "y": 432, 150 | "width": 72, 151 | "height": 72 152 | } 153 | }, 154 | "strings/CompileString_pjcg2": { 155 | "component": "strings/CompileString", 156 | "metadata": { 157 | "label": "strings/CompileString", 158 | "x": -144, 159 | "y": 432, 160 | "width": 72, 161 | "height": 72 162 | } 163 | }, 164 | "strings/CompileString_667tt": { 165 | "component": "strings/CompileString", 166 | "metadata": { 167 | "label": "strings/CompileString", 168 | "x": -144, 169 | "y": 540, 170 | "width": 72, 171 | "height": 72 172 | } 173 | }, 174 | "core/Repeat_r65df": { 175 | "component": "core/Repeat", 176 | "metadata": { 177 | "label": "core/Repeat", 178 | "x": -324, 179 | "y": 540, 180 | "width": 72, 181 | "height": 72 182 | } 183 | } 184 | }, 185 | "connections": [ 186 | { 187 | "src": { 188 | "process": "core/Repeat_r65df", 189 | "port": "out" 190 | }, 191 | "tgt": { 192 | "process": "strings/CompileString_667tt", 193 | "port": "in" 194 | } 195 | }, 196 | { 197 | "src": { 198 | "process": "core/Repeat_ktvob", 199 | "port": "out" 200 | }, 201 | "tgt": { 202 | "process": "strings/CompileString_pjcg2", 203 | "port": "in" 204 | } 205 | }, 206 | { 207 | "src": { 208 | "process": "core/ReadEnv_2mde7", 209 | "port": "out" 210 | }, 211 | "tgt": { 212 | "process": "packets/DoNotDisconnect_uev3m", 213 | "port": "in" 214 | }, 215 | "metadata": { 216 | "route": null 217 | } 218 | }, 219 | { 220 | "src": { 221 | "process": "packets/DoNotDisconnect_uev3m", 222 | "port": "out" 223 | }, 224 | "tgt": { 225 | "process": "strings/CompileString_pjcg2", 226 | "port": "in" 227 | } 228 | }, 229 | { 230 | "src": { 231 | "process": "packets/DoNotDisconnect_uev3m", 232 | "port": "out" 233 | }, 234 | "tgt": { 235 | "process": "strings/CompileString_667tt", 236 | "port": "in" 237 | } 238 | }, 239 | { 240 | "data": "/", 241 | "tgt": { 242 | "process": "strings/CompileString_pjcg2", 243 | "port": "delimiter" 244 | } 245 | }, 246 | { 247 | "data": "/", 248 | "tgt": { 249 | "process": "strings/CompileString_667tt", 250 | "port": "delimiter" 251 | } 252 | } 253 | ] 254 | }`; 255 | it('should produce a graph JSON object', () => { 256 | const fbpData = parser.serialize(jsonData); 257 | const jsonFromFbp = parser.parse(fbpData, { 258 | caseSensitive: true, 259 | }); 260 | chai.expect(jsonFromFbp).to.be.an('object'); 261 | }); 262 | it('should have retained properties', () => { 263 | const fbpData = parser.serialize(jsonData); 264 | const jsonFromFbp = parser.parse(fbpData, { 265 | caseSensitive: true, 266 | }); 267 | chai.expect(jsonFromFbp.properties).to.eql(JSON.parse(jsonData).properties); 268 | }); 269 | }); 270 | describe('roundtrip with FBP string with inports and outports', () => { 271 | let fbpData = `INPORT=Read.IN:FILENAME 272 | INPORT=Display.OPTIONS:OPTIONS 273 | OUTPORT=Display.OUT:OUT 274 | Read(ReadFile) OUT -> IN Display(Output)`; 275 | let graphData = null; 276 | let graphData2 = null; 277 | it('should produce a graph JSON object', () => { 278 | // $1 fbp -> json 279 | graphData = parser.parse(fbpData, { 280 | caseSensitive: true, 281 | }); 282 | chai.expect(graphData).to.be.an('object'); 283 | // json -> fbp 284 | const jsonGraph = parser.serialize(graphData); 285 | // fbp -> json 286 | graphData2 = parser.parse(jsonGraph, { 287 | caseSensitive: true, 288 | }); 289 | // $2 json -> fbp 290 | let fbpData2 = parser.serialize(graphData2); 291 | // remove the formatting 292 | fbpData = fbpData.replace(/(\n)+/g, ''); 293 | fbpData2 = fbpData2.replace(/(\n)+/g, ''); 294 | // make sure $1 and $2 match 295 | chai.expect(fbpData).to.equal(fbpData2); 296 | }); 297 | describe('the generated graph', () => { 298 | it('should contain two nodes', () => chai.expect(graphData2.processes).to.eql({ 299 | Read: { 300 | component: 'ReadFile', 301 | }, 302 | Display: { 303 | component: 'Output', 304 | }, 305 | })); 306 | it('should contain no legacy exports', () => chai.expect(graphData2.exports).to.be.an('undefined')); 307 | it('should contain a single connection', () => { 308 | chai.expect(graphData2.connections).to.be.an('array'); 309 | chai.expect(graphData2.connections.length).to.equal(1); 310 | chai.expect(graphData2.connections[0]).to.eql({ 311 | src: { 312 | process: 'Read', 313 | port: 'OUT', 314 | }, 315 | tgt: { 316 | process: 'Display', 317 | port: 'IN', 318 | }, 319 | }); 320 | }); 321 | it('should contain two inports', () => { 322 | chai.expect(graphData2.inports).to.be.an('object'); 323 | chai.expect(graphData2.inports.FILENAME).to.eql({ 324 | process: 'Read', 325 | port: 'IN', 326 | }); 327 | chai.expect(graphData2.inports.OPTIONS).to.eql({ 328 | process: 'Display', 329 | port: 'OPTIONS', 330 | }); 331 | }); 332 | it('should contain an outport', () => { 333 | chai.expect(graphData2.outports).to.be.an('object'); 334 | chai.expect(graphData2.outports.OUT).to.eql({ 335 | process: 'Display', 336 | port: 'OUT', 337 | }); 338 | }); 339 | }); 340 | }); 341 | describe('annotations', () => { 342 | const fbpData = `# @runtime foo 343 | # @name ReadSomefile 344 | 345 | "somefile" -> SOURCE Read(ReadFile) 346 | `; 347 | const graphData = { 348 | caseSensitive: false, 349 | properties: { 350 | name: 'ReadSomefile', 351 | environment: { 352 | type: 'foo', 353 | }, 354 | }, 355 | inports: {}, 356 | outports: {}, 357 | groups: [], 358 | processes: { 359 | Read: { 360 | component: 'ReadFile', 361 | }, 362 | }, 363 | connections: [ 364 | { 365 | data: 'somefile', 366 | tgt: { 367 | process: 'Read', 368 | port: 'source', 369 | }, 370 | }, 371 | ], 372 | }; 373 | it('should produce expected FBP string', () => { 374 | const serialized = parser.serialize(graphData); 375 | chai.expect(serialized).to.equal(fbpData); 376 | }); 377 | it('should produce expected FBP graph', () => { 378 | const serialized = parser.parse(fbpData); 379 | chai.expect(serialized).to.eql(graphData); 380 | }); 381 | }); 382 | describe('annotations in case sensitive graph', () => { 383 | const fbpData = `# @runtime foo 384 | # @name ReadSomefile 385 | INPORT=Read.Encoding:FileEncoding 386 | OUTPORT=Read.Out:Result 387 | 388 | "somefile" -> SourceCode Read(ReadFile) 389 | Read Errors -> TryAgain Read 390 | `; 391 | const graphData = { 392 | caseSensitive: true, 393 | properties: { 394 | name: 'ReadSomefile', 395 | environment: { 396 | type: 'foo', 397 | }, 398 | }, 399 | inports: { 400 | FileEncoding: { 401 | process: 'Read', 402 | port: 'Encoding', 403 | }, 404 | }, 405 | outports: { 406 | Result: { 407 | process: 'Read', 408 | port: 'Out', 409 | }, 410 | }, 411 | groups: [], 412 | processes: { 413 | Read: { 414 | component: 'ReadFile', 415 | }, 416 | }, 417 | connections: [ 418 | { 419 | data: 'somefile', 420 | tgt: { 421 | process: 'Read', 422 | port: 'SourceCode', 423 | }, 424 | }, 425 | { 426 | src: { 427 | process: 'Read', 428 | port: 'Errors', 429 | }, 430 | tgt: { 431 | process: 'Read', 432 | port: 'TryAgain', 433 | }, 434 | }, 435 | ], 436 | }; 437 | it('should produce expected FBP string', () => { 438 | const serialized = parser.serialize(graphData); 439 | chai.expect(serialized).to.equal(fbpData); 440 | }); 441 | it('should produce expected FBP graph', () => { 442 | const serialized = parser.parse(fbpData, { 443 | caseSensitive: true, 444 | }); 445 | chai.expect(serialized).to.eql(graphData); 446 | }); 447 | }); 448 | }); 449 | -------------------------------------------------------------------------------- /spec/utils/inject.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | if (typeof global !== 'undefined') { 3 | // Node.js injections for Mocha tests 4 | global.chai = require('chai'); 5 | global.parser = require('../../lib/index'); 6 | } else { 7 | // Browser injections for Mocha tests 8 | window.parser = require('fbp'); 9 | } 10 | --------------------------------------------------------------------------------