Releases: Hawkynt/PB-Compiler
Releases · Hawkynt/PB-Compiler
Release list
nightly-20260705
Added
- O10 extended to console-setter coalescing: an earlier LOCATE folds when the next setter covers everything it set - a later LOCATE whose argument-present mask (row/column) is a superset, or a CLS (which clears AND homes the cursor) - and nothing between them writes output or reads cursor state (PRINT/PRINT#, CSRLIN/POS/SCREEN reads, user calls, control flow are observers); a CLS directly re-cleared by another CLS folds likewise; LOCATEs with cursor-shape arguments (3+) never fold, and all argument expressions must be console-pure + 4 pruner tests (consecutive LOCATEs, PRINT/CSRLIN observers keep both, partial-mask coverage both directions, CLS/CLS + CLS-kills-LOCATE) + screen-capture oracle run: the optimized build with folded setters is screen-identical to $OPTIMIZE OFF (a457d19)
nightly-20260704
Added
- C1 completed for everything that pays without register residency: MOVZX/MOVSX byte loads under $CPU 80386 (a BYTE/SBYTE cell read widens in one instruction instead of MOV + XOR AH / CBW) joining the already-shipped constant-divisor 32-bit IDIV/DIV, inline IMUL EAX for LONG multiply, QUAD bitwise/SHLD-SHRD shifts, 32-bit shift/rotate collapse, DWORD block primitives and branchless SETcc relational results * the two remaining checklist lines are recorded where they belong: LONG add/sub stay the two-op ADD/ADC pair because a scratch-staged EAX form is SLOWER without register residency, and scaled-LEA addressing needs the 32-bit ModRM/SIB encoder - both are the 386 register-allocation substrate tracked under O5's 386 tier + DOSBox run: BYTE/SBYTE widening loads verified output-identical optimized vs unoptimized under $CPU 80386 $OPTIMIZE SPEED (6ce640a)
nightly-20260703
Changed
- bit-field accesses lower minimally: a constant write folds its masked/shifted value to a single literal at desugar time (r.s = r.s AND 248 OR 5 instead of ... OR 5 AND 7; ... AND 15 OR 192 instead of ... OR (12 AND 15) * 16), and a constant 0 drops the OR entirely (clear-only) * bit-field write to a field covering its whole container skips the read-modify-write - the BYTE/WORD member store truncates the value itself * bit-field read drops the redundant width mask when the field reaches the container's top bit (offset+width = container bits) - the logical right shift of the unsigned container already cleared everything below, so PRINT r.s \ 16 AND 15 becomes PRINT r.s \ 16; a full-container field reads the member directly + 4 binder tests pinning the minimized shapes (constant fold, zero-write clear-only, full-container plain store, top-of-container maskless read) (8a88a91)
nightly-20260702
Added
- pb36 first-class functions: procedures are values, delegates and events invoke like SUBs + implicit address-of: a bare procedure name assigned to a delegate-typed (or DWORD) variable or added to an event takes its address - f = Triple / OnClick += Log1 need no CODEPTR32 (TryImplicitCodePtr, applied only when the name is not a variable, so a pointer-holding DWORD stays as-is) + event raising is just invoking: OnClick(42), OnClick 42 and CALL OnClick(42) are all equivalent to RAISE OnClick(42) - BindCallStatement routes an event-named call through the shared BuildRaiseGroup + delegate-typed variables invoke like SUBs in statement position: x 15 / CALL x(15) / x(15) route through the typed pointer-call path (ProcPtrStatementCalls side-table -> EmitProcPtrCall, closure env included); a FUNCTION delegate's discarded result stays balanced (float popped, owned string freed) + statement-bodied SUB lambdas: DIM x = SUB(y AS INTEGER) PRINT y * 2 lifts to an anonymous SUB (LambdaExpr.StatementBody); the DIM infers a full delegate type from the lambda's signature so x is immediately callable; lambda parameters are BYVAL by default (delegate params are values; pb36-only, no legacy BYREF expectation) # a SUB lambda's lifted proc no longer gets a result variable with a null type (BindLambdaBodies crashed codegen with an NRE in LayoutFrame) + array slice spreads: ..b(lo TO hi) inside an array literal spreads a slice of another static array - either bound omissible (LBOUND/UBOUND) or from-end ^n, e.g. DIM a = {1, ..b(0 TO 2), ..b(TO ^5), 5..9, ..c(^2 TO)}; bounds are compile-time constants, each slice expands to per-element reads (verified: exact expected element sequence at runtime, identical after the pb35 round-trip) # back-emitter ScopeSymbol now also probes the "name()" array key, so a slice-initialized module array's DIM keeps its bounds under pb35 * the back-emitter renders statement delegate calls as BYVAL arg temps + CALL DWORD (ptr)(...) (result temp only for FUNCTION delegates, matching the thunk shape) * docs/PB36.md documents first-class functions and slice spreads; docs/DECOMPILATION.md regenerated - 52 examples, 47 identical-output round-trips (both new features round-trip), 4 pb35-inexpressible unchanged + 4 round-trip tests (event-call syntaxes, SUB lambda + direct invocation, implicit CODEPTR32, slice expansion incl. from-end); all 2188 unit tests green, 246/246 corpus compile-clean; end-to-end verified output 1|2|3|4|30|40|24 for the first-class program and the exact slice sequence (d9d86c9)
nightly-20260630
Changed
- decompilation reference: show the pb3.6 source next to its decompilation, and broaden coverage of features and optimizations + gen-decompilation.sh now prints the pb3.6 source as a visible block right above the decompilation (was hidden in a collapsed ), so each lowering is readable at a glance + 13 new feature examples closing every uncovered pb36 gate + key sub-variants: XMS/EMS arrays, DEFER, wide integers (INT128+), tuple return+destructure, tuple parallel swap, FOR EACH over a range, capturing lambda, bit-field members, TYPE ALIGN, TYPE explicit-AT overlay, TYPE constructor, generic procedure, manual coroutine driver - 43 feature + 3 optimization examples now + pure-function folding (O25) made source-visible: --emit-basic --optimize consults OptPureFold.Analyze, so a pure constant-argument call folds to its literal in the decompilation (PRINT Cube&(4) -> PRINT 64) - the third optimization example, alongside dead-code (O2) and DEF SEG (O10), which are the only optimizer passes whose effect reaches the source level (the rest rewrite machine code below emit-basic) * back-emitter lowerings so the new features round-trip with identical output (42/46 now): * a UDT/tuple-returning FUNCTION (sret) emits as a SUB with the result buffer as a trailing BYREF parameter and the result-via-name assignments remapped to it (was FUNCTION ... AS NONE with a 3-vs-2 argument mismatch that did not recompile) * a layout-transformed TYPE (bit-fields, ALIGN, SIZE, explicit AT) emits its RESOLVED layout - packed $bits words and STRING*n padding reproducing the byte offsets - so member access binds and LEN() agrees; overlapping AT offsets (a union view) fall back to the surface form * a constant-amount logical shift lowers to multiply / integer-divide by 2^k (SHL/SHR have no pb35 operator usable in an assignment RHS), exact for the masked bit-field values * the 4 genuinely pb35-inexpressible cases stay honestly marked (output diverges): arithmetic shift-right + rotates, far-pointer offset arithmetic, capturing closures (no pb35 closure env), and an AT-overlay union view + 2 back-emitter tests (pure-fold visibility, sret-as-SUB); all 2179 unit tests green, 246/246 corpus compile-clean, output-equivalence 42/46 (c2b2914)
nightly-20260628
Added
- back-emitter: lower function-pointer delegates to pb35 via a BYREF-result thunk + CALL DWORD - 30/32 decompilation examples now round-trip with identical output (was 27). A function called through a code pointer has no pb35 expression form, but it can be wrapped: a typed proc pointer / named delegate becomes a DWORD; an inline lambda or CODEPTR32(func) emits CODEPTR32(thunk), where the thunk is a generated SUB taking the arguments plus a trailing BYREF result (Sresult = target(args)); and the call site is hoisted to materialize the args into BYREF temps and a result temp, then CALL DWORD (ptr)(args, result) - the same struct-return trick the compiler uses internally * TypeText/TypeNameText/FormatVarDecl render ProcPtrType (and a named-delegate-typed variable) as DWORD; LambdaExpr -> CODEPTR32(thunk) via LambdaProcs; CODEPTR32(userFunction) -> CODEPTR32(thunk); the hoist pass now handles ProcPtrCalls alongside ternaries * the only two non-round-tripping examples left are 08 (rotate operators - no pb35 equivalent) and 09 (far-pointer offset arithmetic); the decompilation marks them as diverging, honestly + DOSBox harnesses run headless: gen-decompilation.sh starts a single shared Xvfb display (no window pops up, no per-call xvfb-run cost); the scratchpad round-trip harnesses use xvfb-run when present + 3 delegate round-trip tests (typed proc pointer -> DWORD+thunk+CALL DWORD, inline lambda -> CODEPTR32(thunk), named delegate); all 2178 unit tests green * no regression: 246/246 corpus compile-clean, output-equivalence harness 30/32, docs/DECOMPILATION.md regenerated (30 identical-output, 2 diverge) (beeb35c)
nightly-20260627
Changed
- back-emitter: lower the deep pb36 features to real, output-equivalent PB 3.5 - building the decompilation reference with an OUTPUT-equivalence gate (run the program both ways, diff RESULT.TXT - not just "does it recompile") raised round-trip fidelity from a true 11/32 to 27/32: * TYPE methods / properties / operators: lifted THIS-receiver procedures, member calls and synthesized backing fields now use sanitized pb35-valid names (Counter.Bump -> Counter_Bump, THIS.$Current -> THIS.S_Current); the lifted-member RESULT is assigned through the emitted function name, not its ResultName (a property getter returned 0 before) * generics: the monomorphized type (Box@Long) is emitted from the resolved type table as TYPE Box_Long and DIMs name it via the bound symbol, instead of emitting the uninstantiated T template * coroutines: the synthesized enumerator TYPE (with its $state/$Current backing fields) is emitted, and the implicit FOR EACH enumerator local is given an explicit DIM, so the MoveNext state machine actually iterates under pb35 * TRY/CATCH/FINALLY: lowered to the ON ERROR machinery (handler label + ON ERROR GOTO 0 + finally), matching the codegen lowering at the source level * nested SUB/FUNCTION: an unqualified nested call (Bump) resolves to the lifted name (Outer_Bump) via the call binding, matching the definition * identifier sanitization (
$/@/. -> letter-leading underscores) preserves a trailing $ /@ type-suffix (STR$/HEX$ no longer mangled to STR_); synthesized UDT-typed locals get explicit DIMs # the function-pointer/delegate features (21/22/23) are PROVABLY not expressible in PB 3.5 - it cannot call a code pointer and yield a result (verified) - so they stay illustrative by necessity, not omission; rotate operators and far-pointer offset arithmetic likewise have no faithful pb35 form (08/09 diverge) * gen-decompilation.sh now annotates each example by OUTPUT equivalence via DOSBox (Round-trips ✅ same output / Illustrative - diverges / Illustrative - inexpressible), falling back to a compile check without an emulator: 27 round-trip with identical output, 2 diverge, 3 inexpressible + 5 round-trip tests (TRY, TYPE method, generic, coroutine, nested SUB) re-binding under pb35 * no regression: 246/246 corpus compile-clean, pb35 runtime byte-identical, all unit tests green (e8d727e)
nightly-20260621
Added
- pb36 OPERATOR overloading: a TYPE may define OPERATOR + / - / * / \ / ^ / = / <> / < / > / <= / >= / MOD / AND / OR / XOR (other AS T) AS RetType ... END OPERATOR, with THIS the left operand and RESULT the result; it lifts to a member Type.op_(THIS, other). A binary expression a b whose left operand is that TYPE resolves to the overload at compile time (no dispatch) - a scalar-returning operator (e.g. = overriding the built-in UDT compare) works in any expression, a TYPE-returning operator (e.g. +) uses struct return so c = a + b writes straight into c. New LanguageFeature.OperatorOverloading (pb36); the OPERATOR member lifts through the existing TYPE-member + struct-return machinery (4e70ce6)
Changed
- docs: OPERATOR overloading documented in README, DIALECTS.md and marked done in the wishlist (581bf81)
nightly-20260614
Added
- pb IR binary-record conversions (CodeGen/Ir): MKI$/MKL$/MKDWD$/MKS$/MKD$ encode a number to its raw little-endian bytes as a string (rt_str_mki/mkl/mkdwd/mks/mkd, argument coerced to the exact width), and CVI/CVL/CVDWD/CVS/CVD decode a number back from a record string (rt_str_cvi/.../cvd); these complete the random/binary file-record toolchain + StringLoweringTests: MK*/CV* round-trip lowers to the runtime calls (eb30041)