python으로 recursive descent parser 구현

2022. 11. 12. 01:02프밍언

728x90

20200252_김주현.zip
0.86MB
Internal Document.docx
0.49MB
main.py
0.01MB
과제설명.pdf
0.98MB
External Document.docx
1.15MB

 

import sys
# 단순 산술식에 대한 어휘 분석기 시스템
# 어휘 분석기의 소스 코드는 정수 변수 next_token, 문자열 변수 token_string, 함수 lexical()을 포함해야 한다.

char_class = 0
lexeme = []
next_char = ''
lex_len = 0
next_token = 0
token_string = ''

ID_cnt = 0  # ID 개수
CONST_cnt = 0  # CONST 개수
OP_cnt = 0  # OP 개수

# 문자 유형들(charClass)
letter_type = {'LETTER': 0, 'DIGIT': 1, 'UNKNOWN': 99, 'EOF': -1}

# 토큰 코드들
token_code = {'CONST': 10,
              'IDENT': 11,
              'ASSIGNMENT_OP': 20,
              'ADD_OP': 21,
              'SUB_OP': 22,
              'MULT_OP': 23,
              'DIV_OP': 24,
              'LEFT_PAREN': 25,
              'RIGHT_PAREN': 26,
              'SEMI_COLON': 59}

#변수(IDENT) 값 저장을 위한 dictionary
ident_result = {}

#오류 복구된 결과 저장을 위한 list
read_line = []
is_ok = True    #오류가 있으면 False
error_string = []   #에러 문장들 저장 list


def lookup(ch):
    global next_token, OP_cnt, is_ok
    if ch == '(':
        addchar()  # nextChar을 lexeme에 추가
        next_token = token_code['LEFT_PAREN']

    elif ch == ')':
        addchar()  # nextChar을 lexeme에 추가
        next_token = token_code['RIGHT_PAREN']

    elif ch == '+':
        addchar()
        next_token = token_code['ADD_OP']
        OP_cnt += 1

    elif ch == '-':
        addchar()
        next_token = token_code['SUB_OP']
        OP_cnt += 1

    elif ch == '*':
        addchar()
        next_token = token_code['MULT_OP']
        OP_cnt += 1

    elif ch == '/':
        addchar()
        next_token = token_code['DIV_OP']
        OP_cnt += 1

    elif ch == ';':
        addchar()
        next_token = token_code['SEMI_COLON']
        read_line.append(';')

    elif next_char == ':':
        addchar()
        getchar()
        if next_char == '=':
            addchar()
            next_token = token_code['ASSIGNMENT_OP']
            read_line.append(':=')

    else:
        addchar()
        next_token = letter_type['EOF']

    return next_token


# nextChar을 lexeme에 추가


def addchar():
    global lex_len  # 전역번수 lex_len을 사용하겠다
    if lex_len <= 98:
        lexeme.append(next_char)
        lex_len += 1
    else:
        error_string.append('Error - lexeme is too long')
        is_ok = False


def getchar():
    global f
    global next_char, char_class
    next_char = f.read(1)

    if next_char != '':
        if next_char.isalpha():
            char_class = letter_type['LETTER']

        elif next_char.isdigit():
            char_class = letter_type['DIGIT']

        else:
            char_class = letter_type['UNKNOWN']
    else:
        char_class = letter_type['EOF']
    #print('charclass : ', char_class)


# 비 공백 문자를 반환할 때까지 getchar를 호출하는 함수
def get_non_blank():
    while next_char.isspace():
        getchar()


# lexical() : 산술식을 위한 단순 어휘 분석기
# 입력 스트림을 분석하여 하나의 lexeme을 찾아낸 뒤, 그것의 token type을 next_token에 저장하고,
# lexeme 문자열을 token_string에 저장하는 함수

