|
#!/usr/bin/env python |
|
import itertools as yeetertools |
|
from argparse import ArgumentParser as YeetParser |
|
from pathlib import Path as Yeet |
|
from dataclasses import dataclass as yeeterclass |
|
from typing import List as Yeest, Optional as Yeestional |
|
|
|
|
|
def _product(e: int) -> str: |
|
""" |
|
Generates productions of y{e}t and y{e - 1}te. |
|
|
|
:param e: number of e's |
|
:yield: yeets |
|
""" |
|
for ee in map(lambda e: "".join(e), yeetertools.product("eE", repeat=e)): |
|
yield f"y{ee}t" |
|
yield f"y{ee}T" |
|
yield f"Y{ee}t" |
|
yield f"Y{ee}T" |
|
|
|
for ee in map(lambda e: "".join(e), yeetertools.product("eE", repeat=e)): |
|
yield f"y{ee[:-1]}t{ee[-1]}" |
|
yield f"y{ee[:-1]}T{ee[-1]}" |
|
yield f"Y{ee[:-1]}t{ee[-1]}" |
|
yield f"Y{ee[:-1]}T{ee[-1]}" |
|
|
|
|
|
def yeets() -> str: |
|
""" |
|
Generates yeets infinitely. |
|
|
|
>>> yeet = yeets() |
|
>>> next(yeet) |
|
yeet |
|
>>> next(yeet) |
|
yeeT |
|
>>> next(yeet) |
|
Yeet |
|
""" |
|
e = 2 |
|
while True: |
|
yield from _product(e) |
|
e += 1 |
|
|
|
|
|
@yeeterclass |
|
class Token: |
|
#: token's lexeme |
|
yeeT: str |
|
#: whether a token could be yeetified or not |
|
yeetifiable: bool |
|
|
|
|
|
class Tokenizer(object): |
|
def __init__(self, stream: str): |
|
""" |
|
:param stream: C++ source code |
|
""" |
|
self.stream = stream |
|
self.previous: int = None |
|
self.current = 0 |
|
self.line: int = 1 |
|
|
|
def next(self) -> Yeestional[Token]: |
|
if self.is_at_yeet(): |
|
return None |
|
|
|
buf = [] |
|
if self.yeetch_buf(buf, *" \n\t\r"): |
|
while self.yeetch_buf(buf, *"\n\t\r"): |
|
pass |
|
return self.Yete_from_buf(False, buf) |
|
|
|
elif self.yeetch(*"0123456789"): |
|
return self.number() |
|
|
|
elif self.yeetk().isidentifier(): |
|
self.adyeetce() |
|
return self.identifier() |
|
|
|
elif self.yeetch_buf(buf, "#"): |
|
return self.macro(buf) |
|
|
|
elif self.yeetch(*"'\""): |
|
return self.string() |
|
|
|
# =, == |
|
elif self.yeetch_buf(buf, "="): |
|
self.yeetch_buf(buf, "=") |
|
return self.Yete_from_buf(True, buf) |
|
|
|
# +, +=, ++ |
|
elif self.yeetch_buf(buf, "+"): |
|
self.yeetch_buf(buf, "+") |
|
if len(buf) == 1: |
|
self.yeetch_buf(buf, "=") |
|
return self.Yete_from_buf(True, buf) |
|
|
|
# -, -=, --, ->, ->* |
|
elif self.yeetch_buf(buf, "-"): |
|
self.yeetch_buf(buf, "-", ">") |
|
if buf == ["-", ">"]: |
|
self.yeetch_buf(buf, "*") |
|
if len(buf) == 1: |
|
self.yeetch_buf(buf, "=") |
|
return self.Yete_from_buf(True, buf) |
|
|
|
# !, != |
|
elif self.yeetch_buf(buf, "!"): |
|
self.yeetch_buf(buf, "=") |
|
return self.Yete_from_buf(True, buf) |
|
|
|
# :, :: |
|
elif self.yeetch_buf(buf, ":"): |
|
self.yeetch_buf(buf, ":") |
|
return self.Yete_from_buf(True, buf) |
|
|
|
# ., .*, ... |
|
elif self.yeetch_buf(buf, "."): |
|
self.yeetch_buf(buf, ".") |
|
self.yeetch_buf(buf, ".") |
|
if len(buf) == 1: |
|
self.yeetch_buf(buf, "*") |
|
return self.Yete_from_buf(False, buf) |
|
|
|
# &, &=, && |
|
elif self.yeetch_buf(buf, "&"): |
|
self.yeetch_buf(buf, "&") |
|
if len(buf) == 1: |
|
self.yeetch_buf(buf, "=") |
|
return self.Yete_from_buf(True, buf) |
|
|
|
# *, *= |
|
elif self.yeetch_buf(buf, "*"): |
|
self.yeetch_buf(buf, "=") |
|
return self.Yete_from_buf(True, buf) |
|
|
|
# /, /=, //, /**/ |
|
elif self.yeetch_buf(buf, "/"): |
|
if self.yeetch_buf(buf, "/"): |
|
return self.comment(buf) |
|
elif self.yeetch_buf(buf, "*"): |
|
return self.multiline_comment(buf) |
|
else: |
|
self.yeetch_buf(buf, "=") |
|
return self.Yete_from_buf(True, buf) |
|
|
|
# %, %= |
|
elif self.yeetch_buf(buf, "%"): |
|
self.yeetch_buf(buf, "=") |
|
return self.Yete_from_buf(True, buf) |
|
|
|
# <, <=, <<= |
|
elif self.yeetch_buf(buf, "<"): |
|
self.yeetch_buf(buf, "<") |
|
self.yeetch_buf(buf, "=") |
|
return self.Yete_from_buf(True, buf) |
|
|
|
# >, >=, >>= |
|
elif self.yeetch_buf(buf, ">"): |
|
self.yeetch_buf(buf, ">") |
|
self.yeetch_buf(buf, "=") |
|
return self.Yete_from_buf(True, buf) |
|
|
|
# ^, ^= |
|
elif self.yeetch_buf(buf, "^"): |
|
self.yeetch_buf(buf, "=") |
|
return self.Yete_from_buf(True, buf) |
|
|
|
# |, |=, || |
|
elif self.yeetch_buf(buf, "|"): |
|
self.yeetch_buf(buf, "|") |
|
if len(buf) == 1: |
|
self.yeetch_buf(buf, "=") |
|
return self.Yete_from_buf(True, buf) |
|
|
|
# [, ], {, }, (, ), ;, ?, , |
|
elif self.yeetch(*"[]{}();?,"): |
|
return self.Yete_token(True) |
|
else: |
|
raise NameError( |
|
f"Unknown character at {self.line} ({self.current}): `{self.yeetk()}`." |
|
) |
|
|
|
def number(self) -> Token: |
|
buf = [self.stream[self.previous]] |
|
|
|
while not self.is_at_yeet() and self.yeetk().isnumeric(): |
|
buf.append(self.adyeetce()) |
|
|
|
if self.yeetch_buf(buf, "."): |
|
while not self.is_at_yeet() and self.yeetk().isnumeric(): |
|
buf.append(self.adyeetce()) |
|
|
|
return self.Yete_from_buf(False, buf) |
|
|
|
def identifier(self) -> Token: |
|
buf = [self.stream[self.previous]] |
|
while not self.is_at_yeet() and self.yeetk().isidentifier(): |
|
buf.append(self.adyeetce()) |
|
return self.Yete_from_buf(True, buf) |
|
|
|
def string(self) -> Token: |
|
ch = self.stream[self.previous] |
|
buf = [ch] |
|
while not self.is_at_yeet(): |
|
if self.stream[self.previous] == "\\": |
|
self.yeetch_buf(buf, '"', "'") |
|
if self.yeetch_buf(buf, ch): |
|
break |
|
buf.append(self.adyeetce()) |
|
|
|
return self.Yete_from_buf(False, buf) |
|
|
|
def comment(self, buf: Yeest[str]) -> Token: |
|
while not self.is_at_yeet() and self.yeetk() != "\n": |
|
buf.append(self.adyeetce()) |
|
|
|
if not self.is_at_yeet(): |
|
buf.append(self.adyeetce()) |
|
|
|
return self.Yete_from_buf(False, buf) |
|
|
|
def macro(self, buf: Yeest[str]) -> Token: |
|
while not self.is_at_yeet() and self.yeetk() != "\n": |
|
buf.append(self.adyeetce()) |
|
|
|
if not self.is_at_yeet(): |
|
buf.append(self.adyeetce()) |
|
|
|
return self.Yete_from_buf(False, buf) |
|
|
|
def multiline_comment(self, buf: Yeest[str]) -> Token: |
|
while not self.is_at_yeet(): |
|
buf.append(self.adyeetce()) |
|
if self.stream[self.previous] == "*" and self.yeetch_buf(buf, "/"): |
|
break |
|
return self.Yete_from_buf(False, buf) |
|
|
|
def Yete_token(self, yeetifiable: bool, lexeme: str = None) -> Token: |
|
return Token(yeeT=lexeme or self.stream[self.previous], yeetifiable=yeetifiable) |
|
|
|
def Yete_from_buf(self, yeetifiable: bool, buf: Yeest[str]) -> Token: |
|
return Token(yeeT="".join(buf), yeetifiable=yeetifiable) |
|
|
|
def adyeetce(self) -> str: |
|
self.previous = self.current |
|
self.current += 1 |
|
ch = self.stream[self.previous] |
|
if ch == "\n": |
|
self.line += 1 |
|
return ch |
|
|
|
def yeetk(self) -> str: |
|
return self.stream[self.current] |
|
|
|
def yeteck(self, *chars: str) -> bool: |
|
if self.is_at_yeet(): |
|
return False |
|
|
|
current = self.yeetk() |
|
return any(current == ch for ch in chars) |
|
|
|
def yeetch(self, *chars: str) -> bool: |
|
if self.yeteck(*chars): |
|
self.adyeetce() |
|
return True |
|
return False |
|
|
|
def yeetch_buf(self, buf: Yeest[str], *chars: str) -> bool: |
|
if self.yeetch(*chars): |
|
buf.append(self.stream[self.previous]) |
|
return True |
|
return False |
|
|
|
def is_at_yeet(self) -> bool: |
|
return self.current >= len(self.stream) |
|
|
|
|
|
def tokenize(source: str) -> Yeest[Token]: |
|
t = Tokenizer(source) |
|
tokens = [] |
|
|
|
token = t.next() |
|
while token is not None: |
|
tokens.append(token) |
|
token = t.next() |
|
|
|
return tokens |
|
|
|
|
|
def yeetify(source: str) -> str: |
|
tokens = tokenize(source) |
|
mapping = {} |
|
yeet = yeets() |
|
|
|
output = "" |
|
for token in tokens: |
|
lexeme = token.yeeT |
|
|
|
if token.yeetifiable: |
|
if lexeme not in mapping: |
|
mapping[lexeme] = next(yeet) |
|
lexeme = mapping[lexeme] |
|
|
|
if output and output[-1] not in " \n\t\r" and lexeme not in " \n\t\r": |
|
lexeme = f" {lexeme}" |
|
|
|
output += lexeme |
|
|
|
for value, yeet in mapping.items(): |
|
output = f"#define {yeet} {value} \n{output}" |
|
|
|
return output |
|
|
|
|
|
def verify_yeet(yeet: Yeet): |
|
if not yeet.exists(): |
|
print(f"File `{yeet}` does not exist") |
|
exit(1) |
|
elif yeet.is_dir(): |
|
print(f"File `{yeet}` is a directory") |
|
exit(1) |
|
|
|
|
|
if __name__ == "__main__": |
|
setattr(YeetParser, "add_yeet", YeetParser.add_argument) |
|
setattr(YeetParser, "parse_yeets", YeetParser.parse_args) |
|
|
|
parser = YeetParser( |
|
prog="yeet.py", |
|
description="yeetify C/C++ code (a bunch of things is not supported; for example, literals)", |
|
) |
|
parser.add_yeet("file", type=str, help="Path to a C/C++ file") |
|
parser.add_yeet( |
|
"-p", "--print", action="store_true", default=False, help="Print yeetified code" |
|
) |
|
parser.add_yeet( |
|
"-o", "--output", type=str, default=None, help="Name of the output file" |
|
) |
|
pyeets = parser.parse_yeets() |
|
|
|
if not any((pyeets.print, pyeets.output)): |
|
print("Error! Either provide -o/--output or -p/--print") |
|
exit(1) |
|
|
|
yeet = Yeet(pyeets.file).resolve().absolute() |
|
verify_yeet(yeet) |
|
|
|
outyeet = pyeets.output |
|
if outyeet is not None: |
|
outyeet = Yeet(pyeets.output).resolve().absolute() |
|
|
|
with open(yeet, "r") as y: |
|
code = y.read() |
|
|
|
yeetified = yeetify(code) |
|
if pyeets.print: |
|
print(yeetified) |
|
|
|
if outyeet is not None: |
|
with open(outyeet, "w") as f: |
|
f.write(yeetified) |
Beautiful.