diff --git a/lab2/.gitignore b/lab2/.gitignore new file mode 100644 index 0000000..14054eb --- /dev/null +++ b/lab2/.gitignore @@ -0,0 +1,139 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +.idea/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ \ No newline at end of file diff --git a/lab2/README.md b/lab2/README.md new file mode 100644 index 0000000..591066c --- /dev/null +++ b/lab2/README.md @@ -0,0 +1,34 @@ +# Lab2 JS code conventions analyzer and fixer + + +To use package install it from pipy: + +https://pypi.org/project/jsconventionfixer/1.1.4/ + + +Run 'pip install jsconventionfixer==1.1.4' + + +After successful installation project can be used command: +`jsconventionfixer (-v|-fx) -(p|d|f) ` + +#### Command line options +```text +Options: + -v, --verify Verify files without editing. + + -fx, --fix Rewrite files + + -d, --directory TEXT Absolute or relative path to directory with + files to be fixed. + + -p, --project TEXT Absolute or relative path to project with + files to be fixed. + + -f, --file TEXT Absolute or relative path to file to be fixed. + + -h, --help Show this message and exit.` + ``` + *Note: any other input is equivalent to* `--help` + + \ No newline at end of file diff --git a/lab2/examples/subdir/test.js b/lab2/examples/subdir/test.js new file mode 100644 index 0000000..30f81e6 --- /dev/null +++ b/lab2/examples/subdir/test.js @@ -0,0 +1,499 @@ +"use strict" + +@private +privateFunction_ (var1, var2, var3) { +} + +var makeTree = require("../rbtree.js") +var tape = require("tape") +var util = require("util") +var iota = require("iota-array") + +var colors = [ "r", "b", "bb" ] + +enum Statuses = { + READY: 1, + DOING: 2, + DONE: 3 +} + +enum Duplicate { + READY_D: 1, + DOING_D: 2, + DONE_D: 3 +} + +class TestClass { +} + + +function printTree(tree) { + if(!tree) { + return [] + } + return [ colors[tree.color], tree.key, printTree(tree.left), printTree(tree.right) ] +} + +function print(t) { + console.log(util.inspect(printTree(t.root), {depth:12})) +} + +//Ensures the red black axioms are satisfied by tree +function checkTree(tree, t) { + if(!tree.root) { + return + } + t.equals(tree.root.color, 1, "root is black") + function checkNode(node) { + if(!node) { + return [1, 0] + } + if(node.color === 0) { + t.assert(!node.left || node.left.color === 1, "children of red node must be black") + t.assert(!node.right || node.right.color === 1, "children of red node must be black") + } else { + t.equals(node.color, 1, "node color must be red or black") + } + if(node.left) { + t.assert(tree._compare(node.left.key, node.key) <= 0, "left tree order invariant") + } + if(node.right) { + t.assert(tree._compare(node.right.key, node.key) >= 0, "right tree order invariant") + } + var cl = checkNode(node.left) + var cr = checkNode(node.right) + t.equals(cl[0], cr[0], "number of black nodes along all paths to root must be constant") + t.equals(cl[1] + cr[1] + 1, node._count, "item count consistency") + return [cl[0] + node.color, cl[1] + cr[1] + 1] + } + var r = checkNode(tree.root) + t.equals(r[1], tree.length, "tree length") +} + +tape("insert()", function(t) { + var t1 = makeTree() + + var u = t1 + var arr = [] + for(var i=20; i>=0; --i) { + var x = i + var next = u.insert(x, true) + checkTree(u, t) + checkTree(next, t) + t.equals(u.length, arr.length) + arr.push(x) + u = next + } + for(var i=-20; i<0; ++i) { + var x = i + var next = u.insert(x, true) + checkTree(u, t) + checkTree(next, t) + arr.sort(function(a,b) { return a-b }) + var ptr = 0 + u.forEach(function(k,v) { + t.equals(k, arr[ptr++]) + }) + t.equals(ptr, arr.length) + arr.push(x) + u = next + } + + var start = u.begin + for(var i=-20, j=0; j<=40; ++i, ++j) { + t.equals(u.at(j).key, i, "checking at()") + t.equals(start.key, i, "checking iter") + t.equals(start.index, j, "checking index") + t.assert(start.valid, "checking valid") + if(j < 40) { + t.assert(start.hasNext, "hasNext()") + } else { + t.assert(!start.hasNext, "eof hasNext()") + } + start.next() + } + t.assert(!start.valid, "invalid eof iterator") + t.assert(!start.hasNext, "hasNext() at eof fail") + t.equals(start.index, 41, "eof index") + + t.end() +}) + +tape("foreach", function(t) { + var u = iota(31).reduce(function(u, k, v) { + return u.insert(k, v) + }, makeTree()) + + //Check basic foreach + var visitKeys = [] + var visitVals = [] + u.forEach(function(k,v) { + visitKeys.push(k) + visitVals.push(v) + }) + t.same(visitKeys, u.keys) + t.same(visitVals, u.values) + + //Check foreach with termination + visitKeys = [] + visitVals = [] + t.equals(u.forEach(function(k,v) { + if(k === 5) { + return 1000 + } + visitKeys.push(k) + visitVals.push(v) + }), 1000) + t.same(visitKeys, u.keys.slice(0, 5)) + t.same(visitVals, u.values.slice(0, 5)) + + //Check half interval foreach + visitKeys = [] + visitVals = [] + u.forEach(function(k,v) { + visitKeys.push(k) + visitVals.push(v) + }, 3) + t.same(visitKeys, u.keys.slice(3)) + t.same(visitVals, u.values.slice(3)) + + //Check half interval foreach with termination + visitKeys = [] + visitVals = [] + t.equals(u.forEach(function(k,v) { + if(k === 12) { + return 1000 + } + visitKeys.push(k) + visitVals.push(v) + }, 3), 1000) + t.same(visitKeys, u.keys.slice(3, 12)) + t.same(visitVals, u.values.slice(3, 12)) + + + //Check interval foreach + visitKeys = [] + visitVals = [] + u.forEach(function(k,v) { + visitKeys.push(k) + visitVals.push(v) + }, 3, 15) + t.same(visitKeys, u.keys.slice(3, 15)) + t.same(visitVals, u.values.slice(3, 15)) + + //Check interval foreach with termination + visitKeys = [] + visitVals = [] + t.equals(u.forEach(function(k,v) { + if(k === 12) { + return 1000 + } + visitKeys.push(k) + visitVals.push(v) + }, 3, 15), 1000) + t.same(visitKeys, u.keys.slice(3, 12)) + t.same(visitVals, u.values.slice(3, 12)) + + t.end() +}) + +function compareIterators(a, b, t) { + t.equals(a.tree, b.tree, "iter trees") + t.equals(a.valid, b.valid, "iter validity") + if(!b.valid) { + return + } + t.equals(a.node, b.node, "iter node") + t.equals(a.key, b.key, "iter key") + t.equals(a.value, b.value, "iter value") + t.equals(a.index, b.index, "iter index") +} + +tape("iterators", function(t) { + var u = iota(20).reduce(function(u, k, v) { + return u.insert(k, v) + }, makeTree()) + + //Try walking forward + var iter = u.begin + var c = iter.clone() + t.ok(iter.hasNext, "must have next at beginneing") + t.ok(!iter.hasPrev, "must not have predecessor") + for(var i=0; i<20; ++i) { + var v = u.at(i) + compareIterators(iter, v, t) + t.equals(iter.index, i) + iter.next() + } + t.ok(!iter.valid, "must be eof iterator") + + //Check if the clone worked + compareIterators(c, u.begin, t) + + //Try walking backward + var iter = u.end + t.ok(!iter.hasNext, "must not have next") + t.ok(iter.hasPrev, "must have predecessor") + for(var i=19; i>=0; --i) { + var v = u.at(i) + compareIterators(iter, v, t) + t.equals(iter.index, i) + iter.prev() + } + t.ok(!iter.valid, "must be eof iterator") + + t.end() +}) + + +tape("remove()", function(t) { + + var sz = [1, 2, 10, 20, 23, 31, 32, 33] + for(var n=0; n b[0]) { return 1 } + return 0 + }) + + var keys = zipped.map(function(v) { return v[0] }) + var values = zipped.map(function(v) { return v[1] }) + + t.same(u.keys, keys) + t.same(u.values, values) + + t.end() +}) + +tape("searching", function(t) { + + var arr = [0, 1, 1, 1, 1, 2, 3, 4, 5, 6, 6 ] + var u = arr.reduce(function(u, k, v) { + return u.insert(k, v) + }, makeTree()) + + + for(var i=0; i 0, "find repeat") + t.ok(u.find(1).index < 5, "find repeat") + + for(var i=0; i= 0, "right tree order invariant") + } + var cl = checkNode(node.left) + var cr = checkNode(node.right) + t.equals(cl[0], cr[0], "number of black nodes along all paths to root must be constant") + t.equals(cl[1] + cr[1] + 1, node._count, "item count consistency") + return [cl[0] + node.color, cl[1] + cr[1] + 1] + } + var r = checkNode(tree.root) + t.equals(r[1], tree.length, "tree length") +} + +tape("insert()", function(t) { + var t1 = makeTree() + + var u = t1 + var arr = [] + for(var i=20; i>=0; --i) { + var x = i + var next = u.insert(x, true) + checkTree(u, t) + checkTree(next, t) + t.equals(u.length, arr.length) + arr.push(x) + u = next + } + for(var i=-20; i<0; ++i) { + var x = i + var next = u.insert(x, true) + checkTree(u, t) + checkTree(next, t) + arr.sort(function(a,b) { return a-b }) + var ptr = 0 + u.forEach(function(k,v) { + t.equals(k, arr[ptr++]) + }) + t.equals(ptr, arr.length) + arr.push(x) + u = next + } + + var start = u.begin + for(var i=-20, j=0; j<=40; ++i, ++j) { + t.equals(u.at(j).key, i, "checking at()") + t.equals(start.key, i, "checking iter") + t.equals(start.index, j, "checking index") + t.assert(start.valid, "checking valid") + if(j < 40) { + t.assert(start.hasNext, "hasNext()") + } else { + t.assert(!start.hasNext, "eof hasNext()") + } + start.next() + } + t.assert(!start.valid, "invalid eof iterator") + t.assert(!start.hasNext, "hasNext() at eof fail") + t.equals(start.index, 41, "eof index") + + t.end() +}) + +tape("foreach", function(t) { + var u = iota(31).reduce(function(u, k, v) { + return u.insert(k, v) + }, makeTree()) + + //Check basic foreach + var visitKeys = [] + var visitVals = [] + u.forEach(function(k,v) { + visitKeys.push(k) + visitVals.push(v) + }) + t.same(visitKeys, u.keys) + t.same(visitVals, u.values) + + //Check foreach with termination + visitKeys = [] + visitVals = [] + t.equals(u.forEach(function(k,v) { + if(k === 5) { + return 1000 + } + visitKeys.push(k) + visitVals.push(v) + }), 1000) + t.same(visitKeys, u.keys.slice(0, 5)) + t.same(visitVals, u.values.slice(0, 5)) + + //Check half interval foreach + visitKeys = [] + visitVals = [] + u.forEach(function(k,v) { + visitKeys.push(k) + visitVals.push(v) + }, 3) + t.same(visitKeys, u.keys.slice(3)) + t.same(visitVals, u.values.slice(3)) + + //Check half interval foreach with termination + visitKeys = [] + visitVals = [] + t.equals(u.forEach(function(k,v) { + if(k === 12) { + return 1000 + } + visitKeys.push(k) + visitVals.push(v) + }, 3), 1000) + t.same(visitKeys, u.keys.slice(3, 12)) + t.same(visitVals, u.values.slice(3, 12)) + + + //Check interval foreach + visitKeys = [] + visitVals = [] + u.forEach(function(k,v) { + visitKeys.push(k) + visitVals.push(v) + }, 3, 15) + t.same(visitKeys, u.keys.slice(3, 15)) + t.same(visitVals, u.values.slice(3, 15)) + + //Check interval foreach with termination + visitKeys = [] + visitVals = [] + t.equals(u.forEach(function(k,v) { + if(k === 12) { + return 1000 + } + visitKeys.push(k) + visitVals.push(v) + }, 3, 15), 1000) + t.same(visitKeys, u.keys.slice(3, 12)) + t.same(visitVals, u.values.slice(3, 12)) + + t.end() +}) + +function compareIterators(a, b, t) { + t.equals(a.tree, b.tree, "iter trees") + t.equals(a.valid, b.valid, "iter validity") + if(!b.valid) { + return + } + t.equals(a.node, b.node, "iter node") + t.equals(a.key, b.key, "iter key") + t.equals(a.value, b.value, "iter value") + t.equals(a.index, b.index, "iter index") +} + +tape("iterators", function(t) { + var u = iota(20).reduce(function(u, k, v) { + return u.insert(k, v) + }, makeTree()) + + //Try walking forward + var iter = u.begin + var c = iter.clone() + t.ok(iter.hasNext, "must have next at beginneing") + t.ok(!iter.hasPrev, "must not have predecessor") + for(var i=0; i<20; ++i) { + var v = u.at(i) + compareIterators(iter, v, t) + t.equals(iter.index, i) + iter.next() + } + t.ok(!iter.valid, "must be eof iterator") + + //Check if the clone worked + compareIterators(c, u.begin, t) + + //Try walking backward + var iter = u.end + t.ok(!iter.hasNext, "must not have next") + t.ok(iter.hasPrev, "must have predecessor") + for(var i=19; i>=0; --i) { + var v = u.at(i) + compareIterators(iter, v, t) + t.equals(iter.index, i) + iter.prev() + } + t.ok(!iter.valid, "must be eof iterator") + + t.end() +}) + + +tape("remove()", function(t) { + + var sz = [1, 2, 10, 20, 23, 31, 32, 33] + for(var n=0; n b[0]) { return 1 } + return 0 + }) + + var keys = zipped.map(function(v) { return v[0] }) + var values = zipped.map(function(v) { return v[1] }) + + t.same(u.keys, keys) + t.same(u.values, values) + + t.end() +}) + +tape("searching", function(t) { + + var arr = [0, 1, 1, 1, 1, 2, 3, 4, 5, 6, 6 ] + var u = arr.reduce(function(u, k, v) { + return u.insert(k, v) + }, makeTree()) + + + for(var i=0; i 0, "find repeat") + t.ok(u.find(1).index < 5, "find repeat") + + for(var i=0; i': + return Lexema(LexemType.GREATER_THAN, str[0:1]), str[1:] + elif str[0] == '=': + return Lexema(LexemType.EQUAL, str[0:1]), str[1:] + elif str[0] == '*': + return Lexema(LexemType.OPERATOR, str[0:1]), str[1:] + elif str[0] == '+': + return Lexema(LexemType.OPERATOR, str[0:1]), str[1:] + elif str[0] == '%': + return Lexema(LexemType.OPERATOR, str[0:1]), str[1:] + elif str[0] == '-': + return Lexema(LexemType.OPERATOR, str[0:1]), str[1:] + elif str[0] == '/': + return Lexema(LexemType.OPERATOR, str[0:1]), str[1:] + elif str[0] == '&': + return Lexema(LexemType.OPERATOR, str[0:1]), str[1:] + elif str[0] == '|': + return Lexema(LexemType.OPERATOR, str[0:1]), str[1:] + elif str[0] == '#': + return Lexema(LexemType.HASH, str[0:1]), str[1:] + elif str[0] == '.': + return Lexema(LexemType.DOT, str[0:1]), str[1:] + elif str[0] == ',': + return Lexema(LexemType.COMMA, str[0:1]), str[1:] + elif str[0] == ':': + return Lexema(LexemType.COLON, str[0:1]), str[1:] + elif str[0] == ';': + return Lexema(LexemType.SEMICOLON, str[0:1]), str[1:] + elif str[0] == '@': + return Lexema(LexemType.AT, str[0:1]), str[1:] + elif str[0] == '?': + return Lexema(LexemType.SEMICOLON, str[0:1]), str[1:] + elif str[0] == '!': + return Lexema(LexemType.AT, str[0:1]), str[1:] + else: + return None, str + + @classmethod + def __lex_whitespace(cls, str): + i = 0 + while i < len(str) and str[i] in ' \n\r\t\v\0\f': + i += 1 + + if i == 0: + return None, str + else: + return Lexema(LexemType.WHITESPACE, str[0:i]), str[i:] + + @classmethod + def __lex_multi_comment(cls, str): + if not (str[0] == '/' and str[1] == '*'): + return None, str + i = 2 + + while i < len(str) - 1 and not (str[i] == '*' and str[i + 1] == '/'): + i += 1 + if i == len(str) - 1: + return None, str + i += 2 + + return Lexema(LexemType.MULTI_COMMENT, str[0:i]), str[i:] + + @classmethod + def __lex_comment(cls, str): + if not (str[0] == '/' and str[1] == '/'): + return None, str + i = 2 + + while i < len(str) and str[i] != '\n': + i += 1 + + return Lexema(LexemType.COMMENT, str[0:i]), str[i:] + + @classmethod + def __lex_identifier(cls, str): + i = 0 + if str[i] != '_' and not str[i].isalpha(): + return None, str + + while i < len(str) and (str[i].isalpha() or str[i].isnumeric() or str[i] == '_'): + i += 1 + + return Lexema(LexemType.IDENTIFIER, str[0:i]), str[i:] + + @classmethod + def __lex_str(cls, str): + if str[0] != '\'' and str[0] != '\"': + return None, str + + quote = str[0] + i = 1 + + def is_closing_quote(str, i): + return str[i] == quote and str[i-1] != '\\' + + while i < len(str) and not is_closing_quote(str, i): + i += 1 + + if i == len(str): + return None, str + + i += 1 + return Lexema(LexemType.STRING, str[0:i]), str[i:] + + @classmethod + def __lex_number(cls, str): + t = re.search("[-+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?", str) + + if not t: + return None, str + + number = t.group() + + if not number: + return None, str + + number_len = len(number) + + p_number = str[0:number_len] + if p_number == number: + return Lexema(LexemType.NUMBER, number), str[number_len:] + else: + return None, str diff --git a/lab2/main.py b/lab2/main.py new file mode 100644 index 0000000..b8798b9 --- /dev/null +++ b/lab2/main.py @@ -0,0 +1,46 @@ +import argparse +import logging + +from semantical_analysis.naming_fixer import NamingFixer + +def main(): + + logger = logging.getLogger() + fhandler = logging.FileHandler(filename='all.log', mode='w') + formatter = logging.Formatter('%(levelname)s: %(message)s') + fhandler.setFormatter(formatter) + logger.addHandler(fhandler) + logger.setLevel(logging.DEBUG) + + parser = argparse.ArgumentParser('JavaScriptCCF') + parser.add_argument('-v', '--verify', action='store_true', help='verify naming conventions') + parser.add_argument('-fx', '--fix', action='store_true', help='fix naming conventions') + parser.add_argument('-f', '--file', nargs=1, required=False, help='javascript source code file') + parser.add_argument('-p', '--project', nargs=1, required=False, help='javascript project directory javascript source code files') + parser.add_argument('-d', '--directory', nargs=1, required=False, help='directory javascript source code files') + + my_namespace = parser.parse_args() + if not (my_namespace.verify or my_namespace.fix) or \ + not (my_namespace.directory is not None or + my_namespace.file is not None or + my_namespace.project is not None): + print("Incorrect input. Help:") + parser.print_help() + return + + if my_namespace.fix: + if my_namespace.project is not None: + NamingFixer.fix_project(my_namespace.project[0]) + elif my_namespace.directory is not None: + NamingFixer.fix_directory(my_namespace.directory[0]) + else: + NamingFixer.fix_file(my_namespace.file[0]) + else: + if my_namespace.project is not None: + NamingFixer.verify_project(my_namespace.project[0]) + elif my_namespace.directory is not None: + NamingFixer.verify_directory(my_namespace.directory[0]) + else: + NamingFixer.verify_file(my_namespace.file[0]) + +main() \ No newline at end of file diff --git a/lab2/semantical_analysis/__init__.py b/lab2/semantical_analysis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lab2/semantical_analysis/naming_fixer.py b/lab2/semantical_analysis/naming_fixer.py new file mode 100644 index 0000000..d1289c5 --- /dev/null +++ b/lab2/semantical_analysis/naming_fixer.py @@ -0,0 +1,234 @@ +import logging +import ntpath +import os +import platform +import re +import string + +from lexical_analysis.lexema import LexemType +from lexical_analysis.lexer import Lexer +from semantical_analysis.parser import Parser + +from console_progressbar import ProgressBar + +class NamingFixer: + + @classmethod + def verify_project(cls, projpath): + files_to_verify = [] + for root, dirs, files in os.walk(projpath): + for f in files: + if f.endswith(".js"): + files_to_verify.append(os.path.join(root, f)) + if len(files) == 0: + return + pb = ProgressBar(total=len(files_to_verify), prefix='Files processed', suffix='', decimals=1, length=50) + progress = 0 + pb.print_progress_bar(0) + for f in files_to_verify: + cls.verify_file(f) + progress += 1 + pb.print_progress_bar(progress) + + @classmethod + def verify_directory(cls, dirpath): + files = [] + for f in os.listdir(dirpath): + if f.endswith(".js"): + files.append(os.path.join(dirpath, f)) + if len(files) == 0: + return + pb = ProgressBar(total=len(files), prefix='Files processed', suffix='', decimals=1, length=50) + progress = 0 + pb.print_progress_bar(0) + for f in files: + cls.verify_file(f) + progress += 1 + pb.print_progress_bar(progress) + + @classmethod + def verify_file(cls, filepath): + fhandler = logging.FileHandler(filename=filepath + '_verification.log', mode='w') + formatter = logging.Formatter('%(levelname)s: %(message)s') + fhandler.setFormatter(formatter) + + log = logging.getLogger() # root logger + for hdlr in log.handlers[:]: # remove all old handlers + log.removeHandler(hdlr) + log.addHandler(fhandler) + + filename = ntpath.basename(filepath) + clear_filename = filename[0:(len(filename)-3)] + if not cls.__is_correct_filename(clear_filename): + new_name = cls.__to_correct_filename(clear_filename) + logging.warning(f'{filepath}: {filename} naming error -> should be {new_name+".js"}') + + f = open(filepath, 'r') + text = f.read() + f.close() + lexems = Lexer.lex(text) + Parser.parse(lexems) + + fixes = cls.analyze(lexems) + + for fix in fixes: + for lexema in lexems: + if lexema.get_str() == fix[0]: + logging.warning(f'{filepath}: {fix[0]} naming error -> should be {fix[1]}') + + @classmethod + def fix_project(cls, projpath): + files_to_fix = [] + for root, dirs, files in os.walk(projpath): + for f in files: + if f.endswith(".js"): + files_to_fix.append(os.path.join(root, f)) + if len(files_to_fix) == 0: + return + pb = ProgressBar(total=len(files_to_fix), prefix='Files processed', suffix='', decimals=1, length=50) + progress = 0 + pb.print_progress_bar(0) + for f in files_to_fix: + cls.fix_file(f) + progress += 1 + pb.print_progress_bar(progress) + + @classmethod + def fix_directory(cls, dirpath): + files_to_fix = [] + for f in os.listdir(dirpath): + if f.endswith(".js"): + files_to_fix.append(os.path.join(dirpath, f)) + if len(files_to_fix) == 0: + return + pb = ProgressBar(total=len(files_to_fix), prefix='Files processed', suffix='', decimals=1, length=50) + progress = 0 + pb.print_progress_bar(0) + for f in files_to_fix: + cls.fix_file(f) + progress += 1 + pb.print_progress_bar(progress) + + @classmethod + def fix_file(cls, filepath): + fhandler = logging.FileHandler(filename=filepath + '_fixing.log', mode='w') + formatter = logging.Formatter('%(levelname)s: %(message)s') + fhandler.setFormatter(formatter) + + log = logging.getLogger() # root logger + for hdlr in log.handlers[:]: # remove all old handlers + log.removeHandler(hdlr) + log.addHandler(fhandler) + + filename = ntpath.basename(filepath) + dir_path = os.path.dirname(os.path.realpath(filepath)) + clear_filename = filename[0:(len(filename)-3)] + if not cls.__is_correct_filename(clear_filename): + new_name = cls.__to_correct_filename(clear_filename) + slash = "\\" if platform.system() in ['Windows', 'windows', 'Win', 'win'] else "/" + filepath = dir_path + slash + new_name + ".js" + os.rename(dir_path + slash + filename, filepath) + logging.error(f'{filepath}: {filename} naming error -> fixed to {new_name+".js"}') + + f = open(filepath, 'r') + text = f.read() + f.close() + lexems = Lexer.lex(text) + Parser.parse(lexems) + + fixes = cls.analyze(lexems) + + for fix in fixes: + for lexema in lexems: + if lexema.get_str() == fix[0]: + lexema.set_str(fix[1]) + logging.error(f'{filepath}: {fix[0]} naming error -> fixed to {fix[1]}') + + f = open(filepath, 'w') + for lexema in lexems: + f.write(lexema.get_str()) + f.close() + + @classmethod + def analyze(cls, lexems): + fixes = [] + for lexema in lexems: + if lexema.get_type() == LexemType.FUNCTION_IDENTIFIER and not cls.__is_camel_case(lexema.get_str()): + fixes.append((lexema.get_str(), cls.__to_camel_case(lexema.get_str()))) + elif lexema.get_type() == LexemType.PRIVATE_FUNCTION_IDENTIFIER and not cls.__is_trailing_camel_case(lexema.get_str()): + fixes.append((lexema.get_str(), cls.__to_trailing_camel_case(lexema.get_str()))) + elif lexema.get_type() == LexemType.VARIABLE_IDENTIFIER and not cls.__is_camel_case(lexema.get_str()): + fixes.append((lexema.get_str(), cls.__to_camel_case(lexema.get_str()))) + elif lexema.get_type() == LexemType.CLASS_IDENTIFIER and not cls.__is_pascal_case(lexema.get_str()): + fixes.append((lexema.get_str(), cls.__to_pascal_case(lexema.get_str()))) + elif lexema.get_type() == LexemType.CONSTANT_IDENTIFIER and not lexema.get_str().isupper(): + fixes.append((lexema.get_str(), cls.__to_snake(lexema.get_str()))) + elif lexema.get_type() == LexemType.ENUM_IDENTIFIER and not cls.__is_pascal_case(lexema.get_str()): + fixes.append((lexema.get_str(), cls.__to_pascal_case(lexema.get_str()))) + return fixes + + @classmethod + def __to_pascal_case(cls, name): + return string.capwords(name.replace("_", " ")).replace(" ", "") + + @classmethod + def __to_camel_case(cls, name): + res = string.capwords(name.replace("_", " ")).replace(" ", "") + if not res: + return name + return res[0].lower() + res[1:] + + @classmethod + def __is_camel_case(cls, name): + if name[0].isupper(): + return False + return "_" not in name + + @classmethod + def __to_trailing_camel_case(cls, name): + res = string.capwords(name.replace("_", " ")).replace(" ", "") + res = res[0].lower() + res[1:] + if res[len(res) - 1] != '_': + res = res + "_" + return res + + @classmethod + def __is_trailing_camel_case(cls, name): + if name[0].isupper(): + return False + if name[len(name)-1] != '_': + return False + return "_" not in name[0:len(name)-2] + + @classmethod + def __is_pascal_case(cls, name): + if name[0].islower(): + return False + return "_" not in name + + @classmethod + def __to_snake(cls, name): + name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) + return re.sub('([a-z0-9])([A-Z])', r'\1_\2', name).upper() + + @classmethod + def __is_correct_filename(cls, name): + contains_dashes = "-" in name + contains_underscores = "_" in name + contains_uppercase = False + for ch in name: + if ch.isupper(): + contains_uppercase = True + break + if contains_uppercase: + return False + if contains_underscores and contains_dashes: + return False + return True + + @classmethod + def __to_correct_filename(cls, name): + upper_snake_case = cls.__to_snake(name) + lower_snake_case = upper_snake_case.lower() + lower_dash = lower_snake_case.replace("_", "-") + return lower_dash diff --git a/lab2/semantical_analysis/parser.py b/lab2/semantical_analysis/parser.py new file mode 100644 index 0000000..028b1ed --- /dev/null +++ b/lab2/semantical_analysis/parser.py @@ -0,0 +1,161 @@ +from lexical_analysis.lexema import LexemType + + +class Parser: + @classmethod + def parse(cls, lexems): + position = 0 + while position < len(lexems): + next_position = cls.__parse_function(lexems, position) + if next_position != position: + position = next_position + continue + next_position = cls.__parse_variable(lexems, position) + if next_position != position: + position = next_position + continue + next_position = cls.__parse_class(lexems, position) + if next_position != position: + position = next_position + continue + next_position = cls.__parse_enum(lexems, position) + if next_position != position: + position = next_position + continue + position += 1 + + @classmethod + def __parse_enum(cls, lexems, position): + if lexems[position].get_type() == LexemType.IDENTIFIER: + prev_position = cls.__get_prev_lexema_position(lexems, position) + + if prev_position != None and lexems[prev_position].get_str() == 'enum': + lexems[position].set_type(LexemType.ENUM_IDENTIFIER) + + next_position = cls.__get_next_lexema_position(lexems, position) + + if next_position and lexems[next_position].get_type() == LexemType.EQUAL: + next_position = cls.__get_next_lexema_position(lexems, next_position) + + if next_position < len(lexems) and lexems[next_position].get_type() == LexemType.LEFT_BRACE: + i = next_position + 1 + opened_brace = 1 + while i < len(lexems) and opened_brace != 0: + if lexems[i].get_type() == LexemType.LEFT_BRACE: + opened_brace += 1 + elif lexems[i].get_type() == LexemType.RIGHT_BRACE: + opened_brace -= 1 + i += 1 + if i == len(lexems): + return position + + i = next_position + 1 + opened_brace = 1 + while i < len(lexems) and opened_brace != 0: + if lexems[i].get_type() == LexemType.LEFT_BRACE: + opened_brace += 1 + elif lexems[i].get_type() == LexemType.RIGHT_BRACE: + opened_brace -= 1 + + if lexems[i].get_type() == LexemType.IDENTIFIER: + lexems[i].set_type(LexemType.CONSTANT_IDENTIFIER) + i += 1 + return position + + @classmethod + def __parse_function(cls, lexems, position): + if lexems[position].get_type() == LexemType.IDENTIFIER: + next_position = cls.__get_next_lexema_position(lexems, position) + + if next_position and lexems[next_position].get_type() == LexemType.LEFT_PAREN: + i = next_position + 1 + opened_paren = 1 + while i < len(lexems) and opened_paren != 0: + if lexems[i].get_type() == LexemType.LEFT_PAREN: + opened_paren += 1 + elif lexems[i].get_type() == LexemType.RIGHT_PAREN: + opened_paren -= 1 + i += 1 + if i == len(lexems): + return position + + next_after_right_paren = cls.__get_next_lexema_position(lexems, i) + if not next_after_right_paren: + return position + + if lexems[next_after_right_paren].get_type() != LexemType.LEFT_BRACE: + return position + + lexems[position].set_type(LexemType.FUNCTION_IDENTIFIER) + prev_position = cls.__get_prev_lexema_position(lexems, position) + + if prev_position != None and lexems[prev_position].get_str() == 'private' and \ + lexems[prev_position - 1].get_str() == '@': + lexems[position].set_type(LexemType.PRIVATE_FUNCTION_IDENTIFIER) + + i = next_position + 1 + opened_paren = 1 + while i < len(lexems) and opened_paren != 0: + if lexems[i].get_type() == LexemType.LEFT_PAREN: + opened_paren += 1 + elif lexems[i].get_type() == LexemType.RIGHT_PAREN: + opened_paren -= 1 + if lexems[i].get_type() == LexemType.IDENTIFIER: + lexems[i].set_type(LexemType.VARIABLE_IDENTIFIER) + i += 1 + return i + + return position + + @classmethod + def __parse_variable(cls, lexems, position): + if lexems[position].get_type() == LexemType.IDENTIFIER: + prev_position = cls.__get_prev_lexema_position(lexems, position) + + if prev_position != None: + prev_lexema_str = lexems[prev_position].get_str() + if prev_lexema_str == 'var' or \ + prev_lexema_str == 'let': + lexems[position].set_type(LexemType.VARIABLE_IDENTIFIER) + position += 1 + return position + elif prev_lexema_str == 'const': + lexems[position].set_type(LexemType.CONSTANT_IDENTIFIER) + position += 1 + return position + + return position + + @classmethod + def __parse_class(cls, lexems, position): + if lexems[position].get_type() == LexemType.IDENTIFIER: + prev_position = cls.__get_prev_lexema_position(lexems, position) + + if prev_position != None: + prev_lexema_str = lexems[prev_position].get_str() + if prev_lexema_str == 'class': + lexems[position].set_type(LexemType.CLASS_IDENTIFIER) + position += 1 + return position + + @classmethod + def __get_next_lexema_position(cls, lexems, current_position): + position = current_position + 1 + while position < len(lexems): + if lexems[position].get_type() != LexemType.COMMENT and \ + lexems[position].get_type() != LexemType.MULTI_COMMENT and \ + lexems[position].get_type() != LexemType.WHITESPACE: + return position + position += 1 + return None + + @classmethod + def __get_prev_lexema_position(cls, lexems, current_position): + position = current_position - 1 + while position >= 0: + if lexems[position].get_type() != LexemType.COMMENT and \ + lexems[position].get_type() != LexemType.MULTI_COMMENT and \ + lexems[position].get_type() != LexemType.WHITESPACE: + return position + position -= 1 + return None \ No newline at end of file