Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add "File" and "Errors" classes #479

Merged
merged 3 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 40 additions & 86 deletions norminette/__main__.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,24 @@
import glob
import os
import sys
import pathlib
from importlib.metadata import version

import argparse
from norminette.file import File
from norminette.lexer import Lexer, TokenError
from norminette.exceptions import CParsingError
from norminette.registry import Registry
from norminette.context import Context
from norminette.tools.colors import colors

from pathlib import Path

import _thread
from threading import Event
import time
import subprocess


has_err = False


def timeout(e, timeval=5):
time.sleep(timeval)
if e.is_set():
return
_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(
Expand Down Expand Up @@ -80,36 +64,36 @@ def main():
parser.add_argument("-R", nargs=1, help="compatibility for norminette 2")
args = parser.parse_args()
registry = Registry()
targets = []
has_err = None
content = None

files = []
debug = args.debug
if args.cfile is not None or args.hfile is not None:
if args.filename:
targets = [args.filename]
else:
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)
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)
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)
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 targets:
command = ["git", "check-ignore", "-q", target]
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
Expand All @@ -123,51 +107,21 @@ def main():
elif exit_code == 1:
tmp_targets.append(target)
elif exit_code == 128:
print(f'Error: something wrong with --use-gitignore option {target}')
print(f'Error: something wrong with --use-gitignore option {target.path!r}')
sys.exit(0)
targets = tmp_targets
event = []
for target in filter(os.path.isfile, targets):
if target[-2:] not in [".c", ".h"]:
print(f"Error: {target} is not valid C or C header file")
else:
try:
event.append(Event())
if content is None:
with open(target) as f:
try:
source = f.read()
except Exception as e:
print("Error: File could not be read: ", e)
sys.exit(0)
else:
source = content
try:
lexer = Lexer(source)
tokens = lexer.get_tokens()
except KeyError as e:
print("Error while parsing file:", e)
sys.exit(0)
if args.only_filename is True:
# target = target.split("/")[-1]
target = Path(target).name
context = Context(target, tokens, debug, args.R)
registry.run(context, source)
event[-1].set()
if context.errors:
has_err = True
except TokenError as e:
has_err = True
print(target + f": Error!\n\t{colors(e.msg, 'red')}")
event[-1].set()
except CParsingError as e:
has_err = True
print(target + f": Error!\n\t{colors(e.msg, 'red')}")
event[-1].set()
except KeyboardInterrupt:
event[-1].set()
sys.exit(1)
sys.exit(1 if has_err else 0)
files = tmp_targets
for file in files:
try:
lexer = Lexer(file)
tokens = lexer.get_tokens()
context = Context(file, tokens, debug, args.R)
registry.run(context)
except (TokenError, CParsingError) as e:
print(file.path + f": Error!\n\t{colors(e.msg, 'red')}")
sys.exit(1)
except KeyboardInterrupt:
sys.exit(1)
sys.exit(1 if len(file.errors) else 0)


if __name__ == "__main__":
Expand Down
12 changes: 5 additions & 7 deletions norminette/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,21 +182,19 @@ def has_macro_defined(self, name):


class Context:
def __init__(self, filename, tokens, debug=0, added_value=[]):
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
self.filetype = filename.split(".")[-1] # ?
self.file = file
self.tokens = tokens
self.debug = int(debug)

# Rule relative informations
self.history = []
self.errors = []
self.warnings = []
self.errors = file.errors
self.tkn_scope = len(tokens)

# Scope informations
Expand Down Expand Up @@ -250,7 +248,7 @@ def new_error(self, errno, tkn):
self.errors.append(NormError(errno, pos[0], pos[1]))

def new_warning(self, errno, tkn):
self.warnings.append(NormWarning(errno, tkn.pos[0], tkn.pos[1]))
self.errors.append(NormWarning(errno, tkn.pos[0], tkn.pos[1]))

def get_parent_rule(self):
if len(self.history) == 0:
Expand Down Expand Up @@ -288,7 +286,7 @@ def dprint(self, rule, pos):
if self.debug < 2:
return
print(
f"{colors(self.filename, 'cyan')} - {colors(rule, 'green')} \
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]}\":"
)
Expand Down
57 changes: 57 additions & 0 deletions norminette/file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import os
from functools import cmp_to_key
from typing import Optional, Union, Literal

from norminette.norm_error import NormError, NormWarning


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 Errors:
__slots__ = "_inner"

