Commit Graph
32 Commits
Author SHA1 Message Date
coolguy 718c323938 spec: 타깃을 i386 하나로 정하고 far 를 언어에서 뺀다
세그먼트 주소 지정은 x86 리얼모드에만 있는 개념이고, 평평한 주소 공간을 가진
다른 32비트 프로세서에는 대응물이 없다. 영구 제외다.

usize/isize 는 타깃의 포인터 폭이며 특정 비트 수를 약속하지 않는다. 오늘
32비트지만 u32 와 자동 변환되지 않는다. bits16 에서 배울 것은 '32로 정하자'가
아니라 '폭이 언어 의미론으로 새어나가지 않게 하자'이고, 그것이 나중에 다른
폭의 타깃을 여는 유일한 장치다.

타깃이 하나이므로 레이아웃에서 pointer_bits 분기가 전부 사라졌다. 인터럽트
핸들러와 공유 상태는 v0.2 로 내렸다 -- 문법은 아직 파싱되지만 명세의 약속은
아니다.
2026-08-17 05:40:49 +09:00
coolguy 25d9de65e1 implement: 제네릭 모노모피제이션 (SPEC 9)
타입 인자를 바인딩한 상태로 선언을 인스턴스마다 한 번씩 검사한다. 바인딩된
이름은 그냥 그 인자 타입이므로 본문, 필드 타입, 시그니처가 모두 같은 규칙으로
풀린다. 인스턴스 정체성은 선언 유닛 + 선언 + 인자 철자다.

찾은 버그 셋:

- 파서가 comptime 파라미터의 이름을 'comptime' 이라는 키워드에서 가져갔다.
  타입 파라미터 이름이 전부 comptime 이 되어 아무것도 바인딩되지 않았다.
- 인스턴스 이름이 중첩마다 길어져서, 깊은 사슬에서 잘린 이름끼리 충돌해
  재귀가 깊이 제한에 닿기 전에 조용히 멈췄다. 길어지면 인자를 일련번호로
  적어 정체성을 유지한다.
- 순서 비교 연산자가 피연산자 타입을 보지 않아 구조체끼리 비교해도 통과했다.
  제네릭과 무관한 기존 구멍이다.

fixture 셋이 명세와 어긋나 있어 명세를 따랐다. badbody 와 badop 은 호출 지점을
primary error 로 기대했지만 SPEC 9 는 본문의 연산이 primary 이고 호출에는
'instantiated here' note 를 붙이라고 한다. okscope 는 제네릭 본문이 호출자의
이름을 본다고 기대했지만 SPEC 9 는 정의 유닛에서 해석한다 -- badscope 로 옮기고
이유를 적었다.

188/188.
2026-08-17 05:24:36 +09:00
coolguy a265901d7e implement: 유닛 경계를 넘는 이름, 가시성, 기본 에러 집합
import 가 만든 binding 으로 다른 유닛의 선언에 닿는다. 호출, 구조체 리터럴,
값 참조 세 자리다. 시그니처의 타입은 그 시그니처가 쓰인 유닛에서 해석한다 --
호출한 쪽에서 해석하면 같은 이름이 다른 타입을 가리킨다.

pub 없는 선언과 필드는 자기 유닛 밖에서 보이지 않는다.

error.Name 은 구현된 적이 없었다. 기본 에러 집합 core.Error 의 멤버이고, 그
집합은 선언이 아니라 수집으로 채워지므로 변이 목록 없이 정체성만 갖는다.

units 34/34. dotpriv/main 은 위반이 있는 유닛을 import 하므로 받아들여질 수
없다 -- 마커를 붙이고 이유를 적었다.
2026-08-17 05:06:16 +09:00
coolguy de02b8bbbe tests: bad_bufw 가 무엇을 막는지 기록하고 진단을 고정한다
보고서가 '애매'로 남긴 하나다. io.buf_writer 는 351e5db 가 Writer 핸들 enum
으로 교체하면서 없앤 콜백 방식 writer이고, 이 fixture 는 그것이 돌아오지 않게
막으려고 그 커밋에서 추가됐다. 파일만 봐서는 알 수 없어 주석으로 남긴다.

pin 94 -> 95. 마커 없는 fixture는 이제 없다.
2026-08-17 04:52:48 +09:00
coolguy 1be47fa11c tests: 마커 없던 fixture 36개에 진단을 고정한다
마커가 없으면 러너는 '거부되기만 하면 통과'로 판정한다. 엉뚱한 이유로
거부돼도 초록이었다. fixture 별로 무엇을 검사하는지 읽고 지금 나오는 진단이
그 규칙을 짚는지 확인한 뒤 줄과 문구를 고정했다. 근거는 fixture-report.md에
있다.

