Skip to content

Commit

Permalink
feat: adding javascript basic parser (#45)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
rotkiv93 and vlamas authored Sep 20, 2024
1 parent 486de41 commit 7be087d
Show file tree
Hide file tree
Showing 16 changed files with 445 additions and 2 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/js.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Build and Deploy JS Parser

on:
push

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Setup Node
uses: actions/setup-node@v1
with:
node-version: 19
registry-url: https://registry.npmjs.org/

- name: Install ANTLR4
run: |
make dev
make js_parser
- name: Generate and Build Node Code
run: cd js && npm install && npm run build-grammar

- name: Publish npm package
run: cd js && npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
.vscode
env
python/uvl/__pycache__
uvl/.antlr
uvl/.antlr
js/node_modules
js/package-lock.json
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
all: java_parser python_parser
all: java_parser python_parser js_parser

java_parser:
antlr4 -Dlanguage=Java -o java/src/main/ uvl/UVLJava.g4
Expand All @@ -9,6 +9,10 @@ python_parser:
antlr4 -Dlanguage=Python3 -o python uvl/UVLPython.g4
cp README.md python
cd python && python setup.py build

js_parser:
mkdir -p js/src/lib
antlr4 -Dlanguage=JavaScript -o js/src/lib/ uvl/UVLJavaScript.g4

python_prepare_package:
cd python && python3 -m build
Expand Down
1 change: 1 addition & 0 deletions js/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
eslint.config.js
60 changes: 60 additions & 0 deletions js/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# UVL Parser Javascript

This is a parser for the UVL (Unified Variability Language) written in Javascript. The parser is based on the ANTLR4 grammar for UVL.

index | content
--- | ---
[Installation](#installation) | How to install the parser
[Usage](#usage) | How to use the parser
[Development](#development) | How to develop the parser
[Publishing in npm](#publishing-in-npm) | How to publish the parser in npm

## Installation

```bash
npm install uvl-parser
```

## Usage

### ES6

```javascript
import { FeatureModel } from 'uvl-parser';
const featureModel = new FeatureModel('file.uvl');
const tree = featureModel.getFeatureModel();
```

## Development

### Install dependencies

```bash
npm install
```

### Build grammar

```bash
npm run build-grammar
```

### Run tests

```bash
npm test
```

## Publishing in npm

The run build will create the folder with the compiled code. To publish in npm, run the following commands:

```bash
npm run build
npm publish --access public
```

## Authors

- Victor Lamas: [victor.lamas@udc.es](mailto:victor.lamas@udc.es)
- Maria Isabel Limaylla: [maria.limaylla@udc.es](mailto:maria.limaylla@udc.es)
10 changes: 10 additions & 0 deletions js/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import globals from "globals";
import pluginJs from "@eslint/js";


export default [
{
languageOptions: { globals: globals.browser }
},
pluginJs.configs.recommended,
];
4 changes: 4 additions & 0 deletions js/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import FeatureModel from './src/FeatureModel.js';
import UVLJavaScriptParser from './src/lib/UVLJavaScriptParser.js';

export { UVLJavaScriptParser, FeatureModel };
31 changes: 31 additions & 0 deletions js/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "uvl-parser",
"main": "index.js",
"type": "module",
"version": "0.0.3",
"scripts": {
"build-grammar": "antlr4 -Dlanguage=JavaScript -o src/lib -Xexact-output-dir ../uvl/UVLJavaScript.g4",
"lint": "eslint src --ignore-pattern src/lib/*",
"test": "vitest"
},
"devDependencies": {
"@eslint/js": "^9.8.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"eslint": "^9.8.0",
"globals": "^15.8.0",
"vitest": "^2.0.4"
},
"dependencies": {
"antlr4": "4.12.0"
},
"author": {
"name": "Maria Isabel Limaylla",
"email": "maria.limaylla@udc.es"
},
"contributors": [
{
"name": "Victor Lamas",
"email": "victor.lamas@udc.es"
}
]
}
53 changes: 53 additions & 0 deletions js/src/FeatureModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

import UVLJavaScriptCustomLexer from './UVLJavaScriptCustomLexer.js';
import UVLJavaScriptParser from './lib/UVLJavaScriptParser.js';
import ErrorListener from "./errors/ErrorListener.js";
import antlr4 from 'antlr4';
import fs from 'fs';

export { UVLJavaScriptParser };

export default class FeatureModel {

constructor(param) {
this.featureModel = '';
let chars = '';
if (this.isFile(param)) {
chars = new antlr4.FileStream(param);
} else {
chars = antlr4.CharStreams.fromString(param);
}
this.getTree(chars);
}

isFile(str) {
try {
return fs.statSync(str);
} catch (e) {
console.error('Error: ' + e);
return false;
}
}

getTree(chars) {
const lexer = new UVLJavaScriptCustomLexer(chars);
const tokens = new antlr4.CommonTokenStream(lexer);
const errorListener = new ErrorListener();
let parser = new UVLJavaScriptParser(tokens);
parser.removeErrorListeners();
parser.addErrorListener(errorListener);
const tree = parser.featureModel();
this.featureModel = tree;
}

getFeatureModel() {
return this.featureModel;
}

toString() {
return this.featureModel.getText();
}
}



103 changes: 103 additions & 0 deletions js/src/UVLJavaScriptCustomLexer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import UVLJavaScriptLexer from './lib/UVLJavaScriptLexer.js';
import UVLJavaScriptParser from './lib/UVLJavaScriptParser.js';
import antlr4 from 'antlr4';

export default class UVLJavaScriptCustomLexer extends UVLJavaScriptLexer {

constructor(input_stream) {
super(input_stream);
this.tokens = [];
this.indents = [];
this.opened = 0;
this.lastToken = null;
}

emitToken(t) {
super.emitToken(t);
this.tokens.push(t);
}

nextToken() {
if (this._input.LA(1) === antlr4.Token.EOF && this.indents.length !== 0) {
while (this.tokens.length > 0 && this.tokens[this.tokens.length - 1].type === antlr4.Token.EOF) {
this.tokens.pop();
}

this.emitToken(this.commonToken(UVLJavaScriptLexer.NEWLINE, "\n"));

while (this.indents.length !== 0) {
this.emitToken(this.createDedent());
this.indents.pop();
}

this.emitToken(this.commonToken(antlr4.Token.EOF, "<EOF>"));
}

const nextToken = super.nextToken();

if (nextToken.channel === antlr4.Token.DEFAULT_CHANNEL) {
this.lastToken = nextToken;
}

return this.tokens.length > 0 ? this.tokens.shift() : nextToken;
}

createDedent() {
const dedent = this.commonToken(UVLJavaScriptLexer.DEDENT, "");
dedent.line = this.lastToken.line;
return dedent;
}

commonToken(type, text) {
const stop = this.getCharIndex() - 1;
const start = text ? stop - text.length + 1 : stop;
return new antlr4.CommonToken(this._tokenFactorySourcePair, type, antlr4.Token.DEFAULT_CHANNEL, start, stop);
}

static getIndentationCount(spaces) {
let count = 0;
for (const ch of spaces) {
if (ch === '\t') {
count += 8 - (count % 8);
} else {
count += 1;
}
}
return count;
}

skipToken() {
this.skip();
}

atStartOfInput() {
return this._interp.column === 0 && this._interp.line === 1;
}

handleNewline() {
const newLine = this._interp.getText(this._input).replace(/[^\r\n\f]+/g, "");
const spaces = this._interp.getText(this._input).replace(/[\r\n\f]+/g, "");
const next = String.fromCharCode(this._input.LA(1));

if (this.opened > 0 || next === '\r' || next === '\n' || next === '\f' || next === '#') {
this.skip();
} else {
this.emitToken(this.commonToken(UVLJavaScriptLexer.NEWLINE, newLine));

const indent = UVLJavaScriptCustomLexer.getIndentationCount(spaces);
const previous = this.indents.length === 0 ? 0 : this.indents[this.indents.length - 1];

if (indent === previous) {
this.skip();
} else if (indent > previous) {
this.indents.push(indent);
this.emitToken(this.commonToken(UVLJavaScriptParser.INDENT, spaces));
} else {
while (this.indents.length > 0 && this.indents[this.indents.length - 1] > indent) {
this.emitToken(this.createDedent());
this.indents.pop();
}
}
}
}
}
26 changes: 26 additions & 0 deletions js/src/errors/ErrorListener.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import antlr4 from "antlr4";

import SyntaxGenericError from "./SyntaxGenericError.js";

/**
* Custom Error Listener
*
* @returns {object}
*/
class ErrorListener extends antlr4.error.ErrorListener {
/**
* Checks syntax error
*
* @param {object} recognizer The parsing support code essentially. Most of it is error recovery stuff
* @param {object} symbol Offending symbol
* @param {int} line Line of offending symbol
* @param {int} column Position in line of offending symbol
* @param {string} message Error message
* @param {string} payload Stack trace
*/
syntaxError(recognizer, symbol, line, column, message) {
throw new SyntaxGenericError({ line, column, message });
}
}

export default ErrorListener;
18 changes: 18 additions & 0 deletions js/src/errors/SyntaxGenericError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export default class SyntaxGenericError extends Error {
constructor(payload) {
super(payload);

this.code = "E_SYNTAX_GENERIC";
this.message = "Something went wrong";

if (typeof payload !== "undefined") {
this.message = payload.message || this.message;
this.payload = payload;
}

console.error(
`line ${this.payload.line}, col ${this.payload.column}: ${this.payload.message}`,
);
Error.captureStackTrace(this, SyntaxGenericError);
}
}
Loading

0 comments on commit 7be087d

Please sign in to comment.