# argument — which of your claims stand, once you say what supports and attacks what. # # This module supplies the REASONING. You supply the facts: the claims being # argued about, written as `argument.Claim`, and what your sources say about # them — that one claim supports another (`argument.Support`), attacks it # (`argument.Attack`), or weakens it without defeating it # (`argument.Weakening`). Import this module and the standing rules and the # queries below apply to whatever you have loaded. # # import argument # # Nothing here decides a question for you. Every statement stays a claim with # the source that made it, and what the rules conclude from them withdraws # itself when a statement it rests on is forgotten. # # What the rules settle, and what they leave open. A claim nothing attacks # stands. A claim attacked by a claim that stands falls: `undefended()`. A # claim whose every attacker falls stands again: `reinstated()`. Anything else # that is attacked is left open, and the queries say nothing about it — two # claims that attack each other are the simplest case. Settling those takes # more steps than these two, and the module stops here on purpose, # so that everything it does answer is an answer. class Claim: # What the claim says, for reading. The rules below do not need it: they # take every name a statement uses as a claim, recorded or not, and # `unrecorded()` below is how you find a misspelt one. text: string class Support: # A source says claim `by` supports claim `target`. The statement's own # name is yours to choose; it only keeps two statements apart. by: string target: string class Attack: # A source says claim `by` attacks claim `target`: if `by` stands, # `target` does not. by: string target: string class Weakening: # A source says claim `by` weakens claim `target` without defeating it. A # weakening is reported and never decides whether a claim stands. by: string target: string # ── Standing rules ─────────────────────────────────────────────────────────── # # A rule holds continuously: its conclusion is in force exactly while its # condition holds, and withdraws itself when the condition stops holding. You # never write these; the engine maintains them. # # A statement counts once both its halves are written. One with only a # `target`, say because its `by` was forgotten, attacks, supports or weakens # nothing. @rule def attack_reaches(): for a in Attack, x in Claim, c in Claim: if a.by == x and a.target == c: c.attacked = True @rule def weakening_reaches(): for w in Weakening, x in Claim, c in Claim: if w.by == x and w.target == c: c.weakened = True @rule def nothing_attacks(): for c in Claim: if none(a in Attack: a.target == c and a.by != ""): c.unattacked = True @rule def attack_stands(): # An attack by a claim nothing attacks cannot be answered, so its target # falls. for a in Attack, x in Claim, c in Claim: if a.by == x and a.target == c and x.unattacked == True: c.undefended = True @rule def attack_answered(): # An attack whose author has fallen no longer counts against its target. for a in Attack, x in Claim: if a.by == x and x.undefended == True: a.answered = True @rule def every_attack_answered(): for c in Claim: if c.attacked == True and none(a in Attack: a.target == c and a.by != "" and absent(a.answered)): c.reinstated = True @rule def premise_attacked(): # A support whose supporting claim is itself attacked. for s in Support, x in Claim, c in Claim: if s.by == x and s.target == c and x.attacked == True: s.contested = True # ── Queries ────────────────────────────────────────────────────────────────── def supporters_of(c: string) -> list: # Every claim a source says supports `c`. ss = subjects(Support.target, c) statements = [s for s in ss] out = [] bys = list_values(Support.by) for b in bys: parts = b.path.split(".") v = str(b.value) if parts[-2] in statements and v not in out: out = out + [v] return sorted(out) def attackers_of(c: string) -> list: # Every claim a source says attacks `c`. ss = subjects(Attack.target, c) statements = [s for s in ss] out = [] bys = list_values(Attack.by) for b in bys: parts = b.path.split(".") v = str(b.value) if parts[-2] in statements and v not in out: out = out + [v] return sorted(out) def weakeners_of(c: string) -> list: # Every claim a source says weakens `c`. ss = subjects(Weakening.target, c) statements = [s for s in ss] out = [] bys = list_values(Weakening.by) for b in bys: parts = b.path.split(".") v = str(b.value) if parts[-2] in statements and v not in out: out = out + [v] return sorted(out) def unattacked() -> list: # Every claim nothing attacks. Each of these stands. out = [] cs = subjects(Claim.unattacked, True) for c in cs: out = out + [c] return sorted(out) def undefended() -> list: # Every claim attacked by a claim nothing attacks. Each of these falls. out = [] cs = subjects(Claim.undefended, True) for c in cs: out = out + [c] return sorted(out) def reinstated() -> list: # Every attacked claim whose attackers have all fallen. Each of these # stands again. out = [] cs = subjects(Claim.reinstated, True) for c in cs: out = out + [c] return sorted(out) def weakened() -> list: # Every claim a weakening reaches. out = [] cs = subjects(Claim.weakened, True) for c in cs: out = out + [c] return sorted(out) def contested_premises() -> list: # Every claim that supports another and is itself attacked. ss = subjects(Support.contested, True) statements = [s for s in ss] out = [] bys = list_values(Support.by) for b in bys: parts = b.path.split(".") v = str(b.value) if parts[-2] in statements and v not in out: out = out + [v] return sorted(out) def unrecorded() -> list: # Every name a statement uses that no claim carries — usually a # misspelling. It reads every value a statement's halves hold now: a half # two sources disagree on is read for both values, and a half revised with # `update` for the value that replaced the old one. names = [] support_by = list_values(Support.by) for c in support_by: names = names + [str(c.value)] support_target = list_values(Support.target) for c in support_target: names = names + [str(c.value)] attack_by = list_values(Attack.by) for c in attack_by: names = names + [str(c.value)] attack_target = list_values(Attack.target) for c in attack_target: names = names + [str(c.value)] weakening_by = list_values(Weakening.by) for c in weakening_by: names = names + [str(c.value)] weakening_target = list_values(Weakening.target) for c in weakening_target: names = names + [str(c.value)] out = [] for n in names: r = current(Claim.n.text) match r: case active_claim: pass case empty: if n not in out: out = out + [n] return sorted(out)