├── .gitignore ├── examples ├── add.pashm ├── float.pashm ├── logical-operators.pashm ├── variable.pashm ├── while.pashm ├── input.pashm ├── compare-operators.pashm ├── do-while.pashm ├── math-operators.pashm ├── factorial.pashm └── if-statement.pashm ├── main.py ├── compiler ├── helpers.py ├── fa │ ├── atom.py │ ├── finite_automata.py │ ├── dfa_runner.py │ ├── nfa_runner.py │ ├── multi_dfa_runner.py │ └── re2nfa.py ├── project2 │ ├── main.py │ ├── grammar.txt │ ├── optimized-grammar.txt │ ├── executer.py │ ├── editor.py │ ├── code_generator.py │ ├── parser.py │ └── scanner.py ├── grammar │ ├── statements.py │ ├── expression.py │ ├── statement.py │ ├── grammar.py │ └── ll1.py └── char_set.py ├── readme.md └── gui.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | __pycache__ 3 | .idea/ -------------------------------------------------------------------------------- /examples/add.pashm: -------------------------------------------------------------------------------- 1 | a := 1; 2 | b := 2; 3 | c := a + b; 4 | print(c); -------------------------------------------------------------------------------- /examples/float.pashm: -------------------------------------------------------------------------------- 1 | f := 0.34234; 2 | i := 534; 3 | 4 | print(f); 5 | print(i); -------------------------------------------------------------------------------- /examples/logical-operators.pashm: -------------------------------------------------------------------------------- 1 | num := 3; 2 | 3 | if ( 2 > 1 && !(num % 2 == 0)){ 4 | print(10); 5 | } -------------------------------------------------------------------------------- /examples/variable.pashm: -------------------------------------------------------------------------------- 1 | a := 10; 2 | 3 | b := (20 + 50) * 30; 4 | 5 | c := 1 > 5 + 2; 6 | 7 | print(c); -------------------------------------------------------------------------------- /examples/while.pashm: -------------------------------------------------------------------------------- 1 | num := 10; 2 | 3 | while(num > 0){ 4 | print(num); 5 | num := num - 1; 6 | } -------------------------------------------------------------------------------- /examples/input.pashm: -------------------------------------------------------------------------------- 1 | number := input(); 2 | number := int(number); 3 | 4 | m := number ^ 2; 5 | 6 | print(m); -------------------------------------------------------------------------------- /examples/compare-operators.pashm: -------------------------------------------------------------------------------- 1 | a := 40; 2 | 3 | b := a > 50; 4 | 5 | c := a == 0; 6 | 7 | d := 6 >= 5; 8 | 9 | print(c); -------------------------------------------------------------------------------- /examples/do-while.pashm: -------------------------------------------------------------------------------- 1 | number := 10; 2 | 3 | do{ 4 | print(number); 5 | 6 | number := number - 1; 7 | }while(number > 0) -------------------------------------------------------------------------------- /examples/math-operators.pashm: -------------------------------------------------------------------------------- 1 | add := 60 + 20; 2 | 3 | subtract := 60 - 20; 4 | 5 | m := (20 * 59) - 3/9 % 20; 6 | 7 | print(m); -------------------------------------------------------------------------------- /examples/factorial.pashm: -------------------------------------------------------------------------------- 1 | number := 100; 2 | result := 1; 3 | 4 | while(number > 0){ 5 | result := result * number; 6 | number := number - 1; 7 | } 8 | 9 | print(result); -------------------------------------------------------------------------------- /examples/if-statement.pashm: -------------------------------------------------------------------------------- 1 | number := 12; 2 | 3 | if(number % 2 == 0){ 4 | print(2); 5 | } 6 | 7 | if(number % 2 != 0){ 8 | print(1); 9 | } 10 | 11 | 12 | if ( number % 2 ==0 ){ 13 | if(number % 3 == 0 ){ 14 | print(6); 15 | } 16 | } -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from compiler.project2.main import run, code 2 | from sys import argv 3 | 4 | try: 5 | if argv[1] == 'run': 6 | run(argv[2]) 7 | elif argv[1] == 'code': 8 | print(code(argv[2]), end='') 9 | except Exception as e: 10 | print('Pashmam: ' + str(e)) -------------------------------------------------------------------------------- /compiler/helpers.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from compiler.grammar.expression import Expression 3 | 4 | 5 | def unique_expressions(expressions: List[Expression]): 6 | result = [] 7 | 8 | for expr in expressions: 9 | exists = any(e == expr for e in result) 10 | 11 | if not exists: 12 | result.append(expr) 13 | 14 | return result 15 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # PashmScript 2 | PashmScript is a tiny programming language. 3 | 4 | here, we don't have Error, we just have Pashmam. 5 | 6 | this language use pashm-oriented paradigm. 7 | 8 | # How to run? 9 | first clone this repository 10 | 11 | `git clone https://github.com/amirkabiri/pashm-script.git` 12 | 13 | go to the project directory: 14 | 15 | `cd pashm-script` 16 | 17 | run main.py: 18 | 19 | `py main.py run path/to/file.pashm` 20 | 21 | for running an example: 22 | 23 | `py main.py run examples/add.pashm` 24 | 25 | for showing three-address code: 26 | 27 | `py main.py code examples/add.pashm` 28 | 29 | # Examples 30 | there are several examples of this language in examples folder. 31 | 32 | # License 33 | Pashm Licence 34 | -------------------------------------------------------------------------------- /compiler/fa/atom.py: -------------------------------------------------------------------------------- 1 | from compiler.char_set import CharSet 2 | from typing import Union 3 | 4 | 5 | class Atom: 6 | def __init__(self, value: Union[str, CharSet]): 7 | self.value = value 8 | 9 | def __repr__(self): 10 | return self.value 11 | 12 | 13 | class State(Atom): 14 | def __init__(self, value: Union[str, CharSet]): 15 | super().__init__(value) 16 | 17 | def __eq__(self, other): 18 | return isinstance(other, State) and self.value == other.value 19 | 20 | 21 | class Symbol(Atom): 22 | def __init__(self, value: Union[str, int, CharSet]): 23 | if not isinstance(value, CharSet): 24 | value = CharSet().char(value) 25 | 26 | super().__init__(value) 27 | 28 | def check(self, value: Union[str, int]) -> bool: 29 | return self.value.check(value) 30 | 31 | def __repr__(self): 32 | if not hasattr(self, '__str'): 33 | self.__str = str(self.value) 34 | 35 | 36 | return self.__str -------------------------------------------------------------------------------- /compiler/project2/main.py: -------------------------------------------------------------------------------- 1 | from compiler.project2.scanner import scanner 2 | from compiler.project2.parser import parser 3 | from compiler.project2.executer import executer 4 | 5 | 6 | def code(address): 7 | try: 8 | content = open(address).read() 9 | except: 10 | raise Exception('file address is invalid') 11 | 12 | tokens = scanner(content) 13 | actions = parser(tokens) 14 | 15 | result = '' 16 | for i, code in enumerate(actions.code): 17 | result += str(i) + ' ( ' 18 | for i, item in enumerate(code): 19 | if isinstance(item, list): 20 | result += ':'.join([str(i) for i in item]) 21 | if isinstance(item ,str): 22 | result += item 23 | 24 | if i != len(code) - 1: 25 | result += ' , ' 26 | result += ' ) \n' 27 | 28 | return result 29 | 30 | 31 | def run(address): 32 | try: 33 | content = open(address).read() 34 | except: 35 | raise Exception('file address is invalid') 36 | 37 | tokens = scanner(content) 38 | actions = parser(tokens) 39 | 40 | return executer(actions) -------------------------------------------------------------------------------- /compiler/grammar/statements.py: -------------------------------------------------------------------------------- 1 | from compiler.grammar.statement import Statement 2 | from typing import Union, List 3 | 4 | 5 | StatementsType = Union[List[Statement], Statement] 6 | 7 | 8 | class Statements: 9 | def __init__(self, value: StatementsType): 10 | if isinstance(value, Statement): 11 | self.value = [value] 12 | elif isinstance(value, list) and all(isinstance(item, Statement) for item in value): 13 | self.value = value 14 | else: 15 | raise TypeError('value must be instance of Statement or list of Statements') 16 | 17 | @staticmethod 18 | def create(value): 19 | if isinstance(value, Statements): 20 | return value 21 | 22 | return Statements(value) 23 | 24 | def __add__(self, other): 25 | other = Statements.create(other) 26 | 27 | return Statements.create([*self.value, *other.value]) 28 | 29 | def __mul__(self, other): 30 | other = Statements.create(other) 31 | # TODO implement me 32 | 33 | def __repr__(self): 34 | return ' | '.join(map(lambda statement: str(statement), self.value)) 35 | 36 | def __iter__(self): 37 | self._i = 0 38 | return self 39 | 40 | def __next__(self): 41 | try: 42 | value = self.value[self._i] 43 | except IndexError: 44 | raise StopIteration 45 | 46 | self._i += 1 47 | return value -------------------------------------------------------------------------------- /compiler/project2/grammar.txt: -------------------------------------------------------------------------------- 1 | START -> STATEMENTS 2 | 3 | STATEMENTS -> STATEMENT STATEMENTS 4 | STATEMENTS -> λ 5 | 6 | STATEMENT -> if ( EXPRESSION @save ) BLOCK @if 7 | STATEMENT -> if ( EXPRESSION @save ) BLOCK @if @save else BLOCK @else 8 | STATEMENT -> while( @label EXPRESSION @save ) BLOCK @while 9 | STATEMENT -> @label do BLOCK while ( EXPRESSION @do_while ) 10 | STATEMENT -> @variable variable := EXPRESSION delimiter @assign 11 | STATEMENT -> VOID_FUNCTION_CALL delimiter 12 | 13 | VOID_FUNCTION_CALL -> @function function ( FUNCTION_CALL_PARAMS ) @void_call 14 | FUNCTION_CALL -> @function function ( FUNCTION_CALL_PARAMS ) @call 15 | FUNCTION_CALL_PARAMS -> EXPRESSION params_delimiter FUNCTION_CALL_PARAMS 16 | FUNCTION_CALL_PARAMS -> EXPRESSION 17 | FUNCTION_CALL_PARAMS -> λ 18 | 19 | BLOCK -> STATEMENT 20 | BLOCK -> { STATEMENTS } 21 | 22 | TERM -> @variable variable 23 | TERM -> @number number 24 | TERM -> FUNCTION_CALL 25 | 26 | EXPRESSION -> A 27 | 28 | A -> A || B @or 29 | A -> B 30 | 31 | B -> B && C @and 32 | B -> C 33 | 34 | C -> C != D @not_equal 35 | C -> C == D @equal 36 | C -> D 37 | 38 | D -> D >= E @greater_than_and_equal 39 | D -> D > E @greater_than 40 | D -> D <= E @less_than_and_equal 41 | D -> D < E @less_than 42 | D -> E 43 | 44 | E -> E - F @subtract 45 | E -> E + F @add 46 | E -> F 47 | 48 | F -> F * G @multiply 49 | F -> F / G @divide 50 | F -> F % G @modulo 51 | F -> G 52 | 53 | G -> H ^ G @pow 54 | G -> H 55 | 56 | H -> - H @unary_minus 57 | H -> + H @unary_plus 58 | H -> ! H @not 59 | H -> I 60 | 61 | I -> TERM 62 | I -> ( EXPRESSION ) -------------------------------------------------------------------------------- /compiler/fa/finite_automata.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from compiler.fa.atom import Symbol, State 4 | 5 | 6 | class FiniteAutomata: 7 | def __init__( 8 | self, 9 | states: List[State], 10 | symbols: List[Symbol], 11 | start: State, 12 | terminals: List[State], 13 | transitions 14 | ): 15 | self.states = list(map(lambda state: state.value, states)) 16 | self.symbols = self.create_symbols(symbols) 17 | self.start = start.value 18 | self.terminals = list(map(lambda state: state.value, terminals)) 19 | self.transitions = transitions 20 | self.table = self.create_table() 21 | 22 | def create_symbols(self, symbols): 23 | result = [] 24 | 25 | for symbol in symbols: 26 | result = result + symbol.value.all() 27 | 28 | return list(set(map(lambda ascii: chr(ascii), result))) 29 | 30 | def create_table(self): 31 | value = dict(map(lambda state: [state, dict(map( 32 | lambda symbol: [symbol, []], 33 | self.symbols 34 | ))],self.states)) 35 | 36 | for transition in self.transitions: 37 | start = transition[0].value 38 | end = transition[2].value 39 | 40 | for ascii in transition[1].value.all(): 41 | value[start][chr(ascii)].append(end) 42 | 43 | return value 44 | 45 | def has_state(self, state): 46 | return state in self.states 47 | 48 | def is_terminal(self, state): 49 | return state in self.terminals -------------------------------------------------------------------------------- /compiler/grammar/expression.py: -------------------------------------------------------------------------------- 1 | ExpressionType = str 2 | 3 | 4 | class Expression(object): 5 | def __int__(self, value: ExpressionType): 6 | self.value = value 7 | 8 | def __repr__(self): 9 | return self.value 10 | 11 | def is_lambda(self): 12 | return self.value == '' 13 | 14 | def is_dollar(self): 15 | return False 16 | 17 | def __eq__(self, other): 18 | return False 19 | 20 | 21 | class Dollar(Expression): 22 | def __init__(self): 23 | super().__int__('$') 24 | 25 | def is_dollar(self): 26 | return True 27 | 28 | def __eq__(self, other): 29 | return isinstance(other, Dollar) and self.value == other.value 30 | 31 | 32 | class Action(Expression): 33 | def __init__(self, value: str): 34 | super().__int__('@' + value) 35 | 36 | def __eq__(self, other): 37 | return isinstance(other, Dollar) 38 | 39 | 40 | class Variable(Expression): 41 | def __init__(self, value: ExpressionType): 42 | if value == '': 43 | raise Exception('variable name cant be empty string') 44 | 45 | super().__int__(value) 46 | 47 | def __eq__(self, other): 48 | return isinstance(other, Variable) and self.value == other.value 49 | 50 | 51 | class Terminal(Expression): 52 | def __init__(self, value: ExpressionType): 53 | super().__int__(value) 54 | 55 | def __repr__(self): 56 | if self.value == '': 57 | return 'λ' 58 | return self.value 59 | 60 | def __eq__(self, other): 61 | return isinstance(other, Terminal) and self.value == other.value 62 | -------------------------------------------------------------------------------- /compiler/grammar/statement.py: -------------------------------------------------------------------------------- 1 | from compiler.grammar.expression import Expression 2 | from typing import Union, List 3 | 4 | 5 | StatementType = Union[Expression, List[Expression]] 6 | 7 | 8 | class Statement: 9 | def __init__(self, value: StatementType): 10 | if isinstance(value, Expression): 11 | self.value = [value] 12 | elif isinstance(value, list) and all(isinstance(item, Expression) for item in value): 13 | self.value = value 14 | else: 15 | raise TypeError('value must be instance of Expression or list of Expressions') 16 | 17 | @staticmethod 18 | def create(value): 19 | if isinstance(value, Statement): 20 | return value 21 | 22 | return Statement(value) 23 | 24 | def __getattr__(self, item): 25 | print('__getattr__', item) 26 | pass 27 | 28 | def __repr__(self): 29 | return ''.join(map(lambda exp: str(exp), self.value)) 30 | 31 | def __getitem__(self, key): 32 | if isinstance(key, slice): 33 | return self.value[key.start:key.stop:key.step] 34 | return self.value[key] 35 | 36 | def __len__(self): 37 | return len(self.value) 38 | 39 | def __iter__(self): 40 | self._i = 0 41 | return self 42 | 43 | def __next__(self): 44 | try: 45 | value = self.value[self._i] 46 | except IndexError: 47 | raise StopIteration 48 | 49 | self._i += 1 50 | return value 51 | 52 | # def __add__(self, other): 53 | # other = Statement.create(other) 54 | # 55 | # return Statement.create([*self.value, *other.value]) 56 | # 57 | # def __mul__(self, other): 58 | # pass -------------------------------------------------------------------------------- /compiler/project2/optimized-grammar.txt: -------------------------------------------------------------------------------- 1 | START -> STATEMENTS 2 | 3 | STATEMENTS -> STATEMENT STATEMENTS 4 | STATEMENTS -> λ 5 | 6 | STATEMENT -> while( @label EXPRESSION @save ) BLOCK @while 7 | STATEMENT -> @label do BLOCK while ( EXPRESSION @do_while ) 8 | STATEMENT -> @variable variable := EXPRESSION delimiter @assign 9 | STATEMENT -> VOID_FUNCTION_CALL delimiter 10 | STATEMENT -> if ( EXPRESSION @save ) BLOCK @if IF 11 | 12 | IF -> λ 13 | IF -> @save else BLOCK @else 14 | 15 | VOID_FUNCTION_CALL -> @function function ( FUNCTION_CALL_PARAMS ) @void_call 16 | FUNCTION_CALL -> @function function ( FUNCTION_CALL_PARAMS ) @call 17 | FUNCTION_CALL_PARAMS -> TERM FUNCTION_CALL_PARAMS1 18 | FUNCTION_CALL_PARAMS1 -> params_delimiter FUNCTION_CALL_PARAMS 19 | FUNCTION_CALL_PARAMS1 -> λ 20 | FUNCTION_CALL_PARAMS -> λ 21 | 22 | BLOCK -> STATEMENT 23 | BLOCK -> { STATEMENTS } 24 | 25 | TERM -> @variable variable 26 | TERM -> @number number 27 | TERM -> FUNCTION_CALL 28 | 29 | EXPRESSION -> A 30 | 31 | A -> B A2 32 | A2 -> || B @or A2 33 | A2 -> λ 34 | 35 | B -> C B2 36 | B2 -> && C @and B2 37 | B2 -> λ 38 | 39 | C -> D C2 40 | C2 -> C1 C2 41 | C2 -> λ 42 | C1 -> != D @not_equal 43 | C1 -> == D @equal 44 | 45 | D -> E D2 46 | D2 -> D1 D2 47 | D2 -> λ 48 | D1 -> >= E @greater_than_and_equal 49 | D1 -> > E @greater_than 50 | D1 -> <= E @less_than_and_equal 51 | D1 -> < E @less_than 52 | 53 | E -> F E2 54 | E2 -> E1 E2 55 | E2 -> λ 56 | E1 -> - F @subtract 57 | E1 -> + F @add 58 | 59 | F -> G F2 60 | F2 -> F1 F2 61 | F2 -> λ 62 | F1 -> * G @multiply 63 | F1 -> / G @divide 64 | F1 -> % G @modulo 65 | 66 | G -> H G1 67 | G1 -> ^ G @pow 68 | G1 -> λ 69 | 70 | H -> - H @unary_minus 71 | H -> + H @unary_plus 72 | H -> ! H @not 73 | H -> I 74 | 75 | I -> TERM 76 | I -> ( EXPRESSION ) -------------------------------------------------------------------------------- /compiler/fa/dfa_runner.py: -------------------------------------------------------------------------------- 1 | from compiler.fa.finite_automata import FiniteAutomata 2 | 3 | 4 | class DFARunner: 5 | def __init__(self, fa: FiniteAutomata): 6 | self.fa = fa 7 | self.current = fa.start 8 | 9 | def reset(self): 10 | self.current = self.fa.start 11 | return self 12 | 13 | def read(self, char: str): 14 | self.current = self.fa.table[self.current][char][0] 15 | 16 | return self 17 | 18 | def move(self, state): 19 | if not self.fa.has_state(state): 20 | raise KeyError('state not exists') 21 | 22 | self.current = state 23 | return self 24 | 25 | @property 26 | def accepted(self): 27 | return self.fa.is_terminal(self.current) 28 | 29 | def __repr__(self): 30 | return 'current: ' + self.current + ' & accepted: ' + str(self.accepted) 31 | 32 | @staticmethod 33 | def run(fa, input, on_match): 34 | matched = [] 35 | i = 0 36 | dfa_runner = DFARunner(fa) 37 | 38 | while i < len(input): 39 | j = i 40 | found = '' 41 | accepted_string = '' 42 | 43 | while j < len(input): 44 | try: 45 | dfa_runner.read(input[j]) 46 | except Exception: 47 | break 48 | 49 | found += (input[j]) 50 | j += 1 51 | 52 | if dfa_runner.accepted: 53 | accepted_string = found 54 | 55 | if len(accepted_string): 56 | if callable(on_match): 57 | on_match(accepted_string, i) 58 | 59 | matched.append([i, accepted_string]) 60 | i += len(accepted_string) 61 | else: 62 | i += len(found) or 1 63 | 64 | dfa_runner.reset() 65 | 66 | return dict(matched) -------------------------------------------------------------------------------- /compiler/fa/nfa_runner.py: -------------------------------------------------------------------------------- 1 | from compiler.fa.finite_automata import FiniteAutomata 2 | from compiler.fa.atom import State 3 | 4 | 5 | class NFARunner: 6 | def __init__(self, fa: FiniteAutomata): 7 | self.fa = fa 8 | self.current = [fa.start.value] 9 | 10 | def read(self, char: str): 11 | symbols = self.fa.get_symbols_by_char(char) 12 | 13 | next_cursors = [] 14 | for cursor in self.current: 15 | for symbol in symbols: 16 | try: 17 | next_cursors = next_cursors + self.fa.value[cursor][str(symbol)] 18 | except: 19 | pass 20 | 21 | if len(next_cursors) == 0: 22 | raise Exception('stuck') 23 | 24 | self.current = next_cursors 25 | return self 26 | 27 | def move(self, state): 28 | if not self.fa.has_state(State(state)): 29 | raise KeyError('state not exists') 30 | 31 | self.current = state 32 | return self 33 | 34 | @property 35 | def accepted(self): 36 | return any(self.fa.is_terminal(State(cursor)) for cursor in self.current) 37 | 38 | def __repr__(self): 39 | return 'current: ' + self.current + ' & accepted: ' + str(self.accepted) 40 | 41 | @staticmethod 42 | def run(fa, input): 43 | matched = [] 44 | i = 0 45 | 46 | while i < len(input): 47 | dfa_runner = NFARunner(fa) 48 | j = i 49 | found = '' 50 | accepted_string = '' 51 | 52 | while j < len(input): 53 | try: 54 | dfa_runner.read(input[j]) 55 | except Exception: 56 | print('stuck', [(found + input[j]).replace('\n', '/n'), accepted_string]) 57 | break 58 | 59 | found += (input[j]) 60 | j += 1 61 | 62 | if dfa_runner.accepted: 63 | accepted_string = found 64 | print('accepted', accepted_string) 65 | 66 | if len(accepted_string): 67 | matched.append([i, accepted_string]) 68 | i += len(accepted_string) 69 | else: 70 | i += 1 71 | 72 | return dict(matched) -------------------------------------------------------------------------------- /compiler/char_set.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Union 3 | from json import dumps 4 | from copy import deepcopy 5 | 6 | 7 | class CharSet: 8 | def __init__(self): 9 | self._value = [] 10 | self._every = False 11 | 12 | def clone(self): 13 | obj = CharSet() 14 | obj._value = deepcopy(self._value) 15 | obj._every = self._every 16 | return obj 17 | 18 | def range(self, start: int, end: int) -> CharSet: 19 | self._value.append({'type': 'range', 'value': [start, end]}) 20 | return self 21 | 22 | def not_range(self, start: int, end: int) -> CharSet: 23 | self._value.append({'type': 'not_range', 'value': [start, end]}) 24 | return self 25 | 26 | def char(self, char: Union[int, str]) -> CharSet: 27 | if isinstance(char, str): 28 | char = ord(char[0]) 29 | 30 | self._value.append({'type': 'range', 'value': [char, char + 1]}) 31 | return self 32 | 33 | def not_char(self, char: Union[int, str]) -> CharSet: 34 | if isinstance(char, str): 35 | char = ord(char[0]) 36 | 37 | self._value.append({'type': 'not_range', 'value': [char, char + 1]}) 38 | return self 39 | 40 | def every(self) -> CharSet: 41 | self._every = True 42 | return self 43 | 44 | def check(self, char: Union[int, str]) -> bool: 45 | if isinstance(char, str): 46 | char = ord(char[0]) 47 | 48 | accepted = self._every 49 | 50 | for rule in self._value: 51 | type = rule['type'] 52 | value = rule['value'] 53 | 54 | if type == 'range': 55 | if value[0] <= char < value[1]: 56 | accepted = True 57 | continue 58 | elif type == 'not_range': 59 | if value[0] <= char < value[1]: 60 | accepted = False 61 | 62 | return accepted 63 | 64 | def all(self): 65 | result = [] 66 | 67 | for rule in self._value: 68 | if rule['type'] == 'range': 69 | for i in range(rule['value'][0], rule['value'][1]): 70 | result.append(i) 71 | elif rule['type'] == 'not_range': 72 | for i in range(rule['value'][0], rule['value'][1]): 73 | result.remove(i) 74 | 75 | return result 76 | 77 | def __repr__(self): 78 | return dumps([self._every, self._value]) -------------------------------------------------------------------------------- /compiler/fa/multi_dfa_runner.py: -------------------------------------------------------------------------------- 1 | from compiler.fa.finite_automata import FiniteAutomata 2 | 3 | 4 | class MultiDFARunner: 5 | def __init__(self, *machines: FiniteAutomata): 6 | self.machines = machines 7 | self.tables = list(map(lambda machine: machine.table, machines)) 8 | self.cursors = self.create_cursors() 9 | self.terminals = list(map(lambda machine: dict(map(lambda state: [state, state in machine.terminals], machine.states)), machines)) 10 | 11 | def reset(self): 12 | self.cursors = self.create_cursors() 13 | return self 14 | 15 | def create_cursors(self): 16 | cursors = dict() 17 | 18 | for i in range(len(self.machines)): 19 | cursors[i] = self.machines[i].start 20 | 21 | return cursors 22 | 23 | def read(self, char: str): 24 | new_cursors = dict() 25 | 26 | for i in self.cursors: 27 | current = self.cursors[i] 28 | try: 29 | new_cursors[i] = self.tables[i][current][char][0] 30 | except: 31 | pass 32 | 33 | self.cursors = new_cursors 34 | 35 | return self 36 | 37 | @property 38 | def stucked(self): 39 | return len(self.cursors) == 0 40 | 41 | @property 42 | def accepted(self): 43 | result = [] 44 | 45 | for i in self.cursors: 46 | cursor = self.cursors[i] 47 | 48 | if self.terminals[i][cursor]: 49 | result.append(i) 50 | 51 | return result 52 | 53 | @staticmethod 54 | def run(machines, input, on_match): 55 | i = 0 56 | dfa_runner = MultiDFARunner(*machines) 57 | 58 | while i < len(input): 59 | j = i 60 | found = '' 61 | accepted_string = ['' for _ in range(len(machines))] 62 | 63 | while j < len(input): 64 | dfa_runner.read(input[j]) 65 | 66 | if dfa_runner.stucked: 67 | break 68 | 69 | found += (input[j]) 70 | j += 1 71 | 72 | for machine_index in dfa_runner.accepted: 73 | accepted_string[machine_index] = found 74 | 75 | filtered_accepted_string = list(filter( 76 | lambda item: len(item[1]), 77 | [[i, string] for i, string in enumerate(accepted_string)] 78 | )) 79 | if len(filtered_accepted_string): 80 | skip = on_match(dict(filtered_accepted_string), i) 81 | 82 | i += skip 83 | else: 84 | # i += 1 85 | i += len(found) or 1 86 | 87 | dfa_runner.reset() -------------------------------------------------------------------------------- /compiler/project2/executer.py: -------------------------------------------------------------------------------- 1 | def executer(actions): 2 | def set(address, value): 3 | nonlocal actions 4 | if address[0] == 'const': 5 | return None 6 | getattr(actions, address[0])[address[1]] = value 7 | 8 | def get(address): 9 | nonlocal actions 10 | if address[0] == 'const': 11 | return address[1] 12 | return getattr(actions, address[0])[address[1]] 13 | 14 | i = 0 15 | while i < len(actions.code): 16 | code = actions.code[i] 17 | 18 | if code[0] == '+': 19 | if code[2] is None: 20 | set(code[3], +get(code[1])) 21 | else: 22 | set(code[3], get(code[1]) + get(code[2])) 23 | 24 | elif code[0] == '!': 25 | set(code[3], int(not get(code[1]))) 26 | 27 | elif code[0] == '-': 28 | if code[2] is None: 29 | set(code[3], -get(code[1])) 30 | else: 31 | set(code[3], get(code[1]) - get(code[2])) 32 | 33 | 34 | elif code[0] == '*': 35 | set(code[3], get(code[1]) * get(code[2])) 36 | 37 | elif code[0] == '%': 38 | set(code[3], get(code[1]) % get(code[2])) 39 | 40 | elif code[0] == '>': 41 | set(code[3], int(get(code[1]) > get(code[2]))) 42 | 43 | elif code[0] == '>=': 44 | set(code[3], int(get(code[1]) >= get(code[2]))) 45 | 46 | elif code[0] == '<': 47 | set(code[3], int(get(code[1]) < get(code[2]))) 48 | 49 | elif code[0] == '<=': 50 | set(code[3], int(get(code[1]) <= get(code[2]))) 51 | 52 | elif code[0] == '==': 53 | set(code[3], int(get(code[1]) == get(code[2]))) 54 | 55 | elif code[0] == '!=': 56 | set(code[3], int(get(code[1]) != get(code[2]))) 57 | 58 | elif code[0] == '&&': 59 | set(code[3], int(get(code[1]) and get(code[2]))) 60 | 61 | elif code[0] == '||': 62 | set(code[3], int(get(code[1]) or get(code[2]))) 63 | 64 | elif code[0] == '^': 65 | set(code[3], get(code[1]) ** get(code[2])) 66 | 67 | elif code[0] == '/': 68 | set(code[3], get(code[1]) / get(code[2])) 69 | 70 | elif code[0] == '=': 71 | set(code[3], get(code[1])) 72 | 73 | elif code[0] == 'JMP': 74 | i = code[1][1] 75 | continue 76 | 77 | elif code[0] == 'JMPT': 78 | if get(code[1]): 79 | i = code[2][1] 80 | continue 81 | 82 | elif code[0] == 'JMPF': 83 | if not get(code[1]): 84 | i = code[2][1] 85 | continue 86 | 87 | elif code[0] == 'CALL': 88 | params = [] 89 | for j in range(i - 1, i - code[2] - 1, -1): 90 | params.append(get(actions.code[j][1])) 91 | 92 | result = eval(f'{ code[1] }({ ",".join([str(p) for p in params]) })') 93 | 94 | if code[3] is not None: 95 | set(code[3], result) 96 | 97 | i += 1 98 | 99 | return actions -------------------------------------------------------------------------------- /compiler/grammar/grammar.py: -------------------------------------------------------------------------------- 1 | from compiler.grammar.expression import Variable, Terminal, Action 2 | from compiler.grammar.statements import Statements 3 | import json 4 | 5 | class Grammar: 6 | def __init__(self, variables: list, terminals: list, start: Variable, productions): 7 | if not isinstance(variables, list) or any(not isinstance(variable, Variable) for variable in variables): 8 | raise TypeError('variables must be list of Variable objects') 9 | 10 | if not isinstance(terminals, list) or any(not isinstance(terminal, Terminal) for terminal in terminals): 11 | raise TypeError('terminals must be list of Terminal objects') 12 | 13 | if not isinstance(start, Variable): 14 | raise TypeError('start must be instance of Variable') 15 | 16 | self.start = start 17 | self.variables = variables 18 | self.terminals = terminals 19 | self.productions = productions 20 | self.value = self.create_value() 21 | 22 | def create_value(self): 23 | value = dict(map( 24 | lambda variable: [ 25 | variable.value, 26 | Statements([]) 27 | ], 28 | self.variables 29 | )) 30 | 31 | for [variable, statement] in self.productions: 32 | value[variable.value] = value[variable.value] + statement 33 | 34 | return value 35 | 36 | 37 | def __getitem__(self, key): 38 | return self.value[key] 39 | 40 | def __repr__(self): 41 | return "\n".join(map( 42 | lambda item: str(item[0]) + ' -> ' + str(item[1]), 43 | self.value.items() 44 | )) 45 | 46 | def export_for_node(self): 47 | result = [] 48 | 49 | for variable, statement in self.productions: 50 | right = [] 51 | 52 | for e in statement: 53 | if isinstance(e, Variable): 54 | right.append(e.value) 55 | elif isinstance(e, Terminal) and e.value == '': 56 | right.append(None) 57 | elif isinstance(e, Terminal): 58 | right.append(e.value) 59 | 60 | result.append({ 61 | "left": variable.value, 62 | "right": right 63 | }) 64 | 65 | return json.dumps(result) 66 | 67 | 68 | # class LL1: 69 | # def __init__(self, grammar): 70 | # pass 71 | # 72 | # def first(grammar): 73 | # first = dict(map( 74 | # lambda variable: [variable.value, []], 75 | # grammar.variables 76 | # )) 77 | # 78 | # for variable in grammar.value: 79 | # for statement in grammar.value[variable]: 80 | # pass 81 | # 82 | # 83 | # g = Grammar( 84 | # [Variable('A'), Variable('B')], 85 | # [Terminal('a'), Terminal('b'), Terminal('c')], 86 | # Variable('A'), 87 | # [ 88 | # [ 89 | # Variable('A'), 90 | # Statement([Terminal('a'), Variable('A')]) 91 | # ], 92 | # [ 93 | # Variable('A'), 94 | # Statement([Terminal('c'), Variable('B')]) 95 | # ], 96 | # [ 97 | # Variable('B'), 98 | # Statement([Terminal('b'), Variable('B')]) 99 | # ], 100 | # [ 101 | # Variable('B'), 102 | # Statement([Terminal('b')]) 103 | # ], 104 | # ] 105 | # ) 106 | # 107 | # 108 | # regexGrammar = Grammar( 109 | # [Variable('A'), Variable('B'), Variable('C'), Variable('D'), ], 110 | # [Terminal('|'), Terminal('.'), Terminal('*'), Terminal('+'), Terminal('('), Terminal(')'), Terminal('id'), ], 111 | # Variable('A'), 112 | # [ 113 | # [ 114 | # Variable('A'), 115 | # Statement([Variable('A'), Terminal('|'), Variable('B')]) 116 | # ], 117 | # [ 118 | # Variable('A'), 119 | # Statement([Variable('B')]) 120 | # ], 121 | # [ 122 | # Variable('B'), 123 | # Statement([Variable('B'), Terminal('.'), Variable('C')]) 124 | # ], 125 | # [ 126 | # Variable('B'), 127 | # Statement([Variable('C')]) 128 | # ], 129 | # [ 130 | # Variable('C'), 131 | # Statement([Variable('D'), Terminal('*')]) 132 | # ], 133 | # [ 134 | # Variable('C'), 135 | # Statement([Variable('D'), Terminal('+')]) 136 | # ], 137 | # [ 138 | # Variable('C'), 139 | # Statement([Variable('D')]) 140 | # ], 141 | # [ 142 | # Variable('D'), 143 | # Statement([Terminal('id')]) 144 | # ], 145 | # [ 146 | # Variable('D'), 147 | # Statement([Terminal('('), Variable('A'), Terminal(')')]) 148 | # ], 149 | # ] 150 | # ) 151 | # 152 | # l = LL1(regexGrammar) 153 | # print(regexGrammar) 154 | # print(l.first) -------------------------------------------------------------------------------- /compiler/grammar/ll1.py: -------------------------------------------------------------------------------- 1 | from compiler.grammar.grammar import Grammar 2 | from compiler.grammar.expression import Variable, Terminal, Dollar, Action 3 | from compiler.grammar.statement import Statement 4 | from compiler.helpers import unique_expressions 5 | 6 | 7 | class LL1: 8 | def __init__(self, grammar: Grammar, follow = None): 9 | productions = [] 10 | for production in grammar.productions: 11 | productions.append([ 12 | production[0], 13 | Statement(list(filter( 14 | lambda expr: isinstance(expr, Terminal) or isinstance(expr, Variable), 15 | production[1].value 16 | ))) 17 | ]) 18 | self.grammar = Grammar( 19 | grammar.variables, 20 | grammar.terminals, 21 | grammar.start, 22 | productions 23 | ) 24 | 25 | self.first = self.fill_first() 26 | 27 | # for i in self.first: 28 | # print(i, self.first[i]) 29 | # exit() 30 | 31 | if follow is None: 32 | self.follow = self.fill_follow() 33 | else: 34 | self.follow = follow 35 | self.table = self.fill_table() 36 | 37 | def fill_table(self): 38 | table = dict(map( 39 | lambda variable: [ 40 | variable.value, 41 | dict(map( 42 | lambda expr : [expr.value, None], 43 | self.grammar.terminals + [Dollar()] 44 | )) 45 | ], 46 | self.grammar.variables 47 | )) 48 | 49 | for i, [variable, statement] in enumerate(self.grammar.productions): 50 | first = [statement[0]] 51 | if isinstance(statement[0], Variable): 52 | first = self.first[statement[0].value] 53 | 54 | has_lambda = False 55 | 56 | for terminal in first: 57 | if terminal.is_lambda(): 58 | has_lambda = True 59 | continue 60 | 61 | if table[variable.value][terminal.value] != None: 62 | raise Exception('this grammar is not in LL1 form', [variable, terminal]) 63 | 64 | table[variable.value][terminal.value] = i 65 | 66 | if has_lambda: 67 | follow = self.follow[variable.value] 68 | 69 | for expr in follow: 70 | table[variable.value][expr.value] = i 71 | 72 | return table 73 | 74 | def fill_follow(self): 75 | grammar = self.grammar 76 | follow = dict(map( 77 | lambda variable: [variable.value, []], 78 | grammar.variables 79 | )) 80 | 81 | for variable in self.grammar.variables: 82 | follow[variable.value] = self.find_follow(variable) 83 | 84 | # removing duplicate expressions from follows 85 | for var in follow: 86 | follow[var] = unique_expressions(follow[var]) 87 | 88 | return follow 89 | 90 | def find_follow(self, target: Variable) -> list: 91 | follow = [] 92 | 93 | if self.grammar.start == target: 94 | follow.append(Dollar()) 95 | 96 | for variable, statement in self.grammar.productions: 97 | for i, expr in enumerate(statement): 98 | if expr != target: 99 | continue 100 | 101 | # if expr is last expression of statement 102 | if len(statement[i+1:]) == 0: 103 | if variable != target: 104 | # add variable follow to it, example: A -> aB 105 | follow = follow + self.find_follow(variable) 106 | continue 107 | 108 | next_expr = statement[i+1] 109 | if isinstance(next_expr, Terminal): 110 | first = [next_expr] 111 | else: 112 | first = self.first[next_expr.value] 113 | 114 | 115 | first_has_lambda = any(terminal.is_lambda() for terminal in first) 116 | 117 | follow = follow + list(filter( 118 | lambda terminal: not terminal.is_lambda(), 119 | first 120 | )) 121 | 122 | if first_has_lambda: 123 | follow = follow + self.find_follow(variable) 124 | 125 | return follow 126 | 127 | def fill_first(self): 128 | first = dict(map( 129 | lambda variable: [variable.value, []], 130 | self.grammar.variables 131 | )) 132 | 133 | for [variable, statement] in self.grammar.productions: 134 | statement_first = self.find_first(statement) 135 | first[variable.value] = [ 136 | *first[variable.value], 137 | *statement_first 138 | ] 139 | 140 | # removing duplicate expressions from firsts 141 | for var in first: 142 | first[var] = unique_expressions(first[var]) 143 | 144 | return first 145 | 146 | def find_first(self, statement: Statement): 147 | if isinstance(statement[0], Terminal): 148 | return statement[0:1] 149 | 150 | i = 0 151 | first = [] 152 | while i < len(statement): 153 | if isinstance(statement[i], Terminal): 154 | return first 155 | 156 | var_statements = self.grammar[statement[i].value] 157 | var_first = [] 158 | for var_statement in var_statements: 159 | var_first = var_first + self.find_first(var_statement) 160 | 161 | var_first_has_lambda = any(terminal.is_lambda() for terminal in var_first) 162 | 163 | if not var_first_has_lambda: 164 | return var_first 165 | 166 | first = first + list(filter(lambda terminal: not terminal.is_lambda(), var_first)) 167 | 168 | i += 1 169 | 170 | return first 171 | -------------------------------------------------------------------------------- /gui.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tkinter as tk 3 | from tkinter import ttk 4 | from tkinter import filedialog 5 | from tkinter import colorchooser 6 | from tkinter import font 7 | from tkinter import messagebox 8 | import compiler.project2.main as compiler 9 | 10 | 11 | class App(tk.Tk): 12 | def __init__(self): 13 | super().__init__() 14 | self.title("Pashm Script IDE") 15 | # self.geometry() 16 | # self.minsize(535 ,600) 17 | 18 | self.current_directory = "" 19 | 20 | menubar = tk.Menu(self) 21 | self.config(menu=menubar) 22 | file_menu = tk.Menu(menubar, tearoff="false") 23 | file_menu.add_command(label="Open project shift+O" ,command=self.open) 24 | file_menu.add_command(label="New project shift+N" ,command=self.new) 25 | file_menu.add_command(label="Save project shift+S" ,command=self.save) 26 | file_menu.add_separator() 27 | file_menu.add_command(label="Exit" ,command=self.destroy) 28 | build_menu = tk.Menu(menubar, tearoff="false") 29 | build_menu.add_command(label="Compile F5" ,command=self.compile) 30 | settings_menu = tk.Menu(menubar, tearoff="false") 31 | settings_menu.add_command(label="Change background" ,command=self.background_change) 32 | settings_menu.add_command(label="Change font" ,command=self.font_change) 33 | help_menu = tk.Menu(menubar, tearoff="false") 34 | help_menu.add_command(label="Guide" ,command=lambda: tk.messagebox.showinfo("Hints" ,"-Press F5 to compile.")) 35 | help_menu.add_command(label="About" ,command=lambda: tk.messagebox.showinfo("About" ,"Hello from Madani UNI !")) 36 | menubar.add_cascade(label="File" ,menu=file_menu) 37 | menubar.add_cascade(label="Build" ,menu=build_menu) 38 | menubar.add_cascade(label="Settings" ,menu=settings_menu) 39 | menubar.add_cascade(label="Help" ,menu=help_menu) 40 | 41 | lf = tk.LabelFrame(self) 42 | self.editor = tk.Text(lf) 43 | self.editor.pack(side=tk.LEFT ,fill=tk.BOTH ,expand=True) 44 | editor_scrl = ttk.Scrollbar(lf ,command=self.editor.yview) 45 | self.editor.config(yscrollcommand=editor_scrl.set) 46 | editor_scrl.pack(side=tk.RIGHT ,fill=tk.Y) 47 | lf.pack(side=tk.TOP ,fill=tk.BOTH ,expand=True) 48 | 49 | self.message = tk.Label(self ,text="Welcome" ,fg="purple") 50 | self.message.pack(side=tk.BOTTOM) 51 | 52 | self.editor.bind("<>" ,lambda e: self.on_modification()) 53 | self.bind("" ,lambda e: self.compile()) 54 | self.bind("" ,lambda e: self.new()) 55 | self.bind("" ,lambda e: self.new()) 56 | self.bind("" ,lambda e: self.open()) 57 | self.bind("" ,lambda e: self.open()) 58 | self.bind("" ,lambda e: self.save()) 59 | self.bind("" ,lambda e: self.save()) 60 | 61 | def compile(self): 62 | if not self.current_directory or self.editor.compare("end-1c", "==", "1.0"): 63 | self.message.config(text="Nothing to compile." ,fg="red") 64 | return 65 | 66 | self.save() 67 | 68 | output_path = os.path.splitext(self.current_directory)[0] 69 | try: 70 | content = open(self.current_directory).read() 71 | result = compiler.code(self.current_directory) 72 | 73 | self.message.config(text="Compiled succesfully." ,fg="green") 74 | 75 | # print(self.current_directory ,output_path+".pashm") 76 | self.create_top(output_path ,result) 77 | 78 | except Exception as err: 79 | self.message.config(text="Failed to compile." ,fg="red") 80 | messagebox.showerror("Compiler Error" ,str(err)) 81 | 82 | def open(self): 83 | if not self.editor.compare("end-1c", "==", "1.0"): 84 | if not self.new(): 85 | return 86 | 87 | self.current_directory = filedialog.askopenfilename(title="Select file" ,filetypes=(("pashm files","*.pashm") ,("all files","*.*"))) 88 | self.title("Pashm Script IDE"+"--"+self.current_directory) 89 | code = "" 90 | 91 | with open(self.current_directory) as my_file: 92 | code = my_file.read() 93 | 94 | self.editor.insert(tk.INSERT ,code) 95 | self.message.config(text="File loaded." ,fg="green") 96 | 97 | def new(self): 98 | if messagebox.askyesno("Are you sure?" ,"You are going to clear project from memory. Do you want to proceed?"): 99 | self.current_directory = "" 100 | self.editor.delete("1.0" ,tk.END) 101 | return True 102 | 103 | return False 104 | 105 | def save(self): 106 | if not self.current_directory: 107 | self.current_directory = filedialog.asksaveasfilename(title="Save as" ,filetypes=(("text files" ,"*.txt") ,("all files" ,"*.*"))) 108 | 109 | code = self.editor.get("1.0" ,tk.END) 110 | with open(self.current_directory ,'w') as my_file: 111 | my_file.write(code) 112 | self.message.config(text="File saved." ,fg="green") 113 | 114 | if self.title()[-1] != '*': 115 | return 116 | else: 117 | self.title(self.title()[:-1]) 118 | 119 | def on_modification(self): 120 | if self.title()[-1] != '*': 121 | self.title(self.title() + '*') 122 | 123 | def background_change(self): 124 | color = colorchooser.askcolor(title ="Choose color") 125 | self.editor.config(background=color[1]) 126 | 127 | def font_change(self): 128 | top = tk.Toplevel(self) 129 | top.title("Select Font") 130 | top.geometry("250x350") 131 | textfont = font.Font(family='arial') 132 | self.editor.config(font=textfont) 133 | 134 | fc = tk.Listbox(top) 135 | fc.pack(side=tk.LEFT ,fill=tk.BOTH ,expand=True) 136 | for f in font.families(): 137 | fc.insert('end', f) 138 | 139 | fc.bind('', lambda e: textfont.config(family=fc.get(fc.curselection()))) 140 | 141 | vsb = tk.Scrollbar(top) 142 | vsb.pack(side=tk.RIGHT ,fill=tk.Y) 143 | 144 | fc.configure(yscrollcommand=vsb.set) 145 | vsb.configure(command=fc.yview) 146 | 147 | def create_top(self ,output_path ,compiled_code): 148 | top = tk.Toplevel(self) 149 | top.title("Pashm Script IDE--"+output_path+".pashm") 150 | self.wm_attributes("-disabled", True) 151 | top.transient(self) 152 | top.protocol("WM_DELETE_WINDOW", lambda t=top: self.destroy_top(top)) 153 | 154 | lf = tk.LabelFrame(top) 155 | editor = tk.Text(lf) 156 | editor.pack(side=tk.LEFT ,fill=tk.BOTH ,expand=True) 157 | editor_scrl = ttk.Scrollbar(lf ,command=editor.yview) 158 | editor.config(yscrollcommand=editor_scrl.set) 159 | editor_scrl.pack(side=tk.RIGHT ,fill=tk.Y) 160 | lf.pack(side=tk.TOP ,fill=tk.BOTH ,expand=True) 161 | 162 | editor.insert(tk.INSERT ,compiled_code) 163 | editor.config(state="disable") 164 | 165 | def destroy_top(self ,top): 166 | self.wm_attributes("-disabled", False) 167 | top.destroy() 168 | self.deiconify() 169 | 170 | 171 | if __name__ == "__main__": 172 | App().mainloop() 173 | -------------------------------------------------------------------------------- /compiler/project2/editor.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tkinter as tk 3 | from tkinter import ttk 4 | from tkinter import filedialog 5 | from tkinter import colorchooser 6 | from tkinter import font 7 | from tkinter import messagebox 8 | from compiler.project2.main import compile 9 | 10 | 11 | class App(tk.Tk): 12 | def __init__(self): 13 | super().__init__() 14 | self.title("Eshkene lang editor") 15 | # self.geometry() 16 | # self.minsize(535 ,600) 17 | 18 | self.current_directory = "" 19 | 20 | menubar = tk.Menu(self) 21 | self.config(menu=menubar) 22 | file_menu = tk.Menu(menubar, tearoff="false") 23 | file_menu.add_command(label="Open project shift+O" ,command=self.open) 24 | file_menu.add_command(label="New project shift+N" ,command=self.new) 25 | file_menu.add_command(label="Save project shift+S" ,command=self.save) 26 | file_menu.add_separator() 27 | file_menu.add_command(label="Exit" ,command=self.destroy) 28 | build_menu = tk.Menu(menubar, tearoff="false") 29 | build_menu.add_command(label="Compile F5" ,command=self.compile) 30 | settings_menu = tk.Menu(menubar, tearoff="false") 31 | settings_menu.add_command(label="Change background" ,command=self.background_change) 32 | settings_menu.add_command(label="Change font" ,command=self.font_change) 33 | help_menu = tk.Menu(menubar, tearoff="false") 34 | help_menu.add_command(label="Guide" ,command=lambda: tk.messagebox.showinfo("Hints" ,"-Press F5 to compile.")) 35 | help_menu.add_command(label="About" ,command=lambda: tk.messagebox.showinfo("About" ,"Hello from Madani UNI !")) 36 | menubar.add_cascade(label="File" ,menu=file_menu) 37 | menubar.add_cascade(label="Build" ,menu=build_menu) 38 | menubar.add_cascade(label="Settings" ,menu=settings_menu) 39 | menubar.add_cascade(label="Help" ,menu=help_menu) 40 | 41 | lf = tk.LabelFrame(self) 42 | self.editor = tk.Text(lf) 43 | self.editor.pack(side=tk.LEFT ,fill=tk.BOTH ,expand=True) 44 | editor_scrl = ttk.Scrollbar(lf ,command=self.editor.yview) 45 | self.editor.config(yscrollcommand=editor_scrl.set) 46 | editor_scrl.pack(side=tk.RIGHT ,fill=tk.Y) 47 | lf.pack(side=tk.TOP ,fill=tk.BOTH ,expand=True) 48 | 49 | self.message = tk.Label(self ,text="Welcome" ,fg="purple") 50 | self.message.pack(side=tk.BOTTOM) 51 | 52 | self.editor.bind("<>" ,lambda e: self.on_modification()) 53 | self.bind("" ,lambda e: self.compile()) 54 | self.bind("" ,lambda e: self.new()) 55 | self.bind("" ,lambda e: self.new()) 56 | self.bind("" ,lambda e: self.open()) 57 | self.bind("" ,lambda e: self.open()) 58 | self.bind("" ,lambda e: self.save()) 59 | self.bind("" ,lambda e: self.save()) 60 | 61 | def compile(self): 62 | if not self.current_directory or self.editor.compare("end-1c", "==", "1.0"): 63 | self.message.config(text="Nothing to compile." ,fg="red") 64 | return 65 | 66 | self.save() 67 | 68 | output_path = os.path.splitext(self.current_directory)[0] 69 | try: 70 | content = open(self.current_directory).read() 71 | result = compile(content) 72 | 73 | self.message.config(text="Compiled succesfully." ,fg="green") 74 | 75 | # print(self.current_directory ,output_path+".eshk") 76 | self.create_top(output_path ,"hi") 77 | 78 | except Exception as err: 79 | self.message.config(text="Failed to compile." ,fg="red") 80 | messagebox.showerror("Compiler Error" ,str(err)) 81 | 82 | def open(self): 83 | if not self.editor.compare("end-1c", "==", "1.0"): 84 | if not self.new(): 85 | return 86 | 87 | self.current_directory = filedialog.askopenfilename(title="Select file" ,filetypes=(("text files","*.txt") ,("all files","*.*"))) 88 | self.title("Eshkene lang editor"+"--"+self.current_directory) 89 | code = "" 90 | 91 | with open(self.current_directory) as my_file: 92 | code = my_file.read() 93 | 94 | self.editor.insert(tk.INSERT ,code) 95 | self.message.config(text="File loaded." ,fg="green") 96 | 97 | def new(self): 98 | if messagebox.askyesno("Are you sure?" ,"You are going to clear project from memory. Do you want to proceed?"): 99 | self.current_directory = "" 100 | self.editor.delete("1.0" ,tk.END) 101 | return True 102 | 103 | return False 104 | 105 | def save(self): 106 | if not self.current_directory: 107 | self.current_directory = filedialog.asksaveasfilename(title="Save as" ,filetypes=(("text files" ,"*.txt") ,("all files" ,"*.*"))) 108 | 109 | code = self.editor.get("1.0" ,tk.END) 110 | with open(self.current_directory ,'w') as my_file: 111 | my_file.write(code) 112 | self.message.config(text="File saved." ,fg="green") 113 | 114 | if self.title()[-1] != '*': 115 | return 116 | else: 117 | self.title(self.title()[:-1]) 118 | 119 | def on_modification(self): 120 | if self.title()[-1] != '*': 121 | self.title(self.title() + '*') 122 | 123 | def background_change(self): 124 | color = colorchooser.askcolor(title ="Choose color") 125 | self.editor.config(background=color[1]) 126 | 127 | def font_change(self): 128 | top = tk.Toplevel(self) 129 | top.title("Select Font") 130 | top.geometry("250x350") 131 | textfont = font.Font(family='arial') 132 | self.editor.config(font=textfont) 133 | 134 | fc = tk.Listbox(top) 135 | fc.pack(side=tk.LEFT ,fill=tk.BOTH ,expand=True) 136 | for f in font.families(): 137 | fc.insert('end', f) 138 | 139 | fc.bind('', lambda e: textfont.config(family=fc.get(fc.curselection()))) 140 | 141 | vsb = tk.Scrollbar(top) 142 | vsb.pack(side=tk.RIGHT ,fill=tk.Y) 143 | 144 | fc.configure(yscrollcommand=vsb.set) 145 | vsb.configure(command=fc.yview) 146 | 147 | def create_top(self ,output_path ,compiled_code): 148 | top = tk.Toplevel(self) 149 | top.title("Eshkene lang editor--"+output_path+".eshk") 150 | self.wm_attributes("-disabled", True) 151 | top.transient(self) 152 | top.protocol("WM_DELETE_WINDOW", lambda t=top: self.destroy_top(top)) 153 | 154 | lf = tk.LabelFrame(top) 155 | editor = tk.Text(lf) 156 | editor.pack(side=tk.LEFT ,fill=tk.BOTH ,expand=True) 157 | editor_scrl = ttk.Scrollbar(lf ,command=editor.yview) 158 | editor.config(yscrollcommand=editor_scrl.set) 159 | editor_scrl.pack(side=tk.RIGHT ,fill=tk.Y) 160 | lf.pack(side=tk.TOP ,fill=tk.BOTH ,expand=True) 161 | 162 | editor.insert(tk.INSERT ,compiled_code) 163 | editor.config(state="disable") 164 | 165 | def destroy_top(self ,top): 166 | self.wm_attributes("-disabled", False) 167 | top.destroy() 168 | self.deiconify() 169 | 170 | 171 | if __name__ == "__main__": 172 | App().mainloop() 173 | -------------------------------------------------------------------------------- /compiler/project2/code_generator.py: -------------------------------------------------------------------------------- 1 | class Actions: 2 | def __init__(self): 3 | self.stack = [] 4 | self.code = [] 5 | self.temp = [] 6 | self.vars = {} 7 | 8 | def before(self, action, token): 9 | pass 10 | 11 | def after(self, action, token): 12 | pass 13 | 14 | def add_temp(self, value): 15 | self.temp.append(value) 16 | return len(self.temp) - 1 17 | 18 | def action_number(self, token): 19 | value = token['value'] 20 | self.stack.append([ 21 | 'const', 22 | float(value) if '.' in value else int(value) 23 | ]) 24 | 25 | def action_variable(self, token): 26 | self.stack.append([ 27 | 'vars', 28 | token['value'] 29 | ]) 30 | 31 | def action_assign(self, token): 32 | value = self.stack.pop() 33 | variable = self.stack.pop() 34 | self.code.append(['=', value, None, variable]) 35 | 36 | def action_add(self, token): 37 | operand_b = self.stack.pop() 38 | operand_a = self.stack.pop() 39 | temp_index = self.add_temp(None) 40 | destination = ['temp', temp_index] 41 | 42 | self.code.append(['+', operand_a, operand_b, destination]) 43 | self.stack.append(destination) 44 | 45 | def action_subtract(self, token): 46 | operand_b = self.stack.pop() 47 | operand_a = self.stack.pop() 48 | temp_index = self.add_temp(None) 49 | destination = ['temp', temp_index] 50 | 51 | self.code.append(['-', operand_a, operand_b, destination]) 52 | self.stack.append(destination) 53 | 54 | def action_multiply(self, token): 55 | operand_b = self.stack.pop() 56 | operand_a = self.stack.pop() 57 | temp_index = self.add_temp(None) 58 | destination = ['temp', temp_index] 59 | 60 | self.code.append(['*', operand_a, operand_b, destination]) 61 | self.stack.append(destination) 62 | 63 | def action_pow(self, token): 64 | operand_b = self.stack.pop() 65 | operand_a = self.stack.pop() 66 | temp_index = self.add_temp(None) 67 | destination = ['temp', temp_index] 68 | 69 | self.code.append(['^', operand_a, operand_b, destination]) 70 | self.stack.append(destination) 71 | 72 | def action_divide(self, token): 73 | operand_b = self.stack.pop() 74 | operand_a = self.stack.pop() 75 | temp_index = self.add_temp(None) 76 | destination = ['temp', temp_index] 77 | 78 | self.code.append(['/', operand_a, operand_b, destination]) 79 | self.stack.append(destination) 80 | 81 | def action_save(self, token): 82 | self.stack.append(['save', len(self.code)]) 83 | self.code.append(['NOP', None, None, None]) 84 | 85 | def action_label(self, token): 86 | self.stack.append(['label', len(self.code)]) 87 | 88 | def action_if(self, token): 89 | index = self.stack.pop()[1] 90 | self.code[index] = ['JMPF', self.stack.pop(), ['code', len(self.code)], None] 91 | 92 | def action_while(self, token): 93 | save = self.stack.pop() 94 | expression = self.stack.pop() 95 | label = self.stack.pop() 96 | 97 | self.code[save[1]] = ['JMPF', expression, ['code', len(self.code) + 1], None] 98 | self.code.append(['JMP', ['code', label[1]], None, None]) 99 | 100 | def action_do_while(self, token): 101 | expression = self.stack.pop() 102 | label = self.stack.pop() 103 | 104 | self.code.append(['JMPT', expression, ['code', label[1]], None]) 105 | 106 | def action_function(self, token): 107 | self.stack.append(['function', token['value']]) 108 | 109 | def action_call(self, token): 110 | count = 0 111 | temp_index = self.add_temp(None) 112 | destination = ['temp', temp_index] 113 | 114 | for i in range(len(self.stack) - 1, -1, -1): 115 | head = self.stack[i] 116 | if isinstance(head, list) and head[0] == 'function': 117 | self.code.append(['CALL', head[1], count, destination]) 118 | break 119 | 120 | self.code.append(['PARAM', head, None, None]) 121 | count += 1 122 | 123 | for i in range(count + 1): 124 | self.stack.pop() 125 | 126 | self.stack.append(destination) 127 | 128 | def action_void_call(self, token): 129 | count = 0 130 | 131 | for i in range(len(self.stack) - 1, -1, -1): 132 | head = self.stack[i] 133 | if isinstance(head, list) and head[0] == 'function': 134 | self.code.append(['CALL', head[1], count, None]) 135 | break 136 | 137 | self.code.append(['PARAM', head, None, None]) 138 | count += 1 139 | 140 | for i in range(count + 1): 141 | self.stack.pop() 142 | 143 | def action_not(self, token): 144 | operand = self.stack.pop() 145 | temp_index = self.add_temp(None) 146 | destination = ['temp', temp_index] 147 | 148 | self.code.append(['!', operand, None, destination]) 149 | self.stack.append(destination) 150 | 151 | def action_unary_minus(self, token): 152 | operand = self.stack.pop() 153 | temp_index = self.add_temp(None) 154 | destination = ['temp', temp_index] 155 | 156 | self.code.append(['-', operand, None, destination]) 157 | self.stack.append(destination) 158 | 159 | def action_unary_plus(self, token): 160 | operand = self.stack.pop() 161 | temp_index = self.add_temp(None) 162 | destination = ['temp', temp_index] 163 | 164 | self.code.append(['+', operand, None, destination]) 165 | self.stack.append(destination) 166 | 167 | def action_modulo(self, token): 168 | operand_b = self.stack.pop() 169 | operand_a = self.stack.pop() 170 | temp_index = self.add_temp(None) 171 | destination = ['temp', temp_index] 172 | 173 | self.code.append(['%', operand_a, operand_b, destination]) 174 | self.stack.append(destination) 175 | 176 | def action_less_than(self, token): 177 | operand_b = self.stack.pop() 178 | operand_a = self.stack.pop() 179 | temp_index = self.add_temp(None) 180 | destination = ['temp', temp_index] 181 | 182 | self.code.append(['<', operand_a, operand_b, destination]) 183 | self.stack.append(destination) 184 | 185 | def action_less_than_and_equal(self, token): 186 | operand_b = self.stack.pop() 187 | operand_a = self.stack.pop() 188 | temp_index = self.add_temp(None) 189 | destination = ['temp', temp_index] 190 | 191 | self.code.append(['<=', operand_a, operand_b, destination]) 192 | self.stack.append(destination) 193 | 194 | def action_greater_than(self, token): 195 | operand_b = self.stack.pop() 196 | operand_a = self.stack.pop() 197 | temp_index = self.add_temp(None) 198 | destination = ['temp', temp_index] 199 | 200 | self.code.append(['>', operand_a, operand_b, destination]) 201 | self.stack.append(destination) 202 | 203 | def action_greater_than_and_equal(self, token): 204 | operand_b = self.stack.pop() 205 | operand_a = self.stack.pop() 206 | temp_index = self.add_temp(None) 207 | destination = ['temp', temp_index] 208 | 209 | self.code.append(['>=', operand_a, operand_b, destination]) 210 | self.stack.append(destination) 211 | 212 | def action_equal(self, token): 213 | operand_b = self.stack.pop() 214 | operand_a = self.stack.pop() 215 | temp_index = self.add_temp(None) 216 | destination = ['temp', temp_index] 217 | 218 | self.code.append(['==', operand_a, operand_b, destination]) 219 | self.stack.append(destination) 220 | 221 | def action_not_equal(self, token): 222 | operand_b = self.stack.pop() 223 | operand_a = self.stack.pop() 224 | temp_index = self.add_temp(None) 225 | destination = ['temp', temp_index] 226 | 227 | self.code.append(['!=', operand_a, operand_b, destination]) 228 | self.stack.append(destination) 229 | 230 | def action_and(self, token): 231 | operand_b = self.stack.pop() 232 | operand_a = self.stack.pop() 233 | temp_index = self.add_temp(None) 234 | destination = ['temp', temp_index] 235 | 236 | self.code.append(['&&', operand_a, operand_b, destination]) 237 | self.stack.append(destination) 238 | 239 | def action_or(self, token): 240 | operand_b = self.stack.pop() 241 | operand_a = self.stack.pop() 242 | temp_index = self.add_temp(None) 243 | destination = ['temp', temp_index] 244 | 245 | self.code.append(['||', operand_a, operand_b, destination]) 246 | self.stack.append(destination) 247 | -------------------------------------------------------------------------------- /compiler/fa/re2nfa.py: -------------------------------------------------------------------------------- 1 | from compiler.grammar.grammar import Grammar 2 | from compiler.grammar.expression import Variable, Terminal, Dollar 3 | from compiler.grammar.statement import Statement 4 | from compiler.helpers import unique_expressions 5 | 6 | def tokenizer(regex): 7 | tokens = [] 8 | 9 | for chr in regex: 10 | if chr in ('(', ')'): 11 | tokens.append({ 'type': 'par', 'value': chr }) 12 | continue 13 | 14 | if chr == '*': 15 | tokens.append({'type': 'star', 'value': chr}) 16 | continue 17 | 18 | if chr == '+': 19 | tokens.append({'type': 'plus', 'value': chr}) 20 | continue 21 | 22 | if chr == '|': 23 | tokens.append({'type': 'union', 'value': chr}) 24 | continue 25 | 26 | tokens.append({'type': 'char', 'value': chr}) 27 | 28 | return tokens 29 | 30 | 31 | class LL1: 32 | def __init__(self, grammar: Grammar): 33 | self.grammar = grammar 34 | self.first = self.fill_first() 35 | self.follow = self.fill_follow() 36 | self.table = self.fill_table() 37 | 38 | def fill_table(self): 39 | table = dict(map( 40 | lambda variable: [ 41 | variable.value, 42 | dict(map( 43 | lambda expr : [expr.value, None], 44 | self.grammar.terminals + [Dollar()] 45 | )) 46 | ], 47 | self.grammar.variables 48 | )) 49 | 50 | for i, [variable, statement] in enumerate(self.grammar.productions): 51 | first = [statement[0]] 52 | if isinstance(statement[0], Variable): 53 | first = self.first[statement[0].value] 54 | 55 | has_lambda = False 56 | 57 | for terminal in first: 58 | if terminal.is_lambda(): 59 | has_lambda = True 60 | continue 61 | 62 | if table[variable.value][terminal.value] != None: 63 | raise Exception('this grammar is not in LL1 form') 64 | 65 | table[variable.value][terminal.value] = i + 1 66 | 67 | if has_lambda: 68 | follow = self.follow[variable.value] 69 | 70 | for expr in follow: 71 | table[variable.value][expr.value] = i + 1 72 | 73 | return table 74 | 75 | def fill_follow(self): 76 | grammar = self.grammar 77 | follow = dict(map( 78 | lambda variable: [variable.value, []], 79 | grammar.variables 80 | )) 81 | 82 | for variable in self.grammar.variables: 83 | follow[variable.value] = self.find_follow(variable) 84 | 85 | # removing duplicate expressions from follows 86 | for var in follow: 87 | follow[var] = unique_expressions(follow[var]) 88 | 89 | return follow 90 | 91 | def find_follow(self, target: Variable) -> list: 92 | follow = [] 93 | 94 | if self.grammar.start == target: 95 | follow.append(Dollar()) 96 | 97 | for variable, statement in self.grammar.productions: 98 | for i, expr in enumerate(statement): 99 | if expr != target: 100 | continue 101 | 102 | # if expr is last expression of statement 103 | if len(statement[i+1:]) == 0: 104 | if variable != target: 105 | # add variable follow to it, example: A -> aB 106 | follow = follow + self.find_follow(variable) 107 | continue 108 | 109 | next_expr = statement[i+1] 110 | if isinstance(next_expr, Terminal): 111 | first = [next_expr] 112 | else: 113 | first = self.first[next_expr.value] 114 | 115 | 116 | first_has_lambda = any(terminal.is_lambda() for terminal in first) 117 | 118 | follow = follow + list(filter( 119 | lambda terminal: not terminal.is_lambda(), 120 | first 121 | )) 122 | 123 | if first_has_lambda: 124 | follow = follow + self.find_follow(variable) 125 | 126 | return follow 127 | 128 | def fill_first(self): 129 | first = dict(map( 130 | lambda variable: [variable.value, []], 131 | self.grammar.variables 132 | )) 133 | 134 | for [variable, statement] in self.grammar.productions: 135 | statement_first = self.find_first(statement) 136 | first[variable.value] = [ 137 | *first[variable.value], 138 | *statement_first 139 | ] 140 | 141 | # removing duplicate expressions from firsts 142 | for var in first: 143 | first[var] = unique_expressions(first[var]) 144 | 145 | return first 146 | 147 | def find_first(self, statement: Statement): 148 | if isinstance(statement[0], Terminal): 149 | return statement[0:1] 150 | 151 | i = 0 152 | first = [] 153 | while i < len(statement): 154 | if isinstance(statement[i], Terminal): 155 | return first 156 | 157 | var_statements = self.grammar[statement[i].value] 158 | var_first = [] 159 | for var_statement in var_statements: 160 | var_first = var_first + self.find_first(var_statement) 161 | 162 | var_first_has_lambda = any(terminal.is_lambda() for terminal in var_first) 163 | 164 | if not var_first_has_lambda: 165 | return var_first 166 | 167 | first = first + list(filter(lambda terminal: not terminal.is_lambda(), var_first)) 168 | 169 | i += 1 170 | 171 | return first 172 | 173 | 174 | def parser(tokens): 175 | grammar = Grammar( 176 | [ 177 | Variable('A'), Variable("A'"), Variable('B'), Variable("B'"), 178 | Variable('C'), Variable("C'"), Variable('D'), 179 | ],[ 180 | Terminal('|'), Terminal('.'), Terminal('*'), Terminal('+'), 181 | Terminal('('), Terminal(')'), Terminal('id'), 182 | ], 183 | Variable('A'), 184 | [ 185 | [Variable('A'), Statement([Variable('B'), Variable("A'")])], 186 | 187 | [Variable("A'"), Statement([Terminal('')])], 188 | [Variable("A'"), Statement([Terminal('|'), Variable('B'), Variable("A'")])], 189 | 190 | [Variable("B"), Statement([Variable('C'), Variable("B'")])], 191 | [Variable("B'"), Statement([Terminal('.'), Variable('C'), Variable("B'")])], 192 | [Variable("B'"), Statement([Terminal('')])], 193 | 194 | [Variable("C"), Statement([Variable('D'), Variable("C'")])], 195 | [Variable("C'"), Statement([Terminal('+')])], 196 | [Variable("C'"), Statement([Terminal('*')])], 197 | [Variable("C'"), Statement([Terminal('')])], 198 | 199 | [Variable("D"), Statement([Terminal('id')])], 200 | [Variable("D"), Statement([Terminal('('), Variable('A'), Terminal(')')])], 201 | ] 202 | ) 203 | grammar = Grammar( 204 | [Variable('A'), Variable('B')], 205 | [Terminal('a'), Terminal('b'), Terminal('f')], 206 | Variable('A'), 207 | [ 208 | [Variable('A'), Statement([Variable('A'), Terminal('a'), Variable('B')])], 209 | [Variable('A'), Statement([Variable('A'), Terminal('b'), Variable('B')])], 210 | [Variable('B'), Statement([Terminal('')])], 211 | [Variable('B'), Statement([Terminal('f'), Variable('B')])], 212 | [Variable('A'), Statement([Terminal('')])], 213 | ] 214 | ) 215 | # print(grammar) 216 | # print() 217 | 218 | i = 0 219 | l = LL1(grammar) 220 | 221 | print('first') 222 | for e in l.first: 223 | print(e) 224 | print() 225 | 226 | print('follow') 227 | for e in l.follow: 228 | print(e) 229 | 230 | def run_var(variable: Variable): 231 | nonlocal i 232 | for statement in grammar[variable.value]: 233 | first = [statement[0]] 234 | if isinstance(statement[0], Variable): 235 | first = l.first[statement[0].value] 236 | 237 | for terminal in first: 238 | if terminal.value == 'id' and tokens[i]['type'] == 'char' or terminal.value == tokens[i]['value']: 239 | result = [] 240 | for expr in grammar.productions[l.table[variable.value][terminal.value]][1]: 241 | if isinstance(expr, Terminal): 242 | result.append(run_terminal(expr)) 243 | else: 244 | result.append(run_var(expr)) 245 | return result 246 | 247 | raise Exception('syntax error in run_var') 248 | 249 | 250 | def run_terminal(terminal: Terminal): 251 | nonlocal i 252 | if terminal.value == 'id' and tokens[i]['type'] == 'char' or terminal.value == tokens[i]['value']: 253 | i += 1 254 | return i 255 | else: 256 | raise Exception('syntax error in run_terminal', terminal, tokens[i]['value']) 257 | 258 | return 'hi' 259 | return run_var(grammar.start) 260 | 261 | 262 | tokens = tokenizer('a+b') 263 | print(tokens) 264 | 265 | print() 266 | 267 | ast = parser(tokens) 268 | # print(ast) 269 | 270 | # l = LL1(grammar) 271 | # 272 | # print('firsts') 273 | # for var in l.follow: 274 | # print(var, l.follow[var]) 275 | # 276 | # print() 277 | # 278 | # print('follows') 279 | # for var in l.first: 280 | # print(var, l.first[var]) 281 | # 282 | # print() 283 | # 284 | # print('LL1 table') 285 | # for var in l.table: 286 | # print(var, l.table[var]) 287 | 288 | # s = Statement([Terminal('a'), Variable('A')]) 289 | # print(len(s)) 290 | -------------------------------------------------------------------------------- /compiler/project2/parser.py: -------------------------------------------------------------------------------- 1 | from compiler.grammar.grammar import Grammar 2 | from compiler.grammar.expression import Variable, Terminal, Dollar, Action 3 | from compiler.grammar.statement import Statement 4 | from compiler.grammar.ll1 import LL1 5 | from compiler.project2.code_generator import Actions 6 | 7 | grammar = Grammar( 8 | [ 9 | Variable('START'), 10 | Variable('STATEMENTS'), 11 | Variable('STATEMENT'), 12 | # Variable('IF'), 13 | Variable('FUNCTION_CALL'), 14 | Variable('FUNCTION_CALL_PARAMS'), 15 | Variable('FUNCTION_CALL_PARAMS1'), 16 | Variable('BLOCK'), 17 | Variable('TERM'), 18 | Variable('EXPRESSION'), 19 | Variable('A'), 20 | Variable('A2'), 21 | Variable('B'), 22 | Variable('B2'), 23 | Variable('C'), 24 | Variable('C2'), 25 | Variable('C1'), 26 | Variable('D'), 27 | Variable('D2'), 28 | Variable('D1'), 29 | Variable('E'), 30 | Variable('E2'), 31 | Variable('E1'), 32 | Variable('F'), 33 | Variable('F2'), 34 | Variable('F1'), 35 | Variable('G'), 36 | Variable('G1'), 37 | Variable('H'), 38 | Variable('I'), 39 | Variable('VOID_FUNCTION_CALL'), 40 | ], 41 | [ 42 | Terminal('while'), 43 | Terminal('('), 44 | Terminal(')'), 45 | Terminal('do'), 46 | Terminal('variable'), 47 | Terminal(':='), 48 | Terminal('delimiter'), 49 | Terminal('if'), 50 | # Terminal('else'), 51 | Terminal('function'), 52 | Terminal('params_delimiter'), 53 | Terminal('{'), 54 | Terminal('}'), 55 | Terminal('number'), 56 | Terminal('||'), 57 | Terminal('&&'), 58 | Terminal('!='), 59 | Terminal('=='), 60 | Terminal('>='), 61 | Terminal('>'), 62 | Terminal('<='), 63 | Terminal('<'), 64 | Terminal('-'), 65 | Terminal('+'), 66 | Terminal('*'), 67 | Terminal('/'), 68 | Terminal('%'), 69 | Terminal('^'), 70 | Terminal('!'), 71 | ], 72 | Variable("START"), 73 | [ 74 | [Variable('START'), Statement([Variable('STATEMENTS')])], 75 | [Variable('STATEMENTS'), Statement([Variable('STATEMENT'), Variable('STATEMENTS')])], 76 | [Variable('STATEMENTS'), Statement([Terminal('')])], 77 | [Variable('STATEMENT'), Statement([Terminal('while'), Terminal('('), Action('label'), Variable('EXPRESSION'), Action('save'), Terminal(')'), Variable('BLOCK'), Action('while')])], 78 | [Variable('STATEMENT'), Statement([Action('label'), Terminal('do'), Variable('BLOCK'), Terminal('while'), Terminal('('), Variable('EXPRESSION'), Action('do_while'), Terminal(')')])], 79 | [Variable('STATEMENT'), Statement([Action('variable'), Terminal('variable'), Terminal(':='), Variable('EXPRESSION'), Terminal('delimiter'), Action('assign')])], 80 | [Variable('STATEMENT'), Statement([Variable('VOID_FUNCTION_CALL'), Terminal('delimiter')])], 81 | [Variable('STATEMENT'), Statement([Terminal('if'), Terminal('('), Variable('EXPRESSION'), Action('save'), Terminal(')'), Variable('BLOCK'), Action('if')])], 82 | # [Variable('STATEMENT'), Statement([Terminal('if'), Terminal('('), Variable('EXPRESSION'), Action('save'), Terminal(')'), Variable('BLOCK'), Action('if'), Variable('IF')])], 83 | # [Variable('IF'), Statement([Terminal('')])], 84 | # [Variable('IF'), Statement([Action('save'), Terminal('else'), Variable('BLOCK'), Action('else')])], 85 | [Variable('VOID_FUNCTION_CALL'), Statement([Action('function'), Terminal('function'), Terminal('('), Variable('FUNCTION_CALL_PARAMS'), Terminal(')'), Action('void_call')])], 86 | [Variable('FUNCTION_CALL'), Statement([Action('function'), Terminal('function'), Terminal('('), Variable('FUNCTION_CALL_PARAMS'), Terminal(')'), Action('call')])], 87 | [Variable('FUNCTION_CALL_PARAMS'), Statement([Variable('TERM'), Variable('FUNCTION_CALL_PARAMS1')])], 88 | [Variable('FUNCTION_CALL_PARAMS1'), Statement([Terminal('params_delimiter'), Variable('FUNCTION_CALL_PARAMS')])], 89 | [Variable('FUNCTION_CALL_PARAMS1'), Statement([Terminal('')])], 90 | [Variable('FUNCTION_CALL_PARAMS'), Statement([Terminal('')])], 91 | [Variable('BLOCK'), Statement([Variable('STATEMENT')])], 92 | [Variable('BLOCK'), Statement([Terminal('{'), Variable('STATEMENTS'), Terminal('}')])], 93 | [Variable('TERM'), Statement([Action('variable'), Terminal('variable')])], 94 | [Variable('TERM'), Statement([Action('number'), Terminal('number')])], 95 | [Variable('TERM'), Statement([Variable('FUNCTION_CALL')])], 96 | [Variable('EXPRESSION'), Statement([Variable('A')])], 97 | [Variable('A'), Statement([Variable('B'), Variable('A2')])], 98 | [Variable('A2'), Statement([Terminal('||'), Variable('B'), Action('or'), Variable('A2')])], 99 | [Variable('A2'), Statement([Terminal('')])], 100 | [Variable('B'), Statement([Variable('C'), Variable('B2')])], 101 | [Variable('B2'), Statement([Terminal('&&'), Variable('C'), Action('and'), Variable('B2')])], 102 | [Variable('B2'), Statement([Terminal('')])], 103 | [Variable('C'), Statement([Variable('D'), Variable('C2')])], 104 | [Variable('C2'), Statement([Variable('C1'), Variable('C2')])], 105 | [Variable('C2'), Statement([Terminal('')])], 106 | [Variable('C1'), Statement([Terminal('!='), Variable('D'), Action('not_equal')])], 107 | [Variable('C1'), Statement([Terminal('=='), Variable('D'), Action('equal')])], 108 | [Variable('D'), Statement([Variable('E'), Variable('D2')])], 109 | [Variable('D2'), Statement([Variable('D1'), Variable('D2')])], 110 | [Variable('D2'), Statement([Terminal('')])], 111 | [Variable('D1'), Statement([Terminal('>='), Variable('E'), Action('greater_than_and_equal')])], 112 | [Variable('D1'), Statement([Terminal('>'), Variable('E'), Action('greater_than')])], 113 | [Variable('D1'), Statement([Terminal('<='), Variable('E'), Action('less_than_and_equal')])], 114 | [Variable('D1'), Statement([Terminal('<'), Variable('E'), Action('less_than')])], 115 | [Variable('E'), Statement([Variable('F'), Variable('E2')])], 116 | [Variable('E2'), Statement([Variable('E1'), Variable('E2')])], 117 | [Variable('E2'), Statement([Terminal('')])], 118 | [Variable('E1'), Statement([Terminal('-'), Variable('F'), Action('subtract')])], 119 | [Variable('E1'), Statement([Terminal('+'), Variable('F'), Action('add')])], 120 | [Variable('F'), Statement([Variable('G'), Variable('F2')])], 121 | [Variable('F2'), Statement([Variable('F1'), Variable('F2')])], 122 | [Variable('F2'), Statement([Terminal('')])], 123 | [Variable('F1'), Statement([Terminal('*'), Variable('G'), Action('multiply')])], 124 | [Variable('F1'), Statement([Terminal('/'), Variable('G'), Action('divide')])], 125 | [Variable('F1'), Statement([Terminal('%'), Variable('G'), Action('modulo')])], 126 | [Variable('G'), Statement([Variable('H'), Variable('G1')])], 127 | [Variable('G1'), Statement([Terminal('^'), Variable('G'), Action('pow')])], 128 | [Variable('G1'), Statement([Terminal('')])], 129 | [Variable('H'), Statement([Terminal('-'), Variable('H'), Action('unary_minus')])], 130 | [Variable('H'), Statement([Terminal('+'), Variable('H'), Action('unary_plus')])], 131 | [Variable('H'), Statement([Terminal('!'), Variable('H'), Action('not')])], 132 | [Variable('H'), Statement([Variable('I')])], 133 | [Variable('I'), Statement([Variable('TERM')])], 134 | [Variable('I'), Statement([Terminal('('), Variable('EXPRESSION'), Terminal(')')])], 135 | ] 136 | ) 137 | 138 | 139 | # print(grammar.export_for_node()) 140 | # exit() 141 | 142 | def get_token_nickname(token): 143 | if token['type'] in ['operator', 'par', 'brace', 'end']: 144 | return token['value'] 145 | 146 | return token["type"] 147 | 148 | 149 | def parser(tokens): 150 | ll1 = LL1( 151 | grammar, 152 | {'START': [Dollar()], 'STATEMENTS': [Dollar(), Terminal('}')], 153 | 'STATEMENT': [Terminal('while'), Terminal('do'), Terminal('variable'), Terminal('if'), Terminal('function'), 154 | Dollar(), Terminal('}')], 155 | 'FUNCTION_CALL': [Terminal('delimiter'), Terminal('params_delimiter'), Terminal(')'), Terminal('^'), 156 | Terminal('*'), Terminal('/'), Terminal('%'), Terminal('-'), Terminal('+'), Terminal('>='), 157 | Terminal('>'), Terminal('<='), Terminal('<'), Terminal('!='), Terminal('=='), Terminal('&&'), 158 | Terminal('||')], 'FUNCTION_CALL_PARAMS': [Terminal(')')], 159 | 'FUNCTION_CALL_PARAMS1': [Terminal(')')], 160 | 'BLOCK': [Terminal('while'), Terminal('do'), Terminal('variable'), Terminal('if'), Terminal('function'), 161 | Dollar(), Terminal('}')], 162 | 'TERM': [Terminal('params_delimiter'), Terminal(')'), Terminal('^'), Terminal('*'), Terminal('/'), 163 | Terminal('%'), Terminal('-'), Terminal('+'), Terminal('>='), Terminal('>'), Terminal('<='), 164 | Terminal('<'), Terminal('!='), Terminal('=='), Terminal('&&'), Terminal('||'), Terminal('delimiter')], 165 | 'EXPRESSION': [Terminal(')'), Terminal('delimiter')], 'A': [Terminal(')'), Terminal('delimiter')], 166 | 'A2': [Terminal(')'), Terminal('delimiter')], 'B': [Terminal('||'), Terminal(')'), Terminal('delimiter')], 167 | 'B2': [Terminal('||'), Terminal(')'), Terminal('delimiter')], 168 | 'C': [Terminal('&&'), Terminal('||'), Terminal(')'), Terminal('delimiter')], 169 | 'C2': [Terminal('&&'), Terminal('||'), Terminal(')'), Terminal('delimiter')], 170 | 'C1': [Terminal('!='), Terminal('=='), Terminal('&&'), Terminal('||'), Terminal(')'), Terminal('delimiter')], 171 | 'D': [Terminal('!='), Terminal('=='), Terminal('&&'), Terminal('||'), Terminal(')'), Terminal('delimiter')], 172 | 'D2': [Terminal('!='), Terminal('=='), Terminal('&&'), Terminal('||'), Terminal(')'), Terminal('delimiter')], 173 | 'D1': [Terminal('>='), Terminal('>'), Terminal('<='), Terminal('<'), Terminal('!='), Terminal('=='), 174 | Terminal('&&'), Terminal('||'), Terminal(')'), Terminal('delimiter')], 175 | 'E': [Terminal('>='), Terminal('>'), Terminal('<='), Terminal('<'), Terminal('!='), Terminal('=='), 176 | Terminal('&&'), Terminal('||'), Terminal(')'), Terminal('delimiter')], 177 | 'E2': [Terminal('>='), Terminal('>'), Terminal('<='), Terminal('<'), Terminal('!='), Terminal('=='), 178 | Terminal('&&'), Terminal('||'), Terminal(')'), Terminal('delimiter')], 179 | 'E1': [Terminal('-'), Terminal('+'), Terminal('>='), Terminal('>'), Terminal('<='), Terminal('<'), 180 | Terminal('!='), Terminal('=='), Terminal('&&'), Terminal('||'), Terminal(')'), Terminal('delimiter')], 181 | 'F': [Terminal('-'), Terminal('+'), Terminal('>='), Terminal('>'), Terminal('<='), Terminal('<'), 182 | Terminal('!='), Terminal('=='), Terminal('&&'), Terminal('||'), Terminal(')'), Terminal('delimiter')], 183 | 'F2': [Terminal('-'), Terminal('+'), Terminal('>='), Terminal('>'), Terminal('<='), Terminal('<'), 184 | Terminal('!='), Terminal('=='), Terminal('&&'), Terminal('||'), Terminal(')'), Terminal('delimiter')], 185 | 'F1': [Terminal('*'), Terminal('/'), Terminal('%'), Terminal('-'), Terminal('+'), Terminal('>='), 186 | Terminal('>'), Terminal('<='), Terminal('<'), Terminal('!='), Terminal('=='), Terminal('&&'), 187 | Terminal('||'), Terminal(')'), Terminal('delimiter')], 188 | 'G': [Terminal('*'), Terminal('/'), Terminal('%'), Terminal('-'), Terminal('+'), Terminal('>='), Terminal('>'), 189 | Terminal('<='), Terminal('<'), Terminal('!='), Terminal('=='), Terminal('&&'), Terminal('||'), 190 | Terminal(')'), Terminal('delimiter')], 191 | 'G1': [Terminal('*'), Terminal('/'), Terminal('%'), Terminal('-'), Terminal('+'), Terminal('>='), 192 | Terminal('>'), Terminal('<='), Terminal('<'), Terminal('!='), Terminal('=='), Terminal('&&'), 193 | Terminal('||'), Terminal(')'), Terminal('delimiter')], 194 | 'H': [Terminal('^'), Terminal('*'), Terminal('/'), Terminal('%'), Terminal('-'), Terminal('+'), Terminal('>='), 195 | Terminal('>'), Terminal('<='), Terminal('<'), Terminal('!='), Terminal('=='), Terminal('&&'), 196 | Terminal('||'), Terminal(')'), Terminal('delimiter')], 197 | 'I': [Terminal('^'), Terminal('*'), Terminal('/'), Terminal('%'), Terminal('-'), Terminal('+'), Terminal('>='), 198 | Terminal('>'), Terminal('<='), Terminal('<'), Terminal('!='), Terminal('=='), Terminal('&&'), 199 | Terminal('||'), Terminal(')'), Terminal('delimiter')]} 200 | ) 201 | tokens = [{ "type": "end", "value": "$" }, *(tokens[::-1])] 202 | stack = [Dollar(), grammar.start] 203 | actions = Actions() 204 | 205 | while len(tokens) > 1 or len(stack) > 1: 206 | # print(f'---------------------') 207 | # print('stack', stack) 208 | # print('tokens', [token['type'] for token in tokens]) 209 | 210 | head = stack.pop() 211 | token = tokens[-1] 212 | 213 | if isinstance(head, Variable): 214 | target = ll1.table[head.value][get_token_nickname(token)] 215 | 216 | if target is None: 217 | raise Exception("I don't know where, but you have syntax error!") 218 | 219 | statement = grammar.productions[target][1].value[::-1] 220 | stack = stack + list(filter( 221 | lambda x: not x.is_lambda(), 222 | statement 223 | )) 224 | elif isinstance(head, Terminal): 225 | if head.value == get_token_nickname(token): 226 | tokens.pop() 227 | else: 228 | raise Exception("I don't know where, but you have syntax error!") 229 | elif isinstance(head, Action): 230 | getattr(actions, 'before')(head.value, token) 231 | getattr(actions, 'action_' + head.value[1:])(token) 232 | getattr(actions, 'after')(head.value, token) 233 | # print(head.value, actions.stack) 234 | 235 | if stack[0].value == tokens[0]['value'] and stack[0].is_dollar(): 236 | return actions 237 | else: 238 | raise Exception("I don't know where, but you have syntax error!") -------------------------------------------------------------------------------- /compiler/project2/scanner.py: -------------------------------------------------------------------------------- 1 | from compiler.char_set import CharSet 2 | from compiler.fa.finite_automata import FiniteAutomata 3 | from compiler.fa.atom import State, Symbol 4 | from compiler.fa.multi_dfa_runner import MultiDFARunner 5 | 6 | 7 | def build_assign_dfa(): 8 | return FiniteAutomata( 9 | [State(str(i)) for i in range(3)], 10 | [Symbol(':'), Symbol('=')], 11 | State('0'), 12 | [State('2')], 13 | [ 14 | [State('0'), Symbol(':'), State('1')], 15 | [State('1'), Symbol('='), State('2')], 16 | ] 17 | ) 18 | 19 | 20 | def build_multiply_dfa(): 21 | return FiniteAutomata( 22 | [State(str(i)) for i in range(2)], 23 | [Symbol('*')], 24 | State('0'), 25 | [State('1')], 26 | [ 27 | [State('0'), Symbol('*'), State('1')], 28 | ] 29 | ) 30 | 31 | 32 | def build_divide_dfa(): 33 | return FiniteAutomata( 34 | [State(str(i)) for i in range(2)], 35 | [Symbol('/')], 36 | State('0'), 37 | [State('1')], 38 | [ 39 | [State('0'), Symbol('/'), State('1')], 40 | ] 41 | ) 42 | 43 | 44 | def build_pow_dfa(): 45 | return FiniteAutomata( 46 | [State(str(i)) for i in range(2)], 47 | [Symbol('^')], 48 | State('0'), 49 | [State('1')], 50 | [ 51 | [State('0'), Symbol('^'), State('1')], 52 | ] 53 | ) 54 | 55 | 56 | def build_add_dfa(): 57 | return FiniteAutomata( 58 | [State(str(i)) for i in range(2)], 59 | [Symbol('+')], 60 | State('0'), 61 | [State('1')], 62 | [ 63 | [State('0'), Symbol('+'), State('1')], 64 | ] 65 | ) 66 | 67 | 68 | def build_minus_dfa(): 69 | return FiniteAutomata( 70 | [State(str(i)) for i in range(2)], 71 | [Symbol('-')], 72 | State('0'), 73 | [State('1')], 74 | [ 75 | [State('0'), Symbol('-'), State('1')], 76 | ] 77 | ) 78 | 79 | 80 | def build_open_par_dfa(): 81 | return FiniteAutomata( 82 | [State(str(i)) for i in range(2)], 83 | [Symbol('(')], 84 | State('0'), 85 | [State('1')], 86 | [ 87 | [State('0'), Symbol('('), State('1')], 88 | ] 89 | ) 90 | 91 | 92 | def build_close_par_dfa(): 93 | return FiniteAutomata( 94 | [State(str(i)) for i in range(2)], 95 | [Symbol(')')], 96 | State('0'), 97 | [State('1')], 98 | [ 99 | [State('0'), Symbol(')'), State('1')], 100 | ] 101 | ) 102 | 103 | 104 | def build_variable_dfa(): 105 | first_char = CharSet().range(65, 91).range(97, 123).char('_') 106 | rest_chars = CharSet().range(65, 91).range(97, 123).range(48, 58).char('_') 107 | 108 | return FiniteAutomata( 109 | [State(str(i)) for i in range(2)], 110 | [Symbol(first_char), Symbol(rest_chars)], 111 | State('0'), 112 | [State('1')], 113 | [ 114 | [State('0'), Symbol(first_char), State('1')], 115 | [State('1'), Symbol(rest_chars), State('1')], 116 | ] 117 | ) 118 | 119 | 120 | def build_number_dfa(): 121 | digits = CharSet().range(48, 58) 122 | 123 | return FiniteAutomata( 124 | [State(str(i)) for i in range(4)], 125 | [Symbol(digits), Symbol('.')], 126 | State('0'), 127 | [State('1'), State('3')], 128 | [ 129 | [State('0'), Symbol(digits), State('1')], 130 | [State('1'), Symbol(digits), State('1')], 131 | [State('1'), Symbol('.'), State('2')], 132 | [State('2'), Symbol(digits), State('3')], 133 | [State('3'), Symbol(digits), State('3')], 134 | ] 135 | ) 136 | 137 | 138 | def build_open_brace_dfa(): 139 | return FiniteAutomata( 140 | [State(str(i)) for i in range(2)], 141 | [Symbol('{')], 142 | State('0'), 143 | [State('1')], 144 | [ 145 | [State('0'), Symbol('{'), State('1')], 146 | ] 147 | ) 148 | 149 | 150 | def build_close_brace_dfa(): 151 | return FiniteAutomata( 152 | [State(str(i)) for i in range(2)], 153 | [Symbol('}')], 154 | State('0'), 155 | [State('1')], 156 | [ 157 | [State('0'), Symbol('}'), State('1')], 158 | ] 159 | ) 160 | 161 | 162 | def build_params_delimiter_dfa(): 163 | return FiniteAutomata( 164 | [State(str(i)) for i in range(2)], 165 | [Symbol(',')], 166 | State('0'), 167 | [State('1')], 168 | [ 169 | [State('0'), Symbol(','), State('1')], 170 | ] 171 | ) 172 | 173 | 174 | def build_function_dfa(): 175 | first_char = CharSet().range(65, 91).range(97, 123).char('_') 176 | rest_chars = CharSet().range(65, 91).range(97, 123).range(48, 58).char('_') 177 | 178 | return FiniteAutomata( 179 | [State(str(i)) for i in range(3)], 180 | [Symbol(first_char), Symbol(rest_chars), Symbol('(')], 181 | State('0'), 182 | [State('2')], 183 | [ 184 | [State('0'), Symbol(first_char), State('1')], 185 | [State('1'), Symbol(rest_chars), State('1')], 186 | [State('1'), Symbol('('), State('2')], 187 | ] 188 | ) 189 | 190 | 191 | def build_while_dfa(): 192 | return FiniteAutomata( 193 | [State(str(i)) for i in range(6)], 194 | [ 195 | Symbol('w'), Symbol('h'), Symbol('i'), Symbol('l'), Symbol('e') 196 | ], 197 | State('0'), 198 | [State('5')], 199 | [ 200 | [State('0'), Symbol('w'), State('1')], 201 | [State('1'), Symbol('h'), State('2')], 202 | [State('2'), Symbol('i'), State('3')], 203 | [State('3'), Symbol('l'), State('4')], 204 | [State('4'), Symbol('e'), State('5')], 205 | ] 206 | ) 207 | 208 | 209 | def build_do_dfa(): 210 | return FiniteAutomata( 211 | [State(str(i)) for i in range(3)], 212 | [ 213 | Symbol('d'), Symbol('o') 214 | ], 215 | State('0'), 216 | [State('2')], 217 | [ 218 | [State('0'), Symbol('d'), State('1')], 219 | [State('1'), Symbol('o'), State('2')], 220 | ] 221 | ) 222 | 223 | 224 | def build_if_dfa(): 225 | return FiniteAutomata( 226 | [State(str(i)) for i in range(3)], 227 | [ 228 | Symbol('i'), Symbol('f') 229 | ], 230 | State('0'), 231 | [State('2')], 232 | [ 233 | [State('0'), Symbol('i'), State('1')], 234 | [State('1'), Symbol('f'), State('2')], 235 | ] 236 | ) 237 | 238 | 239 | def build_else_dfa(): 240 | return FiniteAutomata( 241 | [State(str(i)) for i in range(5)], 242 | [ 243 | Symbol('e'), Symbol('l'), Symbol('s'), Symbol('e'), 244 | ], 245 | State('0'), 246 | [State('4')], 247 | [ 248 | [State('0'), Symbol('e'), State('1')], 249 | [State('1'), Symbol('l'), State('2')], 250 | [State('2'), Symbol('s'), State('3')], 251 | [State('3'), Symbol('e'), State('4')], 252 | ] 253 | ) 254 | 255 | 256 | def build_delimiter_dfa(): 257 | return FiniteAutomata( 258 | [State(str(i)) for i in range(2)], 259 | [Symbol(";")], 260 | State('0'), 261 | [State('1')], 262 | [ 263 | [State('0'), Symbol(";"), State('1')], 264 | ] 265 | ) 266 | 267 | 268 | def build_or_dfa(): 269 | return FiniteAutomata( 270 | [State(str(i)) for i in range(3)], 271 | [Symbol("|")], 272 | State('0'), 273 | [State('2')], 274 | [ 275 | [State('0'), Symbol("|"), State('1')], 276 | [State('1'), Symbol("|"), State('2')], 277 | ] 278 | ) 279 | 280 | 281 | def build_and_dfa(): 282 | return FiniteAutomata( 283 | [State(str(i)) for i in range(3)], 284 | [Symbol("&")], 285 | State('0'), 286 | [State('2')], 287 | [ 288 | [State('0'), Symbol("&"), State('1')], 289 | [State('1'), Symbol("&"), State('2')], 290 | ] 291 | ) 292 | 293 | 294 | def build_equal_dfa(): 295 | return FiniteAutomata( 296 | [State(str(i)) for i in range(3)], 297 | [Symbol("=")], 298 | State('0'), 299 | [State('2')], 300 | [ 301 | [State('0'), Symbol("="), State('1')], 302 | [State('1'), Symbol("="), State('2')], 303 | ] 304 | ) 305 | 306 | 307 | def build_not_equal_dfa(): 308 | return FiniteAutomata( 309 | [State(str(i)) for i in range(3)], 310 | [Symbol("="), Symbol('!')], 311 | State('0'), 312 | [State('2')], 313 | [ 314 | [State('0'), Symbol("!"), State('1')], 315 | [State('1'), Symbol("="), State('2')], 316 | ] 317 | ) 318 | 319 | 320 | def build_greater_than_and_equal_dfa(): 321 | return FiniteAutomata( 322 | [State(str(i)) for i in range(3)], 323 | [Symbol("="), Symbol('>')], 324 | State('0'), 325 | [State('2')], 326 | [ 327 | [State('0'), Symbol(">"), State('1')], 328 | [State('1'), Symbol("="), State('2')], 329 | ] 330 | ) 331 | 332 | 333 | def build_less_than_and_equal_dfa(): 334 | return FiniteAutomata( 335 | [State(str(i)) for i in range(3)], 336 | [Symbol("="), Symbol('<')], 337 | State('0'), 338 | [State('2')], 339 | [ 340 | [State('0'), Symbol("<"), State('1')], 341 | [State('1'), Symbol("="), State('2')], 342 | ] 343 | ) 344 | 345 | 346 | def build_greater_than_dfa(): 347 | return FiniteAutomata( 348 | [State(str(i)) for i in range(2)], 349 | [Symbol('>')], 350 | State('0'), 351 | [State('1')], 352 | [ 353 | [State('0'), Symbol(">"), State('1')], 354 | ] 355 | ) 356 | 357 | 358 | def build_less_than_dfa(): 359 | return FiniteAutomata( 360 | [State(str(i)) for i in range(2)], 361 | [Symbol('<')], 362 | State('0'), 363 | [State('1')], 364 | [ 365 | [State('0'), Symbol("<"), State('1')], 366 | ] 367 | ) 368 | 369 | 370 | def build_modulo_dfa(): 371 | return FiniteAutomata( 372 | [State(str(i)) for i in range(2)], 373 | [Symbol('%')], 374 | State('0'), 375 | [State('1')], 376 | [ 377 | [State('0'), Symbol('%'), State('1')], 378 | ] 379 | ) 380 | 381 | 382 | def build_not_dfa(): 383 | return FiniteAutomata( 384 | [State(str(i)) for i in range(2)], 385 | [Symbol('!')], 386 | State('0'), 387 | [State('1')], 388 | [ 389 | [State('0'), Symbol('!'), State('1')], 390 | ] 391 | ) 392 | 393 | 394 | machines = [ 395 | {"dfa": build_assign_dfa(), "priority": 100, "token": {"type": "operator"}}, 396 | {"dfa": build_multiply_dfa(), "priority": 100, "token": {"type": "operator"}}, 397 | {"dfa": build_divide_dfa(), "priority": 100, "token": {"type": "operator"}}, 398 | {"dfa": build_pow_dfa(), "priority": 100, "token": {"type": "operator"}}, 399 | {"dfa": build_add_dfa(), "priority": 100, "token": {"type": "operator"}}, 400 | {"dfa": build_minus_dfa(), "priority": 100, "token": {"type": "operator"}}, 401 | 402 | {"dfa": build_variable_dfa(), "priority": 40, "token": {"type": "variable"}}, 403 | {"dfa": build_number_dfa(), "priority": 40, "token": {"type": "number"}}, 404 | 405 | {"dfa": build_open_brace_dfa(), "priority": 100, "token": {"type": "brace"}}, 406 | {"dfa": build_close_brace_dfa(), "priority": 100, "token": {"type": "brace"}}, 407 | 408 | {"dfa": build_open_par_dfa(), "priority": 100, "token": {"type": "par"}}, 409 | {"dfa": build_close_par_dfa(), "priority": 100, "token": {"type": "par"}}, 410 | 411 | {"dfa": build_delimiter_dfa(), "priority": 100, "token": {"type": "delimiter"}}, 412 | 413 | {"dfa": build_function_dfa(), "priority": 70, "token": {"type": "function"}}, 414 | {"dfa": build_params_delimiter_dfa(), "priority": 100, "token": {"type": "params_delimiter"}}, 415 | 416 | {"dfa": build_while_dfa(), "priority": 80, "token": {"type": "while"}}, 417 | {"dfa": build_do_dfa(), "priority": 80, "token": {"type": "do"}}, 418 | 419 | {"dfa": build_if_dfa(), "priority": 80, "token": {"type": "if"}}, 420 | {"dfa": build_else_dfa(), "priority": 80, "token": {"type": "else"}}, 421 | 422 | {"dfa": build_or_dfa(), "priority": 100, "token": {"type": "operator"}}, 423 | {"dfa": build_and_dfa(), "priority": 100, "token": {"type": "operator"}}, 424 | {"dfa": build_equal_dfa(), "priority": 100, "token": {"type": "operator"}}, 425 | {"dfa": build_not_equal_dfa(), "priority": 100, "token": {"type": "operator"}}, 426 | {"dfa": build_greater_than_and_equal_dfa(), "priority": 100, "token": {"type": "operator"}}, 427 | {"dfa": build_less_than_and_equal_dfa(), "priority": 100, "token": {"type": "operator"}}, 428 | {"dfa": build_greater_than_dfa(), "priority": 100, "token": {"type": "operator"}}, 429 | {"dfa": build_less_than_dfa(), "priority": 100, "token": {"type": "operator"}}, 430 | {"dfa": build_modulo_dfa(), "priority": 100, "token": {"type": "operator"}}, 431 | {"dfa": build_not_dfa(), "priority": 100, "token": {"type": "operator"}}, 432 | ] 433 | 434 | 435 | def scanner(content): 436 | result_tokens = [] 437 | 438 | def on_match(result, index): 439 | nonlocal result_tokens 440 | 441 | list_of_lengths = [] 442 | for machine_index in result: 443 | if machines[machine_index]['token']['type'] == 'function': 444 | list_of_lengths.append(len(result[machine_index]) - 1) 445 | else: 446 | list_of_lengths.append(len(result[machine_index])) 447 | 448 | highest_length = max(list_of_lengths) 449 | filter_by_len = list(filter( 450 | lambda item: len(item[1]) == highest_length, 451 | result.items() 452 | )) 453 | sorted_by_priority = list(sorted( 454 | filter_by_len, 455 | key=lambda item: machines[item[0]]['priority'], 456 | reverse=True 457 | )) 458 | 459 | 460 | candidate = sorted_by_priority[0] 461 | function_has_matched = list(filter( 462 | lambda item: machines[item[0]]['token']['type'] == 'function', 463 | result.items() 464 | )) 465 | if machines[candidate[0]]['token']['type'] == 'variable' and len(function_has_matched): 466 | candidate = function_has_matched[0] 467 | 468 | 469 | [machine_index, matched_string] = candidate 470 | token = machines[machine_index]['token'] 471 | 472 | if token['type'] == 'function': 473 | result_tokens.append({"type": "function", "value": matched_string[0:-1]}) 474 | result_tokens.append({"type": "par", "value": "("}) 475 | elif token['type'] == 'delimiter': 476 | result_tokens.append({"type": "delimiter", "value": ";"}) 477 | else: 478 | result_tokens.append({**token, "value": matched_string}) 479 | 480 | return len(matched_string) 481 | 482 | MultiDFARunner.run( 483 | [machine['dfa'] for machine in machines], 484 | content, 485 | on_match 486 | ) 487 | 488 | return result_tokens 489 | 490 | --------------------------------------------------------------------------------