pin 58 -> 94. 통과 수는 150/188 그대로다.
2026-08-17 04:51:58 +09:00
coolguy 7636a41a4e Revert "tests: fixture 파일명과 unit 동기화"
밑줄만 제거하고 충돌한 이름에 숫자를 붙인 결과라 이름이 무엇을 검사하는지
오히려 덜 드러낸다. own/badfld 와 types/badfld 가 서로 다른 것을 검사하는데
둘 다 badfmem 이 된 것이 그 증거다. 파일을 열어 판단하는 작업이므로
마커 판정과 함께 다시 한다.
2026-08-17 04:21:13 +09:00
coolguy 79aa2208f1 tests: pending-backend README 추가 2026-08-17 04:14:35 +09:00
coolguy d22964cdb6 tests: fixture 파일명과 unit 동기화 2026-08-17 04:14:30 +09:00
coolguy 88b0c83538 fixture README를 현재 상태에 맞게 정리 2026-08-17 04:05:13 +09:00
coolguy d1a4020087 own fixture 파일명과 unit 식별자 정리 2026-08-17 04:05:11 +09:00
coolguy 38dddc234c implement: load the unit graph -- imports, cycles, bindings
The driver handled one file. It now loads the graph rooted at the entry file
and checks every unit in it.

The import root is derived rather than configured: a unit named `a.b` read
from `<root>/a/b.fe` fixes `<root>`, so a sibling `import c.d;` is looked for
at `<root>/c/d.fe`. That is enough for the fixtures and for any tree that
follows 8.1, and it means there is no path flag to get wrong yet.

Loading is depth-first with the chain of units currently open kept on a stack,
so meeting one again is a cycle rather than a revisit -- a unit reached twice
by different paths is loaded once. Cycles, imports with no source file, and two
imports claiming the same binding are all reported.

One thing this exposed: FeDiags held a single source buffer, so once a build
spanned several files every excerpt was drawn from whichever file was parsed
last. `cycle/b.fe:3` printed the text of a.fe. fe_diags_source switches it, and
loading and checking both set it per unit.

The cycle fixtures lost their line markers on purpose. Which import closes the
cycle depends on which file you enter from -- entering at a.fe reports b.fe,
entering at b.fe reports a.fe -- so pinning a line would pin an arbitrary half
of a symmetric pair. The message is pinned; the line is not.

units: missing, bindconf and cycle pass, on top of the identity cases.
146 -> 148 of 188. What is left in units/ needs cross-unit name resolution and
visibility: an importer still cannot see `util.answer`.
2026-08-17 03:54:43 +09:00
coolguy 51e2568ba7 implement: unit identity -- dotted paths, name rules, source path
Units start here, with the part that needs no import graph: what a unit is
called and where it must live.

The parser only ever read a single identifier after `unit` and `import`, so
`unit game.main;` and `import std.io;` were syntax errors -- which is why the
dotted fixtures failed at the semicolon. It now reads a dotted path and stores
it canonically, dots included, since that spelling is the unit's identity
everywhere else. `import a.b as c;` parses too, with the alias on the node.

resolve.c is the new pass between parsing and checking, for the questions that
span files. It carries SPEC 8.1 so far: each path segment is ASCII lowercase,
starts with a letter, continues with letters, digits or underscore, and is at
most eight characters; and the dotted path must match the source path it was
read from, so game.world.map has to come from game/world/map.fe. The source
side is folded to lowercase before comparing, because a case-insensitive host
must not let two spellings become two units.

That rule then applied to the fixtures, which were not obeying it: 57 declared
a unit name unrelated to their file, left over from the milestone directories,
and eight had names too long to be legal. Both are now aligned -- the rule is
worth having only if the tree follows it.

units: badupper, badlong and unitbad pass. 138 -> 146 of 188. The rest of
units/ needs the import graph, which is the next piece: resolution, cycles,
bindings and visibility.
2026-08-17 03:47:28 +09:00
coolguy 547d8c5ec2 fix: reconcile the ERROR markers with what the checker reports
Checking the markers for the first time found five disagreements in areas that
are implemented. Four were the marker's fault:

- own/badarg pinned "self", but the rule being broken is that a returned
  reference must derive from a parameter -- `self` has nothing to do with it.
- own/badbrmov pinned line 9, which is the closing brace; the second destroy is
  on line 8.
- own/badloop pinned line 8, the destroy after the loop. The diagnostic is on
  line 6, inside it, and line 6 is right: the second iteration moves the same
  value again, so the loop body is where it is caught. Whoever wrote the marker
  expected the error after the loop.
