| @@ -57,6 +57,58 @@ def update_set(set1, set2): | |||
| set1 |= set2 | |||
| return set1 != copy | |||
| def calculate_sets(rules): | |||
| """Calculate FOLLOW sets. | |||
| Adapted from: http://lara.epfl.ch/w/cc09:algorithm_for_first_and_follow_sets""" | |||
| symbols = {sym for rule in rules for sym in rule.expansion} | {rule.origin for rule in rules} | |||
| symbols.add('$root') # what about other unused rules? | |||
| # foreach grammar rule X ::= Y(1) ... Y(k) | |||
| # if k=0 or {Y(1),...,Y(k)} subset of NULLABLE then | |||
| # NULLABLE = NULLABLE union {X} | |||
| # for i = 1 to k | |||
| # if i=1 or {Y(1),...,Y(i-1)} subset of NULLABLE then | |||
| # FIRST(X) = FIRST(X) union FIRST(Y(i)) | |||
| # for j = i+1 to k | |||
| # if i=k or {Y(i+1),...Y(k)} subset of NULLABLE then | |||
| # FOLLOW(Y(i)) = FOLLOW(Y(i)) union FOLLOW(X) | |||
| # if i+1=j or {Y(i+1),...,Y(j-1)} subset of NULLABLE then | |||
| # FOLLOW(Y(i)) = FOLLOW(Y(i)) union FIRST(Y(j)) | |||
| # until none of NULLABLE,FIRST,FOLLOW changed in last iteration | |||
| NULLABLE = set() | |||
| FIRST = {} | |||
| FOLLOW = {} | |||
| for sym in symbols: | |||
| FIRST[sym]={sym} if is_terminal(sym) else set() | |||
| FOLLOW[sym]=set() | |||
| changed = True | |||
| while changed: | |||
| changed = False | |||
| for rule in rules: | |||
| if set(rule.expansion) <= NULLABLE: | |||
| if update_set(NULLABLE, {rule.origin}): | |||
| changed = True | |||
| for i, sym in enumerate(rule.expansion): | |||
| if set(rule.expansion[:i]) <= NULLABLE: | |||
| if update_set(FIRST[rule.origin], FIRST[sym]): | |||
| changed = True | |||
| if i==len(rule.expansion)-1 or set(rule.expansion[i:]) <= NULLABLE: | |||
| if update_set(FOLLOW[sym], FOLLOW[rule.origin]): | |||
| changed = True | |||
| for j in range(i+1, len(rule.expansion)): | |||
| if set(rule.expansion[i+1:j]) <= NULLABLE: | |||
| if update_set(FOLLOW[sym], FIRST[rule.expansion[j]]): | |||
| changed = True | |||
| return FIRST, FOLLOW, NULLABLE | |||
| class GrammarAnalyzer(object): | |||
| def __init__(self, rule_tuples, start_symbol, debug=False): | |||
| self.start_symbol = start_symbol | |||
| @@ -79,6 +131,8 @@ class GrammarAnalyzer(object): | |||
| self.init_state = self.expand_rule(start_symbol) | |||
| self.FIRST, self.FOLLOW, self.NULLABLE = calculate_sets(self.rules) | |||
| def expand_rule(self, rule): | |||
| "Returns all init_ptrs accessible by rule (recursive)" | |||
| init_ptrs = set() | |||
| @@ -104,59 +158,7 @@ class GrammarAnalyzer(object): | |||
| else: | |||
| return {rp.next for rp in self.expand_rule(r) if is_terminal(rp.next)} | |||
| def _calc(self): | |||
| """Calculate FOLLOW sets. | |||
| Adapted from: http://lara.epfl.ch/w/cc09:algorithm_for_first_and_follow_sets""" | |||
| symbols = {sym for rule in self.rules for sym in rule.expansion} | {rule.origin for rule in self.rules} | |||
| symbols.add('$root') # what about other unused rules? | |||
| # foreach grammar rule X ::= Y(1) ... Y(k) | |||
| # if k=0 or {Y(1),...,Y(k)} subset of NULLABLE then | |||
| # NULLABLE = NULLABLE union {X} | |||
| # for i = 1 to k | |||
| # if i=1 or {Y(1),...,Y(i-1)} subset of NULLABLE then | |||
| # FIRST(X) = FIRST(X) union FIRST(Y(i)) | |||
| # for j = i+1 to k | |||
| # if i=k or {Y(i+1),...Y(k)} subset of NULLABLE then | |||
| # FOLLOW(Y(i)) = FOLLOW(Y(i)) union FOLLOW(X) | |||
| # if i+1=j or {Y(i+1),...,Y(j-1)} subset of NULLABLE then | |||
| # FOLLOW(Y(i)) = FOLLOW(Y(i)) union FIRST(Y(j)) | |||
| # until none of NULLABLE,FIRST,FOLLOW changed in last iteration | |||
| NULLABLE = set() | |||
| FIRST = {} | |||
| FOLLOW = {} | |||
| for sym in symbols: | |||
| FIRST[sym]={sym} if is_terminal(sym) else set() | |||
| FOLLOW[sym]=set() | |||
| changed = True | |||
| while changed: | |||
| changed = False | |||
| for rule in self.rules: | |||
| if set(rule.expansion) <= NULLABLE: | |||
| if update_set(NULLABLE, {rule.origin}): | |||
| changed = True | |||
| for i, sym in enumerate(rule.expansion): | |||
| if set(rule.expansion[:i]) <= NULLABLE: | |||
| if update_set(FIRST[rule.origin], FIRST[sym]): | |||
| changed = True | |||
| if i==len(rule.expansion)-1 or set(rule.expansion[i:]) <= NULLABLE: | |||
| if update_set(FOLLOW[sym], FOLLOW[rule.origin]): | |||
| changed = True | |||
| for j in range(i+1, len(rule.expansion)): | |||
| if set(rule.expansion[i+1:j]) <= NULLABLE: | |||
| if update_set(FOLLOW[sym], FIRST[rule.expansion[j]]): | |||
| changed = True | |||
| self.FOLLOW = FOLLOW | |||
| def analyze(self): | |||
| self._calc() | |||
| self.states = {} | |||
| def step(state): | |||