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

update Lexer to use Errors and add some support for C23 stuffs #489

Merged
merged 26 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d396bdf
fix types related to error formatters
NiumXp Feb 10, 2024
7c60d96
add to 'col' parameter in 'NormWarning' and 'NormError' classes to no…
NiumXp Feb 10, 2024
576ab8e
add to 'Error' and 'Highlight' dataclasses to be sortable
NiumXp Feb 10, 2024
b97c67c
add to 'level' and 'highlights' field of 'Error' dataclass be optional
NiumXp Feb 10, 2024
05c6e6c
add 'from_name' constructor for 'Error' dataclass
NiumXp Feb 10, 2024
cbb646d
fix opening bad file paths when source is empty in File class
NiumXp Feb 11, 2024
2a0a92f
add to Token class be a dataclass
NiumXp Feb 11, 2024
e7e2e6d
improve 'Errors' and 'Error' classes with QoL
NiumXp Feb 11, 2024
ab6e079
fix 'Error.text' field being override to 'Error not found' in Humaniz…
NiumXp Feb 11, 2024
9d4de17
fix unexpected errors in Lexer.pop that is expected a raise Unexpecte…
NiumXp Feb 11, 2024
af155ae
add 'use_escape' parameter in 'Lexer.pop' method
NiumXp Feb 11, 2024
68d59a5
refactor Lexer to use errors instead of Exceptions to be more descritive
NiumXp Feb 11, 2024
881d855
add to support digraphs and trigraphs when parsing an operator
NiumXp Feb 12, 2024
2c76468
add 'Lexer.peek' tests
NiumXp Feb 12, 2024
9c85867
add removed preproc tests
NiumXp Feb 12, 2024
e5b025e
add tests for floats with fractional part
NiumXp Feb 13, 2024
19109da
remove asserts in Lexer
NiumXp Feb 13, 2024
952a57f
add complex suffix for float literals
NiumXp Feb 13, 2024
9436f07
add to support u and u8 prefixes for chars and strings
NiumXp Feb 13, 2024
e1ef205
add to parse hexadecimal floats
NiumXp Feb 13, 2024
fac99b4
add to handle multiple '.' and 'x' in float literals
NiumXp Feb 13, 2024
cfc9433
Merge branch 'master' into imp/lexer-errors
NiumXp Feb 19, 2024
6def592
fix test_errors
NiumXp Feb 20, 2024
294a205
fix Lexer.get_next_token type of self.__pos
NiumXp Feb 26, 2024
4e93e3d
fix types in rule.py
NiumXp Feb 26, 2024
795183f
move 'lexer_from_source()' and 'dict_to_pytest_param()' to tests/util…
NiumXp Feb 26, 2024
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
6 changes: 3 additions & 3 deletions norminette/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import argparse
from norminette.errors import formatters
from norminette.file import File
from norminette.lexer import Lexer, TokenError
from norminette.lexer import Lexer
from norminette.exceptions import CParsingError
from norminette.registry import Registry
from norminette.context import Context
Expand Down Expand Up @@ -127,10 +127,10 @@ def main():
for file in files:
try:
lexer = Lexer(file)
tokens = lexer.get_tokens()
tokens = list(lexer)
context = Context(file, tokens, debug, args.R)
registry.run(context)
except (TokenError, CParsingError) as e:
except CParsingError as e:
print(file.path + f": Error!\n\t{colors(e.msg, 'red')}")
sys.exit(1)
except KeyboardInterrupt:
Expand Down
137 changes: 115 additions & 22 deletions norminette/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,146 @@
import os
import json
from dataclasses import dataclass, field, asdict
from functools import cmp_to_key
from typing import TYPE_CHECKING, Sequence, Union, Literal, Optional, List
from typing import (
TYPE_CHECKING,
Sequence,
Union,
Literal,
Optional,
List,
overload,
Any,
Type,
)

from norminette.norm_error import NormError, NormWarning, errors as errors_dict

if TYPE_CHECKING:
from norminette.file import File


def sort_errs(a: Error, b: Error):
# TODO Add to Error and Highlight dataclasses be sortable to remove this fn
ah: Highlight = a.highlights[0]
bh: Highlight = b.highlights[0]
if ah.column == bh.column and ah.lineno == bh.lineno:
return 1 if a.name > b.name else -1
return ah.column - bh.column if ah.lineno == bh.lineno else ah.lineno - bh.lineno


@dataclass
class Highlight:
lineno: int
column: int
length: Optional[int] = field(default=None)
hint: Optional[str] = field(default=None)

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: Literal["Error", "Notice"]
highlights: List[Highlight]
level: Literal["Error", "Notice"] = 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 = []
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(key=cmp_to_key(sort_errs))
self._inner.sort()
return iter(self._inner)

# TODO Add `add(...)` method to allow creating `Highlight`s and `Error`s easily
@overload
def add(self, error: Error) -> None:
"""Add an `Error` instance to the errors.
"""
...

@overload
def add(self, name: str, *, level: Literal["Error", "Notice"] = "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: Literal["Error", "Notice"] = "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"]:
Expand All @@ -60,15 +152,17 @@ def append(self, value: Union[NormError, NormWarning]) -> None:
# TODO Remove NormError and NormWarning since it does not provide `length` data
assert isinstance(value, (NormError, NormWarning))
level = "Error" if isinstance(value, NormError) else "Notice"
value = Error(value.errno, value.error_msg, level, highlights=[
error = Error(value.errno, value.error_msg, level, highlights=[
Highlight(value.line, value.col, None),
])
self._inner.append(value)
self._inner.append(error)


class _formatter:
name: str

def __init__(self, files: Union[File, Sequence[File]]) -> None:
if not isinstance(files, list):
if not isinstance(files, Sequence):
files = [files]
self.files = files

Expand All @@ -82,10 +176,9 @@ def __str__(self) -> str:
for file in self.files:
output += f"{file.basename}: {file.errors.status}!"
for error in file.errors:
brief = errors_dict.get(error.name, "Error not found")
highlight = error.highlights[0]
output += f"\n{error.level}: {error.name:<20} "
output += f"(line: {highlight.lineno:>3}, col: {highlight.column:>3}):\t{brief}"
output += f"(line: {highlight.lineno:>3}, col: {highlight.column:>3}):\t{error.text}"
output += '\n'
return output

Expand All @@ -102,7 +195,7 @@ def __str__(self):
output = {
"files": files,
}
return json.dumps(output, separators=",:") + '\n'
return json.dumps(output, separators=(',', ':')) + '\n'


formatters = (
Expand Down
2 changes: 1 addition & 1 deletion norminette/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def __init__(self, path: str, source: Optional[str] = None) -> None:

@property
def source(self) -> str:
if not self._source:
if self._source is None:
with open(self.path) as file:
self._source = file.read()
return self._source
Expand Down
3 changes: 1 addition & 2 deletions norminette/lexer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from norminette.lexer.lexer import Lexer
from norminette.lexer.lexer import TokenError
from norminette.lexer.tokens import Token

__all__ = ["Lexer", "TokenError", "Token"]
__all__ = ["Lexer", "Token"]
1 change: 1 addition & 0 deletions norminette/lexer/dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
">>": "RIGHT_SHIFT",
"<<": "LEFT_SHIFT",
"?": "TERN_CONDITION",
"#": "HASH",
}

brackets = {
Expand Down
Loading
Loading