リポジトリ / emssr / independent_common_compiler
r2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | # -*- coding: utf-8 -*- """セル一つを compile して実行し、scope を組む。 doc: commonc.pipeline casm.lane64.host plain.format 道は一本になる(19 §7.1)。 .dic + .cmn ──(commonc_lane64.bin on lane64)──▶ lane64 image ──(lane64)──▶ M compile の受け渡しは toolchain.compile と同じだが、入口が path ではなく文字列で、 commonc の作業領域を捨てずに返す。scope はその作業領域から組む。 CPython でも Pyodide でも同じ code が動く。違うのは commonc の image の場所だけで、 それは start() の引数で決まる。 """ import hashlib import os import sys import tempfile import scope _HERE = os.path.dirname(os.path.abspath(__file__)) if _HERE not in sys.path: sys.path.insert(0, _HERE) import lex # noqa: E402 Python_vCPU から借りる import toolchain # noqa: E402 import vcpu # noqa: E402 WORK = toolchain.WORK # 本文の作業領域の基底。ワイヤ(語 64+w)の上 DEFAULT_WORDS = WORK + (1 << 13) DEFAULT_BUDGET = 1 << 24 # block を辿る上限。止まらないセルをここで止める class CellError(Exception): pass def start(commonc): """commonc の image の場所を決める。翻訳は最初の compile のときに一度だけ行う""" toolchain.COMMONC = commonc # ===== compile ===== # # host が commonc へ渡すもの(19 §3.1)。toolchain.compile と同じ値を置く。 # # 語 0 N 入力の byte 数(表 + 本文) # 語 1 IB 入力の基底語番号 # 語 4 B 配列の刻み # 語 130 LEXB 表の byte 数 # 読み取り専用の場所へ入れても動くように、表は一時フォルダへ書く _DIC = os.path.join(tempfile.gettempdir(), "common_scope_cell.dic") def _dic_file(text): """採用語彙の表を lex へ渡すために一度だけ file にする""" open(_DIC, "w", encoding="utf-8", newline="\n").write(text) return _DIC def compile_text(source, dic=None): """平文を image にする。返りは (image, n_need, commonc の作業領域)""" table, est, fold = "", len(source) // 2, {} if dic and dic.strip(): path = _dic_file(dic) table = lex.lexicon(path) est = lex.expanded_syllables(path, source) fold = scope.lexicon(lex.entries(path)) # commonc を正本にする。scope.check は表示用の補助表から行う診断なので、 # Common 本体より先に文を棄却してはいけない。語彙が増えたときに補助表だけが # 古いと、正規の commonc では通る文を notebook だけが拒否してしまうためになる。 data = (table + source).encode("utf-8") n = len(data) b = max(n + 80, 4096, 2 * est + 80) base = toolchain.IN_MUL * b lane = vcpu.Lane(base + n + 4096) lane.put_input(list(data), base) lane.M[toolchain.W_B] = b lane.M[toolchain.W_LEXB] = len(table.encode("utf-8")) rep = vcpu.run_lane(toolchain._commonc(), lane) if rep != 0: # commonc が実際に拒否したときだけ、平文側の診断で理由を細かくする。 # 診断表が Common の全語彙を知らなくても、成功した文を壊すことはない。 hit = scope.check(source, fold) if hit: raise CellError(hit) raise CellError("commonc がこの文を読めない(報告 %s)。" "音節表の外の綴りか、机の上が合っていない" % rep) imgb, nimg, need = lane.M[1], lane.M[2], lane.M[3] if not (imgb and nimg): raise CellError("commonc が image を出さない") if need > WORK: raise CellError("箱が多すぎてワイヤが作業領域に重なる(n_need %d > %d)" % (need, WORK)) return bytes(v & 255 for v in lane.M[imgb:imgb + nimg]), need, lane.M # ===== 実行する ===== # # ワイヤの語を落とす覆い(toolchain.load の wires)は使わない。scope は # 箱ひとつずつの値を見せるので、語 64+w を全部残す。 def run_image(image, seeds=None, words=DEFAULT_WORDS, budget=DEFAULT_BUDGET): tab = vcpu.translate(vcpu.decode(image))[1] lane = vcpu.Lane(words) for k, v in (seeds or {}).items(): lane.M[int(k)] = int(v) try: rep = vcpu.run_lane(tab, lane, budget=budget) except RuntimeError: raise CellError("block を %d 回辿っても終わりません。Ω の「もう良いか」を見直す" % budget) except IndexError: raise CellError("語番号が作業領域を越えた(語数 %d)。セルの words を増やす" % words) return rep, lane def cell(source, dic=None, seeds=None, words=DEFAULT_WORDS, budget=DEFAULT_BUDGET): """セル一つ。compile して実行し、scope を返す""" image, need, cm = compile_text(source, dic) rep, lane = run_image(image, seeds, words, budget) table = scope.lexicon(lex.entries(_DIC)) if (dic and dic.strip()) else {} a = scope.arrays(cm) ws = scope.words(a, table) bx = scope.boxes(a, lane.M) gm = scope.gamma(a) depth = {g["word"]: a["opd"][g["box"]] for g in gm} return { "ok": True, "report": rep, "source": source, "size": {"syl": a["nSyl"] - a["body"], "box": a["nBox"], "need": need, "image": len(image)}, # 二つの平文が同じチェンバーかを見る hash。image が一致すれば読みも一致する "mark": hashlib.sha256(image).hexdigest()[:16], "words": ws, "frontier": scope.frontier(ws), "boxes": bx, "align": scope.align(ws, a), "gamma": [dict(g, **scope.dyad(lane.M[g["word"]], depth[g["word"]])) for g in gm], "image": scope.image(vcpu, image), "out": [lane.M[k] for k in range(64)], "put": bytes(lane.OUT).decode("latin-1"), } # ===== 接合秩(Common-Lang/1 定理 4.1)===== # # F[α][β] = f(α, β), ρ = rank_F2 F, f = ⊕_{r<ρ} p_r(x_A) q_r(x_B) # # 階数は common_apps/joinrank の平文が持つ。ここは頂点値の表を作って渡すだけになる。 # 表は同じ image を 2^n 回実行して集める。lane の作業領域は交わらない(20 式 1.2)ので、 # 実行は互いに独立になる。 def vertices(image, inputs, words=DEFAULT_WORDS, budget=DEFAULT_BUDGET, out=0): """入力の語へ {0,1}^n を全部当て、語 out の値を頂点値として並べる""" n = len(inputs) if n > 12: raise CellError("入口が %d 本。頂点は 2^n なので 12 本までにする" % n) tab = vcpu.translate(vcpu.decode(image))[1] tbl = [] for v in range(1 << n): lane = vcpu.Lane(words) for k, w in enumerate(inputs): lane.M[int(w)] = (v >> k) & 1 vcpu.run_lane(tab, lane, budget=budget) tbl.append(1 if lane.M[out] else 0) return tbl |