├── js
├── .npmignore
├── index.js
├── eslint.config.js
├── test
│ ├── example1.uvl
│ ├── fm.test.js
│ └── example2.uvl
├── src
│ ├── errors
│ │ ├── SyntaxGenericError.js
│ │ └── ErrorListener.js
│ ├── FeatureModel.js
│ └── UVLJavaScriptCustomLexer.js
├── package.json
└── README.md
├── python
├── __init__.py
├── setup.py
├── main.py
├── uvl
│ └── UVLCustomLexer.py
└── README.md
├── uvl
├── Python
│ ├── UVLPythonParser.g4
│ └── UVLPythonLexer.g4
├── JavaScript
│ ├── UVLJavaScriptParser.g4
│ └── UVLJavaScriptLexer.g4
├── Java
│ ├── UVLJavaParser.g4
│ └── UVLJavaLexer.g4
├── UVLLexer.g4
└── UVLParser.g4
├── .gitignore
├── .github
├── workflows
│ ├── conventionalpr.yml
│ ├── js.yml
│ ├── python.yml
│ └── java.yml
└── settings.xml
├── Makefile
├── README.md
├── LICENSE
└── java
└── pom.xml
/js/.npmignore:
--------------------------------------------------------------------------------
1 | eslint.config.js
--------------------------------------------------------------------------------
/python/__init__.py:
--------------------------------------------------------------------------------
1 | from python.main import get_tree
2 |
--------------------------------------------------------------------------------
/js/index.js:
--------------------------------------------------------------------------------
1 | import FeatureModel from './src/FeatureModel.js';
2 |
3 | export { FeatureModel };
4 |
--------------------------------------------------------------------------------
/uvl/Python/UVLPythonParser.g4:
--------------------------------------------------------------------------------
1 | parser grammar UVLPythonParser;
2 |
3 | options {
4 | tokenVocab = UVLPythonLexer;
5 | }
6 |
7 | import UVLParser;
--------------------------------------------------------------------------------
/uvl/JavaScript/UVLJavaScriptParser.g4:
--------------------------------------------------------------------------------
1 | parser grammar UVLJavaScriptParser;
2 |
3 | options {
4 | tokenVocab = UVLJavaScriptLexer;
5 | }
6 |
7 | import UVLParser;
--------------------------------------------------------------------------------
/uvl/Java/UVLJavaParser.g4:
--------------------------------------------------------------------------------
1 | parser grammar UVLJavaParser;
2 |
3 | options {
4 | tokenVocab = UVLJavaLexer;
5 | }
6 |
7 | import UVLParser;
8 |
9 | @header {
10 | package uvl;
11 | }
--------------------------------------------------------------------------------
/js/eslint.config.js:
--------------------------------------------------------------------------------
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 | ];
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # VSCode, Eclipse, and IntelliJ IDEA project files
2 | .vscode
3 | .settings
4 | .classpath
5 | .project
6 | .idea
7 |
8 | # Python virtual environments
9 | env
10 | build
11 | python/uvl/__pycache__
12 | python/uvl/*
13 | !python/uvl/UVLCustomLexer.py
14 |
15 | # Node.js
16 | js/node_modules
17 | js/package-lock.json
18 |
19 | # Java build files
20 | .antlr/
21 | target/
--------------------------------------------------------------------------------
/.github/workflows/conventionalpr.yml:
--------------------------------------------------------------------------------
1 | name: Conventional Commit Parser
2 | on:
3 | pull_request:
4 | branches: [main, master, develop]
5 | types: [opened, reopened, edited, review_requested, synchronize]
6 |
7 | jobs:
8 | conventional_commit:
9 | name: Conventional Commits
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v2
13 | - uses: webiny/action-conventional-commits@v1.0.3
14 |
--------------------------------------------------------------------------------
/js/test/example1.uvl:
--------------------------------------------------------------------------------
1 | features
2 | GEMA_SPL
3 | mandatory
4 | GraphicalUserInterface
5 | optional
6 | GUI_Forms
7 | optional
8 | GUI_Lists
9 | optional
10 | GUI_L_Sortable
11 | optional
12 | GUI_L_Filterable
13 | alternative
14 | GUI_L_F_RowFilter
15 | GUI_L_F_BasicSearch
16 | optional
17 | GUI_L_LocateInMap
18 | optional
19 | GUI_L_ViewListAsMap
20 | optional
21 | GUI_L_FormLink
--------------------------------------------------------------------------------
/js/src/errors/SyntaxGenericError.js:
--------------------------------------------------------------------------------
1 | export default class SyntaxGenericError extends Error {
2 | constructor(payload) {
3 | super(payload);
4 |
5 | this.code = "E_SYNTAX_GENERIC";
6 | this.message = "Something went wrong";
7 |
8 | if (typeof payload !== "undefined") {
9 | this.message = payload.message || this.message;
10 | this.payload = payload;
11 | }
12 |
13 | console.error(
14 | `line ${this.payload.line}, col ${this.payload.column}: ${this.payload.message}`,
15 | );
16 | Error.captureStackTrace(this, SyntaxGenericError);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/uvl/Python/UVLPythonLexer.g4:
--------------------------------------------------------------------------------
1 | lexer grammar UVLPythonLexer;
2 | import UVLLexer;
3 |
4 | OPEN_PAREN: '(' {self.opened += 1;};
5 | CLOSE_PAREN: ')' {self.opened -= 1;};
6 | OPEN_BRACK: '[' {self.opened += 1;};
7 | CLOSE_BRACK: ']' {self.opened -= 1;};
8 | OPEN_BRACE: '{' {self.opened += 1;};
9 | CLOSE_BRACE: '}' {self.opened -= 1;};
10 | OPEN_COMMENT: '/*' {self.opened += 1;};
11 | CLOSE_COMMENT: '*/' {self.opened -= 1;};
12 |
13 | //This is here because the way python manage tabs and new lines
14 | NEWLINE: (
15 | {self.atStartOfInput()}? SPACES
16 | | ( '\r'? '\n' | '\r') SPACES?
17 | ) {self.handleNewline();};
--------------------------------------------------------------------------------
/.github/workflows/js.yml:
--------------------------------------------------------------------------------
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 }}
--------------------------------------------------------------------------------
/js/test/fm.test.js:
--------------------------------------------------------------------------------
1 | import { expect, test } from 'vitest'
2 | import { FeatureModel } from "./index.js"
3 | import fs from 'fs';
4 | import path from 'path';
5 |
6 | test('Create a feature model with example1', () => {
7 | const fm = new FeatureModel('test/example1.uvl')
8 | expect(fm.toString().substring(0, 8)).toBe('features')
9 | });
10 |
11 | test('Create a feature model with example2', () => {
12 | const fm = new FeatureModel('test/example2.uvl')
13 | expect(fm.toString().includes('MapViewer')).toBe(true)
14 | });
15 |
16 | test('Create a feature model using string', () => {
17 | const filePath = path.resolve('test/example2.uvl');
18 | const text = fs.readFileSync(filePath, 'utf8');
19 | const fm = new FeatureModel(text)
20 | expect(fm.toString().includes('MapViewer')).toBe(true)
21 | });
22 |
--------------------------------------------------------------------------------
/.github/workflows/python.yml:
--------------------------------------------------------------------------------
1 | name: Build and Deploy Python 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: Install ANTLR4 and Python dependencies
15 | run: |
16 | # Add installation steps for ANTLR4 here
17 | sudo apt-get update
18 | sudo apt-get install -y python3 python3-pip
19 | make dev
20 |
21 | - name: Generate and Build Python Code
22 | run: make python_parser
23 |
24 | - name: Build and publish python package
25 | env:
26 | TWINE_USERNAME: __token__
27 | TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
28 | run: |
29 | make python_prepare_package
30 | cd python && twine upload dist/*
31 |
--------------------------------------------------------------------------------
/js/test/example2.uvl:
--------------------------------------------------------------------------------
1 | features
2 | GEMA_SPL
3 | mandatory
4 | MapViewer
5 | mandatory
6 | MV_MapServer
7 | or
8 | MV_MS_GeoServer
9 | MV_MS_GeoJSON
10 | optional
11 | MV_MS_GJ_Cached
12 | optional
13 | MV_MS_GJ_Paginated
14 | mandatory
15 | MV_MapManagement
16 | alternative
17 | MV_MM_UniqueMapViewer
18 | mandatory
19 | MV_MM_UMV_MapCenter
20 | alternative
21 | MV_MM_UMV_MC_BBox
22 | MV_MM_UMV_MC_Coordinates
23 | MV_MM_UMV_MC_UserPosition
24 | MV_MM_MultipleMapViewer
25 | optional
26 | MV_MM_MMV_MapSelectorInMapViewer
27 | optional
28 | MV_MM_MMV_MapSelectorInMenuElement
29 | constraints
30 | MV_MS_GJ_Paginated => !MV_MS_GJ_Cached
31 | MV_MS_GJ_Cached => !MV_MS_GJ_Paginated
--------------------------------------------------------------------------------
/js/src/errors/ErrorListener.js:
--------------------------------------------------------------------------------
1 | import antlr4 from "antlr4";
2 |
3 | import SyntaxGenericError from "./SyntaxGenericError.js";
4 |
5 | /**
6 | * Custom Error Listener
7 | *
8 | * @returns {object}
9 | */
10 | class ErrorListener extends antlr4.error.ErrorListener {
11 | /**
12 | * Checks syntax error
13 | *
14 | * @param {object} recognizer The parsing support code essentially. Most of it is error recovery stuff
15 | * @param {object} symbol Offending symbol
16 | * @param {int} line Line of offending symbol
17 | * @param {int} column Position in line of offending symbol
18 | * @param {string} message Error message
19 | * @param {string} payload Stack trace
20 | */
21 | syntaxError(recognizer, symbol, line, column, message) {
22 | throw new SyntaxGenericError({ line, column, message });
23 | }
24 | }
25 |
26 | export default ErrorListener;
27 |
--------------------------------------------------------------------------------
/.github/settings.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 | ossrh
8 | ${env.SONATYPE_USERNAME}
9 | ${env.SONATYPE_PASSWORD}
10 |
11 |
12 |
13 |
14 | ossrh
15 |
16 | true
17 |
18 |
19 | gpg
20 | ${env.GPG_PASSPHRASE}
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "uvl-parser",
3 | "main": "index.js",
4 | "type": "module",
5 | "version": "0.0.3",
6 | "scripts": {
7 | "build-grammar": "antlr4 -lib ../uvl/ -Dlanguage=JavaScript -o ./src/lib ../uvl/JavaScript/UVLJavaScriptLexer.g4 ../uvl/JavaScript/UVLJavaScriptParser.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.13.2"
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 | }
32 |
--------------------------------------------------------------------------------
/uvl/JavaScript/UVLJavaScriptLexer.g4:
--------------------------------------------------------------------------------
1 | lexer grammar UVLJavaScriptLexer;
2 |
3 | import UVLLexer;
4 |
5 | @lexer::members {
6 | // Una cola donde se empujan los tokens adicionales (ver la regla del lexer NEWLINE).
7 | let tokens = [];
8 | // La pila que lleva un registro del nivel de indentación.
9 | let indents = [];
10 | // La cantidad de paréntesis, corchetes y llaves abiertos.
11 | let opened = 0;
12 | // El token más recientemente producido.
13 | let lastToken = null;
14 | }
15 |
16 | OPEN_PAREN: '(' {this.opened += 1;};
17 | CLOSE_PAREN: ')' {this.opened -= 1;};
18 | OPEN_BRACK: '[' {this.opened += 1;};
19 | CLOSE_BRACK: ']' {this.opened -= 1;};
20 | OPEN_BRACE: '{' {this.opened += 1;};
21 | CLOSE_BRACE: '}' {this.opened -= 1;};
22 | OPEN_COMMENT: '/*' {this.opened += 1;};
23 | CLOSE_COMMENT: '*/' {this.opened -= 1;};
24 |
25 | NEWLINE: (
26 | {this.atStartOfInput()}? SPACES
27 | | ( '\r'? '\n' | '\r') SPACES?
28 | ) {this.handleNewline();};
--------------------------------------------------------------------------------
/python/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | with open("README.md", "r") as fh:
4 | long_description = fh.read()
5 |
6 | setup(
7 | name="uvlparser",
8 | version="2.0.1",
9 | description="This module provides a get_tree function to obtain an ANTLR parse-tree from a UVL-defined feature model",
10 | long_description=long_description,
11 | long_description_content_type="text/markdown",
12 | url="https://github.com/Universal-Variability-Language/uvl-parser",
13 | author="UVL Team",
14 | author_email="jagalindo@us.es",
15 | # To find more licenses or classifiers go to: https://pypi.org/classifiers/
16 | license="GNU General Public License v3 (GPLv3)",
17 | packages=['.','uvl'],
18 | classifiers=[
19 | "Programming Language :: Python :: 3",
20 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
21 | "Operating System :: OS Independent",
22 | ],
23 | zip_safe=True,
24 | python_requires=">=3.0",
25 | install_requires=[
26 | "antlr4-python3-runtime==4.13.1",
27 | ],
28 | )
29 |
--------------------------------------------------------------------------------
/.github/workflows/java.yml:
--------------------------------------------------------------------------------
1 | name: Build and Deploy Java 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: Install ANTLR4 and Maven
15 | run: |
16 | # Add installation steps for ANTLR4 here
17 | sudo apt-get update
18 | sudo apt-get install -y maven
19 | make dev
20 |
21 | - name: Import GPG key
22 | uses: crazy-max/ghaction-import-gpg@v1
23 | env:
24 | GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
25 | PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
26 |
27 | - name: Generate and Compile Java Code
28 | run: make java_parser
29 |
30 | - name: Cache Maven packages
31 | uses: actions/cache@v2
32 | with:
33 | path: ~/.m2
34 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
35 | restore-keys: ${{ runner.os }}-m2
36 |
37 | - name: Deploy to Maven Central
38 | run: cd java && mvn clean deploy --settings ../.github/settings.xml
39 | env:
40 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
41 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
42 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
--------------------------------------------------------------------------------
/js/src/FeatureModel.js:
--------------------------------------------------------------------------------
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 default class FeatureModel {
9 |
10 | constructor(param) {
11 | this.featureModel = '';
12 | let chars = '';
13 | if (this.isFile(param)) {
14 | chars = new antlr4.FileStream(param);
15 | } else {
16 | chars = antlr4.CharStreams.fromString(param);
17 | }
18 | this.getTree(chars);
19 | }
20 |
21 | isFile(str) {
22 | try {
23 | return fs.statSync(str);
24 | } catch (e) {
25 | console.error('Error: ' + e);
26 | return false;
27 | }
28 | }
29 |
30 | getTree(chars) {
31 | const lexer = new UVLJavaScriptCustomLexer(chars);
32 | const tokens = new antlr4.CommonTokenStream(lexer);
33 | const errorListener = new ErrorListener();
34 | let parser = new UVLJavaScriptParser(tokens);
35 | parser.removeErrorListeners();
36 | parser.addErrorListener(errorListener);
37 | const tree = parser.featureModel();
38 | this.featureModel = tree;
39 | }
40 |
41 | getFeatureModel() {
42 | return this.featureModel;
43 | }
44 |
45 | toString() {
46 | return this.featureModel.getText();
47 | }
48 | }
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/js/README.md:
--------------------------------------------------------------------------------
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)
61 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: all java_parser python_parser js_parser python_prepare_package clean test dev
2 |
3 | all: java_parser python_parser js_parser
4 |
5 | LIB_FLAG = -lib
6 | LIB_PATH = uvl
7 |
8 | ROOT_DIR = $(shell pwd)
9 | PYTHON_OUTPUT_DIR = python/uvl
10 | JAVASCRIPT_OUTPUT_DIR = js/src/lib
11 |
12 | java_parser:
13 | cd java && mvn package
14 | cd java && mvn install
15 |
16 | js_parser:
17 | mkdir -p $(JAVASCRIPT_OUTPUT_DIR)
18 | cd uvl/JavaScript && \
19 | antlr4 $(LIB_FLAG) $(ROOT_DIR)/$(LIB_PATH) -Dlanguage=JavaScript -o $(ROOT_DIR)/$(JAVASCRIPT_OUTPUT_DIR) UVLJavaScriptLexer.g4 UVLJavaScriptParser.g4
20 |
21 | python_parser:
22 | cd uvl/Python && \
23 | antlr4 $(LIB_FLAG) $(ROOT_DIR)/$(LIB_PATH) -Dlanguage=Python3 -o $(ROOT_DIR)/$(PYTHON_OUTPUT_DIR) UVLPythonLexer.g4 UVLPythonParser.g4
24 |
25 | python_prepare_package:
26 | cd python && python3 -m build
27 |
28 | clean:
29 | # Clean Java build artifacts
30 | cd java && mvn clean
31 |
32 | # Remove generated Python files except custom_lexer.py and main.py
33 | find python/uvl/ -type f ! -name 'UVLCustomLexer.py' ! -name 'main.py' -delete
34 | # Remove compiled Python files
35 | find . -name "*.pyc" -exec rm {} \;
36 | # Remove Python build artifacts
37 | rm -rf python/build python/dist
38 |
39 | # Clean JavaScript generated files
40 | rm -rf js/src/lib
41 |
42 |
43 | test:
44 | python3 ./python/main.py ./tests/parsing/boolean.uvl
45 | dev:
46 | pip install antlr4-tools antlr4-python3-runtime setuptools wheel twine build
47 |
--------------------------------------------------------------------------------
/uvl/UVLLexer.g4:
--------------------------------------------------------------------------------
1 | lexer grammar UVLLexer;
2 |
3 | INDENT: '';
4 | DEDENT: '';
5 |
6 | INCLUDE_KEY: 'include';
7 | FEATURES_KEY: 'features';
8 | IMPORTS_KEY: 'imports';
9 | NAMESPACE_KEY: 'namespace';
10 | AS_KEY: 'as';
11 | CONSTRAINT_KEY: 'constraint';
12 | CONSTRAINTS_KEY: 'constraints';
13 | CARDINALITY_KEY: 'cardinality';
14 | STRING_KEY: 'String';
15 | BOOLEAN_KEY: 'Boolean';
16 | INTEGER_KEY: 'Integer';
17 | REAL_KEY: 'Real';
18 | LEN_KEY: 'len';
19 | SUM_KEY: 'sum';
20 | AVG_KEY: 'avg';
21 | FLOOR_KEY: 'floor';
22 | CEIL_KEY: 'ceil';
23 | TYPE_KEY: 'Type';
24 | ARITHMETIC_KEY: 'Arithmetic';
25 | GROUP_CARDINALITY_KEY: 'group-cardinality';
26 | FEATURE_CARDINALITY_KEY: 'feature-cardinality';
27 | AGGREGATE_KEY: 'aggregate-function';
28 | STRING_CONSTRAINTS_KEY: 'string-constraints';
29 |
30 | ORGROUP: 'or';
31 | ALTERNATIVE: 'alternative';
32 | OPTIONAL: 'optional';
33 | MANDATORY: 'mandatory';
34 |
35 | CARDINALITY: '[' INTEGER ('..' (INTEGER | '*'))? ']';
36 |
37 | NOT: '!';
38 | AND: '&';
39 | OR: '|';
40 | EQUIVALENCE: '<=>';
41 | IMPLICATION: '=>';
42 |
43 | EQUAL: '==';
44 | LOWER: '<';
45 | LOWER_EQUALS: '<=';
46 | GREATER: '>';
47 | GREATER_EQUALS: '>=';
48 | NOT_EQUALS: '!=';
49 |
50 | DIV: '/';
51 | MUL: '*';
52 | ADD: '+';
53 | SUB: '-';
54 |
55 | OPEN_PAREN: '(';
56 | CLOSE_PAREN: ')';
57 | OPEN_BRACK: '[';
58 | CLOSE_BRACK: ']';
59 | OPEN_BRACE: '{';
60 | CLOSE_BRACE: '}';
61 | OPEN_COMMENT: '/*';
62 | CLOSE_COMMENT: '*/';
63 |
64 | FLOAT: '-'?[0-9]*[.][0-9]+;
65 | INTEGER: '0' | '-'?[1-9][0-9]*;
66 | BOOLEAN: 'true' | 'false';
67 |
68 | COMMA: ',';
69 | DOT: '.';
70 |
71 | ID_NOT_STRICT: '"'~[\r\n".]+'"';
72 | ID_STRICT: [a-zA-Z]([a-zA-Z0-9_#§%?\\'äüöß;])*;
73 |
74 | STRING: '\''~[\r\n']+'\'';
75 |
76 | SKIP_: ( SPACES | COMMENT) -> skip;
77 |
78 | fragment COMMENT:
79 | '//' ~[\r\n\f]*
80 | | OPEN_COMMENT .* CLOSE_COMMENT;
81 | fragment SPACES: [ \t]+;
82 |
83 | NEWLINE: ('\r'? '\n' | '\r');
84 |
--------------------------------------------------------------------------------
/python/main.py:
--------------------------------------------------------------------------------
1 | from antlr4 import CommonTokenStream, FileStream
2 | from uvl.UVLCustomLexer import UVLCustomLexer
3 | from uvl.UVLPythonParser import UVLPythonParser
4 | from antlr4.error.ErrorListener import ErrorListener
5 |
6 | class CustomErrorListener(ErrorListener):
7 | def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
8 | # If the error message contains a specific keyword related to tabulation, ignore it
9 | if "\\t" in msg:
10 | print(f"The UVL has the following warning that prevents reading it :Line {line}:{column} - {msg}")
11 | return
12 | else:
13 | # Otherwise, print the error (or handle it in another way)
14 | raise Exception(f"The UVL has the following error that prevents reading it :Line {line}:{column} - {msg}")
15 |
16 |
17 | def get_tree(argv):
18 | input_stream = FileStream(argv)
19 | lexer = UVLCustomLexer(input_stream)
20 |
21 | # Attach the custom error listener to the lexer
22 | lexer.removeErrorListeners()
23 | lexer.addErrorListener(CustomErrorListener())
24 |
25 | stream = CommonTokenStream(lexer)
26 | parser = UVLPythonParser(stream)
27 |
28 | # Attach the custom error listener to the parser
29 | parser.removeErrorListeners()
30 | parser.addErrorListener(CustomErrorListener())
31 |
32 | tree = parser.feature_model()
33 |
34 | return tree
35 |
36 |
37 | if __name__ == "__main__":
38 | import sys
39 |
40 | # Check if the user provided a file argument
41 | if len(sys.argv) < 2:
42 | print("Usage: python script_name.py ")
43 | sys.exit(1)
44 |
45 | # Parse the provided file
46 | input_stream = FileStream(sys.argv[1])
47 | lexer = UVLCustomLexer(input_stream)
48 |
49 | # Attach the custom error listener to the lexer
50 | lexer.removeErrorListeners()
51 | lexer.addErrorListener(CustomErrorListener())
52 |
53 | stream = CommonTokenStream(lexer)
54 | parser = UVLPythonParser(stream)
55 |
56 | # Attach the custom error listener to the parser
57 | parser.removeErrorListeners()
58 | parser.addErrorListener(CustomErrorListener())
59 |
60 | tree = parser.featureModel()
61 | # Print the parse tree (optional)
62 | print(tree.toStringTree(recog=parser))
--------------------------------------------------------------------------------
/python/uvl/UVLCustomLexer.py:
--------------------------------------------------------------------------------
1 | import re
2 | from .UVLPythonLexer import UVLPythonLexer
3 |
4 | from .UVLPythonParser import UVLPythonParser
5 | from antlr4.Token import Token
6 | from antlr4.Token import CommonToken
7 |
8 | class UVLCustomLexer(UVLPythonLexer):
9 |
10 | def __init__(self, input_stream):
11 | super().__init__(input_stream)
12 | self.tokens = []
13 | self.indents = []
14 | self.opened = 0
15 | self.lastToken = None
16 |
17 | def emitToken(self, t):
18 | super().emitToken(t)
19 | self.tokens.append(t)
20 |
21 | def nextToken(self):
22 | # Check if the end-of-file is ahead and there are still some DEDENTS expected.
23 | if self._input.LA(1) == Token.EOF and len(self.indents) != 0:
24 | # Remove any trailing EOF tokens from our buffer.
25 | while len(self.tokens) > 0 and self.tokens[-1].type == Token.EOF:
26 | del self.tokens[-1]
27 |
28 | # First emit an extra line break that serves as the end of the statement.
29 | self.emitToken(self.common_token(UVLPythonLexer.NEWLINE, "\n"));
30 |
31 |
32 | # Now emit as much DEDENT tokens as needed.
33 | while len(self.indents) != 0:
34 | self.emitToken(self.create_dedent())
35 | del self.indents[-1]
36 | # Put the EOF back on the token stream.
37 | self.emitToken(self.common_token(Token.EOF, ""));
38 |
39 | next_token = super().nextToken()
40 |
41 | if next_token.channel == Token.DEFAULT_CHANNEL:
42 | # Keep track of the last token on the default channel.
43 | self.lastToken = next_token
44 |
45 | return self.tokens.pop(0) if len(self.tokens) > 0 else next_token
46 |
47 | def create_dedent(self):
48 | dedent = self.common_token(self.DEDENT, "")
49 | dedent.line = self.lastToken.line
50 | return dedent
51 |
52 | def common_token(self, type, text):
53 | stop = self.getCharIndex() - 1
54 | start = stop - len(text) + 1 if text else stop
55 | return CommonToken(self._tokenFactorySourcePair, type, Token.DEFAULT_CHANNEL, start, stop)
56 |
57 | @staticmethod
58 | def getIndentationCount(spaces):
59 | count = 0
60 | for ch in spaces:
61 | if ch == '\t':
62 | count += 8 - (count % 8)
63 | else: # A normal space char.
64 | count += 1
65 | return count
66 |
67 | def skipToken(self):
68 | self.skip()
69 |
70 | def atStartOfInput(self):
71 | return self._interp.column == 0 and self._interp.line == 1
72 |
73 | def handleNewline(self):
74 | new_line = re.sub(r"[^\r\n\f]+", "", self._interp.getText(self._input)) #.replaceAll("[^\r\n\f]+", "")
75 | spaces = re.sub(r"[\r\n\f]+", "", self._interp.getText(self._input)) #.replaceAll("[\r\n\f]+", "")
76 | next = self._input.LA(1)
77 |
78 | if self.opened > 0 or next == '\r' or next == '\n' or next == '\f' or next == '#':
79 | self.skip()
80 | else:
81 | self.emitToken(self.common_token(self.NEWLINE, new_line))
82 |
83 | indent = self.getIndentationCount(spaces)
84 | if len(self.indents) == 0:
85 | previous = 0
86 | else:
87 | previous = self.indents[-1]
88 |
89 | if indent == previous:
90 | self.skip()
91 | elif indent > previous:
92 | self.indents.append(indent)
93 | self.emitToken(self.common_token(UVLPythonParser.INDENT, spaces))
94 | else:
95 | while len(self.indents) > 0 and self.indents[-1] > indent:
96 | self.emitToken(self.create_dedent())
97 | del self.indents[-1]
--------------------------------------------------------------------------------
/js/src/UVLJavaScriptCustomLexer.js:
--------------------------------------------------------------------------------
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, ""));
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 | }
104 |
--------------------------------------------------------------------------------
/uvl/UVLParser.g4:
--------------------------------------------------------------------------------
1 | parser grammar UVLParser;
2 |
3 | options {
4 | tokenVocab = UVLLexer;
5 | }
6 |
7 | featureModel:
8 | namespace? NEWLINE? includes? NEWLINE? imports? NEWLINE? features? NEWLINE? constraints? EOF;
9 |
10 | includes: INCLUDE_KEY NEWLINE INDENT includeLine* DEDENT;
11 | includeLine: languageLevel NEWLINE;
12 |
13 | namespace: NAMESPACE_KEY reference;
14 |
15 | imports: IMPORTS_KEY NEWLINE INDENT importLine* DEDENT;
16 | importLine: ns = reference (AS_KEY alias = reference)? NEWLINE;
17 |
18 | features: FEATURES_KEY NEWLINE INDENT feature DEDENT;
19 |
20 | group:
21 | ORGROUP groupSpec # OrGroup
22 | | ALTERNATIVE groupSpec # AlternativeGroup
23 | | OPTIONAL groupSpec # OptionalGroup
24 | | MANDATORY groupSpec # MandatoryGroup
25 | | CARDINALITY groupSpec # CardinalityGroup;
26 |
27 | groupSpec: NEWLINE INDENT feature+ DEDENT;
28 |
29 | feature:
30 | featureType? reference featureCardinality? attributes? NEWLINE (
31 | INDENT group+ DEDENT
32 | )?;
33 |
34 | featureCardinality: CARDINALITY_KEY CARDINALITY;
35 |
36 | attributes:
37 | OPEN_BRACE (attribute (COMMA attribute)*)? CLOSE_BRACE;
38 |
39 | attribute: valueAttribute | constraintAttribute;
40 |
41 | valueAttribute: key value?;
42 | key: id;
43 | value: BOOLEAN | FLOAT | INTEGER | STRING | attributes | vector;
44 | vector: OPEN_BRACK (value (COMMA value)*)? CLOSE_BRACK;
45 |
46 | constraintAttribute:
47 | CONSTRAINT_KEY constraint # SingleConstraintAttribute
48 | | CONSTRAINTS_KEY constraintList # ListConstraintAttribute;
49 | constraintList:
50 | OPEN_BRACK (constraint (COMMA constraint)*)? CLOSE_BRACK;
51 |
52 | constraints:
53 | CONSTRAINTS_KEY NEWLINE INDENT constraintLine* DEDENT;
54 |
55 | constraintLine: constraint NEWLINE;
56 |
57 | constraint:
58 | equation # EquationConstraint
59 | | reference # LiteralConstraint
60 | | OPEN_PAREN constraint CLOSE_PAREN # ParenthesisConstraint
61 | | NOT constraint # NotConstraint
62 | | constraint AND constraint # AndConstraint
63 | | constraint OR constraint # OrConstraint
64 | | constraint IMPLICATION constraint # ImplicationConstraint
65 | | constraint EQUIVALENCE constraint # EquivalenceConstraint;
66 |
67 | equation:
68 | expression EQUAL expression # EqualEquation
69 | | expression LOWER expression # LowerEquation
70 | | expression GREATER expression # GreaterEquation
71 | | expression LOWER_EQUALS expression # LowerEqualsEquation
72 | | expression GREATER_EQUALS expression # GreaterEqualsEquation
73 | | expression NOT_EQUALS expression # NotEqualsEquation;
74 |
75 | expression
76 | : additiveExpression
77 | ;
78 |
79 | additiveExpression
80 | : additiveExpression ADD multiplicativeExpression # AddExpression
81 | | additiveExpression SUB multiplicativeExpression # SubExpression
82 | | multiplicativeExpression # MultiplicativeExpr
83 | ;
84 |
85 | multiplicativeExpression
86 | : multiplicativeExpression MUL primaryExpression # MulExpression
87 | | multiplicativeExpression DIV primaryExpression # DivExpression
88 | | primaryExpression # PrimaryExpressionExpression
89 | ;
90 |
91 | primaryExpression
92 | : FLOAT # FloatLiteralExpression
93 | | INTEGER # IntegerLiteralExpression
94 | | STRING # StringLiteralExpression
95 | | aggregateFunction # AggregateFunctionExpression
96 | | reference # LiteralExpression
97 | | OPEN_PAREN expression CLOSE_PAREN # BracketExpression
98 | ;
99 |
100 | aggregateFunction:
101 | sumAggregateFunction # SumAggregateFunctionExpression
102 | | avgAggregateFunction # AvgAggregateFunctionExpression
103 | | stringAggregateFunction # StringAggregateFunctionExpression
104 | | numericAggregateFunction # NumericAggregateFunctionExpression
105 | ;
106 |
107 | sumAggregateFunction:
108 | SUM_KEY OPEN_PAREN (reference COMMA)? reference CLOSE_PAREN;
109 |
110 |
111 | avgAggregateFunction:
112 | AVG_KEY OPEN_PAREN (reference COMMA)? reference CLOSE_PAREN;
113 |
114 | stringAggregateFunction:
115 | LEN_KEY OPEN_PAREN reference CLOSE_PAREN # LengthAggregateFunction
116 | ;
117 |
118 | numericAggregateFunction:
119 | FLOOR_KEY OPEN_PAREN reference CLOSE_PAREN # FloorAggregateFunction
120 | | CEIL_KEY OPEN_PAREN reference CLOSE_PAREN # CeilAggregateFunction
121 | ;
122 |
123 | reference: (id DOT)* id;
124 | id: ID_STRICT | ID_NOT_STRICT;
125 |
126 | featureType: STRING_KEY | INTEGER_KEY | BOOLEAN_KEY | REAL_KEY;
127 |
128 | languageLevel: majorLevel (DOT (minorLevel | MUL))?;
129 |
130 | majorLevel: BOOLEAN_KEY | ARITHMETIC_KEY | TYPE_KEY;
131 |
132 | minorLevel:
133 | GROUP_CARDINALITY_KEY
134 | | FEATURE_CARDINALITY_KEY
135 | | AGGREGATE_KEY
136 | | STRING_CONSTRAINTS_KEY;
137 |
--------------------------------------------------------------------------------
/uvl/Java/UVLJavaLexer.g4:
--------------------------------------------------------------------------------
1 | lexer grammar UVLJavaLexer;
2 |
3 | import UVLLexer;
4 |
5 | @header {
6 | package uvl;
7 | }
8 |
9 | @members {
10 | // A queue where extra tokens are pushed on (see the NEWLINE lexer rule).
11 | private java.util.LinkedList tokens = new java.util.LinkedList<>();
12 | // The stack that keeps track of the indentation level.
13 | private java.util.Stack indents = new java.util.Stack<>();
14 | // The amount of opened braces, brackets and parenthesis.
15 | private int opened = 0;
16 | // The most recently produced token.
17 | private Token lastToken = null;
18 |
19 | @Override
20 | public void emit(Token t) {
21 | super.setToken(t);
22 | tokens.offer(t);
23 | }
24 |
25 | @Override
26 | public Token nextToken() {
27 | // Check if the end-of-file is ahead and there are still some DEDENTS expected.
28 | if (_input.LA(1) == EOF && !this.indents.isEmpty()) {
29 | // Remove any trailing EOF tokens from our buffer.
30 | for (int i = tokens.size() - 1; i >= 0; i--) {
31 | if (tokens.get(i).getType() == EOF) {
32 | tokens.remove(i);
33 | }
34 | }
35 | // First emit an extra line break that serves as the end of the statement.
36 | this.emit(commonToken(UVLJavaParser.NEWLINE, "\n"));
37 | // Now emit as much DEDENT tokens as needed.
38 | while (!indents.isEmpty()) {
39 | this.emit(createDedent());
40 | indents.pop();
41 | }
42 | // Put the EOF back on the token stream.
43 | this.emit(commonToken(UVLJavaParser.EOF, ""));
44 | }
45 |
46 | Token next = super.nextToken();
47 | if (next.getChannel() == Token.DEFAULT_CHANNEL) {
48 | // Keep track of the last token on the default channel.
49 | this.lastToken = next;
50 | }
51 |
52 | return tokens.isEmpty() ? next : tokens.poll();
53 | }
54 |
55 | private Token createDedent() {
56 | CommonToken dedent = commonToken(UVLJavaParser.DEDENT, "");
57 | dedent.setLine(this.lastToken.getLine());
58 | return dedent;
59 | }
60 |
61 | private CommonToken commonToken(int type, String text) {
62 | int stop = this.getCharIndex() - 1;
63 | int start = text.isEmpty() ? stop : stop - text.length() + 1;
64 | return new CommonToken(this._tokenFactorySourcePair, type, DEFAULT_TOKEN_CHANNEL, start, stop);
65 | }
66 |
67 | // Calculates the indentation of the provided spaces, taking the
68 | // following rules into account:
69 | //
70 | // "Tabs are replaced (from left to right) by one to eight spaces
71 | // such that the total number of characters up to and including
72 | // the replacement is a multiple of eight [...]"
73 | //
74 | // -- https://docs.python.org/3.1/reference/lexical_analysis.html#indentation
75 | static int getIndentationCount(String spaces) {
76 | int count = 0;
77 | for (char ch : spaces.toCharArray()) {
78 | switch (ch) {
79 | case '\t':
80 | count += 8 - (count % 8);
81 | break;
82 | default:
83 | // A normal space char.
84 | count++;
85 | }
86 | }
87 | return count;
88 | }
89 |
90 | boolean atStartOfInput() {
91 | return super.getCharPositionInLine() == 0 && super.getLine() == 1;
92 | }
93 | }
94 |
95 | // Indentation-sensitive tokens
96 | OPEN_PAREN: '(' {this.opened += 1;};
97 | CLOSE_PAREN: ')' {this.opened -= 1;};
98 | OPEN_BRACK: '[' {this.opened += 1;};
99 | CLOSE_BRACK: ']' {this.opened -= 1;};
100 | OPEN_BRACE: '{' {this.opened += 1;};
101 | CLOSE_BRACE: '}' {this.opened -= 1;};
102 | OPEN_COMMENT: '/*' {this.opened += 1;};
103 | CLOSE_COMMENT: '*/' {this.opened -= 1;};
104 |
105 | // Indentation-sensitive NEWLINE rule
106 | NEWLINE: (
107 | {atStartOfInput()}? SPACES
108 | | ( '\r'? '\n' | '\r') SPACES?
109 | ) {
110 | String newLine = getText().replaceAll("[^\r\n]+", "");
111 | String spaces = getText().replaceAll("[\r\n]+", "");
112 | int next = _input.LA(1);
113 | int nextNext = _input.LA(2);
114 |
115 | if (opened > 0 || next == '\r' || next == '\n' || (next == '/' && nextNext == '/')) {
116 | // If we're inside a list or on a blank line, ignore all indents,
117 | // dedents and line breaks.
118 | skip();
119 | } else {
120 | emit(commonToken(UVLJavaParser.NEWLINE, newLine));
121 | int indent = getIndentationCount(spaces);
122 | int previous = indents.isEmpty() ? 0 : indents.peek();
123 | if (indent == previous) {
124 | // skip indents of the same size as the present indent-size
125 | skip();
126 | }
127 | else if (indent > previous) {
128 | indents.push(indent);
129 | emit(commonToken(UVLJavaParser.INDENT, spaces));
130 | }
131 | else {
132 | // Possibly emit more than 1 DEDENT token.
133 | while(!indents.isEmpty() && indents.peek() > indent) {
134 | this.emit(createDedent());
135 | indents.pop();
136 | }
137 | }
138 | }
139 | };
140 |
141 | // Fragment used by NEWLINE
142 | fragment SPACES: [ \t]+;
143 |
--------------------------------------------------------------------------------
/python/README.md:
--------------------------------------------------------------------------------
1 | # UVL - Universal Variability Language
2 |
3 | **UVL (Universal Variability Language)** is a concise and extensible language for modeling variability in software product lines. It supports multiple programming languages and provides a grammar-based foundation for building tools and parsers.
4 |
5 | This repository contains the **ANTLR4 grammar files** for UVL. With these, you can generate parsers for UVL tailored to specific programming languages like Java, JavaScript, and Python.
6 |
7 | ## ✨ Key Features
8 |
9 | - Language-level modularity
10 | - Namespaces and imports
11 | - Feature trees with attributes and cardinalities
12 | - Cross-tree constraints
13 | - Extensible for different target languages
14 |
15 | ## 📦 Repository Structure
16 |
17 | - `uvl/UVLParser.g4` – Base grammar in EBNF form
18 | - `uvl/UVLLexer.g4` – Base lexer grammar for UVL
19 | - `uvl/Java/UVLJava*.g4`, `uvl/Python/UVLPython*.g4`, etc. – Language-specific grammar files
20 | - `java/` – Java-based parser implementation using Maven
21 | - `python/` – Python-based parser implementation
22 | - `javascript/` – JavaScript-based parser implementation
23 | - `tests/` – UVL test cases for validation
24 |
25 | UVL uses [ANTLR4](https://www.antlr.org/) as its parser generator.
26 |
27 | ---
28 |
29 | ## 💡 Language Overview
30 |
31 | Each UVL model may consist of five optional sections:
32 |
33 | 1. **Language levels**: Enable optional concepts via `include` keyword.
34 | 2. **Namespace**: Allows referencing the model from other UVL models.
35 | 3. **Imports**: Include other feature models (e.g., `subdir.filename as fn`).
36 | 4. **Feature tree**: Hierarchical features with cardinalities, attributes, and group types (`mandatory`, `optional`, `or`, `alternative`).
37 | 5. **Cross-tree constraints**: Logical and arithmetic constraints among features.
38 |
39 | ### 🔍 Example
40 |
41 | ```uvl
42 | namespace Server
43 |
44 | features
45 | Server {abstract}
46 | mandatory
47 | FileSystem
48 | or
49 | NTFS
50 | APFS
51 | EXT4
52 | OperatingSystem {abstract}
53 | alternative
54 | Windows
55 | macOS
56 | Debian
57 | optional
58 | Logging {
59 | default,
60 | log_level "warn"
61 | }
62 |
63 | constraints
64 | Windows => NTFS
65 | macOS => APFS
66 | ```
67 |
68 | **Explanation:**
69 | - `Server` is an abstract feature.
70 | - It must include a `FileSystem` and an `OperatingSystem`.
71 | - `Logging` is optional and includes an attribute.
72 | - Logical constraints define dependencies between features.
73 |
74 | 🔗 More examples: https://github.com/Universal-Variability-Language/uvl-models/tree/main/Feature_Models
75 |
76 | ---
77 |
78 |
79 | ## Usage
80 | To use UVL in your projects, you can either:
81 | 1. **Use the pre-built parsers**
82 | ### Java Parser
83 | Include the following dependency in your Maven project:
84 | ```xml
85 |
86 | io.github.universal-variability-language
87 | uvl-parser
88 | 0.3
89 |
90 | ```
91 | ### Python Parser
92 | Install the package via pip:
93 | ```bash
94 | pip install uvlparser
95 | ```
96 | ### JavaScript Parser
97 | Install the package via npm:
98 | ```bash
99 | npm install uvl-parser
100 | ```
101 | 2. **Build the parser manually** See the sections below for details.
102 |
103 | ## ⚙️ Building the Parser manually
104 | ### Java Parser
105 | #### Prerequisites
106 |
107 | - [ANTLR4](https://www.antlr.org/)
108 | - Java 17+
109 | - [Maven](https://maven.apache.org/)
110 |
111 | #### Build Steps
112 |
113 | 1. Clone the repository:
114 | ```bash
115 | git clone https://github.com/Universal-Variability-Language/uvl-parser
116 | ```
117 |
118 | 2. Build the parser:
119 | ```bash
120 | cd java
121 | mvn clean package
122 | ```
123 |
124 | 3. Include the generated JAR in your Java project.
125 | ---
126 |
127 | ## 📚 Resources
128 |
129 | **UVL Models & Tools**
130 | - https://github.com/Universal-Variability-Language/uvl-models
131 | - https://www.uvlhub.io/
132 |
133 | **Tooling Ecosystem**
134 | - https://github.com/FeatureIDE/FeatureIDE
135 | - https://ide.flamapy.org/
136 | - https://github.com/Universal-Variability-Language/uvl-lsp
137 | - https://github.com/SECPS/TraVarT
138 | - https://github.com/AlexCortinas/spl-js-engine
139 |
140 | ---
141 |
142 | ## 📖 Citation
143 |
144 | If you use UVL in your research, please cite:
145 |
146 | ```bibtex
147 | @article{UVL2024,
148 | title = {UVL: Feature modelling with the Universal Variability Language},
149 | journal = {Journal of Systems and Software},
150 | volume = {225},
151 | pages = {112326},
152 | year = {2025},
153 | issn = {0164-1212},
154 | doi = {https://doi.org/10.1016/j.jss.2024.112326},
155 | url = {https://www.sciencedirect.com/science/article/pii/S0164121224003704},
156 | author = {David Benavides and Chico Sundermann and Kevin Feichtinger and José A. Galindo and Rick Rabiser and Thomas Thüm},
157 | keywords = {Feature model, Software product lines, Variability}
158 | }
159 | ```
160 | ---
161 |
162 | ## 📬 Contact & Contributions
163 |
164 | Feel free to open issues or pull requests if you have suggestions or improvements. For questions or collaboration inquiries, visit the UVL Website:
165 | https://universal-variability-language.github.io/
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # UVL - Universal Variability Language
2 |
3 | **UVL (Universal Variability Language)** is a concise and extensible language for modeling variability in software product lines. It supports multiple programming languages and provides a grammar-based foundation for building tools and parsers.
4 |
5 | This repository contains the **ANTLR4 grammar files** for UVL. With these, you can generate parsers for UVL tailored to specific programming languages like Java, JavaScript, and Python.
6 |
7 | ## ✨ Key Features
8 |
9 | - Language-level modularity
10 | - Namespaces and imports
11 | - Feature trees with attributes and cardinalities
12 | - Cross-tree constraints
13 | - Extensible for different target languages
14 |
15 | ## 📦 Repository Structure
16 |
17 | - `uvl/UVLParser.g4` – Base grammar in EBNF form
18 | - `uvl/UVLLexer.g4` – Base lexer grammar for UVL
19 | - `uvl/Java/UVLJava*.g4`, `uvl/Python/UVLPython*.g4`, etc. – Language-specific grammar files
20 | - `java/` – Java-based parser implementation using Maven (generated)
21 | - `python/` – Python-based parser implementation (generated)
22 | - `js/` – JavaScript-based parser implementation (generated)
23 |
24 | UVL uses [ANTLR4](https://www.antlr.org/) as its parser generator.
25 |
26 | ## Usage
27 |
28 | To use UVL in your projects, you can either:
29 |
30 | 1. **Use the pre-built parsers**
31 | ### Java Parser
32 | Include the following dependency in your Maven project:
33 | ```xml
34 |
35 | io.github.universal-variability-language
36 | uvl-parser
37 | 0.3
38 |
39 | ```
40 | ### Python Parser
41 | Install the package via pip:
42 | ```bash
43 | pip install uvlparser
44 | ```
45 | ### JavaScript Parser
46 | Install the package via npm:
47 | ```bash
48 | npm install uvl-parser
49 | ```
50 | 2. **Build the parser manually** See the sections below for details.
51 |
52 | ## ⚙️ Building the Parser manually
53 |
54 | ### Java Parser
55 |
56 | #### Prerequisites
57 |
58 | - Java 17+
59 | - [Maven](https://maven.apache.org/)
60 |
61 | #### Build Steps
62 |
63 | 1. Clone the repository:
64 |
65 | ```bash
66 | git clone https://github.com/Universal-Variability-Language/uvl-parser
67 | ```
68 |
69 | 2. Build the parser:
70 |
71 | ```bash
72 | make java_parser
73 | ```
74 |
75 | This will generate the jar file in the `java/target/` directory. You can also build the JAR with:
76 |
77 | ```bash
78 | cd java && mvn clean package
79 | ```
80 |
81 | 3. Include the generated JAR in your Java project.
82 |
83 | ---
84 |
85 | ### Python Parser
86 |
87 | #### Prerequisites
88 |
89 | - Python 3.8+
90 | - [ANTLR4](https://www.antlr.org/)
91 |
92 | #### Build Steps
93 |
94 | 1. Clone the repository:
95 |
96 | ```bash
97 | git clone https://github.com/Universal-Variability-Language/uvl-parser
98 | ```
99 |
100 | 2. Build the parser:
101 |
102 | ```bash
103 | make python_parser
104 | ```
105 |
106 | This will generate the parser files in the `python/` directory. To build the wheel package, run:
107 |
108 | ```bash
109 | make python_prepare_package
110 | ```
111 |
112 | ### JavaScript Parser
113 |
114 | #### Prerequisites
115 |
116 | - Node.js 14+
117 | - [ANTLR4](https://www.antlr.org/)
118 |
119 | #### Build Steps
120 |
121 | 1. Clone the repository:
122 |
123 | ```bash
124 | git clone https://github.com/Universal-Variability-Language/uvl-parser
125 | ```
126 |
127 | 2. Build the parser:
128 |
129 | ```bash
130 | make javascript_parser
131 | ```
132 |
133 | ## This will generate the parser files in the `js/` directory.
134 |
135 | ## 💡 Universal-Variability-Language (UVL)
136 |
137 | For comprehensive guidance on utilizing UVL, please refer to the following publication:
138 |
139 | [](https://doi.org/10.1016/j.jss.2024.112326)
140 |
141 | 🔗 **Sample UVL models** are available at: https://github.com/Universal-Variability-Language/uvl-models
142 |
143 | ---
144 |
145 | ## 📚 Resources
146 |
147 | **UVL Models & Tools**
148 |
149 | - https://github.com/Universal-Variability-Language/uvl-models
150 | - https://www.uvlhub.io/
151 |
152 | **Tooling Ecosystem**
153 |
154 | - https://github.com/FeatureIDE/FeatureIDE
155 | - https://ide.flamapy.org/
156 | - https://github.com/Universal-Variability-Language/uvl-lsp
157 | - https://github.com/SECPS/TraVarT
158 | - https://github.com/AlexCortinas/spl-js-engine
159 |
160 | ---
161 |
162 | ## 📖 Citation
163 |
164 | If you use UVL in your research, please cite:
165 |
166 | ```bibtex
167 | @article{UVL2024,
168 | title = {UVL: Feature modelling with the Universal Variability Language},
169 | journal = {Journal of Systems and Software},
170 | volume = {225},
171 | pages = {112326},
172 | year = {2025},
173 | issn = {0164-1212},
174 | doi = {https://doi.org/10.1016/j.jss.2024.112326},
175 | url = {https://www.sciencedirect.com/science/article/pii/S0164121224003704},
176 | author = {David Benavides and Chico Sundermann and Kevin Feichtinger and José A. Galindo and Rick Rabiser and Thomas Thüm},
177 | keywords = {Feature model, Software product lines, Variability}
178 | }
179 | ```
180 |
181 | ---
182 |
183 | ## 📬 Contact & Contributions
184 |
185 | Feel free to open issues or pull requests if you have suggestions or improvements. For questions or collaboration inquiries, visit the UVL Website:
186 | https://universal-variability-language.github.io/
187 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/java/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | 4.0.0
7 |
8 | UVL Parser
9 | This project host the grammar files and generated source files for the Java version of the UVL parser
10 | https://github.com/Universal-Variability-Language/uvl-parser
11 |
12 | io.github.universal-variability-language
13 | uvl-parser
14 | 0.4.1
15 |
16 |
17 |
18 | MIT License
19 | https://github.com/Universal-Variability-Language/uvl-parser/blob/master/LICENSE
20 | repo
21 |
22 |
23 |
24 |
25 |
26 | José A. Galindo
27 | jagalindo@us.es
28 | University of Seville
29 | https://www.us.es
30 |
31 |
32 | Chico Sundermann
33 | jagalindo@us.es
34 | University of Ulm
35 | https://www.uni-ulm.de/
36 |
37 |
38 | Kevin Feichtinger
39 | kevin.feichtinger@jku.at
40 | Johannes Kepler University
41 | https://www.jku.at/
42 |
43 |
44 | Malte Grave
45 | malte.grave@jku.at
46 | Johannes Kepler University
47 | https://www.jku.at/
48 |
49 |
50 |
51 |
52 | scm:git:git@github.com:Universal-Variability-Language/uvl-parser.git
53 | scm:git:git@github.com:Universal-Variability-Language/uvl-parser.git
54 | https://github.com/Universal-Variability-Language/uvl-parser
55 |
56 |
57 |
58 | 4.13.2
59 | 21
60 | 21
61 | UTF-8
62 | UTF-8
63 |
64 |
65 |
66 |
67 | org.antlr
68 | antlr4-runtime
69 | ${antlr4.version}
70 |
71 |
72 |
73 |
74 |
75 | ossrh
76 | https://s01.oss.sonatype.org/content/repositories/snapshots
77 |
78 |
79 | ossrh
80 | https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/
81 |
82 |
83 |
84 |
85 |
87 |
88 |
89 |
90 | org.antlr
91 | antlr4-maven-plugin
92 | ${antlr4.version}
93 |
94 |
95 | -lib
96 | ../uvl
97 |
98 | ../uvl/Java
99 | ${project.build.directory}/generated-sources/antlr4
100 | true
101 | true
102 |
103 |
104 |
105 |
106 | antlr4
107 |
108 | generate-sources
109 |
110 |
111 |
112 |
113 |
114 | org.codehaus.mojo
115 | build-helper-maven-plugin
116 | 3.2.0
117 |
118 |
119 | add-source
120 | generate-sources
121 |
122 | add-source
123 |
124 |
125 |
126 | ${project.build.directory}/generated-sources/antlr4
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | org.apache.maven.plugins
135 | maven-compiler-plugin
136 | 3.14.0
137 |
138 | ${maven.compiler.source}
139 | ${maven.compiler.target}
140 |
141 |
142 |
143 |
144 | org.apache.maven.plugins
145 | maven-gpg-plugin
146 | 3.2.7
147 |
148 |
149 | sign-artifacts
150 | verify
151 |
152 | sign
153 |
154 |
155 |
156 |
157 |
158 |
159 | org.sonatype.plugins
160 | nexus-staging-maven-plugin
161 | 1.7.0
162 | true
163 |
164 | ossrh
165 | https://s01.oss.sonatype.org/
166 | true
167 |
168 |
169 |
170 |
171 | org.apache.maven.plugins
172 | maven-source-plugin
173 | 3.3.1
174 |
175 |
176 | attach-sources
177 |
178 | jar-no-fork
179 |
180 |
181 |
182 |
183 |
184 | org.apache.maven.plugins
185 | maven-javadoc-plugin
186 | 3.11.2
187 |
188 |
189 | attach-javadocs
190 |
191 | jar
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
--------------------------------------------------------------------------------