一個編譯器的前端通常包括詞法分析器和語法分析器。在分析過程中,文本輸入詞法分析器,根據詞法規則解析出詞法單元。詞法單元作為輸入,進入到語法分析器。語法分析器根據語法規則對詞法單元序列進行分析(自頂而下,自底向上),確認詞法單元序列符合語言的語法規則。在分析的同時形成翻譯方案,由源語言(輸入文本的語言)轉換成目標語言(通常是一種中間語言)的過程。
這篇文章是《編譯原理》(龍書)第二版第五章練習5.5的一個作業。
將 if (C) S1 else S2 實現為一個語法分析器。
這個作業基本涵蓋了編譯器前端的最主要的技術,又很好的簡化了詞法和語法種類帶來的復雜性。因此值得作為對編譯技術的研究案例。
本文逐步回顧編譯器前端的關鍵技術,并在過程中用python實現。這個句型是C語言的,翻譯的目標語言是一種接近匯編語言模式的偽代碼。
詞法分析器
詞法分析器的主要作用是從文本中分析并提取語素(lexeme)并生成詞法單元(token)。提供給語法分析器作為語法分析的基本單位。
詞法分析器一般以正則模式作為區分,來識別不同的詞法單元。
需要識別的詞法
在這里,詞法分析器需要識別的模式有:
- 空白字符
包括空格,制表符\t, 和換行\n。將文本分隔為不同的單元,它們在詞法分析過程的第一步被剔除,不進入語法分析,我們在這里以空格為基本模式。 - 分隔符
分隔符一般指語法中有語法作用的標點符號,它本身是一個詞法單元,它還將連在一起的字符分開成不同的詞法單元。這里指( ) - 關鍵字
if 和 else
在這個語句中,C,S1 和 S2實際上是一個文法單元的非終結符號(None terminal),分別是Condition(條件表達式)和Statement(語句)。
關于表達式的定義不在此贅述。一般可以求值的,或者說可以賦值給一個變量的就作為一個表達式(x = expr)。語句主要產生副作用。為求簡便,我們在這里先將其實現為一個詞法值,當詞法器識別 'C' ,就認為它是一個語法單元的條件表達式。'S1', 'S2' 分別作為兩個不同的語句。我們將在實現的過程中根據需要來決定是否擴展它。在編譯過程中,C,S1,S2作為語法單元,實際上是由語法分析器在語法分析的過程中展開或者規約得到的語法樹的節點,正常詞法分析器輸出的全部是終結符號,在這里暫時替代一下,這一點要注意不要混淆。
因此, 我們需要識別的詞法有:
- 空格
- 分隔符( ),返回一個delimiter類型的詞法單元,值為它的字符。
- 關鍵字 if 和 else,分別返回一個keyword類型的詞法單元,值為字符串本身。
- 暫時替換的語法符號(非終結符)C,S1,S2。返回一個非終結符,值為它的字符,我們會附加一些屬性給這個符號。
詞法分析器的作用
詞法分析器的重要作用是識別正則模式,一般命名變量,或者識別數字,或者字符串時,都通過正則引擎來識別。在這里我們需要識別的模式比較簡單,全部是確定字符。
正則引擎的實現原理是有限自動機(finite-automata),有兩種類型,NFA和DFA,非確定有限自動機和確定型有限自動機。可以根據算法機械構造NFA,然后再根據算法機械轉換為DFA。
https://github.com/dannyvi/simple-regex 實現了一個簡單的NFA的正則引擎。我們將用這個引擎來識別詞法單元。它可以用正則模式生成一個有限自動機,并識別相應的字符串是否匹配這個模式。
python的re模塊文檔中給出了一個詞法器更完整的例子。Writing a Tokenizer
下面是詞法器代碼:
from regex import regex_compile
space = regex_compile(r" ")
delimiter = regex_compile(r"\(|\)")
keyword = regex_compile(r"(if)|(else)")
node = regex_compile(r"(S1)|(S2)|C")
class Token:
def __init__(self, typ, value):
self.typ = typ
self.value = value
def __repr__(self):
return '<token: {}.{} >'.format(self.typ, self.value)
def tokenizer(input_stream):
def split(input_stream):
symbol = ''
for i in input_stream:
if space.match(i):
if symbol:
yield symbol
symbol = ''
elif delimiter.match(i):
if symbol:
yield symbol
symbol = ''
yield i
else:
symbol += i
if symbol:
yield symbol
def token(value):
if delimiter.match(value): return Token(value, value)
elif keyword.match(value): return Token(value, value)
elif node.match(value): return Token(value, value)
else: raise RuntimeError('"{}" unexpected symbol'.format(value))
l = split(input_stream)
return map(token, l)
這個詞法器有兩個pass,首先將代碼用空白符和分隔符分開成為一個列表,然后對這個列表分析返回一個Token的map迭代對象。
from tokenizer import tokenizer
s = tokenizer("if ( C ) S1 else S2 ")
list(s)
Out[4]:
[<token: if.if >,
<token: (.( >,
<token: C.C >,
<token: ).) >,
<token: S1.S1 >,
<token: else.else >,
<token: S2.S2 >]
語法分析和翻譯
一個語法分析器主要的作用是按照給定的文法將詞法單元序列規約為語法樹,并將這個語法樹翻譯為目標語言。一般在規約時就可以展開翻譯,所以語法分析和翻譯同時進行。
標準 LR(1)分析器 (Canonical LR(1))
其他的分析器還有LL(1),SLR(LR 0),LALR(LR 1)。不過我們在這里就實現 Canonical LR(1)。
LR分析器是通過移入規約技術來實現分析過程的,所以分成兩個階段,首先構建語法分析表,然后按分析表進行規約和翻譯。
實際上語法分析相當于構建一個有限狀態機,由開始符號進入,并對不同的輸入進行狀態轉化,當輸入完成后,如果狀態處于接受狀態,就表示分析成功了。
語法分析表
通過算法將上下文無關文法G轉化為語法分析動作。我們可以先將其轉化為一個分析表,然后再對照分析表,來編寫分析和翻譯動作。
分析表形狀大概如下:
ACTION
狀態\輸入 | a | b | c |
---|---|---|---|
0 | s2 | e1 | s1 |
1 | r2 | r1 | acc |
2 | e3 | s1 | s2 |
GOTO
狀態\非終結符 | S | N |
---|---|---|
0 | 1 | 2 |
1 | 0 | 1 |
2 | 1 | 0 |
ACTION 負責對輸入的終結符號選擇合適的動作(移入,規約,報錯,接受), GOTO負責對規約后的產生式進行狀態轉化。
語法分析在一個分析狀態棧(或者分析狀態序列)上操作。如果文本屬于這個語法的話,移入的狀態最終會被規約為開始符號。
語法分析表G算法如下:
輸入: 文法G
輸出:分析表
方法:
構造G的LR(1)項集族C={I0, I1,...In}。
語法分析器的狀態i由Ii構造得到,狀態i按照下面規則構建:
- 如果[A -> α?aβ, b],并且GOTO(Ii, a) = ij, 那么將ACTION[i, a]設置為移入j("sj")。a必須是終結符。
- 如果[A -> α?, a] 在 Ii中,且A不是開始符號,那么將ACTION[i, a]設置為規約 A -> α ( "rn",n是產生式的編號 )。
- 如果[S' -> S, $] 在Ii中,那么將ACTION[i, $]設置為接受("acc")。
狀態i對于各個非終結符號A的goto按照下面規則構造:
如果GOTO(Ii, A) = Ij, 那么 GOTO[i, A] = j所有沒按照2, 3條規則定義的分析表條目都設置為錯誤。
語法分析表的初始狀態是由 [S' -> ?S, $] 的項集構造得到的狀態。
所以,項集族,狀態和輸入符號決定了這個表的構造。
輸入符號就是文法里面的終結符號(Terminal)和非終結符號(None Terminal)。
狀態就簡單理解為項集族里面的項集閉包(Item-sets Closure)的編號。
項集族(Item-sets Closure Collections)
項集族是由項集閉包組成的,它是構造出來的一個文法的狀態機的全部狀態。
項集閉包(Item-sets Closure)
一個項集閉包就是一個狀態,大概解釋就是在這個狀態上進行輸入轉換可能用到的全部產生式組成的項。
項(Item)
標準 LR(1)分析器的項是由產生式,當前位置和輸入字符構成的。
class Item(object):
"""The Canonical LR(1) Item definition.
:param symbol: str, the left part of production.
:param body: str, the right part of production.
:param dot: int, current position in the item.
:param follow: str, possible input for the current configuration.
"""
def __init__(self, symbol, body, dot, follow):
self.symbol = symbol
self.body = body
self.pos = dot
self.follow = follow
def __str__(self):
p = list(self.body)
p.insert(self.pos, '◆')
pr = ' '.join(p)
return "[{}] {} -> {}".format( self.follow, self.symbol, pr)
def __repr__(self):
return "<Item:{} >\n".format(self.__str__())
def __eq__(self, other):
if isinstance(other, Item):
return ((self.symbol == other.symbol) and
(self.body == other.body) and
(self.pos == other.pos) and
(self.follow == other.follow))
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(self.__str__())
實現了項,和相關的操作。因為它在閉包中作為集合元素,所以實現 __hash__
, __eq__
和 __ne__
方法,這樣集合就可以迭代。
from regex.parsing_table import Item
i1 = Item('S', 'b|c', 2, 'k')
i1
Out[4]: <Item: S -> b|.c k >
項集閉包的實現
class定義:
class Closure(object):
def __init__(self, sets: Set[Item], label: int = None):
self.label = label
self.sets = sets
self.goto = dict() # type: dict[str, int]
def __len__(self):
return len(self.sets)
def __iter__(self):
return self.sets.__iter__()
def __str__(self):
return "\n".join([i.__str__() for i in self.sets])
def __repr__(self):
return "<Closure>:{}\n{}\n</Closure>\n".format(self.label,
self.__str__())
def __eq__(self, other):
return self.sets == other.sets
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(self.__str__())
def __contains__(self, item):
return item in self.sets
sets
包含所有的項,label
是這個閉包的狀態名,這里規定為一個數字,goto
是一個字典,用于記錄這個狀態在不同輸入的轉換目標狀態。
__contains__
用于計算項(Item)是否包含在這個閉包中。
因為閉包還會包含在族(collection)中作為一個元素,所以也實現 __eq__
和 __hash__
,這樣集合就可以作為一個迭代器 。
項集閉包的算法:
對于一個項集(Item-sets) I ,計算它的閉包 CLOSURE(I) 。
- 將 I 中的各個項加入到閉包當中。
- 閉包中的每個項, 如 [A -> α?Bβ, a],對文法G中的每個非終結符B的產生式 B -> γ,對FIRST(βa)中的每個終結符 b, 將 [B -> ?γ, b] 加入閉包當中。
這里的?B是推理當前位置后面可能出現的規約(B的產生式),以及B規約結束之后,能夠在之后出現的終結符。
def get_closure(cl: Closure, label: int) -> Closure:
"""get all Item of a Closure from given Items, by adding implied Items.
The implied Items are the productions of the None terminals after the
current position, which put a dot on the head."""
def get_nterm(item):
pos, prod = (item.pos, item.body)
if pos < len(prod):
symbol = prod[pos]
if isnterm(symbol):
return symbol
return None
item_set = set()
q = queue.Queue()
for i in cl.sets:
item_set.add(i)
q.put(i)
while not q.empty():
item = q.get()
symbol = get_nterm(item)
if symbol:
products = [i for i in grammar if i[0] == symbol]
suffix = item.body[item.pos+1:] + item.follow
termins = firsts(suffix)
for product in products:
for terminal in termins:
new_item = Item(symbol, product[1], 0, terminal)
if new_item not in item_set:
item_set.add(new_item)
q.put(new_item)
c = Closure(item_set, label)
return c
我們需要知道關于grammar
和firsts
的定義。
grammar = [ ("stmt", ("if", "(", "C", ")", "S", "else", "S")),
...
]
grammar 在這里定義為一個產生式列表。產生式是由一個頭部和體合成的元組,產生式的體本身也是一個元組。
firsts是為了計算一個產生式的體中可能出現的第一個終結符號,我們在規約時需要知道可能出現的下一個終結符號和應該使用哪一個式子來進行規約。這樣我們在推導的時候就能知道應該進入的狀態。
對于一個終結符號或者非終結符號, 它的first(X)算法如下:
- X是終結符號: first(X) = X
- X是非終結符號:查找產生式 X -> Y1Y2...Yk, 如果Y1是非終結符且含有空產生式,那么 first(X) = first(Y1) ∪ firsts(Y2...Yk), 否則就等于 first(Y1),意思就是空產生式會形成多種可能性,確定只有一個first的符號,后面的符號就排除掉了。如果Y是終結符號,就不用下推。如果Y是非終結符號,就考慮它是否產生空產生式,來決定考察下一個符號。
- X是EPSILON: firsts(X) += EPSILON
具體的實現是這樣的:
def isnterm(symbol):
return symbol in n_terminals
def isterm(symbol):
return symbol in terminals
def produce_epsilon(none_terminal):
return 'EPSILON' in [i[1] for i in grammar if i[0] == none_terminal]
# def is_start_symbol(symbol):
# return symbol == "startsup"
def first(symbol):
"""Return the first terminal sets that may occur in the Symbol."""
first_sets = set()
if isterm(symbol):
return set(symbol)
elif produce_epsilon(symbol):
first_sets = first_sets.union('EPSILON')
elif isnterm(symbol):
for i in grammar:
if i[0] == symbol:
body = i[1]
epsilons = True
current = 0
while epsilons is True and current < len(body):
if body[current] != symbol:
first_sets = first_sets.union(first(body[current]))
if not produce_epsilon(body[current]):
epsilons = False
current += 1
return first_sets
def firsts(suffix):
if len(suffix) == 1:
return first(suffix[0])
else:
if not produce_epsilon(suffix[0]):
return first(suffix[0])
else:
return first(suffix[0]).union(firsts(suffix[1:]))
isnterm
, isterm
分別判斷是否終結符號。
produce_epsilon
產生式的特殊形式,判斷是否一個空產生式。這里約定空產生式 N -> EPSILON
的體由 EPSILON
定義。
is_start_symbol
判斷是否開始符號。
first
計算一個終結符號或者非終結符號可能出現的第一個終結符。
firsts
計算一個句型(多個符號連在一起)可能出現的第一個終結符。
為求簡單我們在這里將 grammar
, terminals
, n_terminals
定義為列表。以后再實現如何將文法規則解析為python對象,和判斷是否終結符號的算法。
grammar = [("startsup", ("start")),
("start", ("stmt")),
("stmt", ("if", "(", "C", ")", "S1", "else", "S2")),
]
terminals = ("if", "(", "C", ")", "S", "else", "S2", "$")
n_terminals = ("startsup", "start", "stmt")
實際上 C, S1, S2
都應該是非終結符號。未來會替換為產生式,由編譯器規約它們的體來得到節點。
這個文法是對原文法進行增廣后的增廣文法,由原來的產生式
- start -> stmt
- stmt -> if ( c ) S1 else S2
增加一個開始符號start'和在尾部增加一個結束輸入$ , 對原來的詞法序列在尾部增加一個終結字符 $, 這樣當掃描到最后一個字符為 $, 就進入接受狀態。
- startsup -> start
這樣我們就實現了項集閉包的算法。
推導項集族
項集族C的算法:
- 在C中,初始添加 [startsup -> ◆ start, $] 的項 【由Item('startsup', ('start',), 0, '$')構造】,并計算閉包【get_closure(item)】為開始狀態。
- 在C中,對于每一個項集閉包I, 對于每一個文法符號X,可以是終結符號或者非終結符號,如果 goto(I, X) 非空且不在C中, 就加入到C中。
通過將每一個輸入可能得到的狀態加入到項集族,來完成構建。
這里,goto(I,X)指的是狀態 I 在輸入X下轉換到的狀態。(一個項集閉包)它的算法:
I中所有存在的項 [A -> α?Xβ, a], 將 [A -> αX?β, a]加入到集合,并計算它的閉包。
實現如下:
def goto(clos: Closure, letter: str) -> Closure:
"""a closure that could get from the current closure by input a letter.
:param clos: the current closure.
:param letter: the input letter.
:return: Closure.
"""
item_set = set()
for item in clos.sets:
dot, prod = (item.pos, item.body)
if dot < len(prod) and prod[dot] == letter:
new_item = Item(item.symbol,
item.body,
item.pos + 1,
item.follow)
item_set.add(new_item)
c = Closure(item_set)
return get_closure(c, label=None)
def closure_groups():
def find_label(closure, group):
for i in group:
if closure == i:
return i.label
return None
group = set()
all_symbols = terminals + n_terminals
label = 0
start_item = Item('startsup', 'start', 0, '$')
start = get_closure(Closure({start_item}), label)
q = queue.Queue()
q.put(start)
group.add(start)
while not q.empty():
c = q.get()
for literal in all_symbols: # terminals + n_terminals:
go_clos = goto(c, literal)
if go_clos:
if go_clos not in group:
label += 1
go_clos.label = label
group.add(go_clos)
q.put(go_clos)
c.goto[literal] = label
else:
go_label = find_label(go_clos, group)
if go_label:
c.goto[literal] = go_label
return group
得到了整個文法的項集族之后,我們就可以根據之前給出的構建出語法分析表了。這里將終結符號的ACTION和非終結符號的GOTO拼接在一起。并且沒有???♂?錯誤處理,這里默認將錯誤動作全部初始化為'.'。
def get_states_map(closure_group):
def get_state_map(closure):
""" table row like all_symbols list state maps."""
all_symbols = terminals + n_terminals
row = ["." for i in all_symbols]
# None terminals GOTO action and Terminals shift action.
for input, goto_label in closure.goto.items():
row_pos = all_symbols.index(input)
for item in closure:
if item.pos < len(item.body): # shape like [A -> ?.aβ b]
if item.body[item.pos] == input:
# None terminals GOTO state
if input in n_terminals:
row[row_pos] = str(goto_label)
# Terminals action shift state
elif input in terminals:
row[row_pos] = "s" + str(goto_label)
# Terminals reduce action. shape like [A -> ?. a]
for row_pos, input in enumerate(all_symbols):
for item in closure:
if item.pos == len(item.body) and \
item.follow == input and \
item.symbol != 'startsup':
# 'R' should be replaced with start_symbol
#if item.follow != '*':
production_num = grammar.index([item.symbol, item.body])
row[row_pos] = 'r' + str(production_num)
#else:
# pass
# accept condition 'startsup -> start. , $'
acc_item = Item('startsup', 'start', 1, '$')
if acc_item in closure:
input = '$'
row_pos = all_symbols.index('$')
row[row_pos] = '$'
return row
state_map = [None for i in range(len(closure_group))]
for closure in closure_group:
row = get_state_map(closure)
state_map[closure.label] = row
return state_map
def generate_syntax_table():
g = closure_groups()
state_map = get_states_map(g)
return state_map
這樣就得到了語法分析表。
全部代碼在這里:
import queue
from typing import Set
grammar = [("startsup", ("start", )),
("start", ("stmt", )),
("stmt", ("if", "(", "C", ")", "S1", "else", "S2")),
]
terminals = ("if", "(", "C", ")", "S1", "else", "S2", '$')
n_terminals = ("startsup", "start", "stmt")
all_symbols = terminals + n_terminals
class Item(object):
"""The Canonical LR(1) Item definition.
:param symbol: str, the left part of production.
:param body: str, the right part of production.
:param dot: int, current position in the item.
:param follow: str, possible input for the current configuration.
"""
def __init__(self, symbol, body, dot, follow):
self.symbol = symbol
self.body = body
self.pos = dot
self.follow = follow
def __str__(self):
p = list(self.body)
p.insert(self.pos, '◆')
pr = ' '.join(p)
return "[{}] {} -> {}".format( self.follow, self.symbol, pr)
def __repr__(self):
return "<Item:{} >\n".format(self.__str__())
def __eq__(self, other):
if isinstance(other, Item):
return ((self.symbol == other.symbol) and
(self.body == other.body) and
(self.pos == other.pos) and
(self.follow == other.follow))
else:
return False
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(self.__str__())
class Closure(object):
def __init__(self, sets: Set[Item], label: int = None):
self.label = label
self.sets = sets
self.goto = dict() # type: dict[str, int]
def __len__(self):
return len(self.sets)
def __iter__(self):
return self.sets.__iter__()
def __str__(self):
return "\n".join([i.__str__() for i in self.sets])
def __repr__(self):
return "<Closure>:{}\n{}\n</Closure>\n".format(self.label,
self.__str__())
def __eq__(self, other):
return self.sets == other.sets
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(self.__str__())
def __contains__(self, item):
return item in self.sets
def isnterm(symbol):
return symbol in n_terminals
def isterm(symbol):
return symbol in terminals
def produce_epsilon(none_terminal):
return 'EPSILON' in [i[1] for i in grammar if i[0] == none_terminal]
def first(symbol):
"""Return the first terminal sets that may occur in the Symbol."""
first_sets = set()
if isterm(symbol):
return set(symbol)
elif produce_epsilon(symbol):
first_sets = first_sets.union('EPSILON')
elif isnterm(symbol):
for i in grammar:
if i[0] == symbol:
body = i[1]
epsilons = True
current = 0
while epsilons is True and current < len(body):
if body[current] != symbol:
first_sets = first_sets.union(first(body[current]))
if not produce_epsilon(body[current]):
epsilons = False
current += 1
return first_sets
def firsts(suffix):
if len(suffix) == 1:
return first(suffix[0])
else:
if not produce_epsilon(suffix[0]):
return first(suffix[0])
else:
return first(suffix[0]).union(firsts(suffix[1:]))
def get_closure(cl: Closure, label: int) -> Closure:
"""get all Item of a Closure from given Items, by adding implied Items.
The implied Items are the productions of the None terminals after the
current position, which put a dot on the head."""
def get_nterm(item):
pos, prod = (item.pos, item.body)
if pos < len(prod):
symbol = prod[pos]
if isnterm(symbol):
return symbol
return None
item_set = set()
q = queue.Queue()
for i in cl.sets:
item_set.add(i)
q.put(i)
while not q.empty():
item = q.get()
symbol = get_nterm(item)
if symbol:
products = [i for i in grammar if i[0] == symbol]
suffix = item.body[item.pos+1:] + tuple(item.follow)
termins = firsts(suffix)
for product in products:
for terminal in termins:
new_item = Item(symbol, product[1], 0, terminal)
if new_item not in item_set:
item_set.add(new_item)
q.put(new_item)
c = Closure(item_set, label)
return c
def goto(clos: Closure, letter: str) -> Closure:
"""a closure that could get from the current closure by input a letter.
:param clos: the current closure.
:param letter: the input letter.
:return: Closure.
"""
item_set = set()
for item in clos.sets:
dot, prod = (item.pos, item.body)
if dot < len(prod) and prod[dot] == letter:
new_item = Item(item.symbol,
item.body,
item.pos + 1,
item.follow)
item_set.add(new_item)
c = Closure(item_set)
return get_closure(c, label=None)
def closure_groups():
def find_label(closure, group):
for i in group:
if closure == i:
return i.label
return None
group = set()
label = 0
start_item = Item('startsup', ('start',), 0, '$')
start = get_closure(Closure({start_item}), label)
q = queue.Queue()
q.put(start)
group.add(start)
while not q.empty():
c = q.get()
for literal in all_symbols: # terminals + n_terminals:
go_clos = goto(c, literal)
if go_clos:
if go_clos not in group:
label += 1
go_clos.label = label
q.put(go_clos)
group.add(go_clos)
c.goto[literal] = label
# print('add closure', go_clos)
else:
go_label = find_label(go_clos, group)
if go_label:
c.goto[literal] = go_label
return group
def get_states_map(closure_group):
def get_state_map(closure):
""" table row like all_symbols list state maps."""
row = ["." for i in all_symbols]
# None terminals GOTO action and Terminals shift action.
for input, goto_label in closure.goto.items():
row_pos = all_symbols.index(input)
for item in closure:
if item.pos < len(item.body): # shape like [A -> ?.aβ b]
if item.body[item.pos] == input:
# None terminals GOTO state
if input in n_terminals:
row[row_pos] = str(goto_label)
# Terminals action shift state
elif input in terminals:
row[row_pos] = "s" + str(goto_label)
# Terminals reduce action. shape like [A -> ?. a]
for row_pos, input in enumerate(all_symbols):
for item in closure:
if item.pos == len(item.body) and \
item.follow == input and \
item.symbol != 'startsup':
# 'R' should be replaced with start_symbol
#if item.follow != '*':
production_num = grammar.index((item.symbol, item.body))
row[row_pos] = 'r' + str(production_num)
#else:
# pass
# accept condition 'startsup -> start. , $'
acc_item = Item('startsup', ('start',), 1, '$')
if acc_item in closure:
input = '$'
row_pos = all_symbols.index('$')
row[row_pos] = '$'
return row
state_map = [None for i in range(len(closure_group))]
for closure in closure_group:
row = get_state_map(closure)
state_map[closure.label] = row
return state_map
def generate_syntax_table():
g = closure_groups()
state_map = get_states_map(g)
return state_map
看下結果:
from parser import *
n = generate_syntax_table()
n
state if ( C ) S1 else S2 $ startsup start stmt
0 s1 . . . . . . . . 2 3
1 . s4 . . . . . . . . .
2 . . . . . . . $ . . .
3 . . . . . . . r1 . . .
4 . . s5 . . . . . . . .
5 . . . s6 . . . . . . .
6 . . . . s7 . . . . . .
7 . . . . . s8 . . . . .
8 . . . . . . s9 . . . .
9 . . . . . . . r2 . . .
語法分析和翻譯
語法分析
語法分析器在一個狀態棧上工作,這個棧存儲了移入的狀態,它代表了已經輸入,尚未規約的詞法單元。語法分析器對token_stream(經過詞法器解析后的代碼)的詞法單元逐個進行4種操作。分析器在分析開始前移入狀態0。分析器以狀態棧上的最后一個狀態(棧頂)為當前狀態,并且根據輸入字符查分析表,來獲得當前操作。
四種分析操作:
移入,將目標狀態移入到狀態棧頂。進入下一個詞法單元。
規約,規約目標產生式,當前詞法單元不變,繼續查表進行下一個操作,直到當前詞法單狀態元被移入。
接受,在含有增廣文法開始符號產生式的項 [startsup -> start◆, '$'],如果當前輸入為 '$', 分析成功進入接受狀態,并結束。
錯誤, 目前我們忽略錯誤處理。
代碼如下:
class SDT:
def __init__(self):
self.syntax_table = generate_syntax_table()
self.state_stack = [0]
self.accept = False
def get_action(self, state, literal):
return self.syntax_table[state][all_symbols.index(literal)]
def ahead(self, token):
action = self.get_action(self.state_stack[-1], token.typ)
# shift action push a current state into state_stack
if action[0] == 's':
current_state = int(action[1:])
self.state_stack.append(current_state)
elif action[0] == '$':
self.accept = True # success
# reduce action reduct a production and push
elif action[0] == 'r':
# get the production in grammar
number = int(action[1:])
production = grammar[number]
head, body = production
# pop the states of production body
for _ in body:
self.state_stack.pop()
# push the state of head GOTO(I,X)
state = self.get_action(self.state_stack[-1], head)
self.state_stack.append(int(state))
# reduce actions does not consume a token,
# only when shifting, a token was consume and passed
self.ahead(token)
else:
raise SyntaxError(f"Not a correct token '{token.__str__()}'.")
def parse(self, token_stream):
while True:
try:
token = next(token_stream)
self.ahead(token)
except StopIteration:
# patch "$" in the end of token stream
# to match the augmented grammar
self.ahead(Token("$", "$"))
break
它接受一個詞法單元流,并且分析,如果分析成功,accept就設置為True
from tokenizer import tokenizer
token_stream = tokenizer("if (C) S1 else S2")
sdt = SDT()
sdt.parse(token_stream)
sdt.accept
Out[8]: True
翻譯方案
翻譯方案一般插入到分析過程當中。
每個非終結符號都會形成一個函數,我們這里暫時在代碼中預定義好非終結符號的翻譯函數。
因為LR分析器是從右到左規約,而在移入的時候并不判斷目前在哪個產生式的內部,因此翻譯方案用后綴翻譯來實現,就是在規約的時候翻譯。產生式頭部的名稱作為函數名,規約的內容作為參數來進行調用,向上返回函數的結果。
建立一個參數棧:
self.arg_stack = []
token在移入的時候作為值移入到棧中。
self.push_arg(token)
規約時,將值移出,作為規約函數的參數。返回的結果,就是非終結符號的值,移入到棧中。
# translations
args = []
for _ in body:
arg = self.arg_stack.pop()
args.insert(0, arg)
translation = globals().get(head).__call__(*args)
self.arg_stack.append(translation)
然而后綴翻譯方案只適用于綜合屬性(S屬性),對于繼承屬性并不適用。比如 stmt -> if (C) S1 else S2
大致會形成如下翻譯方案:
C.code
S1.scode
goto stmt.next
label L1
S2.code
其中,stmt.next 由外部傳入,是stmt作為產生式的體時的繼承屬性,LL分析器通過預測分析表已經獲取了頭部,所以可以預先分配一個值。這里由于分析器是規約方式的,因此尚不知道繼承屬性的值。一般采取用一個空產生式來替代翻譯內容并先生成繼承屬性的方法來解決,不過會帶來語法分析時的復雜性。
我們在這里采用延遲調用的方法,就是 stmt
規約完成后并不直接返回翻譯的字符串值(因為還有一些屬性不知道), 而是返回一個函數,通過將未知的內容包裝成參數向上返回,在進行規約 start -> stmt
時, 再將start
生成的必要值作為參數來調用 stmt
規約的返回值,就可以獲得正確的翻譯方案了。
def stmt(IF, LPAR, c, RPAR, s1, ELSE, s2):
def call(next_label):
L1 = get_label()
C_code = c.code(f_cond=L1)
S1_code = s1.code()
S2_code = s2.code()
inter_code = """
{}
{}
goto {}
label {}
{}""".format(C_code, S1_code, next_label, L1, S2_code)
return inter_code
return call
添加對結束狀態的處理,和一些其他必要動作。這樣,分析和翻譯方案就變成了:
class SDT:
def __init__(self):
self.syntax_table = generate_syntax_table()
self.state_stack = [0]
self.arg_stack = []
self.accept = False
self.translation = ''
def get_action(self, state, literal):
return self.syntax_table[state][all_symbols.index(literal)]
def ahead(self, token):
action = self.get_action(self.state_stack[-1], token.typ)
# shift action push a current state into state_stack
if action[0] == 's':
current_state = int(action[1:])
self.state_stack.append(current_state)
self.push_arg(token)
elif action[0] == '$':
self.translation = startsup(self.arg_stack[-1])
self.accept = True # success
print('SUCCESS')
print(self.translation)
# reduce action reduct a production and push
elif action[0] == 'r':
# get the production in grammar
number = int(action[1:])
production = grammar[number]
head, body = production
# pop the states of production body
for _ in body:
self.state_stack.pop()
# push the state of head GOTO(I,X)
state = self.get_action(self.state_stack[-1], head)
self.state_stack.append(int(state))
# translations
args = []
for _ in body:
arg = self.arg_stack.pop()
args.insert(0, arg)
translation = globals().get(head).__call__(*args)
self.arg_stack.append(translation)
# reduce actions does not consume a token,
# only when shifting, a token was consume and passed
self.ahead(token)
else:
raise SyntaxError(f"Not a correct token '{token.__str__()}'.")
def parse(self, token_stream):
while True:
try:
token = next(token_stream)
self.ahead(token)
except StopIteration:
# patch "$" in the end of token stream
# to match the augmented grammar
self.ahead(Token("$", "$"))
break
def push_arg(self, token):
if token.typ == 'C':
token.code = lambda f_cond: 'Ccode Cfalse = {}'.format(f_cond)
elif token.typ == 'S1':
token.code = lambda : 'S1code'
elif token.typ == 'S2':
token.code = lambda : 'S2code'
self.arg_stack.append(token)
all_labels = []
def get_label():
n = 'L' + str(len(all_labels))
all_labels.append(n)
return n
def stmt(IF, LPAR, c, RPAR, s1, ELSE, s2):
def call(next_label):
L1 = get_label()
C_code = c.code(f_cond=L1)
S1_code = s1.code()
S2_code = s2.code()
inter_code = """
{}
{}
goto {}
label {}
{}""".format(C_code, S1_code, next_label, L1, S2_code)
return inter_code
return call
def start(stmt):
def call():
L = get_label()
return stmt(L)
return call
def startsup(f):
return f()
運行一下,
from parser import SDT
from tokenizer import tokenizer
token_stream = tokenizer('if (C) S1 else S2')
sdt = SDT()
sdt.parse(token_stream)
成功翻譯:
Ccode Cfalse = L1
S1code
goto L0
label L1
S2code
這是個簡陋的過程,但是核心功能完整,我們可以在之后的過程中,逐步完善它。
通常,詞法規則和語法規則是由單獨的文件定義的。所以需要對詞法規則和語法規則進行解析的構件,來完成從源文本到python對象的轉換。翻譯方案通常嵌入到語法規則中。
錯誤處理可以在適當的情況引入到編譯過程當中。
另外,二義性文法,空產生式等情況的轉換在語法添加的過程當中會浮現。
當然還有為語法規則添加基本的語句,使之逐漸成為一個完善的編譯前端。
不論如何,我們已經完成了編譯前端從源語言到目標語言的全部流程,是一個成功的開始。