- optional/badcatch pinned line 14, the body of the catch block. The catch
  expression on line 13 is what cannot fall through.

The fifth was the compiler's. own/badweak assigns a `&mut i32` to a `&i32` and
got "initializer type mismatch", which says nothing about why. Weakening an
exclusive borrow to a shared one is a specific rule and now says so, for
references and slices alike.

own/ is fully green: 50/50. Overall 133 -> 138 of 188. The six remaining marker
disagreements are all under units/ and generic/, where nothing is implemented
yet, so there is no diagnostic to compare against and no way to tell whether
the marker is right.
2026-08-17 03:42:06 +09:00
coolguy fb65152901 refactor: keep the front end, drop everything downstream of it
The milestone structure had stopped describing the compiler and started
shaping it: m7.c, check_m7.c, tests/m2..m9, and a checker and emitter that had
each grown past 2,500 lines because there was nowhere else to put anything.
Restart from the pipeline instead.

What is left is the front end -- lexer, parser, types, ownership, semantic
analysis -- and the fixtures that describe it. The C backend, the DOSBox-X
runner, the milestone registry and the batch build are removed. The driver now
stops after semantic analysis; a code generator attaches where emit_c did.

Fixtures move from milestone directories to what they check:

    parse/     grammar            own/       ownership and borrowing
    types/     type rules         optional/  optionals and error unions
    format/    formatting, try    units/     units and visibility
    generic/   generics           pending-backend/

pending-backend/ holds the three fixtures that can only be checked by running
a program -- that the bounds check traps, that --no-checks removes it, and that
drops and defers actually fire, verified through a fake allocator. Those are
not front-end tests and are not pretending to be; they come back first when
there is a code generator.

tests/run.py replaces the DOSBox-X harness. It builds the front end with the
pinned Watcom's Windows-hosted driver and runs every fixture in about two
seconds, and it does something the old runner structurally could not: it reads
the `// ERROR:line:text` marker each fixture carries and checks the diagnostic
against it. Those markers have been in the tree all along, unverified, because
DOS could not redirect the compiler's stderr and only the exit code was ever
compared.

133/188 pass. The 55 failures are not regressions -- they are what was already
true and invisible:

- units (27) and generic (23): `import`, `comptime` and generic declarations
  parse and are then dropped on the floor. No pass looks at them. The old
  registry did not list these fixtures at all, so nothing said so.
- five in own/, optional/ and generic/: a marker disagrees with the diagnostic
  about the line or the wording. Each is either a wrong marker or a wrong
  diagnostic and has to be read individually.

Everything removed is in git history.
2026-08-17 03:33:23 +09:00
coolguy d5ba699744 dev: require the pinned toolchain, and clear the QEMU-era leftovers
The host gate accepted a WATCOM environment override and skipped when nothing
was found. Both are wrong for what it is: a system-wide Open Watcom is a
different version reporting different diagnostics, and a gate that skips is a
gate that is not running, which is the exact shape of the problem this file was
added to close. It now uses .dosboxx/watcom only and fails with the setup
command when that is absent, matching how dosboxx.py already behaves.

Nothing else in the project reaches for a system install: the DOS session sets
WATCOM=W: before calling BUILD.BAT, so the C:\DEVEL\WATCOMC fallback inside it
is unreachable.

fec/test-dos.bat was tracked but dead -- the runner generates RUN.BAT and only
copies build-dos.bat -- so it goes, along with the comment claiming it drives
the build and the three fixture READMEs that still pointed at it. The registry
decides what runs now.

(.qemu/ is untracked local debris from the same era and is left alone.)
2026-08-17 02:26:02 +09:00
coolguy 6713a934f5 fix: emit mem.create and mem.alloc_slice on the M7 path
m7_emit_call reimplements call emission and only carried over mem.destroy and
mem.replace, so every mem.create/mem.alloc_slice fell through to the generic
member path and emitted `fe_missing.create(0)`. wcc386 does not diagnose that
-- it terminates with exit 255, which wcl386 reports as "Unable to invoke
wcc386.exe" with no message at all.

The breakage covered all 17 allocation sites in m5/runtime.fe, and it was
invisible because m5-owned and m5-defer only emit C; m5-runtime is the one M5
fixture that compiles and links what was generated.

Also make runtime.fe legal: `run` used `try` while returning i32, which SPEC
allows only in a function returning an error union. It is `-> !i32` now, which
M7 accepts because contextual success construction lands with it, and
runtime.c takes the { error, value } struct the error union lowers to.

Found by calling wcc386 directly instead of through wcl386, which is the only
way to see a compiler crash here.