def __init__(self) -> None:
self._inner = []

def __len__(self) -> int:
return len(self._inner)

def __iter__(self):
self._inner.sort(key=cmp_to_key(sort_errs))
return iter(self._inner)

@property
def status(self) -> Literal["OK", "Error"]:
if not self:
return "OK"
if all(isinstance(it, NormWarning) for it in self):
return "OK"
return "Error"

def append(self, value: Union[NormError, NormWarning]) -> None:
assert isinstance(value, (NormError, NormWarning))
self._inner.append(value)


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 not self._source:
with open(self.path) as file:
self._source = file.read()
return self._source

def __repr__(self) -> str:
return f"<File {self.path!r}>"
15 changes: 8 additions & 7 deletions norminette/lexer/lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from norminette.lexer.dictionary import keywords
from norminette.lexer.dictionary import operators
from norminette.lexer.tokens import Token
from norminette.file import File


def read_file(filename):
Expand All @@ -20,13 +21,14 @@ def __repr__(self):


class Lexer:
def __init__(self, source_code, starting_line=1):
self.src = source_code
self.len = len(source_code)
def __init__(self, file: File):
self.file = file

self.src = file.source
self.len = len(file.source)
self.__char = self.src[0] if self.src != "" else None
self.__pos = int(0)
self.__line_pos = int(starting_line)
self.__line = int(starting_line)
self.__line_pos = self.__line = 1
self.tokens = []

def peek_sub_string(self, size):
Expand Down Expand Up @@ -155,8 +157,7 @@ def char_constant(self):
tkn_value += self.peek_char()
if self.peek_char() == "\n":
self.pop_char()
self.tokens.append(Token("TKN_ERROR", pos))
return
raise TokenError(pos)
if self.peek_char() == "'":
self.pop_char()
self.tokens.append(Token("CHAR_CONST", pos, tkn_value))
Expand Down
26 changes: 4 additions & 22 deletions norminette/registry.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import collections
from functools import cmp_to_key
from operator import attrgetter

from norminette.rules import Rules, Primary
Expand All @@ -8,12 +7,6 @@
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

Expand All @@ -40,7 +33,7 @@ def run_rules(self, 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
Expand Down Expand Up @@ -85,17 +78,6 @@ def run(self, context, source):
print(context.debug)
if context.debug > 0:
print("uncaught ->", unrecognized_tkns)
if context.errors == []:
print(context.filename + ": OK!")
for warning in sorted(context.warnings, key=cmp_to_key(sort_errs)):
print(warning)
else:
print(context.filename + ": Error!")
context.errors = sorted(
context.errors + context.warnings, key=cmp_to_key(sort_errs)
)
for err in context.errors:
print(err)
# context.warnings = sorted(context.warnings, key=cmp_to_key(sort_errs))
# for warn in context.warnings:
# print (warn)
print(f"{context.file.basename}: {context.file.errors.status}!")
for error in context.file.errors:
print(error)
2 changes: 1 addition & 1 deletion norminette/rules/check_in_header.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def run(self, context):
- Comments
- Function prototypes
"""
if context.filetype != "h":
if context.file.type != ".h":
return False, 0
sc = context.scope
while sc.name != "GlobalScope":
Expand Down
5 changes: 2 additions & 3 deletions norminette/rules/check_preprocessor_protection.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import itertools
from pathlib import Path

from norminette.rules import Rule, Check

Expand All @@ -19,7 +18,7 @@ def run(self, context):
```
Any header instruction must be within the header protection
"""
if context.filetype != "h":
if context.file.type != ".h":
return False, 0
i = context.skip_ws(0)
hash = context.peek_token(i)
Expand All @@ -32,7 +31,7 @@ def run(self, context):
if not t or t.type != "IDENTIFIER" or t.value.upper() not in ("IFNDEF", "ENDIF"):
return False, 0
i += 1
guard = Path(context.filename).name.upper().replace(".", "_")
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)
Expand Down
2 changes: 1 addition & 1 deletion norminette/rules/check_utype_declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def run(self, context):
if context.scope.name not in ("GlobalScope", "UserDefinedType"):
context.new_error("TYPE_NOT_GLOBAL", token)
if (
context.filetype == "c"
context.file.type == ".c"
and token.type in ("STRUCT", "UNION", "ENUM", "TYPEDEF")
and context.scope not in ("UserDefinedType", "UserDefinedEnum")
):
Expand Down
Loading
Loading