mcdiff.sh 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. #!/bin/bash
  2. # mcdiff.sh — verification harness for the src/compiler re-read.
  3. # Stage 1 (runs HERE, no VM needed): static consistency checks.
  4. # Stage 2 (needs the Reloaded VM + original compiler): true MCD diff;
  5. # procedure documented below. Exit nonzero on any stage-1 failure.
  6. #
  7. # Stage 2 procedure (once, per module MOD in dependency order
  8. # Compiler Scanner SymTab Errors CodeGen Express Statemen Pass1):
  9. # 1. Build the VM: cd Turbo-Modula-2-Reloaded-main/vm_linux && make
  10. # 2. Stage sources: copy src/compiler/X.{DEF,MOD} onto m2.dsk
  11. # (mtools: mcopy -i m2.dsk, or sudo mount -o loop).
  12. # File names must be 8+3 uppercase (COMPILER.DEF etc. already are).
  13. # 3. Boot: ./m2_disk m2.dsk ; at the M2 prompt run the resident
  14. # compiler on each MOD (c <file>), producing X.MCD on the image.
  15. # 4. Extract: mcopy -i m2.dsk ::COMPILER.DIR/X.MCD ./build/
  16. # 5. Disassemble both: unassemble.c built from Reversing-Turbo-Modula2-main
  17. # (cc -o unassemble unassemble.c), run on original .MCD from
  18. # Turbo-Modula-2-Reloaded-main/disk_content/COMPILER.DIR/ and on build/.
  19. # NOTE 2026-10-03: the VM needs a real terminal (termios raw mode); plain
  20. # pipes hang. Drive it under a pty, e.g.
  21. # script -qec "./vm_linux/m2 m2.dsk" /dev/null
  22. # or expect(1), sending keys with delays (CR-terminated lines). mtools is
  23. # NOT installed in this environment — staging currently needs manual mount.
  24. # 6. Diff against Reversing-Turbo-Modula2-main/MCode_disassembly/<mod>.txt.
  25. # Byte-identical MCD (hence identical disassembly modulo addresses) =
  26. # proof of a faithful rename/decompile. Record result per module in
  27. # docs/compiler/70-rename-plan.md style commit notes.
  28. set -u
  29. SRC="${1:-src/compiler}"
  30. fail=0
  31. say() { printf '%s\n' "$*"; }
  32. err() { printf 'FAIL: %s\n' "$*" >&2; fail=1; }
  33. say "== stage 1a: numbered identifiers (must be zero outside comments) =="
  34. # strip (* *) comments, then look for procN/globalN/paramN/localN/PROC N
  35. tmp=$(mktemp -d)
  36. for f in "$SRC"/*.MOD "$SRC"/*.DEF; do
  37. [ -f "$f" ] || continue
  38. python3 - "$f" "$tmp" <<'PY'
  39. import re, sys, pathlib
  40. src = pathlib.Path(sys.argv[1]).read_text()
  41. nos = re.sub(r'\(\*.*?\*\)', '', src, flags=re.S) # drop (* *) comments
  42. hits = re.findall(r'\b(?:global|proc|param|local)\d+\b|\bPROC\d+\b', nos)
  43. if hits:
  44. from collections import Counter
  45. print(f'{sys.argv[1]}: {dict(Counter(hits))}')
  46. PY
  47. done | tee "$tmp/hits" | grep . && err "numbered identifiers in code" || say "clean"
  48. say "== stage 1b: FORWARD/def consistency per MOD =="
  49. python3 - "$SRC" <<'PY'
  50. import re, sys, pathlib
  51. bad = False
  52. for f in sorted(pathlib.Path(sys.argv[1]).glob('*.MOD')):
  53. nos = re.sub(r'\(\*.*?\*\)', '', f.read_text(), flags=re.S)
  54. fwds = re.findall(r'PROCEDURE\s+(\w+)\s*(?:\([^;]*\))?(?:\s*:\s*\w+)?\s*;\s*FORWARD\s*;', nos)
  55. defs = re.findall(r'PROCEDURE\s+(\w+)\s*(?:\([^;]*\))?(?:\s*:\s*\w+)?\s*;', nos)
  56. from collections import Counter
  57. dc = Counter(defs)
  58. dupes = [k for k, v in dc.items() if v > 1 and k not in fwds]
  59. missing = [w for w in fwds if w not in defs]
  60. if dupes or missing:
  61. bad = True
  62. print(f'{f.name}: dupes={dupes} missing-defs={missing}')
  63. print('FORWARD-OK' if not bad else 'FORWARD-BAD')
  64. PY
  65. say "== stage 1c: FIXME inventory =="
  66. grep -c "FIXME" "$SRC"/*.MOD "$SRC"/*.DEF 2>/dev/null || true
  67. say "== stage 1d: unknown cross-module callees (spot check) =="
  68. # every Module.Proc call should resolve to a renamed export or a flagged FIXME nearby
  69. python3 - "$SRC" <<'PY'
  70. import re, sys, pathlib
  71. known = set()
  72. for f in pathlib.Path(sys.argv[1]).glob('*.DEF'):
  73. nos = re.sub(r'\(\*.*?\*\)', '', f.read_text(), flags=re.S)
  74. mod = f.stem.capitalize()
  75. for m in re.findall(r'PROCEDURE\s+(\w+)', nos):
  76. known.add(f'{mod}.{m}')
  77. for m in re.findall(r'^\s{4}(\w+)\s*[:,\[]', nos, re.M):
  78. known.add(f'{mod}.{m}')
  79. # + known drafts without DEF
  80. for mod, procs in {
  81. 'Express': ['ParseExpression','ParseSimpleExpression','EmitRangeCheck','EmitCompare','ParseDesignatorTail','ParseTerm','ParseFactor','ParseSetCons','ParseFuncCall','ParseTypeCast','ExpandStdProc43','ExpandStdProc44','ExpandStdProc45','ExpandStdProc46','ParseSetOrCastTail','LoadOperand','StoreOperand','FoldRelation','CheckRelationTypes','GetConstVal','GetTypeDesc','ParseAssignment','IsOrdinal','IsSet','BaseTypeOf','FoldConstOp','ConstToCard','ConstToInt','MatchOpClass','CheckAssignable','ParseSelector','PushExprDesc','PopExprDesc','CentralError','EvalConstExpr'],
  82. 'Statemen': ['ParseStatement','GuardedStatSeq','ParseBeginBlock','ParseCaseStmt','ParseCaseTail','ParseFor','ParseWhile','ParseRepeat','ParseIf','ParseLoop','ParseExit','ParseResult','ParseWith','ParseReturn','CalcMemOffset','SyncToStatStart','ParseStatSeqLoop','EmitJumpFixup','NextSymWrap','CloseBlockChain','EmitNameHash','EmitComBytes'],
  83. 'Pass1': ['ParseType','ParseFormalParams','ParseValParam','ParseVarParam','ParseFieldList','EvalConstExpr','CheckConformant','ParseRecordType','CloseScopeFixup','ParseConstDecl','ParseVarDecl','GetIdent','ParseImportItem','OpenScopeEmit','TypeSizeRec','ParseProcedureDecl','ParseModuleHead','ParseExportList','ParseOneDecl','ParseDeclarations','ParseImportFrom','ParseModuleBlock','ParseProcBody','ParseMainModule','WriteMcdHeader','WriteOutputFiles','EmitConstVal','EmitTypeDesc','PushTempMark','PopTempMark','AllocSymEntry','LinkSymEntry','InsertSetConst','CondInsert','InsertEntry','CheckForwardResolved','Pass1Main','InitPass1','EmitModuleProlog','ParseDeclSync','ParseConstOrTypeArm','ParseModuleArm','TryParseDecl','ParseImportScope','CappedStrLen'],
  84. 'SymTab': ['FindModule','EnterModule','LookupChain','MatchSym','SymbolAssert','CheckNode','NewString','GetName','PutAttr','GetAttr','AllocSym','EnterChained','EnterIdent','UnlinkScope','InitHeaps','InsertBuiltin','EnterStandards','LoadSymFile','FindExport','ListExports','ImportOne','ImportAll','SkipQualident','DefineDefinitionModule','BuildBitsetConst','DefineProgramModule','InitSymbolTables','NoteImported','DoImportList','CompileBody','CheckForwards','TryCompile','CompileDispatch','Checksum7w','SaveSymbolFile','RestoreAfterEdit'],
  85. }.items():
  86. known.update(f'{mod}.{p}' for p in procs)
  87. import collections
  88. susp = collections.Counter()
  89. for f in sorted(pathlib.Path(sys.argv[1]).glob('*.MOD')):
  90. for i, line in enumerate(f.read_text().splitlines(), 1):
  91. code = line.split('(*')[0]
  92. for m in re.findall(r'\b(Scanner|Errors|CodeGen|Compiler|SymTab|Express|Statemen|Pass1|Loader|Files|Texts|Terminal|ComLine|Doubles|Editor|Storage|System)\.(\w+)', code):
  93. q = f'{m[0]}.{m[1]}'
  94. if q not in known and m[1] not in ('WriteString','WriteLn','WriteChar','WriteCard','SetCol','ReadBytes','WriteBytes','SetPos','Open','Close','Create','Delete','Allocate','MARK','RELEASE','MOVE','ADR','BITSET','CARDINAL','ADDRESS','ORD','CHR','CAP','HIGH','NIL','TRUE','TRUE','HALT','RETURN','INC','DEC','INCL','EXCL','IN','ABS','ODD','SIZE','HALT','Close','Create','Delete','Open','ReadBytes','WriteBytes','SetPos','FileSize','NextPos','NoTrailer','ReadByte','WriteByte','ResetSys','UseError','EndError','DiskFull','Call','LibOpen','LibRead','InitLibs','LoadError','firstDrive','Edit','editPosition','editOkFlag','ReadChar','ReadAgain','numCols','commandLine','inName','outName','codepos','execute','RedirectOutput','PromptFor','qcp','qadd','qsub','qmul','qdiv','qneg','qabs','StrToDouble','DerefAliasType','StrLen','FindIdent','InsertSymbol','CopyStringToHeap','Allocate','GetStackMark','CheckStackMark','OpenScope','NewNode','NewSizedNode','DeclareIdentifier','MakeIdentifierNode','ExpectStringLiteral','ChangeFileExtension','GetSym','AcceptSymbol','ExpectSymbol','TestSymbolInSet','NeedIdentifier','ReportError'):
  95. susp[(f.name, q)] += 1
  96. for (f, q), n in sorted(susp.items()):
  97. print(f'{f}:{q} x{n}')
  98. PY
  99. if [ "$fail" -ne 0 ]; then say "STAGE 1 FAILED"; exit 1; fi
  100. say "STAGE 1 PASSED"
  101. rm -rf "$tmp"