M1-M7: 183 passed.
2026-08-17 00:01:51 +09:00
coolguyandClaude Opus 5 0a434d8a9a spec: record why the try rule cannot be enforced yet
SPEC.md allows `try` only inside a function returning an error union, but
check.c only tests it in the FE_N_EXPR_STMT case, so `var x = try e;` and
`x = try e;` walk straight past. m5/owned.fe and m5/runtime.fe both depend on
that gap.

Moving the check onto the try expression is a four-line change and it is
correct, but it cannot land yet. runtime.fe's `run` allocates and returns a
value, so closing the hole forces it to return an error union -- and master
rejects `return <value>;` in `-> !i32` ("return type mismatch") as well as a
bare `return;` in `-> !void` ("void expression returned from value function").
Both need contextual success construction, which is M7 work. `catch` and
`@trap()`, the two spellings SPEC offers as alternatives, are also M7-only, so
there is no way to express `run` legally on master today. All three paths were
tried in DOSBox-X, not assumed.

Fix owned.fe now, since `main() -> !void` is legal today and matches
m4/try-fpr.fe, and leave the checker alone until M7 lands with the rest.

Verified: 155 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BScg8CF1sAAM2zVHAu5zvW
2026-08-16 23:23:47 +09:00
coolguyandClaude Opus 5 5529e3dc14 test: prove --no-checks removes the bounds check
The bounds-no-checks cases emitted and built but never ran, so they only
proved that --no-checks produces compilable C -- not that it removes the
check, which is the entire point of the flag.

A run case could not simply be appended. BOUNDS.FE returns the out-of-bounds
element directly, so with checks removed its exit code is whatever sits past
the array on the stack and there is no correct status to assert. Asserting on
the generated C instead does not work either: emit_c.c defines fe_trap_bounds
unconditionally and --no-checks only suppresses the call sites.

Add NOCHK.FE, which reads one element past a [2]i32 and returns x - x. That
is 0 for whatever garbage the unchecked read produced, so the same source has
a defined outcome both ways: compiled with checks it must trap, compiled with
--no-checks it must run to completion and exit 0. Register both halves and
drop the two BOUNDS-N cases they supersede.

Verified in DOSBox-X: 155 passed, including m3-nochk-trap failing as expected
and m3-nochk-off-run succeeding.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BScg8CF1sAAM2zVHAu5zvW
2026-08-16 22:23:41 +09:00
coolguy 748505513d test: audit M6-M9 fixtures for v0.1.8 2026-08-16 20:00:27 +09:00
정시원 f2e17d265f add M6-M9 specification tests (#3) 2026-08-16 19:08:48 +09:00
coolguy d915a7e63c chore: remove obsolete host test scripts 2026-08-16 18:54:23 +09:00
coolguy deafd27939 feat: complete M5 ownership and cleanup 2026-08-16 18:48:35 +09:00
coolguy 351e5dbb23 feat: replace M4 callback writers with safe handles 2026-08-16 18:27:26 +09:00
coolguy 42abc0d7e7 feat: align M3 slices and strings with v0.1.7 2026-08-16 18:23:30 +09:00
coolguyandClaude Opus 5 f88559c635 wip: extend M5 ownership tests and cleanup emission
조건부 이동, 이중 destroy, 직접 drop 호출에 대한 실패 fixture를 추가하고
runtime harness와 test-dos.bat를 그에 맞춰 갱신한다.

M5는 아직 완료가 아니다. 모든 경로에서 정확히 1회 cleanup, defer/drop의 선언
역순 병합, try 전파 경로 cleanup, MaybeMoved 런타임 live flag, struct drop과
필드 역순 drop, 분기/루프 상태 합류, 누수/이중해제 카운터 harness가 남아 있다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012PQm6oAvWX4Lp3iSN5AHGT
2026-08-16 16:54:41 +09:00
정시원 d4b0dc52a8 Merge pull request #1 from sebastianrcnt/agent/cli-diagnostics-tools
feat: improve diagnostics and compiler CLI tooling
2026-08-16 14:22:16 +09:00
coolguy 631d2ebb48 wip: advance M5 ownership cleanup 2026-08-16 14:22:03 +09:00
정시원 799c8b0a83 test: cover compiler cli tooling 2026-08-16 14:02:33 +09:00
coolguy 53bca214b8 feat: implement M4 formatting builtins 2026-08-16 13:24:25 +09:00
coolguy 8e6a409637 feat: implement M3 aggregate types and iteration 2026-08-16 08:49:50 +09:00
coolguy da78a615fb feat: add M2 type checking and C emission 2026-08-16 07:40:50 +09:00
coolguy 005056a5ea feat: implement M1 Ferro frontend 2026-08-16 07:10:53 +09:00