diff --git a/.flake8 b/.flake8
new file mode 100644
index 00000000..a2c2e033
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,4 @@
+[flake8]
+exclude = .git,.venv,__pycache__,docs/source/conf.py,old,build,dist
+max-line-length = 120
+extend-ignore = E722, E203
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 00000000..3cdd7ed3
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,39 @@
+# Welcome to 42 norminette contributing guide
+
+We appreciate your interest in contributing to our project.
+Please take a moment to read through this guide to understand how you can contribute effectively.
+
+## Issue Tracker
+
+Before starting your contribution, we recommend checking our issue tracker [here](https://github.com/42School/norminette/issues).
+It lists the current tasks, bug reports, and feature requests.
+If you find an open issue that you'd like to work on, please comment on it to let us know.
+
+If you encounter a new issue or have a feature request that is not already listed, please open a new issue following the given template.
+
+## Getting Started
+
+To contribute to this project, follow these steps:
+
+1. Fork the repository to your GitHub account.
+2. Clone the forked repository to your local machine.
+3. Create a new branch for your contribution. Choose a descriptive name related to the task you'll be working on.
+4. Make your changes, write your code, and commit them with clear and concise commit messages.
+5. Push your branch to your forked repository on GitHub.
+6. Open a pull request (PR) from your branch to the main repository's branch. If you work on an open issue please mention it.
+7. Provide a clear title and description for your PR, explaining the changes you made.
+8. Our team will review your code, provide feedback, and discuss any necessary changes.
+9. Make the requested changes, if applicable, and push the updates to your branch.
+10. Once your PR is approved, it will be merged into the main repository.
+
+## Code Guidelines
+
+- Use the flake8 linter to check your code for errors.
+- Try to keep your code easy to understand.
+- Write unit tests for new code or modify existing tests to maintain test coverage.
+- Run the existing tests and ensure they pass before submitting your contribution.
+- Code which not pass flake8 or the test will not be reviewed.
+
+Thank you for your interest in contributing to our project.
+Your contributions are valuable and greatly appreciated.
+Happy coding!
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 6679211b..4b8864d5 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -15,9 +15,7 @@ Please use markdown to send the code here.
**Additional infos**
- - OS:
- - python --version:
- - norminette -v:
+Copy and paste the output of `norminette --version` command here.
**Additional context**
Add any other context about the problem here.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 00000000..fd3d071d
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,16 @@
+## Issues Fixed
+
+- Closes #[issue number]
+- Related to #[issue number] (if applicable)
+
+## Verification Steps
+Please ensure the following steps have been completed:
+- [ ] Added new tests to cover the changes.
+- [ ] Fixed all broken tests.
+- [ ] Ran `poetry run flake8` to check for linting issues.
+- [ ] Verified that all unit tests are passing:
+ - [ ] Ran `poetry run pytest` to ensure unit tests pass.
+ - [ ] Ran `poetry run tox` to validate compatibility across Python versions.
+
+## Additional Notes
+
diff --git a/.github/workflows/check-i18n.yml b/.github/workflows/check-i18n.yml
new file mode 100644
index 00000000..4616c06a
--- /dev/null
+++ b/.github/workflows/check-i18n.yml
@@ -0,0 +1,45 @@
+name: Check I18N Changes
+
+on:
+ pull_request:
+ branches:
+ - master
+
+jobs:
+ check-i18n:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository (PR branch)
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+ fetch-depth: 0
+
+ - name: Install system dependencies
+ run: sudo apt-get update && sudo apt-get install -y gettext
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.11"
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install poetry
+ poetry install
+
+ - name: Run I18N module
+ run: |
+ poetry run python -m norminette.i18n
+
+ - name: Check for uncommitted .po changes
+ run: |
+ if git diff --ignore-matching-lines='^"POT-Creation-Date:' -- '*.po' | grep -P '^[+-](?![+-]{2} [ab]/)' > /dev/null; then
+ echo "Meaningful I18N changes detected. Please run 'python -m norminette/i18n.py' and commit the changes."
+ git diff -- '*.po'
+ exit 1
+ else
+ echo "No changes detected in I18N files."
+ fi
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index a6d01254..00c790bb 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -5,29 +5,41 @@ name: Python package
on:
push:
- branches: [ master ]
+ branches: [master]
pull_request:
- branches: [ master ]
+ branches: [master]
jobs:
build:
-
runs-on: ubuntu-latest
strategy:
+ fail-fast: false
matrix:
- python-version: [3.7, 3.8, 3.9]
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
+ timeout-minutes: 5
steps:
- - uses: actions/checkout@v2
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
- with:
- python-version: ${{ matrix.python-version }}
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- python setup.py develop --user
- - name: Tester
- run: |
- cd norminette && sh run_test.sh
+ - uses: actions/checkout@v2
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install poetry
+ poetry install
+
+ - name: Build package
+ run: |
+ poetry build
+
+ - name: Run linter
+ run: |
+ poetry run flake8
+
+ - name: Run tests
+ run: |
+ poetry run pytest
diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml
index 172678ff..1116d7cf 100644
--- a/.github/workflows/python-publish.yml
+++ b/.github/workflows/python-publish.yml
@@ -17,22 +17,21 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
- python-version: '3.9'
+ python-version: '3.10'
- name: Install dependencies
run: |
+ sudo apt-get update && sudo apt-get install -y gettext
python -m pip install --upgrade pip
- pip install setuptools wheel twine
+ pip install setuptools wheel twine poetry
+ - name: Install norminette package
+ run: |
+ poetry install
+ - name: Compile .mo files
+ run: |
+ poetry run python norminette/i18n.py
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
- python setup.py sdist
- twine upload dist/*
- - name: Build and publish to test_pypi
- env:
- TWINE_USERNAME: ${{ secrets.PYPI_TEST_USERNAME }}
- TWINE_PASSWORD: ${{ secrets.PYPI_TEST_PASSWORD }}
- run: |
- python setup.py sdist
- twine upload --repository testpypi dist/*
\ No newline at end of file
+ poetry publish --build --username $TWINE_USERNAME --password $TWINE_PASSWORD
diff --git a/.gitignore b/.gitignore
index 66929449..88543120 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,4 +12,15 @@ build/*
*.fdb_latexmk
*.fls
*.log
-.eggs
\ No newline at end of file
+.eggs
+bundle/
+pdf/*.out
+pdf/*.toc
+resources/
+dist/
+
+*_cache
+.venv
+.tox
+*.pot
+**/*.mo
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index e69de29b..00000000
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 62a440b3..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-os:
- - linux
-
-dist: bionic
-
-language: python
-python:
- - 3.7
-
-
-before_script:
- - python -V
-
-jobs:
- include:
- - stage: Linting
- script:
- - pip install -r requirements.txt
- - pycodestyle $(find norminette/ -d -name "*.py")
- - stage: Unit tests
- script:
- - cd norminette && python -m unittest discover tests/lexer/unit-tests "*.py"
- - stage: Lexer tests
- script:
- - python -m tests.lexer.files.file_token_test
- - python -m tests.lexer.errors.tester
- - stage: Rule tests
- script:
- - python -m tests.rules.rule_tester
diff --git a/Dockerfile b/Dockerfile
index cd525aaa..d1a5201e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,12 +1,14 @@
-FROM python:3.7
+FROM python:3.13-alpine
WORKDIR /usr/src/norminette
-COPY . .
+COPY pyproject.toml poetry.lock README.md ./
+COPY norminette/ ./norminette/
-RUN pip3 install -r requirements.txt \
- && python3 setup.py install
+RUN pip3 install --no-cache-dir 'poetry>=2,<3' --root-user-action=ignore \
+ && poetry build \
+ && pip3 install dist/*.whl --root-user-action=ignore
WORKDIR /code
-ENTRYPOINT ["norminette"]
\ No newline at end of file
+ENTRYPOINT ["norminette"]
diff --git a/README.md b/README.md
index 0e53af6e..be409e35 100644
--- a/README.md
+++ b/README.md
@@ -1,53 +1,124 @@
# norminette for 42 schools
-## Install:
+## Install
-requires python3.7+ (3.7, 3.8, 3.9)
+Norminette requires Python >=3.10.
+
+### Directly inside your global commands
Install using pip.
```shell
-pip install norminette
+python3 -m pip install -U norminette
+```
+
+Install using pipx.
+```shell
+sudo apt update
+sudo apt install python3-setuptools
+sudo apt install pipx
+pipx install norminette
+pipx ensurepath
```
+
+Install using a virtual environment.
+```shell
+python3 -m venv $HOME/.venv
+source $HOME/.venv/bin/activate
+python3 -m pip install --upgrade pip setuptools
+python3 -m pip install norminette
+echo "export PATH=\$PATH:$HOME/.venv/bin" >> $HOME/.${SHELL##/bin/}rc
+deactivate
+```
+
To upgrade an existing install, use
```shell
-pip install --upgrade norminette
+python3 -m pip install --upgrade norminette
```
## Usage
+- Runs on the current folder and any subfolder:
+
```
norminette
```
-Runs on the current folder and any subfolder
+
+- Runs on the given filename(s):
```
norminette filename.[c/h]
```
-Runs on the given filename(s)
+
+- Prevents stopping on various blocking errors:
```
norminette -d
```
-Prevents stopping on various blocking errors
+
+- Outputs all the debug logging:
```
norminette -dd
```
-Outputs all the debug logging
## Docker usage
```
docker build -t norminette .
cd ~/42/ft_printf
-docker run -v $PWD:/code norminette /code
+docker run --rm -v $PWD:/code norminette
```
If you encounter an error or an incorrect output, you can:
- - Open an issue on github
+ - Open an issue on github
- Post a message on the dedicated slack channel (#norminette-v3-beta)
-
+
Please try to include as much information as possible (the file on which it crashed, etc)
Feel free to do pull requests if you want to help as well. Make sure that run_test.sh properly runs after your modifications.
+
+## Run for development
+
+This new version uses poetry as a dependency manager.
+
+If you want to contribute:
+
+```shell
+poetry install
+
+# Run dev norminette
+poetry run norminette
+
+# Or... with virtual env
+source .venv/bin/activate
+norminette
+
+# Run tests
+poetry run pytest
+```
+
+## Github action
+
+Workflow example to check code with github action :
+
+```yaml
+---
+name: Norminette
+
+on:
+ push:
+
+jobs:
+ check-norminette:
+ name: Norminette
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Norminette
+ uses: 42School/norminette@
+ with:
+ args: '-RCheckForbiddenSourceHeader'
+```
diff --git a/action.yml b/action.yml
new file mode 100644
index 00000000..aeabefa2
--- /dev/null
+++ b/action.yml
@@ -0,0 +1,19 @@
+---
+name: 'norminette-action'
+author: '42 School'
+description: 'It is the official github action for 42 school norminette'
+branding:
+ icon: 'check'
+ color: 'gray-dark'
+inputs:
+ args:
+ description: 'Args passed to norminette'
+ required: false
+ default: '.'
+runs:
+ using: 'docker'
+ image: 'Dockerfile'
+ entrypoint: 'sh'
+ args:
+ - '-c'
+ - "norminette ${{ inputs.args }}"
diff --git a/deploy.sh b/deploy.sh
deleted file mode 100644
index 2fa7476a..00000000
--- a/deploy.sh
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/bash
-
-SUFFIX=""
-
-if [ "$#" -gt "0" ]
-then
- SUFFIX="-dev"
-fi
-
-BUILD_DIR="build"
-PKG_ROOT="$BUILD_DIR/pkgroot"
-PACKAGE_NAME="norminette$SUFFIX"
-DESCRIPTION="Norminette"
-VERSION=`cat norminette/version.py | cut -d'"' -f2`
-OUTFILE="norminette_$VERSION$SUFFIX.pkg"
-SUBDIRECTORY="apps/norminette"
-
-rm $OUTFILE
-
-rm -rf $BUILD_DIR
-mkdir $BUILD_DIR
-python3 -m venv $BUILD_DIR/venv
-source $BUILD_DIR/venv/bin/activate
-python3 setup.py install
-sed -i '' 's#/Users/.*/venv#/usr/share/norminette/venv#' $BUILD_DIR/venv/bin/*
-deactivate
-
-mkdir -p $PKG_ROOT/usr/share/norminette
-mv $BUILD_DIR/venv $PKG_ROOT/usr/share/norminette/venv
-
-pkgbuild --identifier $PACKAGE_NAME --version $VERSION --root $PKG_ROOT --install-location / $OUTFILE
-rm -rf $BUILD_DIR
-rm -rf dist
-
-
diff --git a/norminette/__init__.py b/norminette/__init__.py
index e69de29b..503d22be 100644
--- a/norminette/__init__.py
+++ b/norminette/__init__.py
@@ -0,0 +1,4 @@
+__version__ = "3.3.59"
+__name__ = "norminette"
+__author__ = "42"
+__author__email__ = "pedago@42.fr"
diff --git a/norminette/__main__.py b/norminette/__main__.py
index f45e80a9..3272f36f 100644
--- a/norminette/__main__.py
+++ b/norminette/__main__.py
@@ -1,106 +1,150 @@
-import sys
-import glob
-import os
-file_dir = os.path.dirname(__file__)
-sys.path.append(file_dir)
import argparse
-import pkg_resources
-from lexer import Lexer, TokenError
-from exceptions import CParsingError
-from registry import Registry
-from context import Context
-from tools.colors import colors
-import _thread
-from threading import Thread, Event
-from multiprocessing import Process, Queue
-import time
-#import sentry_sdk
-#from sentry_sdk import configure_scope
-from version import __version__
+import glob
+import pathlib
+import platform
+import subprocess
+import sys
+from importlib.metadata import version
+
+from norminette.context import Context
+from norminette.errors import formatters
+from norminette.exceptions import CParsingError
+from norminette.file import File
+from norminette.lexer import Lexer
+from norminette.registry import Registry
+from norminette.tools.colors import colors
-has_err = False
+version_text = f"norminette {version('norminette')}"
+version_text += f", Python {platform.python_version()}"
+version_text += f", {platform.platform()}"
-def timeout(e, timeval=5):
- time.sleep(timeval)
- if e.is_set():
- return
- #sentry_sdk.capture_exception(Exception(TimeoutError))
- _thread.interrupt_main()
def main():
parser = argparse.ArgumentParser()
- parser.add_argument("file", help="File(s) or folder(s) you wanna run the parser on. If no file provided, runs on current folder.", default=[], action='append', nargs='*')
- parser.add_argument("-d", "--debug", action="count", help="Debug output (multiple values available)", default=0)
- parser.add_argument('-v', '--version', action='version', version='norminette ' + str(__version__))
- #parser.add_argument('-s', '--sentry', action='store_true', default=False)
- parser.add_argument('--cfile', action='store', help="Store C file content directly instead of filename")
- parser.add_argument('--hfile', action='store', help="Store header file content directly instead of filename")
+ parser.add_argument(
+ "file",
+ help="File(s) or folder(s) you wanna run the parser on. If no file provided, runs on current folder.",
+ nargs="*",
+ )
+ parser.add_argument(
+ "-d",
+ "--debug",
+ action="count",
+ help="Debug output (-dd outputs the whole tokenization and such, used for developping)",
+ default=0,
+ )
+ parser.add_argument(
+ "-o",
+ "--only-filename",
+ action="store_true",
+ help="By default norminette displays the full path to the file, this allows to show only filename",
+ default=False,
+ )
+ parser.add_argument(
+ "-v",
+ "--version",
+ action="version",
+ version=version_text,
+ )
+ parser.add_argument(
+ "--cfile",
+ action="store",
+ help="Store C file content directly instead of filename",
+ )
+ parser.add_argument(
+ "--hfile",
+ action="store",
+ help="Store header file content directly instead of filename",
+ )
+ parser.add_argument(
+ "--filename",
+ action="store",
+ help="Stores filename if --cfile or --hfile is passed",
+ )
+ parser.add_argument(
+ "--use-gitignore",
+ action="store_true",
+ help="Parse only source files not match to .gitignore",
+ )
+ parser.add_argument(
+ "-f",
+ "--format",
+ choices=list(formatter.name for formatter in formatters),
+ help="formatting style for errors",
+ default="humanized",
+ )
+ parser.add_argument(
+ "--no-colors", action="store_true", help="Disable colors in output"
+ )
+ parser.add_argument("-R", nargs=1, help="compatibility for norminette 2")
args = parser.parse_args()
registry = Registry()
- targets = []
- has_err = None
- content = None
+ format = next(filter(lambda it: it.name == args.format, formatters))
+ files = []
debug = args.debug
- #if args.sentry == True:
- #sentry_sdk.init("https://e67d9ba802fe430bab932d7b11c9b028@sentry.42.fr/72")
- if args.cfile != None or args.hfile != None:
- targets = ['file.c'] if args.cfile else ['file.h']
- content = args.cfile if args.cfile else args.hfile
+ if args.cfile or args.hfile:
+ file_name = args.filename or ("file.c" if args.cfile else "file.h")
+ file_data = args.cfile if args.cfile else args.hfile
+ file = File(file_name, file_data)
+ files.append(file)
else:
- args.file = args.file[0]
- if args.file == [[]] or args.file == []:
- targets = glob.glob("**/*.[ch]", recursive=True)
- target = targets.sort()
- else:
- for arg in args.file:
- if os.path.exists(arg) is False:
- print(f"'{arg}' no such file or directory")
- elif os.path.isdir(arg):
- if arg[-1] != '/':
- arg = arg + '/'
- targets.extend(glob.glob(arg + '**/*.[ch]', recursive=True))
- elif os.path.isfile(arg):
- targets.append(arg)
- event = []
- for target in targets:
- if target[-2:] not in [".c", ".h"]:
- print(f"{target} is not valid C or C header file")
- else:
- #with configure_scope() as scope:
- # scope.set_extra("File", target)
- try:
- event.append(Event())
- #if args.sentry == True:
- # proc = Thread(target=timeout, args=(event[-1], 5, ))
- # proc.daemon = True
- # proc.start()
- if content == None:
- with open(target) as f:
- #print ("Running on", target)
- source = f.read()
- else:
- source = content
- lexer = Lexer(source)
- tokens = lexer.get_tokens()
- context = Context(target, tokens, debug)
- registry.run(context, source)
- event[-1].set()
- if context.errors:
- has_err = True
- # except (TokenError, CParsingError) as e:
- except TokenError as e:
- has_err = True
- print(target + f": KO!\n\t{colors(e.msg, 'red')}")
- event[-1].set()
- except CParsingError as e:
- has_err = True
- print(target + f": KO!\n\t{colors(e.msg, 'red')}")
- event[-1].set()
- except KeyboardInterrupt as e:
- event[-1].set()
+ stack = []
+ stack += args.file if args.file else glob.glob("**/*.[ch]", recursive=True)
+ for item in stack:
+ path = pathlib.Path(item)
+ if not path.exists():
+ print(f"Error: '{path!s}' no such file or directory")
sys.exit(1)
- sys.exit(1 if has_err else 0)
+ if path.is_file():
+ if path.suffix not in (".c", ".h"):
+ print(f"Error: {path.name!r} is not valid C or C header file")
+ else:
+ file = File(item)
+ files.append(file)
+ if path.is_dir():
+ stack += glob.glob(str(path) + "/**/*.[ch]", recursive=True)
+ del stack
+
+ if args.use_gitignore:
+ tmp_targets = []
+ for target in files:
+ command = ["git", "check-ignore", "-q", target.path]
+ exit_code = subprocess.run(
+ command, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ ).returncode
+ """
+ see: $ man git-check-ignore
+ EXIT STATUS
+ 0: One or more of the provided paths is ignored.
+ 1: None of the provided paths are ignored.
+ 128: A fatal error was encountered.
+ """
+ if exit_code == 0:
+ pass
+ elif exit_code == 1:
+ tmp_targets.append(target)
+ elif exit_code == 128:
+ print(
+ f"Error: something wrong with --use-gitignore option {target.path!r}"
+ )
+ sys.exit(0)
+ files = tmp_targets
+ for file in files:
+ try:
+ lexer = Lexer(file)
+ tokens = list(lexer)
+ context = Context(file, tokens, debug, args.R)
+ registry.run(context)
+ except CParsingError as e:
+ print(file.path + f": Error!\n\t{colors(e.msg, 'red')}")
+ sys.exit(1)
+ except KeyboardInterrupt:
+ sys.exit(1)
+ errors = format(files, use_colors=not args.no_colors)
+ print(errors, end="")
+ sys.exit(1 if any(len(it.errors) for it in files) else 0)
+
if __name__ == "__main__":
- main()
\ No newline at end of file
+ main()
diff --git a/norminette/colors.py b/norminette/colors.py
new file mode 100644
index 00000000..2bb8dc28
--- /dev/null
+++ b/norminette/colors.py
@@ -0,0 +1,62 @@
+from typing import Optional
+
+red = {
+ "TOO_MANY_ARGS",
+ "TOO_MANY_VARS_FUNC",
+}
+yellow = {
+ "MIXED_SPACE_TAB",
+ "BRACE_NEWLINE"
+}
+green = {
+ "TOO_MANY_FUNCS",
+}
+blue = {
+ "SPC_INSTEAD_TAB",
+ "TAB_INSTEAD_SPC",
+ "CONSECUTIVE_SPC",
+ "CONSECUTIVE_WS",
+ "SPC_BFR_OPERATOR",
+ "SPC_AFTER_OPERATOR",
+ "NO_SPC_BFR_OPR",
+ "NO_SPC_AFR_OPR",
+ "SPC_AFTER_PAR",
+ "SPC_BFR_PAR",
+ "NO_SPC_AFR_PAR",
+ "NO_SPC_BFR_PAR",
+ "SPC_AFTER_POINTER",
+ "SPC_LINE_START",
+ "SPC_BFR_POINTER",
+ "SPACE_BEFORE_FUNC",
+ "TOO_MANY_TABS_FUNC",
+ "TOO_MANY_TABS_TD",
+ "MISSING_TAB_FUNC",
+ "MISSING_TAB_VAR",
+ "TOO_MANY_TAB_VAR",
+ "LINE_TOO_LONG",
+ "EXP_PARENTHESIS",
+}
+pink = {
+ "WRONG_SCOPE_COMMENT",
+ "COMMENT_ON_INSTR",
+}
+grey = {
+ "INVALID_HEADER",
+ "WRONG_SCOPE_COMMENT",
+}
+
+_color_table = {
+ "91": red,
+ "92": green,
+ "93": yellow,
+ "94": blue,
+ "95": pink,
+ "97": grey,
+}
+
+
+def error_color(name: str) -> Optional[str]:
+ for color, table in _color_table.items():
+ if name in table:
+ return color
+ return None
diff --git a/norminette/context.py b/norminette/context.py
index c92404dc..0cc52c89 100644
--- a/norminette/context.py
+++ b/norminette/context.py
@@ -1,8 +1,10 @@
-from norm_error import NormError
-from lexer.dictionary import operators, brackets
-from tools.colors import colors
-from scope import *
-from exceptions import CParsingError
+from dataclasses import dataclass, field
+
+from norminette.errors import Error, Highlight
+from norminette.lexer import Token
+from norminette.exceptions import CParsingError
+from norminette.scope import GlobalScope, ControlStructure
+from norminette.tools.colors import colors
types = [
"CHAR",
@@ -15,14 +17,10 @@
"LONG",
"SHORT",
"SIGNED",
- "UNSIGNED"
+ "UNSIGNED",
]
-utypes = [
- "STRUCT",
- "ENUM",
- "UNION"
-]
+utypes = ["STRUCT", "ENUM", "UNION"]
glued_operators = [
"MINUS",
@@ -105,54 +103,99 @@
"GOTO",
"LABEL",
"SWITCH",
- "CASE"
+ "CASE",
]
misc_specifiers = [
"CONST",
+ "RESTRICT",
"REGISTER",
"STATIC",
"VOLATILE",
"EXTERN",
"INLINE",
- "RESTRICT"
+ "RESTRICT",
"SIGNED",
"UNSIGNED",
]
-size_specifiers = [
- "LONG",
- "SHORT"
+assigns = [
+ "RIGHT_ASSIGN",
+ "LEFT_ASSIGN",
+ "ADD_ASSIGN",
+ "SUB_ASSIGN",
+ "MUL_ASSIGN",
+ "DIV_ASSIGN",
+ "MOD_ASSIGN",
+ "AND_ASSIGN",
+ "XOR_ASSIGN",
+ "OR_ASSIGN",
+ "ASSIGN",
]
-sign_specifiers = [
- "SIGNED",
- "UNSIGNED"
-]
+size_specifiers = ["LONG", "SHORT"]
-whitespaces = [
- "SPACE",
- "TAB",
- "ESCAPED_NEWLINE",
- "NEWLINE"
-]
+sign_specifiers = ["SIGNED", "UNSIGNED"]
-arg_separator = [
- "COMMA",
- "CLOSING_PARENTHESIS"
-]
+whitespaces = ["SPACE", "TAB", "ESCAPED_NEWLINE", "NEWLINE"]
+
+arg_separator = ["COMMA", "CLOSING_PARENTHESIS"]
+
+
+@dataclass
+class Macro:
+ name: str
+ is_func: bool = field(default=False)
+
+ @classmethod
+ def from_token(self, token, **kwargs):
+ name = token.value or token.type
+ return Macro(name, **kwargs)
+
+
+class PreProcessors:
+ def __init__(self) -> None:
+ self.indent = 0
+
+ self.macros = []
+ self.includes = []
+
+ self.total_ifs = 0
+ self.total_elifs = 0
+ self.total_elses = 0
+ self.total_ifdefs = 0
+ self.total_ifndefs = 0
+
+ self.skip_define = False
+
+ @property
+ def indent(self):
+ return self._indent
+
+ @indent.setter
+ def indent(self, value):
+ self._indent = max(0, value)
+
+ def has_macro_defined(self, name):
+ for macro in self.macros:
+ if macro.name == name:
+ return True
+ return False
class Context:
- def __init__(self, filename, tokens, debug=0):
+ def __init__(self, file, tokens, debug=0, added_value=[]):
+ # Header relative informations
+ self.header_started = False
+ self.header_parsed = False
+ self.header = ""
# File relative informations
- self.filename = filename.split('/')[-1]
- self.filetype = filename.split('.')[-1] # ?
+ self.file = file
self.tokens = tokens
self.debug = int(debug)
# Rule relative informations
self.history = []
- self.errors = []
+ self.errors = file.errors
self.tkn_scope = len(tokens)
# Scope informations
@@ -163,7 +206,9 @@ def __init__(self, filename, tokens, debug=0):
self.arg_pos = [0, 0]
# Preprocessor handling
- self.preproc_scope_indent = 0
+ self.protected = False
+ self.preproc = PreProcessors()
+ self.preproc.skip_define = "CheckDefine" in (added_value or [])
def peek_token(self, pos):
return self.tokens[pos] if pos < len(self.tokens) else None
@@ -172,22 +217,40 @@ def pop_tokens(self, stop):
self.tokens = self.tokens[stop:]
def check_token(self, pos, value):
- """Compares the token at 'pos' against a value or list of values
- """
+ """Compares the token at 'pos' against a value or list of values"""
tkn = self.peek_token(pos)
if tkn is None:
return None
- if isinstance(value, list):
- if tkn.type in value:
- return True
- return False
+ if isinstance(value, (tuple, list)):
+ return tkn.type in value
return tkn.type == value
- def new_error(self, errno, tkn):
- self.errors.append(NormError(errno, tkn.pos[0], tkn.pos[1]))
+ def find_in_scope(self, value, nested=True):
+ nests = 0
+ for i in range(0, self.tkn_scope):
+ tkn = self.peek_token(i)
+ if self.check_token(i, ["LBRACKET", "LPARENTHESIS", "LBRACE"]) is True:
+ nests += 1
+ if self.check_token(i, ["RBRACKET", "RPARENTHESIS", "RBRACE"]) is True:
+ nests -= 1
+ if tkn.type == value and (
+ nested is True or (nests == 0 and nested is False)
+ ):
+ return i
+ return -1
+
+ def new_error(self, errno, tkn: Token):
+ # XXX Deprecated, use `.errors` and `norminette.errors` module.
+ error = Error.from_name(errno, highlights=[Highlight.from_token(tkn)])
+ self.errors.add(error)
+
+ def new_warning(self, errno, tkn: Token):
+ # XXX Deprecated, use `.errors` and `norminette.errors` module.
+ error = Error.from_name(errno, level="Notice", highlights=[Highlight.from_token(tkn)])
+ self.errors.append(error)
def get_parent_rule(self):
if len(self.history) == 0:
@@ -196,15 +259,23 @@ def get_parent_rule(self):
def update(self):
"""Updates informations about the context and the scope if needed
- after a primary rule has succeeded.
- Do nothing on empty lines since they can be anywhere
+ after a primary rule has succeeded.
+ Do nothing on empty lines since they can be anywhere
"""
- if len(self.history) > 0 and (self.history[-1] == "IsEmptyLine" or self.history[-1] == "IsComment" or self.history[-1] == "IsPreprocessorStatement"):
+ if len(self.history) > 0 and (
+ self.history[-1] == "IsEmptyLine"
+ or self.history[-1] == "IsComment"
+ or self.history[-1] == "IsPreprocessorStatement"
+ ):
return
if self.sub is not None:
self.scope = self.sub
self.sub = None
- if type(self.scope) is ControlStructure and self.scope.multiline is False and self.scope.instructions > 0:
+ if (
+ type(self.scope) is ControlStructure
+ and self.scope.multiline is False
+ and self.scope.instructions > 0
+ ):
self.scope = self.scope.outer()
self.sub = None
self.update()
@@ -212,13 +283,15 @@ def update(self):
def dprint(self, rule, pos):
"""Debug printing, shows the primary rules that succeed in matching
- tokens and print the matching tokens
+ tokens and print the matching tokens
"""
if self.debug < 2:
return
- print(f"{colors(self.filename, 'cyan')} - {colors(rule, 'green')} \
+ print(
+ f"{colors(self.file.basename, 'cyan')} - {colors(rule, 'green')} \
In \"{self.scope.name}\" from \
-\"{self.scope.parent.name if self.scope.parent is not None else None}\" line {self.tokens[0].pos[0]}\":")
+\"{self.scope.parent.name if self.scope.parent is not None else None}\" line {self.tokens[0].pos[0]}\":"
+ )
i = 0
for t in self.tokens[:pos]:
if i == 0:
@@ -231,10 +304,12 @@ def dprint(self, rule, pos):
i += 1
if pos - 1 < len(self.tokens) and self.tokens[pos - 1].type != "NEWLINE":
print("")
+ elif len(self.tokens) == 1 and self.tokens[-1].type != "NEWLINE":
+ print("")
def eol(self, pos):
"""Skips white space characters (tab, space) until end of line
- (included) or any other token (excluded)
+ (included) or any other token (excluded)
"""
while self.check_token(pos, ["TAB", "SPACE", "NEWLINE"]) is True:
if self.check_token(pos, "NEWLINE"):
@@ -243,26 +318,27 @@ def eol(self, pos):
pos += 1
return pos
- def skip_ws(self, pos, nl=False):
+ def skip_ws(self, pos, nl=False, comment=False):
ws = whitespaces[:]
- if nl == False:
+ if nl is False:
ws.remove("NEWLINE")
+ if comment:
+ ws += ("COMMENT", "MULT_COMMENT")
while self.check_token(pos, ws):
pos += 1
return pos
-
def skip_nest_reverse(self, pos):
"""Skips anything between two brackets, parentheses or braces starting
- at 'pos', if the brackets, parentheses or braces are not closed or
- are closed in the wrong order an error shall be raised
+ at 'pos', if the brackets, parentheses or braces are not closed or
+ are closed in the wrong order an error shall be raised
"""
rbrackets = ["LBRACKET", "LBRACE", "LPARENTHESIS"]
lbrackets = ["RBRACKET", "RBRACE", "RPARENTHESIS"]
try:
c = self.peek_token(pos).type
except:
- raise CParsingError(f"Unexpected EOF line {pos}")
+ raise CParsingError(f"Error: Unexpected EOF line {pos}")
if c not in lbrackets:
return pos
c = rbrackets[lbrackets.index(c)]
@@ -276,22 +352,24 @@ def skip_nest_reverse(self, pos):
if c == self.peek_token(i).type:
return i
i -= 1
- raise CParsingError("Nested parentheses, braces or brackets\
- are not correctly closed")
+ raise CParsingError(
+ "Error: Nested parentheses, braces or brackets\
+ are not correctly closed"
+ )
return -1
def skip_nest(self, pos):
"""Skips anything between two brackets, parentheses or braces starting
- at 'pos', if the brackets, parentheses or braces are not closed or
- are closed in the wrong order an error shall be raised
+ at 'pos', if the brackets, parentheses or braces are not closed or
+ are closed in the wrong order an error shall be raised
"""
lbrackets = ["LBRACKET", "LBRACE", "LPARENTHESIS"]
rbrackets = ["RBRACKET", "RBRACE", "RPARENTHESIS"]
- try:
- c = self.peek_token(pos).type
- except:
- raise CParsingError(f"Unexpected EOF line {pos}")
+ # try:
+ c = self.peek_token(pos).type
+ # except:
+ # raise CParsingError(f"Error: Code ended unexpectedly.")
if c not in lbrackets:
return pos
c = rbrackets[lbrackets.index(c)]
@@ -305,16 +383,35 @@ def skip_nest(self, pos):
if c == self.peek_token(i).type:
return i
i += 1
- raise CParsingError("Nested parentheses, braces or brackets\
- are not correctly closed")
+ raise CParsingError(
+ "Error: Nested parentheses, braces or brackets\
+ are not correctly closed"
+ )
return -1
def skip_misc_specifier(self, pos, nl=False):
i = self.skip_ws(pos, nl=nl)
+ if self.check_token(i, "IDENTIFIER"):
+ tmp = self.skip_misc_specifier(i + 1)
+ if tmp != i + 1:
+ tmp = i
+ if self.check_token(i, "MULT"):
+ tmp = i + 1
+ while self.check_token(tmp, "MULT"):
+ tmp += 1
+ tmp = self.skip_ws(tmp, nl=nl)
+ if self.check_token(tmp, misc_specifiers):
+ i = tmp
+ i = self.skip_ws(i, nl=nl)
while self.check_token(i, misc_specifiers):
i += 1
i = self.skip_ws(i, nl=nl)
+ if self.check_token(i, "MULT"):
+ tmp = i + 1
+ tmp = self.skip_ws(i, nl=nl)
+ if self.check_token(tmp, misc_specifiers):
+ i = tmp
return i
def skip_typedef(self, pos):
@@ -326,12 +423,12 @@ def skip_typedef(self, pos):
def check_type_specifier(self, pos, user_def_type=False, nl=False):
"""Returns (True, pos + n) if the tokens from 'pos' to 'n' could match
- a valid type specifier. Valid type specifiers consist of:
- -an optionnal 'misc' specifier (const, register, volatile ...)
- -an optionnal size specifier (long or short)
- -a type specifier (int, char, double, etc...)
- OR an IDENTIFIER
- OR a user type specifier (struct, union, enum) + IDENTIFIER
+ a valid type specifier. Valid type specifiers consist of:
+ -an optionnal 'misc' specifier (const, register, volatile ...)
+ -an optionnal size specifier (long or short)
+ -a type specifier (int, char, double, etc...)
+ OR an IDENTIFIER
+ OR a user type specifier (struct, union, enum) + IDENTIFIER
"""
i = self.skip_misc_specifier(pos, nl=nl)
i = self.skip_ws(i, nl=nl)
@@ -342,7 +439,7 @@ def check_type_specifier(self, pos, user_def_type=False, nl=False):
if self.check_token(i, "IDENTIFIER") is True:
i += 1
return True, i
- #Raise CParsingError?
+ # Raise CParsingError?
if self.check_token(i, types + ["IDENTIFIER", "TYPEDEF"]) is False:
return False, 0
if self.check_token(i, "IDENTIFIER") is True:
@@ -362,16 +459,22 @@ def check_type_specifier(self, pos, user_def_type=False, nl=False):
return False, 0
if self.check_token(i, "IDENTIFIER") is True:
i += 1
+ # i = self.skip_ws(i)
return True, i + 1
- while self.check_token(i, types + whitespaces + ["MULT", "BWISE_AND"]) is True:
+ while (
+ self.check_token(i, types + whitespaces + ["MULT", "BWISE_AND"]) is True
+ ):
i += 1
- i = self.skip_misc_specifier(i, nl=nl)
- return True, i
+ tmp = self.skip_misc_specifier(i, nl=nl)
+ if tmp == i:
+ return True, i - 1
+ else:
+ return True, tmp
def check_identifier(self, pos, nl=False):
"""
- Determines the function return value or the variable type and returns
- an iterator to the next token
+ Determines the function return value or the variable type and returns
+ an iterator to the next token
"""
i = pos
p = 0
@@ -379,10 +482,9 @@ def check_identifier(self, pos, nl=False):
while self.check_token(i, whitespaces + ["MULT", "LPARENTHESIS"]) is True:
if self.check_token(i, "LPARENTHESIS"):
p += 1
- if self.check_token(i, "MULT") and self.check_token(i+1, "CONST"):
+ if self.check_token(i, "MULT") and self.check_token(i + 1, "CONST"):
i += 1
i += 1
-
i = self.skip_misc_specifier(i, nl=nl)
if self.check_token(i, "IDENTIFIER"):
while p and self.check_token(i, whitespaces + ["RPARENTHESIS"]) is True:
@@ -394,21 +496,30 @@ def check_identifier(self, pos, nl=False):
def is_glued_operator(self, pos):
"""
- Returns True if operator (among +-) at given pos is glued to identifier, number
- or constant
+ Returns True if operator (among +-) at given pos is glued to identifier, number
+ or constant
"""
glued = [
- 'LPARENTHESIS',
- 'LBRACKET',
- 'LBRACE',
+ "LPARENTHESIS",
+ "LBRACKET",
+ "LBRACE",
]
glued = glued + glued_operators
start = pos
- if self.check_token(pos, ['PLUS', 'MINUS', 'BWISE_OR', 'BWISE_AND', 'BWISE_NOT', 'BWISE_XOR']) is False:
+ if (
+ self.check_token(
+ pos,
+ ["PLUS", "MINUS", "BWISE_OR", "BWISE_AND", "BWISE_NOT", "BWISE_XOR"],
+ )
+ is False
+ ):
return False
pos += 1
pos = self.skip_ws(pos, nl=False)
- if self.check_token(pos, ['IDENTIFIER', 'CONSTANT']) is False:
+ if (
+ self.check_token(pos, ["IDENTIFIER", "CONSTANT", "MULT", "BWISE_AND"])
+ is False
+ ):
return False
pos = start - 1
while (self.check_token(pos, ["SPACE", "TAB"])) is True:
@@ -419,20 +530,23 @@ def is_glued_operator(self, pos):
def is_operator(self, pos):
"""
- Returns True if the given operator (among '*&') is an actual operator,
- and returns False if said operator is a pointer/adress indicator
+ Returns True if the given operator (among '*&') is an actual operator,
+ and returns False if said operator is a pointer/adress indicator
"""
- i = 0
start = pos + 1
pos -= 1
- if self.history[-1] == "IsFuncPrototype" or self.history[-1] == "IsFuncDeclaration":
+ if (
+ self.history[-1] == "IsFuncPrototype"
+ or self.history[-1] == "IsFuncDeclaration"
+ ):
return False
if self.check_token(start, ["RPARENTHESIS", "MULT"]) is True:
return False
start = self.skip_ws(start, nl=False)
if self.check_token(start, ["SIZEOF"]) is True:
return True
- if self.history[-1] == 'IsVarDeclaration':
+ if self.history[-1] == "IsVarDeclaration":
+ bracketed = False
tmp = pos
right_side = False
while tmp > 0:
@@ -440,33 +554,68 @@ def is_operator(self, pos):
tmp = self.skip_nest_reverse(tmp) - 1
if self.check_token(tmp, ["ASSIGN"]) is True:
right_side = True
+ if self.check_token(tmp, "LBRACKET") is True:
+ bracketed = True
tmp -= 1
- if right_side == False:
+ if right_side is False and bracketed is False:
return False
skip = 0
+ value_before = False
while pos > 0:
if self.check_token(pos, ["RBRACKET", "RPARENTHESIS"]) is True:
+ value_before = True
pos = self.skip_nest_reverse(pos) - 1
- if self.parenthesis_contain(pos + 1)[0] == 'cast':
+ if (
+ self.check_token(pos + 1, "LPARENTHESIS") is True
+ and self.parenthesis_contain(pos + 1)[0] == "variable"
+ ):
+ return True
+ if (
+ self.check_token(pos + 1, "LPARENTHESIS") is True
+ and self.parenthesis_contain(pos + 1)[0] == "cast"
+ ):
return False
skip = 1
- if self.check_token(pos, ["IDENTIFIER", "CONSTANT", "SIZEOF"]) is True:
- if self.check_token(pos, "IDENTIFIER") is True and self.check_token(pos + 1, "TAB") is True:
+ if (
+ self.check_token(
+ pos, ["IDENTIFIER", "CONSTANT", "SIZEOF", "CHAR_CONST"]
+ )
+ is True
+ ):
+ if (
+ self.check_token(pos, "IDENTIFIER") is True
+ and self.check_token(pos + 1, "TAB") is True
+ ):
return False
return True
- if self.check_token(pos, ["COMMA", "LPARENTHESIS"] + operators) is True and skip == 1:
+ if (
+ self.check_token(pos, ["COMMA", "LPARENTHESIS", "LBRACKET"] + operators)
+ is True
+ and skip == 1
+ and self.parenthesis_contain(pos + 1)[0] != "cast"
+ ):
return True
- if self.check_token(pos, ["LBRACKET", "LPARENTHESIS", "MULT", "BWISE_AND", "COMMA"] + operators + types):
+ if self.check_token(
+ pos,
+ ["LBRACKET", "LPARENTHESIS", "MULT", "BWISE_AND", "COMMA"]
+ + operators
+ + types,
+ ):
return False
pos -= 1
- return False
+ if value_before is True:
+ return True
+ else:
+ return False
def parenthesis_contain(self, i, ret_store=None):
"""
- Explore parenthesis to return its content
- Function, pointer, cast, or other
- Uses basic string as return value and skips to the end of the parenthesis nest
+ Explore parenthesis to return its content
+ Function, pointer, cast, or other
+ Uses basic string as return value and skips to the end of the parenthesis nest
"""
+ if self.check_token(i, "LPARENTHESIS") is False:
+ return None, i
start = i
ws = ["SPACE", "TAB", "NEWLINE"]
i += 1
@@ -474,24 +623,40 @@ def parenthesis_contain(self, i, ret_store=None):
nested_id = False
identifier = None
pointer = None
- while (deep > 0):
+ sizeof = False
+ id_only = True
+ if self.check_token(start - 1, "SIZEOF") is True:
+ sizeof = True
+ i = self.skip_ws(i)
+ while deep > 0 and self.peek_token(i) is not None:
+ # print (self.peek_token(i), deep, identifier, self.check_token(i, "NULL"))
if self.check_token(i, "RPARENTHESIS"):
deep -= 1
elif self.check_token(i, "LPARENTHESIS"):
deep += 1
- if identifier is not None and deep >= 0:
- return "pointer", self.skip_nest(start)
- elif self.check_token(i, "COMMA") and nested_id == True:
+ # if identifier is not None and deep >= 0:
+ # return "pointer", self.skip_nest(start)
+ elif (
+ deep > 1
+ and identifier is True
+ and self.check_token(i, ["NULL", "IDENTIFIER"])
+ ):
+ return "fct_call", self.skip_nest(start)
+ elif self.check_token(i, "COMMA") and nested_id is True:
return "function", self.skip_nest(start)
+ elif self.check_token(i, assigns) and deep == 1:
+ return "assign", self.skip_nest(start)
+ elif self.check_token(i, "PTR") and deep == 1:
+ return "variable", self.skip_nest(start)
elif self.check_token(i, "COMMA"):
return None, self.skip_nest(start)
elif self.check_token(i, ws):
pass
elif self.check_token(i, types):
tmp = start - 1
- while self.check_token(tmp, ["SPACE", "TAB"]) == True:
+ while self.check_token(tmp, ["SPACE", "TAB"]) is True:
tmp -= 1
- if self.check_token(tmp, "SIZEOF") == True:
+ if self.check_token(tmp, "SIZEOF") is True:
return None, self.skip_nest(start)
tmp = start + 1
while self.check_token(tmp, "RPARENTHESIS") is False:
@@ -502,17 +667,37 @@ def parenthesis_contain(self, i, ret_store=None):
return "cast", self.skip_nest(start)
elif self.check_token(i, "IDENTIFIER"):
tmp = i + 1
- if (identifier is not True and pointer == True) or ret_store is not None:
+ if (
+ identifier is not True and pointer is True
+ ) or ret_store is not None:
nested_id = True
+ if (
+ identifier is not True
+ and self.check_token(tmp, "RPARENTHESIS")
+ and self.scope.name == "Function"
+ and deep == 1
+ and pointer is None
+ and sizeof is False
+ ):
+ tmp = self.skip_nest(start) + 1
+ tmp = self.skip_ws(tmp)
+ if (
+ self.check_token(
+ tmp, ["IDENTIFIER", "CONSTANT", "MINUS", "PLUS"]
+ )
+ is False
+ ):
+ return None, self.skip_nest(start)
+ return "cast", self.skip_nest(start)
identifier = True
tmp = self.skip_ws(tmp)
- if pointer == True:
+ if pointer is True:
if self.check_token(tmp, "LBRACKET"):
tmp = self.skip_nest(tmp)
tmp += 1
while self.check_token(tmp, "RPARENTHESIS"):
tmp += 1
- #start = tmp
+ # start = tmp
tmp = self.skip_ws(tmp)
if self.check_token(tmp, "LPARENTHESIS"):
return "pointer", self.skip_nest(start)
@@ -521,14 +706,16 @@ def parenthesis_contain(self, i, ret_store=None):
elif self.check_token(i, ["MULT", "BWISE_AND"]):
tmp = i + 1
pointer = True
- if identifier != None:
+ if identifier is not None:
tmp = start - 1
- while self.check_token(tmp, ["SPACE", "TAB"]) == True:
+ while self.check_token(tmp, ["SPACE", "TAB"]) is True:
tmp -= 1
- if self.check_token(tmp, "SIZEOF") == True:
+ if self.check_token(tmp, "SIZEOF") is True:
return None, self.skip_nest(start)
tmp = self.skip_ws(i + 1)
if self.check_token(tmp, "RPARENTHESIS") is True:
return "cast", self.skip_nest(start)
i += 1
+ if identifier is True and id_only is True:
+ return "var", self.skip_nest(start)
return None, self.skip_nest(start)
diff --git a/norminette/errors.py b/norminette/errors.py
new file mode 100644
index 00000000..5134c83a
--- /dev/null
+++ b/norminette/errors.py
@@ -0,0 +1,227 @@
+from __future__ import annotations
+
+import os
+import json
+from dataclasses import dataclass, field, asdict
+from typing import (
+ TYPE_CHECKING,
+ Sequence,
+ Union,
+ Literal,
+ Optional,
+ List,
+ overload,
+ Any,
+ Type,
+)
+
+from norminette.colors import error_color
+from norminette.norm_error import errors as errors_dict
+
+if TYPE_CHECKING:
+ from norminette.lexer import Token
+ from norminette.file import File
+
+ErrorLevel = Literal["Error", "Notice"]
+
+
+@dataclass
+class Highlight:
+ lineno: int
+ column: int
+ length: Optional[int] = field(default=None)
+ hint: Optional[str] = field(default=None)
+
+ @classmethod
+ def from_token(
+ cls,
+ token: Token,
+ *,
+ hint: Optional[str] = None,
+ ) -> Highlight:
+ return cls(token.lineno, token.column, token.unsafe_length, hint)
+
+ def __lt__(self, other: Any) -> bool:
+ assert isinstance(other, Highlight)
+ if self.lineno == other.lineno:
+ if self.column == other.column:
+ return len(self.hint or '') > len(other.hint or '')
+ return self.column > other.column
+ return self.lineno > other.lineno
+
+
+@dataclass
+class Error:
+ name: str
+ text: str
+ level: ErrorLevel = field(default="Error")
+ highlights: List[Highlight] = field(default_factory=list)
+
+ @classmethod
+ def from_name(cls: Type[Error], /, name: str, **kwargs) -> Error:
+ return cls(name, errors_dict[name], **kwargs)
+
+ def __lt__(self, other: Any) -> bool:
+ assert isinstance(other, Error)
+ if not self.highlights:
+ return bool(other.highlights) or self.name > other.name
+ if not other.highlights:
+ return bool(self.highlights) or other.name > self.name
+ ah, bh = min(self.highlights), min(other.highlights)
+ if ah.column == bh.column and ah.lineno == bh.lineno:
+ return self.name < other.name
+ return (ah.lineno, ah.column) < (bh.lineno, bh.column)
+
+ @overload
+ def add_highlight(
+ self,
+ lineno: int,
+ column: int,
+ length: Optional[int] = None,
+ hint: Optional[str] = None,
+ ) -> None: ...
+ @overload
+ def add_highlight(self, highlight: Highlight, /) -> None: ...
+
+ def add_highlight(self, *args, **kwargs) -> None:
+ if len(args) == 1:
+ highlight, = args
+ else:
+ highlight = Highlight(*args, **kwargs)
+ self.highlights.append(highlight)
+
+
+class Errors:
+ __slots__ = "_inner"
+
+ def __init__(self) -> None:
+ self._inner: List[Error] = []
+
+ def __repr__(self) -> str:
+ return repr(self._inner)
+
+ def __len__(self) -> int:
+ return len(self._inner)
+
+ def __iter__(self):
+ self._inner.sort()
+ return iter(self._inner)
+
+ @overload
+ def add(self, error: Error) -> None:
+ """Add an `Error` instance to the errors.
+ """
+ ...
+
+ @overload
+ def add(self, name: str, *, level: ErrorLevel = "Error", highlights: List[Highlight] = ...) -> None:
+ """Builds an `Error` instance from a name in `errors_dict` and adds it to the errors.
+
+ ```python
+ >>> errors.add("TOO_MANY_LINES")
+ >>> errors.add("INVALID_HEADER")
+ >>> errors.add("GLOBAL_VAR_DETECTED", level="Notice")
+ ```
+ """
+ ...
+
+ @overload
+ def add(
+ self,
+ /,
+ name: str,
+ text: str,
+ *,
+ level: ErrorLevel = "Error",
+ highlights: List[Highlight] = ...,
+ ) -> None:
+ """Builds an `Error` instance and adds it to the errors.
+
+ ```python
+ >>> errors.add("BAD_IDENTATION", "You forgot an column here")
+ >>> errors.add("CUSTOM_ERROR", f"name {not_defined!r} is not defined. Did you mean: {levenshtein_distance}?")
+ >>> errors.add("NOOP", "Empty if statement", level="Notice")
+ ```
+ """
+ ...
+
+ def add(self, *args, **kwargs) -> None:
+ kwargs.setdefault("level", "Error")
+ error = None
+ if len(args) == 1:
+ error = args[0]
+ if isinstance(error, str):
+ error = Error.from_name(error, **kwargs)
+ if len(args) == 2:
+ error = Error(*args, **kwargs)
+ assert isinstance(error, Error), "bad function call"
+ return self._inner.append(error)
+
+ @property
+ def status(self) -> Literal["OK", "Error"]:
+ return "OK" if all(it.level == "Notice" for it in self._inner) else "Error"
+
+ def append(self, *args, **kwargs):
+ """Deprecated alias for `.add(...)`, kept for backward compatibility.
+
+ Use `.add(...)` instead.
+ """
+ return self.add(*args, **kwargs)
+
+
+class _formatter:
+ name: str
+
+ def __init__(self, files: Union[File, Sequence[File]], **options) -> None:
+ if not isinstance(files, Sequence):
+ files = [files]
+ self.files = files
+ self.options = options
+
+ def __init_subclass__(cls) -> None:
+ cls.name = cls.__name__.rstrip("ErrorsFormatter").lower()
+
+
+class HumanizedErrorsFormatter(_formatter):
+ @property
+ def use_colors(self) -> bool:
+ return self.options.get("use_colors", True)
+
+ def _colorize_error_text(self, error: Error) -> str:
+ color = error_color(error.name)
+ if not self.use_colors or not color:
+ return error.text
+ return f"\x1b[{color}m{error.text}\x1b[0m"
+
+ def __str__(self) -> str:
+ output = ''
+ for file in self.files:
+ output += f"{file.basename}: {file.errors.status}!"
+ for error in file.errors:
+ highlight = error.highlights[0]
+ error_text = self._colorize_error_text(error)
+ output += f"\n{error.level}: {error.name:<20} "
+ output += f"(line: {highlight.lineno:>3}, col: {highlight.column:>3}):\t{error_text}"
+ output += '\n'
+ return output
+
+
+class JSONErrorsFormatter(_formatter):
+ def __str__(self):
+ files = []
+ for file in self.files:
+ files.append({
+ "path": os.path.abspath(file.path),
+ "status": file.errors.status,
+ "errors": tuple(map(asdict, file.errors)),
+ })
+ output = {
+ "files": files,
+ }
+ return json.dumps(output, separators=(',', ':')) + '\n'
+
+
+formatters = (
+ JSONErrorsFormatter,
+ HumanizedErrorsFormatter,
+)
diff --git a/norminette/exceptions.py b/norminette/exceptions.py
index c45239d0..a90ff597 100644
--- a/norminette/exceptions.py
+++ b/norminette/exceptions.py
@@ -1,4 +1,8 @@
-class CParsingError(Exception):
+class NorminetteError(Exception):
+ pass
+
+
+class CParsingError(NorminetteError):
def __init__(self, errmsg):
self.msg = errmsg
@@ -7,3 +11,12 @@ def __str__(self):
def __repr__(self):
return self.__str__
+
+
+class MaybeInfiniteLoop(NorminetteError):
+ def __init__(self) -> None:
+ super().__init__("The maximum number of iterations a loop can have has been reached")
+
+
+class UnexpectedEOF(NorminetteError):
+ pass
diff --git a/norminette/file.py b/norminette/file.py
new file mode 100644
index 00000000..4f682946
--- /dev/null
+++ b/norminette/file.py
@@ -0,0 +1,24 @@
+import os
+from typing import Optional
+
+from norminette.errors import Errors
+
+
+class File:
+ def __init__(self, path: str, source: Optional[str] = None) -> None:
+ self.path = path
+ self._source = source
+
+ self.errors = Errors()
+ self.basename = os.path.basename(path)
+ self.name, self.type = os.path.splitext(self.basename)
+
+ @property
+ def source(self) -> str:
+ if self._source is None:
+ with open(self.path) as file:
+ self._source = file.read()
+ return self._source
+
+ def __repr__(self) -> str:
+ return f""
diff --git a/norminette/i18n.py b/norminette/i18n.py
new file mode 100644
index 00000000..17bf88c2
--- /dev/null
+++ b/norminette/i18n.py
@@ -0,0 +1,263 @@
+import os
+import subprocess
+import sys
+from pathlib import Path
+from importlib.metadata import version
+from typing import List
+
+
+__all__ = (
+ "set_locale",
+ "get_env_locale",
+ "_",
+)
+
+LOCALES = (
+ "en_US",
+ "pt_BR",
+)
+
+LOCALE_DIR = Path(__file__).parent / "locale"
+
+DOMAIN = "norminette"
+
+# Default fallback
+_ = lambda _: _ # noqa: E731
+
+
+def set_locale(locale: str) -> None:
+ """
+ Set the locale for the application.
+ This function loads the translation files from the locale directory and sets the translation function.
+ """
+ print(f"Setting locale to {locale}")
+ global _
+ try:
+ import gettext
+
+ translation = gettext.translation(
+ DOMAIN,
+ localedir=str(LOCALE_DIR),
+ languages=[locale],
+ fallback=True,
+ )
+ _ = translation.gettext
+ except ImportError:
+ raise
+
+
+def get_env_locale(default: str = "en_US") -> str:
+ """
+ Get the locale from the environment.
+ This function returns the locale based on the LANGUAGE environment variable.
+ """
+ keys = (
+ "NORMINETTE_LOCALE",
+ "LOCALE",
+ )
+ for key in keys:
+ locale = os.environ.get(key)
+ if locale:
+ return locale.split(":")[0]
+ return default
+
+
+def _get_pot_file_path() -> Path:
+ """
+ Get the path to the .pot file.
+ This function returns the path to the .pot file in the locale directory.
+ """
+ return LOCALE_DIR / f"{DOMAIN}.pot"
+
+
+def _collect_python_files(root_dir: Path) -> List[Path]:
+ """
+ Collect all Python source files from the given root directory.
+ """
+ return [file for file in root_dir.rglob("norminette/**/*.py")]
+
+
+def _create_pot_file() -> None:
+ """
+ Create the .pot file by extracting translatable strings from Python source files.
+ """
+ root_dir = Path(__file__).parent.parent
+ source_files = _collect_python_files(root_dir)
+
+ try:
+ result = subprocess.run(
+ [
+ "xgettext",
+ "-o",
+ str(_get_pot_file_path()),
+ "--from-code=UTF-8",
+ "--keyword=_",
+ ] + [str(file.relative_to(root_dir)) for file in source_files],
+ check=True,
+ capture_output=True,
+ text=True,
+ )
+ if result.returncode == 0:
+ _update_pot_header(_get_pot_file_path())
+ print("Successfully created .pot file.")
+ else:
+ print(f"xgettext exited with code {result.returncode}.")
+ print(f"Error output: {result.stderr}")
+ sys.exit(1)
+ except subprocess.CalledProcessError as e:
+ print(f"Error while running xgettext: {e}")
+ print(f"Output: {e.output}")
+ sys.exit(1)
+
+
+def _create_or_update_po_files() -> None:
+ """
+ Create or update .po files for each locale.
+ This function creates or updates .po files for each locale defined in the LOCALES list
+ using the .pot file as a template and placing them in the correct locale directory.
+ """
+ pot_file = _get_pot_file_path()
+ if not pot_file.exists():
+ print("Error: .pot file not found. Run _create_pot_file() first.")
+ sys.exit(1)
+
+ for locale in LOCALES:
+ locale_path = LOCALE_DIR / locale / "LC_MESSAGES"
+ locale_path.mkdir(parents=True, exist_ok=True)
+
+ po_file = locale_path / f"{DOMAIN}.po"
+
+ if po_file.exists():
+ try:
+ # Merge existing .po file with the updated .pot file
+ result = subprocess.run(
+ ["msgmerge", "--update", "--backup=none", str(po_file), str(pot_file)],
+ check=True,
+ text=True,
+ stderr=subprocess.STDOUT,
+ )
+ if result.returncode == 0:
+ print(f"Successfully updated {locale} .po file")
+ _update_po_header(po_file)
+ else:
+ print(f"Error updating {locale} .po file:")
+ print(result.stdout)
+ print(result.stderr)
+ sys.exit(1)
+ except subprocess.CalledProcessError as e:
+ print(f"Error while updating {locale} .po file: {e}")
+ print(f"Output: {e.output}")
+ sys.exit(1)
+ else:
+ try:
+ # Initialize .po file from .pot template
+ result = subprocess.run(
+ ["msginit", "--no-translator",
+ "--input", str(pot_file),
+ "--output-file", str(po_file),
+ "--locale", locale],
+ check=True,
+ text=True,
+ stderr=subprocess.STDOUT, # Redirect stderr to stdout
+ )
+ if result.returncode == 0:
+ print(f"Successfully created {locale} .po file")
+ _update_po_header(po_file)
+ else:
+ print(f"Error creating {locale} .po file.")
+ print(result.stderr)
+ print(result.stdout)
+ sys.exit(1)
+ except subprocess.CalledProcessError as e:
+ print(f"Error while creating {locale} .po file: {e}")
+ print(f"Output: {e.output}")
+ sys.exit(1)
+
+
+def _update_pot_header(pot_file: Path) -> None:
+ """
+ Update the header of the .pot file to set the charset to UTF-8 and update other metadata.
+ """
+ project_version = version("norminette")
+ try:
+ with pot_file.open("r", encoding="utf-8") as f:
+ lines = f.readlines()
+
+ with pot_file.open("w", encoding="utf-8") as f:
+ for line in lines:
+ if "Content-Type:" in line:
+ f.write('"Content-Type: text/plain; charset=UTF-8\\n"\n')
+ elif "Project-Id-Version:" in line:
+ f.write(f'"Project-Id-Version: {project_version}\\n"\n')
+ else:
+ f.write(line)
+
+ print(f"Updated header of {pot_file} with charset=UTF-8 and version={project_version}.")
+ except Exception as e:
+ print(f"Error while updating .pot header: {e}")
+ sys.exit(1)
+
+
+def _update_po_header(po_file: Path) -> None:
+ """
+ Update the header of the .po file to set the project version and other metadata.
+ """
+ project_version = version("norminette")
+ try:
+ with po_file.open("r", encoding="utf-8") as f:
+ lines = f.readlines()
+
+ with po_file.open("w", encoding="utf-8") as f:
+ for line in lines:
+ if "Project-Id-Version:" in line:
+ f.write(f'"Project-Id-Version: {project_version}\\n"\n')
+ else:
+ f.write(line)
+
+ print(f"Updated header of {po_file} with version={project_version}.")
+ except Exception as e:
+ print(f"Error while updating .po header: {e}")
+ sys.exit(1)
+
+
+def _compile_mo_files() -> None:
+ """
+ Compile .po files into .mo files for each locale.
+ This function compiles the .po files into .mo files, which are used by gettext for translations.
+ """
+ for locale in LOCALES:
+ po_file = LOCALE_DIR / locale / "LC_MESSAGES" / f"{DOMAIN}.po"
+ mo_file = LOCALE_DIR / locale / "LC_MESSAGES" / f"{DOMAIN}.mo"
+
+ if po_file.exists():
+ try:
+ result = subprocess.run(
+ ["msgfmt", str(po_file), "-o", str(mo_file)],
+ check=True,
+ capture_output=True,
+ text=True,
+ )
+ if result.returncode == 0:
+ print(f"Successfully compiled {locale} .mo file")
+ else:
+ print(f"Error compiling {locale} .mo file: {result.stderr}")
+ sys.exit(1)
+ except subprocess.CalledProcessError as e:
+ print(f"Error while compiling {locale} .mo file: {e}")
+ print(f"Output: {e.output}")
+ sys.exit(1)
+ else:
+ print(f"Warning: .po file for {locale} does not exist. Skipping compilation.")
+
+
+set_locale(get_env_locale())
+
+
+if __name__ == "__main__":
+ try:
+ _create_pot_file()
+ _create_or_update_po_files()
+ _compile_mo_files()
+ except Exception as e:
+ print(f"Unexpected error: {e}")
+ sys.exit(1)
diff --git a/norminette/lexer/__init__.py b/norminette/lexer/__init__.py
index 91156a86..1d14758c 100644
--- a/norminette/lexer/__init__.py
+++ b/norminette/lexer/__init__.py
@@ -1,2 +1,4 @@
-from .tokens import Token
-from .lexer import Lexer, TokenError
+from norminette.lexer.lexer import Lexer
+from norminette.lexer.tokens import Token
+
+__all__ = ["Lexer", "Token"]
diff --git a/norminette/lexer/dictionary.py b/norminette/lexer/dictionary.py
index 4698f0a4..a971e125 100644
--- a/norminette/lexer/dictionary.py
+++ b/norminette/lexer/dictionary.py
@@ -1,58 +1,62 @@
""" Dictionary that correlates lexeme with token """
-keywords = {
- # C reserved keywords #
- 'auto': "AUTO",
- 'break': "BREAK",
- 'case': "CASE",
- 'char': "CHAR",
- 'const': "CONST",
- 'continue': "CONTINUE",
- 'default': "DEFAULT",
- 'do': "DO",
- 'double': "DOUBLE",
- 'else': "ELSE",
- 'enum': "ENUM",
- 'extern': "EXTERN",
- 'float': "FLOAT",
- 'for': "FOR",
- 'goto': "GOTO",
- 'if': "IF",
- 'int': "INT",
- 'long': "LONG",
- 'register': "REGISTER",
- 'return': "RETURN",
- 'short': "SHORT",
- 'signed': "SIGNED",
- 'sizeof': "SIZEOF",
- 'static': "STATIC",
- 'struct': "STRUCT",
- 'switch': "SWITCH",
- 'typedef': "TYPEDEF",
- 'union': "UNION",
- 'unsigned': "UNSIGNED",
- 'void': "VOID",
- 'volatile': "VOLATILE",
- 'while': "WHILE",
- 'inline': "INLINE",
- 'NULL': "NULL",
- 'restrict': "RESTRICT"
+trigraphs = {
+ "??<": '{',
+ "??>": '}',
+ "??(": '[',
+ "??)": ']',
+ "??=": '#',
+ "??/": '\\',
+ "??'": '^',
+ "??!": '|',
+ "??-": '~',
}
-preproc_keywords = {
- 'define': "DEFINE",
- 'error': "ERROR",
- 'endif': "ENDIF",
- 'elif': "ELIF",
- 'ifdef': "IFDEF",
- 'ifndef': "IFNDEF",
- 'if': "#IF",
- 'else': "#ELSE",
- 'include': "INCLUDE",
- 'pragma': "PRAGMA",
- 'undef': "UNDEF",
- 'warning': 'WARNING',
- 'import': 'IMPORT'
+digraphs = {
+ "<%": '{',
+ "%>": '}',
+ "<:": '[',
+ ":>": ']',
+ "%:": '#',
+}
+
+keywords = {
+ # C reserved keywords #
+ "auto": "AUTO",
+ "break": "BREAK",
+ "case": "CASE",
+ "char": "CHAR",
+ "const": "CONST",
+ "continue": "CONTINUE",
+ "default": "DEFAULT",
+ "do": "DO",
+ "double": "DOUBLE",
+ "else": "ELSE",
+ "enum": "ENUM",
+ "extern": "EXTERN",
+ "float": "FLOAT",
+ "for": "FOR",
+ "goto": "GOTO",
+ "if": "IF",
+ "int": "INT",
+ "long": "LONG",
+ "register": "REGISTER",
+ "return": "RETURN",
+ "short": "SHORT",
+ "signed": "SIGNED",
+ "sizeof": "SIZEOF",
+ "static": "STATIC",
+ "struct": "STRUCT",
+ "switch": "SWITCH",
+ "typedef": "TYPEDEF",
+ "union": "UNION",
+ "unsigned": "UNSIGNED",
+ "void": "VOID",
+ "volatile": "VOLATILE",
+ "while": "WHILE",
+ "inline": "INLINE",
+ "NULL": "NULL",
+ "restrict": "RESTRICT",
}
"""
@@ -64,53 +68,56 @@
"""
operators = {
- '>>=': "RIGHT_ASSIGN",
- '<<=': "LEFT_ASSIGN",
- '+=': "ADD_ASSIGN",
- '-=': "SUB_ASSIGN",
- '*=': "MUL_ASSIGN",
- '/=': "DIV_ASSIGN",
- '%=': "MOD_ASSIGN",
- '&=': "AND_ASSIGN",
- '^=': "XOR_ASSIGN",
- '|=': "OR_ASSIGN",
- '<=': "LESS_OR_EQUAL",
- '>=': "GREATER_OR_EQUAL",
- '==': "EQUALS",
- '!=': "NOT_EQUAL",
- '=': "ASSIGN",
- ';': "SEMI_COLON",
- ':': "COLON",
- ',': "COMMA",
- '.': "DOT",
- '!': "NOT",
- '-': "MINUS",
- '+': "PLUS",
- '*': "MULT",
- '/': "DIV",
- '%': "MODULO",
- '<': "LESS_THAN",
- '>': "MORE_THAN",
- '...': "ELLIPSIS",
- '++': "INC",
- '--': "DEC",
- '->': "PTR",
- '&&': "AND",
- '||': "OR",
- '^': "BWISE_XOR",
- '|': "BWISE_OR",
- '~': "BWISE_NOT",
- '&': "BWISE_AND",
- '>>': "RIGHT_SHIFT",
- '<<': "LEFT_SHIFT",
- '?': "TERN_CONDITION"
+ ">>=": "RIGHT_ASSIGN",
+ "<<=": "LEFT_ASSIGN",
+ "+=": "ADD_ASSIGN",
+ "-=": "SUB_ASSIGN",
+ "*=": "MUL_ASSIGN",
+ "/=": "DIV_ASSIGN",
+ "%=": "MOD_ASSIGN",
+ "&=": "AND_ASSIGN",
+ "^=": "XOR_ASSIGN",
+ "|=": "OR_ASSIGN",
+ "<=": "LESS_OR_EQUAL",
+ ">=": "GREATER_OR_EQUAL",
+ "==": "EQUALS",
+ "!=": "NOT_EQUAL",
+ "=": "ASSIGN",
+ ";": "SEMI_COLON",
+ ":": "COLON",
+ ",": "COMMA",
+ ".": "DOT",
+ "!": "NOT",
+ "-": "MINUS",
+ "+": "PLUS",
+ "*": "MULT",
+ "/": "DIV",
+ "%": "MODULO",
+ "<": "LESS_THAN",
+ ">": "MORE_THAN",
+ "...": "ELLIPSIS",
+ "++": "INC",
+ "--": "DEC",
+ "->": "PTR",
+ "&&": "AND",
+ "||": "OR",
+ "^": "BWISE_XOR",
+ "|": "BWISE_OR",
+ "~": "BWISE_NOT",
+ "&": "BWISE_AND",
+ ">>": "RIGHT_SHIFT",
+ "<<": "LEFT_SHIFT",
+ "?": "TERN_CONDITION",
+ "#": "HASH",
}
brackets = {
- '{': "LBRACE",
- '}': "RBRACE",
- '(': "LPARENTHESIS",
- ')': "RPARENTHESIS",
- '[': "LBRACKET",
- ']': "RBRACKET"
+ "{": "LBRACE",
+ "}": "RBRACE",
+ "(": "LPARENTHESIS",
+ ")": "RPARENTHESIS",
+ "[": "LBRACKET",
+ "]": "RBRACKET",
}
+
+__all__ = ["brackets", "operators", "keywords"]
diff --git a/norminette/lexer/lexer.py b/norminette/lexer/lexer.py
index e86bb1f9..a3c79e1f 100644
--- a/norminette/lexer/lexer.py
+++ b/norminette/lexer/lexer.py
@@ -1,483 +1,537 @@
import re
import string
-from lexer.tokens import Token
-from lexer.dictionary import keywords, preproc_keywords, operators, brackets
-
-
-def read_file(filename):
- with open(filename) as f:
- return f.read()
-
-
-class TokenError(Exception):
- def __init__(self, pos):
- self.msg = f"Unrecognized token line {pos[0]}, col {pos[1]}"
-
- def __repr__(self):
- return self.msg
+from typing import Optional, Tuple, cast
+
+from norminette.exceptions import UnexpectedEOF, MaybeInfiniteLoop
+from norminette.lexer.dictionary import digraphs, trigraphs
+from norminette.lexer.dictionary import brackets
+from norminette.lexer.dictionary import keywords
+from norminette.lexer.dictionary import operators
+from norminette.lexer.tokens import Token
+from norminette.file import File
+from norminette.errors import Error, Highlight as H
+
+
+def c(a: str, b: str):
+ a = a.lower()
+ b = b.lower()
+ return (
+ a + b, a.upper() + b, a + b.upper(), a.upper() + b.upper(),
+ b + a, b.upper() + a, b + a.upper(), b.upper() + a.upper(),
+ )
+
+
+quote_prefixes = (*"lLuU", "u8")
+octal_digits = "01234567"
+hexadecimal_digits = "0123456789abcdefABCDEF"
+integer_suffixes = (
+ '',
+ *"uUlLzZ",
+ "ll", "LL",
+ "wb", "WB",
+ "i64", "I64",
+ *c('u', 'l'),
+ *c('u', "ll"),
+ *c('u', 'z'),
+ *c('u', "wb"),
+ *c('u', "i64"),
+)
+float_suffixes = (
+ '',
+ *"lLfFdD",
+ "dd", "DD",
+ "df", "DF",
+ "dl", "DL",
+ *c('f', 'i'),
+ *c('f', 'j'),
+)
+
+INT_LITERAL_PATTERN = re.compile(r"""
+^
+# (?P[-+]*)
+(?P # prefix can be
+ 0[bBxX]* # 0, 0b, 0B, 0x, 0X, 0bb, 0BB, ...
+ | # or empty
+)
+(?P
+ # BUG If prefix is followed by two or more x, it doesn't works correctly
+ (?<=0[xX]) # is prefix for hex digits?
+ [\da-fA-F]+ # so, collect hex digits
+ | # otherwise
+ \d+ # collect decimal digits
+)
+(?P
+ (?<=[eE]) # is constant ending with an `E`?
+ [\w\d+\-.]* # so, collect `+` and `-` operators
+ | # otherwise
+ \w # collect suffixes that starts with an letter
+ [\w\d.]* # and letters, digits and dots that follows it
+ | # finally, do suffix be optional (empty)
+)
+""", re.VERBOSE)
+
+
+def _float_pattern(const: str, digit: str, exponent: Tuple[str, str]):
+ pattern = r"""
+ ^
+ (?P{0})
+ (?P
+ (?:
+ [{2}]+[-+]{3}+
+ |[{2}]+{3}+
+ |(?:[{2}][+-]?(?:[.{3}]+)?)+
+ ){1}
+ )
+ (?P[\w\d._]*|)
+ """.format(const, *exponent, digit)
+ return re.compile(pattern, re.VERBOSE)
+
+
+FLOAT_EXPONENT_LITERAL_PATTERN = _float_pattern(r"\d+", digit=r"\d", exponent=('', "eE"))
+FLOAT_FRACTIONAL_LITERAL_PATTERN = _float_pattern(r"(?:\d+)?\.\d+|\d+\.", digit=r"\d", exponent=('?', "eE"))
+FLOAT_HEXADECIMAL_LITERAL_PATTERN = _float_pattern(r"0[xX]+[\da-fA-F]+(?:\.[\da-fA-F]+)?",
+ digit=r"[\da-fA-F]", exponent=('?', "pP"))
class Lexer:
- def __init__(self, source_code, starting_line=1):
- self.src = source_code
- self.len = len(source_code)
- self.__char = self.src[0] if self.src != "" else None
+ def __init__(self, file: File):
+ self.file = file
+
self.__pos = int(0)
- self.__line_pos = int(starting_line)
- self.__line = int(starting_line)
- self.tokens = []
-
- def peek_sub_string(self, size):
- return self.src[self.__pos: self.__pos + size]
-
- def peek_char(self):
- """ Return current character being checked,
- if the character is a backslash character the following
- character is appended to the return value. It will allow us to
- parse escaped characters easier.
- """
- if self.__pos < self.len:
- if self.src[self.__pos] == '\\':
- self.__char = self.src[self.__pos:self.__pos + 2]
- else:
- self.__char = self.src[self.__pos]
- else:
- self.__char = None
- return self.__char
+ self.__line_pos = self.__line = 1
- def pop_char(self):
- """ Pop a character that's been read by increasing self.__pos,
- for escaped characters self.__pos will be increased twice
- """
- if self.peek_char() == "\t":
- # this calculates the 'visual offset' of a tab based on it's
- # position on the line, if there's an easier way to calculate this
- # you're welcome
- self.__line_pos = int((
- self.__line_pos + 4 -
- (self.__line_pos - 1) % 4) * 5 / 5)
- else:
- self.__line_pos += 1
- if self.__pos < self.len and self.src[self.__pos] == '\\':
- self.__pos += 1
- self.__pos += 1
- return self.peek_char()
+ def raw_peek(self, *, offset: int = 0, collect: int = 1):
+ if (pos := self.__pos + offset) < len(self.file.source):
+ return ''.join(self.file.source[pos:pos+collect])
+ return None
- def peek_token(self):
- return self.tokens[-1]
+ def peek(self, *, times: int = 1, offset: int = 0) -> Optional[Tuple[str, int]]:
+ char, size = '', 0
+ for _ in range(times):
+ if (trigraph := self.raw_peek(offset=offset + size, collect=3)) in trigraphs:
+ char += trigraphs[trigraph]
+ size += 3
+ elif (digraph := self.raw_peek(offset=offset + size, collect=2)) in digraphs:
+ char += digraphs[digraph]
+ size += 2
+ elif word := self.raw_peek(offset=offset + size):
+ char += word
+ size += 1
+ else:
+ break
+ if size:
+ return char, size
+ return None # Let it crash :D
+
+ def pop(
+ self,
+ *,
+ times: int = 1,
+ use_spaces: bool = False,
+ use_escape: bool = False,
+ ) -> str:
+ result = ""
+ for _ in range(times):
+ for _ in range(100):
+ if peek := self.peek():
+ char, size = peek
+ else:
+ raise UnexpectedEOF()
+ if char != '\\':
+ break
+ peek = self.peek(offset=size)
+ if peek is None:
+ break
+ temp, _ = peek # Don't change the `temp` to `char`
+ if temp != '\n':
+ if use_escape:
+ if temp in r"abefnrtv\"'?":
+ size += 1
+ char += temp
+ elif temp == 'x':
+ size += 1
+ char += temp
+ # BUG It is just considering one `byte` (0x0 to 0xFF), so it not works correctly
+ # with prefixed strings like `L"\0x1234"`.
+ peek = self.raw_peek(offset=size, collect=2)
+ if peek is None or peek[0] not in hexadecimal_digits:
+ error = Error.from_name("NO_HEX_DIGITS", level="Notice")
+ error.add_highlight(self.__line, self.__line_pos + size - 1, length=1)
+ self.file.errors.add(error)
+ else:
+ for digit in peek:
+ if digit not in hexadecimal_digits:
+ break
+ size += 1
+ char += digit
+ elif temp in octal_digits:
+ while (temp := self.raw_peek(offset=size)) and temp in octal_digits:
+ size += 1
+ char += temp
+ else:
+ error = Error.from_name("UNKNOWN_ESCAPE", level="Notice")
+ error.add_highlight(self.__line, self.__line_pos + size, length=1)
+ self.file.errors.add(error)
+ char += temp
+ size += 1
+ break
+ self.__pos += size + 1
+ self.__line += 1
+ self.__line_pos = 0
+ peek = self.peek()
+ if peek is None:
+ raise UnexpectedEOF()
+ char, size = peek
+ else:
+ # It hits when we have multiple lines followed by `\`, e.g:
+ # ```c
+ # // hello \
+ # a \
+ # b \
+ # c\
+ # \
+ # a
+ # ```
+ raise MaybeInfiniteLoop()
+ if char == '\n':
+ self.__line_pos = 0
+ self.__line += 1
+ if char == '\t':
+ self.__line_pos += (spaces := 4 - (self.__line_pos - 1) % 4) - 1
+ if use_spaces:
+ char = ' ' * spaces
+ self.__line_pos += size
+ self.__pos += size
+ result += char
+ return result
def line_pos(self):
return self.__line, self.__line_pos
- def is_string(self):
- """ True if current character could start a string constant
- """
- if self.peek_sub_string(2) == 'L"' or self.peek_char() == '"':
- return True
- else:
- return False
-
- def is_constant(self):
- """ True if current character could start a numeric constant
- """
- if self.peek_char() in string.digits:
- return True
- elif self.peek_char() == ".":
- for i in range(0, self.len - self.__pos):
- if self.src[self.__pos + i] == ".":
- i += 1
- elif self.src[self.__pos + i] in "0123456789":
- return True
- else:
- return False
- else:
- return False
-
- def is_char_constant(self):
- """ True if current character could start a character constant
- """
- if self.peek_char() == '\'' or self.peek_sub_string(2) == "L'":
- return True
+ def parse_char_literal(self) -> Optional[Token]:
+ pos = lineno, column = self.line_pos()
+ value = ''
+ for prefix in quote_prefixes:
+ length = len(prefix)
+ result = self.raw_peek(collect=length + 1)
+ if not result:
+ return
+ if result.startswith(prefix) and result.endswith('\''):
+ value += self.pop(times=length)
+ break
+ if self.raw_peek() != '\'':
+ return
+ chars = 0
+ value += self.pop()
+ for _ in range(100):
+ try:
+ char = self.pop(use_escape=True)
+ except UnexpectedEOF:
+ error = Error.from_name("UNEXPECTED_EOF_CHR", highlights=[
+ H(lineno, column, length=len(value)),
+ ])
+ self.file.errors.add(error)
+ break
+ if char == '\n':
+ error = Error.from_name("UNEXPECTED_EOL_CHR", highlights=[
+ H(lineno, column, length=len(value)),
+ H(lineno, column + len(value), length=1, hint="Perhaps you forgot a single quote (')?")
+ ])
+ self.file.errors.add(error)
+ break
+ value += char
+ if char == '\'':
+ break
+ chars += 1
else:
- return False
-
- def string(self):
+ raise MaybeInfiniteLoop()
+ if value == "''":
+ error = Error.from_name("EMPTY_CHAR", highlights=[H(*pos, length=2)])
+ self.file.errors.add(error)
+ if chars > 1 and value.endswith('\''):
+ error = Error.from_name("CHAR_AS_STRING", highlights=[
+ H(*pos, length=len(value)),
+ H(*pos, length=1,
+ hint="Perhaps you want a string (double quote, \") instead of a char (single quote, ')?"),
+ ])
+ self.file.errors.add(error)
+ return Token("CHAR_CONST", pos, value=value)
+
+ def parse_string_literal(self):
"""String constants can contain any characer except unescaped newlines.
- An unclosed string or unescaped newline is a fatal error and thus
- parsing will stop here.
+ An unclosed string or unescaped newline is a fatal error and thus
+ parsing will stop here.
"""
- pos = self.line_pos()
- tkn_value = ""
- if self.peek_char() == 'L':
- tkn_value += self.peek_char()
- self.pop_char()
- tkn_value += self.peek_char()
- self.pop_char()
- while self.peek_char() not in [None]:
- tkn_value += self.peek_char()
- if self.peek_sub_string(2) == "\\\n":
- self.__line += 1
- self.__line_pos = 1
- if self.peek_char() == '\"':
+ if not self.peek():
+ return
+ pos = lineno, column = self.line_pos()
+ val = ''
+ for prefix in quote_prefixes:
+ length = len(prefix)
+ result = self.raw_peek(collect=length + 1)
+ if not result:
+ return
+ if result.startswith(prefix) and result.endswith('"'):
+ val += self.pop(times=length)
+ break
+ if self.raw_peek() != '"':
+ return
+ val += self.pop()
+ while self.peek() is not None:
+ char = self.pop(use_escape=True)
+ val += char
+ if char == '"':
break
- self.pop_char()
else:
- raise TokenError(pos)
+ error = Error.from_name("UNEXPECTED_EOF_STR")
+ error.add_highlight(*pos, length=len(val))
+ error.add_highlight(lineno, column + len(val), length=1, hint="Perhaps you forgot a double quote (\")?")
+ self.file.errors.add(error)
+ return Token("STRING", pos, val)
+
+ def parse_integer_literal(self):
+ # TODO Add to support single quote (') to separate digits according to C23
+
+ match = INT_LITERAL_PATTERN.match(self.file.source[self.__pos:])
+ if match is None:
return
- self.tokens.append(Token("STRING", pos, tkn_value))
- self.pop_char()
- def char_constant(self):
- """Char constants follow pretty much the same rule as string constants
- """
- pos = self.line_pos()
- tkn_value = '\''
- self.pop_char()
- while self.peek_char():
- tkn_value += self.peek_char()
- if self.peek_char() == '\n':
- self.pop_char()
- self.tokens.append(Token("TKN_ERROR", pos))
- return
- if self.peek_char() == '\'':
- self.pop_char()
- self.tokens.append(Token(
- "CHAR_CONST",
- pos,
- tkn_value))
- return
- self.pop_char()
- raise TokenError(pos)
+ pos = lineno, column = self.line_pos()
+ token = Token("CONSTANT", pos, slice := self.pop(times=match.end()))
- def constant(self):
+ if match["Suffix"] not in integer_suffixes:
+ suffix_length = len(match["Suffix"])
+ string_length = len(slice) - suffix_length
+ if match["Suffix"][0] in "+-":
+ error = Error.from_name("MAXIMAL_MUNCH")
+ error.add_highlight(lineno, column + string_length, length=1, hint="Perhaps you forgot a space ( )?")
+ else:
+ error = Error.from_name("INVALID_SUFFIX")
+ error.add_highlight(lineno, column + string_length, length=suffix_length)
+ self.file.errors.add(error)
+
+ def _check_bad_prefix(name: str, bucket: str):
+ error = Error.from_name(f"INVALID_{name}_INT")
+ for index, char in enumerate(match["Constant"], start=len(match["Prefix"])):
+ if char not in bucket:
+ error.add_highlight(lineno, column + index, length=1)
+ if error.highlights:
+ self.file.errors.add(error)
+
+ if match["Prefix"] in ("0b", "0B"):
+ _check_bad_prefix("BIN", "01")
+ elif match["Prefix"] == '0':
+ _check_bad_prefix("OCT", "01234567")
+ elif match["Prefix"] in ("0x", "0X"):
+ _check_bad_prefix("HEX", "0123456789abcdefABCDEF")
+
+ return token
+
+ def parse_float_literal(self):
"""Numeric constants can take many forms:
- - integer constants only allow digits [0-9]
- - real number constant only allow digits [0-9],
- ONE optionnal dot '.' and ONE optionnal 'e/E' character
- - binary constant only allow digits [0-1] prefixed by '0b' or '0B'
- - hex constant only allow digits [0-9], letters [a-f/A-F] prefixed
- by '0x' or '0X'
- - octal constants allow digits [0-9] prefixed by a zero '0'
- character
+ - integer constants only allow digits [0-9]
+ - real number constant only allow digits [0-9],
+ ONE optionnal dot '.' and ONE optionnal 'e/E' character
+ - binary constant only allow digits [0-1] prefixed by '0b' or '0B'
+ - hex constant only allow digits [0-9], letters [a-f/A-F] prefixed
+ by '0x' or '0X'
+ - octal constants allow digits [0-9] prefixed by a zero '0'
+ character
- Size ('l/L' for long) and sign ('u/U' for unsigned) specifiers can
- be appended to any of those. tokens
+ Size ('l/L' for long) and sign ('u/U' for unsigned) specifiers can
+ be appended to any of those. tokens
- Plus/minus operators ('+'/'-') can prefix any of those tokens
+ Plus/minus operators ('+'/'-') can prefix any of those tokens
- a numeric constant could start with a '.' (dot character)
+ a numeric constant could start with a '.' (dot character)
"""
- pos = self.line_pos()
- tkn_value = ""
- bucket = ".0123456789aAbBcCdDeEfFlLuUxX-+"
- while self.peek_char() and (self.peek_char() in bucket or self.peek_char() == "\\\n"):
- if self.peek_char() in "xX":
- if tkn_value.startswith("0") is False or len(tkn_value) > 1:
- raise TokenError(pos)
- for c in "xX":
- if c in tkn_value:
- raise TokenError(pos)
-
- elif self.peek_char() in "bB":
- if tkn_value != "0" \
- and tkn_value.startswith("0x") is False \
- and tkn_value.startswith("0X") is False:
- raise TokenError(pos)
-
- elif self.peek_char() in "+-":
- if tkn_value.endswith("e") is False \
- and tkn_value.endswith("E") is False \
- or self.peek_sub_string(2) in ["++", "--"]:
- break
-
- elif self.peek_char() in "eE" \
- and "0x" not in tkn_value and "0X" not in tkn_value:
- if "e" in tkn_value or "E" in tkn_value \
- or "f" in tkn_value or "F" in tkn_value \
- or "u" in tkn_value or "U" in tkn_value \
- or "l" in tkn_value or "L" in tkn_value:
- raise TokenError(pos)
-
- elif self.peek_char() in "lL":
- lcount = tkn_value.count("l") + tkn_value.count("L")
- if lcount > 1 or (lcount == 1 and tkn_value[-1] not in "lL") \
- or ("f" in tkn_value or "F" in tkn_value) \
- and "0x" not in tkn_value and "0X" not in tkn_value:
- raise TokenError(pos)
- elif self.peek_char() == 'l' and 'L' in tkn_value \
- or self.peek_char() == 'L' and 'l' in tkn_value:
- raise TokenError(pos)
-
- elif self.peek_char() in "uU":
- if "u" in tkn_value or "U" in tkn_value \
- or (("e" in tkn_value or "E" in tkn_value
- or "f" in tkn_value or "F" in tkn_value)
- and (
- "0x" not in tkn_value
- and "0X" not in tkn_value)):
- raise TokenError(pos)
-
- elif self.peek_char() in "Ff":
- if tkn_value.startswith("0x") is False \
- and tkn_value.startswith("0X") is False \
- and (
- "." not in tkn_value
- or "f" in tkn_value
- or "F" in tkn_value) \
- or "u" in tkn_value or "U" in tkn_value \
- or "l" in tkn_value or "L" in tkn_value:
- raise TokenError(pos)
-
- elif self.peek_char() in "aAbBcCdDeE" \
- and tkn_value.startswith("0x") is False \
- and tkn_value.startswith("0X") is False \
- or "u" in tkn_value or "U" in tkn_value \
- or "l" in tkn_value or "L" in tkn_value:
- raise TokenError(pos)
-
- elif self.peek_char() in "0123456789" \
- and "u" in tkn_value or "U" in tkn_value \
- or "l" in tkn_value or "L" in tkn_value:
- raise TokenError(pos)
-
- elif self.peek_char() == '.' and '.' in tkn_value:
- raise TokenError(pos)
-
- tkn_value += self.peek_char()
- self.pop_char()
- if tkn_value[-1] in "eE" and tkn_value.startswith("0x") is False \
- or tkn_value[-1] in "xX":
- raise TokenError(pos)
+ constant = self.raw_peek()
+ if constant is None:
+ return
+ pos = lineno, column = self.line_pos()
+ src = self.file.source[self.__pos:]
+ if match := FLOAT_EXPONENT_LITERAL_PATTERN.match(src):
+ type = "exponent"
+ elif match := FLOAT_FRACTIONAL_LITERAL_PATTERN.match(src):
+ type = "fractional"
+ elif match := FLOAT_HEXADECIMAL_LITERAL_PATTERN.match(src):
+ type = "hexadecimal"
else:
- self.tokens.append(Token("CONSTANT", pos, tkn_value))
-
- def mult_comment(self):
- pos = self.line_pos()
- self.pop_char(), self.pop_char()
- tkn_value = "/*"
- while self.peek_char():
- if self.src[self.__pos:].startswith("*/"):
- tkn_value += "*/"
- self.pop_char(), self.pop_char()
+ return
+ error = None
+ suffix = len(match["Suffix"])
+ column += len(match["Constant"])
+ badhex = match["Constant"].strip(hexadecimal_digits + '.')
+ if type == "exponent" and not re.match(r"[eE][-+]?\d+", match["Exponent"]):
+ error = Error.from_name("BAD_EXPONENT")
+ error.add_highlight(lineno, column, length=len(match["Exponent"]) + suffix)
+ elif type == "hexadecimal" and '.' not in match["Constant"] and not match["Exponent"]:
+ return # Hexadecimal Integer
+ elif type == "hexadecimal" and badhex not in ('x', 'X'):
+ error = Error.from_name("MULTIPLE_X")
+ error.add_highlight(lineno, column - len(match["Constant"]) + 1, length=len(badhex))
+ elif match["Constant"].count('.') == 1 and match["Suffix"].count('.') > 0:
+ error = Error.from_name("MULTIPLE_DOTS")
+ error.add_highlight(lineno, column, length=len(match["Exponent"]) + suffix)
+ elif match["Suffix"] not in float_suffixes:
+ error = Error.from_name("BAD_FLOAT_SUFFIX")
+ error.add_highlight(lineno, column + len(match["Exponent"]), length=suffix)
+ if error:
+ self.file.errors.add(error)
+ return Token("CONSTANT", pos, self.pop(times=match.end()))
+
+ def parse_multi_line_comment(self) -> Optional[Token]:
+ if self.raw_peek(collect=2) != "/*":
+ return
+ pos = lineno, column = self.line_pos()
+ val = self.pop(times=2)
+ eof = False
+ while self.peek():
+ try:
+ val += self.pop(use_spaces=True)
+ except UnexpectedEOF:
+ eof = True
+ break
+ if val.endswith("*/"):
break
- tkn_value += self.peek_char()
- if self.peek_char() == '\n':
- self.__line += 1
- self.__line_pos = 1
- self.pop_char()
- if tkn_value.endswith("*/"):
- self.tokens.append(Token("MULT_COMMENT", pos, tkn_value))
else:
- raise TokenError(pos)
-
- def comment(self):
+ eof = True
+ if eof:
+ # TODO Add a better highlight since it is a multi-line token
+ error = Error.from_name("UNEXPECTED_EOF_MC")
+ error.add_highlight(lineno, column, length=len(val))
+ self.file.errors.add(error)
+ return Token("MULT_COMMENT", pos, val)
+
+ def parse_line_comment(self) -> Optional[Token]:
"""Comments are anything after '//' characters, up until a newline or
- end of file
+ end of file
"""
+ if self.raw_peek(collect=2) != "//":
+ return
pos = self.line_pos()
- tkn_value = "//"
- self.pop_char(), self.pop_char()
- while self.peek_char() is not None:
- if self.peek_char() == '\n':
- self.tokens.append(Token("COMMENT", pos, tkn_value))
- return
- tkn_value += self.peek_char()
- self.pop_char()
- raise TokenError(pos)
+ val = self.pop(times=2)
+ while result := self.peek():
+ char, _ = result
+ if char == '\n':
+ break
+ try:
+ val += self.pop()
+ except UnexpectedEOF:
+ break
+ return Token("COMMENT", pos, val)
- def identifier(self):
+ def parse_identifier(self) -> Optional[Token]:
"""Identifiers can start with any letter [a-z][A-Z] or an underscore
- and contain any letters [a-z][A-Z] digits [0-9] or underscores
+ and contain any letters [a-z][A-Z] digits [0-9] or underscores
"""
+ char = self.raw_peek()
+ if not char or char not in string.ascii_letters + '_':
+ return
pos = self.line_pos()
- tkn_value = ""
- while self.peek_char() and \
- (
- self.peek_char() in string.ascii_letters + "0123456789_"
- or self.peek_char() == "\\\n"):
- if self.peek_char() == "\\\n":
- self.pop_char()
- continue
- tkn_value += self.peek_char()
- self.pop_char()
- if tkn_value in keywords:
- self.tokens.append(Token(keywords[tkn_value], pos))
-
- else:
- self.tokens.append(Token("IDENTIFIER", pos, tkn_value))
+ val = self.pop()
+ while char := self.raw_peek():
+ if char not in string.ascii_letters + "0123456789_":
+ break
+ val += self.pop()
+ if val in keywords:
+ return Token(keywords[val], pos)
+ return Token("IDENTIFIER", pos, val)
- def operator(self):
+ def parse_operator(self):
"""Operators can be made of one or more sign, so the longest operators
- need to be looked up for first in order to avoid false positives
- eg: '>>' being understood as two 'MORE_THAN' operators instead of
- one 'RIGHT_SHIFT' operator
+ need to be looked up for first in order to avoid false positives
+ eg: '>>' being understood as two 'MORE_THAN' operators instead of
+ one 'RIGHT_SHIFT' operator
"""
+ result = self.peek()
+ if not result:
+ return
+ char, _ = result
+ if char not in "+-*/,<>^&|!=%;:.~?#":
+ return
pos = self.line_pos()
- if self.peek_char() in ".+-*/%<>^&|!=":
-
- if self.peek_sub_string(3) in [">>=", "<<=", "..."]:
- self.tokens.append(Token(
- operators[self.peek_sub_string(3)],
- pos))
- self.pop_char(), self.pop_char(), self.pop_char()
-
- elif self.peek_sub_string(2) in [">>", "<<", "->"]:
- self.tokens.append(Token(
- operators[self.peek_sub_string(2)],
- pos))
- self.pop_char(), self.pop_char()
-
- elif self.peek_sub_string(2) == self.peek_char() + "=":
- self.tokens.append(Token(
- operators[self.peek_sub_string(2)],
- pos))
- self.pop_char(), self.pop_char()
-
- elif self.peek_char() in "+-<>=&|":
- if self.peek_sub_string(2) == self.peek_char() * 2:
- self.tokens.append(Token(
- operators[self.peek_sub_string(2)],
- pos))
- self.pop_char()
- self.pop_char()
-
- else:
- self.tokens.append(Token(
- operators[self.peek_char()], pos))
- self.pop_char()
-
- else:
- self.tokens.append(Token(
- operators[self.peek_char()],
- pos))
- self.pop_char()
-
- else:
- self.tokens.append(Token(
- operators[self.src[self.__pos]],
- pos))
- self.pop_char()
-
- def preprocessor(self):
- pos = self.line_pos()
- tkn_value = ""
- while self.peek_char():
- tkn_value += self.peek_char()
- self.pop_char()
- if self.peek_sub_string(2) == "\\\n":
- self.__line_pos = 1
- self.__line += 1
- # raise TokenError(self.line_pos())
- if self.peek_sub_string(2) in ["//", "/*"] \
- or self.peek_char() == '\n':
- break
- if len(tkn_value) <= 1:
- raise TokenError(self.line_pos())
- tkn_key = tkn_value[1:].split()[0]
- if tkn_key not in preproc_keywords and tkn_key[:len('include')] != 'include':
- raise TokenError(self.line_pos())
- else:
- if tkn_key not in preproc_keywords and tkn_key[:len('include')] == 'include':
- tkn_key = 'include'
- self.tokens.append(Token(
- preproc_keywords.get(tkn_key),
- pos,
- tkn_value))
-
- def get_next_token(self):
+ if char in ".+-*/%<>^&|!=":
+ if self.raw_peek(collect=3) in (">>=", "<<=", "..."):
+ return Token(operators[self.pop(times=3)], pos)
+ temp, _ = self.peek(times=2) # type: ignore
+ if temp in (">>", "<<", "->"):
+ return Token(operators[self.pop(times=2)], pos)
+ if temp == char + "=":
+ return Token(operators[self.pop(times=2)], pos)
+ if char in "+-<>=&|":
+ if temp == char * 2:
+ return Token(operators[self.pop(times=2)], pos)
+ char = self.pop()
+ return Token(operators[char], pos)
+
+ def parse_whitespace(self) -> Optional[Token]:
+ char = self.raw_peek()
+ if char is None or char not in "\n\t ":
+ return
+ if char == ' ':
+ token = Token("SPACE", self.line_pos())
+ elif char == "\t":
+ token = Token("TAB", self.line_pos())
+ elif char == "\n":
+ token = Token("NEWLINE", self.line_pos())
+ self.pop()
+ return token
+
+ def parse_brackets(self) -> Optional[Token]:
+ result = self.peek()
+ if result is None:
+ return
+ char, _ = result
+ if char not in brackets:
+ return
+ start = self.line_pos()
+ value = self.pop()
+ return Token(brackets[value], start)
+
+ parsers = (
+ parse_float_literal, # Need to be above:
+ # `parse_operator` to avoid ``
+ # `parse_integer_literal` to avoid `\d+`
+ parse_integer_literal,
+ parse_char_literal,
+ parse_string_literal,
+ parse_identifier, # Need to be bellow `char` and `string`
+ parse_whitespace,
+ parse_line_comment,
+ parse_multi_line_comment,
+ parse_operator,
+ parse_brackets,
+ )
+
+ def get_next_token(self) -> Optional[Token]:
"""Peeks one character and tries to match it to a token type,
- if it doesn't match any of the token types, an error will be raised
- and current file's parsing will stop
+ if it doesn't match any of the token types, an error will be raised
+ and current file's parsing will stop
"""
- while self.peek_char() is not None:
- if self.is_string():
- self.string()
-
- elif (self.peek_char().isalpha() and self.peek_char().isascii())or self.peek_char() == '_':
- self.identifier()
-
- elif self.is_constant():
- self.constant()
-
- elif self.is_char_constant():
- self.char_constant()
-
- elif self.peek_char() == '#':
- self.preprocessor()
-
- elif self.src[self.__pos:].startswith("/*"):
- self.mult_comment()
-
- elif self.src[self.__pos:].startswith("//"):
- self.comment()
-
- elif self.peek_char() in "+-*/,<>^&|!=%;:.~?":
- self.operator()
-
- elif self.peek_char() == ' ':
- self.tokens.append(Token("SPACE", self.line_pos()))
- self.pop_char()
-
- elif self.peek_char() == '\t':
- self.tokens.append(Token("TAB", self.line_pos()))
- self.pop_char()
-
- elif self.peek_char() == '\n':# or ord(self.peek_char()) == 8203:
- self.tokens.append(Token("NEWLINE", self.line_pos()))
- self.pop_char()
- self.__line_pos = 1
+ while self.raw_peek():
+ if self.raw_peek(collect=2) == "\\\n" or self.raw_peek(collect=4) == "??/\n":
+ # Avoid using `.pop()` here since it ignores the escaped
+ # newline and pops and upcomes after it. E.g, if we have
+ # `\\\nab` and use `.pop()`, the parsers funcs will see `b``.
+ _, size = self.peek() # type: ignore
+ self.__pos += cast(int, size) + 1
self.__line += 1
-
- elif self.peek_char() == '\\\n':
- self.tokens.append(Token("ESCAPED_NEWLINE", self.line_pos()))
- self.pop_char()
self.__line_pos = 1
- self.__line += 1
-
- elif self.peek_char() in brackets:
- self.tokens.append(Token(
- brackets[self.peek_char()],
- self.line_pos()))
- self.pop_char()
-
- else:
- raise TokenError(self.line_pos())
-
- return self.peek_token()
-
- return None
-
- def get_tokens(self):
- """Iterate through self.get_next_token() to convert source code into a
- token list
- """
- while self.get_next_token():
- continue
- return self.tokens
-
- def print_tokens(self):
- if self.tokens == []:
- return
- for t in self.tokens:
- if t.type == "NEWLINE":
- print(t)
else:
- print(t, end="")
- if self.tokens[-1].type != "NEWLINE":
- print("")
+ break
+ for parser in self.parsers:
+ if result := parser(self):
+ return result
+ if char := self.raw_peek():
+ error = Error("BAD_LEXEME", f"No matchable token for '{char}' lexeme")
+ error.add_highlight(*self.line_pos(), length=1)
+ self.file.errors.add(error)
+ self.__pos += 1
+ self.__line_pos += 1
+ # BUG If we have multiples bad lexemes, it can raise RecursionError
+ return self.get_next_token()
- def check_tokens(self):
- """
- Only used for testing
- """
- if self.tokens == []:
- self.get_tokens()
- if self.tokens == []:
- return ""
- ret = ""
- for i in range(0, len(self.tokens)):
- ret += self.tokens[i].test()
- ret += "" if self.tokens[i].type != "NEWLINE" else "\n"
- if self.tokens[-1].type != "NEWLINE":
- ret += "\n"
- return ret
+ def __iter__(self):
+ while token := self.get_next_token():
+ yield token
diff --git a/norminette/lexer/tokens.py b/norminette/lexer/tokens.py
index cc56f03b..763d9384 100644
--- a/norminette/lexer/tokens.py
+++ b/norminette/lexer/tokens.py
@@ -1,24 +1,39 @@
-from lexer.dictionary import operators, brackets, keywords, preproc_keywords
+from typing import Optional, Tuple
+from dataclasses import dataclass, field
+@dataclass(eq=True, repr=True)
class Token:
- def __init__(self, tkn_type, pos, tkn_value=None):
- self.type = str(tkn_type)
- self.pos = pos
- if tkn_value is not None:
- self.value = str(tkn_value)
- self.length = len(tkn_value)
- else:
- self.value = None
- self.length = 0
-
- def __repr__(self):
+ type: str
+ pos: Tuple[int, int]
+ value: Optional[str] = field(default=None)
+
+ @property
+ def length(self) -> int:
+ return len(self.value or '')
+
+ @property
+ def unsafe_length(self) -> Optional[int]:
+ if self.value is None:
+ return None
+ return self.length
+
+ @property
+ def lineno(self) -> int:
+ return self.pos[0]
+
+ @property
+ def column(self) -> int:
+ return self.pos[1]
+
+ @property
+ def line_column(self):
+ return self.pos[1]
+
+ def __str__(self):
"""
Token representation for debugging, using the format
or simply when value is None
"""
- r = f'<{self.type}={self.value}>' if self.value else f'<{self.type}>'
+ r = f"<{self.type}={self.value}>" if self.value else f"<{self.type}>"
return r
-
- def test(self):
- return self.__repr__()
diff --git a/norminette/locale/en_US/LC_MESSAGES/norminette.po b/norminette/locale/en_US/LC_MESSAGES/norminette.po
new file mode 100644
index 00000000..d621ac0a
--- /dev/null
+++ b/norminette/locale/en_US/LC_MESSAGES/norminette.po
@@ -0,0 +1,571 @@
+# English translations for PACKAGE package.
+# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Automatically generated, 2025.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: 3.3.59\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2025-04-11 10:12-0300\n"
+"PO-Revision-Date: 2025-04-09 17:51-0300\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: en_US\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: norminette/norm_error.py:4
+msgid "Spaces at beginning of line"
+msgstr ""
+
+#: norminette/norm_error.py:5 norminette/norm_error.py:35
+msgid "Found tab when expecting space"
+msgstr ""
+
+#: norminette/norm_error.py:6
+msgid "Two or more consecutives spaces"
+msgstr ""
+
+#: norminette/norm_error.py:7
+msgid "Two or more consecutives white spaces"
+msgstr ""
+
+#: norminette/norm_error.py:8
+msgid "missing space before operator"
+msgstr ""
+
+#: norminette/norm_error.py:9
+msgid "missing space after operator"
+msgstr ""
+
+#: norminette/norm_error.py:10
+msgid "extra space before operator"
+msgstr ""
+
+#: norminette/norm_error.py:11
+msgid "extra space after operator"
+msgstr ""
+
+#: norminette/norm_error.py:12
+msgid "Missing space after parenthesis (brace/bracket)"
+msgstr ""
+
+#: norminette/norm_error.py:13
+msgid "Missing space before parenthesis (brace/bracket)"
+msgstr ""
+
+#: norminette/norm_error.py:14
+msgid "Extra space after parenthesis (brace/bracket)"
+msgstr ""
+
+#: norminette/norm_error.py:15
+msgid "Extra space before parenthesis (brace/bracket)"
+msgstr ""
+
+#: norminette/norm_error.py:16
+msgid "space after pointer"
+msgstr ""
+
+#: norminette/norm_error.py:17
+msgid "Unexpected space/tab at line start"
+msgstr ""
+
+#: norminette/norm_error.py:18
+msgid "bad spacing before pointer"
+msgstr ""
+
+#: norminette/norm_error.py:19
+msgid "Found space when expecting tab before function name"
+msgstr ""
+
+#: norminette/norm_error.py:20
+msgid "extra tabs before function name"
+msgstr ""
+
+#: norminette/norm_error.py:21
+msgid "extra tabs before typedef name"
+msgstr ""
+
+#: norminette/norm_error.py:22
+msgid "missing tab before function name"
+msgstr ""
+
+#: norminette/norm_error.py:23
+msgid "missing tab before variable name"
+msgstr ""
+
+#: norminette/norm_error.py:24
+msgid "Missing tab before typedef name"
+msgstr ""
+
+#: norminette/norm_error.py:25
+msgid "extra tab before variable name"
+msgstr ""
+
+#: norminette/norm_error.py:26
+msgid "line too long"
+msgstr ""
+
+#: norminette/norm_error.py:27
+msgid "Expected parenthesis"
+msgstr ""
+
+#: norminette/norm_error.py:28
+msgid "missing type qualifier or identifier in function arguments"
+msgstr ""
+
+#: norminette/norm_error.py:29
+msgid ""
+"user defined identifiers should contain only lowercase characters, digits or "
+"'_'"
+msgstr ""
+
+#: norminette/norm_error.py:31
+msgid "Missing tabs for indent level"
+msgstr ""
+
+#: norminette/norm_error.py:32
+msgid "Extra tabs for indent level"
+msgstr ""
+
+#: norminette/norm_error.py:33
+msgid "Extra whitespaces for indent level"
+msgstr ""
+
+#: norminette/norm_error.py:34
+msgid "Found space when expecting tab"
+msgstr ""
+
+#: norminette/norm_error.py:36
+msgid "Function has more than 25 lines"
+msgstr ""
+
+#: norminette/norm_error.py:37
+msgid "Space on empty line"
+msgstr ""
+
+#: norminette/norm_error.py:38
+msgid "Space before newline"
+msgstr ""
+
+#: norminette/norm_error.py:39
+msgid "Too many instructions on a single line"
+msgstr ""
+
+#: norminette/norm_error.py:40
+msgid "Missing space after preprocessor directive"
+msgstr ""
+
+#: norminette/norm_error.py:41
+msgid "Unrecognized preprocessor statement"
+msgstr ""
+
+#: norminette/norm_error.py:42
+msgid "Preprocessor statement not at the beginning of the line"
+msgstr ""
+
+#: norminette/norm_error.py:43
+msgid "Preprocessor statement must only contain constant defines"
+msgstr ""
+
+#: norminette/norm_error.py:44
+msgid "Expected EOL after preprocessor statement"
+msgstr ""
+
+#: norminette/norm_error.py:45
+msgid "If preprocessor statement without endif"
+msgstr ""
+
+#: norminette/norm_error.py:46
+msgid "Elif preprocessor statement without if or elif"
+msgstr ""
+
+#: norminette/norm_error.py:47
+msgid "Ifdef preprocessor statement without endif"
+msgstr ""
+
+#: norminette/norm_error.py:48
+msgid "Ifndef preprocessor statement without endif"
+msgstr ""
+
+#: norminette/norm_error.py:49
+msgid "Else preprocessor statement without if or elif"
+msgstr ""
+
+#: norminette/norm_error.py:50
+msgid "Endif preprocessor statement without if, elif or else"
+msgstr ""
+
+#: norminette/norm_error.py:51
+msgid "Bad preprocessor indentation"
+msgstr ""
+
+#: norminette/norm_error.py:52
+msgid "Multiline preprocessor statement is forbidden"
+msgstr ""
+
+#: norminette/norm_error.py:53
+msgid "Preprocessor statements are only allowed in the global scope"
+msgstr ""
+
+#: norminette/norm_error.py:54
+msgid "User defined typedef must start with t_"
+msgstr ""
+
+#: norminette/norm_error.py:55
+msgid "Structure name must start with s_"
+msgstr ""
+
+#: norminette/norm_error.py:56
+msgid "Enum name must start with e_"
+msgstr ""
+
+#: norminette/norm_error.py:57
+msgid "Union name must start with u_"
+msgstr ""
+
+#: norminette/norm_error.py:58
+msgid "Global variable must start with g_"
+msgstr ""
+
+#: norminette/norm_error.py:59
+msgid "Missing whitespace before typedef name"
+msgstr ""
+
+#: norminette/norm_error.py:60
+msgid "Global variable present in file. Make sure it is a reasonable choice."
+msgstr ""
+
+#: norminette/norm_error.py:61
+msgid "Logic operator at the end of line"
+msgstr ""
+
+#: norminette/norm_error.py:62
+msgid "Empty line at start of file"
+msgstr ""
+
+#: norminette/norm_error.py:63
+msgid "Empty line in function"
+msgstr ""
+
+#: norminette/norm_error.py:64
+msgid "Empty line at end of file"
+msgstr ""
+
+#: norminette/norm_error.py:65
+msgid "Variable declared in incorrect scope"
+msgstr ""
+
+#: norminette/norm_error.py:66
+msgid "Missing type in variable declaration"
+msgstr ""
+
+#: norminette/norm_error.py:67
+msgid "Variable declaration not at start of function"
+msgstr ""
+
+#: norminette/norm_error.py:68
+msgid "Too many variables declarations in a function"
+msgstr ""
+
+#: norminette/norm_error.py:69
+msgid "Too many functions in file"
+msgstr ""
+
+#: norminette/norm_error.py:70
+msgid "Expected newline after brace"
+msgstr ""
+
+#: norminette/norm_error.py:71
+msgid "Consecutive newlines"
+msgstr ""
+
+#: norminette/norm_error.py:72
+msgid "Functions must be separated by a newline"
+msgstr ""
+
+#: norminette/norm_error.py:73
+msgid "Variable declarations must be followed by a newline"
+msgstr ""
+
+#: norminette/norm_error.py:74
+msgid "Preprocessor statement must be followed by a newline"
+msgstr ""
+
+#: norminette/norm_error.py:75
+msgid "Multiple assignations on a single line"
+msgstr ""
+
+#: norminette/norm_error.py:76
+msgid "Multiple declarations on a single line"
+msgstr ""
+
+#: norminette/norm_error.py:77
+msgid "Declaration and assignation on a single line"
+msgstr ""
+
+#: norminette/norm_error.py:78
+msgid "Forbidden control structure"
+msgstr ""
+
+#: norminette/norm_error.py:79
+msgid "Missing space after keyword"
+msgstr ""
+
+#: norminette/norm_error.py:80
+msgid "Return value must be in parenthesis"
+msgstr "Return value must be in parenthesis"
+
+#: norminette/norm_error.py:81
+msgid "Expected semicolon"
+msgstr ""
+
+#: norminette/norm_error.py:82
+msgid "Expected tab"
+msgstr ""
+
+#: norminette/norm_error.py:83
+msgid "Empty function argument requires void"
+msgstr ""
+
+#: norminette/norm_error.py:84
+msgid "Misaligned variable declaration"
+msgstr ""
+
+#: norminette/norm_error.py:85
+msgid "Misaligned function declaration"
+msgstr ""
+
+#: norminette/norm_error.py:86
+msgid "Comment is invalid in this scope"
+msgstr ""
+
+#: norminette/norm_error.py:87
+msgid "Macro name must be capitalized"
+msgstr ""
+
+#: norminette/norm_error.py:88
+msgid "Macro functions are forbidden"
+msgstr ""
+
+#: norminette/norm_error.py:89
+msgid "Assignment in control structure"
+msgstr ""
+
+#: norminette/norm_error.py:90
+msgid "Variable length array forbidden"
+msgstr ""
+
+#: norminette/norm_error.py:91
+msgid "Function has more than 4 arguments"
+msgstr ""
+
+#: norminette/norm_error.py:92
+msgid ".c file includes are forbidden"
+msgstr ""
+
+#: norminette/norm_error.py:93
+msgid "Include must be at the start of file"
+msgstr ""
+
+#: norminette/norm_error.py:94
+msgid "Header protection must include all the instructions"
+msgstr ""
+
+#: norminette/norm_error.py:95
+msgid "Instructions after header protection are forbidden"
+msgstr ""
+
+#: norminette/norm_error.py:96
+msgid "Wrong header protection name"
+msgstr ""
+
+#: norminette/norm_error.py:97
+msgid "Header protection must be in uppercase"
+msgstr ""
+
+#: norminette/norm_error.py:98
+msgid "Multiple header protections, only one is allowed"
+msgstr ""
+
+#: norminette/norm_error.py:99
+msgid "Header protection not containing #define"
+msgstr ""
+
+#: norminette/norm_error.py:100
+msgid "Ternaries are forbidden"
+msgstr ""
+
+#: norminette/norm_error.py:101
+msgid "Too many values on define"
+msgstr ""
+
+#: norminette/norm_error.py:102
+msgid "Newline in declaration"
+msgstr ""
+
+#: norminette/norm_error.py:103
+msgid "Multiple instructions in single line control structure"
+msgstr ""
+
+#: norminette/norm_error.py:104
+msgid "Newline in define"
+msgstr ""
+
+#: norminette/norm_error.py:105
+msgid "Missing identifier in typedef declaration"
+msgstr ""
+
+#: norminette/norm_error.py:106
+msgid "Label statements are forbidden"
+msgstr ""
+
+#: norminette/norm_error.py:107
+msgid "Goto statements are forbidden"
+msgstr ""
+
+#: norminette/norm_error.py:108
+msgid "Preprocessors can only be used in the global scope"
+msgstr ""
+
+#: norminette/norm_error.py:109
+msgid "Function prototype in incorrect scope"
+msgstr ""
+
+#: norminette/norm_error.py:110
+msgid "Statement is in incorrect scope"
+msgstr ""
+
+#: norminette/norm_error.py:111
+msgid "Incorrect values in define"
+msgstr ""
+
+#: norminette/norm_error.py:112
+msgid "Expected newline before brace"
+msgstr ""
+
+#: norminette/norm_error.py:113
+msgid "Expected newline after control structure"
+msgstr ""
+
+#: norminette/norm_error.py:114
+msgid "Unrecognized variable type"
+msgstr ""
+
+#: norminette/norm_error.py:115
+msgid "Comment must be on its own line or at end of a line"
+msgstr ""
+
+#: norminette/norm_error.py:116
+msgid "Comma at line start"
+msgstr ""
+
+#: norminette/norm_error.py:117
+msgid "Mixed spaces and tabs"
+msgstr ""
+
+#: norminette/norm_error.py:118
+msgid "Function attribute must be at the end of line"
+msgstr ""
+
+#: norminette/norm_error.py:119
+msgid "Missing or invalid 42 header"
+msgstr ""
+
+#: norminette/norm_error.py:120
+msgid "Missing space between include and filename"
+msgstr ""
+
+#: norminette/norm_error.py:121
+msgid "Enums, structs and unions need to be defined only in global scope"
+msgstr ""
+
+#: norminette/norm_error.py:122
+msgid "Typedef declaration are not allowed in .c files"
+msgstr ""
+
+#: norminette/norm_error.py:123
+msgid "Struct declaration are not allowed in .c files"
+msgstr ""
+
+#: norminette/norm_error.py:124
+msgid "Union declaration are not allowed in .c files"
+msgstr ""
+
+#: norminette/norm_error.py:125
+msgid "Enum declaration are not allowed in .c files"
+msgstr ""
+
+#: norminette/norm_error.py:126
+msgid "Unexpected end of file (EOF) while parsing a char"
+msgstr ""
+
+#: norminette/norm_error.py:127
+msgid "Unexpected end of line (EOL) while parsing a char"
+msgstr ""
+
+#: norminette/norm_error.py:128
+msgid "Unexpected end of file (EOF) while parsing a multiline comment"
+msgstr ""
+
+#: norminette/norm_error.py:129
+msgid "Unexpected end of file (EOF) while parsing a string"
+msgstr ""
+
+#: norminette/norm_error.py:130
+msgid "Empty character constant"
+msgstr ""
+
+#: norminette/norm_error.py:131
+msgid "Character constants can have only one character"
+msgstr ""
+
+#: norminette/norm_error.py:132
+msgid "This suffix is invalid"
+msgstr ""
+
+#: norminette/norm_error.py:133
+msgid "Invalid suffix for float/double literal constant"
+msgstr ""
+
+#: norminette/norm_error.py:134
+msgid "Invalid binary integer literal"
+msgstr ""
+
+#: norminette/norm_error.py:135
+msgid "Invalid octal integer literal"
+msgstr ""
+
+#: norminette/norm_error.py:136
+msgid "Invalid hexadecimal integer literal"
+msgstr ""
+
+#: norminette/norm_error.py:137
+msgid "Potential maximal munch detected"
+msgstr ""
+
+#: norminette/norm_error.py:138
+msgid "No hexadecimal digits followed by the \\x"
+msgstr ""
+
+#: norminette/norm_error.py:139
+msgid "Unknown escape sequence"
+msgstr ""
+
+#: norminette/norm_error.py:140
+msgid "Exponent has no digits"
+msgstr ""
+
+#: norminette/norm_error.py:141
+msgid "Multiple dots in float constant"
+msgstr ""
+
+#: norminette/norm_error.py:142
+msgid "Multiple 'x' in hexadecimal float constant"
+msgstr ""
+
+#~ msgid "Hello, World!"
+#~ msgstr "Hello, World!"
diff --git a/norminette/locale/pt_BR/LC_MESSAGES/norminette.po b/norminette/locale/pt_BR/LC_MESSAGES/norminette.po
new file mode 100644
index 00000000..af0cc0a4
--- /dev/null
+++ b/norminette/locale/pt_BR/LC_MESSAGES/norminette.po
@@ -0,0 +1,576 @@
+# Portuguese translations for PACKAGE package.
+# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Automatically generated, 2025.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: 3.3.59\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2025-04-11 10:12-0300\n"
+"PO-Revision-Date: 2025-04-09 17:51-0300\n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: norminette/norm_error.py:4
+msgid "Spaces at beginning of line"
+msgstr "Espaços no começo da linha"
+
+#: norminette/norm_error.py:5 norminette/norm_error.py:35
+msgid "Found tab when expecting space"
+msgstr "Encontrado tabulação ao invés espaço"
+
+#: norminette/norm_error.py:6
+msgid "Two or more consecutives spaces"
+msgstr "Dois ou mais espaços consecutivos"
+
+#: norminette/norm_error.py:7
+msgid "Two or more consecutives white spaces"
+msgstr "Dois ou mais espaços em branco consecutivos"
+
+#: norminette/norm_error.py:8
+msgid "missing space before operator"
+msgstr "Faltando espaço antes do operador"
+
+#: norminette/norm_error.py:9
+msgid "missing space after operator"
+msgstr "Faltando espaço após o operador"
+
+#: norminette/norm_error.py:10
+msgid "extra space before operator"
+msgstr "Espaço a mais antes do operador"
+
+#: norminette/norm_error.py:11
+msgid "extra space after operator"
+msgstr "Espaço a mais depois do operador"
+
+#: norminette/norm_error.py:12
+msgid "Missing space after parenthesis (brace/bracket)"
+msgstr "Faltando espaço após parêntese (chave/colchete)"
+
+#: norminette/norm_error.py:13
+msgid "Missing space before parenthesis (brace/bracket)"
+msgstr "Faltando espaço antes de parêntese (chave/colchete)"
+
+#: norminette/norm_error.py:14
+msgid "Extra space after parenthesis (brace/bracket)"
+msgstr "Espaço extra após parêntese (chave/colchete)"
+
+#: norminette/norm_error.py:15
+msgid "Extra space before parenthesis (brace/bracket)"
+msgstr "Espaço extra antes de parêntese (chave/colchete)"
+
+#: norminette/norm_error.py:16
+msgid "space after pointer"
+msgstr "Espaço após ponteiro"
+
+#: norminette/norm_error.py:17
+msgid "Unexpected space/tab at line start"
+msgstr "Espaço/tabulação inesperado no início da linha"
+
+#: norminette/norm_error.py:18
+msgid "bad spacing before pointer"
+msgstr "Espaçamento incorreto antes do ponteiro"
+
+#: norminette/norm_error.py:19
+msgid "Found space when expecting tab before function name"
+msgstr "Espaço encontrado onde era esperada tabulação antes do nome da função"
+
+#: norminette/norm_error.py:20
+msgid "extra tabs before function name"
+msgstr "Tabulações extras antes do nome da função"
+
+#: norminette/norm_error.py:21
+msgid "extra tabs before typedef name"
+msgstr "Tabulações extras antes do nome do typedef"
+
+#: norminette/norm_error.py:22
+msgid "missing tab before function name"
+msgstr "Faltando tabulação antes do nome da função"
+
+#: norminette/norm_error.py:23
+msgid "missing tab before variable name"
+msgstr "Faltando tabulação antes do nome da variável"
+
+#: norminette/norm_error.py:24
+msgid "Missing tab before typedef name"
+msgstr "Faltando tabulação antes do nome do typedef"
+
+#: norminette/norm_error.py:25
+msgid "extra tab before variable name"
+msgstr "Tabulação extra antes do nome da variável"
+
+#: norminette/norm_error.py:26
+msgid "line too long"
+msgstr "Linha muito longa"
+
+#: norminette/norm_error.py:27
+msgid "Expected parenthesis"
+msgstr "Parêntese esperado"
+
+#: norminette/norm_error.py:28
+msgid "missing type qualifier or identifier in function arguments"
+msgstr ""
+"Faltando qualificador de tipo ou identificador nos argumentos da função"
+
+#: norminette/norm_error.py:29
+msgid ""
+"user defined identifiers should contain only lowercase characters, digits or "
+"'_'"
+msgstr ""
+"Identificadores definidos pelo usuário devem conter apenas letras "
+"minúsculas, dígitos ou '_'"
+
+#: norminette/norm_error.py:31
+msgid "Missing tabs for indent level"
+msgstr "Faltando tabulações para o nível de indentação"
+
+#: norminette/norm_error.py:32
+msgid "Extra tabs for indent level"
+msgstr "Tabulações extras para o nível de indentação"
+
+#: norminette/norm_error.py:33
+msgid "Extra whitespaces for indent level"
+msgstr "Espaços extras para o nível de indentação"
+
+#: norminette/norm_error.py:34
+msgid "Found space when expecting tab"
+msgstr "Espaço encontrado onde era esperado tabulação"
+
+#: norminette/norm_error.py:36
+msgid "Function has more than 25 lines"
+msgstr "A função tem mais de 25 linhas"
+
+#: norminette/norm_error.py:37
+msgid "Space on empty line"
+msgstr "Espaço em linha vazia"
+
+#: norminette/norm_error.py:38
+msgid "Space before newline"
+msgstr "Espaço antes da nova linha"
+
+#: norminette/norm_error.py:39
+msgid "Too many instructions on a single line"
+msgstr "Muitas instruções em uma única linha"
+
+#: norminette/norm_error.py:40
+msgid "Missing space after preprocessor directive"
+msgstr "Faltando espaço após a diretiva do pré-processador"
+
+#: norminette/norm_error.py:41
+msgid "Unrecognized preprocessor statement"
+msgstr "Declaração de pré-processador não reconhecida"
+
+#: norminette/norm_error.py:42
+msgid "Preprocessor statement not at the beginning of the line"
+msgstr "Declaração de pré-processador não está no início da linha"
+
+#: norminette/norm_error.py:43
+msgid "Preprocessor statement must only contain constant defines"
+msgstr "Declaração de pré-processador deve conter apenas constantes definidas"
+
+#: norminette/norm_error.py:44
+msgid "Expected EOL after preprocessor statement"
+msgstr "Esperado fim de linha após a declaração do pré-processador"
+
+#: norminette/norm_error.py:45
+msgid "If preprocessor statement without endif"
+msgstr "Declaração de pré-processador 'if' sem 'endif'"
+
+#: norminette/norm_error.py:46
+msgid "Elif preprocessor statement without if or elif"
+msgstr "Declaração de pré-processador 'elif' sem 'if' ou 'elif'"
+
+#: norminette/norm_error.py:47
+msgid "Ifdef preprocessor statement without endif"
+msgstr "Declaração de pré-processador 'ifdef' sem 'endif'"
+
+#: norminette/norm_error.py:48
+msgid "Ifndef preprocessor statement without endif"
+msgstr "Declaração de pré-processador 'ifndef' sem 'endif'"
+
+#: norminette/norm_error.py:49
+msgid "Else preprocessor statement without if or elif"
+msgstr "Declaração de pré-processador 'else' sem 'if' ou 'elif'"
+
+#: norminette/norm_error.py:50
+msgid "Endif preprocessor statement without if, elif or else"
+msgstr "Declaração de pré-processador 'endif' sem 'if', 'elif' ou 'else'"
+
+#: norminette/norm_error.py:51
+msgid "Bad preprocessor indentation"
+msgstr "Indentação incorreta no pré-processador"
+
+#: norminette/norm_error.py:52
+msgid "Multiline preprocessor statement is forbidden"
+msgstr "Declaração de pré-processador em múltiplas linhas é proibida"
+
+#: norminette/norm_error.py:53
+msgid "Preprocessor statements are only allowed in the global scope"
+msgstr "Declarações de pré-processador são permitidas apenas no escopo global"
+
+#: norminette/norm_error.py:54
+msgid "User defined typedef must start with t_"
+msgstr "Typedef definido pelo usuário deve começar com t_"
+
+#: norminette/norm_error.py:55
+msgid "Structure name must start with s_"
+msgstr "Nome da estrutura deve começar com s_"
+
+#: norminette/norm_error.py:56
+msgid "Enum name must start with e_"
+msgstr "Nome do enum deve começar com e_"
+
+#: norminette/norm_error.py:57
+msgid "Union name must start with u_"
+msgstr "Nome da união deve começar com u_"
+
+#: norminette/norm_error.py:58
+msgid "Global variable must start with g_"
+msgstr "Variável global deve começar com g_"
+
+#: norminette/norm_error.py:59
+msgid "Missing whitespace before typedef name"
+msgstr "Espaço em branco faltando antes do nome do typedef"
+
+#: norminette/norm_error.py:60
+msgid "Global variable present in file. Make sure it is a reasonable choice."
+msgstr ""
+"Variável global presente no arquivo. Certifique-se de que é uma escolha "
+"razoável."
+
+#: norminette/norm_error.py:61
+msgid "Logic operator at the end of line"
+msgstr "Operador lógico no final da linha"
+
+#: norminette/norm_error.py:62
+msgid "Empty line at start of file"
+msgstr "Linha vazia no início do arquivo"
+
+#: norminette/norm_error.py:63
+msgid "Empty line in function"
+msgstr "Linha vazia dentro da função"
+
+#: norminette/norm_error.py:64
+msgid "Empty line at end of file"
+msgstr "Linha vazia no final do arquivo"
+
+#: norminette/norm_error.py:65
+msgid "Variable declared in incorrect scope"
+msgstr "Variável declarada no escopo incorreto"
+
+#: norminette/norm_error.py:66
+msgid "Missing type in variable declaration"
+msgstr "Faltando tipo na declaração da variável"
+
+#: norminette/norm_error.py:67
+msgid "Variable declaration not at start of function"
+msgstr "Declaração de variável não está no início da função"
+
+#: norminette/norm_error.py:68
+msgid "Too many variables declarations in a function"
+msgstr "Muitas declarações de variáveis em uma função"
+
+#: norminette/norm_error.py:69
+msgid "Too many functions in file"
+msgstr "Muitas funções no arquivo"
+
+#: norminette/norm_error.py:70
+msgid "Expected newline after brace"
+msgstr "Esperada nova linha após a chave"
+
+#: norminette/norm_error.py:71
+msgid "Consecutive newlines"
+msgstr "Novas linhas consecutivas"
+
+#: norminette/norm_error.py:72
+msgid "Functions must be separated by a newline"
+msgstr "Funções devem ser separadas por uma linha em branco"
+
+#: norminette/norm_error.py:73
+msgid "Variable declarations must be followed by a newline"
+msgstr "Declarações de variáveis devem ser seguidas por uma nova linha"
+
+#: norminette/norm_error.py:74
+msgid "Preprocessor statement must be followed by a newline"
+msgstr "Declaração de pré-processador deve ser seguida por uma nova linha"
+
+#: norminette/norm_error.py:75
+msgid "Multiple assignations on a single line"
+msgstr "Múltiplas atribuições em uma única linha"
+
+#: norminette/norm_error.py:76
+msgid "Multiple declarations on a single line"
+msgstr "Múltiplas declarações em uma única linha"
+
+#: norminette/norm_error.py:77
+msgid "Declaration and assignation on a single line"
+msgstr "Declaração e atribuição na mesma linha"
+
+#: norminette/norm_error.py:78
+msgid "Forbidden control structure"
+msgstr "Estrutura de controle proibida"
+
+#: norminette/norm_error.py:79
+msgid "Missing space after keyword"
+msgstr "Faltando espaço após a palavra-chave"
+
+#: norminette/norm_error.py:80
+msgid "Return value must be in parenthesis"
+msgstr ""
+
+#: norminette/norm_error.py:81
+msgid "Expected semicolon"
+msgstr "Esperado ponto e vírgula"
+
+#: norminette/norm_error.py:82
+msgid "Expected tab"
+msgstr "Tabulação esperada"
+
+#: norminette/norm_error.py:83
+msgid "Empty function argument requires void"
+msgstr "Argumento de função vazio requer void"
+
+#: norminette/norm_error.py:84
+msgid "Misaligned variable declaration"
+msgstr "Declaração de variável desalinhada"
+
+#: norminette/norm_error.py:85
+msgid "Misaligned function declaration"
+msgstr "Declaração de função desalinhada"
+
+#: norminette/norm_error.py:86
+msgid "Comment is invalid in this scope"
+msgstr "Comentário inválido neste escopo"
+
+#: norminette/norm_error.py:87
+msgid "Macro name must be capitalized"
+msgstr "Nome de macro deve estar em letras maiúsculas"
+
+#: norminette/norm_error.py:88
+msgid "Macro functions are forbidden"
+msgstr "Funções de macro são proibidas"
+
+#: norminette/norm_error.py:89
+msgid "Assignment in control structure"
+msgstr "Atribuição dentro de estrutura de controle"
+
+#: norminette/norm_error.py:90
+msgid "Variable length array forbidden"
+msgstr "Arrays de tamanho variável são proibidos"
+
+#: norminette/norm_error.py:91
+msgid "Function has more than 4 arguments"
+msgstr "A função possui mais de 4 argumentos"
+
+#: norminette/norm_error.py:92
+msgid ".c file includes are forbidden"
+msgstr "Inclusões de arquivos .c são proibidas"
+
+#: norminette/norm_error.py:93
+msgid "Include must be at the start of file"
+msgstr "Instruções de include devem estar no início do arquivo"
+
+#: norminette/norm_error.py:94
+msgid "Header protection must include all the instructions"
+msgstr "A proteção de cabeçalho deve englobar todas as instruções"
+
+#: norminette/norm_error.py:95
+msgid "Instructions after header protection are forbidden"
+msgstr "Instruções após a proteção de cabeçalho são proibidas"
+
+#: norminette/norm_error.py:96
+msgid "Wrong header protection name"
+msgstr "Nome da proteção de cabeçalho incorreto"
+
+#: norminette/norm_error.py:97
+msgid "Header protection must be in uppercase"
+msgstr "A proteção de cabeçalho deve estar em letras maiúsculas"
+
+#: norminette/norm_error.py:98
+msgid "Multiple header protections, only one is allowed"
+msgstr "Múltiplas proteções de cabeçalho — apenas uma é permitida"
+
+#: norminette/norm_error.py:99
+msgid "Header protection not containing #define"
+msgstr "Proteção de cabeçalho não contém #define"
+
+#: norminette/norm_error.py:100
+msgid "Ternaries are forbidden"
+msgstr "Operadores ternários são proibidos"
+
+#: norminette/norm_error.py:101
+msgid "Too many values on define"
+msgstr "Muitos valores em uma diretiva define"
+
+#: norminette/norm_error.py:102
+msgid "Newline in declaration"
+msgstr "Nova linha na declaração"
+
+#: norminette/norm_error.py:103
+msgid "Multiple instructions in single line control structure"
+msgstr "Múltiplas instruções em uma única linha de estrutura de controle"
+
+#: norminette/norm_error.py:104
+msgid "Newline in define"
+msgstr "Nova linha em uma diretiva define"
+
+#: norminette/norm_error.py:105
+msgid "Missing identifier in typedef declaration"
+msgstr "Identificador ausente na declaração de typedef"
+
+#: norminette/norm_error.py:106
+msgid "Label statements are forbidden"
+msgstr "Declarações de rótulo (label) são proibidas"
+
+#: norminette/norm_error.py:107
+msgid "Goto statements are forbidden"
+msgstr "Declarações goto são proibidas"
+
+#: norminette/norm_error.py:108
+msgid "Preprocessors can only be used in the global scope"
+msgstr "Pré-processadores só podem ser usados no escopo global"
+
+#: norminette/norm_error.py:109
+msgid "Function prototype in incorrect scope"
+msgstr "Protótipo de função em escopo incorreto"
+
+#: norminette/norm_error.py:110
+msgid "Statement is in incorrect scope"
+msgstr "Instrução está em escopo incorreto"
+
+#: norminette/norm_error.py:111
+msgid "Incorrect values in define"
+msgstr "Valores incorretos em uma diretiva define"
+
+#: norminette/norm_error.py:112
+msgid "Expected newline before brace"
+msgstr "Esperada nova linha antes da chave"
+
+#: norminette/norm_error.py:113
+msgid "Expected newline after control structure"
+msgstr "Esperada nova linha após a estrutura de controle"
+
+#: norminette/norm_error.py:114
+msgid "Unrecognized variable type"
+msgstr "Tipo de variável não reconhecido"
+
+#: norminette/norm_error.py:115
+msgid "Comment must be on its own line or at end of a line"
+msgstr "Comentário deve estar em sua própria linha ou ao fim de uma linha"
+
+#: norminette/norm_error.py:116
+msgid "Comma at line start"
+msgstr "Vírgula no início da linha"
+
+#: norminette/norm_error.py:117
+msgid "Mixed spaces and tabs"
+msgstr "Mistura de espaços e tabulações"
+
+#: norminette/norm_error.py:118
+msgid "Function attribute must be at the end of line"
+msgstr "Atributo da função deve estar no final da linha"
+
+#: norminette/norm_error.py:119
+msgid "Missing or invalid 42 header"
+msgstr "Cabeçalho 42 ausente ou inválido"
+
+#: norminette/norm_error.py:120
+msgid "Missing space between include and filename"
+msgstr "Faltando espaço entre include e nome do arquivo"
+
+#: norminette/norm_error.py:121
+msgid "Enums, structs and unions need to be defined only in global scope"
+msgstr "Enums, structs e unions devem ser definidos apenas no escopo global"
+
+#: norminette/norm_error.py:122
+msgid "Typedef declaration are not allowed in .c files"
+msgstr "Declarações typedef não são permitidas em arquivos .c"
+
+#: norminette/norm_error.py:123
+msgid "Struct declaration are not allowed in .c files"
+msgstr "Declarações struct não são permitidas em arquivos .c"
+
+#: norminette/norm_error.py:124
+msgid "Union declaration are not allowed in .c files"
+msgstr "Declarações union não são permitidas em arquivos .c"
+
+#: norminette/norm_error.py:125
+msgid "Enum declaration are not allowed in .c files"
+msgstr "Declarações enum não são permitidas em arquivos .c"
+
+#: norminette/norm_error.py:126
+msgid "Unexpected end of file (EOF) while parsing a char"
+msgstr "Fim de arquivo inesperado (EOF) ao analisar um caractere"
+
+#: norminette/norm_error.py:127
+msgid "Unexpected end of line (EOL) while parsing a char"
+msgstr "Fim de linha inesperado (EOL) ao analisar um caractere"
+
+#: norminette/norm_error.py:128
+msgid "Unexpected end of file (EOF) while parsing a multiline comment"
+msgstr "Fim de arquivo inesperado (EOF) ao analisar um comentário multilinha"
+
+#: norminette/norm_error.py:129
+msgid "Unexpected end of file (EOF) while parsing a string"
+msgstr "Fim de arquivo inesperado (EOF) ao analisar uma string"
+
+#: norminette/norm_error.py:130
+msgid "Empty character constant"
+msgstr "Constante de caractere vazia"
+
+#: norminette/norm_error.py:131
+msgid "Character constants can have only one character"
+msgstr "Constantes de caractere devem conter apenas um caractere"
+
+#: norminette/norm_error.py:132
+msgid "This suffix is invalid"
+msgstr "Esse sufixo é inválido"
+
+#: norminette/norm_error.py:133
+msgid "Invalid suffix for float/double literal constant"
+msgstr "Sufixo inválido para constante literal float/double"
+
+#: norminette/norm_error.py:134
+msgid "Invalid binary integer literal"
+msgstr "Literal inteiro binário inválido"
+
+#: norminette/norm_error.py:135
+msgid "Invalid octal integer literal"
+msgstr "Literal inteiro octal inválido"
+
+#: norminette/norm_error.py:136
+msgid "Invalid hexadecimal integer literal"
+msgstr "Literal inteiro hexadecimal inválido"
+
+#: norminette/norm_error.py:137
+msgid "Potential maximal munch detected"
+msgstr "Possível ocorrência de 'maximal munch' detectada"
+
+#: norminette/norm_error.py:138
+msgid "No hexadecimal digits followed by the \\x"
+msgstr "Nenhum dígito hexadecimal após o \\x"
+
+#: norminette/norm_error.py:139
+msgid "Unknown escape sequence"
+msgstr "Sequência de escape desconhecida"
+
+#: norminette/norm_error.py:140
+msgid "Exponent has no digits"
+msgstr "Expoente sem dígitos"
+
+#: norminette/norm_error.py:141
+msgid "Multiple dots in float constant"
+msgstr "Múltiplos pontos em constante float"
+
+#: norminette/norm_error.py:142
+msgid "Multiple 'x' in hexadecimal float constant"
+msgstr "Múltiplos 'x' em constante float hexadecimal"
+
+#~ msgid "space before function name"
+#~ msgstr "Espaço antes do nome da função"
diff --git a/norminette/norm_error.py b/norminette/norm_error.py
index 6abd1a52..07b75ab4 100644
--- a/norminette/norm_error.py
+++ b/norminette/norm_error.py
@@ -1,105 +1,143 @@
+from norminette.i18n import _
+
errors = {
- "SPC_INSTEAD_TAB": "Spaces at beginning of line",
- "TAB_INSTEAD_SPC": "Found tab when expecting space",
- "CONSECUTIVE_SPC": "Two or more consecutives spaces",
- "SPC_BFR_OPERATOR": "missing space before operator",
- "SPC_AFTER_OPERATOR": "missing space after operator",
- "NO_SPC_BFR_OPR": "extra space before operator",
- "NO_SPC_AFR_OPR": "extra space after operator",
- "SPC_AFTER_PAR": "Missing space after parenthesis (brace/bracket)",
- "SPC_BFR_PAR": "Missing space before parenthesis (brace/bracket)",
- "NO_SPC_AFR_PAR": "Extra space after parenthesis (brace/bracket)",
- "NO_SPC_BFR_PAR": "Extra space before parenthesis (brace/bracket)",
- "SPC_AFTER_POINTER": "space after pointer",
- "SPC_BFR_POINTER": "bad spacing before pointer",
- "SPACE_BEFORE_FUNC": "space before function name",
- "TOO_MANY_TABS_FUNC": "extra tabs before function name",
- "MISSING_TAB_FUNC": "missing tab before function name",
- "MISSING_TAB_VAR": "missing tab before variable name",
- "TOO_MANY_TAB_VAR": "extra tab before variable name",
- "LINE_TOO_LONG": "line too long",
- "EXP_PARENTHESIS": "Expected parenthesis",
- "MISSING_IDENTIFIER": "missing type qualifier or identifier in function arguments",
- "FORBIDDEN_CHAR_NAME": "user defined identifiers should contain only lowercase characters, \
-digits or '_'",
- "TOO_FEW_TAB": "Missing tabs for indent level",
- "TOO_MANY_TAB": "Extra tabs for indent level",
- "SPACE_REPLACE_TAB": "Found space when expecting tab",
- "TAB_REPLACE_SPACE": "Found tab when expecting space",
- "TOO_MANY_LINES": "Function has more than 25 lines",
- "SPACE_EMPTY_LINE": "Space on empty line",
- "SPC_BEFORE_NL": "Space before newline",
- "TOO_MANY_INSTR": "Too many instructions on a single line",
- "PREPROC_UKN_STATEMENT": "Unrecognized preprocessor statement",
- "PREPROC_START_LINE": "Preprocessor statement not at the beginning of the line",
- "PREPROC_CONSTANT": "Preprocessor statement must only contain constant defines",
- "PREPROC_EXPECTED_EOL": "Expected EOL after preprocessor statement",
- "PREPROC_BAD_INDENT": "Bad preprocessor indentation",
- "USER_DEFINED_TYPEDEF": "User defined typedef must start with t_",
- "STRUCT_TYPE_NAMING": "Structure name must start with s_",
- "ENUM_TYPE_NAMING": "Enum name must start with e_",
- "UNION_TYPE_NAMING": "Union name must start with u_",
- "GLOBAL_VAR_NAMING": "Global variable must start with g_",
- "EOL_OPERATOR": "Logic operator at the end of line",
- "EMPTY_LINE_FUNCTION": "Empty line in function",
- "EMPTY_LINE_FILE_START": "Empty line at start of file",
- "EMPTY_LINE_FUNCTION": "Empty line in function",
- "EMPTY_LINE_EOF": "Empty line at end of file",
- "WRONG_SCOPE_VAR": "Variable declared in incorrect scope",
- "VAR_DECL_START_FUNC": "Variable declaration not at start of function",
- "TOO_MANY_FUNCS": "Too many functions in file",
- "BRACE_SHOULD_EOL": "Expected newline after brace",
- "CONSECUTIVE_NEWLINES": "Consecutive newlines",
- "NEWLINE_PRECEDES_FUNC": "Functions must be separated by a newline",
- "NL_AFTER_VAR_DECL": "Variable declarations must be followed by a newline",
- "MULT_ASSIGN_LINE": "Multiple assignations on a single line",
- "MULT_DECL_LINE": "Multiple declarations on a single line",
- "DECL_ASSIGN_LINE": "Declaration and assignation on a single line",
- "FORBIDDEN_CS": "Forbidden control structure",
- "SPACE_AFTER_KW": "Missing space after keyword",
- "RETURN_PARENTHESIS": "Return value must be in parenthesis",
- "EXP_SEMI_COLON": "Expected semicolon",
- "EXP_TAB": "Expected tab",
- "NO_ARGS_VOID": "Empty function argument requires void",
- "MISALIGNED_VAR_DECL": "Misaligned variable declaration",
- "MISALIGNED_FUNC_DECL": "Misaligned function declaration",
- "WRONG_SCOPE_COMMENT": "Comment is invalid in this scope",
- "MACRO_NAME_CAPITAL": "Macro name must be capitalized",
- "ASSIGN_IN_CONTROL": "Assignment in control structure",
- "VLA_FORBIDDEN": "Variable length array forbidden",
- "TOO_MANY_ARGS": "Function has more than 4 arguments",
- "INCLUDE_HEADER_ONLY": ".c file includes are forbidden",
- "INCLUDE_START_FILE": "Include must be at the start of file",
- "HEADER_PROT_ALL": "Header protection must include all the instructions",
- "HEADER_PROT_NAME": "Wrong header protection name",
- "TERNARY_FBIDDEN": "Ternaries are forbidden",
- "TOO_MANY_VALS": "Too many values on define",
- "NEWLINE_IN_DECL": "Newline in declaration",
- "MULT_IN_SINGLE_INSTR": "Multiple instructions in single line control structure",
- "NEWLINE_DEFINE": "Newline in define",
- "MISSING_TYPEDEF_ID": "Missing identifier in typedef declaration",
- "LABEL_FBIDDEN": "Label statements are forbidden",
- "PREPROC_GLOBAL": "Preprocessors can only be used in the global scope",
- "WRONG_SCOPE_FCT": "Function prototype in incorrect scope",
- "WRONG_SCOPE": "Statement is in incorrect scope",
- "INCORRECT_DEFINE": "Incorrect values in define",
- "BRACE_NEWLINE": "Expected newline before brace",
- "EXP_NEWLINE": "Expected newline after control structure",
- "ARG_TYPE_UKN": "Unrecognized variable type",
- "COMMENT_ON_INSTR": "Comment must be on its own line",
- "COMMA_START_LINE": "Comma at line start"
+ "SPC_INSTEAD_TAB": _("Spaces at beginning of line"),
+ "TAB_INSTEAD_SPC": _("Found tab when expecting space"),
+ "CONSECUTIVE_SPC": _("Two or more consecutives spaces"),
+ "CONSECUTIVE_WS": _("Two or more consecutives white spaces"),
+ "SPC_BFR_OPERATOR": _("missing space before operator"),
+ "SPC_AFTER_OPERATOR": _("missing space after operator"),
+ "NO_SPC_BFR_OPR": _("extra space before operator"),
+ "NO_SPC_AFR_OPR": _("extra space after operator"),
+ "SPC_AFTER_PAR": _("Missing space after parenthesis (brace/bracket)"),
+ "SPC_BFR_PAR": _("Missing space before parenthesis (brace/bracket)"),
+ "NO_SPC_AFR_PAR": _("Extra space after parenthesis (brace/bracket)"),
+ "NO_SPC_BFR_PAR": _("Extra space before parenthesis (brace/bracket)"),
+ "SPC_AFTER_POINTER": _("space after pointer"),
+ "SPC_LINE_START": _("Unexpected space/tab at line start"),
+ "SPC_BFR_POINTER": _("bad spacing before pointer"),
+ "SPACE_BEFORE_FUNC": _("Found space when expecting tab before function name"),
+ "TOO_MANY_TABS_FUNC": _("extra tabs before function name"),
+ "TOO_MANY_TABS_TD": _("extra tabs before typedef name"),
+ "MISSING_TAB_FUNC": _("missing tab before function name"),
+ "MISSING_TAB_VAR": _("missing tab before variable name"),
+ "MISSING_TAB_TYPDEF": _("Missing tab before typedef name"),
+ "TOO_MANY_TAB_VAR": _("extra tab before variable name"),
+ "LINE_TOO_LONG": _("line too long"),
+ "EXP_PARENTHESIS": _("Expected parenthesis"),
+ "MISSING_IDENTIFIER": _("missing type qualifier or identifier in function arguments"),
+ "FORBIDDEN_CHAR_NAME": _("user defined identifiers should contain only lowercase characters, \
+digits or '_'"),
+ "TOO_FEW_TAB": _("Missing tabs for indent level"),
+ "TOO_MANY_TAB": _("Extra tabs for indent level"),
+ "TOO_MANY_WS": _("Extra whitespaces for indent level"),
+ "SPACE_REPLACE_TAB": _("Found space when expecting tab"),
+ "TAB_REPLACE_SPACE": _("Found tab when expecting space"),
+ "TOO_MANY_LINES": _("Function has more than 25 lines"),
+ "SPACE_EMPTY_LINE": _("Space on empty line"),
+ "SPC_BEFORE_NL": _("Space before newline"),
+ "TOO_MANY_INSTR": _("Too many instructions on a single line"),
+ "PREPROC_NO_SPACE": _("Missing space after preprocessor directive"),
+ "PREPROC_UKN_STATEMENT": _("Unrecognized preprocessor statement"),
+ "PREPROC_START_LINE": _("Preprocessor statement not at the beginning of the line"),
+ "PREPROC_CONSTANT": _("Preprocessor statement must only contain constant defines"),
+ "PREPROC_EXPECTED_EOL": _("Expected EOL after preprocessor statement"),
+ "PREPROC_BAD_IF": _("If preprocessor statement without endif"),
+ "PREPROC_BAD_ELIF": _("Elif preprocessor statement without if or elif"),
+ "PREPROC_BAD_IFDEF": _("Ifdef preprocessor statement without endif"),
+ "PREPROC_BAD_IFNDEF": _("Ifndef preprocessor statement without endif"),
+ "PREPROC_BAD_ELSE": _("Else preprocessor statement without if or elif"),
+ "PREPROC_BAD_ENDIF": _("Endif preprocessor statement without if, elif or else"),
+ "PREPROC_BAD_INDENT": _("Bad preprocessor indentation"),
+ "PREPROC_MULTLINE": _("Multiline preprocessor statement is forbidden"),
+ "PREPOC_ONLY_GLOBAL": _("Preprocessor statements are only allowed in the global scope"),
+ "USER_DEFINED_TYPEDEF": _("User defined typedef must start with t_"),
+ "STRUCT_TYPE_NAMING": _("Structure name must start with s_"),
+ "ENUM_TYPE_NAMING": _("Enum name must start with e_"),
+ "UNION_TYPE_NAMING": _("Union name must start with u_"),
+ "GLOBAL_VAR_NAMING": _("Global variable must start with g_"),
+ "NO_TAB_BF_TYPEDEF": _("Missing whitespace before typedef name"),
+ "GLOBAL_VAR_DETECTED": _("Global variable present in file. Make sure it is a reasonable choice."),
+ "EOL_OPERATOR": _("Logic operator at the end of line"),
+ "EMPTY_LINE_FILE_START": _("Empty line at start of file"),
+ "EMPTY_LINE_FUNCTION": _("Empty line in function"),
+ "EMPTY_LINE_EOF": _("Empty line at end of file"),
+ "WRONG_SCOPE_VAR": _("Variable declared in incorrect scope"),
+ "IMPLICIT_VAR_TYPE": _("Missing type in variable declaration"),
+ "VAR_DECL_START_FUNC": _("Variable declaration not at start of function"),
+ "TOO_MANY_VARS_FUNC": _("Too many variables declarations in a function"),
+ "TOO_MANY_FUNCS": _("Too many functions in file"),
+ "BRACE_SHOULD_EOL": _("Expected newline after brace"),
+ "CONSECUTIVE_NEWLINES": _("Consecutive newlines"),
+ "NEWLINE_PRECEDES_FUNC": _("Functions must be separated by a newline"),
+ "NL_AFTER_VAR_DECL": _("Variable declarations must be followed by a newline"),
+ "NL_AFTER_PREPROC": _("Preprocessor statement must be followed by a newline"),
+ "MULT_ASSIGN_LINE": _("Multiple assignations on a single line"),
+ "MULT_DECL_LINE": _("Multiple declarations on a single line"),
+ "DECL_ASSIGN_LINE": _("Declaration and assignation on a single line"),
+ "FORBIDDEN_CS": _("Forbidden control structure"),
+ "SPACE_AFTER_KW": _("Missing space after keyword"),
+ "RETURN_PARENTHESIS": _("Return value must be in parenthesis"),
+ "EXP_SEMI_COLON": _("Expected semicolon"),
+ "EXP_TAB": _("Expected tab"),
+ "NO_ARGS_VOID": _("Empty function argument requires void"),
+ "MISALIGNED_VAR_DECL": _("Misaligned variable declaration"),
+ "MISALIGNED_FUNC_DECL": _("Misaligned function declaration"),
+ "WRONG_SCOPE_COMMENT": _("Comment is invalid in this scope"),
+ "MACRO_NAME_CAPITAL": _("Macro name must be capitalized"),
+ "MACRO_FUNC_FORBIDDEN": _("Macro functions are forbidden"),
+ "ASSIGN_IN_CONTROL": _("Assignment in control structure"),
+ "VLA_FORBIDDEN": _("Variable length array forbidden"),
+ "TOO_MANY_ARGS": _("Function has more than 4 arguments"),
+ "INCLUDE_HEADER_ONLY": _(".c file includes are forbidden"),
+ "INCLUDE_START_FILE": _("Include must be at the start of file"),
+ "HEADER_PROT_ALL": _("Header protection must include all the instructions"),
+ "HEADER_PROT_ALL_AF": _("Instructions after header protection are forbidden"),
+ "HEADER_PROT_NAME": _("Wrong header protection name"),
+ "HEADER_PROT_UPPER": _("Header protection must be in uppercase"),
+ "HEADER_PROT_MULT": _("Multiple header protections, only one is allowed"),
+ "HEADER_PROT_NODEF": _("Header protection not containing #define"),
+ "TERNARY_FBIDDEN": _("Ternaries are forbidden"),
+ "TOO_MANY_VALS": _("Too many values on define"),
+ "NEWLINE_IN_DECL": _("Newline in declaration"),
+ "MULT_IN_SINGLE_INSTR": _("Multiple instructions in single line control structure"),
+ "NEWLINE_DEFINE": _("Newline in define"),
+ "MISSING_TYPEDEF_ID": _("Missing identifier in typedef declaration"),
+ "LABEL_FBIDDEN": _("Label statements are forbidden"),
+ "GOTO_FBIDDEN": _("Goto statements are forbidden"),
+ "PREPROC_GLOBAL": _("Preprocessors can only be used in the global scope"),
+ "WRONG_SCOPE_FCT": _("Function prototype in incorrect scope"),
+ "WRONG_SCOPE": _("Statement is in incorrect scope"),
+ "INCORRECT_DEFINE": _("Incorrect values in define"),
+ "BRACE_NEWLINE": _("Expected newline before brace"),
+ "EXP_NEWLINE": _("Expected newline after control structure"),
+ "ARG_TYPE_UKN": _("Unrecognized variable type"),
+ "COMMENT_ON_INSTR": _("Comment must be on its own line or at end of a line"),
+ "COMMA_START_LINE": _("Comma at line start"),
+ "MIXED_SPACE_TAB": _("Mixed spaces and tabs"),
+ "ATTR_EOL": _("Function attribute must be at the end of line"),
+ "INVALID_HEADER": _("Missing or invalid 42 header"),
+ "INCLUDE_MISSING_SP": _("Missing space between include and filename"),
+ "TYPE_NOT_GLOBAL": _("Enums, structs and unions need to be defined only in global scope"),
+ "FORBIDDEN_TYPEDEF": _("Typedef declaration are not allowed in .c files"),
+ "FORBIDDEN_STRUCT": _("Struct declaration are not allowed in .c files"),
+ "FORBIDDEN_UNION": _("Union declaration are not allowed in .c files"),
+ "FORBIDDEN_ENUM": _("Enum declaration are not allowed in .c files"),
+ "UNEXPECTED_EOF_CHR": _("Unexpected end of file (EOF) while parsing a char"),
+ "UNEXPECTED_EOL_CHR": _("Unexpected end of line (EOL) while parsing a char"),
+ "UNEXPECTED_EOF_MC": _("Unexpected end of file (EOF) while parsing a multiline comment"),
+ "UNEXPECTED_EOF_STR": _("Unexpected end of file (EOF) while parsing a string"),
+ "EMPTY_CHAR": _("Empty character constant"),
+ "CHAR_AS_STRING": _("Character constants can have only one character"),
+ "INVALID_SUFFIX": _("This suffix is invalid"),
+ "BAD_FLOAT_SUFFIX": _("Invalid suffix for float/double literal constant"),
+ "INVALID_BIN_INT": _("Invalid binary integer literal"),
+ "INVALID_OCT_INT": _("Invalid octal integer literal"),
+ "INVALID_HEX_INT": _("Invalid hexadecimal integer literal"),
+ "MAXIMAL_MUNCH": _("Potential maximal munch detected"),
+ "NO_HEX_DIGITS": _("No hexadecimal digits followed by the \\x"),
+ "UNKNOWN_ESCAPE": _("Unknown escape sequence"),
+ "BAD_EXPONENT": _("Exponent has no digits"),
+ "MULTIPLE_DOTS": _("Multiple dots in float constant"),
+ "MULTIPLE_X": _("Multiple 'x' in hexadecimal float constant"),
}
-class NormError:
- def __init__(self, errno, line, col=None):
- self.errno = errno
- self.line = line
- self.col = col
- if col is not None:
- self.error_pos = f"(line: {(str(self.line)).rjust(3)}, col: {(str(self.col)).rjust(3)}):\t"
- else:
- self.error_pos = f"(line: {(str(self.line)).rjust(3)}):\t "
- self.prefix = f"\t{self.errno:<20} {self.error_pos:>21}"
- self.error_msg = f"{errors.get(self.errno, 'ERROR NOT FOUND')}"
-
- def __str__(self):
- return self.prefix + self.error_msg
\ No newline at end of file
diff --git a/norminette/registry.py b/norminette/registry.py
index bf317a24..03cda22b 100644
--- a/norminette/registry.py
+++ b/norminette/registry.py
@@ -1,69 +1,63 @@
-import rules
-from context import Context
-from functools import cmp_to_key
-from exceptions import CParsingError
+import collections
+from operator import attrgetter
+
+from norminette.rules import Rules, Primary
+from norminette.exceptions import CParsingError
+
+rules = Rules()
-def sort_errs(a, b):
- if a.col == b.col and a.line == b.line:
- return 1 if a.errno > b.errno else -1
- return a.col - b.col if a.line == b.line else a.line - b.line
class Registry:
- global has_err
def __init__(self):
- self.rules = rules.rules
- self.primary_rules = rules.primary_rules
- self.dependencies = {}
- for k, r in self.rules.items():
- r.register(self)
+ self.dependencies = collections.defaultdict(list)
+ for rule in rules.checks:
+ rule.register(self)
+ for name, dependencies in self.dependencies.items():
+ self.dependencies[name] = sorted(dependencies, reverse=True, key=attrgetter("__name__"))
def run_rules(self, context, rule):
- if rule.name.startswith("Is"):
- ret, read = rule.run(context)
- else:
- #print (rule.name)
- ret = False
- read = 0
- rule.run(context)
- #print(context.history, context.tokens[:5], rule)
- #if rule.name.startswith("Is"):
- #print (rule.name, ret)
- if ret is True:
+ rule = rule(context)
+ result = rule.run(context)
+ ret, read = result if isinstance(rule, Primary) else (False, 0)
+ if ret:
context.scope.instructions += 1
- if rule.name.startswith("Is"):
- #print ("Line", context.tokens[0].pos[0], rule.name)
+ if isinstance(rule, Primary):
context.tkn_scope = read
- context.history.append(rule.name)
- for r in self.dependencies.get(rule.name, []):
- self.run_rules(context, self.rules[r])
- if 'all' in self.dependencies:
- for r in self.dependencies['all']:
- self.run_rules(context, self.rules[r])
- #context.history.pop(-1)
+ context.history.append(rule)
+ for rule in self.dependencies[rule.name]:
+ self.run_rules(context, rule)
+ for rule in self.dependencies["_rule"]:
+ self.run_rules(context, rule)
context.tkn_scope = 0
return ret, read
- def run(self, context, source):
+ def run(self, context):
"""
- Main function for each file.
- Primary rules are determined by the prefix "Is" and
- are run by order of priority as defined in each class
- Each secondary rule is then run in arbitrary order based on their
- dependencies
+ Main function for each file.
+ Primary rules are determined by the prefix "Is" and
+ are run by order of priority as defined in each class
+ Each secondary rule is then run in arbitrary order based on their
+ dependencies
"""
unrecognized_tkns = []
+ context.state = "starting"
+ for rule in self.dependencies["_start"]:
+ self.run_rules(context, rule)
+ context.state = "running"
while context.tokens != []:
context.tkn_scope = len(context.tokens)
- for rule in self.primary_rules:
- if type(context.scope) not in rule.scope and rule.scope != []:
+ for rule in rules.primaries:
+ if rule.scope and context.scope not in rule.scope:
continue
ret, jump = self.run_rules(context, rule)
if ret is True:
if unrecognized_tkns != []:
if context.debug == 0:
- raise CParsingError(f"Unrecognized line {unrecognized_tkns[0].pos} while parsing line {unrecognized_tkns}")
- print ('uncaught -> ', context.filename)
- print ('uncaught -> ', unrecognized_tkns)
+ raise CParsingError(
+ f"Error: Unrecognized line {unrecognized_tkns[0].pos} while parsing line {unrecognized_tkns}" # noqa: E501
+ )
+ print("uncaught -> ", context.file.name)
+ print("uncaught -> ", unrecognized_tkns)
unrecognized_tkns = []
context.dprint(rule.name, jump)
context.update()
@@ -71,18 +65,14 @@ def run(self, context, source):
break
# #############################################################
else: # Remove these one ALL primary rules are done
- # print("#, ", context.tokens[0])
+ # print("#, ", context.tokens[0])
unrecognized_tkns.append(context.tokens[0])
context.pop_tokens(1) # ##################################
# #############################################################
+ context.state = "ending"
+ for rule in self.dependencies["_end"]:
+ self.run_rules(context, rule)
if unrecognized_tkns != []:
- print (context.debug)
+ print(context.debug)
if context.debug > 0:
- print ("uncaught ->", unrecognized_tkns)
- if context.errors == []:
- print(context.filename + ": OK!")
- else:
- print(context.filename + ": KO!")
- context.errors = sorted(context.errors, key=cmp_to_key(sort_errs))
- for err in context.errors:
- print(err)
+ print("uncaught ->", unrecognized_tkns)
diff --git a/norminette/rules/__init__.py b/norminette/rules/__init__.py
index 2539de4a..73df2a78 100644
--- a/norminette/rules/__init__.py
+++ b/norminette/rules/__init__.py
@@ -1,34 +1,30 @@
import importlib
import os
-from .rule import Rule, PrimaryRule
-from glob import glob
-from functools import cmp_to_key
+from operator import attrgetter
+from norminette.rules.rule import Rule, Primary, Check
-path = os.path.dirname(os.path.realpath(__file__))
-files = glob(path + "/check_*.py")
-rules = {}
-primary_rules = {}
+class Rules:
+ __slots__ = (
+ "all",
+ "primaries",
+ "checks",
+ )
-for f in files:
- mod_name = f.split('/')[-1].split('.')[0]
- class_name = "".join([s.capitalize() for s in mod_name.split('_')])
- module = importlib.import_module("rules." + mod_name)
- rule = getattr(module, class_name)
- rule = rule()
- rules[class_name] = rule
+ __instance = None
-files = glob(path + "/is_*.py")
+ def __new__(cls):
+ if not cls.__instance:
+ cls.__instance = super().__new__(cls)
+ return cls.__instance
-for f in files:
- mod_name = f.split('/')[-1].split('.')[0]
- class_name = "".join([s.capitalize() for s in mod_name.split('_')])
- module = importlib.import_module("rules." + mod_name)
- rule = getattr(module, class_name)
- primary_rules[class_name] = rule()
+ def __init__(self) -> None:
+ path = os.path.dirname(os.path.realpath(__file__))
+ for f in os.listdir(path):
+ name, _ = os.path.splitext(f)
+ importlib.import_module("norminette.rules." + name)
-
-primary_rules = [v for k, v in sorted(
- primary_rules.items(),
- key=lambda item: -item[1].priority)]
+ self.all = Rule.__subclasses__()
+ self.checks = Check.__subclasses__()
+ self.primaries = sorted(Primary.__subclasses__(), reverse=True, key=attrgetter("priority"))
diff --git a/norminette/rules/check_assignation.py b/norminette/rules/check_assignation.py
index 88d9f3dd..6ca344aa 100644
--- a/norminette/rules/check_assignation.py
+++ b/norminette/rules/check_assignation.py
@@ -1,5 +1,5 @@
-from rules import Rule
-from scope import *
+from norminette.rules import Rule, Check
+
assigns = [
"RIGHT_ASSIGN",
"LEFT_ASSIGN",
@@ -14,43 +14,77 @@
"ASSIGN",
]
+special_assigns = ["INC", "DEC"]
+
-class CheckAssignation(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsAssignation"]
+class CheckAssignation(Rule, Check):
+ depends_on = (
+ "IsAssignation",
+ )
+
+ def check_brace_assign(self, context, i):
+ i += 1
+ deep = 1
+ while context.check_token(i, "RBRACE") is False and deep > 0:
+ i += 1
+ return True, i
- def check_assign_right(self, context, i):
+ def check_assign_right(self, context, i, mini_assign=False):
tmp_typ = None
start = 0
while context.check_token(i, "SEMI_COLON") is False:
typ = None
+ if context.check_token(i, "LBRACE"):
+ ret, i = self.check_brace_assign(context, i)
+ if ret is False:
+ return True, i
+ break
if context.check_token(i, "LPARENTHESIS") is True:
start = i
tmp_typ, i = context.parenthesis_contain(i)
- if tmp_typ != None:
+ if tmp_typ is not None:
typ = tmp_typ
+ if tmp_typ == "assign":
+ context.new_error("MULT_ASSIGN_LINE", context.peek_token(start))
if tmp_typ is None:
tmp = start + 1
- while context.peek_token(tmp) and context.check_token(tmp, "RPARENTHESIS") is False:
- if context.check_token(tmp, "COMMA") is True and typ is not None:
+ while (
+ context.peek_token(tmp)
+ and context.check_token(tmp, "RPARENTHESIS") is False
+ ):
+ if (
+ context.check_token(tmp, "COMMA") is True
+ and typ is not None
+ ):
context.new_error("TOO_MANY_INSTR", context.peek_token(tmp))
tmp += 1
if context.check_token(i, assigns) is True:
- context.new_error("MULT_ASSIGN_LINE", context.peek_token(i))
+ if mini_assign is True:
+ mini_assign = False
+ else:
+ context.new_error("MULT_ASSIGN_LINE", context.peek_token(i))
+ if context.check_token(i, special_assigns) is True:
+ if mini_assign is True:
+ context.new_error("MULT_ASSIGN_LINE", context.peek_token(i))
i += 1
return False, 0
def run(self, context):
"""
- Only one assignation at a time
- Unless the variable is static (or global), you cannot assign its value when you declare it.
+ Only one assignation at a time
+ Unless the variable is static (or global), you cannot assign its value when you declare it.
"""
i = 0
assign_present = False
+ mini_assign = False
while context.check_token(i, "SEMI_COLON") is False:
- if context.check_token(i, assigns) is True and assign_present == False:
+ if (
+ context.check_token(i, assigns + special_assigns) is True
+ and assign_present is False
+ ):
assign_present = True
- return self.check_assign_right(context, i + 1)
+ if context.check_token(i, special_assigns):
+ mini_assign = True
+ return self.check_assign_right(context, i + 1, mini_assign)
i += 1
return False, 0
diff --git a/norminette/rules/check_assignation_indent.py b/norminette/rules/check_assignation_indent.py
index b6d2ad3b..c5b55ca5 100644
--- a/norminette/rules/check_assignation_indent.py
+++ b/norminette/rules/check_assignation_indent.py
@@ -1,5 +1,7 @@
-from rules import Rule
-from scope import *
+from norminette.rules import Rule, Check
+from norminette.exceptions import CParsingError
+
+
operators = [
"RIGHT_ASSIGN",
"LEFT_ASSIGN",
@@ -39,18 +41,22 @@
nest_kw = ["RPARENTHESIS", "LPARENTHESIS", "NEWLINE"]
-class CheckAssignationIndent(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsAssignation", "IsFuncPrototype", "IsFunctionCall"]
+
+class CheckAssignationIndent(Rule, Check):
+ depends_on = (
+ "IsAssignation",
+ "IsFuncPrototype",
+ "IsFunctionCall",
+ "IsVarDeclaration",
+ )
def run(self, context):
"""
- Declared variables must be aligned using tabs with other variables on the same scope
+ Declared variables must be aligned using tabs with other variables on the same scope
"""
i = 0
expected = context.scope.indent
- if context.history[-1] == "IsAssignation":
+ if context.history[-1] in ["IsAssignation", "IsVarDeclaration"]:
nest = expected + 1
elif context.history[-1] == "IsFuncPrototype":
nest = context.func_alignment
@@ -61,18 +67,34 @@ def run(self, context):
if context.check_token(i - 1, operators) is True:
context.new_error("EOL_OPERATOR", context.peek_token(i))
tmp = context.skip_ws(i + 1)
- if context.check_token(tmp, 'COMMA'):
+ if context.check_token(tmp, "COMMA"):
context.new_error("COMMA_START_LINE", context.peek_token(i))
got = 0
i += 1
while context.check_token(i + got, "TAB") is True:
got += 1
- if got > nest:
+ if context.peek_token(i + got) is None:
+ raise CParsingError(
+ f"Error: Unexpected EOF l.{context.peek_token(i - 1).pos[0]}"
+ )
+ if context.check_token(
+ i + got, ["LBRACKET", "RBRACKET", "LBRACE", "RBRACE"]
+ ):
+ nest -= 1
+ if got > nest or (
+ got > nest + 1
+ and context.history[-1] in ["IsAssignation", "IsVarDeclaration"]
+ ):
context.new_error("TOO_MANY_TAB", context.peek_token(i))
- return True, i
- elif got < nest:
+ elif got < nest or (
+ got < nest - 1
+ and context.history[-1] in ["IsAssignation", "IsVarDeclaration"]
+ ):
context.new_error("TOO_FEW_TAB", context.peek_token(i))
- return True, i
+ if context.check_token(
+ i + got, ["LBRACKET", "RBRACKET", "LBRACE", "RBRACE"]
+ ):
+ nest += 1
if context.check_token(i, "LPARENTHESIS") is True:
nest += 1
if context.check_token(i, "RPARENTHESIS") is True:
diff --git a/norminette/rules/check_block_start.py b/norminette/rules/check_block_start.py
index 1a7fc6ff..4db01941 100644
--- a/norminette/rules/check_block_start.py
+++ b/norminette/rules/check_block_start.py
@@ -1,17 +1,18 @@
-from rules import Rule
-from scope import *
+from norminette.rules import Rule, Check
+from norminette.scope import GlobalScope, ControlStructure
-class CheckBlockStart(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsBlockStart"]
+
+class CheckBlockStart(Rule, Check):
+ depends_on = (
+ "IsBlockStart",
+ )
def run(self, context):
"""
- Braces signal that the control structure, function, or user defined type can contain
- multiple lines.
- A control structure that has no braces can only contain one instruction line, but can
- contain multiple control structures
+ Braces signal that the control structure, function, or user defined type can contain
+ multiple lines.
+ A control structure that has no braces can only contain one instruction line, but can
+ contain multiple control structures
"""
outer = context.scope.get_outer()
if len(context.history) > 2:
@@ -23,13 +24,21 @@ def run(self, context):
elif i == 3:
hist_2 = item
i += 1
- if type(context.scope) is GlobalScope and context.scope.tmp_scope is not None \
- and hist_1 == "IsFuncDeclaration" and hist_2 == "IsPreprocessorStatement":
+ if (
+ type(context.scope) is GlobalScope
+ and context.scope.tmp_scope is not None
+ and hist_1 == "IsFuncDeclaration"
+ and hist_2 == "IsPreprocessorStatement"
+ ):
context.scope.functions -= 1
context.scope = context.tmp_scope
context.scope.multiline = True
context.tmp_scope = None
- if type(context.scope) == ControlStructure and outer is not None and type(outer) == ControlStructure:
- if outer.multiline == False:
+ if (
+ type(context.scope) is ControlStructure
+ and outer is not None
+ and type(outer) is ControlStructure
+ ):
+ if outer.multiline is False:
context.new_error("MULT_IN_SINGLE_INSTR", context.peek_token(0))
- return False, 0
\ No newline at end of file
+ return False, 0
diff --git a/norminette/rules/check_brace.py b/norminette/rules/check_brace.py
index 6d039869..df799f87 100644
--- a/norminette/rules/check_brace.py
+++ b/norminette/rules/check_brace.py
@@ -1,26 +1,28 @@
-from rules import Rule
-from scope import *
+from norminette.rules import Rule, Check
-class CheckBrace(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsBlockStart", "IsBlockEnd"]
+class CheckBrace(Rule, Check):
+ depends_on = (
+ "IsBlockStart",
+ "IsBlockEnd",
+ )
def run(self, context):
"""
- C files must end with an empty line
- Functions can only have 25 lines
+ C files must end with an empty line
+ Functions can only have 25 lines
"""
i = 0
i = context.skip_ws(i, nl=False)
- #if context.check_token(i, ["RBRACE", "LBRACE"]) is False and context.scope.type != "GlobalScope":
+ # if context.check_token(i, ["RBRACE", "LBRACE"]) is False and context.scope.type != "GlobalScope":
# context.new_error("BRACE_EMPTY_LINE")
if context.check_token(i, ["RBRACE", "LBRACE"]) is False:
context.new_error("EXPECTED_BRACE", context.peek_token(i))
return False, 0
i += 1
i = context.skip_ws(i, nl=False)
+ if context.check_token(i, "NEWLINE") is True and context.check_token(i - 1, ["SPACE", "TAB"]):
+ context.new_error("SPC_BEFORE_NL", context.peek_token(i - 1))
if context.check_token(i, "NEWLINE") is False or context.check_token(i, "NEWLINE") is None:
if context.scope.name == "UserDefinedType" or context.scope.name == "UserDefinedEnum":
i = context.skip_ws(i, nl=False)
diff --git a/norminette/rules/check_comment.py b/norminette/rules/check_comment.py
index d4ba0443..0d4a5c1b 100644
--- a/norminette/rules/check_comment.py
+++ b/norminette/rules/check_comment.py
@@ -1,33 +1,56 @@
-from rules import Rule
-from scope import *
+from norminette.rules import Rule, Check
-allowed_on_comment = [
- "COMMENT",
- "MULT_COMMENT",
- "SPACE",
- "TAB"
-]
-
-class CheckComment(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = []
+class CheckComment(Rule, Check):
def run(self, context):
"""
- Comments are only allowed in GlobalScope.
+ Comments are forbidden inside functions and in the middle of instructions.
"""
i = context.skip_ws(0)
- has_comment = False
- while context.peek_token(i) is not None and context.check_token(i, "NEWLINE") is False:
- if context.check_token(i, allowed_on_comment) is False:
- if has_comment == True:
- context.new_error("COMMENT_ON_INSTR", context.peek_token(i))
- return True, i
- elif context.check_token(i, ['COMMENT', 'MULT_COMMENT']) is True:
- if context.scope.name != "GlobalScope" or context.history[-1] == 'IsFuncDeclaration':
- context.new_error("WRONG_SCOPE_COMMENT", context.peek_token(i))
- has_comment = True
+
+ tokens = []
+ while context.peek_token(i) and not context.check_token(i, "NEWLINE"):
+ token = context.peek_token(i)
+ tokens.append(token)
i += 1
- i = context.skip_ws(0)
- return False, 0
+
+ for index, token in enumerate(tokens):
+ if token.type in ("COMMENT", "MULT_COMMENT"):
+ if self.is_inside_a_function(context):
+ context.new_error("WRONG_SCOPE_COMMENT", token)
+ if index == 0 or self.is_last_token(token, tokens[index+1:]):
+ continue
+ context.new_error("COMMENT_ON_INSTR", token)
+
+ def is_inside_a_function(self, context):
+ if context.history[-2:] == ["IsFuncDeclaration", "IsBlockStart"]:
+ return True
+ if context.scope.__class__.__name__.lower() == "function":
+ return True
+ # Sometimes the context scope is a `ControlStructure` scope instead of
+ # `Function` scope, so, to outsmart this bug, we need check manually
+ # the `context.history`.
+ last = None
+ for index, record in enumerate(reversed(context.history)):
+ if record == "IsFuncDeclaration" and last == "IsBlockStart":
+ # Since the limited history API, we can't say if we're in a
+ # nested function to reach the first enclosing function, so,
+ # we'll consider that the user just declared a normal function
+ # in global scope.
+ stack = 1
+ index -= 1 # Jumps to next record after `IsBlockStart`
+ while index > 0 and stack > 0:
+ record = context.history[-index]
+ index -= 1
+ if record not in ("IsBlockStart", "IsBlockEnd"):
+ continue
+ stack = stack + (1, -1)[record == "IsBlockEnd"]
+ return bool(stack)
+ last = record
+ return False
+
+ def is_last_token(self, token, foward):
+ expected = ("SPACE", "TAB")
+ if token.type == "MULT_COMMENT":
+ expected += ("COMMENT", "MULT_COMMENT")
+ return all(it.type in ("SPACE", "TAB", "COMMENT", "MULT_COMMENT") for it in foward)
diff --git a/norminette/rules/check_comment_line_len.py b/norminette/rules/check_comment_line_len.py
index f58a1a9f..9a4ebb9a 100644
--- a/norminette/rules/check_comment_line_len.py
+++ b/norminette/rules/check_comment_line_len.py
@@ -1,23 +1,28 @@
-from rules import Rule
-from lexer import Lexer, TokenError
-from scope import *
+from norminette.rules import Rule, Check
-class CheckCommentLineLen(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsComment"]
+
+class CheckCommentLineLen(Rule, Check):
+ depends_on = ("IsComment",)
def run(self, context):
"""
- Lines must not be over 80 characters long
+ Lines must not be over 80 characters long
"""
i = 0
- while context.check_token(i, ["COMMENT", "MULT_COMMENT"]) is False:
+ while not context.check_token(i, ["COMMENT", "MULT_COMMENT"]):
i += 1
- val = context.peek_token(i).value
- line_start = context.peek_token(0).pos[1]
- val = val.split('\n')
- for item in val:
- if len(item) + line_start > 81:
- context.new_error("LINE_TOO_LONG", context.peek_token(0))
- line_start = 0
+ token = context.peek_token(i)
+ if not token:
+ return
+ index = token.pos[1]
+ if token.type == "MULT_COMMENT":
+ lines = token.value.split("\n")
+ # We need to add a padding to the first line because the comment
+ # can be at the end of a line.
+ lines[0] = " " * index + lines[0]
+ for lineno, line in enumerate(lines, start=token.pos[0]):
+ if len(line) > 81:
+ token.pos = (lineno, 1)
+ context.new_error("LINE_TOO_LONG", token)
+ elif index + len(token.value) > 81: # token.type == "COMMENT"
+ context.new_error("LINE_TOO_LONG", token)
diff --git a/norminette/rules/check_control_statement.py b/norminette/rules/check_control_statement.py
index ae6749ad..3cdca8ca 100644
--- a/norminette/rules/check_control_statement.py
+++ b/norminette/rules/check_control_statement.py
@@ -1,12 +1,7 @@
-from rules import Rule
-from scope import *
+from norminette.rules import Rule, Check
-forbidden_cs = [
- "FOR",
- "SWITCH",
- "CASE",
- "GOTO"
-]
+
+forbidden_cs = ["FOR", "SWITCH", "CASE", "GOTO"]
assigns = [
"RIGHT_ASSIGN",
"LEFT_ASSIGN",
@@ -21,28 +16,37 @@
"ASSIGN",
]
-class CheckControlStatement(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsControlStatement"]
+
+class CheckControlStatement(Rule, Check):
+ depends_on = (
+ "IsControlStatement",
+ )
def check_nest(self, context, i):
- while context.check_token(i, "RPARENTHESIS") is False:
+ depth = 1
+ i += 1
+ while depth > 0:
+ if context.check_token(i, "LPARENTHESIS") is True:
+ depth += 1
+ if context.check_token(i, "RPARENTHESIS") is True:
+ depth -= 1
if context.check_token(i, assigns) is True:
context.new_error("ASSIGN_IN_CONTROL", context.peek_token(i))
return -1
if context.check_token(i, forbidden_cs) is True:
context.new_error("FORBIDDEN_CS", context.peek_token(i))
+ if context.check_token(i, "NEWLINE") is True and depth < 1:
+ return
i += 1
return
def run(self, context):
"""
- Forbidden control structures:
- - For
- - Switch case
- - Goto
- Assignations must be done outside of control structures
+ Forbidden control structures:
+ - For
+ - Switch case
+ - Goto
+ Assignations must be done outside of control structures
"""
i = 0
if context.scope.name == "GlobalScope":
diff --git a/norminette/rules/check_declaration.py b/norminette/rules/check_declaration.py
index cc505dc7..4f69a19e 100644
--- a/norminette/rules/check_declaration.py
+++ b/norminette/rules/check_declaration.py
@@ -1,18 +1,18 @@
-from rules import Rule
-from scope import *
+from norminette.rules import Rule, Check
-class CheckDeclaration(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsDeclaration"]
+
+class CheckDeclaration(Rule, Check):
+ depends_on = (
+ "IsDeclaration",
+ )
def run(self, context):
"""
- Checks for nl in declarations
+ Checks for nl in declarations
"""
- #i = context.skip_ws(0)
- #while context.peek_token(i) and context.check_token(i, "SEMI_COLON") is False:
- #if context.check_token(i, "NEWLINE") is True:
- #context.new_error("NEWLINE_IN_DECL", context.peek_token(i))
- #i += 1
+ # i = context.skip_ws(0)
+ # while context.peek_token(i) and context.check_token(i, "SEMI_COLON") is False:
+ # if context.check_token(i, "NEWLINE") is True:
+ # context.new_error("NEWLINE_IN_DECL", context.peek_token(i))
+ # i += 1
return False, 0
diff --git a/norminette/rules/check_empty_line.py b/norminette/rules/check_empty_line.py
index 6a6be0aa..b5032dcc 100644
--- a/norminette/rules/check_empty_line.py
+++ b/norminette/rules/check_empty_line.py
@@ -1,43 +1,54 @@
-from rules import Rule
-from scope import *
+from norminette.rules import Rule, Check
-
-class CheckEmptyLine(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = []
-
+class CheckEmptyLine(Rule, Check):
def run(self, context):
"""
- Empty line must not contains tabs or spaces
- You cannot have 2 empty lines in a row
- Your variable declarations must be followed by an empty line
- No other empty lines are allowed in functions
- You must have an empty between two functions
+ Empty line must not contains tabs or spaces
+ You cannot have 2 empty lines in a row
+ Your variable declarations must be followed by an empty line
+ No other empty lines are allowed in functions
+ You must have an empty between two functions
"""
i = 0
if len(context.history) == 1 and context.history[-1] == "IsEmptyLine":
context.new_error("EMPTY_LINE_FILE_START", context.peek_token(i))
return False, 0
- if context.scope.name != "GlobalScope" and context.history[-1] != "IsBlockStart":
- if context.history[-1] != "IsVarDeclaration" and context.scope.vdeclarations_allowed == True:
+ if context.scope.name != "GlobalScope":
+ if (
+ context.history[-1] != "IsVarDeclaration"
+ and context.scope.vdeclarations_allowed is True
+ ):
context.scope.vdeclarations_allowed = False
if context.history[-1] not in ["IsEmptyLine", "IsComment"]:
- if context.history[-1] == "IsBlockEnd" and context.scope.name == "Function":
+ if (
+ context.history[-1] == "IsBlockEnd"
+ and context.scope.name == "Function"
+ ):
pass
else:
context.new_error("NL_AFTER_VAR_DECL", context.peek_token(i))
return True, i
+ if (
+ len(context.history) > 1
+ and context.history[-2] == "IsPreprocessorStatement"
+ and context.history[-1] != "IsPreprocessorStatement"
+ and context.history[-1] != "IsEmptyLine"
+ and context.history[-1] != "IsComment"
+ ):
+ context.new_error("NL_AFTER_PREPROC", context.peek_token(i))
if context.history[-1] != "IsEmptyLine":
return False, 0
if context.check_token(i, "NEWLINE") is False:
context.new_error("SPACE_EMPTY_LINE", context.peek_token(i))
if context.history[-2] == "IsEmptyLine":
context.new_error("CONSECUTIVE_NEWLINES", context.peek_token(i))
- if context.history[-2] != "IsVarDeclaration" and context.scope.name != "GlobalScope":
- context.new_error("EMPTY_LINE_FUNCTION", context.peek_token(i))
- if context.peek_token(i + 1) is None:
+ if (
+ context.history[-2] != "IsVarDeclaration"
+ and context.scope.name != "GlobalScope"
+ ):
+ context.new_error("EMPTY_LINE_FUNCTION", context.peek_token(0))
+ if context.check_token(i, "NEWLINE") and context.peek_token(i + 1) is None:
context.new_error("EMPTY_LINE_EOF", context.peek_token(i))
return False, 0
diff --git a/norminette/rules/check_enum_var_decl.py b/norminette/rules/check_enum_var_decl.py
index 4cdec2ab..cd490df4 100644
--- a/norminette/rules/check_enum_var_decl.py
+++ b/norminette/rules/check_enum_var_decl.py
@@ -1,14 +1,14 @@
-from rules import Rule
-from scope import *
+from norminette.rules import Rule, Check
-class CheckEnumVarDecl(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsEnumVarDecl"]
+
+class CheckEnumVarDecl(Rule, Check):
+ depends_on = (
+ "IsEnumVarDecl",
+ )
def run(self, context):
"""
- Checks for nl in declarations
+ Checks for nl in declarations
"""
i = context.skip_ws(0)
while context.peek_token(i) and context.check_token(i, "COMMA") is False:
@@ -18,7 +18,7 @@ def run(self, context):
if context.check_token(i, "LBRACE") is True:
return False, 0
i += 1
- #context.new_error("NEWLINE_IN_DECL", context.peek_token(i))
+ # context.new_error("NEWLINE_IN_DECL", context.peek_token(i))
return True, i
i += 1
return False, 0
diff --git a/norminette/rules/check_expression_statement.py b/norminette/rules/check_expression_statement.py
index 5a682043..c150f4f5 100644
--- a/norminette/rules/check_expression_statement.py
+++ b/norminette/rules/check_expression_statement.py
@@ -1,5 +1,4 @@
-from rules import Rule
-from scope import *
+from norminette.rules import Rule, Check
kw = [
# C reserved keywords #
@@ -33,39 +32,52 @@
"UNSIGNED",
"VOID",
"VOLATILE",
- "WHILE"
+ "WHILE",
]
-class CheckExpressionStatement(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsExpressionStatement", "IsControlStatement", "IsFunctionCall", "IsAssignation"]
+
+class CheckExpressionStatement(Rule, Check):
+ depends_on = (
+ "IsExpressionStatement",
+ "IsControlStatement",
+ "IsFunctionCall",
+ "IsAssignation",
+ "IsCast",
+ )
def run(self, context):
"""
- C keywords (return, break, continue...) must be followed by a space, with the
- exception of sizeof
- Return values in a function must be contained in parenthesis
+ C keywords (return, break, continue...) must be followed by a space, with the
+ exception of sizeof
+ Return values in a function must be contained in parenthesis
"""
i = 0
- parenthesis = False
while context.check_token(i, ["SEMI_COLON", "NEWLINE"]) is False:
if context.check_token(i, kw) is True:
- if context.check_token(i + 1, ["SPACE", "NEWLINE", "RPARENTHESIS"]) is False:
+ if (
+ context.check_token(
+ i + 1,
+ ["SPACE", "NEWLINE", "RPARENTHESIS", "COMMENT", "MULT_COMMENT"],
+ )
+ is False
+ ):
context.new_error("SPACE_AFTER_KW", context.peek_token(i))
- return False, 0
+ if context.check_token(i, ["MULT", "BWISE_AND"]) is True and i > 0:
+ if context.check_token(i - 1, "IDENTIFIER") is True:
+ context.new_error("SPACE_AFTER_KW", context.peek_token(i - 1))
if context.check_token(i, "RETURN") is True:
tmp = i + 1
tmp = context.skip_ws(tmp)
- if context.check_token(tmp, "SEMI_COLON") is True:
- return False, 0
- if context.check_token(tmp, "SEMI_COLON") is False and context.check_token(tmp, "LPARENTHESIS") is False:
+ if (
+ context.check_token(tmp, "SEMI_COLON") is False
+ and context.check_token(tmp, "LPARENTHESIS") is False
+ ):
context.new_error("RETURN_PARENTHESIS", context.peek_token(tmp))
return False, 0
- else:
+ elif context.check_token(tmp, "SEMI_COLON") is False:
tmp = context.skip_nest(tmp) + 1
if context.check_token(tmp, "SEMI_COLON") is False:
context.new_error("RETURN_PARENTHESIS", context.peek_token(tmp))
- return False, 0
+ return False, 0
i += 1
return False, 0
diff --git a/norminette/rules/check_func_arguments_name.py b/norminette/rules/check_func_arguments_name.py
index f1a5c8b2..f589e48e 100644
--- a/norminette/rules/check_func_arguments_name.py
+++ b/norminette/rules/check_func_arguments_name.py
@@ -1,51 +1,23 @@
-from lexer import Token
-from rules import Rule
+from norminette.rules import Rule, Check
-type_specifiers = [
- "CHAR",
- "DOUBLE",
- "ENUM",
- "FLOAT",
- "INT",
- "UNION",
- "VOID",
- "SHORT"
-]
+type_specifiers = ["CHAR", "DOUBLE", "ENUM", "FLOAT", "INT", "UNION", "VOID", "SHORT"]
-misc_specifiers = [
- "CONST",
- "REGISTER",
- "STATIC",
- "STRUCT",
- "VOLATILE"
-]
+misc_specifiers = ["CONST", "REGISTER", "STATIC", "STRUCT", "VOLATILE"]
-size_specifiers = [
- "LONG",
- "SHORT"
-]
+size_specifiers = ["LONG", "SHORT"]
-sign_specifiers = [
- "SIGNED",
- "UNSIGNED"
-]
+sign_specifiers = ["SIGNED", "UNSIGNED"]
-whitespaces = [
- "SPACE",
- "TAB",
- "NEWLINE"
-]
+whitespaces = ["SPACE", "TAB", "NEWLINE"]
-arg_separator = [
- "COMMA",
- "CLOSING_PARENTHESIS"
-]
+arg_separator = ["COMMA", "CLOSING_PARENTHESIS"]
-class CheckFuncArgumentsName(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsFuncDeclaration", "IsFuncPrototype"]
+class CheckFuncArgumentsName(Rule, Check):
+ depends_on = (
+ "IsFuncDeclaration",
+ "IsFuncPrototype",
+ )
def check_arg_format(self, context, pos):
"""
@@ -58,21 +30,30 @@ def check_arg_format(self, context, pos):
p = 0
stop = ["COMMA", "RPARENTHESIS"]
if context.check_token(i, ["COMMENT", "MULT_COMMENT"]):
- context.new_error("WRONG_SCOPE_COMMENT", context.peek_token(i))
+ # context.new_error("WRONG_SCOPE_COMMENT", context.peek_token(i))
i += 1
- #if context.check_token(i, "NEWLINE"):
- #context.new_error("NEWLINE_IN_DECL", context.peek_token(i))
- #i += 1
+ # if context.check_token(i, "NEWLINE"):
+ # context.new_error("NEWLINE_IN_DECL", context.peek_token(i))
+ # i += 1
if context.check_token(i, "ELLIPSIS"):
i += 1
if context.peek_token(i).type in stop:
i += 1
return i
ret, i = context.check_type_specifier(i)
- if ret == False:
+ has_tab = False
+ while context.check_token(i, ["SPACE", "TAB"]):
+ if context.check_token(i, "TAB") is True and has_tab is False:
+ context.new_error("TAB_INSTEAD_SPC", context.peek_token(i))
+ has_tab = True
+ i += 1
+
+ if ret is False:
context.new_error("ARG_TYPE_UKN", context.peek_token(i))
return -1
- while context.peek_token(i) is not None and context.check_token(i, ["LPARENTHESIS"] + whitespaces):
+ while context.peek_token(i) is not None and context.check_token(
+ i, ["LPARENTHESIS"] + whitespaces
+ ):
if context.check_token(i, "LPARENTHESIS") is True:
p += 1
if context.check_token(i, "RPARENTHESIS") is True:
@@ -90,18 +71,19 @@ def check_arg_format(self, context, pos):
while context.peek_token(i) is not None and i < context.arg_pos[1]:
if context.check_token(i, stop) is True:
if context.check_token(i, "RPARENTHESIS") is True and p > 0:
-
p -= 1
else:
break
- if context.check_token(i, 'LPARENTHESIS'):
+ if context.check_token(i, "LPARENTHESIS"):
i = context.skip_nest(i)
i += 1
i += 1
else:
- while context.peek_token(i) is not None \
- and context.peek_token(i).type not in stop:
+ while (
+ context.peek_token(i) is not None
+ and context.peek_token(i).type not in stop
+ ):
i += 1
i += 1
return i
@@ -120,28 +102,33 @@ def no_arg_func(self, context, pos):
def run(self, context):
"""
- Empty functions arguments must use void
+ Empty functions arguments must use void
"""
i = context.arg_pos[0] + 1
ret = self.no_arg_func(context, i)
if ret is True:
return False, 0
while i < context.arg_pos[1]:
- if context.check_token(i, 'NEWLINE'):
+ i = context.skip_ws(i)
+ if context.check_token(i, "NEWLINE"):
i += 1
continue
- if context.check_token(i, 'LPARENTHESIS'):
+ if context.check_token(i, "LPARENTHESIS"):
p = 1
while p:
if context.peek_token(i) is not None:
- if context.check_token(i, 'LPARENTHESIS'):
+ if context.check_token(i, "LPARENTHESIS"):
p += 1
- elif context.check_token(i, 'RPARENTHESIS'):
+ elif context.check_token(i, "RPARENTHESIS"):
p -= 1
else:
break
i += 1
- else:
+ elif context.check_token(i, "LBRACKET") is True:
+ i = context.skip_nest(i) + 1
+ elif context.check_token(i, "COMMA") is True:
+ i += 1
+ elif context.check_token(i, "RPARENTHESIS") is not True:
i = self.check_arg_format(context, i)
if i == -1:
return False, 0
diff --git a/norminette/rules/check_func_declaration.py b/norminette/rules/check_func_declaration.py
index 45b538fc..1fa62083 100644
--- a/norminette/rules/check_func_declaration.py
+++ b/norminette/rules/check_func_declaration.py
@@ -1,56 +1,75 @@
-from rules import Rule
-from scope import *
+from norminette.rules import Rule, Check
-types = [
- "INT",
- "FLOAT",
- "CHAR",
- "DOUBLE",
- "LONG",
- "SHORT"
-]
+types = ["INT", "FLOAT", "CHAR", "DOUBLE", "LONG", "SHORT"]
-class CheckFuncDeclaration(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsFuncDeclaration", "IsFuncPrototype"]
+
+class CheckFuncDeclaration(Rule, Check):
+ depends_on = (
+ "IsFuncDeclaration",
+ "IsFuncPrototype",
+ "IsUserDefinedType",
+ )
def run(self, context):
"""
- Maximum 4 arguments in a function
- Function declaration must be preceded by a newline
+ Maximum 4 arguments in a function
+ Function declaration must be preceded by a newline
"""
+ # pdb.set_trace()
i = 0
tmp = 0
+ start = 0
arg = 1
while context.check_token(tmp, ["SEMI_COLON", "NEWLINE"]) is False:
if context.check_token(tmp, "LBRACE") is True:
context.new_error("BRACE_NEWLINE", context.peek_token(tmp))
tmp += 1
- #if tmp < context.tkn_scope - 2:
- #context.new_error("NEWLINE_IN_DECL", context.peek_token(tmp))
- #this is a func declaration
- if context.check_token(tmp, "SEMI_COLON") is False:
- if len(context.history) > 1 and context.history[-2] != "IsEmptyLine" and context.history[-2] != "IsPreprocessorStatement" and context.history[-1] == 'IsFuncDeclaration':
- context.new_error("NEWLINE_PRECEDES_FUNC", context.peek_token(i))
- #this is a func prototype
+ if context.history[-1] == "IsUserDefinedType":
+ return
+ # if tmp < context.tkn_scope - 2:
+ # context.new_error("NEWLINE_IN_DECL", context.peek_token(tmp))
+ # this is a func declaration
+ if context.history[-1] == "IsFuncDeclaration":
+ # if context.check_token(tmp, "SEMI_COLON") is False:
+ i = 2
+ length = len(context.history)
+ while length - i >= 0 and (
+ context.history[-i] == "IsPreprocessorStatement"
+ or context.history[-i] == "IsComment"
+ or context.history[-i] == "IsFuncDeclaration"
+ ):
+ i += 1
+ if length - i > 0 and context.history[-i] != "IsEmptyLine":
+ context.new_error("NEWLINE_PRECEDES_FUNC", context.peek_token(start))
i = context.fname_pos + 1
- while (context.check_token(i, ["RPARENTHESIS", "SPACE", "TAB"])) is True:
+ while (
+ context.check_token(i, ["RPARENTHESIS"])
+ ) is True: # , "SPACE", "TAB"])) is True:
i += 1
if context.check_token(i, "LPARENTHESIS") is False:
context.new_error("EXP_PARENTHESIS", context.peek_token(i))
+ i = context.skip_ws(i)
i += 1
- while context.check_token(i, "RPARENTHESIS") is False:
- if context.check_token(i, "COMMA"):
+ deep = 1
+ while deep > 0 and context.peek_token(i) is not None:
+ if context.check_token(i, "LPARENTHESIS"):
+ i = context.skip_nest(i)
+ elif context.check_token(i, "RPARENTHESIS"):
+ deep -= 1
+ elif context.check_token(i, "COMMA"):
arg += 1
i += 1
if context.check_token(i - 1, ["SPACE", "TAB"]) is True:
tmp = i - 1
- while context.check_token(tmp, ['SPACE', 'TAB']) is True:
+ while context.check_token(tmp, ["SPACE", "TAB"]) is True:
tmp -= 1
- if context.check_token(tmp, 'NEWLINE') is False:
+ if context.check_token(tmp, "NEWLINE") is False:
context.new_error("NO_SPC_BFR_PAR", context.peek_token(i))
if arg > 4:
context.new_error("TOO_MANY_ARGS", context.peek_token(i))
arg = []
+ while context.check_token(i, ["NEWLINE", "SEMI_COLON"]) is False:
+ i += 1
+ if context.check_token(i - 1, ["TAB", "SPACE"]):
+ context.new_error("SPC_BEFORE_NL", context.peek_token(i))
return False, 0
diff --git a/norminette/rules/check_func_spacing.py b/norminette/rules/check_func_spacing.py
index 5f7effa7..2302377b 100644
--- a/norminette/rules/check_func_spacing.py
+++ b/norminette/rules/check_func_spacing.py
@@ -1,55 +1,33 @@
-from rules import Rule
+from norminette.rules import Rule, Check
-whitespaces = [
- "SPACE",
- "TAB",
- "NEWLINE"
-]
+whitespaces = ["SPACE", "TAB", "NEWLINE"]
-type_specifiers = [
- "CHAR",
- "DOUBLE",
- "ENUM",
- "FLOAT",
- "INT",
- "UNION",
- "VOID",
- "SHORT"
-]
+type_specifiers = ["CHAR", "DOUBLE", "ENUM", "FLOAT", "INT", "UNION", "VOID", "SHORT"]
-misc_specifiers = [
- "CONST",
- "REGISTER",
- "STATIC",
- "STRUCT",
- "VOLATILE"
-]
+misc_specifiers = ["CONST", "REGISTER", "STATIC", "STRUCT", "VOLATILE"]
-size_specifiers = [
- "LONG",
- "SHORT"
-]
+size_specifiers = ["LONG", "SHORT"]
-sign_specifiers = [
- "SIGNED",
- "UNSIGNED"
-]
+sign_specifiers = ["SIGNED", "UNSIGNED"]
-arg_separator = [
- "COMMA",
- "CLOSING_PARENTHESIS"
-]
+arg_separator = ["COMMA", "CLOSING_PARENTHESIS"]
-class CheckFuncSpacing(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsFuncDeclaration"]
+class CheckFuncSpacing(Rule, Check):
+ depends_on = (
+ "IsFuncDeclaration",
+ )
def run(self, context):
"""
- Function return type and function name must be separated by a tab
+ Function return type and function name must be separated by a tab
"""
+ i = 0
+ while i < context.fname_pos:
+ if context.check_token(i, "IDENTIFIER") is True and context.peek_token(i).value == "__attribute__":
+ # context.new_error("ATTR_EOL", context.peek_token(i))
+ break
+ i += 1
i = context.fname_pos - 1
while context.check_token(i, ["MULT", "BWISE_AND", "LPARENTHESIS"]) is True:
i -= 1
diff --git a/norminette/rules/check_functions_count.py b/norminette/rules/check_functions_count.py
index 1ec65fca..232b179b 100644
--- a/norminette/rules/check_functions_count.py
+++ b/norminette/rules/check_functions_count.py
@@ -1,18 +1,16 @@
-from lexer import Token
-from rules import Rule
-import string
+from norminette.rules import Rule, Check
-class CheckFunctionsCount(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsFuncDeclaration"]
+class CheckFunctionsCount(Rule, Check):
+ depends_on = (
+ "IsFuncDeclaration",
+ )
def run(self, context):
"""
- Each file cannot contain more than 5 function
+ Each file cannot contain more than 5 function
"""
- if context.scope != None and context.scope.name == "GlobalScope":
+ if context.scope is not None and context.scope.name == "GlobalScope":
if context.scope.functions > 5:
context.new_error("TOO_MANY_FUNCS", context.peek_token(0))
- return False, 0
\ No newline at end of file
+ return False, 0
diff --git a/norminette/rules/check_general_spacing.py b/norminette/rules/check_general_spacing.py
index afe70340..7ae40f4d 100644
--- a/norminette/rules/check_general_spacing.py
+++ b/norminette/rules/check_general_spacing.py
@@ -1,20 +1,18 @@
-from rules import Rule
-from scope import *
+from norminette.rules import Rule, Check
-class CheckGeneralSpacing(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = [
- "IsDeclaration",
- "IsControlStatement",
- "IsExpressionStatement",
- "IsAssignation",
- "IsFunctionCall",
- ]
+
+class CheckGeneralSpacing(Rule, Check):
+ depends_on = (
+ "IsDeclaration",
+ "IsControlStatement",
+ "IsExpressionStatement",
+ "IsAssignation",
+ "IsFunctionCall",
+ )
def run(self, context):
"""
- Checks for tab/space consistency
+ Checks for tab/space consistency
"""
if context.scope.name == "UserDefinedType":
return False, 0
@@ -23,7 +21,7 @@ def run(self, context):
if context.check_token(i, "TAB") is True:
context.new_error("TAB_INSTEAD_SPC", context.peek_token(i))
break
- if context.check_token(i, ["NEWLINE","ESCAPED_NEWLINE"]) is True:
+ if context.check_token(i, ["NEWLINE", "ESCAPED_NEWLINE"]) is True:
i = context.skip_ws(i + 1, nl=True)
i += 1
return False, 0
diff --git a/norminette/rules/check_global_naming.py b/norminette/rules/check_global_naming.py
index c6fa3b44..b5ea6844 100644
--- a/norminette/rules/check_global_naming.py
+++ b/norminette/rules/check_global_naming.py
@@ -1,5 +1,4 @@
-from rules import Rule
-from lexer import Lexer, TokenError
+from norminette.rules import Rule, Check
types = [
"INT",
@@ -19,26 +18,32 @@
"VOLATILE",
"EXTERN",
"SPACE",
- "TAB"
+ "TAB",
]
-class CheckGlobalNaming(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsVarDeclaration"]
+
+class CheckGlobalNaming(Rule, Check):
+ depends_on = (
+ "IsVarDeclaration",
+ )
def run(self, context):
"""
- Global variable names must be preceded by g_
+ Global variable names must be preceded by g_
"""
i = 0
- last_id = ""
if context.scope.name != "GlobalScope":
return False, 0
i = context.skip_ws(i)
_, i = context.check_type_specifier(i)
+ i = context.skip_ws(i)
while context.check_token(i, "IDENTIFIER") is False:
i += 1
- if context.peek_token(i).value.startswith("g_") is False:
+ if (
+ context.peek_token(i) is not None
+ and context.peek_token(i).value != "environ"
+ ):
+ context.new_warning("GLOBAL_VAR_DETECTED", context.peek_token(0))
+ if context.peek_token(i).value.startswith("g_") is False:
context.new_error("GLOBAL_VAR_NAMING", context.peek_token(i))
return False, i
diff --git a/norminette/rules/check_header.py b/norminette/rules/check_header.py
new file mode 100644
index 00000000..85148060
--- /dev/null
+++ b/norminette/rules/check_header.py
@@ -0,0 +1,44 @@
+from norminette.rules import Rule, Check
+import re
+
+
+class CheckHeader(Rule, Check):
+ def parse_header(self, context):
+ if context.check_token(0, "MULT_COMMENT") is False:
+ context.new_error("INVALID_HEADER", context.peek_token(0))
+ context.header_parsed = True
+ return
+ context.header += context.peek_token(0).value + "\n"
+
+ def check_header(self, context):
+ # val = r"\/\* \*{74} \*\/\n\/\*.*\*\/\n\/\*.*\*\/\n\/\*.{3}([^ ]*).*\*\/\n\/\*.*\*\/\n\/\* By: ([^ ]*).*\*\/\n\/\*.*\*\/\n\/\* Created: ([^ ]* [^ ]*) by ([^ ]*).*\*\/\n\/\* Updated: ([^ ]* [^ ]*) by ([^ ]*).*\*\/\n\/\*.*\*\/\n\/\* \*{74} \*\/\n" # noqa: E501
+ val_no_check_nl = r"\/\* \*{74} \*\/.\/\*.*\*\/.\/\*.*\*\/.\/\*.{3}([^ ]*).*\*\/.\/\*.*\*\/.\/\* By: ([^ ]*).*\*\/.\/\*.*\*\/.\/\* Created: ([^ ]* [^ ]*) by ([^ ]*).*\*\/.\/\* Updated: ([^ ]* [^ ]*) by ([^ ]*).*\*\/.\/\*.*\*\/.\/\* \*{74} \*\/." # noqa: E501
+
+ # correct_header = re.match(val, context.header)
+ regex = re.compile(val_no_check_nl, re.DOTALL)
+ # correct_header_no_nl = re.match(val_no_check_nl, context.header)
+ correct_header_no_nl = regex.search(context.header)
+ if correct_header_no_nl is None:
+ context.new_error("INVALID_HEADER", context.peek_token(0))
+ # else:
+ # print (correct_header.group(1,2,3,4,5,6))
+
+ def run(self, context):
+ """
+ Header checking. Just a warning for now. Does not trigger moulinette error
+ """
+ if context.header_parsed is True:
+ return False, 0
+ elif context.history[-1] == "IsComment" and context.header_parsed is False:
+ self.parse_header(context)
+ context.header_started = True
+ elif context.history[-1] != "IsComment" and context.header_started is True:
+ self.check_header(context)
+ context.header_parsed = True
+ elif (
+ context.header_started is False
+ and context.header_parsed is False
+ and context.history[-1] != "IsComment"
+ ):
+ context.new_error("INVALID_HEADER", context.peek_token(0))
+ context.header_parsed = True
diff --git a/norminette/rules/check_identifier_name.py b/norminette/rules/check_identifier_name.py
index 2adaf057..3dbcd89f 100644
--- a/norminette/rules/check_identifier_name.py
+++ b/norminette/rules/check_identifier_name.py
@@ -1,58 +1,35 @@
-from lexer import Token
-from rules import Rule
import string
-from scope import *
+from norminette.rules import Rule, Check
+from norminette.scope import GlobalScope, UserDefinedType
-assigns = [
- 'ASSIGN'
-]
-class CheckIdentifierName(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = []
+assigns = ["ASSIGN"]
+
+class CheckIdentifierName(Rule, Check):
def run(self, context):
"""
- Function can only be declared in the global scope
- User defined identifiers can only contain lowercase characters, '_' or digits
+ Function can only be declared in the global scope
+ User defined identifiers can only contain lowercase characters, '_' or digits
"""
- i = 0
- legal_characters = string.ascii_lowercase + string.digits + '_'
- legal_cap_characters = string.ascii_uppercase + string.digits + '_'
- if context.history[-1] == "IsFuncDeclaration" or context.history[-1] == "IsFuncPrototype":
+ legal_characters = string.ascii_lowercase + string.digits + "_"
+ if context.history[-1] == "IsFuncDeclaration":
sc = context.scope
if type(sc) is not GlobalScope and type(sc) is not UserDefinedType:
context.new_error("WRONG_SCOPE_FCT", context.peek_token(0))
- #while type(sc) != GlobalScope:
- #sc = sc.outer()
- #for c in sc.fnames[-1]:
- #if c not in legal_characters:
- #context.new_error(
- #"FORBIDDEN_CHAR_NAME",
- #context.peek_token(context.fname_pos))
- #break
- #passed_assign = False
- #err = None
- #hist = context.history[-1]
- #while i < context.tkn_scope and context.peek_token(i) is not None:
- #if context.check_token(i, assigns) is True:
- #passed_assign = True
- #if context.check_token(i, "IDENTIFIER") and hist not in ['IsFuncDeclaration', 'IsFuncPrototype']:
- #for c in context.peek_token(i).value:
- #if c not in legal_characters:
- #err = ("FORBIDDEN_CHAR_NAME", context.peek_token(i))
- #break
- #if err is not None and hist not in ['IsFuncDeclaration', 'IsFuncPrototype'] or (hist == 'IsVariable' and passed_assign == True):
- #for c in context.peek_token(i).value:
- #if c not in legal_cap_characters:
- #err = ("FORBIDDEN_CHAR_NAME", context.peek_token(i))
- #break
- #else:
- #err = None
- #if err is not None:
- #context.new_error(err[0], err[1])
- #break
- #i += 1
+ while type(sc) is not GlobalScope:
+ sc = sc.outer()
+ for c in sc.fnames[-1]:
+ if c not in legal_characters:
+ context.new_error(
+ "FORBIDDEN_CHAR_NAME", context.peek_token(context.fname_pos)
+ )
+ if len(context.scope.vars_name) > 0:
+ for val in context.scope.vars_name[::]:
+ for c in val.value:
+ if c not in legal_characters:
+ context.new_error("FORBIDDEN_CHAR_NAME", val)
+ break
+ context.scope.vars_name.remove(val)
return False, 0
diff --git a/norminette/rules/check_in_header.py b/norminette/rules/check_in_header.py
index dd325af6..e9dc5967 100644
--- a/norminette/rules/check_in_header.py
+++ b/norminette/rules/check_in_header.py
@@ -1,5 +1,5 @@
-from rules import Rule
-from scope import *
+from norminette.rules import Rule, Check
+
allowed_in_header = [
"IsVarDeclaration",
@@ -19,38 +19,35 @@
"IsFuncPrototype",
]
-class CheckInHeader(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = [
- "IsVarDeclaration",
- "IsUserDefinedType",
- "IsPreprocessorStatement",
- "IsEmptyLine",
- "IsBlockStart",
- "IsBlockEnd",
- "IsComment",
- "IsEndOfLine",
- "IsFuncPrototype",
- ]
+
+class CheckInHeader(Rule, Check):
+ depends_on = (
+ "IsVarDeclaration",
+ "IsUserDefinedType",
+ "IsPreprocessorStatement",
+ "IsEmptyLine",
+ "IsBlockStart",
+ "IsBlockEnd",
+ "IsComment",
+ "IsEndOfLine",
+ "IsFuncPrototype",
+ )
def run(self, context):
"""
- Each .h file must be protected against double inclusion
- Instructions allowed in header files:
- - Variable declaration
- - User defined types
- - Comments
- - Function prototypes
+ Each .h file must be protected against double inclusion
+ Instructions allowed in header files:
+ - Variable declaration
+ - User defined types
+ - Comments
+ - Function prototypes
"""
- if context.filetype != 'h':
+ if context.file.type != ".h":
return False, 0
sc = context.scope
- while sc.name != 'GlobalScope':
+ while sc.name != "GlobalScope":
sc = sc.get_outer()
if context.history[-1] not in allowed_in_header:
context.new_error("FORBIDDEN_IN_HEADER", context.peek_token(0))
return False, 0
- elif context.history[-1] in must_be_within_define and sc.header_protection != 1:
- context.new_error("HEADER_PROT_ALL", context.peek_token(0))
- return False, 0
\ No newline at end of file
+ return False, 0
diff --git a/norminette/rules/check_label.py b/norminette/rules/check_label.py
index 69d10a38..a7de4daf 100644
--- a/norminette/rules/check_label.py
+++ b/norminette/rules/check_label.py
@@ -1,15 +1,21 @@
-from rules import Rule
-from scope import *
+from norminette.rules import Rule, Check
-class CheckLabel(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsLabel"]
-
+class CheckLabel(Rule, Check):
def run(self, context):
"""
- Goto and labels are forbidden
+ Goto and labels are forbidden
"""
- context.new_error("LABEL_FBIDDEN", context.peek_token(0))
- return False, 0
\ No newline at end of file
+ i = 0
+ if context.scope.name not in ("Function", "ControlStructure"):
+ return False, 0
+ i = context.skip_ws(i)
+ if context.check_token(i, "GOTO"):
+ context.new_error("GOTO_FBIDDEN", context.peek_token(0))
+ return False, 0
+ if context.check_token(i, "IDENTIFIER") is False:
+ return False, 0
+ i = context.skip_ws(i + 1)
+ if context.check_token(i, "COLON"):
+ context.new_error("LABEL_FBIDDEN", context.peek_token(0))
+ return False, 0
diff --git a/norminette/rules/check_line_count.py b/norminette/rules/check_line_count.py
index f887a2bd..55418eef 100644
--- a/norminette/rules/check_line_count.py
+++ b/norminette/rules/check_line_count.py
@@ -1,30 +1,24 @@
-from rules import Rule
-from context import GlobalScope
+from norminette.context import GlobalScope
+from norminette.rules import Rule, Check
-class CheckLineCount(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = []
-
+class CheckLineCount(Rule, Check):
def run(self, context):
"""
- Each function can only have 25 lines between its opening and closing brackets
+ Each function can only have 25 lines between its opening and closing brackets
"""
- for t in context.tokens[:context.tkn_scope]:
- if t.type == "NEWLINE":
+ for t in context.tokens[: context.tkn_scope]:
+ if t.type == "NEWLINE" or t.type == "ESCAPED_NEWLINE":
context.scope.lines += 1
if type(context.scope) is GlobalScope:
- if context.get_parent_rule() == "CheckFuncDeclarations" \
- and context.scope.lines > 25:
+ if context.get_parent_rule() == "CheckFuncDeclarations" and context.scope.lines > 25:
context.new_error("TOO_MANY_LINES", context.tokens[context.tkn_scope])
return False, 0
if context.get_parent_rule() == "CheckBrace":
- if "LBRACE" in \
- [t.type for t in context.tokens[:context.tkn_scope + 1]]:
+ if "LBRACE" in [t.type for t in context.tokens[: context.tkn_scope + 1]]:
if type(context.scope) is GlobalScope:
return False, 0
else:
diff --git a/norminette/rules/check_line_indent.py b/norminette/rules/check_line_indent.py
index 75479ccf..9f319aa3 100644
--- a/norminette/rules/check_line_indent.py
+++ b/norminette/rules/check_line_indent.py
@@ -1,21 +1,25 @@
-from rules import Rule
-from scope import *
+from norminette.rules import Rule, Check
+from norminette.scope import GlobalScope
-
-class CheckLineIndent(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = []
-
+class CheckLineIndent(Rule, Check):
def run(self, context):
"""
- Each new scope (function, control structure, struct/enum type declaration) adds a tab to the general indentation
+ Each new scope (function, control structure, struct/enum type declaration) adds a tab to the general indentation
"""
expected = context.scope.indent
- if context.history[-1] == "IsEmptyLine" or context.history[-1] == "IsComment" or context.history[-1] == "IsPreprocessorStatement":
+ if context.history[-1] in [
+ "IsEmptyLine",
+ "IsComment",
+ "IsPreprocessorStatement",
+ "IsVariableDeclaration",
+ ]:
return False, 0
- if context.history[-1] != "IsPreprocessorStatement" and type(context.scope) is GlobalScope and context.scope.include_allowed == True:
+ if (
+ context.history[-1] != "IsPreprocessorStatement"
+ and type(context.scope) is GlobalScope
+ and context.scope.include_allowed is True
+ ):
context.scope.include_allowed = False
got = 0
while context.check_token(got, "TAB"):
@@ -24,11 +28,19 @@ def run(self, context):
if context.check_token(got, "RBRACE") is True:
expected -= 1
else:
- hist = context.history[:len(context.history) - 1]
+ hist = context.history[: len(context.history) - 1]
for item in hist[::-1]:
- if item == "IsEmptyLine" or item == "IsComment" or item == "IsPreprocessorStatement":
+ if (
+ item == "IsEmptyLine"
+ or item == "IsComment"
+ or item == "IsPreprocessorStatement"
+ ):
continue
- if item not in ["IsControlStatement", "IsFuncDeclaration", "IsUserDefinedType"]:
+ if item not in [
+ "IsControlStatement",
+ "IsFuncDeclaration",
+ "IsUserDefinedType",
+ ]:
break
else:
expected -= 1
@@ -39,4 +51,4 @@ def run(self, context):
elif got > expected:
context.new_error("TOO_MANY_TAB", context.peek_token(0))
return False, got
- return False, 0
\ No newline at end of file
+ return False, 0
diff --git a/norminette/rules/check_line_len.py b/norminette/rules/check_line_len.py
index 6a69d96e..c10b7f61 100644
--- a/norminette/rules/check_line_len.py
+++ b/norminette/rules/check_line_len.py
@@ -1,16 +1,16 @@
-from rules import Rule
+from norminette.rules import Rule, Check
-class CheckLineLen(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = []
-
+class CheckLineLen(Rule, Check):
def run(self, context):
"""
- Lines must not be over 80 characters long
+ Lines must not be over 80 characters long
"""
- for tkn in context.tokens[:context.tkn_scope]:
- if tkn.type == "NEWLINE" and tkn.pos[1] > 81:
+ i = 0
+ line_too_long = {}
+ for tkn in context.tokens[: context.tkn_scope]:
+ if tkn.pos[1] > 81 and tkn.pos[0] not in line_too_long:
context.new_error("LINE_TOO_LONG", tkn)
+ line_too_long[tkn.pos[0]] = True
+ i += 1
return False, 0
diff --git a/norminette/rules/check_many_instructions.py b/norminette/rules/check_many_instructions.py
index 0a909d12..a9d4a15b 100644
--- a/norminette/rules/check_many_instructions.py
+++ b/norminette/rules/check_many_instructions.py
@@ -1,26 +1,32 @@
-from lexer import Token
-from rules import Rule
-import string
+from norminette.rules import Rule, Check
-class CheckManyInstructions(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = [
- "IsAssignation",
- "IsBlockEnd",
- "IsControlStatement",
- "IsExpressionStatement",
- "IsFuncDeclaration",
- "IsFuncPrototype",
- "IsUserDefinedType",
- "IsVarDeclaration",
- "IsFunctionCall"]
+class CheckManyInstructions(Rule, Check):
+ depends_on = (
+ "IsAssignation",
+ "IsBlockEnd",
+ "IsControlStatement",
+ "IsExpressionStatement",
+ "IsFuncDeclaration",
+ "IsFuncPrototype",
+ "IsUserDefinedType",
+ "IsVarDeclaration",
+ "IsFunctionCall",
+ )
def run(self, context):
"""
- Each instruction must be separated by a newline
+ Each instruction must be separated by a newline
"""
if context.peek_token(0).pos[1] > 1:
context.new_error("TOO_MANY_INSTR", context.peek_token(0))
+ return False, 0
+ # if context.history[-1] in ["IsFuncDeclaration", "IsFuncPrototype", "IsControlStatement"]:
+ # return False, 0
+ # i = 0
+ # while i < context.tkn_scope:
+ # if context.check_token(i, SEPARATORS) is True:
+ # context.new_error("TOO_MANY_INSTR", context.peek_token(0))
+ # return False, 0
+ # i += 1
return False, 0
diff --git a/norminette/rules/check_nest_line_indent.py b/norminette/rules/check_nest_line_indent.py
index d2812633..37ed2f3f 100644
--- a/norminette/rules/check_nest_line_indent.py
+++ b/norminette/rules/check_nest_line_indent.py
@@ -1,5 +1,5 @@
-from rules import Rule
-from scope import *
+from norminette.rules import Rule, Check
+
operators = [
"RIGHT_ASSIGN",
@@ -17,7 +17,6 @@
"EQUALS",
"NOT_EQUAL",
"ASSIGN",
- "SEMI_COLON",
"DOT",
"NOT",
"MINUS",
@@ -39,44 +38,56 @@
]
nest_kw = ["RPARENTHESIS", "LPARENTHESIS", "NEWLINE"]
-class CheckNestLineIndent(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsControlStatement", "IsExpressionStatement"]
+
+class CheckNestLineIndent(Rule, Check):
+ depends_on = (
+ "IsControlStatement",
+ "IsExpressionStatement",
+ "IsDeclaration",
+ )
def find_nest_content(self, context, nest, i):
expected = context.scope.indent + nest
while context.peek_token(i) is not None:
- if context.check_token(i, "LPARENTHESIS") is True:
+ if context.check_token(i, ["LPARENTHESIS", "LBRACE", "LBRACKET"]) is True:
i += 1
- i = self.find_nest_content(context, nest + 1, i)
+ i = self.find_nest_content(context, nest + 1, i) + 1
+ if context.check_token(i, ["RBRACE", "RBRACKET", "RPARENTHESIS"]):
+ return i
elif context.check_token(i, "NEWLINE") is True:
if context.check_token(i - 1, operators):
context.new_error("EOL_OPERATOR", context.peek_token(i - 1))
+ if context.check_token(i, "SEMI_COLON") is True:
+ return i
indent = 0
i += 1
while context.check_token(i, "TAB") is True:
indent += 1
i += 1
+ if context.check_token(i, ["RBRACE", "RBRACKET", "RPARENTHESIS"]):
+ expected -= 1
if indent > expected:
context.new_error("TOO_MANY_TAB", context.peek_token(i))
elif indent < expected:
context.new_error("TOO_FEW_TAB", context.peek_token(i))
- elif context.check_token(i, "RPARENTHESIS"):
- return i
- i += 1
+ if context.check_token(i, ["RBRACE", "RBRACKET", "RPARENTHESIS"]):
+ expected += 1
+ else:
+ i += 1
return i
def run(self, context):
"""
- Each nest (parenthesis, brackets, braces) adds a tab to the general indentation
+ Each nest (parenthesis, brackets, braces) adds a tab to the general indentation
"""
i = 0
- expected = context.scope.indent
nest = 0
if context.history[-1] == "IsEmptyLine":
return False, 0
- while context.peek_token(i) and context.check_token(i, ["LPARENTHESIS", "NEWLINE"]) is False:
+ while (
+ context.peek_token(i)
+ and context.check_token(i, ["LPARENTHESIS", "NEWLINE"]) is False
+ ):
i += 1
if context.check_token(i, "NEWLINE") is True:
return False, 0
diff --git a/norminette/rules/check_newline_indent.py b/norminette/rules/check_newline_indent.py
new file mode 100644
index 00000000..c75f15aa
--- /dev/null
+++ b/norminette/rules/check_newline_indent.py
@@ -0,0 +1,29 @@
+from norminette.rules import Rule, Check
+
+
+class CheckNewlineIndent(Rule, Check):
+ depends_on = [
+ "IsDeclaration",
+ "IsAssignation",
+ "IsCast",
+ "IsExpressionStatement",
+ ]
+
+ def run(self, context):
+ """
+ If a line has a newline inside, we must check for indent - authorized : same indent/same + 1 indent
+ """
+ if context.scope.name != "Function":
+ return False, 0
+ expected = context.scope.indent
+ i = context.find_in_scope("NEWLINE", nested=False) + 1
+ if i != -1 and i < context.tkn_scope - 2:
+ start = i
+ got = 0
+ while context.check_token(start + got, "TAB"):
+ got += 1
+ if got > expected + 1:
+ context.new_error("TOO_MANY_TAB", context.peek_token(start))
+ if got < expected:
+ context.new_error("TOO_FEW_TAB", context.peek_token(start))
+ return False, 0
diff --git a/norminette/rules/check_operators_spacing.py b/norminette/rules/check_operators_spacing.py
index e4ee7fd0..a87f6849 100644
--- a/norminette/rules/check_operators_spacing.py
+++ b/norminette/rules/check_operators_spacing.py
@@ -1,4 +1,4 @@
-from rules import Rule
+from norminette.rules import Rule, Check
operators = [
"RIGHT_ASSIGN",
@@ -40,7 +40,7 @@
"BWISE_AND",
"RIGHT_SHIFT",
"LEFT_SHIFT",
- "TERN_CONDITION"
+ "TERN_CONDITION",
]
assign_operators = [
@@ -54,11 +54,10 @@
"AND_ASSIGN",
"XOR_ASSIGN",
"OR_ASSIGN",
- "ASSIGN"
+ "ASSIGN",
]
-gps_operators = [
-]
+gps_operators = []
ps_operators = [
# operators that should be prefixed and suffixed by a space
@@ -77,9 +76,8 @@
"EQUALS", # ==
"NOT_EQUAL", # !=
"ASSIGN", # =
- "COLON", # :
"DIV", # /
- "MULT", # *
+ "MULT", # *
"MODULO", # %
"LESS_THAN", # <
"MORE_THAN", # >
@@ -87,7 +85,7 @@
"OR", # |
"RIGHT_SHIFT", # >>
"LEFT_SHIFT", # <<
- "TERN_CONDITION" # ?
+ "TERN_CONDITION", # ?
]
p_operators = [
@@ -97,7 +95,9 @@
s_operators = [
# operators that should only be suffixed by a space
- "COMMA" # ,
+ "COMMA", # ,
+ # Where do i put that shit
+ # "COLON", # :
]
son_operators = [
@@ -112,105 +112,222 @@
"BWISE_XOR", # ^
"BWISE_OR", # |
"BWISE_AND", # &
- "BWISE_NOT", # ~
+ "BWISE_NOT", # ~
]
-rnests = [
- "RPARENTHESIS",
- "RBRACE",
- "RBRACKET"
+glued_operators = ["MULT", "PLUS", "MINUS", "DIV", "NOT", "BWISE_NOT"]
+
+spec_operators = [
+ "NOT",
+ "BWISE_NOT",
+ "DIV",
]
+rnests = ["RPARENTHESIS", "RBRACE", "RBRACKET"]
+
lnests = [
"LBRACE",
"LBRACKET",
"LPARENTHESIS",
]
-left_auth = [
-]
+left_auth = []
-right_auth = [
-]
+right_auth = []
-whitespaces = [
- "NEWLINE",
- "SPACE",
- "TAB"
-]
+whitespaces = ["NEWLINE", "SPACE", "TAB"]
-class CheckOperatorsSpacing(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = [
- "IsFuncDeclaration",
- "IsFuncPrototype",
- "IsExpressionStatement",
- "IsAssignation",
- "IsControlStatement",
- "IsVarDeclaration",
- "IsFunctionCall",
- "IsDeclaration",
- ]
- self.last_seen_tkn = None
- def check_prefix(self, context, pos):
- tmp = -1
+class CheckOperatorsSpacing(Rule, Check):
+ depends_on = (
+ "IsFuncDeclaration",
+ "IsFuncPrototype",
+ "IsExpressionStatement",
+ "IsAssignation",
+ "IsControlStatement",
+ "IsVarDeclaration",
+ "IsFunctionCall",
+ "IsDeclaration",
+ )
- if pos > 0 and context.peek_token(pos - 1).type != "SPACE":
- context.new_error("SPC_BFR_OPERATOR", context.peek_token(pos))
- if pos + 1 < len(context.tokens[:context.tkn_scope]) \
- and context.peek_token(pos + 1).type == "SPACE":
+ def check_prefix(self, context, pos):
+ if pos > 0 and context.check_token(pos, ["TAB", "SPACE"]):
+ context.new_error("", context.peek_token(pos))
+ if (
+ pos + 1 < len(context.tokens[: context.tkn_scope])
+ and context.peek_token(pos + 1).type == "SPACE"
+ ):
context.new_error("NO_SPC_AFR_OPR", context.peek_token(pos))
def check_lnest(self, context, pos):
- if context.history[-1] == "IsFuncDeclaration" or context.history[-1] == "IsFuncPrototype":
+ if (
+ context.history[-1] == "IsFuncDeclaration"
+ or context.history[-1] == "IsFuncPrototype"
+ ):
return False
tmp = pos + 1
- #Here is `(_`
- while context.peek_token(tmp) and context.check_token(tmp, ["SPACE", "TAB"]) is True:
+ # Here is `(_`
+ while (
+ context.peek_token(tmp)
+ and context.check_token(tmp, ["SPACE", "TAB"]) is True
+ ):
tmp += 1
if context.check_token(tmp, "NEWLINE") is False:
- if context.check_token(tmp, lnests + rnests + ["SEMI_COLON", "PTR", "DOT"]) is True and tmp != pos + 1:
+ if (
+ context.check_token(tmp, lnests + rnests + ["SEMI_COLON", "PTR", "DOT"])
+ is True
+ and tmp != pos + 1
+ ):
context.new_error("SPC_AFTER_PAR", context.peek_token(pos))
- elif context.check_token(tmp, lnests + rnests + ["SEMI_COLON", "PTR", "DOT"]) is False and tmp != pos + 1:
+ elif (
+ context.check_token(tmp, lnests + rnests + ["SEMI_COLON", "PTR", "DOT"])
+ is False
+ and tmp != pos + 1
+ ):
context.new_error("NO_SPC_AFR_PAR", context.peek_token(pos))
tmp = pos - 1
- #Here is `_(`
+ # Here is `_(`
while tmp >= 0 and context.check_token(tmp, ["SPACE", "TAB"]) is True:
tmp -= 1
if context.check_token(tmp, "NEWLINE") is False:
- if context.check_token(tmp, lnests + rnests + ["SEMI_COLON", "PTR", "DOT", "INC", "DEC", "MULT", "BWISE_AND", "IDENTIFIER", "SIZEOF"]) is True and tmp != pos - 1:
- if context.check_token(tmp, ["MULT", "BWISE_AND"]) is True and context.is_operator == False:
+ if (
+ context.check_token(
+ tmp,
+ lnests
+ + rnests
+ + [
+ "SEMI_COLON",
+ "PTR",
+ "DOT",
+ "INC",
+ "DEC",
+ "MULT",
+ "BWISE_AND",
+ "IDENTIFIER",
+ "SIZEOF",
+ ],
+ )
+ is True
+ and tmp != pos - 1
+ ):
+ if (
+ context.check_token(tmp, ["MULT", "BWISE_AND"]) is True
+ and context.is_operator is False
+ ):
context.new_error("NO_SPC_BFR_PAR", context.peek_token(pos))
- elif context.check_token(tmp, lnests + rnests + ["SEMI_COLON", "PTR", "DOT", "INC", "DEC", "MULT", "BWISE_AND", "BWISE_OR", "BWISE_XOR", "BWISE_NOT", "IDENTIFIER", "SIZEOF", "NOT", "MINUS", "PLUS"]) is False and tmp == pos - 1:
+ elif (
+ context.check_token(
+ tmp,
+ lnests
+ + rnests
+ + [
+ "SEMI_COLON",
+ "PTR",
+ "DOT",
+ "INC",
+ "DEC",
+ "MULT",
+ "BWISE_AND",
+ "BWISE_OR",
+ "BWISE_XOR",
+ "BWISE_NOT",
+ "IDENTIFIER",
+ "SIZEOF",
+ "NOT",
+ "MINUS",
+ "PLUS",
+ "CONSTANT",
+ "CHAR_CONSTANT",
+ "STRING",
+ ],
+ )
+ is False
+ and tmp == pos - 1
+ ):
context.new_error("SPC_BFR_PAR", context.peek_token(pos))
return False
def check_rnest(self, context, pos):
- if context.history[-1] == "IsFuncDeclaration" or context.history[-1] == "IsFuncPrototype":
+ if (
+ context.history[-1] == "IsFuncDeclaration"
+ or context.history[-1] == "IsFuncPrototype"
+ ):
return False
tmp = pos + 1
- #Here is `)_`
- while context.peek_token(tmp) and context.check_token(tmp, ["SPACE", "TAB"]) is True:
+ # Here is `)_`
+ while (
+ context.peek_token(tmp)
+ and context.check_token(tmp, ["SPACE", "TAB"]) is True
+ ):
tmp += 1
if context.check_token(tmp, "NEWLINE") is False:
- if context.check_token(tmp, lnests + rnests + ["SEMI_COLON", "PTR", "DOT", "INC", "DEC"]) is True and tmp != pos + 1:
+ if (
+ context.check_token(
+ tmp, lnests + rnests + ["SEMI_COLON", "PTR", "DOT", "INC", "DEC"]
+ )
+ is True
+ and tmp != pos + 1
+ ):
context.new_error("NO_SPC_AFR_PAR", context.peek_token(pos))
- elif context.check_token(tmp, lnests + rnests + ["SEMI_COLON", "PTR", "DOT", "INC", "DEC", "MULT", "BWISE_AND", "IDENTIFIER", "COMMA"]) is False and tmp == pos + 1:
+ elif (
+ context.check_token(
+ tmp,
+ lnests
+ + rnests
+ + [
+ "SEMI_COLON",
+ "PTR",
+ "DOT",
+ "INC",
+ "DEC",
+ "MINUS",
+ "MULT",
+ "BWISE_AND",
+ "IDENTIFIER",
+ "COMMA",
+ "STRING",
+ "CONSTANT",
+ "PLUS",
+ ],
+ )
+ is False
+ and tmp == pos + 1
+ ):
context.new_error("SPC_AFTER_PAR", context.peek_token(pos))
tmp = pos - 1
- #Here is `_)`
+ # Here is `_)`
while tmp > 0 and context.check_token(tmp, ["SPACE", "TAB"]) is True:
tmp -= 1
if context.check_token(tmp, "NEWLINE") is False:
- if context.check_token(tmp, lnests + rnests + ["SEMI_COLON", "PTR", "DOT", "INC", "DEC", "MULT", "BWISE_AND", "IDENTIFIER"]) is True and tmp != pos - 1:
+ if (
+ context.check_token(
+ tmp,
+ lnests
+ + rnests
+ + [
+ "SEMI_COLON",
+ "PTR",
+ "DOT",
+ "INC",
+ "DEC",
+ "MULT",
+ "BWISE_AND",
+ "IDENTIFIER",
+ "CONSTANT",
+ ],
+ )
+ is True
+ and tmp != pos - 1
+ ):
context.new_error("NO_SPC_BFR_PAR", context.peek_token(pos))
return False
def check_suffix(self, context, pos):
- if pos + 1 < len(context.tokens[:context.tkn_scope]) \
- and not context.check_token(pos + 1, ["SPACE", "NEWLINE", "TAB"]):
+ if pos + 1 < len(
+ context.tokens[: context.tkn_scope]
+ ) and not context.check_token(
+ pos + 1, ["SPACE", "NEWLINE", "TAB"] + glued_operators + rnests
+ ):
context.new_error("SPC_AFTER_OPERATOR", context.peek_token(pos))
if pos > 0 and context.peek_token(pos - 1).type == "SPACE":
context.new_error("NO_SPC_BFR_OPR", context.peek_token(pos))
@@ -221,48 +338,121 @@ def check_glued_prefix_and_suffix(self, context, pos):
tmp = -1
while context.check_token(pos + tmp, "TAB") is True:
tmp -= 1
- if context.check_token(pos + tmp, "NEWLINE") is True:
+ if (
+ context.check_token(
+ pos + tmp, ["NEWLINE", "ESCAPED_NEWLINE"] + glued_operators
+ )
+ is True
+ ):
return False, 0
context.new_error("SPC_BFR_OPERATOR", context.peek_token(pos))
- if pos + 1 < len(context.tokens[:context.tkn_scope]) and context.check_token(pos + 1, ["SPACE", "LPARENTHESIS", "LBRACKET", "LBRACE", "NEWLINE"]) is False:
+ if (
+ pos + 1 < len(context.tokens[: context.tkn_scope])
+ and context.check_token(
+ pos + 1,
+ ["SPACE", "LPARENTHESIS", "LBRACKET", "LBRACE", "NEWLINE"]
+ + glued_operators,
+ )
+ is False
+ ):
context.new_error("SPC_AFTER_OPERATOR", context.peek_token(pos))
def check_prefix_and_suffix(self, context, pos):
- if pos > 0 and context.check_token(pos - 1, ['SPACE', 'LPARENTHESIS', 'RPARENTHESIS', "LBRACKET", 'RBRACKET']) is False:
+ if (
+ pos > 0
+ and context.check_token(
+ pos - 1, ["SPACE", "LPARENTHESIS", "LBRACKET"] + glued_operators
+ )
+ is False
+ ):
if context.check_token(pos - 1, "TAB") is True:
tmp = -1
while context.check_token(pos + tmp, "TAB") is True:
tmp -= 1
- if context.check_token(pos + tmp, "NEWLINE") is True:
+ if (
+ context.check_token(pos + tmp, ["NEWLINE", "ESCAPED_NEWLINE"])
+ is True
+ ):
return False, 0
+ if (
+ context.check_token(pos - 1, "RPARENTHESIS")
+ and context.parenthesis_contain(context.skip_nest_reverse(pos - 1))[0]
+ == "cast"
+ ):
+ return False, 0
context.new_error("SPC_BFR_OPERATOR", context.peek_token(pos))
- if pos + 1 < len(context.tokens[:context.tkn_scope]) \
- and context.check_token(pos + 1, ['SPACE', 'LPARENTHESIS', 'RPARENTHESIS', "LBRACKET", 'RBRACKET']) is False:
- context.new_error("SPC_AFTER_OPERATOR", context.peek_token(pos))
+ if (
+ pos + 1 < len(context.tokens[: context.tkn_scope])
+ and context.check_token(
+ pos + 1,
+ [
+ "SPACE",
+ "LPARENTHESIS",
+ "RPARENTHESIS",
+ "LBRACKET",
+ "RBRACKET",
+ "NEWLINE",
+ "COMMA",
+ ]
+ + spec_operators,
+ )
+ is False
+ ):
+ tmp = pos - 1
+ while context.check_token(tmp, ["SPACE", "TAB"]):
+ tmp -= 1
+ if context.check_token(tmp, "RPARENTHESIS"):
+ tmp = context.skip_nest_reverse(tmp)
+ if context.parenthesis_contain(tmp)[0] != "cast":
+ context.new_error("SPC_AFTER_OPERATOR", context.peek_token(pos))
+ elif context.check_token(tmp, glued_operators) is False and not (
+ context.check_token(pos, ["PLUS", "MINUS"])
+ and context.check_token(pos + 1, "CONSTANT")
+ ):
+ context.new_error("SPC_AFTER_OPERATOR", context.peek_token(pos))
def check_glued_operator(self, context, pos):
glued = [
- 'LPARENTHESIS',
- 'LBRACKET',
- 'LBRACE',
+ "LPARENTHESIS",
+ "LBRACKET",
+ "LBRACE",
]
if context.check_token(pos + 1, ["SPACE", "TAB"]) is True:
context.new_error("SPC_AFTER_OPERATOR", context.peek_token(pos))
pos -= 1
- if context.check_token(pos, glued + ['SPACE', 'TAB']) is False:
- context.new_error("SPC_BFR_OPERATOR", context.peek_token(pos))
- while pos >= 0 and context.check_token(pos, ['SPACE', 'TAB']) is True:
+ if (
+ context.check_token(pos, glued + ["SPACE", "TAB"] + glued_operators)
+ is False
+ ):
+ context.new_error("SPC_BFR_OPERATOR", context.peek_token(pos))
+ while pos >= 0 and context.check_token(pos, ["SPACE", "TAB"]) is True:
pos -= 1
if pos >= 0 and context.check_token(pos, glued) is True:
context.new_error("NO_SPC_BFR_OPR", context.peek_token(pos))
-
def check_combined_op(self, context, pos):
- lpointer = ["SPACE", "TAB", "LPARENTHESIS", "LBRACKET", "MULT", "NOT", "RPARENTHESIS", "RBRACKET", "RBRACE"]
- lsign = operators + ["LBRACKET"]
+ lpointer = [
+ "SPACE",
+ "TAB",
+ "LPARENTHESIS",
+ "LBRACKET",
+ "MULT",
+ "NOT",
+ "RPARENTHESIS",
+ "RBRACKET",
+ "RBRACE",
+ "MINUS",
+ "PLUS",
+ "BWISE_NOT",
+ "BWISE_OR",
+ "BWISE_AND",
+ "BWISE_XOR",
+ ]
i = 0
if context.peek_token(pos).type == "MULT":
- if context.check_token(pos - 1, lpointer) == False and context.is_glued_operator(pos - 1) is True:
+ if context.check_token(pos - 1, lpointer) is False and (
+ context.is_glued_operator(pos - 1) is True
+ ): # or context.check_token(pos - 1, c_operators) is False):
context.new_error("SPC_BFR_POINTER", context.peek_token(pos))
if context.check_token(pos + 1, ["SPACE", "TAB"]):
context.new_error("SPC_AFTER_POINTER", context.peek_token(pos))
@@ -271,23 +461,22 @@ def check_combined_op(self, context, pos):
i += 1
if context.peek_token(pos + i).type == "SPACE":
context.new_error("SPC_AFTER_POINTER", context.peek_token(pos + i))
- return (i)
+ return i
def run(self, context):
"""
- Some operators must be followed by a space,
- some must be only followed by a space,
- and the rest must be preceded and followed by a space.
+ Some operators must be followed by a space,
+ some must be only followed by a space,
+ and the rest must be preceded and followed by a space.
"""
- self.last_seen_tkn = None
i = 0
- while i < len(context.tokens[:context.tkn_scope]):
+ while i < len(context.tokens[: context.tkn_scope]):
if context.check_token(i, ["MULT", "BWISE_AND"]) is True:
if context.is_operator(i) is False:
self.check_combined_op(context, i)
i += 1
continue
- if context.check_token(i, c_operators)is True:
+ if context.check_token(i, c_operators) is True:
if context.is_glued_operator(i) is True:
self.check_glued_operator(context, i)
else:
@@ -298,18 +487,18 @@ def run(self, context):
self.check_lnest(context, i)
elif context.check_token(i, rnests) is True:
self.check_rnest(context, i)
- elif context.check_token(i, ps_operators)is True:
+ elif context.check_token(i, ps_operators) is True:
self.check_prefix_and_suffix(context, i)
- elif context.check_token(i, gps_operators)is True:
+ elif context.check_token(i, gps_operators) is True:
self.check_glued_prefix_and_suffix(context, i)
- elif context.check_token(i, s_operators)is True:
+ elif context.check_token(i, s_operators) is True:
self.check_suffix(context, i)
- elif context.check_token(i, son_operators) is True and \
- context.check_token(i + 1, "NEWLINE") is False:
+ elif (
+ context.check_token(i, son_operators) is True
+ and context.check_token(i + 1, "NEWLINE") is False
+ ):
self.check_suffix(context, i)
- elif context.check_token(i, p_operators)is True:
+ elif context.check_token(i, p_operators) is True:
self.check_prefix(context, i)
- if context.check_token(i, whitespaces) is False:
- self.last_seen_tkn = context.peek_token(i)
i += 1
return False, 0
diff --git a/norminette/rules/check_preprocessor_define.py b/norminette/rules/check_preprocessor_define.py
index 4c47e5fe..66624244 100644
--- a/norminette/rules/check_preprocessor_define.py
+++ b/norminette/rules/check_preprocessor_define.py
@@ -1,98 +1,54 @@
-from rules import Rule
-from lexer import Lexer, TokenError
-from scope import *
+from norminette.rules import Rule, Check
-class CheckPreprocessorDefine(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsPreprocessorStatement"]
-
- def skip_define_nest(self, i, tkns):
- eq = {
- 'LPARENTHESIS': 'RPARENTHESIS',
- 'LBRACKET': 'RBRACKET',
- 'LBRACE': 'RBRACE'
- }
- eq_val = eq[tkns[i].type]
- while i < len(tkns) and tkns[i].type != eq_val:
- i += 1
- return i
-
- def check_function_declaration(self, context):
- i = context.skip_ws(0)
- if context.check_token(i, ['#IF', "#ELSE", "IFDEF", "IFNDEF"]) is False:
- return
- context.tmp_scope = context.scope
- context.scope = context.scope.get_outer()
+class CheckPreprocessorDefine(Rule, Check):
+ depends_on = (
+ "IsPreprocessorStatement",
+ )
def run(self, context):
"""
- Preprocessor statements must be defined only in the global scope
- Defined names must be in capital letters
- Define cannot contain newlines
- Define can only contain constant values, such as integers and strings
+ Defined names must be in capital letters
+ Define can only contain constant values, such as integers and strings
"""
i = context.skip_ws(0)
- if len(context.history) > 1 and context.history[-2] == "IsFuncDeclaration":
- self.check_function_declaration(context)
- if type(context.scope) is not GlobalScope:
- if type(context.scope) == Function and context.scope.multiline == False:
- pass
- else:
- context.new_error("PREPROC_GLOBAL", context.peek_token(0))
- if context.check_token(i, "DEFINE") is False:
- return False, 0
- val = context.peek_token(i).value.split("define", 1)[1]
- content = Lexer(val, context.peek_token(i).pos[0])
- tkns = content.get_tokens()
- i = 0
- identifiers = []
- protection = context.filename.upper().split('/')[-1].replace('.', '_')
- for tkn in tkns:
- if tkn.type == "ESCAPED_NEWLINE":
- context.new_error("NEWLINE_DEFINE", tkn)
- elif tkn.type in ["TAB", "SPACE"]:
- i += 1
- continue
- elif tkn.type == "IDENTIFIER" and len(identifiers) == 0:
- if tkn.value.isupper() is False:
- context.new_error("MACRO_NAME_CAPITAL", tkn)
- identifiers.append(tkn)
- tmp = i
- while tmp < len(tkns) - 1 and tkns[tmp].type in ["SPACE", "TAB", "IDENTIFIER"]:
- tmp += 1
- if tmp == (len(tkns) - 1) and context.filetype == 'h':
- if context.scope.header_protection == 0:
- if identifiers[0].value == protection:
- context.scope.header_protection = 1
- elif identifiers[0].value != protection:
- context.new_error("HEADER_PROT_NAME", tkns[1])
- elif context.filetype == 'c' and context.scope.include_allowed == True and \
- (len(tkns) > tmp + 1 or (len(tkns) == tmp + 1 and identifiers[0].value != protection \
- and context.scope.header_protection == -1 )):
- context.scope.include_allowed = False
+ i += 1 # skip HASH
+ i = context.skip_ws(i)
+ if not context.check_token(i, "IDENTIFIER"):
+ return
+ if not context.peek_token(i).value == "define":
+ return
+ if context.preproc.skip_define:
+ return
+ i += 1 # skip DEFINE
+ i = context.skip_ws(i)
+
+ if not context.peek_token(i).value.isupper():
+ context.new_error("MACRO_NAME_CAPITAL", context.peek_token(i))
+ i += 1 # skip macro name
- elif tkn.type in ["IDENTIFIER", "STRING", "CONSTANT"]:
- if len(identifiers) == 1:
- if tkn.type == "IDENTIFIER" and tkn.value.isupper() is False:
- context.new_error("PREPROC_CONSTANT", tkn)
- identifiers.append(tkn)
- elif len(identifiers) == 0:
- context.new_error("INCORRECT_DEFINE", tkn)
- else:
- context.new_error("TOO_MANY_VALS", tkn)
- elif tkn.type == "LPARENTHESIS":
- if len(identifiers) == 0:
- continue
- elif len(identifiers) == 1 and tkns[i - 1].type in ["SPACE", "TAB"]:
- continue
- else:
- context.new_error("PREPROC_CONSTANT", tkn)
- elif tkn.type in ["LBRACKET", "LBRACE"]:
- context.new_error("PREPROC_CONSTANT", tkn)
+ if context.check_token(i, "LPARENTHESIS"):
+ context.new_error("MACRO_FUNC_FORBIDDEN", context.peek_token(i))
+ while not context.check_token(i, "RPARENTHESIS"):
+ i += 1
+ i += 1
+ i = context.skip_ws(i)
+ # It is obscure what `#define` can hold in its value, see:
+ # - https://github.com/42School/norminette/issues/12
+ # - https://github.com/42School/norminette/issues/127
+ # - https://github.com/42School/norminette/issues/282
+ #
+ if context.check_token(i, ("MINUS", "PLUS", "BWISE_NOT")):
i += 1
- if context.filetype == 'h' and context.scope.header_protection != 1:
- context.new_error("HEADER_PROT_ALL", context.peek_token(0))
- return False, 0
+ i = context.skip_ws(i)
+ if not context.check_token(i, ("CONSTANT", "IDENTIFIER")):
+ context.new_error("PREPROC_CONSTANT", context.peek_token(i))
+ return
+ i += 1
+ elif context.check_token(i, ("CONSTANT", "IDENTIFIER", "STRING", "CHAR_CONST")):
+ i += 1
+
+ i = context.skip_ws(i, comment=True)
+ if context.peek_token(i) and not context.check_token(i, "NEWLINE"):
+ context.new_error("PREPROC_CONSTANT", context.peek_token(i))
diff --git a/norminette/rules/check_preprocessor_include.py b/norminette/rules/check_preprocessor_include.py
index 1778fae3..9b4eabee 100644
--- a/norminette/rules/check_preprocessor_include.py
+++ b/norminette/rules/check_preprocessor_include.py
@@ -1,44 +1,59 @@
-from rules import Rule
-from lexer import Lexer, TokenError
-from scope import *
+import os.path
+import itertools
+from norminette.rules import Rule, Check
-class CheckPreprocessorInclude(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsPreprocessorStatement"]
+
+class CheckPreprocessorInclude(Rule, Check):
+ depends_on = (
+ "IsPreprocessorStatement",
+ )
def run(self, context):
"""
- Includes must be at the start of the file
- You cannot include anything that isn't an header file
+ Includes must be at the start of the file
+ You cannot include anything that isn't an header file
"""
- i = 0
- filetype = ''
- if context.check_token(i, "INCLUDE") is False:
+ i = hash_index = context.skip_ws(0)
+ i += 1 # skip HASH
+ i = context.skip_ws(i)
+ if context.check_token(i, "IDENTIFIER") is False:
+ return False, 0
+ if context.peek_token(i).value != "include":
return False, 0
- if type(context.scope) is not GlobalScope or context.scope.include_allowed == False:
- context.new_error("INCLUDE_START_FILE", context.peek_token(i))
- return True, i
- val = context.peek_token(i).value.split("include", 1)[1]
- content = Lexer(val, context.peek_token(i).pos[0])
- tkns = content.get_tokens()
- i = 1
- while i < len(tkns) and tkns[i].type in ["TAB", "SPACE"]:
- i += 1
- if i < len(tkns) and tkns[i].type == "LESS_THAN":
- i = len(tkns) - 1
- while i > 0:
- if i < len(tkns) - 1 and tkns[i].type == "DOT":
- i += 1
- filetype = tkns[i].value
- break
- i -= 1
- elif i < len(tkns) and tkns[i].type == "STRING":
- try:
- filetype = tkns[i].value.split('.')[-1][0]
- except:
- filetype = ''
- if filetype and filetype != 'h':
- context.new_error("INCLUDE_HEADER_ONLY", context.peek_token(0))
- return False, 0
+ if not self.is_in_start_of_file(context):
+ context.new_error("INCLUDE_START_FILE", context.peek_token(hash_index))
+
+ i += 1 # skip INCLUDE
+ i = context.skip_ws(i)
+ if context.check_token(i, "STRING"): # "niumxp.h"
+ file = context.peek_token(i).value.strip().strip('"')
+ file, extension = os.path.splitext(file)
+ if extension != ".h":
+ context.new_error("INCLUDE_HEADER_ONLY", context.peek_token(i))
+ else: #
+ less = context.peek_token(i)
+ while not context.check_token(i, "MORE_THAN"):
+ i += 1
+ last = context.peek_token(i - 1)
+ prev = context.peek_token(i - 2)
+ if last.type != "IDENTIFIER" or not (last.value == "h" and prev.type == "DOT"):
+ context.new_error("INCLUDE_HEADER_ONLY", less)
+
+ i += 1 # skip MORE_THAN or STRING
+
+ return True, i
+
+ def is_in_start_of_file(self, context):
+ """Check if the include is at the start of the file
+ """
+ headers = (
+ "IsComment",
+ "IsEmptyLine",
+ "IsPreprocessorStatement",
+ )
+ history = itertools.filterfalse(lambda item: item in headers, context.history)
+ return (
+ context.scope.include_allowed
+ and next(history, None) is None
+ )
diff --git a/norminette/rules/check_preprocessor_indent.py b/norminette/rules/check_preprocessor_indent.py
index 07b834fe..1d5e766f 100644
--- a/norminette/rules/check_preprocessor_indent.py
+++ b/norminette/rules/check_preprocessor_indent.py
@@ -1,51 +1,93 @@
-from rules import Rule
+from norminette.rules import Rule, Check
+from norminette.scope import GlobalScope
-ALLOWED_PREPROC = ["DEFINE", "IFNDEF", "IFDEF", "#IF", "ELIF", "#ELSE", "ENDIF", "INCLUDE"]
-TOO_MUCH_INDENT = ["IFNDEF", "IFDEF", "ELIF", "#IF", "#ELSE"]
+ARGUMENTED_PREPROCESSORS = (
+ "include",
+ "import",
+ "if", # just in case
+ None, # "if" is a special case ...
+ "ifdef",
+ "ifndef",
+ "elif",
+ "error",
+ "pragma",
+ "undef",
+ "define",
+)
-class CheckPreprocessorIndent(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsPreprocessorStatement"]
-
- def get_space_number(self, val):
- val = val[1:]
- spaces = 0
- for i in val:
- if i == ' ':
- spaces += 1
- else:
- return spaces
+class CheckPreprocessorIndent(Rule, Check):
+ depends_on = (
+ "IsPreprocessorStatement",
+ )
def run(self, context):
"""
- Preprocessor statements must be indented by an additionnal space for each #ifdef/#ifndef/#if
- statement.
- Structure is `#{indentation}preproc_statement`
- Preprocessor must always be at the start of the line
+ Preprocessor statements must be indented by an additionnal space for each #ifdef/#ifndef/#if
+ statement.
+ Structure is `#{indentation}preproc_statement`
+ Preprocessor must always be at the start of the line
+ Argumented preprocessor statements must have a space between the identifier and the argument
"""
- i = 0
- i = context.skip_ws(i)
- tken = context.peek_token(i)
- current_indent = context.preproc_scope_indent
- if context.peek_token(i).pos[1] != 1:
- context.new_error("PREPROC_START_LINE", context.peek_token(0))
- tken = context.peek_token(i)
- if context.check_token(i, ALLOWED_PREPROC) is False:
- context.new_error("PREPROC_UKN_STATEMENT", context.peek_token(i))
- if context.check_token(i, TOO_MUCH_INDENT) is True:
- current_indent -= 1
- if current_indent < 0:
- current_indent = 0
- fmt = ''
- val = tken.value[1:] if tken.value else tken.type
- spaces = self.get_space_number(tken.value if tken.value else tken.type)
- if current_indent != spaces:
- context.new_error("PREPROC_BAD_INDENT", context.peek_token(i))
+ i = context.skip_ws(0)
+ hash_ = context.peek_token(i)
+ if hash_ and hash_.line_column != 1:
+ context.new_error("PREPROC_START_LINE", hash_)
+ if not isinstance(context.scope, GlobalScope):
+ context.new_error("PREPOC_ONLY_GLOBAL", hash_)
i += 1
- tken = context.peek_token(i)
- if tken is not None and tken.type not in ["NEWLINE", "COMMENT", "MULT_COMMENT"]:
- context.new_error("PREPROC_EXPECTED_EOL", context.peek_token(i))
- return False, 0
+
+ # Empty preprocessor statement (only #)
+ k = context.skip_ws(i, comment=True)
+ if context.check_token(k, "NEWLINE"):
+ return
+
+ n = context.skip_ws(i)
+ while context.check_token(i, "SPACE"):
+ i += 1
+ if context.check_token(i, "TAB"):
+ context.new_error("TAB_REPLACE_SPACE", context.peek_token(i))
+ i = n
+
+ # Check indentation
+ spaces = context.peek_token(i).line_column - hash_.line_column - 1
+ indent = context.preproc.indent
+ if context.check_token(i, ("IF", "ELSE")):
+ indent -= 1
+ else:
+ t = context.peek_token(i)
+ if t and t.type == "IDENTIFIER" and t.value.upper() in ("IFNDEF", "IFDEF", "ELIF"):
+ indent -= 1
+ indent = max(0, indent)
+ if spaces > indent:
+ context.new_error("TOO_MANY_WS", hash_)
+ if spaces < indent:
+ context.new_error("PREPROC_BAD_INDENT", hash_)
+
+ # Check spacing after preproc identifier
+ if (
+ context.check_token(i, ("IDENTIFIER", "IF"))
+ and context.peek_token(i).value in ARGUMENTED_PREPROCESSORS
+ ):
+ i += 1
+ # BUG: #error/warning with a "comment" (`#error // Hello`) will be
+ # ignored, but it's not a big deal.
+ n = context.skip_ws(i, comment=True)
+ if context.check_token(n, "NEWLINE"):
+ return
+ # The idea is to avoid:
+ # - `#include"libft.h"` (no space)
+ # - `#include "libft.h"` (tab)
+ # - `#include "libft.h"` (two spaces)
+ # Note that only `#include "libft.h"` is valid and we also check
+ # for `ifdef`, `ifndef`, etc.
+ if not context.check_token(i, ("SPACE", "TAB")):
+ context.new_error("PREPROC_NO_SPACE", context.peek_token(i))
+ j = i
+ while context.check_token(i, "SPACE"):
+ i += 1
+ if context.check_token(i, "TAB"):
+ context.new_error("TAB_REPLACE_SPACE", context.peek_token(i))
+ if context.skip_ws(j) - j > 1:
+ context.new_error("CONSECUTIVE_WS", context.peek_token(j))
diff --git a/norminette/rules/check_preprocessor_protection.py b/norminette/rules/check_preprocessor_protection.py
index 5e9ba6a9..930c3cc3 100644
--- a/norminette/rules/check_preprocessor_protection.py
+++ b/norminette/rules/check_preprocessor_protection.py
@@ -1,43 +1,69 @@
-from rules import Rule
-from lexer import Lexer, TokenError
-from scope import *
+import itertools
+from norminette.rules import Rule, Check
-class CheckPreprocessorProtection(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsPreprocessorStatement"]
+
+class CheckPreprocessorProtection(Rule, Check):
+ depends_on = (
+ "IsPreprocessorStatement",
+ )
def run(self, context):
"""
- Header protection must be as follows:
- ```
- #ifndef __FILENAME_H__
- # define __FILENAME_H__
- #endif
- ```
- Any header instruction must be within the header protection
+ Header protection must be as follows:
+ ```c
+ #ifndef __FILENAME_H__
+ # define __FILENAME_H__
+ #endif
+ ```
+ Any header instruction must be within the header protection
"""
- i = 0
- if type(context.scope) is not GlobalScope:
+ if context.file.type != ".h":
+ return False, 0
+ i = context.skip_ws(0)
+ hash = context.peek_token(i)
+ i += 1 # Skip the HASH
+ i = context.skip_ws(i)
+ if not context.check_token(i, "IDENTIFIER"):
+ return False, 0
+ # TODO: Add to check if macro definition is bellow #ifndef
+ t = context.peek_token(i)
+ if not t or t.type != "IDENTIFIER" or t.value.upper() not in ("IFNDEF", "ENDIF"):
+ return False, 0
+ i += 1
+ guard = context.file.basename.upper().replace(".", "_")
+ if t.value.upper() == "ENDIF":
+ if context.preproc.indent == 0 and not context.protected:
+ i = context.skip_ws(i, nl=True, comment=True)
+ if context.peek_token(i) is not None:
+ context.new_error("HEADER_PROT_ALL_AF", context.peek_token(i))
+ if not context.preproc.has_macro_defined(guard):
+ context.new_error("HEADER_PROT_NODEF", hash)
+ context.protected = True
return False, 0
- if context.check_token(i, ["IFNDEF", "ENDIF"]) is False or context.filetype != 'h':
+ if context.preproc.indent != 1:
return False, 0
- protection = context.filename.upper().split('/')[-1].replace('.', '_')
- val = context.peek_token(i).value.split(' ')[-1]
- content = Lexer(val, context.peek_token(i).pos[0])
- tkns = content.get_tokens()
- if context.check_token(i, "IFNDEF") is True:
- if len(tkns) >= 1 and tkns[0].value == protection and context.scope.header_protection == -1 and context.preproc_scope_indent == 1:
- if len(context.history) > 1:
- for i in range(len(context.history) - 2, 0, -1):
- if context.history[i] != "IsEmptyLine" and context.history[i] != "IsComment":
- context.new_error("HEADER_PROT_ALL", context.peek_token(0))
- break
- context.scope.header_protection = 0
- elif len(tkns) < 1 or (tkns[0].value != protection and context.scope.header_protection == -1):
- context.new_error("HEADER_PROT_NAME", context.peek_token(0))
- elif context.check_token(i, "ENDIF") is True:
- if context.scope.header_protection == 1 and context.preproc_scope_indent == 0:
- context.scope.header_protection = 2
- return False, 0
\ No newline at end of file
+ i = context.skip_ws(i)
+ macro = context.peek_token(i).value
+ if macro != guard and not context.protected:
+ if macro.upper() == guard:
+ context.new_error("HEADER_PROT_UPPER", context.peek_token(i))
+ else:
+ context.new_error("HEADER_PROT_NAME", context.peek_token(i))
+
+ if context.protected:
+ context.new_error("HEADER_PROT_MULT", hash)
+ return False, 0
+
+ headers = (
+ "IsComment",
+ "IsEmptyLine",
+ )
+ history = context.history[:-1] # Remove the current `IsPreprocessorStatement`
+ history = itertools.filterfalse(lambda item: item in headers, history)
+ if next(history, None):
+ # We can't say what line contains the instruction outside
+ # header protection due to limited history information.
+ context.new_error("HEADER_PROT_ALL", hash)
+
+ return False, 0
diff --git a/norminette/rules/check_prototype_indent.py b/norminette/rules/check_prototype_indent.py
index e97c723c..a65bae8f 100644
--- a/norminette/rules/check_prototype_indent.py
+++ b/norminette/rules/check_prototype_indent.py
@@ -1,5 +1,7 @@
-from rules import Rule
import math
+
+from norminette.rules import Rule, Check
+
keywords = [
# C reserved keywords #
"AUTO",
@@ -33,22 +35,20 @@
"UNSIGNED",
"VOID",
"VOLATILE",
- "WHILE",
+ "WHILE",
"IDENTIFIER",
]
-eol = [
- "SEMI_COLON",
- "LPARENTHESIS"
-]
+eol = ["SEMI_COLON", "LPARENTHESIS"]
-class CheckPrototypeIndent(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsFuncPrototype"]
+
+class CheckPrototypeIndent(Rule, Check):
+ depends_on = (
+ "IsFuncPrototype",
+ )
def run(self, context):
"""
- All function prototypes names must be aligned on the same indentation
+ All function prototypes names must be aligned on the same indentation
"""
i = 0
type_identifier_nb = -1
@@ -56,8 +56,13 @@ def run(self, context):
id_length = 0
buffer_len = 0
while context.check_token(i, ["SEMI_COLON"]) is False:
+ if context.check_token(i, "IDENTIFIER") is True and context.peek_token(i).value == "__attribute__":
+ i += 1
+ i = context.skip_ws(i)
+ i = context.skip_nest(i) + 1
+ type_identifier_nb += 1
if context.check_token(i, "LPARENTHESIS") is True:
- if context.parenthesis_contain(i)[0] == 'pointer':
+ if context.parenthesis_contain(i)[0] == "pointer":
i += 1
continue
else:
@@ -67,7 +72,14 @@ def run(self, context):
i += 1
i = 0
while context.check_token(i, eol) is False:
- if context.check_token(i, keywords) is True and type_identifier_nb > 0:
+ if context.check_token(i, "IDENTIFIER") is True and context.peek_token(i).value == "__attribute__":
+ if type_identifier_nb > 0:
+ context.new_error("ATTR_EOL", context.peek_token(i))
+ i += 1
+ i = context.skip_ws(i)
+ i = context.skip_nest(i)
+ type_identifier_nb -= 1
+ elif context.check_token(i, keywords) is True and type_identifier_nb > 0:
type_identifier_nb -= 1
if context.peek_token(i).length == 0:
id_length += len(str(context.peek_token(i))) - 2
@@ -91,4 +103,4 @@ def run(self, context):
return True, i
return False, 0
i += 1
- return False, 0
\ No newline at end of file
+ return False, 0
diff --git a/norminette/rules/check_spacing.py b/norminette/rules/check_spacing.py
index 1630a4de..e11699ff 100644
--- a/norminette/rules/check_spacing.py
+++ b/norminette/rules/check_spacing.py
@@ -1,24 +1,25 @@
-from rules import Rule
+from norminette.rules import Rule, Check
-class CheckSpacing(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = []
-
+class CheckSpacing(Rule, Check):
def run(self, context):
"""
- Indentation (except for preprocessors) must be done with tabs
- There cannot be trailing spaces or tabs at the end of line
+ Indentation (except for preprocessors) must be done with tabs
+ There cannot be trailing spaces or tabs at the end of line
"""
i = 0
- if context.history[-1] == "IsEmptyLine":
+ if context.history[-1] in ("IsEmptyLine", "IsPreprocessorStatement"):
return False, 0
- while i in range(len(context.tokens[:context.tkn_scope])):
+ space_tab_error = False
+ space_error = False
+ while i in range(len(context.tokens[: context.tkn_scope])):
if context.check_token(i, "SPACE"):
+ if context.check_token(i - 1 if i > 0 else 0, "TAB"):
+ if space_tab_error is False:
+ context.new_error("MIXED_SPACE_TAB", context.peek_token(i - 1))
+ space_tab_error = True
if context.peek_token(i).pos[1] == 1:
- while i < context.tkn_scope \
- and context.check_token(i, "SPACE"):
+ while i < context.tkn_scope and context.check_token(i, "SPACE"):
i += 1
if context.check_token(i + 1, "NEWLINE"):
context.new_error("SPACE_EMPTY_LINE", context.peek_token(i))
@@ -26,17 +27,28 @@ def run(self, context):
continue
context.new_error("SPACE_REPLACE_TAB", context.peek_token(i))
continue
+ t = context.skip_ws(i)
+ if (
+ t != i
+ and context.check_token(t, "NEWLINE")
+ # CheckBrace already check for spacing, we avoid duplicating error here
+ and not context.check_token(i-1, ("LBRACE", "RBRACE"))
+ ):
+ context.new_error("SPC_BEFORE_NL", context.peek_token(i))
i += 1
if context.check_token(i, "SPACE"):
- context.new_error("CONSECUTIVE_SPC", context.peek_token(i - 1))
- while i < context.tkn_scope \
- and context.check_token(i, "SPACE"):
+ if space_error is False:
+ context.new_error("CONSECUTIVE_SPC", context.peek_token(i - 1))
+ space_error = True
+ while i < context.tkn_scope and context.check_token(i, "SPACE"):
i += 1
- if context.check_token(i, "NEWLINE"):
- context.new_error("SPC_BEFORE_NL", context.peek_token(i - 1))
- elif context.check_token(i, ["TAB", "SPACE"]):
+ if context.check_token(i, "TAB"):
+ if space_tab_error is False:
+ context.new_error("MIXED_SPACE_TAB", context.peek_token(i - 1))
+ space_tab_error = True
+ elif context.check_token(i, "TAB"):
if context.peek_token(i).pos[1] == 1:
- while context.check_token(i, ["TAB", "SPACE"]):
+ while context.check_token(i, "TAB"):
i += 1
if context.check_token(i, "NEWLINE"):
context.new_error("SPC_BEFORE_NL", context.peek_token(i - 1))
diff --git a/norminette/rules/check_struct_naming.py b/norminette/rules/check_struct_naming.py
index ac5a4f0a..0c976836 100644
--- a/norminette/rules/check_struct_naming.py
+++ b/norminette/rules/check_struct_naming.py
@@ -1,5 +1,4 @@
-from rules import Rule
-from lexer import Lexer, TokenError
+from norminette.rules import Rule, Check
types = [
"STRUCT",
@@ -7,18 +6,17 @@
"UNION",
]
-class CheckStructNaming(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsUserDefinedType"]
- self.__i = 0
+
+class CheckStructNaming(Rule, Check):
+ depends_on = (
+ "IsUserDefinedType",
+ )
def run(self, context):
"""
- Rewritten elsewhere
+ Rewritten elsewhere
"""
return False, 0
- self.__i += 1
i = 0
i = context.skip_ws(i)
while context.check_token(i, types) is False:
@@ -28,13 +26,13 @@ def run(self, context):
def_type = context.peek_token(i).type
i += 1
i = context.skip_ws(i)
- if def_type == "STRUCT":
+ if def_type == "STRUCT":
if context.peek_token(i).value.startswith("s_") is False:
context.new_error("STRUCT_TYPE_NAMING", context.peek_token(i))
- elif def_type == "ENUM":
+ elif def_type == "ENUM":
if context.peek_token(i).value.startswith("e_") is False:
context.new_error("ENUM_TYPE_NAMING", context.peek_token(i))
- elif def_type == "UNION":
+ elif def_type == "UNION":
if context.peek_token(i).value.startswith("u_") is False:
context.new_error("UNION_TYPE_NAMING", context.peek_token(i))
return False, i
diff --git a/norminette/rules/check_ternary.py b/norminette/rules/check_ternary.py
index 113b5865..de0c45d1 100644
--- a/norminette/rules/check_ternary.py
+++ b/norminette/rules/check_ternary.py
@@ -1,15 +1,18 @@
-from lexer import Token
-from rules import Rule
-import string
+from norminette.rules import Rule, Check
-class CheckTernary(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ['IsTernary']
+class CheckTernary(Rule, Check):
def run(self, context):
"""
- Ternaries are forbidden
+ Ternaries are forbidden
"""
- context.new_error("TERNARY_FBIDDEN", context.peek_token(0))
+ for i in range(0, context.tkn_scope):
+ if (
+ context.check_token(i, "IDENTIFIER")
+ and context.peek_token(i).value == "define"
+ and context.preproc.skip_define
+ ):
+ return
+ if context.check_token(i, "TERN_CONDITION") is True:
+ context.new_error("TERNARY_FBIDDEN", context.peek_token(i))
return False, 0
diff --git a/norminette/rules/check_utype_declaration.py b/norminette/rules/check_utype_declaration.py
index 7bb1a564..3915fd45 100644
--- a/norminette/rules/check_utype_declaration.py
+++ b/norminette/rules/check_utype_declaration.py
@@ -1,8 +1,5 @@
-from rules import Rule
-from lexer import Lexer, TokenError
-from scope import *
-import math
-from exceptions import CParsingError
+from norminette.exceptions import CParsingError
+from norminette.rules import Rule, Check
types = [
"STRUCT",
@@ -19,42 +16,49 @@
"STATIC",
"IDENTIFIER",
"SPACE",
- "TAB"
+ "TAB",
]
-utypes = [
- "STRUCT",
- "ENUM",
- "UNION"
-]
+utypes = ["STRUCT", "ENUM", "UNION"]
-class CheckUtypeDeclaration(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsUserDefinedType"]
+
+class CheckUtypeDeclaration(Rule, Check):
+ depends_on = (
+ "IsUserDefinedType",
+ )
def run(self, context):
"""
- User defined types must respect the following rules:
- - Struct names start with s_
- - Enum names start with e_
- - Union names start with u_
- - Typedef names start with t_
+ User defined types must respect the following rules:
+ - Struct names start with s_
+ - Enum names start with e_
+ - Union names start with u_
+ - Typedef names start with t_
"""
i = 0
i = context.skip_ws(i)
- tkns = context.tokens
+ token = context.peek_token(i)
+ if context.scope.name not in ("GlobalScope", "UserDefinedType"):
+ context.new_error("TYPE_NOT_GLOBAL", token)
+ if (
+ context.file.type == ".c"
+ and token.type in ("STRUCT", "UNION", "ENUM", "TYPEDEF")
+ and context.scope not in ("UserDefinedType", "UserDefinedEnum")
+ ):
+ context.new_error(f"FORBIDDEN_{token.type}", token)
is_td = False
on_newline = False
utype = None
contain_full_def = False
ids = []
- while context.check_token(i, ['SEMI_COLON']) is False and i < len(context.tokens):
- if context.check_token(i, ['SPACE', 'TAB']):
+ while context.check_token(i, ["SEMI_COLON"]) is False and i < len(
+ context.tokens
+ ):
+ if context.check_token(i, ["SPACE", "TAB"]):
pass
if context.check_token(i, ["LPARENTHESIS"]) is True:
val, tmp = context.parenthesis_contain(i)
- if val == None:
+ if val is None or val == "cast" or val == "var":
i = tmp
if context.check_token(i, utypes) is True:
utype = context.peek_token(i)
@@ -68,22 +72,32 @@ def run(self, context):
i = context.skip_ws(i)
i = context.skip_nest(i)
continue
- if context.check_token(i - 1, ["MULT", "BWISE_AND"]) is True:
+ if (
+ context.check_token(i - 1, ["MULT", "BWISE_AND", "LPARENTHESIS"])
+ is True
+ ):
tmp = i - 1
- while context.check_token(tmp, ["MULT", "BWISE_AND"]) is True and context.is_operator(tmp) == False:
+ while (
+ context.check_token(
+ tmp - 1, ["MULT", "BWISE_AND", "LPARENTHESIS"]
+ )
+ is True
+ and context.is_operator(tmp) is False
+ ):
tmp -= 1
ids.append((context.peek_token(i), tmp))
else:
ids.append((context.peek_token(i), i))
- if context.check_token(i, 'LBRACE') is True:
+ if context.check_token(i, "LBRACE") is True:
contain_full_def = True
i = context.skip_nest(i)
i += 1
check = -1
- if is_td == True and len(ids) < 2 and utype != None:
+ # print (ids, utype, contain_full_def)
+ if is_td is True and len(ids) < 2 and utype is not None:
context.new_error("MISSING_TYPEDEF_ID", context.peek_token(0))
return False, 0
- if contain_full_def == False and is_td == False and len(ids) > 1:
+ if contain_full_def is False and is_td is False and len(ids) > 1:
check = -2
else:
check = -1
@@ -91,8 +105,10 @@ def run(self, context):
return False, 0
name = ids[0][0]
loc = ids[check][1]
- if is_td == True:
- if ids[check][0].value.startswith('t_') is False:
+ if is_td is True:
+ if not context.check_token(ids[check][1] - 1, ("SPACE", "TAB")):
+ context.new_error("NO_TAB_BF_TYPEDEF", ids[check][0])
+ if ids[check][0].value.startswith("t_") is False:
context.new_error("USER_DEFINED_TYPEDEF", context.peek_token(loc))
if utype is not None:
if len(ids) > 1:
@@ -101,36 +117,71 @@ def run(self, context):
if context.debug >= 1:
pass
elif context.debug == 0:
- raise CParsingError(f"{context.filename}: Could not parse structure line {context.peek_token(0).pos[0]}")
+ raise CParsingError(
+ f"Error: {context.filename}: Could not parse structure line {context.peek_token(0).pos[0]}"
+ )
loc = ids[0][1]
else:
loc = ids[0][1]
- if utype is not None and utype.type == "STRUCT" and name.value.startswith('s_') is False:
- context.new_error("STRUCT_TYPE_NAMING", context.peek_token(loc))
- if utype is not None and utype.type == "UNION" and name.value.startswith('u_') is False:
- context.new_error("UNION_TYPE_NAMING", context.peek_token(loc))
- if utype is not None and utype.type == "ENUM" and name.value.startswith('e_') is False:
- context.new_error("ENUM_TYPE_NAMING", context.peek_token(loc))
- if is_td or (is_td == False and contain_full_def == False):
+ if is_td is False:
+ if (
+ utype is not None
+ and utype.type == "STRUCT"
+ and name.value.startswith("s_") is False
+ ):
+ context.new_error("STRUCT_TYPE_NAMING", context.peek_token(loc))
+ if (
+ utype is not None
+ and utype.type == "UNION"
+ and name.value.startswith("u_") is False
+ ):
+ context.new_error("UNION_TYPE_NAMING", context.peek_token(loc))
+ if (
+ utype is not None
+ and utype.type == "ENUM"
+ and name.value.startswith("e_") is False
+ ):
+ context.new_error("ENUM_TYPE_NAMING", context.peek_token(loc))
+ if is_td or (is_td is False and contain_full_def is False):
tmp = ids[-1][1] - 1
+ tabs = 0
while (context.check_token(tmp, "TAB")) is True and tmp > 0:
+ tabs += 1
tmp -= 1
+ # if tabs > 1:
+ # context.new_error("TOO_MANY_TABS_TD", context.peek_token(tmp))
if context.check_token(tmp, "SPACE") is True:
context.new_error("SPACE_REPLACE_TAB", context.peek_token(tmp))
+ tab_error = False
+ can_nl_error = False
while tmp > 0:
if context.check_token(tmp, "RBRACE") is True:
+ can_nl_error = True
tmp = context.skip_nest_reverse(tmp)
- if context.check_token(tmp, "TAB") is True and on_newline == False:
- context.new_error("TAB_REPLACE_SPACE", context.peek_token(tmp))
+ if context.check_token(tmp, "TAB") is True and on_newline is False:
+ tab_error = True
+ if (
+ context.check_token(tmp, "NEWLINE") is True
+ and can_nl_error is False
+ ):
+ context.new_error("NEWLINE_IN_DECL", context.peek_token(ids[-1][1]))
+ can_nl_error = True
tmp -= 1
- if contain_full_def == False:
+ if tab_error:
+ context.new_error("TAB_REPLACE_SPACE", context.peek_token(tmp))
+ if contain_full_def is False:
i = 0
- identifier = ids[-1][0]
i = ids[-1][1]
- if context.check_token(i - 1, ["MULT", "BWISE_AND", "LPARENTHESIS"]) is True:
+ if (
+ context.check_token(i - 1, ["MULT", "BWISE_AND", "LPARENTHESIS"])
+ is True
+ ):
i -= 1
- while context.check_token(i, ["MULT", "BWISE_AND", "LPARENTHESIS"]) is True \
- and context.is_operator(i) is False:
+ while (
+ context.check_token(i, ["MULT", "BWISE_AND", "LPARENTHESIS"])
+ is True
+ and context.is_operator(i) is False
+ ):
i -= 1
current_indent = context.peek_token(i).pos[1]
if context.scope.vars_alignment == 0:
@@ -138,4 +189,4 @@ def run(self, context):
elif context.scope.vars_alignment != current_indent:
context.new_error("MISALIGNED_VAR_DECL", context.peek_token(0))
return True, i
- return False, 0
\ No newline at end of file
+ return False, 0
diff --git a/norminette/rules/check_variable_declaration.py b/norminette/rules/check_variable_declaration.py
index eb0c58c4..83120a5e 100644
--- a/norminette/rules/check_variable_declaration.py
+++ b/norminette/rules/check_variable_declaration.py
@@ -1,5 +1,4 @@
-from rules import Rule
-from scope import *
+from norminette.rules import Rule, Check
assigns = [
"RIGHT_ASSIGN",
@@ -15,31 +14,53 @@
"ASSIGN",
]
-class CheckVariableDeclaration(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsVarDeclaration"]
+
+class CheckVariableDeclaration(Rule, Check):
+ depends_on = (
+ "IsVarDeclaration",
+ )
def run(self, context):
"""
- Variables can be declared as global or in the scope of a function
- Only static variables, global variables, and constants can be initialised at declaration.
- Each variable must be declared on a separate line
+ Variables can be declared as global or in the scope of a function
+ Only static variables, global variables, and constants can be initialised at declaration.
+ Each variable must be declared on a separate line
"""
i = 0
static_or_const = False
passed_assign = False
if context.scope.name == "Function":
- if context.history[-2] != "IsBlockStart" and context.history[-2] != "IsVarDeclaration":
+ context.scope.vars += 1
+ if context.scope.vars > 5:
+ context.new_error("TOO_MANY_VARS_FUNC", context.peek_token(i))
+ if (
+ context.history[-2] != "IsBlockStart"
+ and context.history[-2] != "IsVarDeclaration"
+ ):
context.new_error("VAR_DECL_START_FUNC", context.peek_token(i))
- elif context.scope.vdeclarations_allowed == False:
+ elif context.scope.vdeclarations_allowed is False:
context.new_error("VAR_DECL_START_FUNC", context.peek_token(i))
- elif context.scope.vdeclarations_allowed == None:
+ elif context.scope.vdeclarations_allowed is None:
context.scope.vdeclarations_allowed = True
- elif context.scope.name == "GlobalScope" or context.scope.name == "UserDefinedType":
+ elif (
+ context.scope.name == "GlobalScope"
+ or context.scope.name == "UserDefinedType"
+ ):
pass
else:
context.new_error("WRONG_SCOPE_VAR", context.peek_token(i))
+ tmp = 0
+ ret, tmp = context.check_type_specifier(tmp)
+ tmp = context.skip_ws(tmp)
+ tmp -= 1
+ identifier = False
+ while context.check_token(tmp, ["SEMI_COLON"] + assigns) is False:
+ if context.check_token(tmp, "IDENTIFIER"):
+ identifier = True
+ tmp += 1
+ if identifier is False:
+ context.new_error("IMPLICIT_VAR_TYPE", context.peek_token(0))
+ return False
while context.peek_token(i) and context.check_token(i, "SEMI_COLON") is False:
if context.check_token(i, "LPARENTHESIS") is True:
i = context.skip_nest(i)
@@ -47,12 +68,12 @@ def run(self, context):
passed_assign = True
if context.check_token(i, ["STATIC", "CONST"]) is True:
static_or_const = True
- if context.check_token(i, assigns) is True and static_or_const == False:
+ if context.check_token(i, assigns) is True and static_or_const is False:
if context.scope.name == "GlobalScope":
i += 1
continue
context.new_error("DECL_ASSIGN_LINE", context.peek_token(i))
- if context.check_token(i, "COMMA") is True and passed_assign == False:
+ if context.check_token(i, "COMMA") is True and passed_assign is False:
context.new_error("MULT_DECL_LINE", context.peek_token(i))
i += 1
return False, 0
diff --git a/norminette/rules/check_variable_indent.py b/norminette/rules/check_variable_indent.py
index 47a22e66..8c78fba5 100644
--- a/norminette/rules/check_variable_indent.py
+++ b/norminette/rules/check_variable_indent.py
@@ -1,8 +1,8 @@
-from rules import Rule
-from scope import *
import math
import string
+from norminette.rules import Rule, Check
+
keywords = [
# C reserved keywords #
@@ -37,8 +37,8 @@
"UNSIGNED",
"VOID",
"VOLATILE",
- "WHILE",
- "IDENTIFIER"
+ "WHILE",
+ "IDENTIFIER",
]
assigns_or_eol = [
"RIGHT_ASSIGN",
@@ -54,14 +54,14 @@
"ASSIGN",
"SEMI_COLON",
"NEWLINE",
- "COMMA"
+ "COMMA",
]
-class CheckVariableIndent(Rule):
- def __init__(self):
- super().__init__()
- self.depends_on = ["IsVarDeclaration"]
+class CheckVariableIndent(Rule, Check):
+ depends_on = (
+ "IsVarDeclaration",
+ )
def check_tabs(self, context):
i = 0
@@ -74,7 +74,11 @@ def check_tabs(self, context):
while context.check_token(i, assigns_or_eol) is False:
if context.check_token(i, keywords) is True:
type_identifier_nb += 1
- if context.check_token(i, ["LPARENTHESIS", "LBRACE", "LBRACKET"]) and type_identifier_nb > 0:
+ if (
+ context.check_token(i, ["LPARENTHESIS", "LBRACE", "LBRACKET"])
+ and type_identifier_nb > 0
+ and context.parenthesis_contain(i)[0] != "pointer"
+ ):
i = context.skip_nest(i)
i += 1
i = 0
@@ -88,7 +92,9 @@ def check_tabs(self, context):
elif context.check_token(i, "IDENTIFIER") is True:
for c in context.peek_token(i).value:
if c in string.ascii_lowercase:
- context.new_error("VLA_FORBIDDEN", context.peek_token(i))
+ context.new_error(
+ "VLA_FORBIDDEN", context.peek_token(i)
+ )
break
return True, i
i += 1
@@ -110,22 +116,28 @@ def check_tabs(self, context):
has_tab += 1
current_indent += 1
type_identifier_nb -= 1
- elif context.check_token(i, "TAB") and type_identifier_nb > 0 and \
- line_start == False:
+ elif (
+ context.check_token(i, "TAB")
+ and type_identifier_nb > 0
+ and line_start is False
+ ):
context.new_error("TAB_REPLACE_SPACE", context.peek_token(i))
i += 1
return False, 0
def run(self, context):
"""
- Each variable must be indented at the same level for its scope
+ Each variable must be indented at the same level for its scope
"""
i = 0
identifier = None
- ident = [0,0]
+ ident = [0, 0]
ret = None
self.check_tabs(context)
- while context.peek_token(i) and context.check_token(i, ["SEMI_COLON", "COMMA", "ASSIGN"]) is False:
+ while (
+ context.peek_token(i)
+ and context.check_token(i, ["SEMI_COLON", "COMMA", "ASSIGN"]) is False
+ ):
if context.check_token(i, ["LBRACKET", "LBRACE"]) is True:
i = context.skip_nest(i)
if context.check_token(i, "LPARENTHESIS") is True:
@@ -139,8 +151,11 @@ def run(self, context):
identifier = ident[0]
if context.check_token(i - 1, ["MULT", "BWISE_AND", "LPARENTHESIS"]) is True:
i -= 1
- while context.check_token(i - 1, ["MULT", "BWISE_AND", "LPARENTHESIS"]) is True \
- and context.is_operator(i) is False:
+ while (
+ context.check_token(i - 1, ["MULT", "BWISE_AND", "LPARENTHESIS"])
+ is True
+ and context.is_operator(i) is False
+ ):
i -= 1
identifier = context.peek_token(i)
if context.scope.vars_alignment == 0:
@@ -148,4 +163,4 @@ def run(self, context):
elif context.scope.vars_alignment != identifier.pos[1]:
context.new_error("MISALIGNED_VAR_DECL", context.peek_token(i))
return True, i
- return False, 0
\ No newline at end of file
+ return False, 0
diff --git a/norminette/rules/is_ambiguous_declaration.py b/norminette/rules/is_ambiguous_declaration.py
index db4382ea..24f4f926 100644
--- a/norminette/rules/is_ambiguous_declaration.py
+++ b/norminette/rules/is_ambiguous_declaration.py
@@ -1,24 +1,17 @@
-from rules import PrimaryRule
-from context import ControlStructure, Function, GlobalScope
-from exceptions import CParsingError
+from norminette.rules import Primary, Rule
cs_keywords = ["DO", "WHILE", "FOR", "IF", "ELSE", "SWITCH", "CASE", "DEFAULT"]
whitespaces = ["TAB", "SPACE", "NEWLINE"]
-class IsAmbiguousDeclaration(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.priority = 0
- self.scope = []
-
+class IsAmbiguousDeclaration(Primary, Rule, priority=0):
def run(self, context):
"""
- Catches missing semi-colon or other various missing stuff. Dev feature
+ Catches missing semi-colon or other various missing stuff. Dev feature
"""
i = context.skip_ws(0, nl=False)
while context.peek_token(i) and context.check_token(i, "NEWLINE") is False:
if context.check_token(i, ["SEMI_COLON"]) is False:
return False, 0
i += 1
- return True, i
\ No newline at end of file
+ return True, i
diff --git a/norminette/rules/is_assignation.py b/norminette/rules/is_assignation.py
index b8206000..b059cd82 100644
--- a/norminette/rules/is_assignation.py
+++ b/norminette/rules/is_assignation.py
@@ -1,7 +1,4 @@
-from rules import PrimaryRule
-from context import GlobalScope, VariableAssignation
-from exceptions import CParsingError
-
+from norminette.rules import Rule, Primary
assign_ops = [
"RIGHT_ASSIGN",
@@ -16,7 +13,14 @@
"OR_ASSIGN",
"ASSIGN",
"INC",
- "DEC"
+ "DEC",
+]
+
+SEPARATORS = [
+ "COMMA",
+ # "AND",
+ # "OR",
+ "SEMI_COLON",
]
types = [
@@ -37,7 +41,7 @@
"CONST",
"REGISTER",
"STATIC",
- "VOLATILE"
+ "VOLATILE",
]
op = [
@@ -50,61 +54,68 @@
"PLUS",
"DIV",
"PTR",
- "DOT"
+ "DOT",
]
ws = ["SPACE", "TAB", "NEWLINE"]
-class IsAssignation(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.primary = True
- self.priority = 20
- self.scope = []
-
- def skip_ptr(self, context, pos):
- i = context.skip_ws(pos)
- while context.check_token(i, operators + ws + ["IDENTIFIER"]) is True:
- i += 1
- return i
-
+class IsAssignation(Rule, Primary, priority=20):
def check_identifier(self, context, pos):
i = pos
- while context.check_token(i, types + ws + op + ["IDENTIFIER", "CONSTANT"]):
+ while context.check_token(
+ i, types + ws + op + ["IDENTIFIER", "CONSTANT", "INC", "DEC"]
+ ):
if context.check_token(i, "LBRACKET"):
- i = context.skip_nest(i)
+ i = context.skip_nest(i)
i += 1
- if "IDENTIFIER" in [t.type for t in context.tokens[:i + 1]]:
+ if "IDENTIFIER" in [t.type for t in context.tokens[: i + 1]]:
return True, i
else:
return False, 0
+ def parse_assign_right_side(self, context, i):
+ while context.check_token(i, SEPARATORS) is False:
+ if context.check_token(i, ["LBRACE", "LPARENTHESIS", "LBRACKET"]):
+ i = context.skip_nest(i)
+ i += 1
+ return i
+
def run(self, context):
"""
- Catches all assignation instructions
- Requires assign token
+ Catches all assignation instructions
+ Requires assign token
"""
+ # pdb.set_trace()
ret, i = self.check_identifier(context, 0)
if ret is False:
return False, 0
- if context.check_token(i, assign_ops) is False:
+ tmp = 0
+ while context.check_token(tmp, assign_ops) is False and tmp <= i:
+ tmp += 1
+ if context.check_token(tmp, assign_ops) is False:
return False, 0
- i += 1
+ i = tmp + 1
+ # i += 1
i = context.skip_ws(i)
- #if context.check_token(i, "LBRACE") is True:
- #i += 1
- #context.sub = context.scope.inner(VariableAssignation)
- #return True, i
+ # if context.check_token(i, "LBRACE") is True:
+ # i += 1
+ # context.sub = context.scope.inner(VariableAssignation)
+ # return True, i
if context.scope.name == "UserDefinedEnum":
- while context.peek_token(i) and (context.check_token(i, ['COMMA', 'SEMI_COLON', 'NEWLINE'])) is False:
+ while (
+ context.peek_token(i)
+ and (context.check_token(i, ["COMMA", "SEMI_COLON", "NEWLINE"]))
+ is False
+ ):
i += 1
i = context.eol(i)
return True, i
- while context.check_token(i, ["SEMI_COLON"]) is False:
- i += 1
- if context.peek_token(i) is None:
- return False, 0
+ i = self.parse_assign_right_side(context, i)
+ # while context.check_token(i, SEPARATORS) is False:
+ # i += 1
+ # if context.peek_token(i) is None:
+ # return False, 0
i += 1
i = context.eol(i)
return True, i
diff --git a/norminette/rules/is_block_end.py b/norminette/rules/is_block_end.py
index 6e12550a..0a2eb870 100644
--- a/norminette/rules/is_block_end.py
+++ b/norminette/rules/is_block_end.py
@@ -1,19 +1,11 @@
-from lexer import Token
-from rules import PrimaryRule
-from context import (
- Function,
- UserDefinedType,
- VariableAssignation,
- ControlStructure,
- UserDefinedEnum)
+from norminette.context import ControlStructure
+from norminette.scope import UserDefinedEnum
+from norminette.scope import UserDefinedType
+from norminette.scope import VariableAssignation
+from norminette.rules import Rule, Primary
-class IsBlockEnd(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.priority = 40
- self.scope = []
-
+class IsBlockEnd(Rule, Primary, priority=54):
def check_udef_typedef(self, context, pos):
i = context.skip_ws(pos)
if context.check_token(i, "IDENTIFIER") is False:
@@ -28,14 +20,14 @@ def check_udef_typedef(self, context, pos):
def run(self, context):
"""
- Catches RBRACE tokens.
- Handles scope related stuff: Exiting a scope is done here and in registry.py
- Scope is calculated AFTER the rules have run for this primary rule
+ Catches RBRACE tokens.
+ Handles scope related stuff: Exiting a scope is done here and in registry.py
+ Scope is calculated AFTER the rules have run for this primary rule
"""
i = context.skip_ws(0)
if context.check_token(i, "RBRACE") is False:
return False, 0
- if type(context.scope) != ControlStructure:
+ if type(context.scope) is not ControlStructure:
context.sub = context.scope.outer()
else:
context.scope.multiline = False
@@ -47,7 +39,7 @@ def run(self, context):
ret, i = self.check_udef_typedef(context, i)
i = context.eol(i)
return ret, i
- elif context.check_token(i, 'SEMI_COLON') is True:
+ elif context.check_token(i, "SEMI_COLON") is True:
i += 1
i = context.eol(i)
return True, i
@@ -59,7 +51,7 @@ def run(self, context):
if type(context.scope) is VariableAssignation:
i = context.skip_ws(i)
if context.check_token(i, "SEMI_COLON"):
- #Fatal err?
+ # Fatal err?
return False, 0
i += 1
i = context.eol(i)
diff --git a/norminette/rules/is_block_start.py b/norminette/rules/is_block_start.py
index be8c07ae..b1f795c5 100644
--- a/norminette/rules/is_block_start.py
+++ b/norminette/rules/is_block_start.py
@@ -1,44 +1,47 @@
-from lexer import Token
-from rules import PrimaryRule
-from scope import *
-from context import (
- Function,
- UserDefinedType,
- VariableAssignation,
- ControlStructure)
+from norminette.context import ControlStructure
+from norminette.rules import Rule, Primary
+from norminette.scope import GlobalScope, UserDefinedEnum, Function, UserDefinedType, VariableAssignation
-class IsBlockStart(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.priority = 35
- self.scope = [
- Function,
- UserDefinedType,
- VariableAssignation,
- ControlStructure,
- UserDefinedEnum,
- GlobalScope]
+class IsBlockStart(Rule, Primary, priority=55):
+ scope = (
+ Function,
+ UserDefinedType,
+ VariableAssignation,
+ ControlStructure,
+ UserDefinedEnum,
+ GlobalScope,
+ )
def run(self, context):
"""
- Catches LBRACE tokens
- Creates new scope based on previous instruction or set it to multiline if it
- is a control statement
+ Catches LBRACE tokens
+ Creates new scope based on previous instruction or set it to multiline if it
+ is a control statement
"""
i = context.skip_ws(0, nl=False)
if context.check_token(i, "LBRACE") is False:
return False, 0
i += 1
- hist = context.history
lines = context.scope.lines
- for item in hist[::-1]:
+ for item in reversed(context.history):
if item == "IsEmptyLine" or item == "IsComment" or item == "IsPreprocessorStatement":
lines -= 1
continue
- if item not in ["IsControlStatement", "IsFuncDeclaration", "IsUserDefinedType"] or \
- (item in ["IsControlStatement", "IsFuncDeclaration", "IsUserDefinedType"] and lines >= 1):
- context.sub = context.scope.inner(ControlStructure)
+ if (
+ item
+ not in [
+ "IsControlStatement",
+ "IsFuncDeclaration",
+ "IsUserDefinedType",
+ ]
+ or (item in ["IsControlStatement", "IsFuncDeclaration", "IsUserDefinedType"] and lines >= 1)
+ ):
+ scope = {
+ "IsFuncDeclaration": Function,
+ "IsUserDefinedType": UserDefinedType,
+ }.get(item, ControlStructure)
+ context.sub = context.scope.inner(scope)
context.sub.multiline = True
break
else:
@@ -46,7 +49,7 @@ def run(self, context):
break
tmp = i
- #while context.peek_token(tmp) and (context.check_token(tmp, ["NEWLINE"])) is False:
+ # while context.peek_token(tmp) and (context.check_token(tmp, ["NEWLINE"])) is False:
# tmp += 1
tmp = context.eol(tmp)
if context.peek_token(tmp) is not None:
diff --git a/norminette/rules/is_cast.py b/norminette/rules/is_cast.py
index 934dd95e..bcf89e64 100644
--- a/norminette/rules/is_cast.py
+++ b/norminette/rules/is_cast.py
@@ -1,6 +1,4 @@
-from rules import PrimaryRule
-from context import GlobalScope, VariableAssignation
-from exceptions import CParsingError
+from norminette.rules import Rule, Primary
types = [
"CHAR",
@@ -20,7 +18,7 @@
"CONST",
"REGISTER",
"STATIC",
- "VOLATILE"
+ "VOLATILE",
]
op = [
@@ -33,22 +31,16 @@
"PLUS",
"DIV",
"PTR",
- "DOT"
+ "DOT",
]
ws = ["SPACE", "TAB", "NEWLINE"]
-class IsCast(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.primary = True
- self.priority = 15
- self.scope = []
-
+class IsCast(Rule, Primary, priority=15):
def run(self, context):
"""
- Catches all casts instructions
+ Catches all casts instructions
"""
i = 0
i = context.skip_ws(i, nl=False)
@@ -61,4 +53,4 @@ def run(self, context):
i = context.skip_ws(i, nl=False)
i = context.eol(i)
return True, i
- return False, 0
\ No newline at end of file
+ return False, 0
diff --git a/norminette/rules/is_comment.py b/norminette/rules/is_comment.py
index 3a36d584..ca0d8fe5 100644
--- a/norminette/rules/is_comment.py
+++ b/norminette/rules/is_comment.py
@@ -1,20 +1,14 @@
-from lexer import Token
-from rules import PrimaryRule
-from context import GlobalScope, UserDefinedType, ControlStructure, Function
+from norminette.rules import Rule, Primary
-class IsComment(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.priority = 90
- self.scope = []
-
+class IsComment(Rule, Primary, priority=90):
def run(self, context):
"""
- Catches comments tokens
+ Catches comments tokens
"""
i = context.skip_ws(0)
if context.check_token(i, ["MULT_COMMENT", "COMMENT"]) is True:
+ self.comment = context.peek_token(i)
i += 1
i = context.eol(i)
return True, i
diff --git a/norminette/rules/is_control_statement.py b/norminette/rules/is_control_statement.py
index a85b6f83..927ca098 100644
--- a/norminette/rules/is_control_statement.py
+++ b/norminette/rules/is_control_statement.py
@@ -1,21 +1,33 @@
-from rules import PrimaryRule
-from context import ControlStructure, Function, GlobalScope
-from exceptions import CParsingError
+from norminette.context import ControlStructure
+from norminette.scope import Function
+from norminette.context import GlobalScope
+from norminette.rules import Rule, Primary
-cs_keywords = ["DO", "WHILE", "FOR", "IF", "ELSE", "SWITCH", "CASE", "DEFAULT", "IDENTIFIER"]
+cs_keywords = [
+ "DO",
+ "WHILE",
+ "FOR",
+ "IF",
+ "ELSE",
+ "SWITCH",
+ "CASE",
+ "DEFAULT",
+ "IDENTIFIER",
+]
whitespaces = ["TAB", "SPACE", "NEWLINE"]
-class IsControlStatement(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.priority = 70
- self.scope = [Function, ControlStructure, GlobalScope]
+class IsControlStatement(Rule, Primary, priority=65):
+ scope = (
+ Function,
+ ControlStructure,
+ GlobalScope,
+ )
def run(self, context):
"""
- Catches control statements, including for/switch
- Includes the condition, even if over multiple lines
+ Catches control statements, including for/switch
+ Includes the condition, even if over multiple lines
"""
is_id = False
id_instead_cs = False
@@ -71,7 +83,7 @@ def run(self, context):
i = context.eol(i)
return True, i
i += 1
- if id_instead_cs == True:
+ if id_instead_cs is True:
return False, 0
i = context.skip_ws(i, nl=False)
if context.check_token(i, "LPARENTHESIS") is False:
@@ -80,7 +92,7 @@ def run(self, context):
i += 1
tmp = context.skip_ws(i, nl=True)
if context.check_token(tmp, "SEMI_COLON") is True:
- if is_id == True:
+ if is_id is True:
return False, 0
tmp += 1
tmp = context.eol(tmp)
diff --git a/norminette/rules/is_declaration.py b/norminette/rules/is_declaration.py
index 9aabe8b4..f18f600c 100644
--- a/norminette/rules/is_declaration.py
+++ b/norminette/rules/is_declaration.py
@@ -1,30 +1,27 @@
-from rules import PrimaryRule
-from context import ControlStructure, Function
-from exceptions import CParsingError
+from norminette.rules import Rule, Primary
-class IsDeclaration(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.priority = 10
- self.scope = []
+class IsDeclaration(Rule, Primary, priority=5):
def run(self, context):
- #return False, 0
+ # return False, 0
i = context.skip_ws(0, nl=False)
p = 0
ident = None
- while context.peek_token(i) is not None and context.check_token(i, "SEMI_COLON") is False:
+ while (
+ context.peek_token(i) is not None
+ and context.check_token(i, "SEMI_COLON") is False
+ ):
if context.check_token(i, "LPARENTHESIS"):
p += 1
- if context.check_token(i, 'RPARENTHESIS'):
+ if context.check_token(i, "RPARENTHESIS"):
p -= 1
- if context.check_token(i, ['IDENTIFIER', "NULL"]):
+ if context.check_token(i, ["IDENTIFIER", "NULL"]):
ident = context.peek_token(i)
i += 1
i += 1
i = context.skip_ws(i, nl=False)
if context.check_token(i, "NEWLINE"):
i += 1
- if p == 0 and ident != None:
+ if p == 0 and ident is not None:
return True, i
- return False, 0
\ No newline at end of file
+ return False, 0
diff --git a/norminette/rules/is_empty_line.py b/norminette/rules/is_empty_line.py
index 8ddf26d7..02d982b7 100644
--- a/norminette/rules/is_empty_line.py
+++ b/norminette/rules/is_empty_line.py
@@ -1,26 +1,19 @@
-from rules import PrimaryRule
-from context import ControlStructure, Function
-
+from norminette.rules import Rule, Primary
cs_keywords = ["DO", "WHILE", "FOR", "IF", "ELSE", "SWITCH"]
whitespaces = ["TAB", "SPACE", "NEWLINE"]
-class IsEmptyLine(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.priority = 65
- self.scope = []
-
+class IsEmptyLine(Rule, Primary, priority=70):
def run(self, context):
"""
- Catches empty line
- BUG: Catches end of line token on unrecognized line
+ Catches empty line
+ BUG: Catches end of line token on unrecognized line
"""
i = 0
while context.check_token(i, ["SPACE", "TAB"]) is True:
i += 1
- if context.check_token(i, "NEWLINE") is True:
+ if context.check_token(i, "NEWLINE") is True or context.peek_token(i) is None:
i = context.eol(i)
return True, i
return False, 0
diff --git a/norminette/rules/is_enum_var_decl.py b/norminette/rules/is_enum_var_decl.py
index 8b7f0399..720560b2 100644
--- a/norminette/rules/is_enum_var_decl.py
+++ b/norminette/rules/is_enum_var_decl.py
@@ -1,18 +1,15 @@
-from lexer import Token
-from rules import PrimaryRule
-from scope import *
-from context import GlobalScope, UserDefinedType, ControlStructure, Function, UserDefinedEnum
+from norminette.rules import Rule, Primary
+from norminette.scope import UserDefinedEnum
lbrackets = ["LBRACE", "LPARENTHESIS", "LBRACKET"]
rbrackets = ["RBRACE", "RPARENTHESIS", "RBRACKET"]
-class IsEnumVarDecl(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.priority = 30
- self.scope = [UserDefinedEnum]
+class IsEnumVarDecl(Rule, Primary, priority=30):
+ scope = (
+ UserDefinedEnum,
+ )
def assignment_right_side(self, context, pos):
sep = ["COMMA", "ASSIGN", "NEWLINE"]
@@ -24,14 +21,21 @@ def assignment_right_side(self, context, pos):
return True, i
def var_declaration(self, context, pos):
- pclose = ["RPARENTHESIS", "NEWLINE", "SPACE", "TAB"]
brackets = 0
parenthesis = 0
braces = 0
i = pos
identifier = False
- while context.peek_token(i) is not None and context.check_token(i, ["COMMA", "RBRACE", "NEWLINE"]) is False:
- if context.check_token(i, "IDENTIFIER") is True and braces == 0 and brackets == 0 and parenthesis == 0:
+ while (
+ context.peek_token(i) is not None
+ and context.check_token(i, ["COMMA", "RBRACE", "NEWLINE"]) is False
+ ):
+ if (
+ context.check_token(i, "IDENTIFIER") is True
+ and braces == 0
+ and brackets == 0
+ and parenthesis == 0
+ ):
identifier = True
elif context.check_token(i, lbrackets) is True:
if context.check_token(i, "LBRACE") is True:
@@ -48,18 +52,20 @@ def var_declaration(self, context, pos):
if context.check_token(i, "RPARENTHESIS") is True:
parenthesis -= 1
elif context.check_token(i, "ASSIGN") is True:
- if identifier == False:
+ if identifier is False:
return False, pos
ret, i = self.assignment_right_side(context, i + 1)
i -= 1
if ret is False:
return False, pos
- elif context.check_token(i, ['SPACE', "TAB", "MULT", "BWISE_AND", "NEWLINE"]):
+ elif context.check_token(
+ i, ["SPACE", "TAB", "MULT", "BWISE_AND", "NEWLINE"]
+ ):
pass
elif parenthesis == 0 and brackets == 0 and braces == 0:
return False, 0
i += 1
- if identifier == False:
+ if identifier is False:
return False, pos
if context.check_token(i, ["NEWLINE", "COMMA"]) is True:
return True, i
@@ -67,7 +73,7 @@ def var_declaration(self, context, pos):
def run(self, context):
"""
- Enum have special var declarations so this catches these specific variables
+ Enum have special var declarations so this catches these specific variables
"""
ret, i = self.var_declaration(context, 0)
if ret is False:
diff --git a/norminette/rules/is_expression_statement.py b/norminette/rules/is_expression_statement.py
index 70f9192f..123b0ee0 100644
--- a/norminette/rules/is_expression_statement.py
+++ b/norminette/rules/is_expression_statement.py
@@ -1,13 +1,9 @@
-from rules import PrimaryRule
-from context import Function, ControlStructure
-from exceptions import CParsingError
+from norminette.context import ControlStructure
+from norminette.scope import Function
+from norminette.exceptions import CParsingError
+from norminette.rules import Rule, Primary
-keywords = [
- "BREAK",
- "CONTINUE",
- "GOTO",
- "RETURN"
-]
+keywords = ["BREAK", "CONTINUE", "GOTO", "RETURN"]
operators = [
"MULT",
@@ -16,16 +12,17 @@
"LBRACKET",
"RBRACKET",
"PTR",
- "DOT"
+ "DOT",
]
-ws = ["SPACE", "TAB","NEWLINE"]
+ws = ["SPACE", "TAB", "NEWLINE"]
+
-class IsExpressionStatement(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.priority = 25
- self.scope = [Function, ControlStructure]
+class IsExpressionStatement(Rule, Primary, priority=25):
+ scope = (
+ Function,
+ ControlStructure,
+ )
def check_reserved_keywords(self, context, pos):
if context.check_token(pos, keywords) is False:
@@ -39,14 +36,19 @@ def check_reserved_keywords(self, context, pos):
elif context.check_token(pos, "GOTO"):
i = pos + 1
i = context.skip_ws(i)
- while context.check_token(i, ["MULT", "BWISE_AND"]) is True and context.is_operator(i) is False:
+ while (
+ context.check_token(i, ["MULT", "BWISE_AND"]) is True
+ and context.is_operator(i) is False
+ ):
i += 1
if context.check_token(i, "IDENTIFIER") is False:
if context.check_token(i, "LPARENTHESIS") is True:
- #parse label value here
+ # parse label value here
i = context.skip_nest(i)
elif context.debug == 0:
- raise CParsingError("Goto statement should be followed by a label")
+ raise CParsingError(
+ "Error: Goto statement should be followed by a label"
+ )
i += 1
i = context.skip_ws(i)
i += 1
@@ -134,17 +136,17 @@ def void_identifier(self, context, pos):
def run(self, context):
"""
- Catches expression statement by elimination
+ Catches expression statement by elimination
"""
i = context.skip_ws(0)
ret, i = self.check_instruction(context, i)
if ret is False:
ret, i = self.check_reserved_keywords(context, i)
+ # if ret is False:
+ # ret, i = self.check_inc_dec(context, i)
if ret is False:
- ret, i = self.check_inc_dec(context, i)
+ ret, i = self.void_identifier(context, i)
if ret is False:
- ret, i = self.void_identifier(context, i)
- if ret is False:
- return False, 0
+ return False, 0
i = context.eol(i)
return True, i
diff --git a/norminette/rules/is_func_declaration.py b/norminette/rules/is_func_declaration.py
index 6e088810..e4f6be0b 100644
--- a/norminette/rules/is_func_declaration.py
+++ b/norminette/rules/is_func_declaration.py
@@ -1,21 +1,11 @@
-from lexer import Token
-from rules import PrimaryRule
-from context import GlobalScope, Function, UserDefinedType
+from norminette.scope import Function
+from norminette.context import GlobalScope
+from norminette.rules import Rule, Primary
+
whitespaces = ["SPACE", "TAB"]
-preproc = [
- "DEFINE",
- "ERROR",
- "ENDIF",
- "ELIF",
- "IFDEF",
- "IFNDEF",
- "#IF",
- "#ELSE",
- "INCLUDE",
- "PRAGMA",
- "UNDEF"
-]
+
+SEPARATORS = ["COMMA", "AND", "OR", "SEMI_COLON"]
assigns = [
"RIGHT_ASSIGN",
"LEFT_ASSIGN",
@@ -42,7 +32,7 @@
"TYPEDEF",
"STRUCT",
"ENUM",
- "UNION"
+ "UNION",
]
type_identifier = [
"CHAR",
@@ -55,11 +45,12 @@
"LONG",
"SHORT",
]
-class IsFuncDeclaration(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.priority = 60
- self.scope = [GlobalScope]
+
+
+class IsFuncDeclaration(Rule, Primary, priority=81):
+ scope = (
+ GlobalScope,
+ )
def check_args(self, context, pos):
i = context.skip_ws(pos, nl=True)
@@ -118,13 +109,31 @@ def check_func_format(self, context):
if context.check_token(i, "NEWLINE") is True:
return False, 0
while context.peek_token(i):
- if context.check_token(i, "NEWLINE") is True and identifier == False and misc_id == [] and type_id == []:
+ while (
+ context.check_token(i, "IDENTIFIER") is True
+ and context.peek_token(i).value == "__attribute__"
+ ):
+ i += 1
+ i = context.skip_ws(i)
+ i = context.skip_nest(i)
+ i = context.skip_ws(i)
+ if (
+ context.check_token(i, "NEWLINE") is True
+ and identifier is False
+ and misc_id == []
+ and type_id == []
+ ):
return False, 0
if context.check_token(i, misc_identifier) is True:
misc_id.append(context.peek_token(i))
elif context.check_token(i, type_identifier) is True:
type_id.append(context.peek_token(i))
- if context.check_token(i, assigns + ["TYPEDEF", "COMMA", "LBRACE"] + preproc) is True:
+ if (
+ context.check_token(
+ i, assigns + ["TYPEDEF", "COMMA", "LBRACE", "HASH"]
+ )
+ is True
+ ):
return False, 0
if context.check_token(i, "SEMI_COLON") is True:
return False, 0
@@ -133,7 +142,7 @@ def check_func_format(self, context):
type_id.append(identifier[0])
identifier = (context.peek_token(i), i)
if context.check_token(i, "NEWLINE") is True:
- if args == False:
+ if args is False:
i += 1
continue
else:
@@ -164,14 +173,13 @@ def check_func_format(self, context):
i += 1
else:
i += 1
- #print (type_id, args, identifier)
- if len(type_id) > 0 and args == True and identifier != None:
+ if len(type_id) > 0 and args is True and identifier is not None:
i = identifier[1]
i = context.skip_ws(i, nl=True)
while context.check_token(i, ["LPARENTHESIS", "MULT", "BWISE_AND"]) is True:
i += 1
sc = context.scope
- while type(sc) != GlobalScope:
+ while type(sc) is not GlobalScope:
sc = sc.outer()
sc.fnames.append(context.peek_token(i).value)
context.fname_pos = i
@@ -185,34 +193,47 @@ def check_func_format(self, context):
i = context.skip_nest(i)
i += 1
i = context.skip_ws(i, nl=True)
+ while context.check_token(i, "LBRACKET"):
+ i = context.skip_nest(i)
+ i += 1
+ i = context.skip_ws(i, nl=True)
+ while (
+ context.check_token(i, "IDENTIFIER") is True
+ and context.peek_token(i).value == "__attribute__"
+ ):
+ i += 1
+ i = context.skip_ws(i)
+ i = context.skip_nest(i) + 1
+ i = context.skip_ws(i)
return True, i
return False, 0
def run(self, context):
"""
- Catches function declaration
- Allows newline inside it
- Creates context variable for function name, arg_start, arg_end
+ Catches function declaration
+ Allows newline inside it
+ Creates context variable for function name, arg_start, arg_end
"""
if type(context.scope) is not GlobalScope:
return False, 0
-
ret, read = self.check_func_format(context)
if ret is False:
return False, 0
while context.check_token(read, ["COMMENT", "MULT_COMMENT"]) is True:
read += 1
read = context.skip_ws(read, nl=False)
- if context.check_token(read, ["NEWLINE", "LBRACE"] + preproc):
- if context.check_token(read, ["LBRACE"] + preproc) is True:
+ if context.check_token(read, ["NEWLINE", "LBRACE", "HASH"]):
+ if context.check_token(read, ["LBRACE", "HASH"]) is True:
read -= 1
context.scope.functions += 1
read += 1
- context.sub = context.scope.inner(Function)
+ temp = context.skip_ws(read, nl=True)
+ if context.check_token(temp, "LBRACE"):
+ context.sub = context.scope.inner(Function)
read = context.eol(read)
return True, read
- elif context.check_token(read, "SEMI_COLON"):
+ elif context.check_token(read, SEPARATORS):
read += 1
read = context.eol(read)
return False, 0
diff --git a/norminette/rules/is_func_prototype.py b/norminette/rules/is_func_prototype.py
index 6662384e..16f3c66f 100644
--- a/norminette/rules/is_func_prototype.py
+++ b/norminette/rules/is_func_prototype.py
@@ -1,21 +1,8 @@
-from lexer import Token
-from rules import PrimaryRule
-from context import GlobalScope, Function, UserDefinedType
+from norminette.context import GlobalScope
+from norminette.scope import UserDefinedType
+from norminette.rules import Rule, Primary
whitespaces = ["SPACE", "TAB"]
-preproc = [
- "DEFINE",
- "ERROR",
- "ENDIF",
- "ELIF",
- "IFDEF",
- "IFNDEF",
- "#IF",
- "#ELSE",
- "INCLUDE",
- "PRAGMA",
- "UNDEF"
-]
assigns = [
"RIGHT_ASSIGN",
"LEFT_ASSIGN",
@@ -42,7 +29,7 @@
"TYPEDEF",
"STRUCT",
"ENUM",
- "UNION"
+ "UNION",
]
type_identifier = [
"CHAR",
@@ -55,11 +42,12 @@
"LONG",
"SHORT",
]
-class IsFuncPrototype(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.priority = 80
- self.scope = [GlobalScope, UserDefinedType]
+
+
+class IsFuncPrototype(Rule, Primary, priority=82):
+ scope = (
+ GlobalScope,
+ )
def check_args(self, context, pos):
i = context.skip_ws(pos)
@@ -117,12 +105,25 @@ def check_func_format(self, context):
identifier = None
if context.check_token(i, "NEWLINE") is True:
return False, 0
- while context.peek_token(i):# and context.check_token(i, "NEWLINE") is False:
+ while context.peek_token(i): # and context.check_token(i, "NEWLINE") is False:
+ while (
+ context.check_token(i, "IDENTIFIER") is True
+ and context.peek_token(i).value == "__attribute__"
+ ):
+ i += 1
+ i = context.skip_ws(i)
+ i = context.skip_nest(i) + 1
+ i = context.skip_ws(i)
if context.check_token(i, misc_identifier) is True:
misc_id.append(context.peek_token(i))
elif context.check_token(i, type_identifier) is True:
type_id.append(context.peek_token(i))
- if context.check_token(i, assigns + ["TYPEDEF", "COMMA", "LBRACE", "RBRACE"] + preproc) is True:
+ if (
+ context.check_token(
+ i, assigns + ["TYPEDEF", "COMMA", "LBRACE", "RBRACE", "HASH"]
+ )
+ is True
+ ):
return False, 0
if context.check_token(i, "SEMI_COLON") is True:
break
@@ -157,17 +158,19 @@ def check_func_format(self, context):
break
else:
i += 1
- if len(type_id) > 0 and args == True and identifier != None:
+ if len(type_id) > 0 and args is True and identifier is not None:
i = identifier[1]
while context.check_token(i, ["LPARENTHESIS", "MULT", "BWISE_AND"]) is True:
i += 1
sc = context.scope
- while type(sc) != GlobalScope:
+ while type(sc) is not GlobalScope:
sc = sc.outer()
sc.fnames.append(context.peek_token(i).value)
if context.func_alignment == 0:
tmp = i
- while context.check_token(tmp - 1, ["LPARENTHESIS", "MULT", "BWISE_AND"]):
+ while context.check_token(
+ tmp - 1, ["LPARENTHESIS", "MULT", "BWISE_AND"]
+ ):
tmp -= 1
context.func_alignment = int(context.peek_token(tmp).pos[1] / 4)
context.fname_pos = i
@@ -184,21 +187,25 @@ def check_func_format(self, context):
def run(self, context):
"""
- Catches function prototypes
- Allows newline inside it
- End condition is SEMI_COLON token, otherwise line will be considered as
- function declaration
+ Catches function prototypes
+ Allows newline inside it
+ End condition is SEMI_COLON token, otherwise line will be considered as
+ function declaration
"""
- if type(context.scope) is not GlobalScope and type(context.scope) is not UserDefinedType:
+ if (
+ type(context.scope) is not GlobalScope
+ and type(context.scope) is not UserDefinedType
+ ):
return False, 0
ret, read = self.check_func_format(context)
if ret is False:
return False, 0
if context.check_token(read, "IDENTIFIER") is True:
- if context.peek_token(read).value == "__attribute__":
+ while context.peek_token(read).value == "__attribute__":
read += 1
read = context.skip_ws(read)
read = context.skip_nest(read) + 1
+ read = context.skip_ws(read, nl=True)
while context.check_token(read, ["COMMENT", "MULT_COMMENT"]) is True:
read += 1
read = context.skip_ws(read, nl=False)
diff --git a/norminette/rules/is_function_call.py b/norminette/rules/is_function_call.py
index aa67955b..e1e48d30 100644
--- a/norminette/rules/is_function_call.py
+++ b/norminette/rules/is_function_call.py
@@ -1,6 +1,5 @@
-from rules import PrimaryRule
-from context import GlobalScope, VariableAssignation
-from exceptions import CParsingError
+from norminette.rules import Rule, Primary
+from norminette.lexer.dictionary import keywords
condition_ops = [
"LESS_OR_EQUAL",
@@ -28,6 +27,8 @@
"MORE_THAN",
]
+SEPARATORS = ["COMMA", "AND", "OR", "SEMI_COLON"]
+
assign_ops = [
"RIGHT_ASSIGN",
"LEFT_ASSIGN",
@@ -39,7 +40,7 @@
"AND_ASSIGN",
"XOR_ASSIGN",
"OR_ASSIGN",
- "ASSIGN"
+ "ASSIGN",
]
types = [
@@ -60,7 +61,7 @@
"CONST",
"REGISTER",
"STATIC",
- "VOLATILE"
+ "VOLATILE",
]
op = [
@@ -75,37 +76,33 @@
"INC",
"DEC",
"PTR",
- "DOT"
+ "DOT",
]
ws = ["SPACE", "TAB", "NEWLINE"]
-class IsFunctionCall(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.primary = True
- self.priority = 55
- self.scope = []
-
+class IsFunctionCall(Rule, Primary, priority=80):
def run(self, context):
"""
- Catches function calls when it's in an assignation
+ Catches function calls when it's in an assignation
"""
i = context.skip_ws(0, nl=False)
types = []
while context.check_token(i, "LPARENTHESIS") is True:
- start = i
typ, i = context.parenthesis_contain(i)
types.append(typ)
- if typ == None or typ == "pointer":
+ if typ is None or typ == "pointer":
i = context.skip_ws(i + 1)
- if context.peek_token(i) is None or context.check_token(i, "NEWLINE") is True:
+ if (
+ context.peek_token(i) is None
+ or context.check_token(i, "NEWLINE") is True
+ ):
return False, 0
- #i += 1
+ # i += 1
if len(types) > 1:
i = context.skip_ws(i, nl=False)
- if context.check_token(i, "SEMI_COLON") is True:
+ if context.check_token(i, SEPARATORS) is True:
i += 1
i = context.eol(i)
return True, i
@@ -119,15 +116,31 @@ def run(self, context):
if context.check_token(i, "IDENTIFIER") is True:
i += 1
i = context.skip_ws(i)
- if context.check_token(i, "LPARENTHESIS") is True:
- i = context.skip_nest(i)
- while context.peek_token(i) is not None and context.check_token(i, "SEMI_COLON") is False:
+ if context.check_token(i, "LPARENTHESIS"):
+ while context.check_token(i, "LPARENTHESIS") is True:
+ i = context.skip_nest(i) + 1
+ i = context.skip_ws(i)
+ if context.check_token(i, "PTR"): # ->
+ i = context.skip_ws(i + 1)
+ if context.check_token(i, ("IDENTIFIER", *map(str.upper, keywords))):
+ i = context.skip_ws(i + 1)
+ if context.check_token(i, assign_ops):
+ expected = "SEMI_COLON"
+ else:
+ expected = SEPARATORS
+ while context.peek_token(i) is not None and not context.check_token(i, expected):
i += 1
+ if context.peek_token(i) is None:
+ return False, 0
i += 1
i = context.eol(i)
return True, i
- elif len(types) > 1 and typ == "cast" and (types[-2] == "function" or types[-2] == "pointer"):
+ elif (
+ len(types) > 1
+ and typ == "cast"
+ and (types[-2] == "function" or types[-2] == "pointer")
+ ):
i += 1
i = context.eol(i)
return True, i
- return False, 0
\ No newline at end of file
+ return False, 0
diff --git a/norminette/rules/is_label.py b/norminette/rules/is_label.py
index cd18d73e..76b70e13 100644
--- a/norminette/rules/is_label.py
+++ b/norminette/rules/is_label.py
@@ -1,22 +1,21 @@
-from rules import PrimaryRule
+from norminette.rules import Rule, Primary
-class IsLabel(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.priority = 5
+class IsLabel(Rule, Primary, priority=10):
def run(self, context):
"""
- Catches label and raises norm error whenever
+ Catches label and raises norm error whenever
"""
i = context.skip_ws(0)
if context.check_token(i, "IDENTIFIER") is False:
return False, 0
- i += 1
- i = context.skip_ws(i)
+ i = context.skip_ws(i + 1) # +1 to skip the identifier
if context.check_token(i, "COLON") is False:
return False, 0
- while context.peek_token(i) and context.check_token(i, "NEWLINE") is False:
+ i = context.skip_ws(i + 1) # +1 to skip the colon
+ if context.check_token(i, "NEWLINE"):
+ return True, i+1
+ while context.peek_token(i) and context.check_token(i, "SEMI_COLON") is False:
i += 1
- i = context.eol(i)
+ i = context.eol(i + 1) # +1 to skip the semi-colon
return True, i
diff --git a/norminette/rules/is_preprocessor_statement.py b/norminette/rules/is_preprocessor_statement.py
index 90d8da6c..06c87d4a 100644
--- a/norminette/rules/is_preprocessor_statement.py
+++ b/norminette/rules/is_preprocessor_statement.py
@@ -1,48 +1,363 @@
-from rules import PrimaryRule, Rule
-import context
-from scope import GlobalScope
-
-pp_keywords = [
- "PRAGMA",
- "INCLUDE",
- "UNDEF",
- "DEFINE",
- "#IF",
- "ELIF",
- "#ELSE",
- "IFDEF",
- "IFNDEF",
- "ENDIF",
- "ERROR",
- "WARNING",
- "IMPORT"]
-
-whitespaces = ["TAB", "SPACE", "NEWLINE"]
-
-
-class IsPreprocessorStatement(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.priority = 85
- self.scope = []
+import sys
+import contextlib
+from norminette.rules import Rule, Primary
+from norminette.lexer.dictionary import keywords
+from norminette.exceptions import CParsingError
+from norminette.context import Macro
+
+UNARY_OPERATORS = (
+ "PLUS",
+ "MINUS",
+ "NOT", # !
+ "BWISE_NOT", # ~
+)
+
+BINARY_OPERATORS = (
+ # Arithmetic operators
+ "PLUS",
+ "MINUS",
+ "MULT",
+ "DIV",
+ "MODULO",
+ # Relational operators
+ "EQUALS", # ==
+ "NOT_EQUAL", # !=
+ "MORE_THAN", # > (why not GREATER_THAN?)
+ "LESS_THAN", # <
+ "GREATER_OR_EQUAL", # >=
+ "LESS_OR_EQUAL", # <=
+ # Logical operators
+ "AND",
+ "OR",
+ # Bitwise operators
+ "BWISE_AND", # &
+ "BWISE_OR", # |
+ "BWISE_XOR", # ^
+ "BWISE_XOR", # ^
+ "LEFT_SHIFT", # << (why not BWISE_LEFT?)
+ "RIGHT_SHIFT", # >>
+)
+
+ALLOWED_IN_PATH = (
+ "IDENTIFIER",
+ "DIV",
+ "MINUS",
+ "DOT",
+ "SPACE",
+ "TAB",
+ # TODO Remove all keyword tokens and add to just use 'IDENTIFIER' instead
+ *keywords.values(), # https://github.com/42School/norminette/issues/470
+)
+
+
+@contextlib.contextmanager
+def recursion_limit(limit):
+ old_limit = sys.getrecursionlimit()
+ sys.setrecursionlimit(limit)
+ yield
+ sys.setrecursionlimit(old_limit)
+
+
+class IsPreprocessorStatement(Rule, Primary, priority=100):
def run(self, context):
"""
- Catches any kind of preprocessor statements
- Handles indentation related informations
+ Catches any kind of preprocessor statements
+ Handles indentation related informations
"""
i = context.skip_ws(0)
- if context.check_token(i, pp_keywords) is True:
- if context.peek_token(i).value is None \
- or context.peek_token(i).value.startswith("#") is False:
- return False, 0
- if context.check_token(i, ["IFDEF", "IFNDEF"]):
- context.preproc_scope_indent += 1
- elif context.check_token(i, "ENDIF") \
- and context.preproc_scope_indent > 0:
- context.preproc_scope_indent -= 1
- i += 1
- else:
+ if not context.check_token(i, "HASH"):
return False, 0
- i = context.eol(i)
- return True, i
+ self.hash = context.peek_token(i)
+ i += 1
+ i = context.skip_ws(i)
+ if context.check_token(i, "NEWLINE"): # Null directive
+ return True, i + 1 # TODO: Fix null directives (comments)
+ # Why `if` and `else` need to be a special case?
+ if not context.check_token(i, ("IDENTIFIER", "IF", "ELSE")):
+ raise CParsingError(f"Invalid preprocessor statement {context.peek_token(i)}")
+ token = context.peek_token(i)
+ direc = (token.value if token.type == "IDENTIFIER" else token.type).lower()
+ i += 1
+ i = context.skip_ws(i)
+ if checker := getattr(self, f"check_{direc}", None):
+ return checker(context, i)
+ raise CParsingError(f"Invalid preprocessing directive #{direc}")
+
+ def check_define(self, context, index):
+ if not context.check_token(index, "IDENTIFIER"):
+ raise CParsingError("No identifier after #define")
+ token = context.peek_token(index)
+ index += 1
+ if is_function := context.check_token(index, "LPARENTHESIS"):
+ index += 1
+ index = context.skip_ws(index)
+ while context.check_token(index, "IDENTIFIER"):
+ index += 1
+ index = context.skip_ws(index)
+ if context.check_token(index, "COMMA"):
+ index += 1
+ index = context.skip_ws(index)
+ # Add better errors like check EOF and invalid identifier?
+ if not context.check_token(index, "RPARENTHESIS"):
+ raise CParsingError("Invalid macro function definition")
+ index += 1
+ macro = Macro.from_token(token, is_func=is_function)
+ context.preproc.macros.append(macro)
+ index = context.skip_ws(index)
+ return self._just_token_string("define", context, index)
+
+ def check_import(self, context, index):
+ is_valid_argument, index = self._check_path(context, index)
+ if not is_valid_argument:
+ raise CParsingError("Invalid file argument for #import directive")
+ index = context.skip_ws(index)
+ return self._just_token_string("import", context, index)
+
+ def check_pragma(self, context, index):
+ return self._just_token_string("pragma", context, index)
+
+ def check_error(self, context, index):
+ return self._just_token_string("error", context, index)
+
+ def check_warning(self, context, index):
+ return self._just_token_string("warning", context, index)
+
+ def check_if(self, context, index):
+ if not self.corresponding_endif(context, index):
+ context.new_error("PREPROC_BAD_IF", self.hash)
+ context.preproc.indent += 1
+ context.preproc.total_ifs += 1
+ return self._just_constant_expression("if", context, index)
+
+ def check_elif(self, context, index):
+ if not self.corresponding_endif(context, index):
+ context.new_error("PREPROC_BAD_ELIF", self.hash)
+ context.preproc.total_elifs += 1
+ return self._just_constant_expression("elif", context, index)
+
+ def check_ifdef(self, context, index):
+ if not self.corresponding_endif(context, index):
+ context.new_error("PREPROC_BAD_IFDEF", self.hash)
+ context.preproc.indent += 1
+ context.preproc.total_ifdefs += 1
+ return self._just_identifier("ifdef", context, index)
+
+ def check_ifndef(self, context, index):
+ if not self.corresponding_endif(context, index):
+ context.new_error("PREPROC_BAD_IFNDEF", self.hash)
+ context.preproc.indent += 1
+ context.preproc.total_ifndefs += 1
+ return self._just_identifier("infdef", context, index)
+
+ def check_undef(self, context, index):
+ return self._just_identifier("undef", context, index)
+
+ def check_else(self, context, index):
+ if context.preproc.indent == 0:
+ context.new_error("PREPROC_BAD_ELSE", self.hash)
+ context.preproc.total_elses += 1
+ return self._just_eol("else", context, index)
+
+ def check_endif(self, context, index):
+ if context.preproc.indent == 0:
+ context.new_error("PREPROC_BAD_ENDIF", self.hash)
+ context.preproc.indent -= 1
+ return self._just_eol("endif", context, index)
+
+ def check_include(self, context, index):
+ is_valid_argument, index = self._check_path(context, index)
+ if not is_valid_argument:
+ raise CParsingError("Invalid file argument for #include directive")
+ return self._just_eol("include", context, index)
+
+ def corresponding_endif(self, context, index):
+ """Checks if the corresponding `#endif` is present.
+ """
+ depth = 0
+ while index < len(context.tokens):
+ if not context.check_token(index, "HASH"):
+ index += 1
+ continue
+
+ index += 1
+ index = context.skip_ws(index)
+ if not context.check_token(index, ("IDENTIFIER", "IF", "ELSE")):
+ continue
+
+ token = context.peek_token(index)
+ direc = (token.value if token.type == "IDENTIFIER" else token.type).lower()
+ if direc == "endif":
+ if depth == 0:
+ return True
+ depth -= 1
+ elif direc in ("if", "ifdef", "ifndef"):
+ depth += 1
+ index += 1
+ return False
+
+ def _check_path(self, context, index):
+ """Checks the argument of an include/import statement.
+
+ Examples of valid headers:
+ - `"libft.h"`
+ - `< bla.h >`
+ - `<42.h >`
+ - `< four.two>`
+ """
+ # TODO: It not works with `#include ` because of the `if` keyword
+ if context.check_token(index, "STRING"):
+ index += 1
+ return True, index
+ if not context.check_token(index, "LESS_THAN"):
+ return False, index
+ index = context.skip_ws(index + 1)
+ while context.check_token(index, ALLOWED_IN_PATH):
+ index += 1
+ if not context.check_token(index, "MORE_THAN"):
+ return False, index
+ index += 1
+ return True, index
+
+ def _just_token_string(self, directive, context, index):
+ index = context.skip_ws(index, comment=True)
+ if context.check_token(index, "NEWLINE"):
+ index += 1
+ return True, index
+ lines = 1
+ newline = False
+ while context.peek_token(index) is not None and lines > 0:
+ if context.check_token(index, "NEWLINE"):
+ lines -= 1
+ newline = False
+ elif context.check_token(index, "BACKSLASH") and not newline:
+ lines += 1
+ newline = True
+ index += 1
+ if lines > 0 and context.peek_token(index) is not None:
+ raise CParsingError(f"Unexpected end of file after #{directive} directive")
+ return True, index
+
+ def _just_constant_expression(self, directive, context, index):
+ parser = ConstantExpressionParser(directive, context, index)
+ ok, index = parser.parse()
+ if not ok:
+ return ok, index
+ return self._just_eol(directive, context, index)
+
+ def _just_identifier(self, directive, context, index):
+ if not context.check_token(index, "IDENTIFIER"):
+ raise CParsingError(f"Invalid argument for #{directive} statement")
+ index += 1
+ return self._just_eol(directive, context, index)
+
+ def _just_eol(self, directive, context, index):
+ index = context.skip_ws(index, comment=True)
+ if context.peek_token(index) is None:
+ return True, index
+ # raise CParsingError(f"Unexpected end of file after #{directive} directive")
+ if not context.check_token(index, "NEWLINE"):
+ raise CParsingError(f"Extra tokens at end of #{directive} directive")
+ index += 1
+ return True, index
+
+
+class ConstantExpressionParser:
+ """Parses a constant expression that can be used in preprocessor statements.
+
+ ```bnf
+ ::=
+ | unary_operator
+ | ( "(" ")" | ) ( binary_operator )*
+ ::= string
+ | constant
+ | identifier
+ | identifier '(' [ ("," )* ] ')'
+ ```
+ The `string`, `constant` and `identifier` comes from the tokenizer.
+ """
+
+ def __init__(self, directive, context, index):
+ self.directive = directive
+ self.context = context
+ self.index = index
+
+ def parse(self):
+ try:
+ index = self.index
+ self.parse_constant_expression()
+ if index == self.index: # No tokens were parsed
+ raise CParsingError(f"No argument for #{self.directive} statement")
+ if self.context.peek_token(self.index) is None:
+ raise CParsingError("Unexpected end of file while parsing constant expression")
+ self.index = self.context.skip_ws(self.index, comment=True)
+ if not self.context.check_token(self.index, "NEWLINE"):
+ raise CParsingError("Unexpected tokens after the constant expression")
+ # self.index += 1 # Skip the newline
+ except RecursionError:
+ raise CParsingError("Constant expression too complex")
+ return True, self.index
+
+ def skip_ws(self):
+ self.index = self.context.skip_ws(self.index)
+
+ @recursion_limit(100)
+ def parse_constant_expression(self):
+ self.parse_expression()
+
+ def parse_expression(self):
+ if self.context.check_token(self.index, "LPARENTHESIS"):
+ self.index += 1
+ self.parse_expression()
+ if not self.context.check_token(self.index, "RPARENTHESIS"):
+ raise CParsingError("Missing closing parenthesis while parsing constant expression")
+ self.index += 1
+ self.parse_potential_binary_operator()
+ return
+
+ if (
+ self.context.check_token(self.index, UNARY_OPERATORS)
+ or (
+ self.context.check_token(self.index, "IDENTIFIER")
+ and self.context.peek_token(self.index).value == "defined"
+ )
+ ):
+ self.index += 1
+ self.skip_ws()
+ self.parse_expression()
+ return
+
+ if self.context.check_token(self.index, "IDENTIFIER"):
+ self.index += 1
+ if self.context.check_token(self.index, "LPARENTHESIS"):
+ self.index += 1
+ self.parse_function_macro()
+ return
+ self.parse_potential_binary_operator()
+ return
+
+ if self.context.check_token(self.index, ("STRING", "CONSTANT", "CHAR_CONST")):
+ self.index += 1
+ self.parse_potential_binary_operator()
+ return
+
+ def parse_function_macro(self):
+ self.skip_ws()
+ if not self.context.check_token(self.index, "RPARENTHESIS"):
+ self.parse_expression()
+ while self.context.check_token(self.index, "COMMA"):
+ self.index += 1
+ self.skip_ws()
+ self.parse_expression()
+ if not self.context.check_token(self.index, "RPARENTHESIS"):
+ raise CParsingError("Missing closing parenthesis")
+ self.index += 1
+ self.skip_ws()
+
+ def parse_potential_binary_operator(self):
+ self.skip_ws()
+ if self.context.check_token(self.index, BINARY_OPERATORS):
+ self.index += 1
+ self.skip_ws()
+ self.parse_expression()
+ return
diff --git a/norminette/rules/is_ternary.py b/norminette/rules/is_ternary.py
index 5ffa5f9c..59e30087 100644
--- a/norminette/rules/is_ternary.py
+++ b/norminette/rules/is_ternary.py
@@ -1,21 +1,15 @@
-from lexer import Token
-from rules import Rule
-import string
+from norminette.rules import Rule, Primary
-class IsTernary(Rule):
- def __init__(self):
- super().__init__()
- self.priority = 50
- self.scope = []
+class IsTernary(Rule, Primary, priority=53):
def run(self, context):
"""
- Catches ternaries and raises an error
+ Catches ternaries and raises an error
"""
i = 0
- while context.peek_token(i) is not None and context.check_token(i, "SEMI_COLON") is False:
+ while context.peek_token(i) is not None and context.check_token(i, ["SEMI_COLON", "NEWLINE"]) is False:
if context.check_token(i, "TERN_CONDITION") is True:
- while context.peek_token(i) is not None and context.check_token(i, "SEMI_COLON") is False:
+ while context.peek_token(i) is not None and context.check_token(i, ["SEMI_COLON", "NEWLINE"]) is False:
i += 1
i += 1
i = context.eol(i)
diff --git a/norminette/rules/is_user_defined_type.py b/norminette/rules/is_user_defined_type.py
index b90aa271..97ab6b2b 100644
--- a/norminette/rules/is_user_defined_type.py
+++ b/norminette/rules/is_user_defined_type.py
@@ -1,19 +1,10 @@
-from lexer import Token
-from rules import PrimaryRule
-from context import GlobalScope, UserDefinedType
-from exceptions import CParsingError
-from scope import *
+from norminette.rules import Rule, Primary
+from norminette.scope import UserDefinedType, UserDefinedEnum
utypes = ["TYPEDEF", "UNION", "STRUCT", "ENUM"]
-class IsUserDefinedType(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.priority = 45
- self.scope = [GlobalScope, UserDefinedType]
-
-
+class IsUserDefinedType(Rule, Primary, priority=45):
def typedef(self, context, pos):
i = context.skip_ws(pos)
if "TYPEDEF" not in [tkn.type for tkn in context.tokens[:i]]:
@@ -32,18 +23,17 @@ def utype_definition(self, context, pos):
if not [tkn for tkn in context.tokens[:pos] if tkn.type in utypes]:
return False, pos
return True, pos
- i = context.skip_ws(i)
- return ret, i
def run(self, context):
"""
- Catches user type definitions
- Can include the whole type definition in case it's a structure
- Variable declarations aren't included
+ Catches user type definitions
+ Can include the whole type definition in case it's a structure
+ Variable declarations aren't included
"""
i = context.skip_ws(0, nl=False)
enum = False
p = 0
+ ids = []
while context.peek_token(i) is not None:
if context.check_token(i, utypes) is True and p <= 0:
break
@@ -51,7 +41,7 @@ def run(self, context):
p += 1
if context.check_token(i, "RPARENTHESIS") is True:
p -= 1
- if context.check_token(i, ['NEWLINE', 'SEMI_COLON']) is True:
+ if context.check_token(i, ["NEWLINE", "SEMI_COLON"]) is True:
return False, 0
i += 1
if context.peek_token(i) is None:
@@ -66,9 +56,11 @@ def run(self, context):
enum = True
if context.check_token(i, ["NEWLINE", "SEMI_COLON"]) is True and p == 0:
break
+ if context.check_token(i, "IDENTIFIER"):
+ ids.append(context.peek_token(i))
i += 1
if context.check_token(i, "NEWLINE") is True and p <= 0:
- if enum == True:
+ if enum is True:
context.sub = context.scope.inner(UserDefinedEnum)
else:
context.sub = context.scope.inner(UserDefinedType)
@@ -76,5 +68,6 @@ def run(self, context):
return True, i
elif context.check_token(i, "SEMI_COLON") is True:
i += 1
+ context.scope.vars_name.append(ids[-1])
i = context.eol(i)
return True, i
diff --git a/norminette/rules/is_var_declaration.py b/norminette/rules/is_var_declaration.py
index c3eea4e1..93ae9d64 100644
--- a/norminette/rules/is_var_declaration.py
+++ b/norminette/rules/is_var_declaration.py
@@ -1,7 +1,8 @@
-from lexer import Token
-from rules import PrimaryRule
-from context import GlobalScope, UserDefinedType, ControlStructure, Function
-
+from norminette.context import ControlStructure
+from norminette.scope import Function
+from norminette.context import GlobalScope
+from norminette.scope import UserDefinedType
+from norminette.rules import Rule, Primary
lbrackets = ["LBRACE", "LPARENTHESIS", "LBRACKET"]
rbrackets = ["RBRACE", "RPARENTHESIS", "RBRACKET"]
@@ -13,7 +14,7 @@
"VOLATILE",
"EXTERN",
"INLINE",
- "RESTRICT"
+ "RESTRICT",
"SIGNED",
"UNSIGNED",
]
@@ -30,14 +31,17 @@
"SHORT",
"STRUCT",
"ENUM",
- "UNION"
+ "UNION",
]
-class IsVarDeclaration(PrimaryRule):
- def __init__(self):
- super().__init__()
- self.priority = 75
- self.scope = [GlobalScope, UserDefinedType, Function, ControlStructure]
+
+class IsVarDeclaration(Rule, Primary, priority=75):
+ scope = (
+ GlobalScope,
+ UserDefinedType,
+ Function,
+ ControlStructure,
+ )
def assignment_right_side(self, context, pos):
sep = ["COMMA", "SEMI_COLON", "ASSIGN"]
@@ -49,19 +53,31 @@ def assignment_right_side(self, context, pos):
return True, i
def var_declaration(self, context, pos, identifier=False):
- pclose = ["RPARENTHESIS", "NEWLINE", "SPACE", "TAB"]
brackets = 0
parenthesis = 0
braces = 0
i = pos
ret_store = None
- while context.peek_token(i) is not None and context.check_token(i, ["COMMA", "SEMI_COLON"]) is False:
- if context.check_token(i, "IDENTIFIER") is True and braces == 0 and brackets == 0 and parenthesis == 0:
+ ids = []
+ while (
+ context.peek_token(i) is not None
+ and context.check_token(i, ["SEMI_COLON"]) is False
+ ):
+ if (
+ context.check_token(i, "IDENTIFIER") is True
+ and braces == 0
+ and brackets == 0
+ and parenthesis == 0
+ ):
identifier = True
+ ids.append(context.peek_token(i))
elif context.check_token(i, ["COMMENT", "MULT_COMMENT"]) is True:
i += 1
continue
- elif context.check_token(i, ["COLON", "CONSTANT"]) is True and identifier == True:
+ elif (
+ context.check_token(i, ["COLON", "CONSTANT"]) is True
+ and identifier is True
+ ):
i += 1
continue
elif context.check_token(i, lbrackets) is True:
@@ -69,11 +85,25 @@ def var_declaration(self, context, pos, identifier=False):
braces += 1
if context.check_token(i, "LBRACKET") is True:
brackets += 1
- if context.check_token(i, "LPARENTHESIS") is True:
+ if (
+ context.check_token(i, "LPARENTHESIS") is True
+ and brackets == 0
+ and braces == 0
+ ):
ret, tmp = context.parenthesis_contain(i, ret_store)
- if ret == 'function' or ret == 'pointer':
+ if ret == "function" or ret == "pointer" or ret == "var":
ret_store = ret
identifier = True
+ tmp2 = tmp - 1
+ deep = 1
+ while tmp2 > 0 and deep > 0:
+ if context.check_token(tmp2, "IDENTIFIER"):
+ ids.append(context.peek_token(tmp2))
+ if context.check_token(tmp2, "RPARENTHESIS"):
+ deep += 1
+ if context.check_token(tmp2, "LPARENTHESIS"):
+ deep -= 1
+ tmp2 -= 1
i = tmp
else:
parenthesis += 1
@@ -85,21 +115,34 @@ def var_declaration(self, context, pos, identifier=False):
if context.check_token(i, "RPARENTHESIS") is True:
parenthesis -= 1
elif context.check_token(i, "ASSIGN") is True:
- if identifier == False:
+ if identifier is False:
return False, pos
ret, i = self.assignment_right_side(context, i + 1)
i -= 1
if ret is False:
return False, pos
- elif context.check_token(i, ['SPACE', "TAB", "MULT", "BWISE_AND", "NEWLINE"] + misc_specifiers + type_specifiers):
+ elif context.check_token(
+ i,
+ ["SPACE", "TAB", "MULT", "BWISE_AND", "NEWLINE"]
+ + misc_specifiers
+ + type_specifiers,
+ ):
pass
+ elif (
+ context.check_token(i, "COMMA") is True
+ and parenthesis == 0
+ and brackets == 0
+ and braces == 0
+ ):
+ break
elif parenthesis == 0 and brackets == 0 and braces == 0:
return False, 0
i += 1
- if identifier == False or braces > 0 or brackets > 0 or parenthesis > 0:
+ if identifier is False or braces > 0 or brackets > 0 or parenthesis > 0:
return False, 0
+ context.scope.vars_name.append(ids[-1])
if context.check_token(i, "SEMI_COLON") is True:
- if brackets == 0 and braces == 0 and parenthesis == 0:
+ if brackets <= 0 and braces <= 0 and parenthesis <= 0:
return True, i
else:
return False, 0
@@ -110,13 +153,13 @@ def var_declaration(self, context, pos, identifier=False):
def is_func_pointer(self, context, pos):
i = context.skip_ws(pos)
- ws = ['SPACE', "TAB", "NEWLINE"]
+ ws = ["SPACE", "TAB", "NEWLINE"]
if context.check_token(i, "LPARENTHESIS") is False:
return False, pos
identifier = False
i += 1
p = 1
- plvl= 0 # nesting level of the first pointer operator encountered
+ plvl = 0 # nesting level of the first pointer operator encountered
while p and context.check_token(i, ["MULT", "LPARENTHESIS"] + ws):
if context.check_token(i, "MULT") and not plvl:
@@ -133,7 +176,6 @@ def is_func_pointer(self, context, pos):
elif context.check_token(i, "RPARENTHESIS") is True:
p -= 1
if identifier is True:
- par_pos = i
break
elif context.check_token(i, "IDENTIFIER") is True:
identifier = True
@@ -146,15 +188,21 @@ def is_func_pointer(self, context, pos):
def run(self, context):
"""
- Catches all kinds of variable declarations
+ Catches all kinds of variable declarations
"""
ret, i = context.check_type_specifier(0)
+ i = context.skip_ws(i)
if ret is False:
return False, 0
tmp = i - 1
while context.check_token(tmp, ["LPARENTHESIS", "MULT", "BWISE_AND"]):
tmp -= 1
- if context.check_token(tmp, ['SPACE', 'TAB']) is False and context.check_token(tmp - 1, ['SPACE', 'TAB']) is False:
+ if context.check_token(tmp, "SEMI_COLON"):
+ return True, i
+ if (
+ context.check_token(tmp, ["SPACE", "TAB", "NEWLINE"]) is False
+ and context.check_token(tmp - 1, ["SPACE", "TAB", "NEWLINE"]) is False
+ ):
return False, 0
ret, i = self.var_declaration(context, i)
if ret is False:
diff --git a/norminette/rules/rule.py b/norminette/rules/rule.py
index 2820dcc6..7c0465c1 100644
--- a/norminette/rules/rule.py
+++ b/norminette/rules/rule.py
@@ -1,30 +1,89 @@
+from typing import Tuple, Any
+
+from norminette.context import Context
+
class Rule:
- def __init__(self):
- self.name = type(self).__name__
- self.depends_on = []
- self.primary = False
-
- def register(self, registry):
- if self.depends_on == []:
- if 'all' in registry.dependencies:
- registry.dependencies['all'].append(self.name)
- else:
- registry.dependencies['all'] = [self.name]
-
- for rule in self.depends_on:
- if rule in registry.dependencies:
- registry.dependencies[rule].append(self.name)
- else:
- registry.dependencies[rule] = [self.name]
-
-
-class PrimaryRule(Rule):
- def __init__(self):
- super().__init__()
- self.primary = True
- self.priority = 0
- self.scope = []
-
- def run(self, context):
+ __slots__ = ()
+
+ def __new__(cls, context: Context, *args, **kwargs):
+ cls.context = context
+ cls.name = cls.__name__
+
+ return super().__new__(cls, *args, **kwargs)
+
+ def __repr__(self) -> str:
+ return self.name
+
+ def __hash__(self) -> int:
+ return hash(self.name)
+
+ def __eq__(self, value: Any) -> bool:
+ if isinstance(value, str):
+ return self.name == value
+ if isinstance(value, Rule):
+ return self.name == value.name
+ return super().__eq__(value)
+
+ def __ne__(self, value: Any) -> bool:
+ return not (self == value)
+
+
+class Check:
+ __slots__ = ()
+
+ depends_on: Tuple[str, ...]
+
+ runs_on_start: bool
+ runs_on_rule: bool
+ runs_on_end: bool
+
+ def __init_subclass__(cls, **kwargs):
+ if not hasattr(cls, "depends_on"):
+ cls.depends_on = ()
+ cls.runs_on_start = kwargs.pop("runs_on_start", getattr(cls, "runs_on_start", False))
+ cls.runs_on_rule = kwargs.pop("runs_on_rule", getattr(cls, "runs_on_rule", not cls.depends_on))
+ cls.runs_on_end = kwargs.pop("runs_on_end", getattr(cls, "runs_on_end", False))
+
+ @classmethod
+ def register(cls, registry):
+ for rule in cls.depends_on:
+ registry.dependencies[rule].append(cls)
+ if cls.runs_on_start:
+ registry.dependencies["_start"].append(cls)
+ if cls.runs_on_rule:
+ registry.dependencies["_rule"].append(cls)
+ if cls.runs_on_end:
+ registry.dependencies["_end"].append(cls)
+
+ def is_starting(self):
+ """Returns if this `Check` is being run before `Primary`.
+
+ It is only called if `runs_on_start` is set to `True`.
+ """
+ return self.context.state == "starting" # type: ignore
+
+ def is_ending(self):
+ """Returns if this `Check` is being run after all rules.
+
+ It is only called if `runs_on_end` is set to `True`.
+ """
+ return self.context.state == "ending" # type: ignore
+
+ def run(self, context: Context) -> None:
+ return
+
+
+class Primary:
+ __slots__ = ()
+
+ priority: int
+ scope: Tuple[str, ...]
+
+ def __init_subclass__(cls, **kwargs: Any):
+ cls.priority = kwargs.pop("priority", 0)
+ if not hasattr(cls, "scope"):
+ cls.scope = ()
+
+ def run(self, context: Context) -> Tuple[bool, int]:
return False, 0
diff --git a/norminette/run_test.sh b/norminette/run_test.sh
deleted file mode 100755
index 16b2a1a8..00000000
--- a/norminette/run_test.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-echo "Running lexer unit test:"
-python -m unittest discover tests/lexer/unit-tests/ "*.py"
-echo "Running lexer test on files:"
-python -m tests.lexer.files.file_token_test
-python -m tests.lexer.errors.tester
-python -m tests.rules.rule_tester
diff --git a/norminette/scope.py b/norminette/scope.py
index 1db98de0..a7ad9402 100644
--- a/norminette/scope.py
+++ b/norminette/scope.py
@@ -1,8 +1,9 @@
class Scope:
"""
- Main scope class
- Contain various scope informations updated as the norminette runs through the file
+ Main scope class
+ Contain various scope informations updated as the norminette runs through the file
"""
+
def __init__(self, parent=None):
self.parent = parent
self.name = type(self).__name__
@@ -13,6 +14,7 @@ def __init__(self, parent=None):
# ########################################################## #
self.vdeclarations_allowed = False
self.vars = 0
+ self.vars_name = []
self.vars_alignment = 0
self.func_alignment = 0
# ########################################################## #
@@ -27,27 +29,41 @@ def inner(self, sub):
def outer(self):
"""
- Return outer scope (None if called on GlobalScope)
- Adds the line of current scope to parent scope
- to calculate function length or control structure length
+ Return outer scope (None if called on GlobalScope)
+ Adds the line of current scope to parent scope
+ to calculate function length or control structure length
"""
if self.parent is not None:
self.parent.lines += self.lines
- #print (f"{self.name} -> {self.parent.name}")
+ # print (f"{self.name} -> {self.parent.name}")
return self.parent
+ def __eq__(self, value) -> bool:
+ if isinstance(value, str):
+ return self.name == value
+ if issubclass(value, Scope):
+ return self.name == value.__name__
+ if hasattr(value, "name"):
+ return self.name == value.name
+ return super().__eq__(value)
+
+ def __ne__(self, value) -> bool:
+ return not (self == value)
+
def get_outer(self):
"""
- Allows to peek to the parent scope without adding lines to
- the parent scope
+ Allows to peek to the parent scope without adding lines to
+ the parent scope
"""
return self.parent
+
class GlobalScope(Scope):
"""
- GlobalScope contains every other scope
- Has no parent scope (returns None)
+ GlobalScope contains every other scope
+ Has no parent scope (returns None)
"""
+
def __init__(self):
super().__init__()
self.fdeclarations_allowed = True
@@ -56,11 +72,13 @@ def __init__(self):
self.func_alignment = 0
self.include_allowed = True
+
class Function(Scope):
"""
Function definition scope, anything between the opening/closing braces of
a function
"""
+
def __init__(self, parent):
super().__init__(parent)
self.fname_pos = 0
@@ -75,6 +93,7 @@ class ControlStructure(Scope):
only one instruction, if that instruction creates a new sub scope, it can
contain as many instruction as that scope can "hold"
"""
+
def __init__(self, parent, multiline=False):
super().__init__(parent)
self.multiline = multiline
@@ -85,15 +104,18 @@ class UserDefinedType(Scope):
User defined type scope (struct, union, enum), only variables declarations
are allowed within this scope
"""
+
def __init__(self, parent, typedef=False):
super().__init__(parent)
self.typedef = typedef
+
class UserDefinedEnum(Scope):
"""
User defined type scope (struct, union, enum), only variables declarations
are allowed within this scope
"""
+
def __init__(self, parent, typedef=False):
super().__init__(parent)
self.typedef = typedef
@@ -105,4 +127,5 @@ class VariableAssignation(Scope):
assignations (int foo[4] = {0, 0, 0, 0};) easier.
- Unused
"""
+
pass
diff --git a/norminette/tests/__init__.py b/norminette/tests/__init__.py
deleted file mode 100644
index 2f409eb5..00000000
--- a/norminette/tests/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# Rule testing suite
diff --git a/norminette/tests/a.c b/norminette/tests/a.c
deleted file mode 100644
index f6a149c3..00000000
--- a/norminette/tests/a.c
+++ /dev/null
@@ -1,63 +0,0 @@
-int (foo(int a))
-{
- if (1)
- {
-
-}
- return (1);
-}
-
-short int a() { return 1; }
-
-int (faa)(int *a(int), char b, int c, int r)
-{
- return 1;
-}
-
-#include
-int *truc()
-{
- return malloc(sizeof(int));
-}
-
-int (*f2(void))(int) {
- return foo; }
-
-int ((*((((bar)(int a))))))(int)
-{
- return (foo);
-}
-
-int (*(f)(char a, int t, int b))(int) { return foo;}
-
-int (*fp(int)); //Function pointer, NOT FUNC!
-int ((*(fp2))(int a));
-
-//int ((*T)(int))(int);
-
-int ((*f23(int))) ;
-int (*fff[1])(void);
-enum Bla {
- A,
- B
-};
-
-enum Bla func(void);
-unsigned enum Bla func(void);
-long long enum Bla func(void);
-enum long long Bla func(void);
-enum long long Bla func(void);
- Bla func(void);
-youpi func2(void);
-trololo43 const func3(youpibanane cahuete);
-trololo43 func3(youpibanane);
-trololo43 func3(cahuete);
-trololo43 func3(youpibanane cahuete);
-trololo43 func3(youpibanane cahuete, lol choupette);
-trololo43 func3(youpibanane cahuete, lol choupette);
-trololo43 func3(youpibanane cahuete,lol choupette);
-trololo43 func3(youpibanane cahuete, lol ***choupette);
-trololo43 func3(youpibanane cahuete, lol * choupette);
-trololo43 func3(youpibanane **cahuete, lol*** **choupette);
-trololo trololol func4(uopi sks);
-***func4(udidf fdfd);
diff --git a/norminette/tests/c.c b/norminette/tests/c.c
deleted file mode 100644
index 83eca060..00000000
--- a/norminette/tests/c.c
+++ /dev/null
@@ -1,64 +0,0 @@
-#define foo 1 void bar(struct foo *a, struct foo **b)
-{
- int a;
- int b;
-}
-
-int f(void)
-{
-return 1;
-}
-
-int a = f();
-
-struct foo {
-int a, b, c;
-};
-
-typedef struct f0o {
- int a, b, c;
-} t_foo;
-
-t_foo variable = {
- 1, 2, 3
-};
-
-typedef int POUET;
-
-
-POUET a = 5;
-
-typedef struct bar {
- int a;
- int b;
- char truc;
-} t_bar;
-
-void bar(struct foo *a, struct foo **b)
-{
- (void)a, (void)b;
-}
-
-struct mystruct;
-
-
-int write(int a, char *p, int n);
-
-struct mystruct {
-int a, b, c;
-};
-
-#include
-int main(void) {
- int a, b = 5, c, d, e;
-
- b = 2, c =3, write(1, "A", 2);;
- (void)1;
- while (1)
- if (1)
- if (1)
- if (1)
- write(1,"B", 1);
-
- printf("%lu", sizeof(struct mystruct));
-}
diff --git a/norminette/tests/func.c b/norminette/tests/func.c
deleted file mode 100644
index 19a65304..00000000
--- a/norminette/tests/func.c
+++ /dev/null
@@ -1,39 +0,0 @@
-#include
-/* basic func */
- int foo(int a) { return 1; }
-
-/* basic func, name and params wrapped in parentheses */
-int (foo2(int a))
-{ return 1; }
-
-
-/* basic func, name wrapped in parentheses
- * */
-int (*foo3(int *f(int), int a)) { return (int*)0; }
-
-/* basic func, name and params wrapped in way too much parentheses */
-int ((((foo4(int a))))) { return 1; }
-
-/* basic func, returning a pointer*/
-int *foo5(){ return malloc(sizeof(int)); }
-
-/* func returning a func pointer*/
-int(*foo6(int a, int b, int *z, int c, int d))(int)
-{
-return foo;
-}
-
-/* func returning a func pointer wrapped in way too much parentheses */
-int (((*foo7(int a,
- int b))(int))) { return foo; }
-
-
-/* func returning a func pointer, wrapped in way too much parentheses #2 */
-int (((*foOo8(void))(int)))
-{
-int a();
-return foo;
-}
-/* func pointers, not funcs!!*/
-//int *(foo9)(int);
-//int (*(foo10)(int));
diff --git a/norminette/tests/functest.c b/norminette/tests/functest.c
deleted file mode 100644
index 03123b89..00000000
--- a/norminette/tests/functest.c
+++ /dev/null
@@ -1,44 +0,0 @@
-#include
-int foo(int a[2]);
-int (foobar(int a)) { return 1;}
-int (truc(int a)) { return a; }
-int bar(int a,int b, ...);
-int ((((foo2bar(int a,int b ,int c)))))
-{
- return 1;
-}
-int f(int a);
-int func(int a);
-char (*s1(int a[1]))
-{
- return malloc(sizeof(char) * 1);
-}
-
-
-//int (*fp(int a))(int) { return foo; }
-
-
-//int ((*T)(int))(int);
-int (*p)(int) = 0;
-int ((*p)(int))(int) { return f;}
-
-//int ((*fppppp)(int a)) {return 1;}
-
-#include
-#include
-
-char * (s(void))
-{
- return strdup("yo!\n");
-}
-
-int main(void)
-{
- int ((*fp)(int));
- int (*ffp)();
- int **fpp(int *f(int));
-
- fp = f;
- write(1, s(), strlen(s()));
- return 1;
-}
diff --git a/norminette/tests/ko_struct_name.out b/norminette/tests/ko_struct_name.out
deleted file mode 100644
index 8d00d40a..00000000
--- a/norminette/tests/ko_struct_name.out
+++ /dev/null
@@ -1,12 +0,0 @@
-tests/rules/ko_struct_name.c: KO!
- ESTRUCT_TYPE_NAMING (line: 1, col: 16): Structure name must start with s_
- EUSER_DEFINED_TYPEDEF (line: 1, col: 23): User defined typedef must start with t_
- EGLOBAL_VAR_NAMING (line: 2, col: 19): Global variable must start with g_
- EGLOBAL_VAR_NAMING (line: 3, col: 10): Global variable must start with g_
- ESTRUCT_TYPE_NAMING (line: 5, col: 16): Structure name must start with s_
- ESTRUCT_TYPE_NAMING (line: 6, col: 12): Structure name must start with s_
- EUNION_TYPE_NAMING (line: 7, col: 11): Union name must start with u_
- EUNION_TYPE_NAMING (line: 8, col: 19): Union name must start with u_
- EUSER_DEFINED_TYPEDEF (line: 8, col: 24): User defined typedef must start with t_
- EENUM_TYPE_NAMING (line: 9, col: 10): Enum name must start with e_
- EUSER_DEFINED_TYPEDEF (line: 10, col: 5): User defined typedef must start with t_
\ No newline at end of file
diff --git a/norminette/tests/lexer/__init__.py b/norminette/tests/lexer/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/norminette/tests/lexer/errors/__init__.py b/norminette/tests/lexer/errors/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/norminette/tests/lexer/errors/dict.py b/norminette/tests/lexer/errors/dict.py
deleted file mode 100644
index 96403b75..00000000
--- a/norminette/tests/lexer/errors/dict.py
+++ /dev/null
@@ -1,34 +0,0 @@
-failed_tokens_tests = {
- # String to test as key: error position as value
- "\tdouble f=45e++ai": [1, 14],
- "\tchar *b = \"e42\n\n": [1, 15],
- "int\t\t\tn\t= 0x1uLl;": [1, 19],
- "char\t\t\t*yo\t\t\t= \"": [1, 31],
- "{return 1;}\\\\\\n": [1, 12],
- "int a = a+++++a;\ndouble b = .0e4x;": [2, 12],
- "int a = 1;\nint b = 10ul;\nint c = 10lul;\n": [3, 9],
- "int number = 0x1uLl;": [1, 14],
- "int number = 0x1ULl;": [1, 14],
- "int number = 0x1lL;": [1, 14],
- "int number = 0x1Ll;": [1, 14],
- "int number = 0x1UlL;": [1, 14],
- "int number = 10ullll": [1, 14],
- "int number = 10lul": [1, 14],
- "int number = 10lUl": [1, 14],
- "int number = 10LUl": [1, 14],
- "int number = 10uu": [1, 14],
- "int number = 10Uu": [1, 14],
- "int number = 10UU": [1, 14],
- "int number = 0b0101e": [1, 14],
- "int number = 0b0101f": [1, 14],
- "int number = 0b0X101f": [1, 14],
- "int number = 0X101Uf": [1, 14],
- "int number = 0101f": [1, 14],
- "float number=10.12fe10": [1, 14],
- "float number=10.fU": [1, 14],
- "float number=21.3E56E4654": [1, 14],
- "float number=105e4d": [1, 14],
- "float number=105flu": [1, 14],
- "float number=105fu": [1, 14],
- "float number=105eu": [1, 14]
-}
diff --git a/norminette/tests/lexer/errors/tester.py b/norminette/tests/lexer/errors/tester.py
deleted file mode 100644
index d44c556a..00000000
--- a/norminette/tests/lexer/errors/tester.py
+++ /dev/null
@@ -1,63 +0,0 @@
-
-import sys
-import glob
-import difflib
-from lexer import Lexer
-from lexer import TokenError
-from tests.lexer.errors.dict import failed_tokens_tests as test_dict
-
-
-def read_file(filename):
- with open(filename) as f:
- return f.read()
-
-
-class norminetteTester():
-
- def __init__(self):
- self.__tests = 0
- self.__failed = 0
- self.__success = 0
- self.result = []
-
- def assertRaises(self, test, ref, test_line):
- try:
- diff = "".join(test())
- self.__failed += 1
- print(test_line + "KO")
- print(diff, end="")
- self.result.append("✗ ")
- except TokenError as e:
- if e.msg == ref:
- self.__success += 1
- self.result.append("✓ ")
- else:
- self.__failed += 1
- print(test_line + "KO")
- diff = difflib.ndiff(e.msg.splitlines(),
- ref.splitlines())
- diff = list(diff)
- self.result.append("✗ ")
- print(''.join(diff))
-
- def main(self):
- print("\n\nTesting error cases:\n")
- i = 1
- for key, val in test_dict.items():
- self.__tests += 1
- ref_output = f"Unrecognized token line {val[0]}, col {val[1]}"
- func = Lexer(key).check_tokens
- self.assertRaises(func, ref_output, f"Test {i}: " + repr(str(key)))
- i += 1
-
- print("----------------------------------")
- print(f"Total {self.__tests}")
- print("".join(self.result))
- print(f"Success {self.__success}, Failed {self.__failed}: ", end="")
- print("✅ OK!" if self.__failed == 0 else "❌ KO!")
-
- sys.exit(0 if self.__failed == 0 else 1)
-
-
-if __name__ == '__main__':
- norminetteTester().main()
diff --git a/norminette/tests/lexer/files/__init__.py b/norminette/tests/lexer/files/__init__.py
deleted file mode 100644
index 131337dc..00000000
--- a/norminette/tests/lexer/files/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# Testing modules
diff --git a/norminette/tests/lexer/files/file_token_test.py b/norminette/tests/lexer/files/file_token_test.py
deleted file mode 100644
index 49482a9c..00000000
--- a/norminette/tests/lexer/files/file_token_test.py
+++ /dev/null
@@ -1,84 +0,0 @@
-import sys
-import glob
-import difflib
-from lexer import Lexer
-from lexer import TokenError
-
-
-def read_file(filename):
- with open(filename) as f:
- return f.read()
-
-
-class norminetteFileTester():
-
- def __init__(self):
- self.__tests = 0
- self.__failed = 0
- self.__success = 0
- self.result = []
-
- def assertEqual(self, first, second):
- if first == second:
- self.__success += 1
- print("OK")
- self.result.append("✓ ")
- else:
- print("KO")
- self.__failed += 1
- diff = difflib.ndiff(first.splitlines(keepends=True),
- second.splitlines(keepends=True))
- diff = list(diff)
- self.result.append("✗ ")
- print(''.join(diff))
-
- def assertRaises(self, test, ref):
- try:
- diff = "".join(test())
- self.__failed += 1
- print("KO")
- print(diff, end="")
- self.result.append("✗ ")
- except TokenError as e:
- if e.msg == ref:
- self.__success += 1
- print(f"OK")
- self.result.append("✓ ")
- else:
- self.__failed += 1
- print("KO")
- diff = difflib.ndiff(e.msg.splitlines(),
- ref.splitlines())
- diff = list(diff)
- self.result.append("✗ ")
- print(''.join(diff))
-
- def test_files(self):
- files = glob.glob("tests/lexer/files/*.c")
- files.sort()
- for f in files:
- self.__tests += 1
- print(f.split('/')[-1], end=": ")
-
- try:
- output = Lexer(read_file(f)).check_tokens()
- except TokenError as t:
- self.__failed += 1
- print("KO")
- print(t)
- self.result.append("✗ ")
- continue
- reference_output = read_file(f.split(".")[0] + ".tokens")
- self.assertEqual(output, reference_output)
-
- print("----------------------------------")
- print(f"Total {self.__tests}")
- print("".join(self.result))
- print(f"Success {self.__success}, Failed {self.__failed}: ", end="")
- print("✅ OK!" if self.__failed == 0 else "❌ KO!")
-
- sys.exit(0 if self.__failed == 0 else 1)
-
-
-if __name__ == '__main__':
- norminetteFileTester().test_files()
diff --git a/norminette/tests/lexer/files/ok_test_05.tokens b/norminette/tests/lexer/files/ok_test_05.tokens
deleted file mode 100644
index e4f26f39..00000000
--- a/norminette/tests/lexer/files/ok_test_05.tokens
+++ /dev/null
@@ -1,13 +0,0 @@
->
-
-
-
-
-
-
-
-
-
diff --git a/norminette/tests/lexer/files/ok_test_21.tokens b/norminette/tests/lexer/files/ok_test_21.tokens
deleted file mode 100644
index 8643cfdd..00000000
--- a/norminette/tests/lexer/files/ok_test_21.tokens
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/norminette/tests/lexer/unit-tests/brackets_tokens_test.py b/norminette/tests/lexer/unit-tests/brackets_tokens_test.py
deleted file mode 100644
index 5d764768..00000000
--- a/norminette/tests/lexer/unit-tests/brackets_tokens_test.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import unittest
-import sys
-from lexer.lexer import Lexer
-
-
-class BracketsTokensTest(unittest.TestCase):
-
- def test_opening_bracket(self):
- self.assertEqual(
- Lexer("{").get_next_token().type,
- "LBRACE")
-
- def test_closing_bracket(self):
- self.assertEqual(Lexer("}").get_next_token().type, "RBRACE")
-
- def test_opening_parenthesis(self):
- self.assertEqual(Lexer("(").get_next_token().type, "LPARENTHESIS")
-
- def test_closing_parenthesis(self):
- self.assertEqual(Lexer(")").get_next_token().type, "RPARENTHESIS")
-
- def test_opening_square_bracket(self):
- self.assertEqual(
- Lexer("[").get_next_token().type,
- "LBRACKET")
-
- def test_closing_square_bracket(self):
- self.assertEqual(
- Lexer("]").get_next_token().type,
- "RBRACKET")
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/norminette/tests/lexer/unit-tests/char_constant_tokens_test.py b/norminette/tests/lexer/unit-tests/char_constant_tokens_test.py
deleted file mode 100644
index c217a01e..00000000
--- a/norminette/tests/lexer/unit-tests/char_constant_tokens_test.py
+++ /dev/null
@@ -1,50 +0,0 @@
-import unittest
-import sys
-from lexer.lexer import Lexer, TokenError
-
-
-class CharConstTokenTest(unittest.TestCase):
- def assertRaises(self, test):
- try:
- test()
- return False
- except TokenError:
- return True
-
- def test_basic_char(self):
- self.assertEqual(
- Lexer("'*'").get_next_token().test(),
- "")
-
- def test_escaped_newline(self):
- self.assertEqual(
- Lexer("'\\n'").get_next_token().test(),
- "")
-
- def test_octal_char(self):
- self.assertEqual(
- Lexer("'\\042'").get_next_token().test(),
- "")
-
- def test_hex_char(self):
- self.assertEqual(
- Lexer("'0x042'").get_next_token().test(),
- "")
-
- def test_hex_char(self):
- self.assertEqual(
- Lexer("'0x042'").get_next_token().test(),
- "")
-
- def test_error_newline_in_const(self):
- self.assertRaises(Lexer("'\n1'").get_next_token)
-
- def test_error_escaped_newline_followed_by_newline(self):
- self.assertRaises(Lexer("'\\n\n'").get_next_token)
-
- def test_error_unclosed_quote(self):
- self.assertRaises(Lexer("'A").get_next_token)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/norminette/tests/lexer/unit-tests/constant_tokens_test.py b/norminette/tests/lexer/unit-tests/constant_tokens_test.py
deleted file mode 100644
index 3b85380c..00000000
--- a/norminette/tests/lexer/unit-tests/constant_tokens_test.py
+++ /dev/null
@@ -1,129 +0,0 @@
-import unittest
-import sys
-from lexer.lexer import Lexer, TokenError
-
-
-class ConstantTokensTest(unittest.TestCase):
-
- def assertRaises(self, test):
- try:
- test()
- return False
- except TokenError:
- return True
-
- def test_basic_constant(self):
- self.assertEqual(Lexer("42").check_tokens(), "\n")
-
- def test_plus_sign_constant(self):
- self.assertEqual(
- Lexer("+42").check_tokens(),
- "\n")
-
- def test_minus_sign_constant(self):
- self.assertEqual(
- Lexer("-42").check_tokens(),
- "\n")
-
- def test_many_signs_constant(self):
- self.assertEqual(
- Lexer("+-42").check_tokens(),
- "\n")
-
- def test_decimal_constant(self):
- self.assertEqual(
- Lexer("4.2").check_tokens(),
- "\n")
-
- def test_decimal_constant_starting_with_dot(self):
- self.assertEqual(
- Lexer(".42").check_tokens(),
- "\n")
-
- def test_exponential_constant(self):
- self.assertEqual(
- Lexer("4e2").check_tokens(),
- "\n")
-
- def test_exponential_constant_starting_with_dot(self):
- self.assertEqual(
- Lexer(".4e2").check_tokens(),
- "\n")
-
- def test_octal_constant(self):
- self.assertEqual(
- Lexer("042").check_tokens(),
- "\n")
-
- def test_hex_constant(self):
- self.assertEqual(
- Lexer("0x42").check_tokens(),
- "\n")
-
- def test_hex_with_sign_constant(self):
- self.assertEqual(
- Lexer("-0x4e2").check_tokens(),
- "\n")
-
- def test_hex_with_many_signs_constant(self):
- self.assertEqual(
- Lexer("-+-+-+-+-+-+-+-0Xe4Ae2").check_tokens(),
- ""
- + ""
- + ""
- + "\n")
-
- def test_long_constant(self):
- self.assertEqual(
- Lexer("42l").check_tokens(),
- "\n")
-
- def test_unsigned_long_constant(self):
- self.assertEqual(
- Lexer("42ul").check_tokens(),
- "\n")
-
- def test_long_long_constant(self):
- self.assertEqual(
- Lexer("42ll").check_tokens(),
- "\n")
-
- def test_unsigned_long_long_constant(self):
- self.assertEqual(
- Lexer("42ull").check_tokens(),
- "\n")
-
- def test_unsigned_constant(self):
- self.assertEqual(
- Lexer("42u").check_tokens(),
- "\n")
-
- def test_error_too_many_dots(self):
- self.assertRaises(Lexer("4.4.4").check_tokens)
-
- def test_error_too_many_e(self):
- self.assertRaises(Lexer("4e4e4").check_tokens)
-
- def test_error_too_many_x(self):
- self.assertRaises(Lexer("4x4x4").check_tokens)
-
- def test_error_too_many_u(self):
- self.assertRaises(Lexer("42uul").check_tokens)
-
- def test_error_too_many_l(self):
- self.assertRaises(Lexer("42Lllu").check_tokens)
-
- def test_error_misplaced_l(self):
- self.assertRaises(Lexer("42lul").check_tokens)
-
- def test_misplaced_e(self):
- self.assertEqual(
- Lexer(".e42").check_tokens(),
- "\n")
-
- def test_another_misplaced_e(self):
- self.assertRaises(Lexer(".42e").check_tokens)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/norminette/tests/lexer/unit-tests/identifiers_tokens_test.py b/norminette/tests/lexer/unit-tests/identifiers_tokens_test.py
deleted file mode 100644
index 005b11a5..00000000
--- a/norminette/tests/lexer/unit-tests/identifiers_tokens_test.py
+++ /dev/null
@@ -1,45 +0,0 @@
-import unittest
-import sys
-from lexer.lexer import Lexer
-
-
-def eat_tokens(line):
- lex = Lexer(line)
- line = ""
- while lex.get_next_token():
- line += lex.peek_token().test()
- if lex.peek_token().type in ["EOF", "ERROR"]:
- break
- return line
-
-
-class IdentifiersTokensTest(unittest.TestCase):
-
- def test_simple_identifier(self):
- self.assertEqual(eat_tokens("foo"), "")
-
- def test_underscore_identifier(self):
- self.assertEqual(eat_tokens("_foo"), "")
-
- def test_underscore_with_number_identifier(self):
- self.assertEqual(eat_tokens("_foo42"), "")
-
- def test_double_underscore_with_number_identifier(self):
- self.assertEqual(eat_tokens("_foo__42"), "")
-
- def test_underscore_and_uppercase_identifier(self):
- self.assertEqual(eat_tokens("_FOO"), "")
-
- def test_underscore_at_the_end_and_uppercase_identifier(self):
- self.assertEqual(eat_tokens("FOO_"), "")
-
- def test_identifier_can_not_start_with_a_number(self):
- self.assertNotEqual(eat_tokens("5_FOO_"), "")
-
- def test_identifier_can_not_have_a_space(self):
- self.assertNotEqual(eat_tokens("foo 1"), "")
diff --git a/norminette/tests/lexer/unit-tests/keywords_tokens_test.py b/norminette/tests/lexer/unit-tests/keywords_tokens_test.py
deleted file mode 100644
index f4a88e41..00000000
--- a/norminette/tests/lexer/unit-tests/keywords_tokens_test.py
+++ /dev/null
@@ -1,132 +0,0 @@
-import unittest
-import sys
-from lexer.lexer import Lexer
-
-
-def eat_tokens(line):
- lex = Lexer(line)
- line = ""
- while lex.get_next_token():
- line += lex.peek_token().test()
- return line
-
-
-class TokensKeywordsTest(unittest.TestCase):
-
- def test_auto_keyword(self):
- self.assertEqual(eat_tokens("auto"), "")
-
- def test_break_keyword(self):
- self.assertEqual(eat_tokens("break"), "")
-
- def test_case_keyword(self):
- self.assertEqual(eat_tokens("case"), "")
-
- def test_char_keyword(self):
- self.assertEqual(eat_tokens("char"), "")
-
- def test_const_keyword(self):
- self.assertEqual(eat_tokens("const"), "")
-
- def test_continue_keyword(self):
- self.assertEqual(eat_tokens("continue"), "")
-
- def test_default_keyword(self):
- self.assertEqual(eat_tokens("default"), "")
-
- def test_do_keyword(self):
- self.assertEqual(eat_tokens("do"), "")
-
- def test_double_keyword(self):
- self.assertEqual(eat_tokens("double"), "")
-
- def test_else_keyword(self):
- self.assertEqual(eat_tokens("else"), "")
-
- def test_enum_keyword(self):
- self.assertEqual(eat_tokens("enum"), "")
-
- def test_extern_keyword(self):
- self.assertEqual(eat_tokens("extern"), "")
-
- def test_float_keyword(self):
- self.assertEqual(eat_tokens("float"), "")
-
- def test_for_keyword(self):
- self.assertEqual(eat_tokens("for"), "")
-
- def test_goto_keyword(self):
- self.assertEqual(eat_tokens("goto"), "")
-
- def test_if_keyword(self):
- self.assertEqual(eat_tokens("if"), "")
-
- def test_int_keyword(self):
- self.assertEqual(eat_tokens("int"), "")
-
- def test_long_keyword(self):
- self.assertEqual(eat_tokens("long"), "")
-
- def test_register_keyword(self):
- self.assertEqual(eat_tokens("register"), "")
-
- def test_return_keyword(self):
- self.assertEqual(eat_tokens("return"), "")
-
- def test_signed_keyword(self):
- self.assertEqual(eat_tokens("signed"), "")
-
- def test_sizeof_keyword(self):
- self.assertEqual(eat_tokens("sizeof"), "")
-
- def test_static_keyword(self):
- self.assertEqual(eat_tokens("static"), "")
-
- def test_struct_keyword(self):
- self.assertEqual(eat_tokens("struct"), "")
-
- def test_switch_keyword(self):
- self.assertEqual(eat_tokens("switch"), "")
-
- def test_typedef_keyword(self):
- self.assertEqual(eat_tokens("typedef"), "")
-
- def test_union_keyword(self):
- self.assertEqual(eat_tokens("union"), "")
-
- def test_unsigned_keyword(self):
- self.assertEqual(eat_tokens("unsigned"), "")
-
- def test_void_keyword(self):
- self.assertEqual(eat_tokens("void"), "")
-
- def test_volatile_keyword(self):
- self.assertEqual(eat_tokens("volatile"), "")
-
- def test_while_keyword(self):
- self.assertEqual(eat_tokens("while"), "")
-
- def test_define_keyword(self):
- self.assertEqual(eat_tokens("#define"), "")
-
- def test_error_keyword(self):
- self.assertEqual(eat_tokens("#error"), "")
-
- def test_ifndef_keyword(self):
- self.assertEqual(eat_tokens("#ifndef"), "")
-
- def test_ifdef_keyword(self):
- self.assertEqual(eat_tokens("#ifdef"), "")
-
- def test_include_keyword(self):
- self.assertEqual(eat_tokens("#include"), "")
-
- def test_pragma_keyword(self):
- self.assertEqual(eat_tokens("#pragma"), "")
-
- def test_undef_keyword(self):
- self.assertEqual(eat_tokens("#undef"), "")
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/norminette/tests/lexer/unit-tests/operators_tokens_test.py b/norminette/tests/lexer/unit-tests/operators_tokens_test.py
deleted file mode 100644
index 6b23c0e8..00000000
--- a/norminette/tests/lexer/unit-tests/operators_tokens_test.py
+++ /dev/null
@@ -1,129 +0,0 @@
-import unittest
-import sys
-from lexer.lexer import Lexer
-
-
-class TokensOperatorsTest(unittest.TestCase):
-
- def test_op_right_assign(self):
- self.assertEqual(Lexer(">>=").get_next_token().type, "RIGHT_ASSIGN")
-
- def test_op_left_assign(self):
- self.assertEqual(Lexer("<<=").get_next_token().type, "LEFT_ASSIGN")
-
- def test_op_add_assign(self):
- self.assertEqual(Lexer("+=").get_next_token().type, "ADD_ASSIGN")
-
- def test_op_sub_assign(self):
- self.assertEqual(Lexer("-=").get_next_token().type, "SUB_ASSIGN")
-
- def test_op_mul_assign(self):
- self.assertEqual(Lexer("*=").get_next_token().type, "MUL_ASSIGN")
-
- def test_op_div_assign(self):
- self.assertEqual(Lexer("/=").get_next_token().type, "DIV_ASSIGN")
-
- def test_op_mod_assign(self):
- self.assertEqual(Lexer("%=").get_next_token().type, "MOD_ASSIGN")
-
- def test_op_and_assign(self):
- self.assertEqual(Lexer("&=").get_next_token().type, "AND_ASSIGN")
-
- def test_op_xor_assign(self):
- self.assertEqual(Lexer("^=").get_next_token().type, "XOR_ASSIGN")
-
- def test_op_or_assign(self):
- self.assertEqual(Lexer("|=").get_next_token().type, "OR_ASSIGN")
-
- def test_op_le_assign(self):
- self.assertEqual(Lexer("<=").get_next_token().type, "LESS_OR_EQUAL")
-
- def test_op_ge_assign(self):
- self.assertEqual(Lexer(">=").get_next_token().type, "GREATER_OR_EQUAL")
-
- def test_op_eq_assign(self):
- self.assertEqual(Lexer("==").get_next_token().type, "EQUALS")
-
- def test_op_ne_assign(self):
- self.assertEqual(Lexer("!=").get_next_token().type, "NOT_EQUAL")
-
- def test_op_assign(self):
- self.assertEqual(Lexer("=").get_next_token().type, "ASSIGN")
-
- def test_op_semi_colon(self):
- self.assertEqual(Lexer(";").get_next_token().type, "SEMI_COLON")
-
- def test_op_colon(self):
- self.assertEqual(Lexer(":").get_next_token().type, "COLON")
-
- def test_op_comma(self):
- self.assertEqual(Lexer(",").get_next_token().type, "COMMA")
-
- def test_op_dot(self):
- self.assertEqual(Lexer(".").get_next_token().type, "DOT")
-
- def test_op_not(self):
- self.assertEqual(Lexer("!").get_next_token().type, "NOT")
-
- def test_op_minus(self):
- self.assertEqual(Lexer("-").get_next_token().type, "MINUS")
-
- def test_op_plus(self):
- self.assertEqual(Lexer("+").get_next_token().type, "PLUS")
-
- def test_op_mult(self):
- self.assertEqual(Lexer("*").get_next_token().type, "MULT")
-
- def test_op_div(self):
- self.assertEqual(Lexer("/").get_next_token().type, "DIV")
-
- def test_op_modulo(self):
- self.assertEqual(Lexer("%").get_next_token().type, "MODULO")
-
- def test_op_less_than(self):
- self.assertEqual(Lexer("<").get_next_token().type, "LESS_THAN")
-
- def test_op_more_than(self):
- self.assertEqual(Lexer(">").get_next_token().type, "MORE_THAN")
-
- def test_op_ellipsis(self):
- self.assertEqual(Lexer("...").get_next_token().type, "ELLIPSIS")
-
- def test_op_inc(self):
- self.assertEqual(Lexer("++").get_next_token().type, "INC")
-
- def test_op_dec(self):
- self.assertEqual(Lexer("--").get_next_token().type, "DEC")
-
- def test_op_ptr(self):
- self.assertEqual(Lexer("->").get_next_token().type, "PTR")
-
- def test_op_and(self):
- self.assertEqual(Lexer("&&").get_next_token().type, "AND")
-
- def test_op_or(self):
- self.assertEqual(Lexer("||").get_next_token().type, "OR")
-
- def test_op_bwise_xor(self):
- self.assertEqual(Lexer("^").get_next_token().type, "BWISE_XOR")
-
- def test_op_bwise_or(self):
- self.assertEqual(Lexer("|").get_next_token().type, "BWISE_OR")
-
- def test_op_bwise_not(self):
- self.assertEqual(Lexer("~").get_next_token().type, "BWISE_NOT")
-
- def test_op_bwise_and(self):
- self.assertEqual(Lexer("&").get_next_token().type, "BWISE_AND")
-
- def test_op_right_shift(self):
- self.assertEqual(Lexer(">>").get_next_token().type, "RIGHT_SHIFT")
-
- def test_op_left_shift(self):
- self.assertEqual(Lexer("<<").get_next_token().type, "LEFT_SHIFT")
-
- def test_op_tern_condition(self):
- self.assertEqual(Lexer("?").get_next_token().type, "TERN_CONDITION")
-
- if __name__ == '__main__':
- unittest.main()
diff --git a/norminette/tests/lexer/unit-tests/string_tokens_test.py b/norminette/tests/lexer/unit-tests/string_tokens_test.py
deleted file mode 100644
index d2bcebd3..00000000
--- a/norminette/tests/lexer/unit-tests/string_tokens_test.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import unittest
-import sys
-from lexer.lexer import Lexer
-
-
-class StringTokenTest(unittest.TestCase):
-
- def test_basic_string(self):
- self.assertEqual(
- Lexer('"Basic string"').get_next_token().test(),
- '')
-
- def test_basic_L_string(self):
- self.assertEqual(
- Lexer('L"Basic string"').get_next_token().test(),
- '')
-
- def test_basic_escaped_string(self):
- self.assertEqual(
- Lexer('"Basic \\"string\\""').get_next_token().test(),
- '')
-
- def test_escaped_string(self):
- self.assertEqual(
- Lexer('"Escaped \\\\\\"string\\\\\\\\\\\"\\\\"').get_next_token()
- .test(),
- '')
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/norminette/tests/loop.zsh b/norminette/tests/loop.zsh
deleted file mode 100755
index 8c32e9ff..00000000
--- a/norminette/tests/loop.zsh
+++ /dev/null
@@ -1,2 +0,0 @@
-for ((i = 0; i < 100; i++)); do
-python . **/*.c; done
diff --git a/norminette/tests/rules/integer_constants.out b/norminette/tests/rules/integer_constants.out
deleted file mode 100644
index c1dfcdc8..00000000
--- a/norminette/tests/rules/integer_constants.out
+++ /dev/null
@@ -1,79 +0,0 @@
-[36minteger_constants.c[0m - [32mIsFuncDeclaration[0m In "GlobalScope" from "None" line 1":
-
-[36minteger_constants.c[0m - [32mIsBlockStart[0m In "Function" from "GlobalScope" line 2":
-
-[36minteger_constants.c[0m - [32mIsVarDeclaration[0m In "Function" from "GlobalScope" line 3":
-
-[36minteger_constants.c[0m - [32mIsVarDeclaration[0m In "Function" from "GlobalScope" line 4":
-
-[36minteger_constants.c[0m - [32mIsVarDeclaration[0m In "Function" from "GlobalScope" line 5":
-
-[36minteger_constants.c[0m - [32mIsVarDeclaration[0m In "Function" from "GlobalScope" line 6":
-
-[36minteger_constants.c[0m - [32mIsVarDeclaration[0m In "Function" from "GlobalScope" line 7":
-