Skip to content

Commit 7be087d

Browse files
rotkiv93vlamas
andauthored
feat: adding javascript basic parser (#45)
* Added js basic parser * Deleted lib files / updated package.json name * Added workflow / updated project npm name * Updated workflow to install antlr4 * Updated makefile / antlr4 js build * Added missing uvl grammar js * Changed route on makefile for js * Added call to make dev in workflow * Update version 0.0.3 * Change env variable name --------- Co-authored-by: vlamas <vlamas@udc.es>
1 parent 486de41 commit 7be087d

16 files changed

Lines changed: 445 additions & 2 deletions

.github/workflows/js.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Build and Deploy JS Parser
2+
3+
on:
4+
push
5+
6+
jobs:
7+
build:
8+
runs-on: ubuntu-latest
9+
10+
steps:
11+
- name: Checkout code
12+
uses: actions/checkout@v2
13+
14+
- name: Setup Node
15+
uses: actions/setup-node@v1
16+
with:
17+
node-version: 19
18+
registry-url: https://registry.npmjs.org/
19+
20+
- name: Install ANTLR4
21+
run: |
22+
make dev
23+
make js_parser
24+
25+
- name: Generate and Build Node Code
26+
run: cd js && npm install && npm run build-grammar
27+
28+
- name: Publish npm package
29+
run: cd js && npm publish --access public
30+
env:
31+
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22
.vscode
33
env
44
python/uvl/__pycache__
5-
uvl/.antlr
5+
uvl/.antlr
6+
js/node_modules
7+
js/package-lock.json

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
all: java_parser python_parser
1+
all: java_parser python_parser js_parser
22

33
java_parser:
44
antlr4 -Dlanguage=Java -o java/src/main/ uvl/UVLJava.g4
@@ -9,6 +9,10 @@ python_parser:
99
antlr4 -Dlanguage=Python3 -o python uvl/UVLPython.g4
1010
cp README.md python
1111
cd python && python setup.py build
12+
13+
js_parser:
14+
mkdir -p js/src/lib
15+
antlr4 -Dlanguage=JavaScript -o js/src/lib/ uvl/UVLJavaScript.g4
1216

1317
python_prepare_package:
1418
cd python && python3 -m build

js/.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
eslint.config.js

js/README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# UVL Parser Javascript
2+
3+
This is a parser for the UVL (Unified Variability Language) written in Javascript. The parser is based on the ANTLR4 grammar for UVL.
4+
5+
index | content
6+
--- | ---
7+
[Installation](#installation) | How to install the parser
8+
[Usage](#usage) | How to use the parser
9+
[Development](#development) | How to develop the parser
10+
[Publishing in npm](#publishing-in-npm) | How to publish the parser in npm
11+
12+
## Installation
13+
14+
```bash
15+
npm install uvl-parser
16+
```
17+
18+
## Usage
19+
20+
### ES6
21+
22+
```javascript
23+
import { FeatureModel } from 'uvl-parser';
24+
const featureModel = new FeatureModel('file.uvl');
25+
const tree = featureModel.getFeatureModel();
26+
```
27+
28+
## Development
29+
30+
### Install dependencies
31+
32+
```bash
33+
npm install
34+
```
35+
36+
### Build grammar
37+
38+
```bash
39+
npm run build-grammar
40+
```
41+
42+
### Run tests
43+
44+
```bash
45+
npm test
46+
```
47+
48+
## Publishing in npm
49+
50+
The run build will create the folder with the compiled code. To publish in npm, run the following commands:
51+
52+
```bash
53+
npm run build
54+
npm publish --access public
55+
```
56+
57+
## Authors
58+
59+
- Victor Lamas: [victor.lamas@udc.es](mailto:victor.lamas@udc.es)
60+
- Maria Isabel Limaylla: [maria.limaylla@udc.es](mailto:maria.limaylla@udc.es)

js/eslint.config.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import globals from "globals";
2+
import pluginJs from "@eslint/js";
3+
4+
5+
export default [
6+
{
7+
languageOptions: { globals: globals.browser }
8+
},
9+
pluginJs.configs.recommended,
10+
];

js/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import FeatureModel from './src/FeatureModel.js';
2+
import UVLJavaScriptParser from './src/lib/UVLJavaScriptParser.js';
3+
4+
export { UVLJavaScriptParser, FeatureModel };

js/package.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "uvl-parser",
3+
"main": "index.js",
4+
"type": "module",
5+
"version": "0.0.3",
6+
"scripts": {
7+
"build-grammar": "antlr4 -Dlanguage=JavaScript -o src/lib -Xexact-output-dir ../uvl/UVLJavaScript.g4",
8+
"lint": "eslint src --ignore-pattern src/lib/*",
9+
"test": "vitest"
10+
},
11+
"devDependencies": {
12+
"@eslint/js": "^9.8.0",
13+
"@rollup/plugin-node-resolve": "^15.2.3",
14+
"eslint": "^9.8.0",
15+
"globals": "^15.8.0",
16+
"vitest": "^2.0.4"
17+
},
18+
"dependencies": {
19+
"antlr4": "4.12.0"
20+
},
21+
"author": {
22+
"name": "Maria Isabel Limaylla",
23+
"email": "maria.limaylla@udc.es"
24+
},
25+
"contributors": [
26+
{
27+
"name": "Victor Lamas",
28+
"email": "victor.lamas@udc.es"
29+
}
30+
]
31+
}

js/src/FeatureModel.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
2+
import UVLJavaScriptCustomLexer from './UVLJavaScriptCustomLexer.js';
3+
import UVLJavaScriptParser from './lib/UVLJavaScriptParser.js';
4+
import ErrorListener from "./errors/ErrorListener.js";
5+
import antlr4 from 'antlr4';
6+
import fs from 'fs';
7+
8+
export { UVLJavaScriptParser };
9+
10+
export default class FeatureModel {
11+
12+
constructor(param) {
13+
this.featureModel = '';
14+
let chars = '';
15+
if (this.isFile(param)) {
16+
chars = new antlr4.FileStream(param);
17+
} else {
18+
chars = antlr4.CharStreams.fromString(param);
19+
}
20+
this.getTree(chars);
21+
}
22+
23+
isFile(str) {
24+
try {
25+
return fs.statSync(str);
26+
} catch (e) {
27+
console.error('Error: ' + e);
28+
return false;
29+
}
30+
}
31+
32+
getTree(chars) {
33+
const lexer = new UVLJavaScriptCustomLexer(chars);
34+
const tokens = new antlr4.CommonTokenStream(lexer);
35+
const errorListener = new ErrorListener();
36+
let parser = new UVLJavaScriptParser(tokens);
37+
parser.removeErrorListeners();
38+
parser.addErrorListener(errorListener);
39+
const tree = parser.featureModel();
40+
this.featureModel = tree;
41+
}
42+
43+
getFeatureModel() {
44+
return this.featureModel;
45+
}
46+
47+
toString() {
48+
return this.featureModel.getText();
49+
}
50+
}
51+
52+
53+

js/src/UVLJavaScriptCustomLexer.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import UVLJavaScriptLexer from './lib/UVLJavaScriptLexer.js';
2+
import UVLJavaScriptParser from './lib/UVLJavaScriptParser.js';
3+
import antlr4 from 'antlr4';
4+
5+
export default class UVLJavaScriptCustomLexer extends UVLJavaScriptLexer {
6+
7+
constructor(input_stream) {
8+
super(input_stream);
9+
this.tokens = [];
10+
this.indents = [];
11+
this.opened = 0;
12+
this.lastToken = null;
13+
}
14+
15+
emitToken(t) {
16+
super.emitToken(t);
17+
this.tokens.push(t);
18+
}
19+
20+
nextToken() {
21+
if (this._input.LA(1) === antlr4.Token.EOF && this.indents.length !== 0) {
22+
while (this.tokens.length > 0 && this.tokens[this.tokens.length - 1].type === antlr4.Token.EOF) {
23+
this.tokens.pop();
24+
}
25+
26+
this.emitToken(this.commonToken(UVLJavaScriptLexer.NEWLINE, "\n"));
27+
28+
while (this.indents.length !== 0) {
29+
this.emitToken(this.createDedent());
30+
this.indents.pop();
31+
}
32+
33+
this.emitToken(this.commonToken(antlr4.Token.EOF, "<EOF>"));
34+
}
35+
36+
const nextToken = super.nextToken();
37+
38+
if (nextToken.channel === antlr4.Token.DEFAULT_CHANNEL) {
39+
this.lastToken = nextToken;
40+
}
41+
42+
return this.tokens.length > 0 ? this.tokens.shift() : nextToken;
43+
}
44+
45+
createDedent() {
46+
const dedent = this.commonToken(UVLJavaScriptLexer.DEDENT, "");
47+
dedent.line = this.lastToken.line;
48+
return dedent;
49+
}
50+
51+
commonToken(type, text) {
52+
const stop = this.getCharIndex() - 1;
53+
const start = text ? stop - text.length + 1 : stop;
54+
return new antlr4.CommonToken(this._tokenFactorySourcePair, type, antlr4.Token.DEFAULT_CHANNEL, start, stop);
55+
}
56+
57+
static getIndentationCount(spaces) {
58+
let count = 0;
59+
for (const ch of spaces) {
60+
if (ch === '\t') {
61+
count += 8 - (count % 8);
62+
} else {
63+
count += 1;
64+
}
65+
}
66+
return count;
67+
}
68+
69+
skipToken() {
70+
this.skip();
71+
}
72+
73+
atStartOfInput() {
74+
return this._interp.column === 0 && this._interp.line === 1;
75+
}
76+
77+
handleNewline() {
78+
const newLine = this._interp.getText(this._input).replace(/[^\r\n\f]+/g, "");
79+
const spaces = this._interp.getText(this._input).replace(/[\r\n\f]+/g, "");
80+
const next = String.fromCharCode(this._input.LA(1));
81+
82+
if (this.opened > 0 || next === '\r' || next === '\n' || next === '\f' || next === '#') {
83+
this.skip();
84+
} else {
85+
this.emitToken(this.commonToken(UVLJavaScriptLexer.NEWLINE, newLine));
86+
87+
const indent = UVLJavaScriptCustomLexer.getIndentationCount(spaces);
88+
const previous = this.indents.length === 0 ? 0 : this.indents[this.indents.length - 1];
89+
90+
if (indent === previous) {
91+
this.skip();
92+
} else if (indent > previous) {
93+
this.indents.push(indent);
94+
this.emitToken(this.commonToken(UVLJavaScriptParser.INDENT, spaces));
95+
} else {
96+
while (this.indents.length > 0 && this.indents[this.indents.length - 1] > indent) {
97+
this.emitToken(this.createDedent());
98+
this.indents.pop();
99+
}
100+
}
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)