def lexical():
    global next_token, lex_len, char_class, ID_cnt, CONST_cnt, OP_cnt, lexeme, token_string, is_ok
    lexeme.clear()
    lex_len = 0
    get_non_blank()

    # 식별자를 파싱한다.
    if char_class == letter_type['LETTER']:
        addchar()
        getchar()
        while char_class == letter_type['LETTER'] or char_class == letter_type['DIGIT']:
            addchar()
            getchar()
        next_token = token_code['IDENT']
        ID_cnt += 1  # ID 개수 1 증가
        token_string = ''.join(s for s in lexeme)
        read_line.append(token_string)

    # 정수 리터럴을 파싱한다.
    elif char_class == letter_type['DIGIT']:
        addchar()
        getchar()
        while char_class == letter_type['DIGIT']:
            addchar()
            getchar()
        next_token = token_code['CONST']
        CONST_cnt += 1
        token_string = ''.join(s for s in lexeme)
        read_line.append(token_string)

    elif char_class == letter_type['UNKNOWN']:
        lookup(next_char)
        getchar()

    elif char_class == letter_type['EOF']:
        next_token = letter_type['EOF']
        #lexeme.append('EOF')

    token_string = ''.join(s for s in lexeme)

    #print('Next token is ', next_token, ', Next lexeme is ', token_string)

    if next_char == '\n' or next_char == '':
        if read_line:
            read_line_string = ''.join(read_line)
            print(read_line_string)
            print('ID:', ID_cnt, '; CONST: ', CONST_cnt, '; OP: ', OP_cnt,';')
            if is_ok:
                print('(OK)')
            else:
                for error in error_string:
                    print(error)
                error_string.clear()
            print()
            ID_cnt = 0
            CONST_cnt = 0
            OP_cnt = 0
            is_ok = True
            read_line.clear()



#Recursive Descent Parsing
# <program> -> <statements>
def program():
    statements()

# <statements> -> <statement>|<statement><semi_colon><statements>


def statements():
    global is_ok
    statement()
    while next_token == token_code['SEMI_COLON']:
        lexical()
        statements()


# <statement> → <ident><assignment_op><expression>
def statement():
    global error_string
    if next_token == token_code['IDENT']:
        ident_result[token_string] = 'Unknown'  #<ident>를 dictionary의 key값으로 추가
        temp_ident_string = token_string    #<ident>에 <expression> 결과를 저장하기 위해 변수 이름을 임시 저장
        lexical()

        if next_token == token_code['ASSIGNMENT_OP']:
            lexical()
            e = expression()
            ident_result[temp_ident_string] = e   #expression에서 계산된 결과를 IDENT에 대입
        else:
            pass
    else:
        pass

#<expression> → <term><term_tail>


def expression():
    t = 0    # term()의 반환값을 받을 변수
    expr_result = 0

    t = term()
    if t != 'Unknown':
        # term_tail의 연산(+/-)을 수행한 결과
        expr_result = term_tail(t)
    else:   #정의되지 않은 변수에 대한 연산
        expr_result = 'Unknown'
        term_tail(t)

    return expr_result


#<term_tail> → <add_op><term><term_tail> | ε
# t: <expr> 내 <term>의 반환값을 전달받는다.


def term_tail(t):
    term_result = t
    global is_ok, error_string, OP_cnt
    t1 = 0  #term()의 반환값을 받을 변수

    if next_token == token_code['ADD_OP']:
        read_line.append(token_string)
        lexical()

        # 중복 연산자(*) 오류 검사
        if next_token == token_code['ADD_OP']:
            error_string.append('(Warning)\"중복 연산자(+) 제거\"')
            OP_cnt -= 1
            is_ok = False
            lexical()

        t1 = term()
        term_tail(t1)

        if t1 != 'Unknown' and t != 'Unknown':
            term_result = t + t1  # 더하기 연산 수행
        else:
            return 'Unknown'

    elif next_token == token_code['SUB_OP']:
        read_line.append(token_string)
        lexical()

        # 중복 연산자(-) 오류 검사
        if next_token == token_code['SUB_OP']:
            error_string.append('(Warning) 중복 연산자(-) 제거')
            OP_cnt -= 1  #중복 연산자는 개수 안셈
            is_ok = False
            lexical()

        t1 = term()
        term_tail(t1)
        if t1 != 'Unknown' or t != 'Unknown':
            term_result = t - t1  # 빼기 연산 수행

    return term_result

