alkkofficial

Simple lexer in Python

Jun 28th, 2026 (edited)
85
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.48 KB | None | 0 0
  1. from abc import ABC, abstractmethod
  2. from enum import Enum
  3. from types import NoneType
  4. from typing import Optional
  5. import string
  6.  
  7. with open("hidden/src.txt", "r") as f:
  8.     src = f.read()
  9.  
  10. lexeme_list = []
  11.  
  12.  
  13. class ReservedWordToken(Enum):
  14.     TokenReturn = "return"
  15.     TokenIf = "if"
  16.     TokenElse = "else"
  17.     TokenFor = "for"
  18.     TokenWhile = "while"
  19.     TokenInt = "int"
  20.     TokenReal = "real"
  21.     TokenString = "string"
  22.     TokenUnion = "union"
  23.     TokenDef = "def"
  24.     TokenBool = "bool"
  25.     TokenArray = "array"
  26.     TokenEnum = "enum"
  27.     TokenContinue = "continue"
  28.     TokenBreak = "break"
  29.     TokenMatch = "match"
  30.     TokenDefault = "default"
  31.  
  32.  
  33. class SingleCharToken(Enum):
  34.     TokenAdd = "+"
  35.     TokenSub = "-"
  36.     TokenMul = "*"
  37.     TokenDiv = "/"
  38.     TokenMod = "%"
  39.     TokenAssignment = "="
  40.     TokenGE = ">"
  41.     TokenLE = "<"
  42.     TokenAnd = "&"
  43.     TokenOr = "|"
  44.     # not operators
  45.     TokenLeftBracket = "("
  46.     TokenRightBracket = ")"
  47.     TokenComma = ","
  48.  
  49.  
  50. class NewInstructionToken:
  51.     NEW_INSTRUCTION_CHAR = ";"
  52.  
  53.     def __init__(self) -> None:
  54.         self.name = type(self).__name__
  55.         self.value = self.NEW_INSTRUCTION_CHAR
  56.         pass
  57.  
  58.  
  59. single_chr_toks = [x.value for x in list(SingleCharToken.__members__.values())]
  60. keyword_toks = set(x.value for x in list(ReservedWordToken.__members__.values()))
  61.  
  62.  
  63. class DataTypeToken(ABC):
  64.     def __init__(self) -> None:
  65.         pass
  66.  
  67.     @abstractmethod
  68.     def check_termination(self, chr) -> bool: ...
  69.  
  70.     def __repr__(self) -> str:
  71.         return type(self).__name__
  72.  
  73.  
  74. class NumericToken(DataTypeToken):
  75.     def check_termination(self, chr):
  76.         # legal chars are numbers, dots, single ops and spaces
  77.         # terminate if NOT (number or dot) and legal char
  78.         # print("a" + chr + "b")
  79.         numeric_condition = chr in (string.digits + ".")
  80.         space = chr.isspace()
  81.         single_op_chrs = chr in "".join(single_chr_toks)
  82.         legal = numeric_condition or space or single_op_chrs
  83.         if not legal:
  84.             raise ValueError(f"Illegal character: {chr}")
  85.         return not numeric_condition and ((single_op_chrs) or (chr.isspace()))
  86.  
  87.  
  88. class TypeIntToken(NumericToken):
  89.     pass
  90.  
  91.  
  92. class TypeRealToken(NumericToken):
  93.     pass
  94.  
  95.  
  96. class TypeStringToken(DataTypeToken):
  97.     def __init__(self, opener=None) -> None:
  98.         self.opener = opener
  99.  
  100.     def check_termination(self, chr) -> bool:
  101.         return chr == self.opener if hasattr(self, "opener") else False
  102.  
  103.  
  104. class UserIdentifierToken(DataTypeToken):
  105.     def check_termination(self, chr) -> bool:
  106.         # TODO make things like hello(arg1, arg2) work, parse args too
  107.         return chr not in (string.ascii_letters + "_" + string.digits)
  108.  
  109.  
  110. class DataToken(Enum):
  111.     TokenInt = TypeIntToken()
  112.     TokenReal = TypeRealToken()
  113.     TokenString = TypeStringToken()
  114.     TokenUserDef = UserIdentifierToken()
  115.  
  116.  
  117. class Lexeme:
  118.     def __init__(
  119.         self,
  120.         token_type: SingleCharToken
  121.         | DataToken
  122.         | ReservedWordToken
  123.         | NewInstructionToken,
  124.         start: int,
  125.         end: Optional[int] = None,
  126.     ) -> None:
  127.         self.token_type = token_type
  128.         self.start = start
  129.         self.end = end
  130.  
  131.     def __repr__(self) -> str:
  132.         return f"Lexeme({src[self.start : self.end] if not (isinstance(self.start, NoneType) and isinstance(self.end, NoneType)) else ''}, {self.start=}, {self.end=}, self.token_type={self.token_type.name})"
  133.  
  134.  
  135. def lex(src):
  136.     current_lexeme: Optional[Lexeme] = None
  137.     for i, s in enumerate(src):
  138.         # check for newline
  139.         if s == NewInstructionToken.NEW_INSTRUCTION_CHAR:
  140.             # check if there is an existing lexeme
  141.             if current_lexeme:
  142.                 current_lexeme.end = i
  143.             lexeme_list.append(Lexeme(NewInstructionToken(), i, i + 1))
  144.             current_lexeme = None
  145.             continue
  146.  
  147.         # check for reserved keywords
  148.         target_set = set()
  149.         if current_lexeme and current_lexeme.token_type == DataToken.TokenUserDef:
  150.             target_set.add(src[current_lexeme.start : i + 1])
  151.  
  152.         if target_set.intersection(keyword_toks):
  153.             current_lexeme.token_type = ReservedWordToken(
  154.                 src[current_lexeme.start : i + 1]
  155.             )
  156.             current_lexeme.end = i + 1
  157.             current_lexeme = None
  158.             continue
  159.  
  160.         # check for termination
  161.         if (
  162.             current_lexeme
  163.             and isinstance(current_lexeme.token_type, DataToken)
  164.             and current_lexeme.token_type.value.check_termination(s)
  165.         ):
  166.             if current_lexeme.token_type == DataToken.TokenString:
  167.                 current_lexeme.end = i + 1
  168.                 current_lexeme = None
  169.                 continue
  170.             else:
  171.                 current_lexeme.end = i
  172.             current_lexeme = None
  173.  
  174.         # check for single chr lexemes
  175.         if s in single_chr_toks and not current_lexeme:
  176.             lexeme_list.append(Lexeme(SingleCharToken(s), i, i + 1))
  177.             current_lexeme = None
  178.             continue
  179.  
  180.         # discriminate against raw strings, first token is NOT a quotation mark
  181.         if not current_lexeme and s in (string.ascii_letters + "_"):
  182.             current_lexeme = Lexeme(DataToken.TokenUserDef, i)
  183.             lexeme_list.append(current_lexeme)
  184.  
  185.         # check strings
  186.         # NOTE: escape chars not supported
  187.         if (s == '"' or s == "'") and (not current_lexeme):
  188.             current_lexeme = Lexeme(DataToken.TokenString, i)
  189.             current_lexeme.token_type.value.opener = s
  190.             lexeme_list.append(current_lexeme)
  191.             continue
  192.  
  193.         # check if digits are reals (prerequisite is to satisfy int + ".")
  194.         if s == ".":
  195.             if current_lexeme and current_lexeme.token_type == DataToken.TokenInt:
  196.                 current_lexeme.token_type = DataToken.TokenReal
  197.                 continue
  198.             raise TypeError("Illegal character '.'")
  199.  
  200.         # check if chr can be an int
  201.         if s.isdigit() and not current_lexeme:
  202.             current_lexeme = Lexeme(DataToken.TokenInt, i)
  203.             lexeme_list.append(current_lexeme)
  204.             continue
  205.  
  206.     # check for unterminated strings
  207.  
  208.     if current_lexeme and current_lexeme.token_type == DataToken.TokenString:
  209.         raise ValueError("Unterminated string")
  210.  
  211.     # terminate last lexeme if not ended alr
  212.  
  213.     elif current_lexeme:
  214.         current_lexeme.end = len(src)
  215.     return lexeme_list
  216.  
Advertisement
Add Comment
Please, Sign In to add comment