Module: grammar-compiler Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND define inline function find (obj, list :: , #key test = \==, key = identity) // any?(method (x) test(obj, key(x)) & x end, seq) iterate loop (list :: = list) if (empty?(list)) #f elseif (test(obj, key(list.head))) list.head else loop(list.tail) end; end; end; define function assoc (item, list :: ) => (a :: false-or()) find(item, list, key: head) end; define constant $state-item-shift = 14; define constant $max-item-index = ash(1, $state-item-shift) - 1; define constant = limited(, min: 0, max: $max-item-index); define constant = limited(, min: 0); define constant = limited(, of: ); define abstract class () end; define sealed domain make(subclass()); define sealed domain initialize(); define class () constant slot rule-index :: , required-init-keyword: index:; constant slot rule-lhs :: , required-init-keyword: lhs:; constant slot rule-user-rule :: , required-init-keyword: user-rule:; slot rule-item :: ; end; define class () constant slot term-token, required-init-keyword: token:; slot term-index :: , required-init-keyword: index:; slot term-first-set :: false-or() = #f; end; define class () end; define method initialize (t :: , #rest keys, #key) t.term-first-set := list(t); end; define class () slot nont-rules :: = #(); slot nont-derivs :: = #(); slot nont-epsilon-item :: false-or() = #f; slot nont-error-rule-index :: false-or() = #f; // TODO: this doesn't seem different from term-first-set. slot nont-deriv-first-set :: = #(); end; // Note that this will be distinct from any user token of the same name. // We could make it non-distinct by interning it during setup-symbols... define constant $eoi-term = make(, token: #"eoi", index: $max-item-index); define inline function term-index-code (index :: ) if (index == $max-item-index) #"eoi" else index end; end; define inline function term-code (term :: ) term-index-code(term.term-index) end; define abstract class () constant slot item-index :: , required-init-keyword: index:; constant slot item-rule :: , required-init-keyword: rule:; slot item-lr1-closure :: = #(); end; define class (, ) end; define sealed method table-protocol (table :: ) => (test :: , hash :: ) values(\=, item-set-hash) end method table-protocol; define function item-set-hash (items :: , hash-state) => (id :: , hash-state) for (item :: in items, id = 23 then merge-hash-ids(id, item.item-index, ordered: #t)) finally values(id, hash-state) end for; end; define class () end; define inline sealed method empty? (x :: ) => (empty? :: ) ~instance?(x, ); end; // TODO: really want and .. define class () constant slot item-first-term :: , required-init-keyword: first-term:; slot item-next-item :: , required-init-keyword: next-item:; slot item-first-set :: false-or() = #f; end; define inline method item-non-terminal-left? (item :: ) ~empty?(item) & instance?(item.item-first-term, ); end; define inline method item-terminal-left? (item :: ) ~empty?(item) & instance?(item.item-first-term, ); end; define /* EXPORTED */ class () constant slot grammar-rules :: , required-init-keyword: rules:; constant slot grammar-error-rules :: , required-init-keyword: error-rules:; constant slot grammar-terminals :: , required-init-keyword: terminals:; constant slot grammar-goto-table :: , required-init-keyword: goto-table:; constant slot grammar-action-table :: , required-init-keyword: action-table:; constant slot grammar-rule-reduction-table :: , required-init-keyword: rule-reduction-table:; end; define class () // The rules in order given. constant slot %grammar-user-rules :: , required-init-keyword: user-rules:; constant slot %grammar-user-error-rules :: , required-init-keyword: user-error-rules:; // All terminal tokens in the grammar. slot %grammar-terminal-tokens :: = #[]; // Vector of , same order as user rules slot %grammar-rules :: = #[]; // Vector of 's. slot %grammar-non-terminals :: = #[]; // Vector of 's slot %grammar-items :: = #[]; // state => item sets constant slot %grammar-item-sets :: = make(); // state => error-action constant slot %grammar-error-action-table :: = make(); // state, item -> terminals constant slot %grammar-lookaheads :: = make(); // state, term -> next-state constant slot %grammar-gotos :: = make(); // state, term -> action constant slot %grammar-actions :: = make(); // lookahead propagation queue. slot %grammar-propagation-queue = #f; end; define inline function %grammar-initial-item (gv :: ) => (item :: ) gv.%grammar-items[0] end; /* define inline function %grammar-number-items (gv :: ) => (n :: ) gv.%grammar-items.size end; */ define inline function %grammar-number-states (gv :: ) => (n :: ) gv.%grammar-item-sets.size end; define inline method state-key (state :: , item :: ) => (key :: ) ash(state, $state-item-shift) + item.item-index end; define inline method state-key (state :: , term :: ) => (key :: ) ash(state, $state-item-shift) + term.term-index end; define inline function key-state (key :: ) => (state :: ) ash(key, - $state-item-shift) end; define inline function key-value (key :: ) => (index :: ) logand(key, $max-item-index) end; define inline function state-item-lookaheads (gv :: , state :: , item :: ) => (lookaheads :: ) gv.%grammar-lookaheads[state-key(state, item)] end; define inline function state-item-lookaheads-setter (lookaheads :: , gv :: , state :: , item :: ) gv.%grammar-lookaheads[state-key(state, item)] := lookaheads end; define inline function state-term-goto-state (gv :: , state :: , term :: ) => (state :: ) gv.%grammar-gotos[state-key(state, term)] end; define inline function state-item-set (gv :: , state :: ) => (items :: ) gv.%grammar-item-sets[state]; end; define inline function item-start-item (item :: ) => (start-item :: ) item.item-rule.rule-item end; define inline function item-rule-index (item :: ) => (rule-index :: ) item.item-rule.rule-index end; define /* EXPORTED */ function compile-grammar-rules (rules :: , #key error-rules :: = #[]) => (grammar :: ) let gv = make(, user-rules: as(, rules), user-error-rules: as(, error-rules)); setup-symbols(gv); setup-first-sets(gv); setup-derivs(gv); create-parse-table(gv); define-actions(gv); make-compiled-grammar(gv) end; define function make-compiled-grammar (gv :: ) make(, rules: gv.%grammar-user-rules, error-rules: gv.%grammar-user-error-rules, terminals: gv.%grammar-terminal-tokens, rule-reduction-table: map(method (rule :: ) rule.rule-lhs.term-code end, gv.%grammar-rules), goto-table: externalize-goto-table(gv), action-table: externalize-action-table(gv)) end; define constant $all-same-code = 65535; define function externalize-action-table (gv :: ) => (v :: ) let nstates = gv.%grammar-number-states; let v = make(, size: nstates, fill: #()); for (entry :: keyed-by state-term-key in gv.%grammar-actions) let state :: = key-state(state-term-key); let term-code = term-index-code(key-value(state-term-key)); v[state] := pair(pair(term-code, if (entry.aentry-shift?) lognot(gv.%grammar-gotos[state-term-key]) else let rule-index = entry.aentry-item.item-rule-index; if (zero?(rule-index) & term-code == #"eoi") #"accept" else rule-index end end), v[state]); end; for (state from 0 below nstates) let actions :: = v[state]; let err = element(gv.%grammar-error-action-table, state, default: #f); when (err) actions := concatenate!(actions, list(pair(#"error", err))); end; let r = ~empty?(actions) & begin let r = actions.first.tail; instance?(r, ) & r >= 0 & every?(method (p) p.tail == r end, actions) & r end; when (r) actions := concatenate!(list(pair($all-same-code, r)), actions); end; let plist = make(, size: 2 * actions.size); for (index from 0 below plist.size by 2, pair :: in actions) plist[index] := pair.head; plist[index + 1] := pair.tail; end for; v[state] := plist; end for; v end; define function externalize-goto-table (gv :: ) => (v :: ) let nstates = gv.%grammar-number-states; let v = make(, size: nstates, fill: #()); for (goto keyed-by state-term-index in gv.%grammar-gotos) let state :: = key-state(state-term-index); let term-index = key-value(state-term-index); v[state] := pair(goto, pair(term-index-code(term-index), v[state])); end; for (state from 0 below nstates) v[state] := as(, reverse!(v[state])); end; v end; define function setup-symbols (gv :: ) let user-rules = gv.%grammar-user-rules; let nrules = user-rules.size; let nitems = reduce(method (n, r) n + r.second.size end, nrules, user-rules); assert(nitems <= $max-item-index, "Too many productions in grammar"); let terms = make(); let nont-seq = make(); for (urule in user-rules) let tkn = urule.first; element(terms, tkn, default: #f) | add!(nont-seq, terms[tkn] := make(, token: tkn, index: 0)); end; gv.%grammar-non-terminals := as(, nont-seq); let error-rules = gv.%grammar-user-error-rules; for (index from 0 below error-rules.size) let tkn = error-rules[index].first; let nt = element(terms, tkn, default: #f); assert(nt, "Error rule for terminal %s", tkn); assert(~nt.nont-error-rule-index, "Multiple error rules for %s", tkn); nt.nont-error-rule-index := index; end; let tkn-seq = make(); local method as-term (tkn) element(terms, tkn, default: #f) | begin add!(tkn-seq, tkn); terms[tkn] := make(, token: tkn, index: tkn-seq.size - 1) end; end; let rules = make(, size: nrules); let items = make(, size: nitems); let item-index :: = 0; for (rule-index from 0 below nrules) let urule = user-rules[rule-index]; let nt :: = terms[urule.first]; let seq = urule.second; let nseq :: = seq.size; let rule = make(, user-rule: urule, index: rule-index, lhs: nt); nt.nont-rules := add(nt.nont-rules, rule); rules[rule-index] := rule; let empty = make(, rule: rule, index: item-index + nseq); if (nseq == 0) assert(~nt.nont-epsilon-item, "Multiple empty rules for %s", nt.term-token); nt.nont-epsilon-item := empty; rule.rule-item := empty; else let previous :: = empty; for (tkn in seq) let new = make(, rule: rule, index: item-index, first-term: as-term(tkn), next-item: empty); items[item-index] := new; item-index := item-index + 1; if (previous == empty) rule.rule-item := new; else previous.item-next-item := new; end; previous := new; end for; end; items[item-index] := empty; item-index := item-index + 1; end; gv.%grammar-items := items; gv.%grammar-rules := rules; gv.%grammar-terminal-tokens := as(, tkn-seq); for (index from tkn-seq.size by 1, nt in gv.%grammar-non-terminals) nt.term-index := index; end; end; define function setup-first-sets (gv :: ) for (nt :: in gv.%grammar-non-terminals) unless (nt.term-first-set) nt.term-first-set := compute-first-set(gv, nt, #()); end; end; end; define function compute-first-set (gv :: , nt :: , visited :: ) => (s :: ) let set :: = #(); let complete? = #t; local method do-item (item :: ) if (empty?(item)) set := add-new(set, #f) elseif (~member?(item, visited)) let t :: = item.item-first-term; let initial-first-set = t.term-first-set | compute-first-set(gv, t, add(visited, item)); if (~t.term-first-set) complete? := #f end; for (t in initial-first-set) when (t) set := add-new(set, t) end end; when (member?(#f, initial-first-set)) do-item(item.item-next-item) end; else complete? := #f; end if; end; for (rule :: in nt.nont-rules) do-item(rule.rule-item) end; if (complete?) nt.term-first-set := set end; set end; /// Structure definitions for items and rules define class () constant slot deriv-item-initial :: , required-init-keyword: initial:; constant slot deriv-item-result :: , required-init-keyword: result:; constant slot deriv-item-terminal :: false-or(), required-init-keyword: terminal:; end; define class () constant slot deriv-rule-to :: , required-init-keyword: to:; constant slot deriv-rule-terminals :: , required-init-keyword: terminals:; end; define function setup-derivs (gv :: ) let rule-set = make-deriv-rule-set(gv); let items = make-initial-item-list(gv); for (item in items) add-deriv(item) end; iterate loop (items :: = items) unless (empty?(items)) let item = items.head; let undone-items :: = items.tail; let rules = element(rule-set, item.deriv-item-initial, default: #f); when (rules) for (new-item in apply-rules(rules, item)) when (add-deriv(new-item)) undone-items := pair(new-item, undone-items) end; end; end; loop(undone-items) end; end; end; define function make-deriv-rule-set (gv :: ) => (set ::
) // first-nt => list of 's let rule-set = make(
); for (rule :: in gv.%grammar-rules) let item = rule.rule-item; if (item-non-terminal-left?(item)) let nt = item.item-first-term; let dr = make(, to: rule.rule-lhs, terminals: item-next-first-set(item)); rule-set[nt] := pair(dr, element(rule-set, nt, default: #())); end; end; rule-set end; /// apply-rules (rule-set item) applies all the rules in rule-set to /// the item, returning a list of the new items generated by the /// rules. The returned list may contain duplicates. define function apply-rules (rules :: , item :: ) => (new-items :: ) let result = item.deriv-item-result; let terminal = item.deriv-item-terminal; if (~terminal) reduce(method (so-far :: , rule :: ) reduce(method (so-far :: , term) add(so-far, make(, initial: rule.deriv-rule-to, result: result, terminal: term)) end, so-far, rule.deriv-rule-terminals) end, #(), rules) else map(method (rule :: ) make(, initial: rule.deriv-rule-to, result: result, terminal: terminal) end, rules); end; end; define function make-initial-item-list (gv :: ) => (deriv-items :: ) map-as(, method (nt :: ) make(, initial: nt, result: nt, terminal: #f) end, gv.%grammar-non-terminals); end; define function add-deriv (item :: ) => (new?) let initial = item.deriv-item-initial; let result = item.deriv-item-result; let terminal = item.deriv-item-terminal; let alist = initial.nont-derivs; let data = assoc(result, alist); if (~data) initial.nont-derivs := pair(pair(result, list(terminal)), alist); elseif (~member?(terminal, data.tail)) data.tail := pair(terminal, data.tail); else #f end; end; define function create-parse-table (gv :: ) /// The start symbol's rule is always the first of the grammar rules compute-kernel-sets(gv); initialize-lookahead-table(gv); determine-lookaheads(gv); propagate-the-lookaheads(gv); end; define function compute-kernel-sets (gv :: ) let indices :: = make(); let item-sets = gv.%grammar-item-sets; local method intern-item-set (items :: ) => (state :: ) let items = make-canonical(items); element(indices, items, default: #f) | begin let index = item-sets.size; add!(item-sets, items); indices[items] := index // return index end; end; let reductions :: = make(); for (nt in gv.%grammar-non-terminals) reductions[nt] := map(head, nt.nont-derivs) end; local method state-transitions (state) let item-set = state-item-set(gv, state); let goto-sets = make-goto-table(reductions, item-set); let result = #(); for (new-item-set keyed-by sym in goto-sets) let goto-state = intern-item-set(new-item-set); add-to-goto-table(gv, state, sym, goto-state); result := pair(goto-state, result); end; result end; make-set-closure(state-transitions, intern-item-set(list(gv.%grammar-initial-item))); end; define function initialize-lookahead-table (gv :: ) for (item-set keyed-by state in gv.%grammar-item-sets) for (item :: in item-set) state-item-lookaheads(gv, state, item) := #(); end; end; end; define constant $initial-state = 0; define function determine-lookaheads (gv :: ) for (item :: in gv.%grammar-items) item.item-lr1-closure := compute-lr1-closure(item); end; /// By definition the end of input token :eoi is spontaneously /// generated for the initial rule add-to-lookahead-table(gv, $initial-state, gv.%grammar-initial-item, $eoi-term); for (state from 0 below gv.%grammar-number-states) determine-state-lookaheads(gv, state); end; end; /// /// Propagate the lookaheads /// define function propagate-the-lookaheads (gv :: ) iterate loop () let more? = #f; for (pe = gv.%grammar-propagation-queue then pe.pentry-next, while: pe) more? := propagate-a-lookahead(gv, pe) | more?; end; when (more?) // try again. loop() end; end; end; define function define-actions (gv :: ) let nstates = gv.%grammar-number-states; for (nt :: in gv.%grammar-non-terminals) nt.nont-deriv-first-set := compute-nont-deriv-first-set(nt); end; for (state from 0 below nstates) make-actions(gv, state); end; end; // TODO is this different from term-first-set?? define function compute-nont-deriv-first-set (nt :: ) => (fs :: ) let seen :: = #(); for (d in nt.nont-derivs) for (rule in d.head.nont-rules) let item = rule.rule-item; when (item-terminal-left?(item)) seen := add-new(seen, item.item-first-term); end; end; end; seen end; define function make-actions (gv :: , state :: ) let item-set = state-item-set(gv, state); for (item in item-set) make-action(gv, state, item) end; end; define function item-next-first-set (item :: ) => (s :: ) let item = item.item-next-item; if (empty?(item)) #(#f) elseif (item-terminal-left?(item) | empty?(item.item-next-item)) term-first-set(item.item-first-term) else item.item-first-set | (item.item-first-set := compute-item-first-set(item)) end; end; define function compute-item-first-set (item :: ) => (s :: ) let first-set = term-first-set(item.item-first-term); if (member?(#f, first-set)) let rest-of-first-set = item-next-first-set(item); combine-first-sets(first-set, rest-of-first-set); else first-set end; end; define function add-to-goto-table (gv :: , state :: , term :: , newstate :: ) gv.%grammar-gotos[state-key(state, term)] := newstate; end; define function compute-lr1-closure (item :: ) => (l :: ) let result :: = list(pair(item, #(#f))); if (~item-non-terminal-left?(item)) result else let beta-first-set = item.item-next-first-set; for (deriv :: in item.item-first-term.nont-derivs) let nont = deriv.head; let zeta = deriv.tail; let zeta-beta-first-set = combine-first-sets(zeta, beta-first-set); for (rule in nont.nont-rules) result := add!(result, pair(rule.rule-item, zeta-beta-first-set)); end; end; result end; end; define function combine-first-sets (alpha :: , beta :: ) => (s :: ) if (~member?(#f, alpha)) alpha else let result = beta; for (la :: false-or() in alpha) when (la & ~member?(la, beta)) result := pair(la, result); end; end; result end; end; define function determine-state-lookaheads (gv :: , state :: ) let kernel-set = state-item-set(gv, state); for (kernel-item :: in kernel-set) for (lr1-item :: in kernel-item.item-lr1-closure) let item :: = lr1-item.head; let lookaheads :: = lr1-item.tail; for (la :: false-or() in lookaheads) if (~la) add-to-propagation-table(gv, kernel-item, state, item); else generate-spontaneous-lookahead(gv, state, item, la); end; end; end; end; end; define class () constant slot pentry-from-key, required-init-keyword: from-key:; constant slot pentry-goto-key, required-init-keyword: goto-key:; constant slot pentry-next :: false-or(), required-init-keyword: next:; end; define inline function add-to-propagation-table (gv :: , from-item :: , in-state :: , to-item :: ) unless (empty?(to-item)) let to-item :: = to-item; gv.%grammar-propagation-queue := make(, from-key: state-key(in-state, from-item), goto-key: state-key(state-term-goto-state(gv, in-state, to-item.item-first-term), to-item.item-next-item), next: gv.%grammar-propagation-queue); end; end; define inline function generate-spontaneous-lookahead (gv :: , state :: , to-item :: , lookahead :: ) unless (empty?(to-item)) let to-item :: = to-item; let goto-term = to-item.item-first-term; let goto-state = state-term-goto-state(gv, state, goto-term); let goto-item = to-item.item-next-item; add-to-lookahead-table(gv, goto-state, goto-item, lookahead) end; end; define function propagate-a-lookahead (gv :: , entry :: ) => (changed? :: ) // entry is "state-item1 -> state-item2" // add lookaheads for state-item1 to those of state-item2, if not // already there. let from-las = gv.%grammar-lookaheads[entry.pentry-from-key]; let goto-key = entry.pentry-goto-key; let goto-las = gv.%grammar-lookaheads[goto-key]; let changed? = #f; for (t in from-las, las = goto-las then if (member?(t, goto-las)) las else pair(t, las) end) finally unless (las == goto-las) gv.%grammar-lookaheads[goto-key] := las; #t end; end; end; define function add-to-lookahead-table (gv :: , state :: , item :: , la :: ) let key = state-key(state, item); let las :: = gv.%grammar-lookaheads[key]; unless (member?(la, las)) gv.%grammar-lookaheads[key] := pair(la, las); end; end; define inline function make-set-closure (f :: , top-state :: ) let seen-states = make(); add!(seen-states, top-state); iterate loop (states :: = f(top-state)) unless(empty?(states)) let top-state = states.head; if (member?(top-state, seen-states)) loop(states.tail); else add!(seen-states, top-state); loop(concatenate(f(top-state), states.tail)) end; end; end iterate; end function; define function make-derived-actions (gv :: , state :: , item :: , itemb :: ) generate-shifts(gv, state, item, itemb); generate-errors(gv, state, item, itemb); generate-reductions(gv, state, item, itemb); end; define function generate-shifts (gv :: , state :: , item :: , itemb :: ) for (term in itemb.nont-deriv-first-set) set-shift-action(gv, state, item, term); end; end; define function generate-errors (gv :: , state :: , item :: , itemb :: ) for (d in itemb.nont-derivs) maybe-set-error-action(gv, state, d.head); end; end; define function generate-reductions (gv :: , state :: , item :: , nt :: ) let derivs = nt.nont-derivs; let possible-reductors = choose(method (d) d.head.nont-epsilon-item end, derivs); when (~empty?(possible-reductors)) let lookaheads = state-item-lookaheads(gv, state, item); let beta-lookaheads = item-next-first-set(item); for (deriv :: in possible-reductors) let las = deriv.tail; let reducing-input = las; when (member?(#f, las)) reducing-input := concatenate(beta-lookaheads, reducing-input); when (member?(#f, beta-lookaheads)) reducing-input := concatenate(lookaheads, reducing-input); end; end; for (term in remove-duplicates(reducing-input)) when (term) set-reduce-action(gv, state, deriv.head.nont-epsilon-item, term) end; end; end; end; end; define function make-goto-table (reductions :: , item-set :: ) // Returns a hash table containing the goto sets for the given states. // That is, make-goto-table(state)[symbol] is the set of items in the // set GOTO(state, symbol). let newstates = make(); // rehash-size: 2.0 local method add-goto (sym, item :: ) let next-item = item.item-next-item; let old = element(newstates, sym, default: #()); unless (member?(next-item, old)) newstates[sym] := pair(next-item, old); end; end; for (item :: in item-set) unless (empty?(item)) let next-term = item.item-first-term; add-goto(next-term, item); when (item-non-terminal-left?(item)) for (nont in element(reductions, next-term, default: #())) for (rule in nont.nont-rules) let start-item = rule.rule-item; unless (empty?(start-item)) add-goto(start-item.item-first-term, start-item) end; end; end; end; end; end; newstates end; define function make-canonical (item-set :: ) if (item-set == #() | item-set.tail == #()) item-set else sort(item-set, stable: #t, test: method (i :: , j :: ) i.item-index < j.item-index end) end; end; define function make-action (gv :: , state :: , item :: ) if (empty?(item)) make-simple-reductions(gv, state, item); elseif (item-terminal-left?(item)) set-shift-action(gv, state, item, item.item-first-term); else make-derived-actions(gv, state, item, item.item-first-term) end; end; define function make-simple-reductions (gv :: , state :: , item :: ) let lookaheads = state-item-lookaheads(gv, state, item); for (lookahead in lookaheads) set-reduce-action(gv, state, item, lookahead) end; end; define class () slot aentry-shift? :: , required-init-keyword: shift?:; slot aentry-item :: , required-init-keyword: item:; end; define function maybe-set-error-action (gv :: , state :: , nt :: ) let index = nt.nont-error-rule-index; when (index) let old :: false-or() = element(gv.%grammar-error-action-table, state, default: #f); if (~old | old < index) gv.%grammar-error-action-table[state] := index; end end; end; define /* EXPORTED */ class (, ) constant slot grammar-conflict-terminal, required-init-keyword: terminal:; constant slot grammar-conflict-action-1 :: one-of(#"shift", #"reduce"), required-init-keyword: action-1:; constant slot grammar-conflict-rule-1 :: , required-init-keyword: rule-1:; constant slot grammar-conflict-position-1 :: , required-init-keyword: position-1:; constant slot grammar-conflict-action-2 :: one-of(#"shift", #"reduce"), required-init-keyword: action-2:; constant slot grammar-conflict-rule-2 :: , required-init-keyword: rule-2:; constant slot grammar-conflict-position-2 :: , required-init-keyword: position-2:; end; define function set-reduce-action (gv, state, item, terminal) set-action(gv, state, item, terminal, #f); end; define function set-shift-action (gv :: , state :: , item :: , terminal :: ) set-action(gv, state, item, terminal, #t) end; define function note-conflict (terminal :: , shift-1? :: , item-1 :: , shift-2? :: , item-2 :: ) local method item-position (item :: ) iterate loop (start = item.item-start-item, n = 0) if (item == start) n else loop(start.item-next-item, n + 1) end end; end; signal(make(, terminal: terminal.term-token, action-1: if (shift-1?) #"shift" else #"reduce" end, rule-1: item-1.item-rule.rule-user-rule, position-1: item-position(item-1), action-2: if (shift-2?) #"shift" else #"reduce" end, rule-2: item-2.item-rule.rule-user-rule, position-2: item-position(item-2))) end; define function set-action (gv :: , state :: , item :: , terminal :: , shift? :: ) let key = state-key(state, terminal); let old :: false-or() = element(gv.%grammar-actions, key, default: #f); if (~old) gv.%grammar-actions[key] := make(, shift?: shift?, item: item); elseif (shift? & old.aentry-shift?) // Remember earliest item in case of conflict... when (item.item-rule-index < old.aentry-item.item-rule-index) old.aentry-item := item; end; else let old-item = old.aentry-item; let old-shift? = old.aentry-shift?; let old-rule = old-item.item-rule-index; let rule = item.item-rule-index; unless (~shift? & ~old-shift? & rule == old-rule) // There is a conflict if (old-rule < rule | (old-rule = rule & shift?)) note-conflict(terminal, old-shift?, old-item, shift?, item) else note-conflict(terminal, shift?, item, old-shift?, old-item); old.aentry-shift? := shift?; old.aentry-item := item; end; end unless; end if; end; // eof