#<term> → <factor> <factor_tail>


def term():
    term_result = 0
    fa = 0  # factor()의 반환값을 받을 변수
    fa = factor()
    if fa != 'Unknown':
        term_result = factor_tail(fa)  # factor_tail()에서 factor의 반환값을 받아서 * 또는 / 연산 수행
    else:   # 정의되지 않은 변수
        term_result = 'Unknown'
        factor_tail(fa)

    return term_result

#<factor_tail> → <mult_op><factor><factor_tail> | ε
#fa : <term> 내부의 <factor>의 반환값


def factor_tail(fa):
    global is_ok, error_string, OP_cnt
    factor_tail_result = fa
    if next_token == token_code['MULT_OP']:
        read_line.append(token_string)
        lexical()

        #중복 연산자(*) 오류 검사
        if next_token == token_code['MULT_OP']:
            print('(Warning) 중복 연산자(*) 제거')
            OP_cnt -= 1
            is_ok = False
            lexical()

        fa1 = factor()  # fa1 : factor()의 리턴값
        factor_tail(fa1)

        if fa1 != 'Unknown' and fa != 'Unknown':
            factor_tail_result = fa * fa1  # 곱하기 연산

    elif next_token == token_code['DIV_OP']:
        read_line.append(token_string)
        lexical()

        # 중복 연산자(/) 오류 검사
        if next_token == token_code['DIV_OP']:
            print('(Warning) 중복 연산자(/) 제거')
            OP_cnt -= 1
            is_ok = False
            lexical()

        fa1 = factor()   #f1 : factor()의 리턴값
        factor_tail(fa1)
        if fa1 != 'Unknown' and fa != 'Unknown':
            factor_tail_result = fa / fa1  # 나누기 연산

    return factor_tail_result


#<factor> → <left_paren><expression><right_paren> | <ident> | <const>


def factor():
    global is_ok
    factor_result = 0

    # RHS가 (<expression>)이면, lexical을 호출하여 왼쪽 괄호를 전달하고, expression을 호출하고, 오른쪽 괄호를 검사한다.
    if next_token == token_code['LEFT_PAREN']:
        read_line.append(token_string)
        lexical()
        factor_result = expression()
        if next_token == token_code['RIGHT_PAREN']:
            read_line.append(token_string)
            lexical()
        else:
            error_string.append('(Warning): ) 누락')
            is_ok = False


    # 어느 RHS를 파싱할 것인지 결정한다.
    elif next_token == token_code['IDENT']:

        #Error : 선언된 적이 없는 식별자라면 오류메세지 출력
        if token_string not in ident_result:
            s = ('(Error)\"정의되지 않은 변수 (', token_string, ')가 참조됨\"')
            error_string.append(s)
            is_ok = False

            #dictionary에 key값으로 추가
            ident_result[token_string] = 'Unknown'
            factor_result = 'Unknown'

        else:
            #factor()의 반환값으로 변수의 값을 준다.
            factor_result = ident_result[token_string]
            # 다음 토큰을 가져온다

        lexical()

    elif next_token == token_code['CONST']:
        factor_result = int(token_string)
        lexical()

    #왼쪽 괄호, ident, 또는 상수가 아니다.
    else:
        error_string.append('(Error) 왼쪽괄호, ident, 또는 상수 필요')
        is_ok = False

    return factor_result



# 입력 데이터 파일을 열고 그 내용을 처리
#print('파일명을 입력하세요')
args = sys.argv[1:]
file_name = ""
for i in args:
    file_name += i
f = open(file_name, 'r')

getchar()
while next_token != letter_type['EOF']:
    lexical()
    program()

sorted_dict = sorted(ident_result.items())
print('Result ==> ', end='')
for i in range(len(sorted_dict)):
    print(sorted_dict[i][0], ':', sorted_dict[i][1], end='')
    if i != (len(sorted_dict)-1):
        print('; ', end='')
f.close()

'프밍언' 카테고리의 다른 글

python에서 파일의 끝을 어떻게 알 수 있을까? (EOF)  (1) 2022.10.29