From 5250d7d90ddfc67e54a22b77eb573e63403e6f54 Mon Sep 17 00:00:00 2001 From: Kamila Szewczyk Date: Sun, 12 Nov 2023 14:49:57 +0100 Subject: [PATCH] stuff --- langsmoke | 0 ref_6502 | 1244 +++++++ ref_apl | 2531 +++++++++++++ ref_c | 5010 ++++++++++++++++++++++++++ ref_cpp | 9263 ++++++++++++++++++++++++++++++++++++++++++++++++ ref_java | 9761 +++++++++++++++++++++++++++++++++++++++++++++++++++ ref_x86 | 5117 +++++++++++++++++++++++++++ ref_z80 | 9176 ++++++++++++++++++++++++++++++++++++++++++++++++ service.asm | 1094 ------ 9 files changed, 42102 insertions(+), 1094 deletions(-) mode change 100644 => 100755 langsmoke create mode 100644 ref_6502 create mode 100644 ref_apl create mode 100644 ref_c create mode 100644 ref_cpp create mode 100644 ref_java create mode 100644 ref_x86 create mode 100644 ref_z80 delete mode 100644 service.asm diff --git a/langsmoke b/langsmoke old mode 100644 new mode 100755 diff --git a/ref_6502 b/ref_6502 new file mode 100644 index 0000000..2e04eb9 --- /dev/null +++ b/ref_6502 @@ -0,0 +1,1244 @@ + +; +; 6551 I/O Port Addresses +; +ACIADat = $7F70 +ACIASta = $7F71 +ACIACmd = $7F72 +ACIACtl = $7F73 +; +; page zero variables +; +BOARD = $50 +BK = $60 +PIECE = $B0 +SQUARE = $B1 +SP2 = $B2 +SP1 = $B3 +INCHEK = $B4 +STATE = $B5 +MOVEN = $B6 +REV = $B7 +OMOVE = $DC +WCAP0 = $DD +COUNT = $DE +BCAP2 = $DE +WCAP2 = $DF +BCAP1 = $E0 +WCAP1 = $E1 +BCAP0 = $E2 +MOB = $E3 +MAXC = $E4 +CC = $E5 +PCAP = $E6 +BMOB = $E3 +BMAXC = $E4 +BMCC = $E5 ; was BCC (TASS doesn't like it as a label) +BMAXP = $E6 +XMAXC = $E8 +WMOB = $EB +WMAXC = $EC +WCC = $ED +WMAXP = $EE +PMOB = $EF +PMAXC = $F0 +PCC = $F1 +PCP = $F2 +OLDKY = $F3 +BESTP = $FB +BESTV = $FA +BESTM = $F9 +DIS1 = $FB +DIS2 = $FA +DIS3 = $F9 +temp = $FC +; +; +; + *=$1000 ; load into RAM @ $1000-$15FF + + LDA #$00 ; REVERSE TOGGLE + STA REV + JSR Init_6551 +CHESS CLD ; INITIALIZE + LDX #$FF ; TWO STACKS + TXS + LDX #$C8 + STX SP2 +; +; ROUTINES TO LIGHT LED +; DISPLAY AND GET KEY +; FROM KEYBOARD +; +OUT JSR pout ; DISPLAY AND + JSR KIN ; GET INPUT *** my routine waits for a keypress +; CMP OLDKY ; KEY IN ACC *** no need to debounce +; BEQ OUT ; (DEBOUNCE) +; STA OLDKY +; + CMP #$43 ; [C] + BNE NOSET ; SET UP + LDX #$1F ; BOARD +WHSET LDA SETW,X ; FROM + STA BOARD,X ; SETW + DEX + BPL WHSET + LDX #$1B ; *ADDED + STX OMOVE ; INITS TO $FF + LDA #$CC ; Display CCC + BNE CLDSP +; +NOSET CMP #$45 ; [E] + BNE NOREV ; REVERSE + JSR REVERSE ; BOARD IS + SEC + LDA #$01 + SBC REV + STA REV ; TOGGLE REV FLAG + LDA #$EE ; IS + BNE CLDSP +; +NOREV CMP #$40 ; [P] + BNE NOGO ; PLAY CHESS + JSR GO +CLDSP STA DIS1 ; DISPLAY + STA DIS2 ; ACROSS + STA DIS3 ; DISPLAY + BNE CHESS +; +NOGO CMP #$0D ; [Enter] + BNE NOMV ; MOVE MAN + JSR MOVE ; AS ENTERED + JMP DISP +NOMV CMP #$41 ; [Q] ***Added to allow game exit*** + BEQ DONE ; quit the game, exit back to system. + JMP INPUT ; process move +DONE JMP $FF00 ; *** MUST set this to YOUR OS starting address +; +; THE ROUTINE JANUS DIRECTS THE +; ANALYSIS BY DETERMINING WHAT +; SHOULD OCCUR AFTER EACH MOVE +; GENERATED BY GNM +; +; +; +JANUS LDX STATE + BMI NOCOUNT +; +; THIS ROUTINE COUNTS OCCURRENCES +; IT DEPENDS UPON STATE TO INDEX +; THE CORRECT COUNTERS +; +COUNTS LDA PIECE + BEQ OVER ; IF STATE=8 + CPX #$08 ; DO NOT COUNT + BNE OVER ; BLK MAX CAP + CMP BMAXP ; MOVES FOR + BEQ XRT ; WHITE +; +OVER INC MOB,X ; MOBILITY + CMP #$01 ; + QUEEN + BNE NOQ ; FOR TWO + INC MOB,X +; +NOQ BVC NOCAP + LDY #$0F ; CALCULATE + LDA SQUARE ; POINTS +ELOOP CMP BK,Y ; CAPTURED + BEQ FOUN ; BY THIS + DEY ; MOVE + BPL ELOOP +FOUN LDA POINTS,Y + CMP MAXC,X + BCC LESS ; SAVE IF + STY PCAP,X ; BEST THIS + STA MAXC,X ; STATE +; +LESS CLC + PHP ; ADD TO + ADC CC,X ; CAPTURE + STA CC,X ; COUNTS + PLP +; +NOCAP CPX #$04 + BEQ ON4 + BMI TREE ;(=00 ONLY) +XRT RTS +; +; GENERATE FURTHER MOVES FOR COUNT +; AND ANALYSIS +; +ON4 LDA XMAXC ; SAVE ACTUAL + STA WCAP0 ; CAPTURE + LDA #$00 ; STATE=0 + STA STATE + JSR MOVE ; GENERATE + JSR REVERSE ; IMMEDIATE + JSR GNMZ ; REPLY MOVES + JSR REVERSE +; + LDA #$08 ; STATE=8 + STA STATE ; GENERATE +; JSR OHM ; CONTINUATION + JSR UMOVE ; MOVES +; + JMP STRATGY ; FINAL EVALUATION +NOCOUNT CPX #$F9 + BNE TREE +; +; DETERMINE IF THE KING CAN BE +; TAKEN, USED BY CHKCHK +; + LDA BK ; IS KING + CMP SQUARE ; IN CHECK? + BNE RETJ ; SET INCHEK=0 + LDA #$00 ; IF IT IS + STA INCHEK +RETJ RTS +; +; IF A PIECE HAS BEEN CAPTURED BY +; A TRIAL MOVE, GENERATE REPLIES & +; EVALUATE THE EXCHANGE GAIN/LOSS +; +TREE BVC RETJ ; NO CAP + LDY #$07 ; (PIECES) + LDA SQUARE +LOOPX CMP BK,Y + BEQ FOUNX + DEY + BEQ RETJ ; (KING) + BPL LOOPX ; SAVE +FOUNX LDA POINTS,Y ; BEST CAP + CMP BCAP0,X ; AT THIS + BCC NOMAX ; LEVEL + STA BCAP0,X +NOMAX DEC STATE + LDA #$FB ; IF STATE=FB + CMP STATE ; TIME TO TURN + BEQ UPTREE ; AROUND + JSR GENRM ; GENERATE FURTHER +UPTREE INC STATE ; CAPTURES + RTS +; +; THE PLAYER'S MOVE IS INPUT +; +INPUT CMP #$08 ; NOT A LEGAL + BCS ERROR ; SQUARE # + JSR DISMV +DISP LDX #$1F +SEARCH LDA BOARD,X + CMP DIS2 + BEQ HERE ; DISPLAY + DEX ; PIECE AT + BPL SEARCH ; FROM +HERE STX DIS1 ; SQUARE + STX PIECE +ERROR JMP CHESS +; +; GENERATE ALL MOVES FOR ONE +; SIDE, CALL JANUS AFTER EACH +; ONE FOR NEXT STE? +; +; +GNMZ LDX #$10 ; CLEAR +GNMX LDA #$00 ; COUNTERS +CLEAR STA COUNT,X + DEX + BPL CLEAR +; +GNM LDA #$10 ; SET UP + STA PIECE ; PIECE +NEWP DEC PIECE ; NEW PIECE + BPL NEX ; ALL DONE? + RTS ; #NAME? +; +NEX JSR RESET ; READY + LDY PIECE ; GET PIECE + LDX #$08 + STX MOVEN ; COMMON START + CPY #$08 ; WHAT IS IT? + BPL PAWN ; PAWN + CPY #$06 + BPL KNIGHT ; KNIGHT + CPY #$04 + BPL BISHOP ; BISHOP + CPY #$01 + BEQ QUEEN ; QUEEN + BPL ROOK ; ROOK +; +KING JSR SNGMV ; MUST BE KING! + BNE KING ; MOVES + BEQ NEWP ; 8 TO 1 +QUEEN JSR LINE + BNE QUEEN ; MOVES + BEQ NEWP ; 8 TO 1 +; +ROOK LDX #$04 + STX MOVEN ; MOVES +AGNR JSR LINE ; 4 TO 1 + BNE AGNR + BEQ NEWP +; +BISHOP JSR LINE + LDA MOVEN ; MOVES + CMP #$04 ; 8 TO 5 + BNE BISHOP + BEQ NEWP +; +KNIGHT LDX #$10 + STX MOVEN ; MOVES +AGNN JSR SNGMV ; 16 TO 9 + LDA MOVEN + CMP #$08 + BNE AGNN + BEQ NEWP +; +PAWN LDX #$06 + STX MOVEN +P1 JSR CMOVE ; RIGHT CAP? + BVC P2 + BMI P2 + JSR JANUS ; YES +P2 JSR RESET + DEC MOVEN ; LEFT CAP? + LDA MOVEN + CMP #$05 + BEQ P1 +P3 JSR CMOVE ; AHEAD + BVS NEWP ; ILLEGAL + BMI NEWP + JSR JANUS + LDA SQUARE ; GETS TO + AND #$F0 ; 3RD RANK? + CMP #$20 + BEQ P3 ; DO DOUBLE + JMP NEWP +; +; CALCULATE SINGLE STEP MOVES +; FOR K,N +; +SNGMV JSR CMOVE ; CALC MOVE + BMI ILL1 ; -IF LEGAL + JSR JANUS ; -EVALUATE +ILL1 JSR RESET + DEC MOVEN + RTS +; +; CALCULATE ALL MOVES DOWN A +; STRAIGHT LINE FOR Q,B,R +; +LINE JSR CMOVE ; CALC MOVE + BCC OVL ; NO CHK + BVC LINE ; NOCAP +OVL BMI ILL ; RETURN + PHP + JSR JANUS ; EVALUATE POSN + PLP + BVC LINE ; NOT A CAP +ILL JSR RESET ; LINE STOPPED + DEC MOVEN ; NEXT DIR + RTS +; +; EXCHANGE SIDES FOR REPLY +; ANALYSIS +; +REVERSE LDX #$0F +ETC SEC + LDY BK,X ; SUBTRACT + LDA #$77 ; POSITION + SBC BOARD,X ; FROM 77 + STA BK,X + STY BOARD,X ; AND + SEC + LDA #$77 ; EXCHANGE + SBC BOARD,X ; PIECES + STA BOARD,X + DEX + BPL ETC + RTS +; +; CMOVE CALCULATES THE TO SQUARE +; USING SQUARE AND THE MOVE +; TABLE FLAGS SET AS FOLLOWS: +; N#NAME? MOVE +; V#NAME? (LEGAL UNLESS IN CR) +; C#NAME? BECAUSE OF CHECK +; [MY &THANKS TO JIM BUTTERFIELD +; WHO WROTE THIS MORE EFFICIENT +; VERSION OF CMOVE) +; +CMOVE LDA SQUARE ; GET SQUARE + LDX MOVEN ; MOVE POINTER + CLC + ADC MOVEX,X ; MOVE LIST + STA SQUARE ; NEW POS'N + AND #$88 + BNE ILLEGAL ; OFF BOARD + LDA SQUARE +; + LDX #$20 +LOOP DEX ; IS TO + BMI NO ; SQUARE + CMP BOARD,X ; OCCUPIED? + BNE LOOP +; + CPX #$10 ; BY SELF? + BMI ILLEGAL +; + LDA #$7F ; MUST BE CAP! + ADC #$01 ; SET V FLAG + BVS SPX ; (JMP) +; +NO CLV ; NO CAPTURE +; +SPX LDA STATE ; SHOULD WE + BMI RETL ; DO THE + CMP #$08 ; CHECK CHECK? + BPL RETL +; +; CHKCHK REVERSES SIDES +; AND LOOKS FOR A KING +; CAPTURE TO INDICATE +; ILLEGAL MOVE BECAUSE OF +; CHECK SINCE THIS IS +; TIME CONSUMING, IT IS NOT +; ALWAYS DONE +; +CHKCHK PHA ; STATE #392 + PHP + LDA #$F9 + STA STATE ; GENERATE + STA INCHEK ; ALL REPLY + JSR MOVE ; MOVES TO + JSR REVERSE ; SEE IF KING + JSR GNM ; IS IN + JSR RUM ; CHECK + PLP + PLA + STA STATE + LDA INCHEK + BMI RETL ; NO - SAFE + SEC ; YES - IN CHK + LDA #$FF + RTS +; +RETL CLC ; LEGAL + LDA #$00 ; RETURN + RTS +; +ILLEGAL LDA #$FF + CLC ; ILLEGAL + CLV ; RETURN + RTS +; +; REPLACE PIECE ON CORRECT SQUARE +; +RESET LDX PIECE ; GET LOGAT + LDA BOARD,X ; FOR PIECE + STA SQUARE ; FROM BOARD + RTS +; +; +; +GENRM JSR MOVE ; MAKE MOVE +GENR2 JSR REVERSE ; REVERSE BOARD + JSR GNM ; GENERATE MOVES +RUM JSR REVERSE ; REVERSE BACK +; +; ROUTINE TO UNMAKE A MOVE MADE BY +; MOVE +; +UMOVE TSX ; UNMAKE MOVE + STX SP1 + LDX SP2 ; EXCHANGE + TXS ; STACKS + PLA ; MOVEN + STA MOVEN + PLA ; CAPTURED + STA PIECE ; PIECE + TAX + PLA ; FROM SQUARE + STA BOARD,X + PLA ; PIECE + TAX + PLA ; TO SOUARE + STA SQUARE + STA BOARD,X + JMP STRV +; +; THIS ROUTINE MOVES PIECE +; TO SQUARE, PARAMETERS +; ARE SAVED IN A STACK TO UNMAKE +; THE MOVE LATER +; +MOVE TSX + STX SP1 ; SWITCH + LDX SP2 ; STACKS + TXS + LDA SQUARE + PHA ; TO SQUARE + TAY + LDX #$1F +CHECK CMP BOARD,X ; CHECK FOR + BEQ TAKE ; CAPTURE + DEX + BPL CHECK +TAKE LDA #$CC + STA BOARD,X + TXA ; CAPTURED + PHA ; PIECE + LDX PIECE + LDA BOARD,X + STY BOARD,X ; FROM + PHA ; SQUARE + TXA + PHA ; PIECE + LDA MOVEN + PHA ; MOVEN +STRV TSX + STX SP2 ; SWITCH + LDX SP1 ; STACKS + TXS ; BACK + RTS +; +; CONTINUATION OF SUB STRATGY +; -CHECKS FOR CHECK OR CHECKMATE +; AND ASSIGNS VALUE TO MOVE +; +CKMATE LDY BMAXC ; CAN BLK CAP + CPX POINTS ; MY KING? + BNE NOCHEK + LDA #$00 ; GULP! + BEQ RETV ; DUMB MOVE! +; +NOCHEK LDX BMOB ; IS BLACK + BNE RETV ; UNABLE TO + LDX WMAXP ; MOVE AND + BNE RETV ; KING IN CH? + LDA #$FF ; YES! MATE +; +RETV LDX #$04 ; RESTORE + STX STATE ; STATE=4 +; +; THE VALUE OF THE MOVE (IN ACCU) +; IS COMPARED TO THE BEST MOVE AND +; REPLACES IT IF IT IS BETTER +; +PUSH CMP BESTV ; IS THIS BEST + BCC RETP ; MOVE SO FAR? + BEQ RETP + STA BESTV ; YES! + LDA PIECE ; SAVE IT + STA BESTP + LDA SQUARE + STA BESTM ; FLASH DISPLAY +RETP LDA #"." ; print ... instead of flashing disp + Jmp syschout ; print . and return +; +; MAIN PROGRAM TO PLAY CHESS +; PLAY FROM OPENING OR THINK +; +GO LDX OMOVE ; OPENING? + BMI NOOPEN ; -NO *ADD CHANGE FROM BPL + LDA DIS3 ; -YES WAS + CMP OPNING,X ; OPPONENT'S + BNE END ; MOVE OK? + DEX + LDA OPNING,X ; GET NEXT + STA DIS1 ; CANNED + DEX ; OPENING MOVE + LDA OPNING,X + STA DIS3 ; DISPLAY IT + DEX + STX OMOVE ; MOVE IT + BNE MV2 ; (JMP) +; +END LDA #$FF ; *ADD - STOP CANNED MOVES + STA OMOVE ; FLAG OPENING +NOOPEN LDX #$0C ; FINISHED + STX STATE ; STATE=C + STX BESTV ; CLEAR BESTV + LDX #$14 ; GENERATE P + JSR GNMX ; MOVES +; + LDX #$04 ; STATE=4 + STX STATE ; GENERATE AND + JSR GNMZ ; TEST AVAILABLE +; +; MOVES +; + LDX BESTV ; GET BEST MOVE + CPX #$0F ; IF NONE + BCC MATE ; OH OH! +; +MV2 LDX BESTP ; MOVE + LDA BOARD,X ; THE + STA BESTV ; BEST + STX PIECE ; MOVE + LDA BESTM + STA SQUARE ; AND DISPLAY + JSR MOVE ; IT + JMP CHESS +; +MATE LDA #$FF ; RESIGN + RTS ; OR STALEMATE +; +; SUBROUTINE TO ENTER THE +; PLAYER'S MOVE +; +DISMV LDX #$04 ; ROTATE +DROL ASL DIS3 ; KEY + ROL DIS2 ; INTO + DEX ; DISPLAY + BNE DROL ; + ORA DIS3 + STA DIS3 + STA SQUARE + RTS +; +; THE FOLLOWING SUBROUTINE ASSIGNS +; A VALUE TO THE MOVE UNDER +; CONSIDERATION AND RETURNS IT IN +; THE ACCUMULATOR +; + +STRATGY CLC + LDA #$80 + ADC WMOB ; PARAMETERS + ADC WMAXC ; WITH WHEIGHT + ADC WCC ; OF O25 + ADC WCAP1 + ADC WCAP2 + SEC + SBC PMAXC + SBC PCC + SBC BCAP0 + SBC BCAP1 + SBC BCAP2 + SBC PMOB + SBC BMOB + BCS POS ; UNDERFLOW + LDA #$00 ; PREVENTION +POS LSR + CLC ; ************** + ADC #$40 + ADC WMAXC ; PARAMETERS + ADC WCC ; WITH WEIGHT + SEC ; OF 05 + SBC BMAXC + LSR ; ************** + CLC + ADC #$90 + ADC WCAP0 ; PARAMETERS + ADC WCAP0 ; WITH WEIGHT + ADC WCAP0 ; OF 10 + ADC WCAP0 + ADC WCAP1 + SEC ; [UNDER OR OVER- + SBC BMAXC ; FLOW MAY OCCUR + SBC BMAXC ; FROM THIS + SBC BMCC ; SECTION] + SBC BMCC + SBC BCAP1 + LDX SQUARE ; *************** + CPX #$33 + BEQ POSN ; POSITION + CPX #$34 ; BONUS FOR + BEQ POSN ; MOVE TO + CPX #$22 ; CENTRE + BEQ POSN ; OR + CPX #$25 ; OUT OF + BEQ POSN ; BACK RANK + LDX PIECE + BEQ NOPOSN + LDY BOARD,X + CPY #$10 + BPL NOPOSN +POSN CLC + ADC #$02 +NOPOSN JMP CKMATE ; CONTINUE + + +;----------------------------------------------------------------- +; The following routines were added to allow text-based board +; display over a standard RS-232 port. +; +POUT jsr pout9 ; print CRLF + jsr pout13 ; print copyright + JSR POUT10 ; print column labels + LDY #$00 ; init board location + JSR POUT5 ; print board horz edge +POUT1 lDA #"|" ; print vert edge + JSR syschout ; PRINT ONE ASCII CHR - SPACE + LDX #$1F +POUT2 TYA ; scan the pieces for a location +match + CMP BOARD,X ; match found? + BEQ POUT4 ; yes; print the piece's color and +type + DEX ; no + BPL POUT2 ; if not the last piece, try again + tya ; empty square + and #$01 ; odd or even column? + sta temp ; save it + tya ; is the row odd or even + lsr ; shift column right 4 spaces + lsr ; + lsr ; + lsr ; + and #$01 ; strip LSB + clc ; + adc temp ; combine row & col to determine +square color + and #$01 ; is board square white or blk? + bne pout25 ; white, print space + lda #"*" ; black, print * + .byte $2c ; used to skip over LDA #$20 +POUT25 LDA #$20 ; ASCII space + JSR syschout ; PRINT ONE ASCII CHR - SPACE + JSR syschout ; PRINT ONE ASCII CHR - SPACE +POUT3 INY ; + TYA ; get row number + AND #$08 ; have we completed the row? + BEQ POUT1 ; no, do next column + LDA #"|" ; yes, put the right edge on + JSR syschout ; PRINT ONE ASCII CHR - | + jsr pout12 ; print row number + JSR POUT9 ; print CRLF + JSR POUT5 ; print bottom edge of board + CLC ; + TYA ; + ADC #$08 ; point y to beginning of next row + TAY ; + CPY #$80 ; was that the last row? + BEQ POUT8 ; yes, print the LED values + BNE POUT1 ; no, do new row + +POUT4 LDA REV ; print piece's color & type + BEQ POUT41 ; + LDA cpl+16,X ; + BNE POUT42 ; +POUT41 LDA cpl,x ; +POUT42 JSR syschout ; + lda cph,x ; + jsr syschout ; + BNE POUT3 ; branch always + +POUT5 TXA ; print "-----...-----" + PHA + LDX #$19 + LDA #"-" +POUT6 JSR syschout ; PRINT ONE ASCII CHR - "-" + DEX + BNE POUT6 + PLA + TAX + JSR POUT9 + RTS + +POUT8 jsr pout10 ; + LDA $FB + JSR syshexout ; PRINT 1 BYTE AS 2 HEX CHRS + LDA #$20 + JSR syschout ; PRINT ONE ASCII CHR - SPACE + LDA $FA + JSR syshexout ; PRINT 1 BYTE AS 2 HEX CHRS + LDA #$20 + JSR syschout ; PRINT ONE ASCII CHR - SPACE + LDA $F9 + JSR syshexout ; PRINT 1 BYTE AS 2 HEX CHRS + +POUT9 LDA #$0D + JSR syschout ; PRINT ONE ASCII CHR - CR + LDA #$0A + JSR syschout ; PRINT ONE ASCII CHR - LF + RTS + +pout10 ldx #$00 ; print the column labels +POUT11 lda #$20 ; 00 01 02 03 ... 07 + jsr syschout + txa + jsr syshexout + INX + CPX #$08 + BNE POUT11 + BEQ POUT9 +POUT12 TYA + and #$70 + JSR syshexout + rts + +Pout13 ldx #$00 ; Print the copyright banner +Pout14 lda banner,x + beq POUT15 + jsr syschout + inx + bne POUT14 +POUT15 rts + +KIN LDA #"?" + JSR syschout ; PRINT ONE ASCII CHR - ? + JSR syskin ; GET A KEYSTROKE FROM SYSTEM + AND #$4F ; MASK 0-7, AND ALPHA'S + RTS +; +; 6551 I/O Support Routines +; +; +Init_6551 lda #$1F ; 19.2K/8/1 + sta ACIActl ; control reg + lda #$0B ; N parity/echo off/rx int off/ +dtr active low + sta ACIAcmd ; command reg + rts ; done +; +; input chr from ACIA1 (waiting) +; +syskin lda ACIASta ; Serial port status + and #$08 ; is recvr full + beq syskin ; no char to get + Lda ACIAdat ; get chr + RTS ; +; +; output to OutPut Port +; +syschout PHA ; save registers +ACIA_Out1 lda ACIASta ; serial port status + and #$10 ; is tx buffer empty + beq ACIA_Out1 ; no + PLA ; get chr + sta ACIAdat ; put character to Port + RTS ; done + +syshexout PHA ; prints AA hex digits + LSR ; MOVE UPPER NIBBLE TO LOWER + LSR ; + LSR ; + LSR ; + JSR PrintDig ; + PLA ; +PrintDig PHY ; prints A hex nibble (low 4 bits) + AND #$0F ; + TAY ; + LDA Hexdigdata,Y ; + PLY ; + jmp syschout ; + +Hexdigdata .byte "0123456789ABCDEF" +banner .byte "MicroChess (c) 1996-2002 Peter Jennings, +peterj@benlo.com" + .byte $0d, $0a, $00 +cpl .byte "WWWWWWWWWWWWWWWWBBBBBBBBBBBBBBBBWWWWWWWWWWWWWWWW" +cph .byte "KQCCBBRRPPPPPPPPKQCCBBRRPPPPPPPP" + .byte $00 +; +; end of added code +; +; BLOCK DATA + *= $1580 +SETW .byte $03, $04, $00, $07, $02, $05, $01, $06 + .byte $10, $17, $11, $16, $12, $15, $14, $13 + .byte $73, $74, $70, $77, $72, $75, $71, $76 + .byte $60, $67, $61, $66, $62, $65, $64, $63 + +MOVEX .byte $00, $F0, $FF, $01, $10, $11, $0F, $EF, $F1 + .byte $DF, $E1, $EE, $F2, $12, $0E, $1F, $21 + +POINTS .byte $0B, $0A, $06, $06, $04, $04, $04, $04 + .byte $02, $02, $02, $02, $02, $02, $02, $02 + +OPNING .byte $99, $25, $0B, $25, $01, $00, $33, $25 + .byte $07, $36, $34, $0D, $34, $34, $0E, $52 + .byte $25, $0D, $45, $35, $04, $55, $22, $06 + .byte $43, $33, $0F, $CC + +; +; +; end of file + + +TMP EQU $6 ; Temporary storage + +WEEKDAY: + CPX #3 ; Year starts in March to bypass + BCS MARCH ; leap year problem + DEY ; If Jan or Feb, decrement year +MARCH EOR #$7F ; Invert A so carry works right + CPY #200 ; Carry will be 1 if 22nd century + ADC MTAB-1,X ; A is now day+month offset + STA TMP + TYA ; Get the year + JSR MOD7 ; Do a modulo to prevent overflow + SBC TMP ; Combine with day+month + STA TMP + TYA ; Get the year again + LSR ; Divide it by 4 + LSR + CLC ; Add it to y+m+d and fall through + ADC TMP +MOD7 ADC #7 ; Returns (A+3) modulo 7 + BCC MOD7 ; for A in 0..255 + RTS +MTAB DB 1,5,6,3,1,5,3,0,4,2,6,4 ; Month offsets + +R0L EQU $0 +R0H EQU $1 +R14H EQU $1D +R15L EQU $1E +R15H EQU $1F +SAVE EQU $FF4A +RESTORE EQU $FF3F + + ORG $F689 + + AST 32 + + JSR SAVE ;PRESERVE 6502 REG CONTENTS + PLA + STA R15L ;INIT SWEET16 PC + PLA ;FROM RETURN + STA R15H ;ADDRESS +SW16B JSR SW16C ;INTERPRET AND EXECUTE + JMP SW16B ;ONE SWEET16 INSTR. +SW16C INC R15L + BNE SW16D ;INCR SWEET16 PC FOR FETCH + INC R15H +SW16D LDA >SET ;COMMON HIGH BYTE FOR ALL ROUTINES + PHA ;PUSH ON STACK FOR RTS + LDY $0 + LDA (R15L),Y ;FETCH INSTR + AND $F ;MASK REG SPECIFICATION + ASL ;DOUBLE FOR TWO BYTE REGISTERS + TAX ;TO X REG FOR INDEXING + LSR + EOR (R15L),Y ;NOW HAVE OPCODE + BEQ TOBR ;IF ZERO THEN NON-REG OP + STX R14H ;INDICATE "PRIOR RESULT REG" + LSR + LSR ;OPCODE*2 TO LSB'S + LSR + TAY ;TO Y REG FOR INDEXING + LDA OPTBL-2,Y ;LOW ORDER ADR BYTE + PHA ;ONTO STACK + RTS ;GOTO REG-OP ROUTINE +TOBR INC R15L + BNE TOBR2 ;INCR PC + INC R15H +TOBR2 LDA BRTBL,X ;LOW ORDER ADR BYTE + PHA ;ONTO STACK FOR NON-REG OP + LDA R14H ;"PRIOR RESULT REG" INDEX + LSR ;PREPARE CARRY FOR BC, BNC. + RTS ;GOTO NON-REG OP ROUTINE +RTNZ PLA ;POP RETURN ADDRESS + PLA + JSR RESTORE ;RESTORE 6502 REG CONTENTS + JMP (R15L) ;RETURN TO 6502 CODE VIA PC +SETZ LDA (R15L),Y ;HIGH ORDER BYTE OF CONSTANT + STA R0H,X + DEY + LDA (R15L),Y ;LOW ORDER BYTE OF CONSTANT + STA R0L,X + TYA ;Y REG CONTAINS 1 + SEC + ADC R15L ;ADD 2 TO PC + STA R15L + BCC SET2 + INC R15H +SET2 RTS +OPTBL DFB SET-1 ;1X +BRTBL DFB RTN-1 ;0 + DFB LD-1 ;2X + DFB BR-1 ;1 + DFB ST-1 ;3X + DFB BNC-1 ;2 + DFB LDAT-1 ;4X + DFB BC-1 ;3 + DFB STAT-1 ;5X + DFB BP-1 ;4 + DFB LDDAT-1 ;6X + DFB BM-1 ;5 + DFB STDAT-1 ;7X + DFB BZ-1 ;6 + DFB POP-1 ;8X + DFB BNZ-1 ;7 + DFB STPAT-1 ;9X + DFB BM1-1 ;8 + DFB ADD-1 ;AX + DFB BNM1-1 ;9 + DFB SUB-1 ;BX + DFB BK-1 ;A + DFB POPD-1 ;CX + DFB RS-1 ;B + DFB CPR-1 ;DX + DFB BS-1 ;C + DFB INR-1 ;EX + DFB NUL-1 ;D + DFB DCR-1 ;FX + DFB NUL-1 ;E + DFB NUL-1 ;UNUSED + DFB NUL-1 ;F + +* FOLLOWING CODE MUST BE +* CONTAINED ON A SINGLE PAGE! + +SET BPL SETZ ;ALWAYS TAKEN +LD LDA R0L,X +BK EQU *-1 + STA R0L + LDA R0H,X ;MOVE RX TO R0 + STA R0H + RTS +ST LDA R0L + STA R0L,X ;MOVE R0 TO RX + LDA R0H + STA R0H,X + RTS +STAT LDA R0L +STAT2 STA (R0L,X) ;STORE BYTE INDIRECT + LDY $0 +STAT3 STY R14H ;INDICATE R0 IS RESULT NEG +INR INC R0L,X + BNE INR2 ;INCR RX + INC R0H,X +INR2 RTS +LDAT LDA (R0L,X) ;LOAD INDIRECT (RX) + STA R0L ;TO R0 + LDY $0 + STY R0H ;ZERO HIGH ORDER R0 BYTE + BEQ STAT3 ;ALWAYS TAKEN +POP LDY $0 ;HIGH ORDER BYTE = 0 + BEQ POP2 ;ALWAYS TAKEN +POPD JSR DCR ;DECR RX + LDA (R0L,X) ;POP HIGH ORDER BYTE @RX + TAY ;SAVE IN Y REG +POP2 JSR DCR ;DECR RX + LDA (R0L,X) ;LOW ORDER BYTE + STA R0L ;TO R0 + STY R0H +POP3 LDY $0 ;INDICATE R0 AS LAST RESULT REG + STY R14H + RTS +LDDAT JSR LDAT ;LOW ORDER BYTE TO R0, INCR RX + LDA (R0L,X) ;HIGH ORDER BYTE TO R0 + STA R0H + JMP INR ;INCR RX +STDAT JSR STAT ;STORE INDIRECT LOW ORDER + LDA R0H ;BYTE AND INCR RX. THEN + STA (R0L,X) ;STORE HIGH ORDER BYTE. + JMP INR ;INCR RX AND RETURN +STPAT JSR DCR ;DECR RX + LDA R0L + STA (R0L,X) ;STORE R0 LOW BYTE @RX + JMP POP3 ;INDICATE R0 AS LAST RESULT REG +DCR LDA R0L,X + BNE DCR2 ;DECR RX + DEC R0H,X +DCR2 DEC R0L,X + RTS +SUB LDY $0 ;RESULT TO R0 + CPR SEC ;NOTE Y REG = 13*2 FOR CPR + LDA R0L + SBC R0L,X + STA R0L,Y ;R0-RX TO RY + LDA R0H + SBC R0H,X +SUB2 STA R0H,Y + TYA ;LAST RESULT REG*2 + ADC $0 ;CARRY TO LSB + STA R14H + RTS +ADD LDA R0L + ADC R0L,X + STA R0L ;R0+RX TO R0 + LDA R0H + ADC R0H,X + LDY $0 ;R0 FOR RESULT + BEQ SUB2 ;FINISH ADD +BS LDA R15L ;NOTE X REG IS 12*2! + JSR STAT2 ;PUSH LOW PC BYTE VIA R12 + LDA R15H + JSR STAT2 ;PUSH HIGH ORDER PC BYTE +BR CLC +BNC BCS BNC2 ;NO CARRY TEST +BR1 LDA (R15L),Y ;DISPLACEMENT BYTE + BPL BR2 + DEY +BR2 ADC R15L ;ADD TO PC + STA R15L + TYA + ADC R15H + STA R15H +BNC2 RTS +BC BCS BR + RTS +BP ASL ;DOUBLE RESULT-REG INDEX + TAX ;TO X REG FOR INDEXING + LDA R0H,X ;TEST FOR PLUS + BPL BR1 ;BRANCH IF SO + RTS +BM ASL ;DOUBLE RESULT-REG INDEX + TAX + LDA R0H,X ;TEST FOR MINUS + BMI BR1 + RTS +BZ ASL ;DOUBLE RESULT-REG INDEX + TAX + LDA R0L,X ;TEST FOR ZERO + ORA R0H,X ;(BOTH BYTES) + BEQ BR1 ;BRANCH IF SO + RTS +BNZ ASL ;DOUBLE RESULT-REG INDEX + TAX + LDA R0L,X ;TEST FOR NON-ZERO + ORA R0H,X ;(BOTH BYTES) + BNE BR1 ;BRANCH IF SO + RTS +BM1 ASL ;DOUBLE RESULT-REG INDEX + TAX + LDA R0L,X ;CHECK BOTH BYTES + AND R0H,X ;FOR $FF (MINUS 1) + EOR $FF + BEQ BR1 ;BRANCH IF SO + RTS +BNM1 ASL ;DOUBLE RESULT-REG INDEX + TAX + LDA R0L,X + AND R0H,X ;CHECK BOTH BYTES FOR NO $FF + EOR $FF + BNE BR1 ;BRANCH IF NOT MINUS 1 +NUL RTS +RS LDX $18 ;12*2 FOR R12 AS STACK POINTER + JSR DCR ;DECR STACK POINTER + LDA (R0L,X) ;POP HIGH RETURN ADDRESS TO PC + STA R15H + JSR DCR ;SAME FOR LOW ORDER BYTE + LDA (R0L,X) + STA R15L + RTS +RTN JMP RTNZ + + .org $f000 +init: + lda #$00 ; init output ports of pia 2 + sta mtalock ; reset multitasking lock + lda #$40 ; setup timer for free running + sta acr + lda #$c0 ; enable timer interrupts + sta ier + + ldy #0 + lda #0 +stinit: sta $100,y ; reset stack to x'00' + iny + bne stinit + + lda #$00 ; set actual task # to 0 + sta mtatask + ldy #maxtask-1 ; get max. number of tasks +initloop: + lda mtasini,y ; get initial stackpointer value + sta mtastab,y ; and save value in page 0 table + tax ; move stack pointer value to reg x + lda #$b0 ; set initial flag register contents + sta $0104,x ; save flag register on stack + tya ; get actual task number + asl a ; multiply with 2 + tax ; and move result to reg x + stx mtatemp ; save reg x + lda ent_tab,x ; get pcl-value + ldx mtastab,y ; get stack pointer value + sta $0105,x ; save pcl register on stack + ldx mtatemp ; get reg x + lda ent_tab+1,x ; get pch-value + ldx mtastab,y ; get stack pointer value + sta $0106,x ; save pch register on stack + dey + bpl initloop ; ==> loop for all tasks + ldx #$3f ; set stack for task 0 + txs + + lda #0 ; initial load timer + sta t1lsl + lda #40 ; about 1/100 sec timer value + sta t1csh + cli ; enable interrupts + jmp ent_task0 ; enter task 0 +;*------------------------------------------------------------------* +;* table of task entry addresses * +;*------------------------------------------------------------------* +ent_tab: + .word ent_task0 + .word ent_task1 + .word ent_task2 + .word ent_task3 + +;*------------------------------------------------------------------* +;* program call entry to interrupt service routine * +;*------------------------------------------------------------------* +mtaentry: + php ; save processor status on stack +; ; for interrupt simulation + pha ; save registers on current stack + txa + pha + tya + pha + tsx + + inc $105,x ; add 1 to return address + bne mtaent01 ; because of jsr command + inc $106,x +mtaent01: + + lda #$00 ; reset task lock + sta mtalock + jmp mtaswitch ; and process task switch + +;*------------------------------------------------------------------* +;* interrupt service routine * +;*------------------------------------------------------------------* +irq: + pha ; save registers on current stack + txa + pha + tya + pha + + lda t1csl ; enable interrupt + lda #$c0 ; reset flag + sta ifr + + lda mtalock ; is task locked ? + beq mtaswitch ; ==> no, then process task change + inc mtalock ; indicate task switch requested + jmp irq_ret ; ==> and skip task change + +mtaswitch: ; task switcher + ldy mtatask ; get actual task number + tsx ; get actual stack pointer + stx mtastab,y ; and save it in table + iny ; calculate next task number + cpy #maxtask ; valid task number ? + bcc mtanumok ; ==> yes + ldy #0 ; else start with task 0 +mtanumok: + sty mtatask ; save new task number + ldx mtastab,y ; get new stack pointer + txs ; and load it in sp-register + +irq_ret: + pla ; load registers from current stack + tay + pla + tax + pla + rti ; ==> go and process task + +mtasini .byte $39,$79,$b9,$f9 ; initial stackpointer values +maxtask =$-mtasini + + .org $fffa +nmivector .word init ; nmi vector +resvector .word init ; reset vector +irqvector .word irq ; irq vector + .end \ No newline at end of file diff --git a/ref_apl b/ref_apl new file mode 100644 index 0000000..e42eb2b --- /dev/null +++ b/ref_apl @@ -0,0 +1,2531 @@ +lam←{ + ⎕IO⎕ML←1 1 + ∆d←⍪('true' ('Lam' (,'t') ('Lam' (,'f') ('Var' (,'t'))))) + ∆d,←('false' ('Lam' (,'t') ('Lam' (,'f') ('Var' (,'f'))))) + ⍺←∆d⋄∆t←⍺⋄∆hi←{∆t,←⍺⍵}⋄∆hr←{∆t∘←((∆t↑[2]⍨¯1∘+),∆t↓[2]⍨⊢)⍵⍳⍨,1↑∆t}⋄err←{⍵⎕SIGNAL 8} + hash←{{1e10|⍺+31×⍵}/128+1(220⌶)⍵}⋄sd←'₀₁₂₃₄₅₆₇₈₉'⋄l←'()λ.='⋄ad←{⍺,sd[,1+10⊥⍣¯1⊢⍵]} + str←{'Lam'≡⊃⍵:∊'(λ'(2⊃⍵)'. '(∇3⊃⍵)')'⋄'Var'≡⊃⍵:2⊃⍵⋄'App'≡⊃⍵:∊'('(∇2⊃⍵)' '(∇3⊃⍵)')'} + lx←{0=≢⍵:⍬⋄(⊃⍵)∊l:(⊂0,⊃⍵),∇1↓⍵⋄3≠(⎕UCS 10 32)⍳⊃⍵:∇1↓⍵⋄'#'=⊃⍵:∇⍵↓⍨⍵⍳⎕UCS 10 + ×k←⊥⍨⌽⍵∊sd,⎕A,⎕C⎕A:(⊂1,k↑⍵),∇k↓⍵⋄err'eltoken'⍵} + pr←{L←0'λ'⋄P←0'('⋄E←0'='⋄C←0')'⋄D←0'.' + at←{P≡⊃⍵:{lx t←tr(1↓⍵)⋄C≢⊃lx:err'eparen'⋄(1↓lx) t}⍵ + 1≡⊃⊃⍵:(1↓⍵)('Var'(1↓⊃⍵))⋄L≡⊃⍵:ab 1↓⍵⋄err'etoken'} + ab←{i←⊥⍨⌽1=⊃¨⍵⋄i<1:err'elambda'⋄D≢⍵⊃⍨i+1:err'edot' + lx tv←tr(1+i)↓⍵⋄nm←1↓¨i↑⍵⋄lx(nm{0=≢⍺:⍵⋄(¯1↓⍺)∇'Lam'(⊃⌽⍺)⍵}tv)} + tr←{L≡⊃⍵:ab 1↓⍵⋄↑{(L≢⊃⍺)∧(1≢⊃⊃⍺)∧(P≢⊃⍺):⍺⍵⋄lx t←at ⍺⋄lx ∇'App'⍵ t}/at ⍵} + bi←{k←⊃⍵⋄lx v←tr 2↓⍵⋄0≠≢lx:err'estray'⋄_←∆hr k⋄_←∆hi k v⋄⍬} + E≡⊃1↓⍵:bi ⍵⋄⊃⌽tr ⍵} + a←⍪''0⋄ac←{∆i←{i←(,1↑a)⍳⊂⍵⋄_←⍺{i>⊃⌽⍴a:a,←⍵ 0⋄a[2;i]+←⍺⋄0}⍵⋄⍵ad,a[2;i]} + {'Var'≡⊃⍵:'Var' (0 ∆i ⊃⌽⍵)⋄'App'≡⊃⍵:'App' (∇2⊃⍵) (∇3⊃⍵) + 'Lam'≡⊃⍵:(⊂'Lam'),((∇3⊃⍵) ,⍨⍥⊂ (1 ∆i 2⊃⍵))}⍵} + de←{lk←{(⊂⍵)∊⍺:'Var'⍵⋄i←(⊂⍵)⍳⍨,1↑∆t⋄i>⊃⌽⍴∆t:'Var'⍵⋄⊃∆t[2;i]} + {⍺←⊂''⋄'Var'≡⊃⍵:(⍺ lk ⊃⌽⍵)⋄'App'≡⊃⍵:'App' (⍺∇2⊃⍵) (⍺∇3⊃⍵) + 'Lam'≡⊃⍵:(2↑⍵),⊂(⍺,⊂2⊃⍵)∇3⊃⍵}⍵} + br←{'Lam'≡⊃⍵:(2↑⍵),⊂∇3⊃⍵⋄'Var'≡⊃⍵:⍵⋄'App'≢⊃⍵:err'eint' + an bn←∇¨1↓⍵⋄'Lam'≢⊃an:⍵⋄an bn←1↓ac 'App' an bn⋄av←2⊃an + {v←'Var'≡⊃⍵⋄v∧av≡2⊃⍵:bn⋄v:⍵⋄v←'Lam'≡⊃⍵⋄v∧av≡2⊃⍵:⍵⋄v:(2↑⍵),⊂∇3⊃⍵ + 'App'≡⊃⍵:'App' (∇2⊃⍵) (∇3⊃⍵)}3⊃an} + rd←{h←⍬⋄i←{⍵∊h:1⋄h,←⍵⋄0}⋄in←de ⍵⋄r←br⍣{(i hash ⍺)∨⍺≡⍵}in⋄(in≡r)∨(hash r)∊¯1↓h:err'einf'⋄r} + ⍬≢ast←pr lx ⍵:str rd ast + } + + +⍝ apl-misc-math - Copyright (C) Kamila Szewczyk, 2022. +⍝ Redistributed under the terms of the AGPLv3 license. +⍝ Load using: ⎕fix'file:///.../apl-misc-math/mm.apl'⋄mm.setup + +⍝ Special thanks to Adám Brudzewsky. + +:Namespace mm + ⍝ Default settings. The library works optimally with + ⍝ higher precision arithmetic. + ##.(⎕FR⎕PP)←1287 34 + + ⍝ Alter to change the precision of operations. + ⍝ Note: A value too small will carry more error due to + ⍝ floating point inaccurancy. + epsilon←0.0000001 + int_prec←0.0001 + + ⍝ Braces were supposed to make the result shy, but apparently they don't. + ∇ {r}←setup + (_tanh_sinh_pf _tanh_sinh_m2)←↓(○.5)×5 6∘.○int_prec×⍳÷int_prec + _tanh_sinh_m2×←int_prec + (_tanh_xk _tanh_wkd)←↓7 6∘.○_tanh_sinh_pf + _tanh_sinh_m2÷←×⍨_tanh_wkd + _erf_c←2÷(○1)*.5 + euler_gamma←(+/∘÷∘⍳-⍟) lim_inf 1 ⍝ Alternatively: -digamma 1 + 'ok' + ∇ + + ⍝ d⍺⍺/dx |x=⍵ + derv←{epsilon÷⍨-/⍺⍺¨⍵+epsilon 0} + + ⍝ d^n⍺⍺/dx^n |x=⍵ + nderv←{⍵⍵=1:⍺⍺ D ⍵ ⋄ ((⍺⍺ D) ∇∇ (⍵⍵-1)) ⍵} + + ⍝ The secant root-finding method. ⍵ is starting x1,x2 + secant←{ + f←⍺⍺⋄⊃⌽{ + dy←-/y1 y2←f¨x1 x2←⍵ + x2,x1-y1×dy÷⍨-/⍵ + }⍣{epsilon>|-/⍺}⍵ + } + + ⍝ Trim insignificant real/imaginary parts. + ztrim←{¯9 ¯11+.○(⊢×epsilon<|)9 11∘.○⍵} + + ⍝ Durand-Kerner method for finding complex polynomial roots. + ⍝ 0.4J0.9 was chosen arbitrarily as a starting point. It is + ⍝ neither a real number nor a de Moivre number. + durand_kerner←{ + f←⊥∘((⊢÷⊃)⍵)⋄g←{⍵⍪⍉⍪f¨⍵} + ztrim¨,1↑{ + v←,1↑⍵⋄g{⍺-⍵÷×/0~⍨⍺-v}⌿⍵ + }⍣⍺ g 0.4J0.9*⎕io-⍨⍳1-⍨≢⍵ + } + + ⍝ The Faddeev-LeVerrier algorithm for finding the characteristic + ⍝ polynomial of a square matrix. + faddeev_leverrier←{ + ⎕io←0⋄(≠/⍴⍵)∨2≠≢⍴⍵:⍬⋄n←≢⍵ + M0←⍵⋄I←n n⍴1↑⍨1+n⋄⊃ { + ⍵=0:1 I⋄(cp MP)←∇⍵-1⋄X←M0+.×MP + c←(+/0 0⍉X)÷-⍵⋄(cp,c)(X+I×c) + } n + } + + ⍝ An extension to the Faddeev-LeVerrier implementation above that + ⍝ also keeps track of the matrix used to compute the inverse. + ⍝ The inverse can be obtained using inv cpoly←... and inv×-÷⊃⌽cpoly + faddeev_leverrier_ex←{ + ⎕io←0⋄(≠/⍴⍵)∨2≠≢⍴⍵:⍬⋄n←≢⍵⋄inv←⍬ + M0←⍵⋄I←n n⍴1↑⍨1+n⋄cpoly←⊃ { + ⍵=0:1 I⋄(cp MP)←∇⍵-1⋄X←M0+.×MP + c←(+/0 0⍉X)÷-⍵ + MC←X+I×c + _←{⍵=n-1:inv∘←MC⋄0}⍵ + (cp,c)MC + } n + inv cpoly + } + + ⍝ Eigenvector computation. + eigenvec←{ + ⎕io←0⋄(≠/⍴⍵)∨2≠≢⍴⍵:⍬ + n←≢⍵⋄I←n n⍴1↑⍨1+n⋄s←⍵-⍺×I + q←1,⍨1↑⍨1-⍨⊃⌽⍴s⋄ztrim¨1,⍨∊⌹⍨∘-/q⊂1↓s + } + + ⍝ A range function from dfns. + range←{↑+/⍵{⍵×{⍵-⎕IO}⍳1+0⌈⌊(⍺⍺-⍺)÷⍵+⍵=0}\1 ¯1×-\2↑⍺,⍺+×⍵-⍺} + + ⍝ Simpson integration. Assumes bounds ⍺<⍵. + simpson←{ + h←(⍵-⍺)÷S←÷int_prec + (h÷3)×+/(⍺+⍥⍺⍺ ⍵),⍺((⍺⍺⊣+h×⊢)×2×1+2|⊢⍤0)⍳S + } + + ⍝ Trapezoidal rule. + trapz←{ + ⍺=⍵:0 + sgn←¯1*⍺>⍵ + a b←⍺(⌊,⌈)⍵ + x←↑2,/(a+0 int_prec)range b + sgn×+/0.5×int_prec×+/⍺⍺⍤0⊢x + } + + ⍝ The tanh-sinh quadrature. + tanh_sinh←{ + ⍺>⍵:-⍵(⍺⍺∇∇)⍺ + ⍺ ⍵≡0 1:+/_tanh_sinh_m2×⍺⍺¨_tanh_xk + a b←⍺ ⍵⋄g←⍺⍺ + (b-a)×+/_tanh_sinh_m2×{g a+⍵×b-a}¨_tanh_xk + } + + ⍝ Some APLCart stuff I dislike grabbing over and over again. + median←2÷⍨1⊥⊢⌷⍨∘⊂⍋⌷⍨∘⊂∘⌈2÷⍨0 1+≢ + stddev←≢÷⍨2*∘÷⍨(≢×+.*∘2)-2*⍨+⌿ + diag←{⍵⊂⍤⊢⌸⍥,⍨+/↑⍳⍴⍵} ⍝ Antidiagonals as a vector of vectors. + + ⍝ Partition a n-element index array according to an invertible + ⍝ complexity function. + part_f←{⌽⌽¨(⌽⍳⍵)⊂⍨⍸⍣¯1⌊⍺⍺⍳⌊⍺⍺⍣¯1⊢⍵} + + ⍝ Complexity functions. Used in the partitioning algoithm, + ⍝ they include an additional n factor. + Onbang←⊢×! ⍝ O(n!) + Onlogn←×⍨×⍟ ⍝ O(n log n) + Ologn←⊢×⍟ ⍝ O(log n) + Osqrtn←⊢×(.5*⍨⊢) ⍝ O(sqrt(n)) + On3←⊢*∘4 ⍝ O(n^3) + On2←⊢*∘3 ⍝ O(n^2) + On←×⍨ ⍝ O(n) + O1←⊢ ⍝ O(1) + + ⍝ A primitive approximation of limits at infinity. + lim_inf←{0::⍺⍺ ⍵⋄x←⍺⍺¨ 0 1+⍵⋄epsilon<|-/x:⍺⍺∇∇(1+⍵)⋄⊃x} + + ⍝ The error function. + erf←{_erf_c×0(*∘-×⍨)simpson⍵} + + ⍝ The sine integrals. + Si←{0 (1∘○÷⊢)simpson ⍵} + si←{(mm.Si ⍵)-○.5} + + ⍝ The cosine integrals. + Cin←{0 {⍵÷⍨1-2○⍵}simpson ⍵} + Ci←{mm.euler_gamma + (⍟-mm.Cin)⍵} + + ⍝ Offset logarithmic integral. + Li←{2 (÷∘⍟)mm.simpson ⍵} + + ⍝ Partial derivatives. + invariant_a←{⍵⍵ ⍺⍺ ⍵} + invariant_b←{⍵ ⍺⍺ ⍵⍵} + pderv_a←{epsilon÷⍨-/(⍺⍺ invariant_b ⍵)¨⍺+epsilon 0} ⍝ Partial derivative df/d⍺ + pderv_b←{epsilon÷⍨-/(⍺⍺ invariant_a ⍺)¨⍵+epsilon 0} ⍝ Partial derivative df/d⍵ + + ⍝ The digamma function. + digamma←(!¯1∘+)derv÷(!¯1∘+) + + ⍝ Gradient vector. + nabla_grad←{⍺(⍺⍺ pderv_a,⍺⍺ pderv_b)⍵} +:EndNamespace + + + sim←{ + eunderspec←'Underspecified system. Missing the definition of nodes: ' + enoint←'Failed to ensure integrity of the system' + epref←'Invalid prefix in specifier ' + ea←' requires no arguments.' ' requires one argument.' ' requires two arguments.' + + ⍺←0.5 + code←{ + (2⊃¨v/⍨x)@(⍸x←∊⊃¨v←⎕VFI¨⍵)⊢⍵ + }¨' '(≠⊆⊢)¨⊃⎕NGET ⍵ 1 + ind←⊃¨srt←code[⍋⊃¨code] + sys←∪{⍵/⍨(2|⎕DR)¨⍵}↑,/2↓¨srt + vrf←sys∊⍥∊ind + 0=∧/vrf:(∊eunderspec(⍕sys/⍨~vrf))⎕SIGNAL 8 + (≢≠⊃∘⌽)ind:enoint ⎕SIGNAL 8 + leds←⍸(⊂'LED')≡¨2⊃¨srt + unpref←{'x'≠1↑⍵:(epref ⍵)⎕SIGNAL 8 ⋄ 1↓⍵} + load←{∊'v[',(⍕⍵),']'} + fmt←{{⍵/⍨(∨\∧∘⌽∨\∘⌽)' '≠⍵}∊('⍝'(≠⊆⊢)∊' '⍺' '),¨⍵,⊂⍬} + state←⎕NS ⍬ ⋄ state.v←0⍴⍨≢srt ⋄ state.t←0 + arity←⊂'HIGH' 'LOW' + arity,←⊂'NOT' 'LED' 'BUTTON' 'CLOCK' + arity,←⊂'AND' 'OR' 'XOR' 'XNOR' + chka←{ + 0=∨/ind←(⊂⍺)∘∊¨arity:0 + ⍵≠¯1+⍸ind:(∊⍺,ea[⍸ind])⎕SIGNAL 8 ⋄ 1 + } + src←∊'⋄'(1↓∘,,⍤0)(⊂'t+←1⋄⍬'),⍨{ + var op args←2(↑,⊂⍤↓)⍵ ⋄ _←op chka≢args + av←load¨var,args + op≡'AND':'⍝←⍝∧⍝'fmt av⋄op≡'OR':'⍝←⍝∨⍝'fmt av + op≡'XOR':'⍝←⍝≠⍝'fmt av⋄op≡'XNOR':'⍝←⍝=⍝'fmt av + op≡'NOT':'⍝←~⍝'fmt av⋄op≡'HIGH':'⍝←1'fmt⊂av + op≡'LOW':'⍝←0'fmt⊂av⋄op≡'LED':'' + op≡'BUTTON':'⍝←0≠⍝ t'fmt(⊂load var),args + op≡'CLOCK':'⍝←0=⍝|t'fmt(⊂load var),⊂unpref⊃args + (∊'unrecognised op 'op)⎕SIGNAL 8 + }¨srt + ⍺∘{ + _←'state'⍎src + ⎕←∊'Time: '(⍕state.t)' LEDs: '(⍕leds,¨state.v[3⊃¨srt[leds]]) + ⎕DL ⍺ + }⍣{0}⊢⍬ + } + + dx←{ + ⍝ import dfns + dfns←{⍵⊣⍵.⎕CY'dfns'}⎕NS⍬ + ⍝ namespace for symbol implementations + ns←⎕NS ⍬ + ⍝ code page + cp←'⌶%⍺⍵_abcdefghijklmnopqrstuvwxyz¯.' + cp,←'⍬0123456789⊢∆ABCDEFGHIJKLMNOPQRS' + cp,←'TUVWXYZ⍙[/⌿\⍀<≤=≥>≠∨∧-+÷×?∊"#&@:' + cp,←'⍷⋄←⍝)]⍴~↑↓⍳○*⌈⌊∇∘(⊂⊃∩∪⊥⊤|;,⍱⍲⍒⍋⍉' + cp,←'⌽⊖⍟⌹!⍕⍎⍫⍪≡≢⎕⍞⍣⍭√⍛⍢⍍…⍙φ⍗⍐⍦⍁⍮Φ⍡' + cp,←∊(⎕UCS 32)(⎕UCS 10) + ⍝ custom operations + t←⊂'~' '_Neg' ⋄ ns._Neg←{0=⍵} + t,←⊂'⍭' '_Pco' ⋄ ns._Pco←dfns.pco + t,←⊂'√' '_Root' ⋄ ns._Root←{⍺←2 ⋄ ⍵*÷⍺} + t,←⊂'<' '_Lt' ⋄ ns._Lt←{0=⎕NC'⍺':⍵-1 ⋄ ⍺<⍵} + t,←⊂'>' '_Gt' ⋄ ns._Gt←{0=⎕NC'⍺':⍵+1 ⋄ ⍺>⍵} + t,←⊂'⍛' '_Rc' ⋄ ns._Rc←{⍵⍵∘⍺⍺} + t,←⊂'⍢' '_Round' ⋄ ns._Round←{⍺←1 ⋄ ⍺(⊢∘××⊣×∘⌈¯0.5+∘|÷⍨)⍵} + t,←⊂'⍍' '_MatMul' ⋄ ns._MatMul←{0=⎕NC'⍺':(,⍨⍴1,⍴∘0)⍵ ⋄ ⍺+.×⍵} + t,←⊂'…' '_Range' ⋄ ns._Range←{ + ⍝ Adam Brudzewsky's Range function. + ⍝ modified to follow a terser code style. + ⎕IO←0 ⋄ c←0 2∊⍨10|⎕DR ⋄ t←1↓⍵ ⋄ d←c(e←⊃⍵) + f←⎕UCS⍣d⊢0 ⋄ ⍺←f ⋄ l←-(2-d)⌊(≢⍺)⌊+/d=c¨¯2↑⍺ + s←l↓⍺ ⋄ b←(¯1⌊l)↑¯2↑f,l↑⍺ ⋄ d:s,t,⍨⎕UCS(⎕UCS b)∇ ⎕UCS e + F S←-⍨\2↑b,b+×e-b ⋄ s,t,⍨F+S×⍳0⌈1+⌊(e-F)÷S+S=0 + } + t,←⊂'⍙' '_MonadicDot' ⋄ ns._MonadicDot←{ + ⍝ https://dfns.dyalog.com/n_alt.htm + r c←⍴⍵ + 0=r:⍵⍵⌿,⍵ + 1≥c:⍺⍺⌿,⍵ + M←~⍤1 0⍨⍳r + ⍵[;⎕IO]⍺⍺.⍵⍵(∇⍤2)⍵[M;1↓⍳c] + } + t,←⊂'φ' '_Fib' ⋄ ns._Fib←{⍺←0 1 ⋄ 0=⍵:⊃⍺ ⋄ (1↓⍺,+/⍺)∇ ⍵-1} + t,←⊂'⍗' '_PowerDown' ⋄ ns._PowerDown←{op←⍺⍺⍣¯1 ⋄ 0=⎕NC'⍺':op ⍵ ⋄ ⍺ op ⍵} + t,←⊂'⍐' '_PowerUp' ⋄ ns._PowerUp←{op←⍺⍺⍣(¯1+2*31) ⋄ 0=⎕NC'⍺':op ⍵ ⋄ ⍺ op ⍵} + t,←⊂'∧' '_And' ⋄ ns._And←{0=⎕NC'⍺':⍵[⍋⍵] ⋄ ⍺∧⍵} + t,←⊂'∨' '_Or' ⋄ ns._Or←{0=⎕NC'⍺':⍵[⍒⍵] ⋄ ⍺∨⍵} + t,←⊂'⍦' '_Middle' ⋄ ns._Middle←{⍺>0:(-⍺)↓⍺↓⍵ ⋄ x←⌈(|⍺)-⍨2÷⍨≢⍵ ⋄ (-x)↓x↓⍵} + t,←⊂'⍁' '_Diagonal' ⋄ ns._Diagonal←{⍵⊢∘⊂⌸⍨⍥,+/↑⍳⍴⍵} + t,←⊂'⍮' '_Pair' ⋄ ns._Pair←{ + 0≠⎕NC'⍺':⍺ ⍵ + ⍵=0:'0123456789' + ⍵=1:'abcdefghijklmnopqrstuvwxyz' + ⍵=2:'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + ⍵=3:'yaeiou' + ⍵=4:'YAEIOU' + ⍵=5:'YAEIOU' + ⍵=6:819⌶⎕A~'YAEIOU' + ⍵=7:⎕A~'YAEIOU' + ⍵=8:4294967296 + ⍵=9:4294967295 + ⍵=10:2÷¯1+5*÷2 ⍝ golden ratio + ⍵=11:⎕UCS 10 + ⍵=12:3.1415 + } + t,←⊂'Φ' '_Totient' ⋄ ns._Totient←((×/⊢-≠)3∘dfns.pco) + t,←⊂'⍲' '_Prefixes' ⋄ ns._Prefixes←{0=⎕NC'⍺':(⍳∘≢↑¨⊂)⍵ ⋄ ⍺⍲⍵} + t,←⊂'⍱' '_Suffixes' ⋄ ns._Suffixes←{0=⎕NC'⍺':(⌽∘,¨,\∘⌽)⍵ ⋄ ⍺⍱⍵} + t,←⊂'=' '_Equal' ⋄ ns._Equal←{0=⎕NC'⍺':(1=≢∘∪)⍵ ⋄ ⍺=⍵} + t,←⊂'⍡' '_Filter' ⋄ ns._Filter←{⍵/⍨⍺⍺ ⍵} + t,←⊂'⊤' '_DownTack' ⋄ ns._DownTack←{⍺←10⋄⍺⊥⍣¯1⊢⍵} + t,←⊂'⌂' '_DX' ⋄ ns._DX←∇∇ + ⍝ translation and execution + k←,¯1↓⍉↑t ⋄ t3←{{0=≢⊃⍵:⊃⌽⍵ ⋄ ⊃⌽t⊃⍨⊃⍵}¨⍵,⍥⊂¨⍨{⍸∊k=⊃⍵}¨⍵} + g←⍺⍺ ⋄ r←t3¨{⍵/⍨{0≠≢⍵}¨⍵}{{⍵↓⍨-'⍝'=⊃⊃⌽⍵}60⌶⍵}¨↓⎕CR'g' + p←↑↑{∊⍺'⋄'⍵}/{∊⍺' '⍵}/¨r + 2=⎕NC'⍺':⍺(ns⍎p)⍵ ⋄ (ns⍎p)⍵ + } + + AddCentury←{ + ⍝ ⍺ ←→ Century Window, Century Anchor Year + ⍝ ⍵ ←→Decimal time number 60 + ⍝ ← ←→ ⍵ with added century + f w t←⍺ + 1∊'YYYY'⍷f:⍵ + y←⌊⍵÷10*4 + b←y<100 + ~∨/b:⍵ + w>999:⍵+b×w×10*4 + s x←0 100⊤w-⍨t + c←b×s+y0)∧k[2;]≤l+31 28 31 30 31 30 31 31 30 31 30 31[11⌊0⌈k[1;]-1] + ⍵×(k[5;]=⌈k[5;])∧g∧∧⌿(f(≤⍤¯1)k)∧c(>⍤¯1)k + } + + + :Class Jarvis +⍝ Dyalog Web Service Server +⍝ See https://dyalog.github.io/Jarvis for documentation + + (⎕ML ⎕IO)←1 1 + + ∇ r←Version + :Access public shared + r←'Jarvis' '1.14.5' '2023-10-14' + ∇ + + ∇ Documentation + :Access public shared + ⎕←'See https://dyalog.github.io/Jarvis' + ∇ + + ⍝ User hooks settings + :Field Public AppCloseFn←'' ⍝ name of the function to run on application (server) shutdown + :Field Public AppInitFn←'' ⍝ name of the application "bootstrap" function + :Field Public AuthenticateFn←'' ⍝ name of function to perform authentication,if empty, no authentication is necessary + :Field Public SessionInitFn←'' ⍝ Function name to call when initializing a session + :Field Public ValidateRequestFn←'' ⍝ name of the request validation function + + ⍝ Operational settings + :Field Public CodeLocation←'#' ⍝ reference to application code location, if the user specifies a folder or file, that value is saved in CodeSource + :Field Public ConnectionTimeout←30 ⍝ HTTP/1.1 connection timeout in seconds + :Field Public Debug←0 ⍝ 0 = all errors are trapped, 1 = stop on an error, 2 = stop on intentional error before processing request, 4 = Jarvis framework debugging + :Field Public DefaultContentType←'application/json; charset=utf-8' + :Field Public ErrorInfoLevel←1 ⍝ level of information to provide if an APL error occurs, 0=none, 1=⎕EM, 2=⎕SI + :Field Public Hostname←'' ⍝ external-facing host name + :Field Public HTTPAuthentication←'basic' ⍝ valid settings are currently 'basic' or '' + :Field Public JarvisConfig←'' ⍝ configuration file path (if any). This parameter was formerly named ConfigFile + :Field Public LoadableFiles←'*.apl?,*.dyalog' ⍝ file patterns that can be loaded if loading from folder + :Field Public Logging←1 ⍝ turn logging on/off + :Field Public Paradigm←'JSON' ⍝ either 'JSON' or 'REST' + :Field Public Report404InHTML←1 ⍝ Report HTTP 404 status (not found) in HTML (only valid if HTML interface is enabled) + :Field Public UseZip←0 ⍝ Use compression if client allows it, 0- don't compress, 0<- compress if UseZip≤≢payload + :Field Public ZipLevel←3 ⍝ default compression level (0-9) + + ⍝ Container-related settings + :Field Public DYALOG_JARVIS_THREAD←'' ⍝ 0 = Run in thread 0, 1 = Use separate thread and ⎕TSYNC, 'DEBUG' = Use separate thread and return to immediate execution, "AUTO" = if InTerm use "DEBUG" otherwise 1 + :Field Public DYALOG_JARVIS_CODELOCATION←'' ⍝ If supplied, overrides CodeLocation in config file + :Field Public DYALOG_JARVIS_PORT←'' ⍝ If supplied, overrides Port both default port and config file + + ⍝ Session settings + :Field Public SessionIdHeader←'Jarvis-SessionID' ⍝ Name of the header field for the session token + :Field Public SessionUseCookie←0 ⍝ 0 - just use the header; 1 - use an HTTP cookie + :Field Public SessionPollingTime←1 ⍝ how frequently (in minutes) we should poll for timed out sessions + :Field Public SessionTimeout←0 ⍝ 0 = do not use sessions, ¯1 = no timeout , 0< session timeout time (in minutes) + :Field Public SessionCleanupTime←60 ⍝ how frequently (in minutes) do we clean up timed out session info from _sessionsInfo + + ⍝ JSON mode settings + :Field Public AllowFormData←0 ⍝ do we allow POST form data in JSON paradigm? + :Field Public AllowGETs←0 ⍝ do we allow calling endpoints with HTTP GETs? + :Field Public HTMLInterface←¯1 ⍝ ¯1=unassigned, 0/1=dis/allow the HTML interface, 'Path to HTML[/home-page]', or '' 'fn' + :Field Public JSONInputFormat←'D' ⍝ set this to 'M' to have Jarvis convert JSON request payloads to the ⎕JSON matrix format + + ⍝ REST mode settings + :Field Public ParsePayload←1 ⍝ 1=parse request payload based on content-type header (REST only) + :Field Public RESTMethods←'Get,Post,Put,Delete,Patch,Options' + + ⍝ CORS settings + :Field Public EnableCORS←1 ⍝ set to 0 to disable CORS + :Field Public CORS_Origin←'*' ⍝ default value for Access-Control-Allow-Origin header (set to 1 to reflect request Origin) + :Field Public CORS_Methods←¯1 ⍝ ¯1 = set based on paradigm, 1 = reflect the request's requested method + :Field Public CORS_Headers←'*' ⍝ default value for Access-Control-Allow-Headers header (set to 1 to reflect request Headers) + :Field Public CORS_MaxAge←60 ⍝ default value (in seconds) for Access-Control-Max-Age header + + ⍝ Conga-related settings + :Field Public AcceptFrom←⍬ ⍝ Conga: IP addresses to accept requests from - empty means accept from any IP address + :Field Public BufferSize←10000 ⍝ Conga: buffer size + :Field Public DenyFrom←⍬ ⍝ Conga: IP addresses to refuse requests from - empty means deny none + :Field Public DOSLimit←¯1 ⍝ Conga: DOSLimit, ¯1 means use default + :Field Public Port←8080 ⍝ Conga: Default port to listen on + :Field Public RootCertDir←'' ⍝ Conga: Root CA certificate folder + :field Public Priority←'NORMAL:!CTYPE-OPENPGP' ⍝ Conga: Priorities for GnuTLS when negotiation connection + :Field Public Secure←0 ⍝ 0 = use HTTP, 1 = use HTTPS + :field Public ServerCertSKI←'' ⍝ Conga: Server cert's Subject Key Identifier from store + :Field Public ServerCertFile←'' ⍝ Conga: public certificate file + :Field Public ServerKeyFile←'' ⍝ Conga: private key file + :Field Public ServerName←'' ⍝ Server name, '' means Conga assigns it + :Field Public SSLValidation←64 ⍝ Conga: request, but do not require a client certificate + :Field Public WaitTimeout←15000 ⍝ ms to wait in LDRC.Wait + + :Field Public Shared LDRC←'' ⍝ Jarvis-set reference to Conga after CongaRef has been resolved + :Field Public Shared CongaPath←'' ⍝ user-supplied path to Conga workspace and/or shared libraries + :Field Public Shared CongaRef←'' ⍝ user-supplied reference to Conga library instance + :Field CongaVersion←'' ⍝ Conga version + + :Property CodeSource + :Access Public + ∇ r←get + r←_codeSource + ∇ + :EndProperty + + ⍝ IncludeFns/ExcludeFns Properties + :Property IncludeFns, ExcludeFns + ⍝ IncludeFns and ExcludeFns are vectors the defined endpoint (function) names to expose or hide respectively + ⍝ They can be function names, simple wildcarded patterns (e.g. 'Foo*'), or regex + :Access Public + ∇ r←get ipa + r←⍎'_',ipa.Name + ∇ + ∇ set ipa + :Select ipa.Name + :Case 'IncludeFns' + _includeRegex←makeRegEx¨(⊂'')~⍨∪,⊆_IncludeFns←ipa.NewValue + :Case 'ExcludeFns' + _excludeRegex←makeRegEx¨(⊂'')~⍨∪,⊆_ExcludeFns←ipa.NewValue + :EndSelect + ∇ + :EndProperty + + ⍝↓↓↓ some of these private fields are also set in ∇init so that a server can be stopped, updated, and restarted + :Field _rootFolder←'' ⍝ root folder for relative file paths + :Field _codeSource←'' ⍝ file or folder that code was loaded from, if applicable + :Field _configLoaded←0 ⍝ indicates whether config was already loaded by Autostart + :Field _htmlFolder←'' ⍝ folder containing HTML interface files, if any + :Field _htmlDefaultPage←'index.html' ⍝ default page name if HTMLInterface is set to serve from a folder + :Field _htmlEnabled←0 ⍝ is the HTML interface enabled? + :Field _htmlRootFn←'' ⍝ function name if serving HTML root from a function rather than file + :Field _stop←0 ⍝ set to 1 to stop server + :Field _started←0 ⍝ is the server started + :Field _stopped←1 ⍝ is the server stopped + :field _paused←0 ⍝ is the server paused + :Field _sessionThread←¯1 ⍝ thread for the session cleanup process + :Field _serverThread←¯1 ⍝ thread for the HTTP server + :Field _taskThreads←⍬ ⍝ vector of thread handling requests + :Field _sessions←⍬ ⍝ vector of session namespaces + :Field _sessionsInfo←0 5⍴'' '' 0 0 0 ⍝ [;1] id [;2] ip addr [;3] creation time [;4] last active time [;5] ref to session + :Field _IncludeFns←'' ⍝ private IncludeFns + :Field _ExcludeFns←'' ⍝ private ExcludeFns + :Field _includeRegex←'' ⍝ private compiled regex from _IncludeFns + :Field _excludeRegex←'' ⍝ private compiled regex from _ExcludeFns + :Field _connections ⍝ namespace containing open connections + + ∇ r←Config + ⍝ returns current configuration + :Access public + r←↑{⍵(⍎⍵)}¨⎕THIS⍎'⎕NL ¯2.2 ¯2.1 ¯2.3' + ∇ + + ∇ r←{value}DebugLevel level + ⍝ monadic: return 1 if level is within Debug (powers of 2) + ⍝ example: stopIf DebugLevel 2 ⍝ sets a stop if Debug contains 2 + ⍝ dyadic: return value unless level is within Debug (powers of 2) + ⍝ example: :Trap 0 DebugLevel 5 ⍝ set Trap 0 unless Debug contains 1 or 4 in its + r←∨/(2 2 2⊤⊃Debug)∨.∧2 2 2⊤level + :If 0≠⎕NC'value' + r←value/⍨~r + :EndIf + ∇ + + ∇ {r}←{level}Log msg;ts + :Access public overridable + :If Logging>0∊⍴msg + ts←fmtTS ⎕TS + :If 1=≢⍴msg←⍕msg + :OrIf 1=⊃⍴msg + r←ts,' - ',msg + :Else + r←ts,∊(⎕UCS 13),msg + :EndIf + ⎕←r + :EndIf + ∇ + + ∇ r←New arg + ⍝ create a new instance of Jarvis + :Access public shared + :If 0∊⍴arg + r←##.⎕NEW ⎕THIS + :Else + r←##.⎕NEW ⎕THIS arg + :EndIf + ∇ + + ∇ make + :Access public + :Implements constructor + MakeCommon + ∇ + + ∇ make1 args;rc;msg;char;t + :Access public + :Implements constructor + ⍝ args is one of + ⍝ - a simple character vector which is the name of a configuration file + ⍝ - a reference to a namespace containing named configuration settings + ⍝ - a depth 1 or 2 vector of + ⍝ [1] integer port to listen on + ⍝ [2] charvec function folder or ref to code location + ⍝ [3] paradigm to use ('JSON' or 'REST') + MakeCommon + :If char←isChar args ⍝ character argument? it's either config filename or CodeLocation folder + :If ~⎕NEXISTS args + →0⊣Log'Unable to find "',args,'"' + :ElseIf 2=t←1 ⎕NINFO args ⍝ normal file + :If (lc⊢/⎕NPARTS args)∊'.json' '.json5' ⍝ json files are configuration + :If 0≠⊃(rc msg)←LoadConfiguration JarvisConfig←args + Log'Error loading configuration: ',msg + :EndIf + :Else + CodeLocation←args ⍝ might be a namespace script or class + :EndIf + :ElseIf 1=t ⍝ folder means it's CodeLocation + CodeLocation←args + :Else ⍝ not a file or folder + Log'Invalid constructor argument "',args,'"' + :EndIf + :ElseIf 9.1={⎕NC⊂,'⍵'}args ⍝ namespace? + :If 0≠⊃(rc msg)←LoadConfiguration args + Log'Error loading configuration: ',msg + :EndIf + :Else + :If 326=⎕DR args + :AndIf 0∧.=≡¨2↑args ⍝ if 2↑args is (port ref) (both scalar) + args[1]←⊂,args[1] ⍝ nest port so ∇default works properly + :EndIf + + (Port CodeLocation Paradigm JarvisConfig)←args default Port CodeLocation Paradigm JarvisConfig + :EndIf + ∇ + + ∇ MakeCommon + :Trap 11 + JSONin←0 ##.##.⎕JSON⍠('Dialect' 'JSON5')('Format'JSONInputFormat)⊢ ⋄ {}JSONin'1' + JSONout←1 ##.##.⎕JSON⍠'HighRank' 'Split'⊢ ⋄ {}JSONout 1 + JSONread←0 ##.##.⎕JSON⍠'Dialect' 'JSON5'⊢ ⍝ for reading configuration files + :Else + JSONin←0 ##.##.⎕JSON⍠('Format'JSONInputFormat)⊢ + JSONout←1 ##.##.⎕JSON⊢ + JSONread←0 ##.##.⎕JSON⊢ + :EndTrap + ∇ + + ∇ r←args default defaults + args←,⊆args + r←(≢defaults)↑args,(≢args)↓defaults + ∇ + + ∇ Close + :Implements destructor + {0:: ⋄ {}LDRC.Close ServerName}⍬ + ∇ + + ∇ r←Run args;msg;rc + ⍝ args is one of + ⍝ - a simple character vector which is the name of a configuration file + ⍝ - a reference to a namespace containing named configuration settings + ⍝ - a depth 1 or 2 vector of + ⍝ [1] integer port to listen on + ⍝ [2] charvec function folder or ref to code location + ⍝ [3] paradigm to use ('JSON' or 'REST') + :Access shared public + :Trap 0 + (rc msg)←(r←New args).Start + :Else + (r rc msg)←'' ¯1 ⎕DMX.EM + :EndTrap + r←(r(rc msg)) + ∇ + + ∇ (rc msg)←Start;html;homePage;t + :Access public + :Trap 0 DebugLevel 1 + Log'Starting ',⍕2↑Version + :If _started + :If 0(,2)≡LDRC.GetProp ServerName'Pause' + rc←1⊃LDRC.SetProp ServerName'Pause' 0 + →0 If(rc'Failed to unpause server') + (rc msg)←0 'Server resuming operations' + →0 + :EndIf + →0 If(rc msg)←¯1 'Server thinks it''s already started' + :EndIf + + :If _stop + →0 If(rc msg)←¯1 'Server is in the process of stopping' + :EndIf + + :If 'CLEAR WS'≡⎕WSID + :If ⎕NEXISTS JarvisConfig + :AndIf 2=⊃1 ⎕NINFO JarvisConfig + _rootFolder←⊃1 ⎕NPARTS JarvisConfig + :Else + _rootFolder←⊃1 ⎕NPARTS SourceFile + :EndIf + :Else + _rootFolder←⊃1 ⎕NPARTS ⎕WSID + :EndIf + + →0 If(rc msg)←LoadConfiguration JarvisConfig + →0 If(rc msg)←CheckPort + →0 If(rc msg)←CheckCodeLocation + →0 If(rc msg)←Setup + →0 If(rc msg)←LoadConga + + homePage←1 ⍝ default is to use built-in home page + :Select ⊃HTMLInterface + :Case 0 ⍝ explicitly no HTML interface, carry on + _htmlEnabled←0 + :Case 1 ⍝ explicitly turned on + :If Paradigm≢'JSON' + Log'HTML interface is only available using JSON paradigm' + :Else + _htmlEnabled←1 + :EndIf + :Case ¯1 ⍝ turn on if JSON paradigm + _htmlEnabled←Paradigm≡'JSON' ⍝ if not specified, HTML interface is enabled for JSON paradigm + :Else + :If 1<|≡HTMLInterface ⍝ is it '' 'function'? + t←2⊃HTMLInterface + :If 1 1 0≡⊃CodeLocation.⎕AT t + _htmlRootFn←t + _htmlEnabled←1 + :Else + →0 If(rc msg)←¯1('HTML root function "',(⍕CodeLocation),'.',t,'" is not a monadic, result-returning function.') + :EndIf + :Else ⍝ otherwise it's 'file/folder' + _htmlEnabled←1 + html←1 ⎕NPARTS((isRelPath HTMLInterface)/_rootFolder),HTMLInterface + :If isDir∊html + _htmlFolder←{⍵,('/'=⊢/⍵)↓'/'}∊html + :Else + _htmlFolder←1⊃html + _htmlDefaultPage←∊1↓html + :EndIf + homePage←⎕NEXISTS html←_htmlFolder,_htmlDefaultPage + Log(~homePage)/'HTML home page file "',(∊html),'" not found.' + :EndIf + :EndSelect + + :If EnableCORS ⍝ if we've enabled CORS + :AndIf ¯1∊CORS_Methods ⍝ but not set any pre-flighted methods + :If Paradigm≡'JSON' + CORS_Methods←'GET,POST,OPTIONS' ⍝ allowed JSON methods are GET, POST, and OPTIONS + :Else + CORS_Methods←1↓∊',',¨RESTMethods[;1] ⍝ allowed REST methods are what the service supports + :EndIf + :EndIf + + CORS_Methods←uc CORS_Methods + + →0 If(rc msg)←StartServer + + Log'Jarvis starting in "',Paradigm,'" mode on port ',⍕Port + Log'Serving code in ',(⍕CodeLocation),(CodeSource≢'')/' (populated with code from "',CodeSource,'")' + Log(_htmlEnabled∧homePage)/'Click http',(~Secure)↓'s://',MyAddr,':',(⍕Port),' to access web interface' + + :Else ⍝ :Trap + (rc msg)←¯1 ⎕DMX.EM + :EndTrap + ∇ + + ∇ (rc msg)←Stop;ts + :Access public + :If _stop + →0⊣(rc msg)←¯1 'Server is already stopping' + :EndIf + :If ~_started + →0⊣(rc msg)←¯1 'Server is not running' + :EndIf + ts←⎕AI[3] + _stop←1 + Log'Stopping server...' + {0:: ⋄ {}LDRC.Close 2⊃LDRC.Clt'' ''Port'http'}'' + :While ~_stopped + :If WaitTimeout<⎕AI[3]-ts + →0⊣(rc msg)←¯1 'Server seems stuck' + :EndIf + :EndWhile + (rc msg)←0 'Server stopped' + ∇ + + ∇ (rc msg)←Pause + :Access public + →0 If~_started⊣(rc msg)←¯1 'Server is not running' + →0 If 2=⊃2⊃LDRC.GetProp ServerName'Pause'⊣(rc msg)←¯2 Error'Server is already paused' + →0 If 0≠rc←⊃LDRC.SetProp ServerName'Pause' 2⊣msg←'Error attempting to pause server' + Log'Pausing server...' + (rc msg)←0 'Server paused' + ∇ + + ∇ (rc msg)←Reset + :Access Public + ⎕TKILL _serverThread,_sessionThread,_taskThreads + _sessions←⍬ + _sessionsInfo←0 5⍴0 + _stopped←~_stop←_started←0 + (rc msg)←0 'Server reset (previously set options are still in effect)' + ∇ + + ∇ r←Running + :Access public + r←~_stopped + ∇ + + ∇ (rc msg)←CheckPort;p + ⍝ check for valid port number + :If DYALOG_JARVIS_PORT≢'' ⍝ environment variable takes precedence + Port←DYALOG_JARVIS_PORT + :EndIf + (rc msg)←3('Invalid port: ',∊⍕Port) + →0 If 0=p←⊃⊃(//)⎕VFI⍕Port + →0 If{(⍵>32767)∨(⍵<1)∨⍵≠⌊⍵}p + (rc msg)←0 '' + ∇ + + ∇ (rc msg)←{force}LoadConfiguration value;config;public;set;file + :Access public + :If 0=⎕NC'force' ⋄ force←0 ⋄ :EndIf + (rc msg)←0 '' + →(_configLoaded>force)⍴0 ⍝ did we already load from AutoStart? + :Trap 0 DebugLevel 1 + :If isChar value + :If '#.'≡2↑value ⍝ check if a namespace reference + :AndIf 9.1=⎕NC⊂value + config←⍎value + →Load + :EndIf + file←JarvisConfig + :If ~0∊⍴value + file←value + :EndIf + →0 If 0∊⍴file + :If ⎕NEXISTS file + config←JSONread⊃⎕NGET file + :Else + →0⊣(rc msg)←6('Configuation file "',file,'" not found') + :EndIf + :ElseIf 9.1={⎕NC⊂,'⍵'}value ⍝ namespace? + config←value + :EndIf + Load: + public←⎕THIS⍎'⎕NL ¯2.2 ¯2.1 ¯2.3' ⍝ find all the public fields in this class + :If ~0∊⍴set←public∩config.⎕NL ¯2 ¯9 + config{⍎⍵,'←⍺⍎⍵'}¨set + :EndIf + _configLoaded←1 + :Else + →0⊣(rc msg)←⎕DMX.EN ⎕DMX.('Error loading configuration: ',EM,(~0∊⍴Message)/' (',Message,')') + :EndTrap + ∇ + + ∇ (rc msg)←LoadConga;ref;root;nc;n;ns;congaCopied;class;path + ⍝↓↓↓ Check if LDRC exists (VALUE ERROR (6) if not), and is LDRC initialized? (NONCE ERROR (16) if not) + + (rc msg)←1 '' + + :Hold 'JarvisInitConga' + :If {6 16 999::1 ⋄ ''≡LDRC:1 ⋄ 0⊣LDRC.Describe'.'}'' + LDRC←'' + :If ~0∊⍴CongaRef ⍝ did the user supply a reference to Conga? + LDRC←ResolveCongaRef CongaRef + →∆END↓⍨0∊⍴msg←(''≡LDRC)/'CongaRef (',(⍕CongaRef),') does not point to a valid instance of Conga' + :Else + :For root :In ##.## # + ref nc←root{1↑¨⍵{(×⍵)∘/¨⍺ ⍵}⍺.⎕NC ⍵}ns←'Conga' 'DRC' + :If 9=⊃⌊nc ⋄ :Leave ⋄ :EndIf + :EndFor + + :If 9=⊃⌊nc + LDRC←ResolveCongaRef root⍎∊ref + →∆END↓⍨0∊⍴msg←(''≡LDRC)/(⍕root),'.',(∊ref),' does not point to a valid instance of Conga' + →∆COPY↓⍨{999::0 ⋄ 1⊣LDRC.Describe'.'}'' ⍝ it's possible that Conga was saved in a semi-initialized state + Log'Conga library found at ',(⍕root),'.',∊ref + :Else + ∆COPY: + class←⊃⊃⎕CLASS ⎕THIS + congaCopied←0 + :For n :In ns + :For path :In (1+0∊⍴CongaPath)⊃(⊂CongaPath)((DyalogRoot,'ws/')'') ⍝ if CongaPath specified, use it exclusively + :Trap Debug↓0 + n class.⎕CY path,'conga' + LDRC←ResolveCongaRef(class⍎n) + →∆END↓⍨0∊⍴msg←(''≡LDRC)/n,' was copied from ',path,'conga but is not valid' + Log n,' copied from ',path,'conga' + →∆COPIED⊣congaCopied←1 + :EndTrap + :EndFor + :EndFor + →∆END↓⍨0∊⍴msg←(~congaCopied)/'Neither Conga nor DRC were successfully copied from [DYALOG]/ws/conga' + ∆COPIED: + :EndIf + :EndIf + :EndIf + CongaVersion←0.1⊥2↑LDRC.Version + LDRC.X509Cert.LDRC←LDRC ⍝ reset X509Cert.LDRC reference + Log'Local Conga reference is ',⍕LDRC + rc←0 + ∆END: + :EndHold + ∇ + + ∇ LDRC←ResolveCongaRef CongaRef;z;failed + ⍝ Attempt to resolve what CongaRef refers to + ⍝ CongaRef can be a charvec, reference to the Conga or DRC namespaces, or reference to an iConga instance + ⍝ LDRC is '' if Conga could not be initialized, otherwise it's a reference to the the Conga.LIB instance or the DRC namespace + + LDRC←'' ⋄ failed←0 + :Select nameClass CongaRef ⍝ what is it? + :Case 9.1 ⍝ namespace? e.g. CongaRef←DRC or Conga + ∆TRY: + :Trap 0 DebugLevel 1 + :If ∨/'.Conga'⍷⍕CongaRef ⋄ LDRC←CongaPath CongaRef.Init'Jarvis' ⍝ is it Conga? + :ElseIf 0≡⊃CongaRef.Init CongaPath ⋄ LDRC←CongaRef ⍝ DRC? + :Else ⋄ →∆EXIT⊣LDRC←'' + :End + :Else ⍝ if Jarvis is reloaded and re-executed in rapid succession, Conga initialization may fail, so we try twice + :If failed ⋄ →∆EXIT⊣LDRC←'' + :Else ⋄ →∆TRY⊣failed←1 + :EndIf + :EndTrap + :Case 9.2 ⍝ instance? e.g. CongaRef←Conga.Init '' + LDRC←CongaRef ⍝ an instance is already initialized + :Case 2.1 ⍝ variable? e.g. CongaRef←'#.Conga' + :Trap 0 DebugLevel 1 + LDRC←ResolveCongaRef(⍎∊⍕CongaRef) + :EndTrap + :EndSelect + ∆EXIT: + ∇ + + ∇ (rc msg secureParams)←CreateSecureParams;cert;certs;msg;inds + ⍝ return Conga parameters for running HTTPS, if Secure is set to 1 + + LDRC.X509Cert.LDRC←LDRC ⍝ make sure the X509 instance points to the right LDRC + (rc secureParams msg)←0 ⍬'' + :If Secure + :If ~0∊⍴RootCertDir ⍝ on Windows not specifying RootCertDir will use MS certificate store + →∆EXIT If(rc msg)←'RootCertDir'Exists RootCertDir + →∆EXIT If(rc msg)←{(⊃⍵)'Error setting RootCertDir'}LDRC.SetProp'.' 'RootCertDir'RootCertDir +⍝ The following is commented out because it seems the GnuTLS knows to use the operating system's certificate collection even on non-Windows platforms +⍝ :ElseIf ~isWin +⍝ →∆EXIT⊣(rc msg)←¯1 'No RootCertDir spcified' + :EndIf + :If 0∊⍴ServerCertSKI ⍝ no certificate ID specified, check for Cert and Key files + →∆EXIT If(rc msg)←'ServerCertFile'Exists ServerCertFile + →∆EXIT If(rc msg)←'ServerKeyFile'Exists ServerKeyFile + :Trap 0 DebugLevel 1 + cert←⊃LDRC.X509Cert.ReadCertFromFile ServerCertFile + :Else + (rc msg)←⎕DMX.EN('Unable to decode ServerCertFile "',(∊⍕ServerCertFile),'" as a certificate') + →∆EXIT + :EndTrap + cert.KeyOrigin←'DER'ServerKeyFile + :ElseIf isWin ⍝ ServerCertSKI only on Windows + certs←LDRC.X509Cert.ReadCertUrls + :If 0∊⍴certs + →∆EXIT⊣(rc msg)←8 'No certificates found in Microsoft Certificate Store' + :Else + inds←1+('id=',ServerCertSKI,';')⎕S{⍵.BlockNum}⍠'Greedy' 0⊢2⊃¨certs.CertOrigin + :If 1≠≢inds + rc←9 + msg←(0 2⍸≢inds)⊃('Certificate with id "',ServerCertSKI,'" was not found in the Microsoft Certificate Store')('There is more than one certificate with Subject Key Identifier "',ServerCertSKI,'" in the Microsoft Certificate Store') + →∆EXIT + :EndIf + cert←certs[⊃inds] + :EndIf + :Else ⍝ ServerCertSKI is defined, but we're not running Windows + →∆EXIT⊣(rc msg)←10 'ServerCertSKI is currently valid only under Windows' + :EndIf + secureParams←('X509'cert)('SSLValidation'SSLValidation)('Priority'Priority) + :EndIf + ∆EXIT: + ∇ + + ∇ (rc msg)←CheckCodeLocation;root;m;res;tmp;fn;path + (rc msg)←0 '' + :If DYALOG_JARVIS_CODELOCATION≢'' ⍝ environment variable take precedence + CodeLocation←DYALOG_JARVIS_CODELOCATION + :EndIf + :If 0∊⍴CodeLocation + :If 0∊⍴JarvisConfig ⍝ if there's a configuration file, use its folder for CodeLocation + →0⊣(rc msg)←4 'CodeLocation is empty!' + :Else + CodeLocation←⊃1 ⎕NPARTS JarvisConfig + :EndIf + :EndIf + :Select ⊃{⎕NC'⍵'}CodeLocation ⍝ need dfn because CodeLocation is a field and will always be nameclass 2 + :Case 9 ⍝ reference, just use it + :Case 2 ⍝ variable, could be file path or ⍕ of reference from JarvisConfig + :If 326=⎕DR tmp←{0::⍵ ⋄ '#'≠⊃⍵:⍵ ⋄ ⍎⍵}CodeLocation + :AndIf 9={⎕NC'⍵'}tmp ⋄ CodeLocation←tmp + :Else + root←(isRelPath CodeLocation)/_rootFolder + path←∊1 ⎕NPARTS root,CodeLocation + :Trap 0 DebugLevel 1 + :If 1=t←1 ⎕NINFO path ⍝ folder? + CodeLocation←⍎'CodeLocation'#.⎕NS'' + _codeSource←path + →0 If(rc msg)←CodeLocation LoadFromFolder path + :ElseIf 2=t ⍝ file? + CodeLocation←#.⎕FIX'file://',path + _codeSource←path + :Else + →0⊣(rc msg)←5('CodeLocation "',(∊⍕CodeLocation),'" is not a folder or script file.') + :EndIf + + :Case 22 ⍝ file name error + →0⊣(rc msg)←6('CodeLocation "',(∊⍕CodeLocation),'" was not found.') + :Else ⍝ anything else + →0⊣(rc msg)←7((⎕DMX.(EM,' (',Message,') ')),'occured when validating CodeLocation "',(∊⍕CodeLocation),'"') + :EndTrap + :EndIf + :Else + →0⊣(rc msg)←5 'CodeLocation is not valid, it should be either a namespace/class reference or a file path' + :EndSelect + + :For fn :In AppInitFn AppCloseFn ValidateRequestFn AuthenticateFn SessionInitFn~⊂'' + :If 3≠CodeLocation.⎕NC fn + msg,←(0∊⍴msg)↓',"CodeLocation.',fn,'" was not found ' + :EndIf + :EndFor + →0 If rc←8×~0∊⍴msg + + :If ~0∊⍴AppInitFn ⍝ initialization function specified? + :Select ⊃CodeLocation.⎕AT AppInitFn + :Case 1 0 0 ⍝ result-returning niladic? + stopIf DebugLevel 2 + res←CodeLocation⍎AppInitFn ⍝ run it + :Case 1 1 0 ⍝ result-returning monadic? + stopIf DebugLevel 2 + res←(CodeLocation⍎AppInitFn)⎕THIS ⍝ run it + :Else + →0⊣(rc msg)←8('"',(⍕CodeLocation),'.',AppInitFn,'" is not a niladic or monadic result-returning function') + :EndSelect + :If 0≠⊃res + →0⊣(rc msg)←2↑res,(≢res)↓¯1('"',(⍕CodeLocation),'.',AppInitFn,'" did not return a 0 return code') + :EndIf + :EndIf + + + :If ~0∊⍴AppCloseFn ⍝ application close function specified? + :If 1 0 0≢⊃CodeLocation.⎕AT AppCloseFn ⍝ result-returning niladic? + →0⊣(rc msg)←8('"',(⍕CodeLocation),'.',AppCloseFn,'" is not a niladic result-returning function') + :EndIf + :EndIf + + Validate←{0} ⍝ dummy validation function + :If ~0∊⍴ValidateRequestFn ⍝ Request validation function specified? + :If ∧/(⊃CodeLocation.⎕AT ValidateRequestFn)∊¨1(1 ¯2)0 ⍝ result-returning monadic or ambivalent? + Validate←CodeLocation⍎ValidateRequestFn + :Else + →0⊣(rc msg)←8('"',(⍕CodeLocation),'.',ValidateRequestFn,'" is not a monadic result-returning function') + :EndIf + :EndIf + + Authenticate←{0} ⍝ dummy authentication function + :If ~0∊⍴AuthenticateFn ⍝ authentication function specified? + :If ∧/(⊃CodeLocation.⎕AT AuthenticateFn)∊¨1(1 ¯2)0 ⍝ result-returning monadic or ambivalent? + Authenticate←CodeLocation⍎AuthenticateFn + :Else + →0⊣(rc msg)←8('"',(⍕CodeLocation),'.',AuthenticateFn,'" is not a monadic result-returning function') + :EndIf + :EndIf + ∇ + + ∇ (rc msg)←Setup + ⍝ perform final setup before starting server + (rc msg)←0 '' + Paradigm←uc Paradigm + :Select Paradigm + :Case 'JSON' + RequestHandler←HandleJSONRequest + :Case 'REST' + RequestHandler←HandleRESTRequest + :If 2>≢⍴RESTMethods + RESTMethods←↑2⍴¨'/'(≠⊆⊢)¨','(≠⊆⊢),RESTMethods + :EndIf + :Else + (rc msg)←¯1 'Invalid paradigm' + :EndSelect + ∇ + + Exists←{0:: ¯1 (⍺,' "',⍵,'" is not a valid folder name.') ⋄ ⎕NEXISTS ⍵:0 '' ⋄ ¯1 (⍺,' "',⍵,'" was not found.')} + + ∇ (rc msg)←StartServer;r;cert;secureParams;accept;deny;mask;certs;options + msg←'Unable to start server' + accept←'Accept'ipRanges AcceptFrom + deny←'Deny'ipRanges DenyFrom + →∆EXIT If⊃(rc msg secureParams)←CreateSecureParams + + {}LDRC.SetProp'.' 'EventMode' 1 ⍝ report Close/Timeout as events + + options←'' + + :If 3.3≤CongaVersion ⍝ can we set DecodeBuffers at server creation? + options←⊂'Options' 5 ⍝ DecodeBuffers + WSAutoAccept + :EndIf + + :If 3.4≤CongaVersion ⍝ DOSLimit support started with v3.4 + :AndIf DOSLimit≠¯1 ⍝ not using Conga's default value + :If 0≠⊃LDRC.SetProp'.' 'DOSLimit'DOSLimit + →∆EXIT⊣(rc msg)←¯1 'Invalid DOSLimit setting: ',∊⍕DOSLimit + :EndIf + :EndIf + + _connections←⎕NS'' + _connections.index←2 0⍴'' 0 ⍝ row-oriented for faster lookup + _connections.lastCheck←0 + + :If 0=rc←1⊃r←LDRC.Srv ServerName''Port'http'BufferSize,secureParams,accept,deny,options + ServerName←2⊃r + :If 3.3>CongaVersion + {}LDRC.SetProp ServerName'FIFOMode' 0 ⍝ deprecated in Conga v3.2 + {}LDRC.SetProp ServerName'DecodeBuffers' 15 ⍝ 15 ⍝ decode all buffers + {}LDRC.SetProp ServerName'WSFeatures' 1 ⍝ auto accept WS requests + :EndIf + :If 0∊⍴Hostname ⍝ if Host hasn't been set, set it to the default + Hostname←'http',(~Secure)↓'s://',(2 ⎕NQ'.' 'TCPGetHostID'),((~Port∊80 443)/':',⍕Port),'/' + :EndIf + InitSessions + (rc msg)←RunServer + :Else + Log msg←'Error ',(⍕rc),' creating server',(rc∊98 10048)/': port ',(⍕Port),' is already in use' ⍝ 98=Linux, 10048=Windows + :EndIf + ∆EXIT: + ∇ + + ∇ (rc msg)←RunServer;thread + thread←lc,⍕DYALOG_JARVIS_THREAD + :If (⊂thread)∊'' 'auto' + :If InTerm ⍝ do we have an interactive terminal? + thread←'debug' + :Else + thread←,'1' + :EndIf + :EndIf + :Select thread + :Case ,'0' ⍝ Run in thread 0 + (rc msg)←Server'' + QuadOFF + :Case ,'1' ⍝ Run in non-0 thread, use ⎕TSYNC + (rc msg)←⎕TSYNC _serverThread←Server&⍬ + QuadOFF + :Case 'debug' + _serverThread←Server&⍬ + (rc msg)←0 'Server started' + :Else + (rc msg)←¯1 'Invalid setting for DYALOG_JARVIS_THREAD' + :EndSelect + ∇ + + ∇ {r}←Server arg;wres;rc;obj;evt;data;ref;ip;msg;tmp;conx + (_started _stopped)←1 0 + :While ~_stop + :Trap 0 DebugLevel 1 + wres←LDRC.Wait ServerName WaitTimeout ⍝ Wait for WaitTimeout before timing out + ⍝ wres: (return code) (object name) (command) (data) + (rc obj evt data)←4↑wres + conx←obj(⍳↓⊣)'.' + :Select rc + :Case 0 + :Select evt + :Case 'Error' + _stop←ServerName≡obj ⍝ if we got an error on the server itself, signal to stop + :If 0≠4⊃wres + Log'Server: DRC.Wait reported error ',(⍕4⊃wres),' on ',(2⊃wres),GetIP obj + :EndIf + RemoveConnection conx ⍝ Conga closes object on an Error event + + :Case 'Connect' + AddConnection conx + + :CaseList 'HTTPHeader' 'HTTPTrailer' 'HTTPChunk' 'HTTPBody' + :If 0≠_connections.⎕NC conx + ref←_connections⍎conx + _taskThreads←⎕TNUMS∩_taskThreads,ref{⍺ HandleRequest ⍵}&wres + ref.Time←⎕AI[3] + :Else + Log'Server: Object ''_connections.',conx,''' was not found.' + {0:: ⋄ {}LDRC.Close ⍵}obj + :EndIf + + :Case 'Closed' + RemoveConnection conx + + :Case 'Timeout' + + :Else ⍝ unhandled event + Log'Server: Unhandled Conga event:' + Log⍕wres + :EndSelect ⍝ evt + + :Case 1010 ⍝ Object Not found + :If ~_stop + Log'Server: Object ''',ServerName,''' has been closed - Jarvis shutting down' + _stop←1 + :EndIf + :Else + Log'Server: Conga wait failed:' + Log wres + :EndSelect ⍝ rc + + CleanupConnections + + :Else ⍝ :Trap + Log'*** Server error ',msg←1 ⎕JSON⍠'Compact' 0⊢⎕DMX + r←¯1 msg + →Exit + :EndTrap + :EndWhile + + r←0 'Server stopped' + + Exit: + + :If ~0∊⍴AppCloseFn + r←CodeLocation⍎AppCloseFn + :EndIf + + Close + ⎕TKILL _sessionThread + (_stop _started _stopped)←0 0 1 + ∇ + + ∇ AddConnection conx + :Hold '_connections' + conx _connections.⎕NS'' + _connections.index,←conx(⎕AI[3]) + (_connections⍎conx).IP←2⊃2⊃LDRC.GetProp obj'PeerAddr' + :EndHold + ∇ + + ∇ RemoveConnection conx + :Hold '_connections' + _connections.⎕EX conx + _connections.index/⍨←_connections.index[1;]≢¨⊂conx + :EndHold + ∇ + + ∇ CleanupConnections;conxNames;timedOut;dead;kids;connecting;connected + :If _connections.lastCheck<⎕AI[3]-ConnectionTimeout×1000 + :Hold '_connections' + connecting←connected←⍬ + :If ~0∊⍴kids←2 2⊃LDRC.Tree ServerName ⍝ retrieve children of server + ⍝ LDRC.Tree + ⍝ connecting → status 3 1 - incoming connection + ⍝ connected → status 3 4 - connected connection + (connecting connected)←2↑{((2 2⍴3 1 3 4)⍪⍵[;2 3]){⊂1↓⍵}⌸'' '',⍵[;1]}↑⊃¨kids + :EndIf + conxNames←_connections.index[1;]~connecting + timedOut←_connections.index[1;]/⍨ConnectionTimeout<0.001×⎕AI[3]-_connections.index[2;] + :If ∨/{~0∊⍴⍵}¨connected conxNames + :If ~0∊⍴timedOut + timedOut/⍨←{6::1 ⋄ 0=(_connections⍎⍵).⎕NC⊂'Req'}¨timedOut + :EndIf + dead←(connected~conxNames),timedOut ⍝ (connections not in the index), timed out + {0∊⍴⍵: ⋄ {}LDRC.Close ServerName,'.',⍵}¨dead ⍝ attempt to close them + ⍝ remove timed out, or connections that are + _connections.⎕EX(conxNames~connected~dead),timedOut + _connections.index/⍨←_connections.index[1;]∊_connections.⎕NL ¯9 + :EndIf + _connections.lastCheck←⎕AI[3] + :EndHold + :EndIf + ∇ + + :Section RequestHandling + + ∇ r←ErrorInfo + :Trap 0 + r←⍕ErrorInfoLevel↑⎕DMX.(EM({⍵↑⍨⍵⍳']'}2⊃DM)) + :Else + r←'' + :EndTrap + ∇ + + ∇ req←MakeRequest args + ⍝ create a request, use MakeRequest '' for interactive debugging + ⍝ :Access public ⍝ uncomment for debugging + :If 0∊⍴args + req←⎕NEW Request + :Else + req←⎕NEW Request args + :EndIf + req.(Server ErrorInfoLevel)←⎕THIS ErrorInfoLevel + ∇ + + ∇ ns HandleRequest req;data;evt;obj;rc;cert;fn + (rc obj evt data)←req ⍝ from Conga.Wait + :Hold obj + :Select evt + :Case 'HTTPHeader' + ns.Req←MakeRequest data + ns.Req.PeerCert←'' + ns.Req.PeerAddr←2⊃2⊃LDRC.GetProp obj'PeerAddr' + ns.Req.Server←⎕THIS + + :If Secure + (rc cert)←2↑LDRC.GetProp obj'PeerCert' + :If rc=0 + ns.Req.PeerCert←cert + :Else + ns.Req.PeerCert←'Could not obtain certificate' + :EndIf + :EndIf + + :Case 'HTTPBody' + ns.Req.ProcessBody data + :Case 'HTTPChunk' + ns.Req.ProcessChunk data + :Case 'HTTPTrailer' + ns.Req.ProcessTrailer data + :EndSelect + + :If ns.Req.Complete + :Select lc ns.Req.GetHeader'content-encoding' ⍝ zipped request? + :Case '' ⍝ no encoding + :If ns.Req.Charset≡'utf-8' + ns.Req.Body←'UTF-8'⎕UCS ⎕UCS ns.Req.Body + :EndIf + :Case 'gzip' + ns.Req.Body←⎕UCS 256|¯3 Zipper 83 ⎕DR ns.Req.Body + :Case 'deflate' + ns.Req.Body←⎕UCS 256|¯2 Zipper 83 ⎕DR ns.Req.Body + :Else + →resp⊣'Unsupported content-encoding'ns.Req.Fail 400 + :EndSelect + + :If _htmlEnabled∧ns.Req.Response.Status≠200 + ns.Req.Response.Headers←1 2⍴'Content-Type' 'text/html; charset=utf-8' + ns.Req.Response.Payload←'

',(⍕ns.Req.Response.((⍕Status),' ',StatusText)),'

' + →resp + :EndIf + + ⍝ Application-specified validation + stopIf DebugLevel 4+2×~0∊⍴ValidateRequestFn + rc←Validate ns.Req + ns.Req.Fail 400×(ns.Req.Response.Status=200)∧0≠rc ⍝ default status 400 if not set by application + →resp If rc≠0 + + fn←1↓'.'@('/'∘=)ns.Req.Endpoint + + fn RequestHandler ns ⍝ RequestHandler is either HandleJSONRequest or HandleRESTRequest + + resp: obj Respond ns + + :EndIf + :EndHold + ∇ + + ∇ fn HandleJSONRequest ns;payload;resp;valence;nc;debug;file;isGET + + →handle If~isGET←'get'≡ns.Req.Method + + :If AllowGETs ⍝ if we allow GETs + :AndIf ~'.'∊ns.Req.Endpoint ⍝ and the endpoint doesn't have a '.' (file extension) + →handle If 3=⌊|{0::0 ⋄ CodeLocation.⎕NC⊂⍵}fn ⍝ handle it if there's a matching function for the endpoint + :EndIf + + →End If'Request method should be POST'ns.Req.Fail 405×~_htmlEnabled + + →handleHtml If~0∊⍴_htmlFolder + ns.Req.Response.Headers←1 2⍴'Content-Type' 'text/html; charset=utf-8' + ns.Req.Response.Payload←'

400 Bad Request

' + →End If'Bad URI'ns.Req.Fail 400×~0∊⍴fn ⍝ either fail with a bad URI or exit if favicon.ico (no-op) + + :If 0∊⍴_htmlRootFn + ns.Req.Response.Payload←HtmlPage + :Else + ns.Req.Response.Payload←{1 CodeLocation.(85⌶)_htmlRootFn,' ⍵'}ns.Req + :EndIf + →End + + handleHtml: + :If (,'/')≡ns.Req.Endpoint + file←_htmlFolder,_htmlDefaultPage + :Else + file←_htmlFolder,('/'=⊣/ns.Req.Endpoint)↓ns.Req.Endpoint + :EndIf + file←∊1 ⎕NPARTS file + file,←(isDir file)/'/',_htmlDefaultPage + →End If ns.Req.Fail 400×~_htmlFolder begins file + :If 0≠ns.Req.Fail 404×~⎕NEXISTS file + →End If 0=Report404InHTML + ns.Req.Response.Headers←1 2⍴'Content-Type' 'text/html; charset=utf-8' + ns.Req.Response.Payload←'

Not found: ',(file↓⍨≢_htmlFolder),'

' + →End + :EndIf + ns.Req.Response.Payload←''file + 'Content-Type'ns.Req.DefaultHeader ns.Req.ContentTypeForFile file + →End + + handle: + →End If HandleCORSRequest ns.Req + →End If'No function specified'ns.Req.Fail 400×0∊⍴fn + →End If'Unsupported request method'ns.Req.Fail 405×(⊂ns.Req.Method)(~∊)(~AllowGETs)↓'get' 'post' + →End If'Cannot accept query parameters'ns.Req.Fail 400×AllowGETs⍱0∊⍴ns.Req.QueryParams + + :Select ns.Req.ContentType + + :Case 'application/json' + :Trap 0 DebugLevel 1 + ns.Req.Payload←{0∊⍴⍵:⍵ ⋄ JSONin ⍵}ns.Req.Body + :Else + →End⊣'Could not parse payload as JSON'ns.Req.Fail 400 + :EndTrap + + :Case 'multipart/form-data' + →End If'Content-Type should be "application/json"'ns.Req.Fail 400×~AllowFormData + :Trap 0 DebugLevel 1 + ns.Req.Payload←ParseMultipartForm ns.Req + :Else + →End⊣'Could not parse payload as "multipart/form-data"'ns.Req.Fail 400 + :EndTrap + + :Case '' + →End If'No Content-Type specified'ns.Req.Fail 400×~isGET∧AllowGETs + :Trap 0 DebugLevel 1 + :If 0∊⍴ns.Req.QueryParams + ns.Req.Payload←'' + :ElseIf 1=≢⍴ns.Req.QueryParams ⍝ name/value pairs + ns.Req.Payload←JSONin ns.Req.QueryParams + :Else + ns.Req.Payload←{JSONin{1⌽'}{',¯1↓∊'"',¨⍵[;,1],¨'":'∘,¨⍵[;,2],¨','}⍵}ns.Req.QueryParams + :EndIf + :Else + →0⊣'Could not parse query string as JSON'ns.Req.Fail 400 + :EndTrap + + :Else + →0⊣('Content-Type should be "application/json"',AllowFormData/' or "multipart/form-data"')ns.Req.Fail 400 + :EndSelect + + →End If CheckAuthentication ns.Req + + →End If('Invalid function "',fn,'"')ns.Req.Fail CheckFunctionName fn + →End If('Invalid function "',fn,'"')ns.Req.Fail 404×3≠⌊|{0::0 ⋄ CodeLocation.⎕NC⊂⍵}fn ⍝ is it a function? + valence←|⊃CodeLocation.⎕AT fn + nc←CodeLocation.⎕NC⊂fn + →End If('"',fn,'" is not a monadic result-returning function')ns.Req.Fail 400×(1 1 0≢×valence)>(0∧.=valence)∧3.3=nc + + resp←'' + :Trap 0 DebugLevel 1 + :Trap 85 + :If (2=valence[2])>3.3=nc ⍝ dyadic and not tacit + stopIf DebugLevel 2 + resp←ns.Req{0 CodeLocation.(85⌶)'⍺ ',fn,' ⍵'}ns.Req.Payload ⍝ intentional stop for application-level debugging + :Else + stopIf DebugLevel 2 + resp←{0 CodeLocation.(85⌶)fn,' ⍵'}ns.Req.Payload ⍝ intentional stop for application-level debugging + :EndIf + :Else ⍝ no result from the endpoint + :If 0∊⍴ns.Req.Response.Payload ⍝ no payload? + :AndIf 200=ns.Req.Response.Status ⍝ endpoint did not change the status + →End⊣ns.Req.Fail 204 ⍝ no content + :EndIf + :EndTrap + :Else + →End⊣ErrorInfo ns.Req.Fail 500 + :EndTrap + + →End If 204=ns.Req.Response.Status + + ⍝ Exit if + ⍝ ↓↓↓↓↓↓↓ no response from endpoint, + ⍝ and ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ endpoint did not set payload + ⍝ and ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ endpoint did not fail the request + →End If(0∊⍴resp)∧(0∊⍴ns.Req.Response.Payload)∧200≠ns.Req.Response.Status + + 'Content-Type'ns.Req.DefaultHeader DefaultContentType ⍝ set the header if not set + :If ∨/'application/json'⍷ns.Req.(Response.Headers GetHeader'content-type') ⍝ if the response is JSON + ns.Req.Response ToJSON resp ⍝ convert it + :Else + ns.Req.Response.Payload←resp + :EndIf + :If 0∊⍴ns.Req.Response.Payload + 'Content-Length'ns.Req.DefaultHeader 0 + :EndIf + End: + ∇ + + ∇ formData←ParseMultipartForm req;boundary;body;part;headers;payload;disposition;type;name;filename;tmp + boundary←crlf,'--',req.Boundary ⍝ the HTTP standard prepends '--' to the boundary + body←req.Body + formData←⎕NS'' + body←⊃body splitOnFirst boundary,'--' ⍝ drop off trailing boundary ('--' is appended to the trailing boundary) + :For part :In (crlf,body)splitOn boundary ⍝ split into parts + (headers payload)←part splitOnFirst crlf,crlf + (disposition type)←deb¨2↑headers splitOn crlf + (name filename)←deb¨2↑1↓disposition splitOn';' + name←'"'~⍨2⊃name splitOn'=' + tmp←⎕NS'' + :If {¯1=⎕NC ⍵}name + →0⊣'Invalid form field name for Jarvis'req.Fail 400 + :EndIf + filename←'"'~⍨2⊃2↑filename splitOn'=' + tmp.(Name Filename)←name filename + tmp.Content←payload + tmp.Content_Type←deb 2⊃2↑type splitOn':' + :If 0=formData.⎕NC name ⋄ formData{⍺⍎⍵,'←⍬'}name ⋄ :EndIf + formData(name{⍺⍎⍺⍺,',←⍵'})tmp + :EndFor + ∇ + + ∇ fn HandleRESTRequest ns;ind;exec;valence;ct;resp + →0 If HandleCORSRequest ns.Req + →0 If CheckAuthentication ns.Req + + :If ParsePayload + :Trap 0 DebugLevel 1 + :Select ns.Req.ContentType + :Case 'application/json' + ns.Req.Payload←JSONin ns.Req.Body + :Case 'application/xml' + ns.Req.(Payload←⎕XML Body) + :EndSelect + :Else + →0⊣('Unable to parse request body as ',ct)ns.Req.Fail 400 + :EndTrap + :EndIf + + ind←RESTMethods[;1](⍳nocase)⊂ns.Req.Method + →0 If ns.Req.Fail 405×(≢RESTMethods)'keep-alive'≡conx)∨'close'≡conx + close∨←2≠⌊0.01×res.Status ⍝ close the connection on non-2XX status + UseZip ContentEncode ns.Req + :Select 1⊃z←LDRC.Send obj(status,res.Headers res.Payload)close + :Case 0 ⍝ everything okay, nothing to do + :Case 1008 ⍝ Wrong object class likely caused by socket being closed during the request + ⍝ do nothing for now + :Else + Log'Respond: Conga error when sending response',GetIP obj + Log⍕z + :EndSelect + ns.⎕EX'Req' + ∇ + + ∇ UseZip ContentEncode req;enc + →End If 0=UseZip ⍝ is zipping enabled? + →End If 0∊⍴enc←req.AcceptEncodings ⍝ does the client accept zipped responses? + :If UseZip≤≢req.Response.Payload ⍝ payload exceeds size threshhold? + :Select ⊃enc + :Case 'gzip' + :Trap 0 + req.Response.Payload←2⊃3 ZipLevel Zipper sint req.Response.Payload + :Else + Log'ContentEncode: gzip content-encoding failed' + →End + :EndTrap + 'Content-Encoding'req.SetHeader'gzip' + :Case 'deflate' + :Trap 0 + req.Response.Payload←2⊃2 ZipLevel Zipper sint req.Response.Payload + :Else + Log'ContentEncode: deflate content-encoding failed' + →End + :EndTrap + 'Content-Encoding'req.SetHeader'deflate' + :Else + Log'ContentEncode: unsupported content-encoding - ',⊃enc ⍝ this should NEVER happen + :EndSelect + :EndIf + End: + ∇ + + :EndSection ⍝ Request Handling + + ∇ ip←GetIP objname + ip←{6::'' ⋄ ' (IP Address ',(⍕(_connections⍎⍵).IP),')'}objname + ∇ + + ∇ r←CheckFunctionName fn + ⍝ checks the requested function name and returns + ⍝ 0 if the function is allowed + ⍝ 404 (not found) either the function name does not exist, is not in IncludeFns (if defined), is in ExcludeFns (if defined) + :Access public + r←0 + :If 1<|≡fn + r←CheckFunctionName¨fn + :Else + fn←⊆,fn + →0 If r←404×fn∊AppInitFn AppCloseFn ValidateRequestFn AuthenticateFn SessionInitFn + :If ~0∊⍴_includeRegex + →0 If r←404×0∊⍴(_includeRegex ⎕S'%')fn + :EndIf + :If ~0∊⍴_excludeRegex + r←404×~0∊⍴(_excludeRegex ⎕S'%')fn + :EndIf + :EndIf + ∇ + + :class Request + :Field Public Instance AcceptEncodings←''⍝ content-encodings that the client will accept + :Field Public Instance Boundary←'' ⍝ boundary for content-type 'multipart/form-data' + :Field Public Instance Charset←'' ⍝ content charset (defaults to 'utf-8' if content-type is application/json) + :Field Public Instance Complete←0 ⍝ do we have a complete request? + :Field Public Instance ContentType←'' ⍝ content-type header value + :Field Public Instance Cookies←0 2⍴⊂'' ⍝ cookie name/value pairs + :Field Public Instance Input←'' + :Field Public Instance Headers←0 2⍴⊂'' ⍝ HTTPRequest header fields (plus any supplied from HTTPTrailer event) + :Field Public Instance Method←'' ⍝ HTTP method (GET, POST, PUT, etc) + :Field Public Instance Endpoint←'' ⍝ Requested URI + :Field Public Instance Body←'' ⍝ body of the request + :Field Public Instance Payload←'' ⍝ parsed (if JSON or XML) payload + :Field Public Instance PeerAddr←'unknown'⍝ client IP address + :Field Public Instance PeerCert←0 0⍴⊂'' ⍝ client certificate + :Field Public Instance HTTPVersion←'' + :Field Public Instance ErrorInfoLevel←1 + :Field Public Instance Response + :Field Public Instance Server + :Field Public Instance Session←⍬ + :Field Public Instance QueryParams←0 2⍴0 + :Field Public Instance UserID←'' + :Field Public Instance Password←'' + :Field Public Shared HttpStatus←↑(200 'OK')(201 'Created')(204 'No Content')(301 'Moved Permanently')(302 'Found')(303 'See Other')(304 'Not Modified')(305 'Use Proxy')(307 'Temporary Redirect')(400 'Bad Request')(401 'Unauthorized')(403 'Forbidden')(404 'Not Found')(405 'Method Not Allowed')(406 'Not Acceptable')(408 'Request Timeout')(409 'Conflict')(410 'Gone')(411 'Length Required')(412 'Precondition Failed')(413 'Request Entity Too Large')(414 'Request-URI Too Long')(415 'Unsupported Media Type')(500 'Internal Server Error')(501 'Not Implemented')(503 'Service Unavailable') + + ⍝ Content types for common file extensions + :Field Public Shared ContentTypes←18 2⍴'txt' 'text/plain' 'htm' 'text/html' 'html' 'text/html' 'css' 'text/css' 'xml' 'text/xml' 'svg' 'image/svg+xml' 'json' 'application/json' 'zip' 'application/x-zip-compressed' 'csv' 'text/csv' 'pdf' 'application/pdf' 'mp3' 'audio/mpeg' 'pptx' 'application/vnd.openxmlformats-officedocument.presentationml.presentation' 'js' 'application/javascript' 'png' 'image/png' 'jpg' 'image/jpeg' 'bmp' 'image/bmp' 'jpeg' 'image/jpeg' 'woff' 'application/font-woff' + + GetFromTable←{(⍵[;1]⍳⊂,⍺)⊃⍵[;2],⊂''} + split←{p←(⍺⍷⍵)⍳1 ⋄ ((p-1)↑⍵)(p↓⍵)} ⍝ Split ⍵ on first occurrence of ⍺ + lc←0∘(819⌶) + deb←{{1↓¯1↓⍵/⍨~' '⍷⍵}' ',⍵,' '} + + ∇ {r}←{message}Fail status + ⍝ Set HTTP response status code and message if status≠0 + :Access public + :If r←0≠1↑status + :If 0=⎕NC'message' + :If 500=status + message←ErrorInfo + :Else + message←'' ⋄ :EndIf + :EndIf + message SetStatus status + :EndIf + ∇ + + ∇ make + ⍝ barebones constructor for interactive debugging (use Jarvis.MakeRequest '') + :Access public + :Implements constructor + makeResponse + ∇ + + ∇ make1 args;query;origin;length;param;value;type + ⍝ args is the result of Conga HTTPHeader event + :Access public + :Implements constructor + + (Method Input HTTPVersion Headers)←args + Headers[;1]←lc Headers[;1] ⍝ header names are case insensitive + Method←lc Method + + (ContentType param)←deb¨2↑(';'(≠⊆⊢)GetHeader'content-type'),⊂'' + ContentType←lc ContentType + (type value)←2↑⊆deb¨'='(≠⊆⊢)param + :Select lc type + :Case '' ⍝ no parameter set + Charset←(ContentType≡'application/json')/'utf-8' + :Case 'charset' + Charset←lc value + :Case 'boundary' + Boundary←value + :EndSelect + + Cookies←ParseCookies Headers + + AcceptEncodings←ParseEncodings GetHeader'accept-encoding' + + makeResponse + + (Endpoint query)←'?'split Input + + :Trap 11 ⍝ trap domain error on possible bad UTF-8 sequence + Endpoint←URLDecode Endpoint + QueryParams←ParseQueryString query + :If 'basic '≡lc 6↑auth←GetHeader'authorization' + (UserID Password)←':'split Base64Decode 6↓auth + :EndIf + :Else + Complete←1 ⍝ mark as complete + Fail 400 ⍝ 400 = bad request + →0 + :EndTrap + + length←GetHeader'content-length' + Complete←('get'≡Method)∧0=⊃⊃(//)⎕VFI length ⍝ we're a GET and there's no content-length or content-length=0 + Complete∨←(0∊⍴length)>∨/'chunked'⍷GetHeader'transfer-encoding' ⍝ or no length supplied and we're not chunked + ∇ + + ∇ makeResponse + ⍝ create the response namespace + Response←⎕NS'' + Response.(Status StatusText Payload)←200 'OK' '' + Response.Headers←0 2⍴'' '' + ∇ + + ∇ ProcessBody args + :Access public + Body←args + Complete←1 + ∇ + + ∇ ProcessChunk args + :Access public + ⍝ args is [1] chunk content [2] chunk-extension name/value pairs (which we don't expect and won't process) + Body,←1⊃args + ∇ + + ∇ ProcessTrailer args;inds;mask + :Access public + args[;1]←lc args[;1] + mask←(≢Headers)≥inds←Headers[;1]⍳args[;1] + Headers[mask/inds;2]←mask/args[;2] + Headers⍪←(~mask)⌿args + Complete←1 + ∇ + + ∇ r←Hostname;h + :Access public + :If ~0∊⍴h←GetHeader'host' + r←'http',(~Server.Secure)↓'s://',h + :Else + r←Server.Hostname + :EndIf + ∇ + + ∇ params←ParseQueryString query + params←0 2⍴⊂'' + →0⍴⍨0∊⍴query + :If '='∊query ⍝ contains name=value? + params←URLDecode¨2↑[2]↑'='(≠⊆⊢)¨'&'(≠⊆⊢)query + :Else + params←URLDecode query + :EndIf + ∇ + + ∇ r←ParseEncodings encodings + r←(⎕C(⊃¨';'(≠⊆⊢)¨','(≠⊆⊢)encodings~' '))∩'gzip' 'deflate' + ∇ + + ∇ cookies←ParseCookies headers;cookieHeader;cookie + :Access public shared + cookies←0 2⍴⊂'' + :For cookieHeader :In (headers[;1]≡¨⊂'cookie')/headers[;2] + :For cookie :In (({⍵↓⍨+/∧\' '=⍵}⌽)⍣2)¨';'(≠⊆⊢)cookieHeader + cookies⍪←2↑('='(≠⊆⊢)cookie),⊂'' + :EndFor + :EndFor + cookies←(⌽≠⌽cookies[;1])⌿cookies + ∇ + + ∇ r←URLDecode r;rgx;rgxu;i;j;z;t;m;⎕IO;lens;fill + :Access public shared + ⍝ Decode a Percent Encoded string https://en.wikipedia.org/wiki/Percent-encoding + ⎕IO←0 + ((r='+')/r)←' ' + rgx←'[0-9a-fA-F]' + rgxu←'%[uU]',(4×⍴rgx)⍴rgx ⍝ 4 characters + r←(rgxu ⎕R{{⎕UCS 16⊥⍉16|'0123456789ABCDEF0123456789abcdef'⍳⍵}2↓⍵.Match})r + :If 0≠⍴i←(r='%')/⍳⍴r + :AndIf 0≠⍴i←(i≤¯2+⍴r)/i + z←r[j←i∘.+1 2] + t←'UTF-8'⎕UCS 16⊥⍉16|'0123456789ABCDEF0123456789abcdef'⍳z + lens←⊃∘⍴¨'UTF-8'∘⎕UCS¨t ⍝ UTF-8 is variable length encoding + fill←i[¯1↓+\0,lens] + r[fill]←t + m←(⍴r)⍴1 ⋄ m[(,j),i~fill]←0 + r←m/r + :EndIf + ∇ + + base64←{⎕IO ⎕ML←0 1 ⍝ from dfns workspace - Base64 encoding and decoding as used in MIME. + chars←'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + bits←{,⍉(⍺⍴2)⊤⍵} ⍝ encode each element of ⍵ in ⍺ bits, and catenate them all together + part←{((⍴⍵)⍴⍺↑1)⊂⍵} ⍝ partition ⍵ into chunks of length ⍺ + 0=2|⎕DR ⍵:2∘⊥∘(8∘↑)¨8 part{(-8|⍴⍵)↓⍵}6 bits{(⍵≠64)/⍵}chars⍳⍵ ⍝ decode a string into octets + four←{ ⍝ use 4 characters to encode either + 8=⍴⍵:'=='∇ ⍵,0 0 0 0 ⍝ 1, + 16=⍴⍵:'='∇ ⍵,0 0 ⍝ 2 + chars[2∘⊥¨6 part ⍵],⍺ ⍝ or 3 octets of input + } + cats←⊃∘(,/)∘((⊂'')∘,) ⍝ catenate zero or more strings + cats''∘four¨24 part 8 bits ⍵ + } + + ∇ r←{cpo}Base64Encode w + ⍝ Base64 Encode + ⍝ Optional cpo (code points only) suppresses UTF-8 translation + ⍝ if w is numeric (single byte integer), skip any conversion + :Access public shared + :If 83=⎕DR w ⋄ r←base64 w + :ElseIf 0=⎕NC'cpo' ⋄ r←base64'UTF-8'⎕UCS w + :Else ⋄ r←base64 ⎕UCS w + :EndIf + ∇ + + ∇ r←{cpo}Base64Decode w + ⍝ Base64 Decode + ⍝ Optional cpo (code points only) suppresses UTF-8 translation + :Access public shared + :If 0=⎕NC'cpo' ⋄ r←'UTF-8'⎕UCS base64 w + :Else ⋄ r←⎕UCS base64 w + :EndIf + ∇ + + ∇ r←{table}GetHeader name + :Access Public Instance + :If 0=⎕NC'table' ⋄ table←Headers ⋄ :EndIf + table[;1]←lc table[;1] + r←(lc name)GetFromTable table + ∇ + + ∇ name DefaultHeader value + :Access public instance + :If 0∊⍴Response.Headers GetHeader name + name SetHeader value + :EndIf + ∇ + + ∇ r←{endpoint}MakeURI resource + :Access public instance + ⍝ make a URI for a RESTful resource relative to the request endpoint + :If 0≠⎕NC'endpoint' + r←Hostname,endpoint,∊'/',¨⍕¨⊆resource + :Else + r←Hostname,Endpoint,∊'/',¨⍕¨⊆resource + :EndIf + ∇ + + ∇ r←ErrorInfo + :Trap 0 + r←⍕ErrorInfoLevel↑⎕DMX.(EM({⍵↑⍨⍵⍳']'}2⊃DM)) + :Else + r←'' + :EndTrap + ∇ + + ∇ {(name value)}←name SetHeader value + :Access Public Instance + Response.Headers⍪←name(∊⍕value) + ∇ + + ∇ {(name cookie)}←name SetCookie cookie + :Access public instance + ⍝ create a response "set-cookie" header + ⍝ cookie is the cookie value followed by any ;-delimited attributes + 'set-cookie'SetHeader name,'=',cookie + ∇ + + ∇ {(name value)}←SetContentType contentType + :Access public instance + ⍝ shortcut function to set the response content-type header + (name value)←'Content-Type'SetHeader contentType + ∇ + + ∇ value←GetCookie name + :Access public instance + ⍝ retrieve a request cookie + value←(Cookies[;1]⍳⊆,name)⊃Cookies[;2],⊂'' + ∇ + + ∇ {status}←{statusText}SetStatus status + :Access public instance + :If status≠0 + :If 0=⎕NC'statusText' ⋄ statusText←'' ⋄ :EndIf + statusText←{0∊⍴⍵:⍵ ⋄ '('=⊣/⍵:⍵ ⋄ '(',⍵,')'}statusText + statusText←deb((HttpStatus[;1]⍳status)⊃HttpStatus[;2],⊂''),' ',statusText + Response.(Status StatusText)←status statusText + :EndIf + ∇ + + ∇ r←ContentTypeForFile filename;ext + :Access public instance + ext←⊂1↓3⊃⎕NPARTS filename + r←(ContentTypes[;1]⍳ext)⊃ContentTypes[;2],⊂'text/html' + r,←('text/html'≡r)/'; charset=utf-8' + ∇ + + :EndClass + + :Section SessionHandler + + ∇ InitSessions + ⍝ initialize session handling + :If 0≠SessionTimeout ⍝ are we using sessions? + _sessions←⍬ + _sessionsInfo←0 5⍴0 ⍝ [;1] id, [;2] IP address, [;3] creation time, [;4] last active time, [;5] ref to session + ⎕RL←⍬ + :If 0 means no timeout and sessions are managed by the application + _sessionThread←SessionMonitor&SessionTimeout + :EndIf + :EndIf + ∇ + + ∇ SessionMonitor timeout;expired;dead + :Repeat + :If 0<≢_sessionsInfo + :Hold 'Sessions' + :If ∨/expired←SessionTimeout IsExpired _sessionsInfo[;4] ⍝ any expired? + ⍝ ↓↓↓ if a session expires, remove the namespace from _sessions + ⍝ but leave the entry in _sessionsInfo (removing the namespace reference) + ⍝ so that we can report to the user that his session timed out + ⍝ if he returns before SessionCleanupTime passes + _sessions~←expired/_sessionsInfo[;5] ⍝ remove from sessions list + (expired/_sessionsInfo[;5])←0 ⍝ remove reference from _sessionsInfo + :EndIf + ⍝ ↓↓↓ SessionCleanupTime is used to clean up _sessionsInfo after a session has expired + ⍝ In general SessionCleanupTime should be set to a value ≥ SessionTimeout + :If ∨/dead←(0=_sessionsInfo[;5])∧SessionCleanupTime IsExpired _sessionsInfo[;4] ⍝ any expired sessions need their info removed? + _sessionsInfo⌿⍨←~dead ⍝ remove from _sessionsInfo + :EndIf + :EndHold + :EndIf + {}⎕DL timeout×60 + :EndRepeat + ∇ + + MakeSessionId←{⎕IO←0 ⋄((0(819⌶)⎕A),⎕A,⎕D)[(?20⍴62),5↑1↓⎕TS]} + IsExpired←{⍺≤0: 0 ⋄ (Now-⍵)>(⍺×60000)÷86400000} + + ∇ r←DateToIDNX ts + ⍝ Date to IDN eXtended (will be replaced by ⎕DT when ⎕DT is in the latest 3 versions of Dyalog APL) + r←(2 ⎕NQ'.' 'DateToIDN'(3↑ts))+(0 60 60 1000⊥¯4↑7↑ts)÷86400000 + ∇ + + ∇ CreateSession req;ref;now;id;ts;rc + id←MakeSessionId'' + now←Now + :Hold 'Sessions' + _sessions,←ref←⎕NS'' + _sessionsInfo⍪←id req.PeerAddr now now ref + req.Session←ref + :EndHold + :If ~0∊⍴SessionInitFn + :If 3=CodeLocation.⎕NC SessionInitFn + :Trap 0 DebugLevel 1 + :Trap 85 + stopIf DebugLevel 2 + rc←SessionInitFn CodeLocation.{1(85⌶)⍺,' ⍵'}req + :Else ⋄ rc←0 + :EndTrap + + :If 0≠rc + (_sessions _sessionsInfo)←¯1↓¨_sessions _sessionsInfo + →0⊣('Session intialization returned ',⍕rc)req.Fail 500 + :EndIf + :Else + →0⊣(⎕DMX.EM,' occurred during session initialization failed')req.Fail 500 + :EndTrap + :Else + →0⊣('Session initialization function "',SessionInitFn,'" not found')req.Fail 500 + :EndIf + :EndIf + :If SessionUseCookie + SessionIdHeader req.SetCookie id,(SessionTimeout>0)/'; Max-Age=',⍕⌈60×SessionTimeout + :Else + SessionIdHeader req.SetHeader id + :EndIf + ∇ + + ∇ r←KillSession id;ind + ⍝ forcibly kill a session + ⍝ r is 1 if session was killed, 0 if not found + :Hold 'Sessions' + :If r←(≢_sessionsInfo)≥ind←_sessionsInfo[;1]⍳⊆id + _sessions~←_sessionsInfo[ind;5] + _sessionsInfo⌿⍨←ind≠⍳≢_sessionsInfo + :EndIf + :EndHold + ∇ + + ∇ req TimeoutSession ind + ⍝ assumes :Hold 'Sessions' is set in calling environment + ⍝ removes session from _sessions and marks it as time out in _sessionsInfo + _sessions~←_sessionsInfo[ind;5] + _sessionsInfo⌿←ind≠⍳≢_sessionsInfo + ∇ + + ∇ id←GetSessionId req + :If SessionUseCookie + id←req.GetCookie SessionIdHeader + :Else + id←req.GetHeader SessionIdHeader + :EndIf + ∇ + + ∇ r←CheckSession req;ind;session;timedOut;id + ⍝ check for valid session (only called if SessionTimeout≠0) + r←1 + :Hold 'Sessions' + id←GetSessionId req + ind←_sessionsInfo[;1]⍳⊂id + →0⍴⍨ind>≢_sessionsInfo + + :If 0∊⍴session←⊃_sessionsInfo[ind;5] ⍝ already timed out (session was already removed from _sessions) + :OrIf SessionTimeout IsExpired _sessionsInfo[ind;4] ⍝ newly expired + req TimeoutSession ind + →0 + :EndIf + ⍝ we have a valid session, refresh the cookie or set the header + :If SessionUseCookie + SessionIdHeader req.SetCookie id,(SessionTimeout>0)/'; Max-Age=',⍕⌈60×SessionTimeout + :ElseIf + SessionIdHeader req.SetHeader id + :EndIf + _sessionsInfo[ind;4]←Now + req.Session←session + r←0 + :EndHold + ∇ + + :EndSection + + :Section Utilities + + If←((0≠⊃)⊢)⍴⊣ ⍝ test for 0 return + isChar←{0 2∊⍨10|⎕DR ⍵} + toChar←{(⎕DR'')⎕DR ⍵} + stripQuotes←{'""'≡2↑¯1⌽⍵:¯1↓1↓⍵ ⋄ ⍵} ⍝ strip leading and ending " + deb←{{1↓¯1↓⍵/⍨~' '⍷⍵}' ',⍵,' '} ⍝ delete extraneous blanks + dlb←{⍵↓⍨+/∧\' '=⍵} ⍝ delete leading blanks + lc←0∘(819⌶) ⍝ lower case + uc←1∘(819⌶) ⍝ upper case + nameClass←{⎕NC⊂,'⍵'} ⍝ name class of argument + nocase←{(lc ⍺)⍺⍺ lc ⍵} ⍝ case insensitive operator + begins←{⍺≡(⍴⍺)↑⍵} ⍝ does ⍺ begin with ⍵? + ends←{⍺≡(-≢⍺)↑⍵} ⍝ does ⍺ end with ⍵? + match←{⍺ (≡nocase) ⍵} ⍝ case insensitive ≡ + sins←{0∊⍴⍺:⍵ ⋄ ⍺} ⍝ set if not set + stopIf←{1∊⍵:-⎕TRAP←0 'C' '⎕←''Stopped for debugging... (Press Ctrl-Enter)''' ⋄ shy←0} ⍝ faster alternative to setting ⎕STOP + show←{(2⊃⎕SI),'[',(⍕2⊃⎕LC),'] ',⍵} ⍝ debugging utility + utf8←{3=10|⎕DR ⍵: 256|⍵ ⋄ 'UTF-8' ⎕UCS ⍵} + fromutf8←{0::(⎕AV,'?')[⎕AVU⍳⍵] ⋄ 'UTF-8'⎕UCS ⍵} ⍝ Turn raw UTF-8 input into text + sint←{⎕IO←0 ⋄ 83=⎕DR ⍵:⍵ ⋄ 0 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 ¯127 ¯126 ¯125 ¯124 ¯123 ¯122 ¯121 ¯120 ¯119 ¯118 ¯117 ¯116 ¯115 ¯114 ¯113 ¯112 ¯111 ¯110 ¯109 ¯108 ¯107 ¯106 ¯105 ¯104 ¯103 ¯102 ¯101 ¯100 ¯99 ¯98 ¯97 ¯96 ¯95 ¯94 ¯93 ¯92 ¯91 ¯90 ¯89 ¯88 ¯87 ¯86 ¯85 ¯84 ¯83 ¯82 ¯81 ¯80 ¯79 ¯78 ¯77 ¯76 ¯75 ¯74 ¯73 ¯72 ¯71 ¯70 ¯69 ¯68 ¯67 ¯66 ¯65 ¯64 ¯63 ¯62 ¯61 ¯60 ¯59 ¯58 ¯57 ¯56 ¯55 ¯54 ¯53 ¯52 ¯51 ¯50 ¯49 ¯48 ¯47 ¯46 ¯45 ¯44 ¯43 ¯42 ¯41 ¯40 ¯39 ¯38 ¯37 ¯36 ¯35 ¯34 ¯33 ¯32 ¯31 ¯30 ¯29 ¯28 ¯27 ¯26 ¯25 ¯24 ¯23 ¯22 ¯21 ¯20 ¯19 ¯18 ¯17 ¯16 ¯15 ¯14 ¯13 ¯12 ¯11 ¯10 ¯9 ¯8 ¯7 ¯6 ¯5 ¯4 ¯3 ¯2 ¯1[utf8 ⍵]} + Zipper←219⌶ + + ∇ r←DyalogRoot + r←{⍵,('/\'∊⍨⊢/⍵)↓'/'}{0∊⍴t←2 ⎕NQ'.' 'GetEnvironment' 'DYALOG':⊃1 ⎕NPARTS⊃2 ⎕NQ'.' 'GetCommandLineArgs' ⋄ t}'' + ∇ + + ∇ r←MyAddr + :Access public shared + :Trap 0 + r←2 ⎕NQ #'TCPGetHostID' + :Else + r←'localhost' + :EndTrap + ∇ + + ∇ r←crlf + r←⎕UCS 13 10 + ∇ + + ∇ QuadOFF + ⍝ cover for ⎕OFF in case we want to add debugging + ⎕OFF + ∇ + + ∇ r←Now + r←DateToIDNX ⎕TS + ∇ + + ∇ r←InTerm;system + :Access Public Shared + ⍝ determine if interactive terminal is available + →0⍴⍨r←~0∊⍴2 ⎕NQ'.' 'GetEnvironment' 'RIDE_INIT' + →0⍴⍨r←'Win' 'Dev'≡system←3↑¨(⊂1 4)⌷'.'⎕WG'APLVersion' + r←('Lin' 'Dev'≡system)∧{0::0 ⋄ 1⊣⎕SH'test -t 0'}'' + ∇ + + ∇ r←fmtTS ts + r←,'G⊂9999/99/99 @ 99:99:99⊃'⎕FMT 100⊥6↑ts + ∇ + + ∇ r←a splitOn w + ⍝ split a where w occurs (removing w from the result) + r←a{⍺{(¯1+⊃¨⊆⍨⍵)↓¨⍵⊆⍺}(1+≢⍵)*⍵⍷⍺}w + ∇ + + ∇ r←a splitOnFirst w + ⍝ split a on first occurence of w (removing w from the result) + r←a{⍺{(¯1+⊃¨⊆⍨⍵)↓¨⍵⊆⍺}(1+≢⍵)*<\⍵⍷⍺}w + ∇ + + ∇ r←type ipRanges string;ranges + r←'' + :Select ≢ranges←{('.'∊¨⍵){⊂1↓∊',',¨⍵}⌸⍵}string splitOn',' + :Case 0 + →0 + :Case 1 + r←,⊂((1+'.'∊⊃ranges)⊃'IPV6' 'IPV4')(⊃ranges) + :Case 2 + r←↓'IPV4' 'IPV6',⍪ranges + :EndSelect + r←⊂(('Accept' 'Deny'⍳⊂type)⊃'AllowEndPoints' 'DenyEndPoints')r + ∇ + + ∇ r←isWin + ⍝ are we running under Windows? + r←'Win'≡3↑⊃#.⎕WG'APLVersion' + ∇ + + ∇ r←isRelPath w + ⍝ is path w a relative path? + r←{{~'/\'∊⍨(⎕IO+2×isWin∧':'∊⍵)⊃⍵}3↑⍵}w + ∇ + + ∇ r←isDir path + ⍝ is path a directory? + r←{22::0 ⋄ 1=1 ⎕NINFO ⍵}path + ∇ + + ∇ r←SourceFile;class + :If 0∊⍴r←4⊃5179⌶class←⊃∊⎕CLASS ⎕THIS + r←{6::'' ⋄ ∊1 ⎕NPARTS ⍵⍎'SALT_Data.SourceFile'}class + :EndIf + ∇ + + ∇ r←makeRegEx w + :Access public shared + ⍝ convert a simple search using ? and * to regex + r←{0∊⍴⍵:⍵ + {'^',(⍵~'^$'),'$'}{¯1=⎕NC('A'@(∊∘'?*'))r←⍵:('/'=⊣/⍵)↓(¯1×'/'=⊢/⍵)↓⍵ ⍝ already regex? (remove leading/trailing '/' + r←∊(⊂'\.')@('.'=⊢)r ⍝ escape any periods + r←'.'@('?'=⊢)r ⍝ ? → . + r←∊(⊂'\/')@('/'=⊢)r ⍝ / → \/ + ∊(⊂'.*')@('*'=⊢)r ⍝ * → .* + }⍵ ⍝ add start and end of string markers + }w + ∇ + + ∇ (rc msg)←{root}LoadFromFolder path;type;name;nsName;parts;ns;files;folders;file;folder;ref;r;m;findFiles;pattern + :Access public + ⍝ Loads an APL "project" folder + (rc msg)←0 '' + root←{6::⍵ ⋄ root}# + findFiles←{ + (names type hidden)←0 1 6(⎕NINFO⍠1)∊1 ⎕NPARTS path,'/',⍵ + names/⍨(~hidden)∧type=2 + } + files←'' + :For pattern :In ','(≠⊆⊢)LoadableFiles + files,←findFiles pattern + :EndFor + folders←{ + (names type hidden)←0 1 6(⎕NINFO⍠1)∊1 ⎕NPARTS path,'/*' + names/⍨(~hidden)∧type=1 + }⍬ + :For file :In files + :Trap 11 + 2(root ⍙FIX)'file://',file + :Else + msg,←'Unable to load file: ',file,⎕UCS 13 + :EndTrap + :EndFor + :For folder :In folders + nsName←2⊃1 ⎕NPARTS folder + ref←0 + :Select root.⎕NC⊂nsName + :Case 9.1 ⍝ namespace + ref←root⍎nsName + :Case 0 ⍝ not defined + ref←⍎nsName root.⎕NS'' + :Else ⍝ oops + msg,←'"',folder,'" cannot be mapped to a valid namespace name',⎕UCS 13 + :EndSelect + :If ref≢0 + (r m)←ref LoadFromFolder folder + r←rc⌈r + msg,←m + :EndIf + :EndFor + msg←¯1↓msg + rc←4××≢msg + ∇ + + ∇ {r}←{larg}(ref ⍙FIX)rarg;isArrayNotation;t;f;p + ⍝ ⎕FIX cover that accommodates Array Notation and .apla files + ⍝ revert to using ⎕FIX when it supports them + larg←{6::⍵ ⋄ larg}1 + isArrayNotation←{~0 2∊⍨10|⎕DR ⍵:0 ⋄ {(⊃⍵)∊d←'[''¯.⊂⎕⍬',⎕D:1 ⋄ (2⊃2↑⍵)∊d,'( '}(∊⍵)~⎕UCS 9 32} + :Trap 0 + :If 1=≡rarg + :AndIf 'file://'≡7↑rarg + :AndIf '.apla'≡lc⊃⌽p←⎕NPARTS f←7↓rarg + :If larg=2 + r←ref⍎(2⊃p),'←',0 Deserialise⊃⎕NGET f + :Else + r←ref⍎0 Deserialise⊃⎕NGET f + :EndIf + :ElseIf isArrayNotation 1↓∊(⎕UCS 13),¨⊆rarg + r←ref⍎0 Deserialise rarg + :Else + r←larg ref.⎕FIX rarg + :EndIf + :Else + ⎕SIGNAL⊂t,⍪⎕DMX⍎1⌽')(',∊⍕t←'EN' 'EM' 'Message' + :EndTrap + ∇ + + Deserialise←{ +⍝ copied (and modified to work here) from +⍝ https://github.com/Dyalog/qSE/tree/db88297980476d81e0122624ecaa246c94149555 + +⍝ Convert text to array + ⍺←⍬ ⍝ 1=execute expression; 0=return expression + ⎕IO←0 + Char←0 2∊⍨10|⎕DR + Num←2|⎕DR + Null←∧/⎕NULL≡¨⊢ ⍝ can't use ∧.= because = is pervasive on deep arrays + Ptr←6=10|⎕DR + Basic←Char∨Num∨Null + FirstNum←Num¨⊃⍤/⊢ + FirstNs←{9∊⎕NC'⍵'}¨⊃⍤/⊢ + + sysVars←'⎕CT' '⎕DIV' '⎕IO' '⎕ML' '⎕PP' '⎕RL' '⎕RTL' '⎕WX' '⎕USING' '⎕AVU' '⎕DCT' '⎕FR' + L←lc + + execute←FirstNum ⍺,1 + caller←FirstNs ⍺,⊃⎕RSI + q←'''' + SEP←'⋄',⎕UCS 10 13 + Unquot←{(⍺⍺ ⍵)×~≠\q=⍵} + SepMask←∊∘SEP Unquot + ParenLev←+\(ׯ3+7|¯3+'([{)]}'∘⍳)Unquot + Paren←1⌽')(',⊢ + Split←{1↓¨⍺⍺⊂Over(1∘,)⍵} + Over←{(⍵⍵ ⍺)⍺⍺(⍵⍵ ⍵)} + EachIfAny←{0=≢⍵:⍵ ⋄ ⍺ ⍺⍺¨⍵} + EachNonempty←{⍺ ⍺⍺ EachIfAny Over((×≢¨⍵~¨' ')/⊢)⍵} + Parse←{ + 0=≢⍵:'' + bot←0=⍺ + (2≤≢⍵)>∨/¯1↓bot:⍺ SubParse ⍵ + p←bot×SepMask ⍵ + ∨/p:∊{1=≢⍵:',⊂',⍵ ⋄ ⍵}⍺(Paren ∇)EachNonempty Over(p Split)⍵ + p←2(1,>/∨¯1↓0,0)∧(l≠1)∨(t=0))×+\(t=1)∧(l=1))⊆x ⍝ cut expression within level-1 parentheses + 1=≢x:H ⍺ ∇⊃x ⍝ single expression : don't enclose with ¨ + DEBUG∧1<⌈/l:H ⍺ ∇¨x ⍝ force going through the hard code + 10::H ⍺ ∇¨x ⋄ H ⍺⍎¨x ⍝ attempt to ⍎¨ with a single guard - otherwise dig each + } + DEBUG:⍺ ExecuteEach ⍵ ⍝ force going through the hard code + 10::⍺ ExecuteEach ⍵ ⋄ ⍺⍎⍵ ⍝ attempt simple ⍎ and catch LIMIT ERROR + } + ⍝ Make normalised simple vector: + w←↓⍣(2=≢⍴⍵)⊢⍵ ⍝ if mat, make nested + w←{¯1↓∊⍵,¨⎕UCS 13}⍣(2=|≡w)⊢w ⍝ if nested, make simple + w←'''[^'']*''' '⍝.*'⎕R'&' ''⊢w ⍝ strip comments + w/⍨←{(∨\⍵)∧⌽∨\⌽⍵}33≤⎕UCS w ⍝ strip leading/trailing non-printables + pl←ParenLev w + (0≠⊢/pl)∨(∨/0>pl):'Unmatched brackets'⎕SIGNAL 2 + ∨/(pl=0)×SepMask w:'Multi-line input'⎕SIGNAL 11 + (⊃⎕RSI)Execute⍣execute⊢pl Parse w ⍝ materialise namespace as child of calling namespace + } + + :EndSection + + :Section HTML + ∇ r←ScriptFollows + ⍝ return the subsequent block of comments as a text script + r←{⍵/⍨'⍝'≠⊃¨⍵}{1↓¨⍵/⍨∧\'⍝'=⊃¨⍵}{⍵{((∨\⍵)∧⌽∨\⌽⍵)/⍺}' '≠⍵}¨(1+2⊃⎕LC)↓↓(180⌶)2⊃⎕XSI + r←2↓∊(⎕UCS 13 10)∘,¨r + ∇ + + ∇ r←{path}EndPoints ref;ns + :Access public + :If 0=⎕NC'path' ⋄ path←'' + :Else ⋄ path,←'.' + :EndIf + r←path∘,¨{(⊂'')~⍨⍵.{⍵/⍨1 1 0≡×|⎕IO⊃⎕AT ⍵}¨⍵.⎕NL ¯3}ref ⍝ limit to result-returning monadic/dyadic/ambivalent functions + :For ns :In ref.⎕NL ¯9.1 + r,←(path,ns)EndPoints ref⍎ns + :EndFor + ∇ + + ∇ r←HtmlPage;endpoints + :Access public + r←ScriptFollows +⍝ +⍝ +⍝ +⍝ +⍝ +⍝Jarvis +⍝ +⍝ +⍝ +⍝
+⍝
+⍝ Request +⍝
+⍝
+⍝ +⍝ ⍠ +⍝
+⍝
+⍝ +⍝ +⍝
+⍝
+⍝ +⍝
+⍝
+⍝
+⍝
+⍝ Response +⍝
+⍝
+⍝
+⍝ +⍝
+⍝ +⍝ + endpoints←{⍵/⍨0=CheckFunctionName ⍵}EndPoints CodeLocation + :If 0∊⍴endpoints + endpoints←'No Endpoints Found' + :Else + endpoints←∊{''}¨'/'@('.'=⊢)¨endpoints + endpoints←'' + :EndIf + r←endpoints{i←⍵⍳'⍠' ⋄ ((i-1)↑⍵),⍺,i↓⍵}r + r←⎕UCS'UTF-8'⎕UCS r + ∇ + :EndSection + +:EndClass \ No newline at end of file diff --git a/ref_c b/ref_c new file mode 100644 index 0000000..4e9a857 --- /dev/null +++ b/ref_c @@ -0,0 +1,5010 @@ + +#include +#include +#include "common.h" + +uint16_t Q; + +static int8_t next(FILE * input) { + char c; + while((c = getc(input)) != EOF) { + Q++; + if(c == ' ' || c == '\n' || c == '\t') + return c; + } + return 0; +} + +static int32_t getnum(FILE * input) { + uint8_t sign = next(input), c; + int32_t n = 0, q; + if(sign != '\t' && sign != ' ') + return 0; + q = sign == '\t' ? -1 : 1; + while((c = next(input)) != '\n') { + n <<= 1; + if(c == '\t') n++; + if(!c) break; + } + return n * q; +} + +static vector(char) getlab(FILE * input) { + vector(char) label = NULL; + uint8_t c; + while((c = next(input)) != '\n') + vector_push_back(label, c == '\t' ? 'T' : 'S'); + vector_push_back(label, 0); + return label; +} + +static struct instruction_t parse_heap(FILE * input, void (*fatal)(char * s)) { + switch(next(input)) { + case ' ': return (struct instruction_t) { STO, 0 }; + case '\t': return (struct instruction_t) { RCL, 0 }; + default: fatal("|e, IMP heap"); return (struct instruction_t) { ERR, 0 }; + } +} + +static struct instruction_t parse_arith(FILE * input, void (*fatal)(char * s)) { + switch(next(input)) { + case ' ': switch(next(input)) { + case ' ': return (struct instruction_t) { ADD, 0 }; + case '\t': return (struct instruction_t) { SUB, 0 }; + case '\n': return (struct instruction_t) { MUL, 0 }; + default: fatal("e, IMP arith"); return (struct instruction_t) { ERR, 0 }; + } + case '\t': switch(next(input)) { + case ' ': return (struct instruction_t) { DIV, 0 }; + case '\t': return (struct instruction_t) { MOD, 0 }; + default: fatal("|e, IMP arith"); return (struct instruction_t) { ERR, 0 }; + } + default: fatal("|e, IMP arith"); return (struct instruction_t) { ERR, 0 }; + } +} + +static struct instruction_t parse_io(FILE * input, void (*fatal)(char * s)) { + switch(next(input)) { + case ' ': switch(next(input)) { + case ' ': return (struct instruction_t) { PUTC, 0 }; + case '\t': return (struct instruction_t) { PUTN, 0 }; + default: fatal("|e, IMP io"); return (struct instruction_t) { ERR, 0 }; + } + case '\t': switch(next(input)) { + case ' ': return (struct instruction_t) { GETC, 0 }; + case '\t': return (struct instruction_t) { GETN, 0 }; + default: fatal("|e, IMP io"); return (struct instruction_t) { ERR, 0 }; + } + default: fatal("|e, IMP io"); return (struct instruction_t) { ERR, 0 }; + } +} + +static struct instruction_t parse_stack(FILE * input, void (*fatal)(char * s)) { + switch(next(input)) { + case ' ': return (struct instruction_t) { PSH, getnum(input) }; + case '\n': switch(next(input)) { + case ' ': return (struct instruction_t) { DUP, 0 }; + case '\t': return (struct instruction_t) { XCHG, 0 }; + case '\n': return (struct instruction_t) { DROP, 0 }; + default: fatal("e, IMP stk"); return (struct instruction_t) { ERR, 0 }; + } + case '\t': switch(next(input)) { + case ' ': return (struct instruction_t) { COPY, .data = getnum(input) }; + case '\n': return (struct instruction_t) { SLIDE, .data = getnum(input) }; + default: fatal("|e, IMP stk"); return (struct instruction_t) { ERR, 0 }; + } + default: fatal("e, IMP stk"); return (struct instruction_t) { ERR, 0 }; + } +} + +static struct instruction_t parse_flow(FILE * input, void (*fatal)(char * s)) { + switch(next(input)) { + case ' ': switch(next(input)) { + case ' ':return (struct instruction_t) { LBL, .label = getlab(input) }; + case '\t': return (struct instruction_t) { CALL, .label = getlab(input) }; + case '\n': return (struct instruction_t) { JMP, .label = getlab(input) }; + default: fatal("e, IMP flow"); return (struct instruction_t) { ERR, 0 }; + } + case '\t': switch(next(input)) { + case ' ': return (struct instruction_t) { BZ, .label = getlab(input) }; + case '\t': return (struct instruction_t) { BLTZ, .label = getlab(input) }; + case '\n': return (struct instruction_t) { RET, 0 }; + default: fatal("e, IMP flow"); return (struct instruction_t) { ERR, 0 }; + } + case '\n': + if(next(input) == '\n') + return (struct instruction_t) { STOP, 0 }; + else + { fatal("bad end, IMP flow"); return (struct instruction_t) { ERR, 0 }; } + default: fatal("e, IMP flow"); return (struct instruction_t) { ERR, 0 }; + } +} + +struct _label_t { + int32_t id; + char * name; + struct instruction_t parent; +}; + +static struct _label_t * getlabel(vector(struct _label_t) vec, char * label_text) { + vector_foreach(struct _label_t, it, vec) + if(!strcmp(label_text, it->name)) + return it; + return NULL; +} + +static vector(struct label_t) fixup_labels(vector(struct instruction_t) program, void(*warn)(char * s)) { + vector(struct _label_t) labels = NULL; + vector(struct label_t) labs = NULL; + int32_t labid = 1; + + vector_foreach(struct instruction_t, it, program) + if(it->type == LBL) { + struct _label_t * l; + if((l = getlabel(labels, it->label)) != NULL) { + warn("duplicated label."); + vector_free(it->label); + it->data = l->id; + continue; + } + struct _label_t lab = {.id = labid, .parent = *it, .name = it->label}; + struct label_t public_label = {.id = labid, .parent = it}; + it->data = labid++; + vector_push_back(labels, lab); + vector_push_back(labs, public_label); + } + + vector_foreach(struct instruction_t, it, program) + switch(it->type) { + case CALL: case JMP: case BZ: case BLTZ: { + struct _label_t * l; + if((l = getlabel(labels, it->label)) != NULL) { + vector_free(it->label); + it->data = l->id; + } else { + vector_free(it->label); + warn("dead label."); + it->data = 0; + } + } + } + + vector_foreach(struct _label_t, it, labels) + vector_free(it->name); + + vector_free(labels); + + return labs; +} + +struct parse_result_t parse(FILE * input, void (*fatal)(char * s), void (*warn)(char * s)) { + vector(struct instruction_t) q = NULL; + struct instruction_t cur; + + while(!feof(input)) { + switch(next(input)) { + case '\t': + switch(next(input)) { + case '\t': + vector_push_back(q, cur = parse_heap(input, fatal)); + break; + case ' ': + vector_push_back(q, cur = parse_arith(input, fatal)); + break; + case '\n': + vector_push_back(q, cur = parse_io(input, fatal)); + break; + default: + fatal("? , E, IMP N/A"); return (struct parse_result_t) { NULL, NULL }; + } + break; + case ' ': + vector_push_back(q, cur = parse_stack(input, fatal)); + break; + case '\n': + vector_push_back(q, cur = parse_flow(input, fatal)); + break; + } + + if(cur.type == ERR) + return (struct parse_result_t) { NULL, NULL }; + } + + return (struct parse_result_t) { q, fixup_labels(q, warn) }; +} + + +#include +#include +#include +#include +#include +#include + +#include "vector.h" +#include "common.h" + +static void append_code(char ** buf, char * format, ...) { + va_list args, args2; + va_start(args, format); + va_copy(args2, args); + uint32_t buflen = *buf ? strlen(*buf) : 0; + uint32_t length = 1 + buflen + vsnprintf(NULL, 0, format, args); + va_end(args); + if(*buf) + *buf = realloc(*buf, length); + else + *buf = malloc(length); + assert(*buf); + vsnprintf(*buf + buflen, length - buflen, format, args2); + va_end(args2); +} + +#define emit(x) append_code(&code, x) +#define emitf(x,...) append_code(&code, x, __VA_ARGS__) + +char * compile(struct parse_result_t program) { + unsigned callid = vector_size(program.labels); + char * code = NULL; + emit( + "#include \"vector.h\"\n" + "#ifndef DURING_JIT\n" + "\t#include \n" + "\t#include \n" + "\t#include \n" + "#else\n" + "\tint putchar(int);\n" + "\tint getchar(void);\n" + "\tint printf(char * fmt, ...);\n" + "\tint scanf(char * fmt, ...);\n" + "\ttypedef int int32_t;\n" + "\t#define NULL ((void *) 0)\n" + "#endif\n" + "\n" + "static vector(int32_t) stack;\n" + "static vector(int32_t) heap;\n" + "static vector(int32_t) callstack;\n" + "static int32_t lhs, rhs, tmp, ip;\n" + "\n" + "#define AT(x, y) vector_end(x)[-y]\n" + "#define BILOAD \\\n" + "\trhs = AT(stack, 1); \\\n" + "\tlhs = AT(stack, 2); \\\n" + "\tvector_pop_back(stack); \\\n" + "\tvector_pop_back(stack)\n" + "\n" + "int main(void) {\n" + "\tbranch: switch(ip) {\n" + "case 0:\n" + ); + + vector_foreach(struct instruction_t, ins, program.program) { + switch(ins->type) { + case GETC: + emit( + "\trhs = AT(stack, 1);\n" + "\tvector_pop_back(stack);\n" + "\twhile(vector_size(heap) <= (unsigned) rhs)\n" + "\t\tvector_push_back(heap, 0);\n" + "\theap[rhs] = getchar();\n" + "\tif(heap[rhs] < 0) heap[rhs] = -1;\n" + ); + break; + case PUTC: + emit( + "\tputchar(AT(stack, 1));\n" + "\tvector_pop_back(stack);\n" + ); + break; + case GETN: + emit( + "\trhs = AT(stack, 1);\n" + "\tvector_pop_back(stack);\n" + "\twhile(vector_size(heap) <= (unsigned) rhs)\n" + "\t\tvector_push_back(heap, 0);\n" + "\tscanf(\"%%d\", &tmp);\n" + "\theap[rhs] = tmp;\n" + ); + break; + case PUTN: + emit( + "\tprintf(\"%%d\", AT(stack, 1));\n" + "\tvector_pop_back(stack);\n" + ); + break; + case PSH: + emitf("\tvector_push_back(stack, %d);\n", ins->data); + break; + case DUP: + emit("\tvector_push_back(stack, AT(stack, 1));\n"); + break; + case XCHG: + emit( + "\ttmp = AT(stack, 2);\n" + "\tAT(stack, 2) = AT(stack, 1);\n" + "\tAT(stack, 1) = tmp;\n" + ); + break; + case DROP: + emit("\tvector_pop_back(stack);\n"); + break; + case ADD: + emit("\tBILOAD; vector_push_back(stack, lhs + rhs);\n"); + break; + case SUB: + emit("\tBILOAD; vector_push_back(stack, lhs - rhs);\n"); + break; + case DIV: + emit("\tBILOAD; vector_push_back(stack, lhs / rhs);\n"); + break; + case MUL: + emit("\tBILOAD; vector_push_back(stack, lhs * rhs);\n"); + break; + case MOD: + emit("\tBILOAD; vector_push_back(stack, lhs %% rhs);\n"); + break; + case STOP: + emit("\tgoto end;\n"); + break; + case STO: + emit( + "\tBILOAD;\n" + "\twhile(vector_size(heap) <= (unsigned) lhs)\n" + "\t\tvector_push_back(heap, 0);\n" + "\theap[lhs] = rhs;\n" + ); + break; + case RCL: + emit( + "\tlhs = AT(stack, 1);\n" + "\tvector_pop_back(stack);\n" + "\tif(vector_size(heap) <= (unsigned) lhs)\n" + "\t\tvector_push_back(stack, 0);\n" + "\telse\n" + "\t\tvector_push_back(stack, heap[lhs]);\n" + ); + break; + case LBL: + emitf("case %d:;\n", ins->data); + break; + case JMP: + emitf( + "\tip = %d;\n" + "\tgoto branch;\n" + , ins->data); + break; + case BZ: + emitf( + "\tlhs = AT(stack, 1);\n" + "\tvector_pop_back(stack);\n" + "\tif(lhs == 0) {\n" + "\t\tip = %d;\n" + "\t\tgoto branch;\n" + "\t}\n" + , ins->data); + break; + case BLTZ: + emitf( + "\tlhs = AT(stack, 1);\n" + "\tvector_pop_back(stack);\n" + "\tif(lhs < 0) {\n" + "\t\tip = %d;\n" + "\t\tgoto branch;\n" + "\t}\n" + , ins->data); + break; + case CALL: + emitf( + "\tvector_push_back(callstack, %d);\n" + "\tip = %d;\n" + "\tgoto branch;\n" + "case %d:\n" + , callid + 1, ins->data, callid + 1); + callid++; + break; + case RET: + emit( + "\tip = AT(callstack, 1);\n" + "\tvector_pop_back(callstack);\n" + "\tgoto branch;\n" + ); + break; + case COPY: + emitf( + "\tvector_push_back(stack, stack[vector_size(stack) - 1 - %d]);\n" + , ins->data); + break; + case SLIDE: + emitf( + "\tfor(rhs = 0; rhs < %d; rhs++)\n" + "\t\tvector_erase(stack, vector_size(stack) - 1);\n" + , ins->data); + break; + } + } + + emit( + "\t}\n" + "\tend:\n" + "\tvector_free(stack);\n" + "\tvector_free(heap);\n" + "\tvector_free(callstack);\n" + "}\n" + ); + + return code; +} + + +#ifndef _MAP_H_ +#define _MAP_H_ + +#define hashmap_str_lit(str) (str), sizeof(str) - 1 +#define hashmap_static_arr(arr) (arr), sizeof(arr) + +#include +#include +#include + +#define HASHMAP_HASH_INIT 2166136261u + +#ifdef DIRAC_64 +static uint32_t hash_data(const unsigned char* data, size_t size) +{ + size_t nblocks = size / 8; + uint64_t hash = HASHMAP_HASH_INIT, last; + size_t i; + for (i = 0; i < nblocks; ++i) + { + hash ^= (uint64_t)data[0] << 0 | (uint64_t)data[1] << 8 | + (uint64_t)data[2] << 16 | (uint64_t)data[3] << 24 | + (uint64_t)data[4] << 32 | (uint64_t)data[5] << 40 | + (uint64_t)data[6] << 48 | (uint64_t)data[7] << 56; + hash *= 0xbf58476d1ce4e5b9; + data += 8; + } + + last = size & 0xff; + switch (size % 8) + { + case 7: + last |= (uint64_t)data[6] << 56; /* fallthrough */ + case 6: + last |= (uint64_t)data[5] << 48; /* fallthrough */ + case 5: + last |= (uint64_t)data[4] << 40; /* fallthrough */ + case 4: + last |= (uint64_t)data[3] << 32; /* fallthrough */ + case 3: + last |= (uint64_t)data[2] << 24; /* fallthrough */ + case 2: + last |= (uint64_t)data[1] << 16; /* fallthrough */ + case 1: + last |= (uint64_t)data[0] << 8; + hash ^= last; + hash *= 0xd6e8feb86659fd93; + } + + /* compress to a 32-bit result. also serves as a finalizer. */ + return hash ^ hash >> 32; +} +#else +#ifdef DIRAC_32 +static uint32_t hash_data(const unsigned char* data, size_t size) { + int i, j; + unsigned int byte, crc, mask; + + i = 0; + crc = 0xFFFFFFFF; + while (i < size) { + byte = data[i]; + crc = crc ^ byte; + for (j = 7; j >= 0; j--) { + mask = -(crc & 1); + crc = (crc >> 1) ^ (0xEDB88320 & mask); + } + i = i + 1; + } + return ~crc; +} +#else +static uint16_t hash_data(const unsigned char* data, size_t size) { + unsigned char x; + unsigned short crc = 0xFFFF; + + while (size--){ + x = crc >> 8 ^ *data++; + x ^= x>>4; + crc = (crc << 8) ^ ((unsigned short)(x << 12)) ^ ((unsigned short)(x <<5)) ^ ((unsigned short)x); + } + return crc; +} +#endif +#endif + +/* hashmaps can associate keys with pointer values or integral types. */ +typedef struct hashmap hashmap; + +/* a callback type used for iterating over a map/freeing entries: + * `void (void* key, size_t size, uintptr_t value, void* usr)` + * `usr` is a user pointer which can be passed through `hashmap_iterate`. + */ +typedef void (*hashmap_callback)(void *key, size_t ksize, uintptr_t value, void *usr); + +static hashmap* hashmap_create(void); + +/* only frees the hashmap object and buckets. + * does not call free on each element's `key` or `value`. + * to free data associated with an element, call `hashmap_iterate`. + */ +static void hashmap_free(hashmap* map); + +/* does not make a copy of `key`. + * you must copy it yourself if you want to guarantee its lifetime, + * or if you intend to call `hashmap_key_free`. + */ +static void hashmap_set(hashmap* map, void* key, size_t ksize, uintptr_t value); + +/* adds an entry if it doesn't exist, using the value of `*out_in`. + * if it does exist, it sets value in `*out_in`, meaning the value + * of the entry will be in `*out_in` regardless of whether or not + * it existed in the first place. + * returns true if the entry already existed, returns false otherwise. + */ +static int hashmap_get_set(hashmap* map, void* key, size_t ksize, uintptr_t* out_in); + +/* similar to `hashmap_set()`, but when overwriting an entry, + * you'll be able properly free the old entry's data via a callback. + * unlike `hashmap_set()`, this function will overwrite the original key pointer, + * which means you can free the old key in the callback if applicable. + */ +static void hashmap_set_free(hashmap* map, void* key, size_t ksize, uintptr_t value, hashmap_callback c, void* usr); + +static int hashmap_get(hashmap* map, void* key, size_t ksize, uintptr_t* out_val); + +static int hashmap_size(hashmap* map); + +/* iterate over the map, calling `c` on every element. + * goes through elements in the order they were added. + * the element's key, key size, value, and `usr` will be passed to `c`. + */ +static void hashmap_iterate(hashmap* map, hashmap_callback c, void* usr); + +#define HASHMAP_DEFAULT_CAPACITY 5 +#define HASHMAP_MAX_LOAD 0.75f +#define HASHMAP_RESIZE_FACTOR 2 + +struct bucket +{ + /* `next` must be the first struct element. + * changing the order will break multiple functions */ + struct bucket* next; + + /* key, key size, key hash, and associated value */ + void* key; + size_t ksize; + uint32_t hash; + uintptr_t value; +}; + +struct hashmap +{ + struct bucket* buckets; + int capacity; + int count; + + /* a linked list of all valid entries, in order */ + struct bucket* first; + /* lets us know where to add the next element */ + struct bucket* last; +}; + +static hashmap* hashmap_create(void) +{ + hashmap* m = malloc(sizeof(hashmap)); + m->capacity = HASHMAP_DEFAULT_CAPACITY; + m->count = 0; + + m->buckets = calloc(HASHMAP_DEFAULT_CAPACITY, sizeof(struct bucket)); + m->first = NULL; + + /* this prevents branching in hashmap_set. + * m->first will be treated as the "next" pointer in an imaginary bucket. + * when the first item is added, m->first will be set to the correct address. + */ + m->last = (struct bucket*)&m->first; + return m; +} + +static void hashmap_free(hashmap* m) +{ + free(m->buckets); + free(m); +} + +/* puts an old bucket into a resized hashmap */ +static struct bucket* resize_entry(hashmap* m, struct bucket* old_entry) +{ + uint32_t index = old_entry->hash % m->capacity; + for (;;) + { + struct bucket* entry = &m->buckets[index]; + + if (entry->key == NULL) + { + *entry = *old_entry; + return entry; + } + + index = (index + 1) % m->capacity; + } +} + +static void hashmap_resize(hashmap* m) +{ + struct bucket* old_buckets = m->buckets; + + m->capacity *= HASHMAP_RESIZE_FACTOR; + m->buckets = calloc(m->capacity, sizeof(struct bucket)); + m->last = (struct bucket*)&m->first; + + do + { + m->last->next = resize_entry(m, m->last->next); + m->last = m->last->next; + } while (m->last->next != NULL); + + free(old_buckets); +} + +static struct bucket* find_entry(hashmap* m, void* key, size_t ksize, uint32_t hash) +{ + uint32_t index = hash % m->capacity; + + for (;;) + { + struct bucket* entry = &m->buckets[index]; + + /* kind of a thicc condition; */ + /* I didn't want this to span multiple if statements or functions. */ + if (entry->key == NULL || + /* compare sizes, then hashes, then key data as a last resort. */ + (entry->ksize == ksize && + entry->hash == hash && + memcmp(entry->key, key, ksize) == 0)) + { + /* return the entry if a match or an empty bucket is found */ + return entry; + } + + index = (index + 1) % m->capacity; + } +} + +static void hashmap_set(hashmap* m, void* key, size_t ksize, uintptr_t val) +{ + uint32_t hash; + struct bucket * entry; + + if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity) + hashmap_resize(m); + + hash = hash_data(key, ksize); + entry = find_entry(m, key, ksize, hash); + if (entry->key == NULL) + { + m->last->next = entry; + m->last = entry; + entry->next = NULL; + + ++m->count; + + entry->key = key; + entry->ksize = ksize; + entry->hash = hash; + } + entry->value = val; +} + +static int hashmap_get_set(hashmap* m, void* key, size_t ksize, uintptr_t* out_in) +{ + uint32_t hash; + struct bucket * entry; + + if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity) + hashmap_resize(m); + + hash = hash_data(key, ksize); + entry = find_entry(m, key, ksize, hash); + if (entry->key == NULL) + { + m->last->next = entry; + m->last = entry; + entry->next = NULL; + + ++m->count; + + entry->value = *out_in; + entry->key = key; + entry->ksize = ksize; + entry->hash = hash; + + return 0; + } + *out_in = entry->value; + return 1; +} + +static void hashmap_set_free(hashmap* m, void* key, size_t ksize, uintptr_t val, hashmap_callback c, void* usr) +{ + uint32_t hash; + struct bucket * entry; + + if (m->count + 1 > HASHMAP_MAX_LOAD * m->capacity) + hashmap_resize(m); + + hash = hash_data(key, ksize); + entry = find_entry(m, key, ksize, hash); + if (entry->key == NULL) + { + m->last->next = entry; + m->last = entry; + entry->next = NULL; + + ++m->count; + + entry->key = key; + entry->ksize = ksize; + entry->hash = hash; + entry->value = val; + return; + } + /* allow the callback to free entry data. + * use old key and value so the callback can free them. + * the old key and value will be overwritten after this call. */ + c(entry->key, ksize, entry->value, usr); + + /* overwrite the old key pointer in case the callback frees it. */ + entry->key = key; + entry->value = val; +} + +static int hashmap_get(hashmap* m, void* key, size_t ksize, uintptr_t* out_val) +{ + uint32_t hash = hash_data(key, ksize); + struct bucket* entry = find_entry(m, key, ksize, hash); + + /* if there is no match, output val will just be NULL */ + *out_val = entry->value; + + return entry->key != NULL; +} + +static int hashmap_size(hashmap* m) +{ + return m->count; +} + +static void hashmap_iterate(hashmap* m, hashmap_callback c, void* user_ptr) +{ + /* loop through the linked list of valid entries + * this way we can skip over empty buckets */ + struct bucket* current = m->first; + + int co = 0; + + while (current != NULL) + { + c(current->key, current->ksize, current->value, user_ptr); + + current = current->next; + + if (co > 1000) + { + break; + } + co++; + + } +} + +#endif + +/* + Copyright (C) 2006-2016,2018 Con Kolivas + Copyright (C) 2011, 2022 Peter Hyman + Copyright (C) 1998-2003 Andrew Tridgell + Copyright (C) 2022 Kamila Szewczyk + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../include/config.h" +#include "../include/runzip.h" +#include "../include/rzip.h" +#include "../include/stream.h" +#include "../include/util.h" + +#define MAGIC_LEN (20) // new v 0.9 magic header +#define MAGIC_V8_LEN (18) // new v 0.8 magic header +#define OLD_MAGIC_LEN (24) // Just to read older versions +#define MAGIC_HEADER (6) // to validate file initially + +static void release_hashes(rzip_control * control); + +static i64 fdout_seekto(rzip_control * control, i64 pos) { + if (TMP_OUTBUF) { + pos -= control->out_relofs; + control->out_ofs = pos; + if (unlikely(pos > control->out_len || pos < 0)) { + print_err("Trying to seek to %'" PRId64 " outside tmp outbuf in fdout_seekto\n", pos); + return -1; + } + + return 0; + } + return lseek(control->fd_out, pos, SEEK_SET); +} + +i64 get_ram(rzip_control * control) { +#ifdef __APPLE__ + #include + int mib[2]; + size_t len; + i64 ramsize; + + mib[0] = CTL_HW; + mib[1] = HW_MEMSIZE; + sysctl(mib, 2, &ramsize, &len, NULL, 0); +#elif defined(__OpenBSD__) + #include + struct rlimit rl; + i64 ramsize = (i64)sysconf(_SC_PHYS_PAGES) * PAGE_SIZE; + + /* Raise limits all the way to the max */ + + if (getrlimit(RLIMIT_DATA, &rl) == -1) fatal(("Failed to get limits in get_ram\n"), -1); + + rl.rlim_cur = rl.rlim_max; + if (setrlimit(RLIMIT_DATA, &rl) == -1) fatal(("Failed to set limits in get_ram\n"), -1); + + /* Declare detected RAM to be either the max RAM available from + physical memory or the max RAM allowed by RLIMIT_DATA, whatever + is smaller, to prevent the heuristics from selecting + compression windows which cause mrzip to go into deep swap */ + + if (rl.rlim_max < ramsize) return rl.rlim_max; + + return ramsize; +#else /* __APPLE__ or __Open_BSD__ */ + i64 ramsize; + FILE * meminfo; + char aux[256]; + + ramsize = (i64)sysconf(_SC_PHYS_PAGES) * PAGE_SIZE; + if (ramsize <= 0) { + /* Workaround for uclibc which doesn't properly support sysconf */ + if (!(meminfo = fopen("/proc/meminfo", "r"))) fatal("Failed to open /proc/meminfo\n"); + + while (!feof(meminfo) && !fscanf(meminfo, "MemTotal: %" PRId64 " kB", &ramsize)) { + if (unlikely(fgets(aux, sizeof(aux), meminfo) == NULL)) { + fclose(meminfo); + fatal("Failed to fgets in get_ram\n"); + } + } + if (fclose(meminfo) == -1) fatal("Failed to close /proc/meminfo\n"); + ramsize *= 1024; + } +#endif + if (ramsize <= 0) fatal("No memory or can't determine ram? Can't continue.\n"); + return ramsize; +} + +i64 nloops(i64 seconds, uchar * b1, uchar * b2) { + i64 nloops; + int nbits; + + nloops = ARBITRARY_AT_EPOCH * pow(MOORE_TIMES_PER_SECOND, seconds); + if (nloops < ARBITRARY) nloops = ARBITRARY; + for (nbits = 0; nloops > 255; nbits++) nloops = nloops >> 1; + *b1 = nbits; + *b2 = nloops; + return nloops << nbits; +} + +bool write_magic(rzip_control * control) { + unsigned char magic[MAGIC_LEN] = { 'M', 'R', 'Z', 'I', MRZIP_MAJOR, MRZIP_MINOR }; + + /* In encrypted files, the size is left unknown + * and instead the salt is stored here to preserve space. */ + // FIXME. I think we can do better. 8 bytes is no reason to save space + if (ENCRYPT) { + memcpy(&magic[6], &control->salt, 8); + magic[15] = control->enc_code; /* write whatever encryption code */ + } else if (control->eof) { + i64 esize = htole64(control->st_size); // we know file size even when piped + memcpy(&magic[6], &esize, 8); + } + /* This is a flag that the archive contains an hash sum at the end + * which can be used as an integrity check instead of crc check. + * crc is still stored for compatibility with 0.5 versions. + */ + if (HAS_HASH) magic[14] = control->hash_code; /* write whatever hash */ + + magic[16] = 0; + + /* save LZMA dictionary size */ + if (ZPAQ_COMPRESS) { + /* Save zpaq compression level and block size as one byte */ + /* High order bits = 128 + (16 * Compression Level 3-5) + * Low order bits = Block Size 1-11 + * 128 necessary to distinguish in decoding LZMA which is 1-40 + * 1CCC BBBB in binary */ + magic[17] = 0b10000000 + (control->zpaq_level << 4) + control->zpaq_bs; + /* Decoding would be + * magic byte & 127 (clear high bit) + * zpaq_bs = magic byte & 0X0F + * zpaq_level = magic_byte >> 4 + */ + } else if (BZIP3_COMPRESS) { + /* Save block size. ZPAQ compression level is from 3 to 5, so this is sound. + bzip3 blocksize is from 1 to 8 (or 0 to 7). */ + magic[17] = 0b11110000 + bzip3_prop_from_block_size(control->bzip3_block_size); + } + + /* save compression levels + * high order bits, rzip compression level + * low order bits mrzip compression level + */ + magic[18] = (control->rzip_compression_level << 4) + control->compression_level; + + /* store comment length */ + magic[19] = (char)control->comment_length; + + if (unlikely(fdout_seekto(control, 0))) fatal("Failed to seek to BOF to write Magic Header\n"); + + if (unlikely(put_fdout(control, magic, MAGIC_LEN) != MAGIC_LEN)) fatal("Failed to write magic header\n"); + + /* now write comment if any */ + if (magic[19]) { + if (unlikely(put_fdout(control, control->comment, control->comment_length) != control->comment_length)) + fatal("Failed to write comment after magic header\n"); + } + + control->magic_written = 1; + return true; +} + +static inline i64 enc_loops(uchar b1, uchar b2) { return (i64)b2 << (i64)b1; } + +// check for comments +// Called only if comment length > 0 + +static void get_comment(rzip_control * control, int fd_in, unsigned char * magic) { + if (unlikely(!(control->comment = malloc(magic[19] + 1)))) fatal("Failed to allocate memory for comment\n"); + /* read comment */ + if (unlikely(read(fd_in, control->comment, magic[19]) != magic[19])) fatal("Failed to read comment\n"); + + control->comment_length = magic[19]; + control->comment[control->comment_length] = '\0'; + return; +} + +// retriev lzma properties + +static void get_hash_from_magic(rzip_control * control, unsigned char * magic) { + /* Whether this archive contains hash data at the end or not */ + if (*magic > 0 && *magic <= MAXHASH) { + control->flags |= FLAG_HASHED; + control->hash_code = *magic; /* set hash code */ + control->hash_label = &hashes[control->hash_code].label[0]; + control->hash_gcode = &hashes[control->hash_code].gcode; + control->hash_len = &hashes[control->hash_code].length; + } else + print_verbose("Unknown hash, falling back to CRC\n"); + + return; +} + +// get encrypted salt + +static void get_encryption(rzip_control * control, unsigned char * magic, unsigned char * salt) { + if (*magic > 0 && *magic <= MAXENC) { + control->flags |= FLAG_ENCRYPT; + control->enc_code = *magic; + /* In encrypted files, the size field is used to store the salt + * instead and the size is unknown, just like a STDOUT chunked + * file */ + memcpy(&control->salt, salt, 8); + control->st_size = 0; + control->encloops = enc_loops(control->salt[0], control->salt[1]); + } else if (ENCRYPT) { + print_err("Asked to decrypt a non-encrypted archive. Bypassing decryption. May fail!\n"); + control->flags &= ~FLAG_ENCRYPT; + control->enc_code = 0; + } + control->enc_label = &encryptions[control->enc_code].label[0]; + control->enc_gcode = &encryptions[control->enc_code].gcode; + control->enc_keylen = &encryptions[control->enc_code].keylen; + control->enc_ivlen = &encryptions[control->enc_code].ivlen; + + return; +} + +// expected size + +static void get_expected_size(rzip_control * control, unsigned char * magic) { + i64 expected_size; + + memcpy(&expected_size, &magic[6], 8); + control->st_size = le64toh(expected_size); + + return; +} + +// new mrzip v8 magic header format. + +static void get_magic_v8(rzip_control * control, unsigned char * magic) { + int i; + + if (!magic[15]) // not encrypted + get_expected_size(control, magic); + get_encryption(control, &magic[15], &magic[6]); + + if ((magic[17] & 0b10000000)) // bzip3 or zpaq block sizes/levels stored + { + if ((magic[17] & 0b11110000) == 0b11110000) { // bzip3 block size + control->bzip3_bs = magic[17] & 0b00001111; // bzip3 block size code 0 to 8 + control->bzip3_block_size = BZIP3_BLOCK_SIZE_FROM_PROP(control->bzip3_bs); // Real Block Size + } else // zpaq block and compression level stored + { + control->zpaq_bs = magic[17] & 0b00001111; // low order bits are block size + magic[17] &= 0b01110000; // strip high bit + control->zpaq_level = magic[17] >> 4; // divide by 16 + } + } + + get_hash_from_magic(control, &magic[14]); + + return; +} + +// new mrzip v9 magic header format. + +static void get_magic_v9(rzip_control * control, int fd_in, unsigned char * magic) { + /* get compression levels + * rzip level is high order bits + * mrzip level is low order bits + */ + control->compression_level = magic[18] & 0b00001111; + control->rzip_compression_level = magic[18] >> 4; + + if (magic[19]) /* get comment if there is one */ + get_comment(control, fd_in, magic); + + return; +} +static bool get_magic(rzip_control * control, int fd_in, unsigned char * magic) { + memcpy(&control->major_version, &magic[4], 1); + memcpy(&control->minor_version, &magic[5], 1); + + /* zero out compression levels so info does not show for earlier versions */ + control->rzip_compression_level = control->compression_level = 0; + /* remove checks for mrzip < 0.6 */ + if (control->major_version == 0) { + switch (control->minor_version) { + case 9: /* version 0.9 adds two bytes */ + get_magic_v8(control, magic); + get_magic_v9(control, fd_in, magic); + break; + default: + print_err("mrzip version %d.%d archive is not supported. Aborting\n", control->major_version, + control->minor_version); + return false; + } + } + + return true; +} + +static bool read_magic(rzip_control * control, int fd_in, i64 * expected_size) { + unsigned char magic[OLD_MAGIC_LEN]; // Make at least big enough for old magic + int bytes_to_read; // simplify reading of magic + + memset(magic, 0, sizeof(magic)); + /* Initially read only file type and version */ + if (unlikely(read(fd_in, magic, MAGIC_HEADER) != MAGIC_HEADER)) fatal("Failed to read initial magic header\n"); + + if (unlikely(strncmp(magic, "MRZI", 4))) fatal("Not an mrzip file\n"); + + if (magic[4] == 0) { + if (magic[5] < 8) /* old magic */ + bytes_to_read = OLD_MAGIC_LEN; + else if (magic[5] == 8) /* 0.8 file */ + bytes_to_read = MAGIC_V8_LEN; + else /* ASSUME current version */ + bytes_to_read = MAGIC_LEN; + + if (unlikely(read(fd_in, &magic[6], bytes_to_read - MAGIC_HEADER) != bytes_to_read - MAGIC_HEADER)) + fatal("Failed to read magic header\n"); + } + + if (unlikely(!get_magic(control, fd_in, magic))) return false; + *expected_size = control->st_size; + return true; +} + +/* show mrzip version + * helps preserve output format when validating + */ +static void show_version(rzip_control * control) { + print_verbose("Detected mrzip version %'d.%'d file.\n", control->major_version, control->minor_version); +} + +/* preserve ownership and permissions where possible */ +static bool preserve_perms(rzip_control * control, int fd_in, int fd_out) { + struct stat st; + + if (unlikely(fstat(fd_in, &st))) fatal("Failed to fstat input file\n"); + if (unlikely(fchmod(fd_out, (st.st_mode & 0666)))) + print_verbose("Warning, unable to set permissions on %s\n", control->outfile); + + /* chown fail is not fatal_return(( */ + if (unlikely(fchown(fd_out, st.st_uid, st.st_gid))) + print_verbose("Warning, unable to set owner on %s\n", control->outfile); + return true; +} + +static bool preserve_times(rzip_control * control, int fd_in) { + struct utimbuf times; + struct stat st; + + if (unlikely(fstat(fd_in, &st))) fatal("Failed to fstat input file\n"); + times.actime = 0; + times.modtime = st.st_mtime; + if (unlikely(utime(control->outfile, ×))) + print_verbose("Warning, unable to set time on %s\n", control->outfile); + + return true; +} + +/* Open a temporary outputfile to emulate stdout */ +int open_tmpoutfile(rzip_control * control) { + int fd_out; + + if (STDOUT && !TEST_ONLY) print_verbose("Outputting to stdout.\n"); + control->outfile = realloc(NULL, strlen(control->tmpdir) + 16); + if (unlikely(!control->outfile)) fatal("Failed to allocate outfile name\n"); + strcpy(control->outfile, control->tmpdir); + strcat(control->outfile, "mrzipout.XXXXXX"); + + fd_out = mkstemp(control->outfile); + if (fd_out == -1) fatal("Failed to create out tmpfile: %s\n", control->outfile); + + register_outfile(control, control->outfile, TEST_ONLY || STDOUT || !KEEP_BROKEN); + return fd_out; +} + +static bool fwrite_stdout(rzip_control * control, void * buf, i64 len) { + uchar * offset_buf = buf; + ssize_t ret, nmemb; + i64 total; + + total = 0; + while (len > 0) { + nmemb = len; + ret = fwrite(offset_buf, 1, nmemb, control->outFILE); + if (unlikely(ret == -1)) fatal("Failed to fwrite %'" PRId64 " bytes in fwrite_stdout\n", nmemb); + len -= ret; + offset_buf += ret; + total += ret; + } + fflush(control->outFILE); + return true; +} + +bool write_fdout(rzip_control * control, void * buf, i64 len) { + uchar * offset_buf = buf; + ssize_t ret, nmemb; + + while (len > 0) { + nmemb = len; + ret = write(control->fd_out, offset_buf, (size_t)nmemb); + /* error if ret == -1 only. Otherwise, buffer not wholly written */ + if (unlikely(ret == -1)) /* error, not underflow */ + fatal("Failed to write %'" PRId64 " bytes to fd_out in write_fdout\n", nmemb); + len -= ret; + offset_buf += ret; + } + return true; +} + +bool flush_tmpoutbuf(rzip_control * control) { + if (!TEST_ONLY) { + print_maxverbose("Dumping buffer to physical file.\n"); + if (STDOUT) { + if (unlikely(!fwrite_stdout(control, control->tmp_outbuf, control->out_len))) return false; + } else { + if (unlikely(!write_fdout(control, control->tmp_outbuf, control->out_len))) return false; + } + } + control->out_relofs += control->out_len; + control->out_ofs = control->out_len = 0; + return true; +} + +/* Dump temporary outputfile to perform stdout */ +bool dump_tmpoutfile(rzip_control * control, int fd_out) { + FILE * tmpoutfp; + int tmpchar; + + if (unlikely(fd_out == -1)) fatal("Failed: No temporary outfile created, unable to do in ram\n"); + /* flush anything not yet in the temporary file */ + fsync(fd_out); + tmpoutfp = fdopen(fd_out, "r"); + if (unlikely(tmpoutfp == NULL)) fatal("Failed to fdopen out tmpfile\n"); + rewind(tmpoutfp); + + if (!TEST_ONLY) { + print_verbose("Dumping temporary file to control->outFILE.\n"); + while ((tmpchar = fgetc(tmpoutfp)) != EOF) putchar(tmpchar); + fflush(control->outFILE); + rewind(tmpoutfp); + } + + if (unlikely(ftruncate(fd_out, 0))) fatal("Failed to ftruncate fd_out in dump_tmpoutfile\n"); + return true; +} + +/* Used if we're unable to read STDIN into the temporary buffer, shunts data + * to temporary file */ +bool write_fdin(rzip_control * control) { + uchar * offset_buf = control->tmp_inbuf; + i64 len = control->in_len; + ssize_t ret; + + while (len > 0) { + ret = len; + ret = write(control->fd_in, offset_buf, (size_t)ret); + if (unlikely(ret == -1)) fatal("Failed to write to fd_in in write_fdin\n"); + len -= ret; + offset_buf += ret; + } + return true; +} + +/* Open a temporary inputfile to perform stdin decompression */ +int open_tmpinfile(rzip_control * control) { + int fd_in = -1; + + /* Use temporary directory if there is one. /tmp is default */ + control->infile = malloc(strlen(control->tmpdir) + 15); + if (unlikely(!control->infile)) fatal("Failed to allocate infile name\n"); + strcpy(control->infile, control->tmpdir); + strcat(control->infile, "mrzipin.XXXXXX"); + fd_in = mkstemp(control->infile); + + if (fd_in == -1) fatal("Failed to create in tmpfile: %s\n", control->infile); + + register_infile(control, control->infile, (DECOMPRESS || TEST_ONLY) && STDIN); + /* Unlink temporary file immediately to minimise chance of files left + * lying around */ + if (unlikely(unlink(control->infile))) { + close(fd_in); + fatal("Failed to unlink tmpfile: %s\n", control->infile); + } + return fd_in; +} + +static bool read_tmpinmagic(rzip_control * control, int fd_in) { + /* just in case < 0.8 file */ + char magic[OLD_MAGIC_LEN]; + int bytes_to_read, i, tmpchar; + + memset(magic, 0, sizeof(magic)); + /* Initially read only file type and version */ + for (i = 0; i < MAGIC_HEADER; i++) { + tmpchar = getchar(); + if (unlikely(tmpchar == EOF)) fatal("Reached end of file on STDIN prematurely on magic read\n"); + magic[i] = (char)tmpchar; + } + + if (unlikely(strncmp(magic, "MRZI", 4))) fatal("Not an mrzip stream\n"); + + if (magic[4] == 0) { + if (magic[5] < 8) /* old magic */ + bytes_to_read = OLD_MAGIC_LEN; + else if (magic[5] == 8) /* 0.8 file */ + bytes_to_read = MAGIC_V8_LEN; + else /* ASSUME current version */ + bytes_to_read = MAGIC_LEN; + + for (; i < bytes_to_read; i++) { + tmpchar = getchar(); + if (unlikely(tmpchar == EOF)) fatal("Reached end of file on STDIN prematurely on magic read\n"); + magic[i] = (char)tmpchar; + } + } + + return get_magic(control, fd_in, magic); +} + +/* Read data from stdin into temporary inputfile */ +bool read_tmpinfile(rzip_control * control, int fd_in) { + FILE * tmpinfp; + int tmpchar; + + if (fd_in == -1) return false; + if (control->flags & FLAG_SHOW_PROGRESS) fprintf(control->msgout, "Copying from stdin.\n"); + tmpinfp = fdopen(fd_in, "w+"); + if (unlikely(tmpinfp == NULL)) fatal("Failed to fdopen in tmpfile\n"); + + while ((tmpchar = getchar()) != EOF) fputc(tmpchar, tmpinfp); + + fflush(tmpinfp); + rewind(tmpinfp); + return true; +} + +/* To perform STDOUT, we allocate a proportion of ram that is then used as + * a pseudo-temporary file */ +static bool open_tmpoutbuf(rzip_control * control) { + i64 maxlen = control->maxram; + void * buf; + + while (42) { + round_to_page(&maxlen); + buf = malloc(maxlen); + if (buf) { + print_maxverbose("Malloced %'" PRId64 " for tmp_outbuf\n", maxlen); + break; + } + maxlen = maxlen / 3 * 2; + if (maxlen < 100000000) fatal("Unable to even malloc 100MB for tmp_outbuf\n"); + } + control->flags |= FLAG_TMP_OUTBUF; + /* Allocate slightly more so we can cope when the buffer overflows and + * fall back to a real temporary file */ + control->out_maxlen = maxlen + control->page_size; + control->tmp_outbuf = buf; + if (!DECOMPRESS && !TEST_ONLY) control->out_ofs = control->out_len = MAGIC_LEN + control->comment_length; + return true; +} + +/* We've decided to use a temporary output file instead of trying to store + * all the output buffer in ram so we can free up the ram and increase the + * maximum sizes of ram we can allocate */ +void close_tmpoutbuf(rzip_control * control) { + control->flags &= ~FLAG_TMP_OUTBUF; + dealloc(control->tmp_outbuf); + control->usable_ram = control->maxram += control->ramsize / 18; +} + +static bool open_tmpinbuf(rzip_control * control) { + control->flags |= FLAG_TMP_INBUF; + control->in_maxlen = control->maxram; + control->tmp_inbuf = malloc(control->maxram + control->page_size); + if (unlikely(!control->tmp_inbuf)) fatal("Failed to malloc tmp_inbuf in open_tmpinbuf\n"); + return true; +} + +void clear_tmpinbuf(rzip_control * control) { control->in_len = control->in_ofs = 0; } + +bool clear_tmpinfile(rzip_control * control) { + if (unlikely(lseek(control->fd_in, 0, SEEK_SET))) fatal("Failed to lseek on fd_in in clear_tmpinfile\n"); + if (unlikely(ftruncate(control->fd_in, 0))) fatal("Failed to truncate fd_in in clear_tmpinfile\n"); + return true; +} + +/* As per temporary output file but for input file */ +void close_tmpinbuf(rzip_control * control) { + control->flags &= ~FLAG_TMP_INBUF; + dealloc(control->tmp_inbuf); + control->usable_ram = control->maxram += control->ramsize / 18; +} + +static int get_pass(rzip_control * control, char * s) { + int len; + + memset(s, 0, PASS_LEN - SALT_LEN); + if (control->passphrase) + strncpy(s, control->passphrase, PASS_LEN - SALT_LEN - 1); + else if (unlikely(fgets(s, PASS_LEN - SALT_LEN, stdin) == NULL)) + fatal("Failed to retrieve passphrase\n"); + len = strlen(s); + if (len > 0 && ('\r' == s[len - 1] || '\n' == s[len - 1])) s[len - 1] = '\0'; + if (len > 1 && ('\r' == s[len - 2] || '\n' == s[len - 2])) s[len - 2] = '\0'; + len = strlen(s); + if (unlikely(0 == len)) fatal("Empty passphrase\n"); + return len; +} + +static bool get_hash(rzip_control * control, int make_hash) { + char *passphrase, *testphrase; + struct termios termios_p; + int prompt = control->passphrase == NULL; + + passphrase = calloc(PASS_LEN, 1); + testphrase = calloc(PASS_LEN, 1); + control->salt_pass = calloc(PASS_LEN, 1); + control->hash = calloc(HASH_LEN, 1); + if (unlikely(!passphrase || !testphrase || !control->salt_pass || !control->hash)) { + dealloc(testphrase); + dealloc(passphrase); + dealloc(control->salt_pass); + dealloc(control->hash); + fatal("Failed to calloc encrypt buffers in get_hash\n"); + } + mlock(passphrase, PASS_LEN); + mlock(testphrase, PASS_LEN); + mlock(control->salt_pass, PASS_LEN); + mlock(control->hash, HASH_LEN); + + /* mrzip library callback code removed */ + /* Disable stdin echo to screen */ + tcgetattr(fileno(stdin), &termios_p); + termios_p.c_lflag &= ~ECHO; + tcsetattr(fileno(stdin), 0, &termios_p); +retry_pass: + if (prompt) print_output("Enter passphrase: "); + control->salt_pass_len = get_pass(control, passphrase) + SALT_LEN; + if (prompt) print_output("\n"); + if (make_hash) { + if (prompt) print_output("Re-enter passphrase: "); + get_pass(control, testphrase); + if (prompt) print_output("\n"); + if (strcmp(passphrase, testphrase)) { + print_output("Passwords do not match. Try again.\n"); + goto retry_pass; + } + } + termios_p.c_lflag |= ECHO; + tcsetattr(fileno(stdin), 0, &termios_p); + memset(testphrase, 0, PASS_LEN); + memcpy(control->salt_pass, control->salt, SALT_LEN); + memcpy(control->salt_pass + SALT_LEN, passphrase, PASS_LEN - SALT_LEN); + lrz_stretch(control); + memset(passphrase, 0, PASS_LEN); + munlock(passphrase, PASS_LEN); + munlock(testphrase, PASS_LEN); + dealloc(testphrase); + dealloc(passphrase); + return true; +} + +static void release_hashes(rzip_control * control) { + memset(control->salt_pass, 0, PASS_LEN); + memset(control->hash, 0, HASH_LEN); + munlock(control->salt_pass, PASS_LEN); + munlock(control->hash, HASH_LEN); + dealloc(control->salt_pass); + dealloc(control->hash); +} + +bool get_header_info(rzip_control * control, int fd_in, uchar * ctype, i64 * c_len, i64 * u_len, i64 * last_head, + int chunk_bytes) { + uchar enc_head[25 + SALT_LEN]; + if (ENCRYPT) { + // read in salt + // first 8 bytes, instead of chunk bytes and size + if (unlikely(read(fd_in, enc_head, SALT_LEN) != SALT_LEN)) + fatal("Failed to read encrypted header in get_header_info\n"); + } + if (unlikely(read(fd_in, ctype, 1) != 1)) fatal("Failed to read in get_header_info\n"); + + *c_len = *u_len = *last_head = 0; + /* remove checks for mrzip < 0.6 */ + if (control->major_version == 0) { + // header the same after v 0.4 except for chunk bytes + int read_len; + + read_len = chunk_bytes; + if (unlikely(read(fd_in, c_len, read_len) != read_len)) fatal("Failed to read in get_header_info\n"); + if (unlikely(read(fd_in, u_len, read_len) != read_len)) fatal("Failed to read in get_header_info\n"); + if (unlikely(read(fd_in, last_head, read_len) != read_len)) fatal("Failed to read_i64 in get_header_info\n"); + *c_len = le64toh(*c_len); + *u_len = le64toh(*u_len); + *last_head = le64toh(*last_head); + if (ENCRYPT) { + // decrypt header suppressing printing max verbose message + if (unlikely(!decrypt_header(control, enc_head, ctype, c_len, u_len, last_head, LRZ_VALIDATE))) + fatal("Failed to decrypt header in get_header_info\n"); + } + } // control->major_version + return true; +} + +static double percentage(i64 num, i64 den) { + double d_num, d_den; + + if (den < 100) { + d_num = num * 100; + d_den = den; + if (!d_den) d_den = 1; + } else { + d_num = num; + d_den = den / 100; + } + return d_num / d_den; +} + +// If Decompressing or Testing, omit printing, just read file and see if valid +// using construct if (INFO) +// Encrypted files cannot be checked now +bool get_fileinfo(rzip_control * control) { + i64 u_len, c_len, second_last, last_head, utotal = 0, ctotal = 0, ofs, stream_head[2]; + i64 expected_size, infile_size, chunk_size = 0, chunk_total = 0; + int header_length = 0, stream = 0, chunk = 0; + char *tmp, *infilecopy = NULL; + char chunk_byte = 0; + long double cratio, bpb; + uchar ctype = 0; + uchar save_ctype = 255; + struct stat st; + int fd_in; + int lzma_ret; + + // Take out all STDIN checks + struct stat fdin_stat; + + if (unlikely(stat(control->infile, &fdin_stat))) + fatal("File %s not found...\n", control->infile); + else if (unlikely(!S_ISREG(fdin_stat.st_mode))) + fatal("File %s us not a regular file. mrzip cannot continue...\n", control->infile); + else + infilecopy = strdupa(control->infile); + + fd_in = open(infilecopy, O_RDONLY); + if (unlikely(fd_in == -1)) fatal("Failed to open %s\n", infilecopy); + + /* Get file size */ + if (unlikely(fstat(fd_in, &st))) fatal("bad magic file descriptor!?\n"); + infile_size = st.st_size; + + /* Get decompressed size */ + if (unlikely(!read_magic(control, fd_in, &expected_size))) goto error; + + if (INFO) show_version(control); // show version if not validating + + if (ENCRYPT) { + /* can only show info for current mrzip files */ + if (control->major_version == 0) { + if (!control->salt_pass_len) // Only get password if needed + if (unlikely(!get_hash(control, 0))) return false; + } + } + + /* remove checks for mrzip < 0.6 */ + if (control->major_version == 0) { + if (unlikely(read(fd_in, &chunk_byte, 1) != 1)) fatal("Failed to read chunk_byte in get_fileinfo\n"); + if (unlikely(chunk_byte < 1 || chunk_byte > 8)) fatal("Invalid chunk bytes %'d\n", chunk_byte); + if (unlikely(read(fd_in, &control->eof, 1) != 1)) fatal("Failed to read eof in get_fileinfo\n"); + if (!ENCRYPT) { + if (unlikely(read(fd_in, &chunk_size, chunk_byte) != chunk_byte)) + fatal("Failed to read chunk_size in get_fileinfo\n"); + chunk_size = le64toh(chunk_size); + if (unlikely(chunk_size < 0)) fatal("Invalid chunk size %'" PRId64 "\n", chunk_size); + /* set header offsets for earlier versions */ + switch (control->minor_version) { + case 9: + ofs = 22 + control->comment_length; /* comment? Add length */ + break; + } + ofs += chunk_byte; + /* header length is the same for non-encrypted files */ + header_length = 1 + (chunk_byte * 3); + } else { /* ENCRYPTED */ + chunk_byte = 8; // chunk byte size is always 8 for encrypted files + chunk_size = 0; // chunk size is unknown with encrypted files + header_length = 33; // 25 + 8 + // salt is first 8 bytes + if (control->major_version == 0) { + switch (control->minor_version) { + case 9: + ofs = 22 + control->comment_length; + break; + default: + fatal("Cannot decrypt earlier versions of mrzip\n"); + break; + } + } + } + } + +next_chunk: + stream = 0; + stream_head[0] = 0; + stream_head[1] = stream_head[0] + header_length; + + if (!ENCRYPT) { + chunk_total += chunk_size; + if (unlikely(chunk_byte && (chunk_byte > 8 || chunk_size <= 0))) fatal("Invalid chunk data\n"); + } + + if (INFO) { + print_verbose("Rzip chunk: %'d\n", ++chunk); + print_verbose("Chunk byte width: %'d\n", chunk_byte); + print_verbose("Chunk size: "); + if (!ENCRYPT) + print_verbose("%'" PRId64 "\n", chunk_size); + else + print_verbose("N/A %s Encrypted File\n", control->enc_label); + } + while (stream < NUM_STREAMS) { + int block = 1; + + second_last = 0; + if (unlikely(lseek(fd_in, stream_head[stream] + ofs, SEEK_SET) == -1)) + fatal("Failed to seek to header data in get_fileinfo\n"); + + if (unlikely(!get_header_info(control, fd_in, &ctype, &c_len, &u_len, &last_head, chunk_byte))) return false; + + if (ENCRYPT && ctype != CTYPE_NONE) + fatal("Invalid stream ctype (%02x) for encrypted file. Bad Password?\n", ctype); + + if (INFO) { + print_verbose("Stream: %'d\n", stream); + print_maxverbose("Offset: %'" PRId64 "\n", stream_head[stream] + ofs); + print_verbose("%s\t%s\t%s\t%16s / %14s", "Block", "Comp", "Percent", "Comp Size", "UComp Size"); + print_maxverbose("%18s : %14s", "Offset", "Head"); + print_verbose("\n"); + } + do { + i64 head_off; + + if (unlikely(last_head && last_head <= second_last)) + fatal("Invalid earlier last_head position, corrupt archive.\n"); + second_last = last_head; + if (!ENCRYPT) { + if (unlikely(last_head + ofs > infile_size)) + fatal("Offset greater than archive size, likely corrupted/truncated archive.\n"); + } else { + if (unlikely(last_head + ofs + header_length > infile_size)) + fatal("Offset greater than archive size, likely corrupted/truncated archive.\n"); + } + + if (unlikely((head_off = lseek(fd_in, last_head + ofs, SEEK_SET)) == -1)) + fatal("Failed to seek to header data in get_fileinfo\n"); + if (unlikely(!get_header_info(control, fd_in, &ctype, &c_len, &u_len, &last_head, chunk_byte))) + return false; + if (unlikely(last_head < 0 || c_len < 0 || u_len < 0)) fatal("Entry negative, likely corrupted archive.\n"); + if (INFO) print_verbose("%'d\t", block); + if (ctype == CTYPE_NONE) { + if (INFO) print_verbose("none"); + } else if (ctype == CTYPE_LZ4) { + if (INFO) print_verbose("lz4"); + } else if (ctype == CTYPE_LZMA) { + if (INFO) print_verbose("lzma"); + } else if (ctype == CTYPE_ZSTD) { + if (INFO) print_verbose("zstd"); + } else if (ctype == CTYPE_ZPAQ) { + if (INFO) print_verbose("zpaq"); + } else if (ctype == CTYPE_BZIP3) { + if (INFO) print_verbose("bzip3"); + } else + fatal("Unknown Compression Type: %'d\n", ctype); + if (save_ctype == 255) + save_ctype = ctype; /* need this for lzma when some chunks could have no compression + * and info will show rzip + none on info display if last chunk + * is not compressed. Adjust for all types in case it's used in + * the future */ + utotal += u_len; + ctotal += c_len; + if (INFO) { + print_verbose("\t%5.1f%%\t%'16" PRId64 " / %'14" PRId64 "", percentage(c_len, u_len), c_len, u_len); + print_maxverbose("%'18" PRId64 " : %'14" PRId64 "", head_off, last_head); + print_verbose("\n"); + } + block++; + } while (last_head); + ++stream; + } + + if (unlikely((ofs = lseek(fd_in, c_len, SEEK_CUR)) == -1)) fatal("Failed to lseek c_len in get_fileinfo\n"); + + if (ofs >= infile_size - *control->hash_len) + goto done; + else if (ENCRYPT) + if (ofs + header_length + *control->hash_len > infile_size) goto done; + + /* Chunk byte entry */ + /* remove checks for mrzip < 0.6 */ + if (control->major_version == 0) { + if (!ENCRYPT) { + if (unlikely(read(fd_in, &chunk_byte, 1) != 1)) fatal("Failed to read chunk_byte in get_fileinfo\n"); + if (unlikely(chunk_byte < 1 || chunk_byte > 8)) fatal("Invalid chunk bytes %'d\n", chunk_byte); + ofs++; + if (unlikely(read(fd_in, &control->eof, 1) != 1)) fatal("Failed to read eof in get_fileinfo\n"); + if (unlikely(read(fd_in, &chunk_size, chunk_byte) != chunk_byte)) + fatal("Failed to read chunk_size in get_fileinfo\n"); + chunk_size = le64toh(chunk_size); + if (unlikely(chunk_size < 0)) fatal("Invalid chunk size %'" PRId64 "\n", chunk_size); + ofs += 1 + chunk_byte; + header_length = 1 + (chunk_byte * 3); + } else { + // ENCRYPTED + // no change to chunk_byte + ofs += 10; + // no change to header_length + } + } + + goto next_chunk; +done: + /* compression ratio and bits per byte ratio */ + cratio = (long double)expected_size / (long double)infile_size; + bpb = ((long double)infile_size / (long double)expected_size) * 8; + if (unlikely(ofs > infile_size)) fatal("Offset greater than archive size, likely corrupted/truncated archive.\n"); + + if (INFO) { + print_output("\nSummary\n=======\n"); + print_output("File: %s\nmrzip version: %'d.%'d ", infilecopy, control->major_version, control->minor_version, + ENCRYPT ? "Encrypted " : ""); + if (ENCRYPT) print_output("%s Encrypted ", control->enc_label); + print_output("file\n"); + if (control->comment_length) /* print comment */ + print_output("Archive Comment: %s\n", control->comment); + + print_output("Compression Method: "); + if (save_ctype == CTYPE_NONE) + print_output("rzip alone\n"); + else if (save_ctype == CTYPE_LZ4) + print_output("rzip + lz4\n"); + else if (save_ctype == CTYPE_LZMA) { + print_output("rzip + lzma"); + } else if (save_ctype == CTYPE_ZSTD) + print_output("rzip + zstd\n"); + else if (save_ctype == CTYPE_ZPAQ) { + print_output("rzip + zpaq "); + if (control->zpaq_level) // update magic with zpaq coding. + print_output("-- Compression Level = %d, Block Size = %d, %'dMB\n", control->zpaq_level, + control->zpaq_bs, (1 << control->zpaq_bs)); + else // early 0.8 or <0.8 file without zpaq coding in magic header + print_output("\n"); + } else if (save_ctype == BZIP3_COMPRESS) { + print_output("rzip + bzip3 -- Block Size: %d - %'" PRIu32 "\n", control->bzip3_bs, + control->bzip3_block_size); + } else + print_output("Dunno wtf\n"); + + /* only print stored compression level for versions that have it! */ + if (control->compression_level) + print_output("Rzip Compression Level: %d, Lrzip-next Compressinn Level: %d\n", + control->rzip_compression_level, control->compression_level); + + if (!expected_size) + print_output("Due to using %s, expected decompression size not available\n", + ENCRYPT ? "Encryption" : "Compression to STDOUT"); + print_verbose( + " Stats Percent Compressed / Uncompressed\n " + "-------------------------------------------------------\n"); + /* If we can't show expected size, tailor output for it */ + if (expected_size) { + print_verbose(" Rzip: %5.1f%%\t%'16" PRId64 " / %'14" PRId64 "\n", + percentage(utotal, expected_size), utotal, expected_size); + print_verbose(" Back end: %5.1f%%\t%'16" PRId64 " / %'14" PRId64 "\n", percentage(ctotal, utotal), + ctotal, utotal); + print_verbose(" Overall: %5.1f%%\t%'16" PRId64 " / %'14" PRId64 "\n", + percentage(ctotal, expected_size), ctotal, expected_size); + } else { + print_verbose(" Rzip: Unavailable\n"); + print_verbose(" Back end: %5.1f%%\t%'16" PRId64 " / %'14" PRId64 "\n", percentage(ctotal, utotal), + ctotal, utotal); + print_verbose(" Overall: Unavailable\n"); + } + + if (expected_size) { + print_output("\n Decompressed file size: %'14" PRIu64 "\n", expected_size); + print_output(" Compressed file size: %'14" PRIu64 "\n", infile_size); + print_output(" Compression ratio: %14.3Lfx, bpb: %.3Lf\n", cratio, bpb); + } else { + print_output(" Decompressed file size: Unavailable\n"); + print_output(" Compressed file size: %'14" PRIu64 "\n", infile_size); + print_output(" Compression ratio: Unavailable\n"); + } + } /* end if (INFO) */ + + if (HAS_HASH) { + uchar * hash_stored; + + int i; + + if (INFO) { + hash_stored = calloc(*control->hash_len, 1); + if (unlikely(lseek(fd_in, -*control->hash_len, SEEK_END) == -1)) + fatal("Failed to seek to %s data in get_fileinfo.\n", control->hash_label); + if (unlikely(read(fd_in, hash_stored, *control->hash_len) != *control->hash_len)) + fatal("Failed to read %s data in get_fileinfo.\n", control->hash_label); + if (ENCRYPT) + if (unlikely(!lrz_decrypt(control, hash_stored, *control->hash_len, control->salt_pass, LRZ_VALIDATE))) + fatal("Failure decrypting %s in get_fileinfo.\n", control->hash_label); + print_output("\n %s Checksum: ", control->hash_label); + for (i = 0; i < *control->hash_len; i++) print_output("%02x", hash_stored[i]); + print_output("\n"); + dealloc(hash_stored); + } + } else { + if (INFO) print_output("\n CRC32 used for integrity testing\n"); + } + +out: + if (unlikely(close(fd_in))) fatal("Failed to close fd_in in get_fileinfo\n"); + return true; +error: + dealloc(control->outfile); + return false; +} + +/* + compress one file from the command line +*/ +bool compress_file(rzip_control * control) { + const char *tmp, *tmpinfile; /* we're just using this as a proxy for control->infile. + * Spares a compiler warning + */ + int fd_in = -1, fd_out = -1, len = MAGIC_LEN + control->comment_length; + char * header; + + header = calloc(len, 1); + + control->flags |= FLAG_HASHED; + /* allocate result block for selected hash */ + control->hash_resblock = calloc(*control->hash_len, 1); + + if (ENCRYPT) { /* AES 128 now default */ + if (unlikely(!get_hash(control, 1))) return false; + } + + if (!STDIN) { + fd_in = open(control->infile, O_RDONLY); + if (unlikely(fd_in == -1)) fatal("Failed to open %s\n", control->infile); + } else + fd_in = fileno(control->inFILE); + + if (!STDOUT) { + if (control->outname) { + control->outfile = strdup(control->outname); + } else { + /* default output name from control->infile + * test if outdir specified. If so, strip path from filename of + * control->infile + */ + if (control->outdir && (tmp = strrchr(control->infile, '/'))) + tmpinfile = tmp + 1; + else + tmpinfile = control->infile; + + control->outfile = malloc((control->outdir == NULL ? 0 : strlen(control->outdir)) + strlen(tmpinfile) + + strlen(control->suffix) + 1); + if (unlikely(!control->outfile)) fatal("Failed to allocate outfile name\n"); + + if (control->outdir) { /* prepend control->outdir */ + strcpy(control->outfile, control->outdir); + strcat(control->outfile, tmpinfile); + } else + strcpy(control->outfile, tmpinfile); + strcat(control->outfile, control->suffix); + // print_progress("Output filename is: %s\n", control->outfile); + // Not needed since printed at end of decompression + } + + if (!strcmp(control->infile, control->outfile)) + fatal("Input and Output files are the same. %s. Exiting\n", control->infile); + + fd_out = open(control->outfile, O_RDWR | O_CREAT | O_EXCL, 0666); + if (FORCE_REPLACE && (-1 == fd_out) && (EEXIST == errno)) { + if (unlikely(unlink(control->outfile))) fatal("Failed to unlink an existing file: %s\n", control->outfile); + fd_out = open(control->outfile, O_RDWR | O_CREAT | O_EXCL, 0666); + } + if (unlikely(fd_out == -1)) { + /* We must ensure we don't delete a file that already + * exists just because we tried to create a new one */ + control->flags |= FLAG_KEEP_BROKEN; + fatal("Failed to create %s\n", control->outfile); + } + control->fd_out = fd_out; + if (!STDIN) { + if (unlikely(!preserve_perms(control, fd_in, fd_out))) goto error; + } + } else { + if (unlikely(!open_tmpoutbuf(control))) goto error; + } + + /* Write zeroes to header at beginning of file */ + if (unlikely(!STDOUT && write(fd_out, header, len) != len)) fatal("Cannot write file header\n"); + + rzip_fd(control, fd_in, fd_out); + + /* need to write magic after compression for expected size */ + if (!STDOUT) { + if (unlikely(!write_magic(control))) goto error; + } + + if (ENCRYPT) release_hashes(control); + + if (unlikely(!STDIN && !STDOUT && !preserve_times(control, fd_in))) { + fatal("Failed to preserve times on output file\n"); + goto error; + } + + if (unlikely(close(fd_in))) { + fatal("Failed to close fd_in\n"); + fd_in = -1; + goto error; + } + if (unlikely(!STDOUT && close(fd_out))) fatal("Failed to close fd_out\n"); + if (TMP_OUTBUF) close_tmpoutbuf(control); + + if (!KEEP_FILES && !STDIN) { + if (unlikely(unlink(control->infile))) fatal("Failed to unlink %s\n", control->infile); + } + + dealloc(control->outfile); + dealloc(control->hash_resblock); + dealloc(header); + return true; +error: + dealloc(header); + if (!STDIN && (fd_in > 0)) close(fd_in); + if ((!STDOUT) && (fd_out > 0)) close(fd_out); + return false; +} + +/* + decompress one file from the command line +*/ +bool decompress_file(rzip_control * control) { + char *tmp, *tmpoutfile, *infilecopy = NULL; + int fd_in, fd_out = -1, fd_hist = -1; + i64 expected_size = 0, free_space; + struct statvfs fbuf; + + if (!STDIN) { + struct stat fdin_stat; + infilecopy = strdupa(control->infile); + if (unlikely(stat(infilecopy, &fdin_stat))) + fatal("File %s not found...\n", control->infile); + else if (unlikely(!S_ISREG(fdin_stat.st_mode))) + fatal("mrzip only works on regular FILES\n"); + /* regardless, infilecopy has the input filename */ + } + + if (!STDOUT && !TEST_ONLY) { + /* if output name already set, use it */ + if (control->outname) + control->outfile = strdup(control->outname); + else { + /* default output name from infilecopy + * test if outdir specified. If so, strip path from filename of + * infilecopy, then remove suffix. + */ + if (control->outdir && (tmp = strrchr(infilecopy, '/'))) + tmpoutfile = strdupa(tmp + 1); + else + tmpoutfile = strdupa(infilecopy); + + /* remove suffix to make outfile name */ + if ((tmp = strrchr(tmpoutfile, '.')) && !strcmp(tmp, control->suffix)) *tmp = '\0'; + + control->outfile = malloc((control->outdir == NULL ? 0 : strlen(control->outdir)) + strlen(tmpoutfile) + 1); + if (unlikely(!control->outfile)) fatal("Failed to allocate outfile name\n"); + + if (control->outdir) { /* prepend control->outdir */ + strcpy(control->outfile, control->outdir); + strcat(control->outfile, tmpoutfile); + } else + strcpy(control->outfile, tmpoutfile); + } + + if (!STDOUT) print_progress("Output filename is: %s\n", control->outfile); + + if (unlikely(!strcmp(control->outfile, infilecopy))) { + control->flags |= FLAG_TEST_ONLY; // stop and no more decompres or deleting files. + fatal("Output and Input files are the same...Cannot continue\n"); + } + } + + if (STDIN) { + fd_in = open_tmpinfile(control); + read_tmpinmagic(control, fd_in); + if (ENCRYPT) fatal("Cannot decompress encrypted file from STDIN\n"); + expected_size = control->st_size; + if (unlikely(!open_tmpinbuf(control))) return false; + } else { + fd_in = open(infilecopy, O_RDONLY); + if (unlikely(fd_in == -1)) { + fatal("Failed to open %s\n", infilecopy); + } + } + control->fd_in = fd_in; + + if (!(TEST_ONLY || STDOUT)) { + fd_out = open(control->outfile, O_WRONLY | O_CREAT | O_EXCL, 0666); + if (FORCE_REPLACE && (-1 == fd_out) && (EEXIST == errno)) { + if (unlikely(unlink(control->outfile))) fatal("Failed to unlink an existing file: %s\n", control->outfile); + fd_out = open(control->outfile, O_WRONLY | O_CREAT | O_EXCL, 0666); + } + if (unlikely(fd_out == -1)) { + /* We must ensure we don't delete a file that already + * exists just because we tried to create a new one */ + control->flags |= FLAG_KEEP_BROKEN; + fatal("Failed to create %s\n", control->outfile); + } + fd_hist = open(control->outfile, O_RDONLY); + if (unlikely(fd_hist == -1)) fatal("Failed to open history file %s\n", control->outfile); + + /* Can't copy permissions from STDIN */ + if (!STDIN) + if (unlikely(!preserve_perms(control, fd_in, fd_out))) return false; + } else { + fd_out = open_tmpoutfile(control); + if (fd_out == -1) { + fd_hist = -1; + } else { + fd_hist = open(control->outfile, O_RDONLY); + if (unlikely(fd_hist == -1)) fatal("Failed to open history file %s\n", control->outfile); + /* Unlink temporary file as soon as possible */ + if (unlikely(unlink(control->outfile))) fatal("Failed to unlink tmpfile: %s\n", control->outfile); + } + } + + // check for STDOUT removed. In memory compression speedup. No memory leak. + if (unlikely(!open_tmpoutbuf(control))) return false; + + if (!STDIN) { + if (unlikely(!read_magic(control, fd_in, &expected_size))) return false; + if (unlikely(expected_size < 0)) fatal("Invalid expected size %'" PRId64 "\n", expected_size); + } + + if (!STDOUT) { + /* Check if there's enough free space on the device chosen to fit the + * decompressed or test file. */ + if (unlikely(fstatvfs(fd_out, &fbuf))) fatal("Failed to fstatvfs in decompress_file\n"); + free_space = (i64)fbuf.f_bsize * (i64)fbuf.f_bavail; + if (free_space < expected_size) { + if (FORCE_REPLACE && !TEST_ONLY) + print_err( + "Warning, inadequate free space detected, but attempting to decompress file due to -f option being " + "used.\n"); + else + fatal("Inadequate free space to %s. Space needed: %'" PRId64 ". Space available: %'" PRId64 + ".\nTry %s and select a larger volume.\n", + TEST_ONLY ? "test file" : "decompress file. Use -f to override", expected_size, free_space, + TEST_ONLY ? "setting `TMP=dirname`" : "using `-O dirname` or `-o [dirname/]filename` options"); + } + } + control->fd_out = fd_out; + control->fd_hist = fd_hist; + + show_version(control); + + if (NO_HASH) print_verbose("Not performing hash check\n"); + if (HAS_HASH) + print_verbose("%s ", control->hash_label); + else + print_verbose("CRC32 "); + print_verbose("being used for integrity testing.\n"); + + control->hash_resblock = calloc(*control->hash_len, 1); + + if (ENCRYPT && !control->salt_pass_len) { // Only get password if needed + if (unlikely(!get_hash(control, 0))) return false; + print_maxverbose("Encryption hash loops %'" PRId64 "\n", control->encloops); + if (!INFO) print_verbose("%s Encryption Used\n", control->enc_label); + } + + // vailidate file on decompression or test + if (STDIN) + print_err("Unable to validate a file from STDIN. To validate, check file directly.\n"); + else { + print_progress("Validating file for consistency..."); + if (unlikely((get_fileinfo(control)) == false)) + fatal("File validation failed. Corrupt mrzip archive. Cannot continue\n"); + print_progress("[OK]"); + if (!VERBOSE) print_progress("\n"); // output LF to prevent overwriing decompression output + } + show_version(control); // show version here to preserve output formatting + print_progress("Decompressing..."); + + if (unlikely(runzip_fd(control, fd_in, fd_out, fd_hist, expected_size) < 0)) { + clear_rulist(control); + return false; + } + + /* We can now safely delete sinfo and pthread data of all threads + * created. */ + clear_rulist(control); + + if (STDOUT && !TMP_OUTBUF) { + if (unlikely(!dump_tmpoutfile(control, fd_out))) return false; + } + + /* if we get here, no fatal_return(( errors during decompression */ + print_progress("\r"); + if (!(STDOUT || TEST_ONLY)) print_progress("Output filename is: %s: ", control->outfile); + if (!expected_size) expected_size = control->st_size; + if (!ENCRYPT) + print_progress("[OK] - %'" PRId64 " bytes \n", expected_size); + else + print_progress("[OK] \n"); + + if (TMP_OUTBUF) close_tmpoutbuf(control); + + if (fd_out > 0) + if (unlikely(close(fd_hist) || close(fd_out))) fatal("Failed to close files\n"); + + if (unlikely(!STDIN && !STDOUT && !TEST_ONLY && !preserve_times(control, fd_in))) return false; + + if (!STDIN) close(fd_in); + + if (!KEEP_FILES && !STDIN) + if (unlikely(unlink(control->infile))) fatal("Failed to unlink %s\n", infilecopy); + + if (ENCRYPT) release_hashes(control); + + dealloc(control->outfile); + dealloc(control->hash_resblock); + return true; +} + +bool initialise_control(rzip_control * control) { + time_t now_t, tdiff; + char localeptr[] = "/tmp", *eptr; /* for environment. OR Default to /tmp if none set */ + size_t len; + + memset(control, 0, sizeof(rzip_control)); + control->locale = ""; /* empty string for default locale */ + control->msgout = stderr; + control->msgerr = stderr; + register_outputfile(control, control->msgout); + control->flags = FLAG_SHOW_PROGRESS | FLAG_KEEP_FILES | FLAG_THRESHOLD; + control->filter_flag = 0; /* filter flag. Default to none */ + control->compression_level = 7; /* compression level default */ + control->rzip_compression_level = + 0; /* rzip compression level default will equal compression level unless explicitly set */ + control->ramsize = get_ram(control); /* if something goes wrong, exit from get_ram */ + control->threshold = 100; /* default for no threshold limiting */ + /* for testing single CPU */ + control->threads = PROCESSORS; /* get CPUs for LZMA */ + control->page_size = PAGE_SIZE; + control->nice_val = 19; + + /* The first 5 bytes of the salt is the time in seconds. + * The next 2 bytes encode how many times to hash the password. + * The last 9 bytes are random data, making 16 bytes of salt */ + if (unlikely((now_t = time(NULL)) == ((time_t)-1))) fatal("Failed to call time in main\n"); + if (unlikely(now_t < T_ZERO)) { + print_output("Warning your time reads before the year 2011, check your system clock\n"); + now_t = T_ZERO; + } + /* Workaround for CPUs no longer keeping up with Moore's law! + * This way we keep the magic header format unchanged. */ + tdiff = (now_t - T_ZERO) / 4; + now_t = T_ZERO + tdiff; + control->secs = now_t; + control->encloops = nloops(control->secs, control->salt, control->salt + 1); + gcry_create_nonce(control->salt + 2, 6); + + /* Get Temp Dir. Try variations on canonical unix environment variable */ + eptr = getenv("TMPDIR"); + if (!eptr) eptr = getenv("TMP"); + if (!eptr) eptr = getenv("TEMPDIR"); + if (!eptr) eptr = getenv("TEMP"); + if (!eptr) eptr = localeptr; + len = strlen(eptr); + + control->tmpdir = malloc(len + 2); + if (control->tmpdir == NULL) fatal("Failed to allocate for tmpdir\n"); + strcpy(control->tmpdir, eptr); + if (control->tmpdir[len - 1] != '/') { + control->tmpdir[len] = '/'; /* need a trailing slash */ + control->tmpdir[len + 1] = '\0'; + } + + /* just in case, set pointers for hash and encryptions */ + + return true; +} + +/* + Copyright (C) 2006-2016 Con Kolivas + Copyright (C) 2011 Peter Hyman + Copyright (C) 1998 Andrew Tridgell + Copyright (C) 2022 Kamila Szewczyk + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef MRZIP_UTIL_H +#define MRZIP_UTIL_H + +#include +#include +#include +#include +#include +#include + +#include "./mrzip_private.h" + +void register_infile(rzip_control * control, const char * name, char delete); +void register_outfile(rzip_control * control, const char * name, char delete); +void unlink_files(rzip_control * control); +void register_outputfile(rzip_control * control, FILE * f); +noreturn void fatal_exit(rzip_control * control); +void setup_overhead(rzip_control * control); +void setup_ram(rzip_control * control); +void round_to_page(i64 * size); +size_t round_up_page(rzip_control * control, size_t len); +bool read_config(rzip_control * control); +void lrz_stretch(rzip_control * control); +bool lrz_crypt(const rzip_control * control, uchar * buf, i64 len, const uchar * salt, int encrypt); +/* decrypt_header will take a final variable for either decrypt or validate. + * Valdidate will suppress printing message during validation or info + */ +bool decrypt_header(rzip_control * control, uchar * head, uchar * c_type, i64 * c_len, i64 * u_len, i64 * last_head, + int decompress_type); + +static inline noreturn void fatal(const rzip_control * control, unsigned int line, const char * file, const char * func, + const char * format, ...) { + va_list ap; + /* mrzip library callback code removed */ + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + fatal_exit((rzip_control *)control); +} +#ifdef fatal + #undef fatal +#endif +#define fatal(...) fatal(control, __LINE__, __FILE__, __func__, __VA_ARGS__) + +static inline bool lrz_encrypt(const rzip_control * control, uchar * buf, i64 len, const uchar * salt) { + return lrz_crypt(control, buf, len, salt, LRZ_ENCRYPT); +} + +static inline bool lrz_decrypt(const rzip_control * control, uchar * buf, i64 len, const uchar * salt, + int dec_or_validate) { + return lrz_crypt(control, buf, len, salt, dec_or_validate); +} + +/* ck specific wrappers for true unnamed semaphore usage on platforms + * that support them and for apple which does not. We use a single byte across + * a pipe to emulate semaphore behaviour there. */ +#ifdef __APPLE__ +static inline void cksem_init(const rzip_control * control, cksem_t * cksem) { + int flags, fd, i; + + if (pipe(cksem->pipefd) == -1) fatal("Failed pipe errno=%d", errno); + + /* Make the pipes FD_CLOEXEC to allow them to close should we call + * execv on restart. */ + for (i = 0; i < 2; i++) { + fd = cksem->pipefd[i]; + flags = fcntl(fd, F_GETFD, 0); + flags |= FD_CLOEXEC; + if (fcntl(fd, F_SETFD, flags) == -1) fatal("Failed to fcntl errno=%d", errno); + } +} + +static inline void cksem_post(const rzip_control * control, cksem_t * cksem) { + const char buf = 1; + int ret; + + ret = write(cksem->pipefd[1], &buf, 1); + if (unlikely(ret == 0)) fatal("Failed to write in cksem_post errno=%d", errno); +} + +static inline void cksem_wait(const rzip_control * control, cksem_t * cksem) { + char buf; + int ret; + + ret = read(cksem->pipefd[0], &buf, 1); + if (unlikely(ret == 0)) fatal("Failed to read in cksem_post errno=%d", errno); +} +#else +static inline void cksem_init(const rzip_control * control, cksem_t * cksem) { + int ret; + if ((ret = sem_init(cksem, 0, 0))) fatal("Failed to sem_init ret=%d errno=%d", ret, errno); +} + +static inline void cksem_post(const rzip_control * control, cksem_t * cksem) { + if (unlikely(sem_post(cksem))) fatal("Failed to sem_post errno=%d cksem=0x%p", errno, cksem); +} + +static inline void cksem_wait(const rzip_control * control, cksem_t * cksem) { + if (unlikely(sem_wait(cksem))) fatal("Failed to sem_wait errno=%d cksem=0x%p", errno, cksem); +} +#endif + +#endif + + +#if defined(RAUDIO_STANDALONE) + #include "raudio.h" +#else + #include "raylib.h" // Declares module functions + + // Check if config flags have been externally provided on compilation line + #if !defined(EXTERNAL_CONFIG_FLAGS) + #include "config.h" // Defines module configuration flags + #endif + #include "utils.h" // Required for: fopen() Android mapping +#endif + +#if defined(SUPPORT_MODULE_RAUDIO) + +#if defined(_WIN32) +// To avoid conflicting windows.h symbols with raylib, some flags are defined +// WARNING: Those flags avoid inclusion of some Win32 headers that could be required +// by user at some point and won't be included... +//------------------------------------------------------------------------------------- + +// If defined, the following flags inhibit definition of the indicated items. +#define NOGDICAPMASKS // CC_*, LC_*, PC_*, CP_*, TC_*, RC_ +#define NOVIRTUALKEYCODES // VK_* +#define NOWINMESSAGES // WM_*, EM_*, LB_*, CB_* +#define NOWINSTYLES // WS_*, CS_*, ES_*, LBS_*, SBS_*, CBS_* +#define NOSYSMETRICS // SM_* +#define NOMENUS // MF_* +#define NOICONS // IDI_* +#define NOKEYSTATES // MK_* +#define NOSYSCOMMANDS // SC_* +#define NORASTEROPS // Binary and Tertiary raster ops +#define NOSHOWWINDOW // SW_* +#define OEMRESOURCE // OEM Resource values +#define NOATOM // Atom Manager routines +#define NOCLIPBOARD // Clipboard routines +#define NOCOLOR // Screen colors +#define NOCTLMGR // Control and Dialog routines +#define NODRAWTEXT // DrawText() and DT_* +#define NOGDI // All GDI defines and routines +#define NOKERNEL // All KERNEL defines and routines +#define NOUSER // All USER defines and routines +//#define NONLS // All NLS defines and routines +#define NOMB // MB_* and MessageBox() +#define NOMEMMGR // GMEM_*, LMEM_*, GHND, LHND, associated routines +#define NOMETAFILE // typedef METAFILEPICT +#define NOMINMAX // Macros min(a,b) and max(a,b) +#define NOMSG // typedef MSG and associated routines +#define NOOPENFILE // OpenFile(), OemToAnsi, AnsiToOem, and OF_* +#define NOSCROLL // SB_* and scrolling routines +#define NOSERVICE // All Service Controller routines, SERVICE_ equates, etc. +#define NOSOUND // Sound driver routines +#define NOTEXTMETRIC // typedef TEXTMETRIC and associated routines +#define NOWH // SetWindowsHook and WH_* +#define NOWINOFFSETS // GWL_*, GCL_*, associated routines +#define NOCOMM // COMM driver routines +#define NOKANJI // Kanji support stuff. +#define NOHELP // Help engine interface. +#define NOPROFILER // Profiler interface. +#define NODEFERWINDOWPOS // DeferWindowPos routines +#define NOMCX // Modem Configuration Extensions + +// Type required before windows.h inclusion +typedef struct tagMSG *LPMSG; + +#include // Windows functionality (miniaudio) + +// Type required by some unused function... +typedef struct tagBITMAPINFOHEADER { + DWORD biSize; + LONG biWidth; + LONG biHeight; + WORD biPlanes; + WORD biBitCount; + DWORD biCompression; + DWORD biSizeImage; + LONG biXPelsPerMeter; + LONG biYPelsPerMeter; + DWORD biClrUsed; + DWORD biClrImportant; +} BITMAPINFOHEADER, *PBITMAPINFOHEADER; + +#include // Component Object Model (COM) header +#include // Windows Multimedia, defines some WAVE structs +#include // Windows Multimedia, used by Windows GDI, defines DIBINDEX macro + +// Some required types defined for MSVC/TinyC compiler +#if defined(_MSC_VER) || defined(__TINYC__) + #include "propidl.h" +#endif +#endif + +#define MA_MALLOC RL_MALLOC +#define MA_FREE RL_FREE + +#define MA_NO_JACK +#define MA_NO_WAV +#define MA_NO_FLAC +#define MA_NO_MP3 + +// Threading model: Default: [0] COINIT_MULTITHREADED: COM calls objects on any thread (free threading) +#define MA_COINIT_VALUE 2 // [2] COINIT_APARTMENTTHREADED: Each object has its own thread (apartment model) + +#define MINIAUDIO_IMPLEMENTATION +//#define MA_DEBUG_OUTPUT +#include "external/miniaudio.h" // Audio device initialization and management +#undef PlaySound // Win32 API: windows.h > mmsystem.h defines PlaySound macro + +#include // Required for: malloc(), free() +#include // Required for: FILE, fopen(), fclose(), fread() +#include // Required for: strcmp() [Used in IsFileExtension(), LoadWaveFromMemory(), LoadMusicStreamFromMemory()] + +#if defined(RAUDIO_STANDALONE) + #ifndef TRACELOG + #define TRACELOG(level, ...) printf(__VA_ARGS__) + #endif + + // Allow custom memory allocators + #ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) + #endif + #ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) + #endif + #ifndef RL_REALLOC + #define RL_REALLOC(ptr,sz) realloc(ptr,sz) + #endif + #ifndef RL_FREE + #define RL_FREE(ptr) free(ptr) + #endif +#endif + +#if defined(SUPPORT_FILEFORMAT_WAV) + #define DRWAV_MALLOC RL_MALLOC + #define DRWAV_REALLOC RL_REALLOC + #define DRWAV_FREE RL_FREE + + #define DR_WAV_IMPLEMENTATION + #include "external/dr_wav.h" // WAV loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_OGG) + // TODO: Remap stb_vorbis malloc()/free() calls to RL_MALLOC/RL_FREE + #include "external/stb_vorbis.c" // OGG loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_MP3) + #define DRMP3_MALLOC RL_MALLOC + #define DRMP3_REALLOC RL_REALLOC + #define DRMP3_FREE RL_FREE + + #define DR_MP3_IMPLEMENTATION + #include "external/dr_mp3.h" // MP3 loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_QOA) + #define QOA_MALLOC RL_MALLOC + #define QOA_FREE RL_FREE + + #if defined(_MSC_VER) // Disable some MSVC warning + #pragma warning(push) + #pragma warning(disable : 4018) + #pragma warning(disable : 4267) + #pragma warning(disable : 4244) + #endif + + #define QOA_IMPLEMENTATION + #include "external/qoa.h" // QOA loading and saving functions + #include "external/qoaplay.c" // QOA stream playing helper functions + + #if defined(_MSC_VER) + #pragma warning(pop) // Disable MSVC warning suppression + #endif +#endif + +#if defined(SUPPORT_FILEFORMAT_FLAC) + #define DRFLAC_MALLOC RL_MALLOC + #define DRFLAC_REALLOC RL_REALLOC + #define DRFLAC_FREE RL_FREE + + #define DR_FLAC_IMPLEMENTATION + #define DR_FLAC_NO_WIN32_IO + #include "external/dr_flac.h" // FLAC loading functions +#endif + +#if defined(SUPPORT_FILEFORMAT_XM) + #define JARXM_MALLOC RL_MALLOC + #define JARXM_FREE RL_FREE + + #if defined(_MSC_VER) // Disable some MSVC warning + #pragma warning(push) + #pragma warning(disable : 4244) + #endif + + #define JAR_XM_IMPLEMENTATION + #include "external/jar_xm.h" // XM loading functions + + #if defined(_MSC_VER) + #pragma warning(pop) // Disable MSVC warning suppression + #endif +#endif + +#if defined(SUPPORT_FILEFORMAT_MOD) + #define JARMOD_MALLOC RL_MALLOC + #define JARMOD_FREE RL_FREE + + #define JAR_MOD_IMPLEMENTATION + #include "external/jar_mod.h" // MOD loading functions +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef AUDIO_DEVICE_FORMAT + #define AUDIO_DEVICE_FORMAT ma_format_f32 // Device output format (float-32bit) +#endif +#ifndef AUDIO_DEVICE_CHANNELS + #define AUDIO_DEVICE_CHANNELS 2 // Device output channels: stereo +#endif +#ifndef AUDIO_DEVICE_SAMPLE_RATE + #define AUDIO_DEVICE_SAMPLE_RATE 0 // Device output sample rate +#endif + +#ifndef MAX_AUDIO_BUFFER_POOL_CHANNELS + #define MAX_AUDIO_BUFFER_POOL_CHANNELS 16 // Audio pool channels +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if defined(RAUDIO_STANDALONE) +// Trace log level +// NOTE: Organized by priority level +typedef enum { + LOG_ALL = 0, // Display all logs + LOG_TRACE, // Trace logging, intended for internal use only + LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds + LOG_INFO, // Info logging, used for program execution info + LOG_WARNING, // Warning logging, used on recoverable failures + LOG_ERROR, // Error logging, used on unrecoverable failures + LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) + LOG_NONE // Disable logging +} TraceLogLevel; +#endif + +// Music context type +// NOTE: Depends on data structure provided by the library +// in charge of reading the different file types +typedef enum { + MUSIC_AUDIO_NONE = 0, // No audio context loaded + MUSIC_AUDIO_WAV, // WAV audio context + MUSIC_AUDIO_OGG, // OGG audio context + MUSIC_AUDIO_FLAC, // FLAC audio context + MUSIC_AUDIO_MP3, // MP3 audio context + MUSIC_AUDIO_QOA, // QOA audio context + MUSIC_MODULE_XM, // XM module audio context + MUSIC_MODULE_MOD // MOD module audio context +} MusicContextType; + +// NOTE: Different logic is used when feeding data to the playback device +// depending on whether data is streamed (Music vs Sound) +typedef enum { + AUDIO_BUFFER_USAGE_STATIC = 0, + AUDIO_BUFFER_USAGE_STREAM +} AudioBufferUsage; + +// Audio buffer struct +struct rAudioBuffer { + ma_data_converter converter; // Audio data converter + + AudioCallback callback; // Audio buffer callback for buffer filling on audio threads + rAudioProcessor *processor; // Audio processor + + float volume; // Audio buffer volume + float pitch; // Audio buffer pitch + float pan; // Audio buffer pan (0.0f to 1.0f) + + bool playing; // Audio buffer state: AUDIO_PLAYING + bool paused; // Audio buffer state: AUDIO_PAUSED + bool looping; // Audio buffer looping, default to true for AudioStreams + int usage; // Audio buffer usage mode: STATIC or STREAM + + bool isSubBufferProcessed[2]; // SubBuffer processed (virtual double buffer) + unsigned int sizeInFrames; // Total buffer size in frames + unsigned int frameCursorPos; // Frame cursor position + unsigned int framesProcessed; // Total frames processed in this buffer (required for play timing) + + unsigned char *data; // Data buffer, on music stream keeps filling + + rAudioBuffer *next; // Next audio buffer on the list + rAudioBuffer *prev; // Previous audio buffer on the list +}; + +// Audio processor struct +// NOTE: Useful to apply effects to an AudioBuffer +struct rAudioProcessor { + AudioCallback process; // Processor callback function + rAudioProcessor *next; // Next audio processor on the list + rAudioProcessor *prev; // Previous audio processor on the list +}; + +#define AudioBuffer rAudioBuffer // HACK: To avoid CoreAudio (macOS) symbol collision + +// Audio data context +typedef struct AudioData { + struct { + ma_context context; // miniaudio context data + ma_device device; // miniaudio device + ma_mutex lock; // miniaudio mutex lock + bool isReady; // Check if audio device is ready + size_t pcmBufferSize; // Pre-allocated buffer size + void *pcmBuffer; // Pre-allocated buffer to read audio data from file/memory + } System; + struct { + AudioBuffer *first; // Pointer to first AudioBuffer in the list + AudioBuffer *last; // Pointer to last AudioBuffer in the list + int defaultSize; // Default audio buffer size for audio streams + } Buffer; + rAudioProcessor *mixedProcessor; +} AudioData; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static AudioData AUDIO = { // Global AUDIO context + + // NOTE: Music buffer size is defined by number of samples, independent of sample size and channels number + // After some math, considering a sampleRate of 48000, a buffer refill rate of 1/60 seconds and a + // standard double-buffering system, a 4096 samples buffer has been chosen, it should be enough + // In case of music-stalls, just increase this number + .Buffer.defaultSize = 0, + .mixedProcessor = NULL +}; + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +static void OnLog(void *pUserData, ma_uint32 level, const char *pMessage); +static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount); +static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer); + +#if defined(RAUDIO_STANDALONE) +static bool IsFileExtension(const char *fileName, const char *ext); // Check file extension +static const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes the dot: .png) + +static unsigned char *LoadFileData(const char *fileName, int *dataSize); // Load file data as byte array (read) +static bool SaveFileData(const char *fileName, void *data, int dataSize); // Save data to file from byte array (write) +static bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated +#endif + +//---------------------------------------------------------------------------------- +// AudioBuffer management functions declaration +// NOTE: Those functions are not exposed by raylib... for the moment +//---------------------------------------------------------------------------------- +AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage); +void UnloadAudioBuffer(AudioBuffer *buffer); + +bool IsAudioBufferPlaying(AudioBuffer *buffer); +void PlayAudioBuffer(AudioBuffer *buffer); +void StopAudioBuffer(AudioBuffer *buffer); +void PauseAudioBuffer(AudioBuffer *buffer); +void ResumeAudioBuffer(AudioBuffer *buffer); +void SetAudioBufferVolume(AudioBuffer *buffer, float volume); +void SetAudioBufferPitch(AudioBuffer *buffer, float pitch); +void SetAudioBufferPan(AudioBuffer *buffer, float pan); +void TrackAudioBuffer(AudioBuffer *buffer); +void UntrackAudioBuffer(AudioBuffer *buffer); + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Audio Device initialization and Closing +//---------------------------------------------------------------------------------- +// Initialize audio device +void InitAudioDevice(void) +{ + // Init audio context + ma_context_config ctxConfig = ma_context_config_init(); + ma_log_callback_init(OnLog, NULL); + + ma_result result = ma_context_init(NULL, 0, &ctxConfig, &AUDIO.System.context); + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize context"); + return; + } + + // Init audio device + // NOTE: Using the default device. Format is floating point because it simplifies mixing. + ma_device_config config = ma_device_config_init(ma_device_type_playback); + config.playback.pDeviceID = NULL; // NULL for the default playback AUDIO.System.device. + config.playback.format = AUDIO_DEVICE_FORMAT; + config.playback.channels = AUDIO_DEVICE_CHANNELS; + config.capture.pDeviceID = NULL; // NULL for the default capture AUDIO.System.device. + config.capture.format = ma_format_s16; + config.capture.channels = 1; + config.sampleRate = AUDIO_DEVICE_SAMPLE_RATE; + config.dataCallback = OnSendAudioDataToDevice; + config.pUserData = NULL; + + result = ma_device_init(&AUDIO.System.context, &config, &AUDIO.System.device); + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize playback device"); + ma_context_uninit(&AUDIO.System.context); + return; + } + + // Mixing happens on a separate thread which means we need to synchronize. I'm using a mutex here to make things simple, but may + // want to look at something a bit smarter later on to keep everything real-time, if that's necessary. + if (ma_mutex_init(&AUDIO.System.lock) != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to create mutex for mixing"); + ma_device_uninit(&AUDIO.System.device); + ma_context_uninit(&AUDIO.System.context); + return; + } + + // Keep the device running the whole time. May want to consider doing something a bit smarter and only have the device running + // while there's at least one sound being played. + result = ma_device_start(&AUDIO.System.device); + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to start playback device"); + ma_device_uninit(&AUDIO.System.device); + ma_context_uninit(&AUDIO.System.context); + return; + } + + TRACELOG(LOG_INFO, "AUDIO: Device initialized successfully"); + TRACELOG(LOG_INFO, " > Backend: miniaudio / %s", ma_get_backend_name(AUDIO.System.context.backend)); + TRACELOG(LOG_INFO, " > Format: %s -> %s", ma_get_format_name(AUDIO.System.device.playback.format), ma_get_format_name(AUDIO.System.device.playback.internalFormat)); + TRACELOG(LOG_INFO, " > Channels: %d -> %d", AUDIO.System.device.playback.channels, AUDIO.System.device.playback.internalChannels); + TRACELOG(LOG_INFO, " > Sample rate: %d -> %d", AUDIO.System.device.sampleRate, AUDIO.System.device.playback.internalSampleRate); + TRACELOG(LOG_INFO, " > Periods size: %d", AUDIO.System.device.playback.internalPeriodSizeInFrames*AUDIO.System.device.playback.internalPeriods); + + AUDIO.System.isReady = true; +} + +// Close the audio device for all contexts +void CloseAudioDevice(void) +{ + if (AUDIO.System.isReady) + { + ma_mutex_uninit(&AUDIO.System.lock); + ma_device_uninit(&AUDIO.System.device); + ma_context_uninit(&AUDIO.System.context); + + AUDIO.System.isReady = false; + RL_FREE(AUDIO.System.pcmBuffer); + AUDIO.System.pcmBuffer = NULL; + AUDIO.System.pcmBufferSize = 0; + + TRACELOG(LOG_INFO, "AUDIO: Device closed successfully"); + } + else TRACELOG(LOG_WARNING, "AUDIO: Device could not be closed, not currently initialized"); +} + +// Check if device has been initialized successfully +bool IsAudioDeviceReady(void) +{ + return AUDIO.System.isReady; +} + +// Set master volume (listener) +void SetMasterVolume(float volume) +{ + ma_device_set_master_volume(&AUDIO.System.device, volume); +} + +// Get master volume (listener) +float GetMasterVolume(void) +{ + float volume = 0.0f; + ma_device_get_master_volume(&AUDIO.System.device, &volume); + return volume; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Audio Buffer management +//---------------------------------------------------------------------------------- + +// Initialize a new audio buffer (filled with silence) +AudioBuffer *LoadAudioBuffer(ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 sizeInFrames, int usage) +{ + AudioBuffer *audioBuffer = (AudioBuffer *)RL_CALLOC(1, sizeof(AudioBuffer)); + + if (audioBuffer == NULL) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to allocate memory for buffer"); + return NULL; + } + + if (sizeInFrames > 0) audioBuffer->data = RL_CALLOC(sizeInFrames*channels*ma_get_bytes_per_sample(format), 1); + + // Audio data runs through a format converter + ma_data_converter_config converterConfig = ma_data_converter_config_init(format, AUDIO_DEVICE_FORMAT, channels, AUDIO_DEVICE_CHANNELS, sampleRate, AUDIO.System.device.sampleRate); + converterConfig.allowDynamicSampleRate = true; + + ma_result result = ma_data_converter_init(&converterConfig, NULL, &audioBuffer->converter); + + if (result != MA_SUCCESS) + { + TRACELOG(LOG_WARNING, "AUDIO: Failed to create data conversion pipeline"); + RL_FREE(audioBuffer); + return NULL; + } + + // Init audio buffer values + audioBuffer->volume = 1.0f; + audioBuffer->pitch = 1.0f; + audioBuffer->pan = 0.5f; + + audioBuffer->callback = NULL; + audioBuffer->processor = NULL; + + audioBuffer->playing = false; + audioBuffer->paused = false; + audioBuffer->looping = false; + + audioBuffer->usage = usage; + audioBuffer->frameCursorPos = 0; + audioBuffer->sizeInFrames = sizeInFrames; + + // Buffers should be marked as processed by default so that a call to + // UpdateAudioStream() immediately after initialization works correctly + audioBuffer->isSubBufferProcessed[0] = true; + audioBuffer->isSubBufferProcessed[1] = true; + + // Track audio buffer to linked list next position + TrackAudioBuffer(audioBuffer); + + return audioBuffer; +} + +// Delete an audio buffer +void UnloadAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) + { + ma_data_converter_uninit(&buffer->converter, NULL); + UntrackAudioBuffer(buffer); + RL_FREE(buffer->data); + RL_FREE(buffer); + } +} + +// Check if an audio buffer is playing +bool IsAudioBufferPlaying(AudioBuffer *buffer) +{ + bool result = false; + + if (buffer != NULL) result = (buffer->playing && !buffer->paused); + + return result; +} + +// Play an audio buffer +// NOTE: Buffer is restarted to the start. +// Use PauseAudioBuffer() and ResumeAudioBuffer() if the playback position should be maintained. +void PlayAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) + { + buffer->playing = true; + buffer->paused = false; + buffer->frameCursorPos = 0; + } +} + +// Stop an audio buffer +void StopAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) + { + if (IsAudioBufferPlaying(buffer)) + { + buffer->playing = false; + buffer->paused = false; + buffer->frameCursorPos = 0; + buffer->framesProcessed = 0; + buffer->isSubBufferProcessed[0] = true; + buffer->isSubBufferProcessed[1] = true; + } + } +} + +// Pause an audio buffer +void PauseAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) buffer->paused = true; +} + +// Resume an audio buffer +void ResumeAudioBuffer(AudioBuffer *buffer) +{ + if (buffer != NULL) buffer->paused = false; +} + +// Set volume for an audio buffer +void SetAudioBufferVolume(AudioBuffer *buffer, float volume) +{ + if (buffer != NULL) buffer->volume = volume; +} + +// Set pitch for an audio buffer +void SetAudioBufferPitch(AudioBuffer *buffer, float pitch) +{ + if ((buffer != NULL) && (pitch > 0.0f)) + { + // Pitching is just an adjustment of the sample rate. + // Note that this changes the duration of the sound: + // - higher pitches will make the sound faster + // - lower pitches make it slower + ma_uint32 outputSampleRate = (ma_uint32)((float)buffer->converter.sampleRateOut/pitch); + ma_data_converter_set_rate(&buffer->converter, buffer->converter.sampleRateIn, outputSampleRate); + + buffer->pitch = pitch; + } +} + +// Set pan for an audio buffer +void SetAudioBufferPan(AudioBuffer *buffer, float pan) +{ + if (pan < 0.0f) pan = 0.0f; + else if (pan > 1.0f) pan = 1.0f; + + if (buffer != NULL) buffer->pan = pan; +} + +// Track audio buffer to linked list next position +void TrackAudioBuffer(AudioBuffer *buffer) +{ + ma_mutex_lock(&AUDIO.System.lock); + { + if (AUDIO.Buffer.first == NULL) AUDIO.Buffer.first = buffer; + else + { + AUDIO.Buffer.last->next = buffer; + buffer->prev = AUDIO.Buffer.last; + } + + AUDIO.Buffer.last = buffer; + } + ma_mutex_unlock(&AUDIO.System.lock); +} + +// Untrack audio buffer from linked list +void UntrackAudioBuffer(AudioBuffer *buffer) +{ + ma_mutex_lock(&AUDIO.System.lock); + { + if (buffer->prev == NULL) AUDIO.Buffer.first = buffer->next; + else buffer->prev->next = buffer->next; + + if (buffer->next == NULL) AUDIO.Buffer.last = buffer->prev; + else buffer->next->prev = buffer->prev; + + buffer->prev = NULL; + buffer->next = NULL; + } + ma_mutex_unlock(&AUDIO.System.lock); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Sounds loading and playing (.WAV) +//---------------------------------------------------------------------------------- + +// Load wave data from file +Wave LoadWave(const char *fileName) +{ + Wave wave = { 0 }; + + // Loading file to memory + int dataSize = 0; + unsigned char *fileData = LoadFileData(fileName, &dataSize); + + // Loading wave from memory data + if (fileData != NULL) wave = LoadWaveFromMemory(GetFileExtension(fileName), fileData, dataSize); + + RL_FREE(fileData); + + return wave; +} + +// Load wave from memory buffer, fileType refers to extension: i.e. ".wav" +// WARNING: File extension must be provided in lower-case +Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize) +{ + Wave wave = { 0 }; + + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if ((strcmp(fileType, ".wav") == 0) || (strcmp(fileType, ".WAV") == 0)) + { + drwav wav = { 0 }; + bool success = drwav_init_memory(&wav, fileData, dataSize, NULL); + + if (success) + { + wave.frameCount = (unsigned int)wav.totalPCMFrameCount; + wave.sampleRate = wav.sampleRate; + wave.sampleSize = 16; + wave.channels = wav.channels; + wave.data = (short *)RL_MALLOC(wave.frameCount*wave.channels*sizeof(short)); + + // NOTE: We are forcing conversion to 16bit sample size on reading + drwav_read_pcm_frames_s16(&wav, wav.totalPCMFrameCount, wave.data); + } + else TRACELOG(LOG_WARNING, "WAVE: Failed to load WAV data"); + + drwav_uninit(&wav); + } +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + else if ((strcmp(fileType, ".ogg") == 0) || (strcmp(fileType, ".OGG") == 0)) + { + stb_vorbis *oggData = stb_vorbis_open_memory((unsigned char *)fileData, dataSize, NULL, NULL); + + if (oggData != NULL) + { + stb_vorbis_info info = stb_vorbis_get_info(oggData); + + wave.sampleRate = info.sample_rate; + wave.sampleSize = 16; // By default, ogg data is 16 bit per sample (short) + wave.channels = info.channels; + wave.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples(oggData); // NOTE: It returns frames! + wave.data = (short *)RL_MALLOC(wave.frameCount*wave.channels*sizeof(short)); + + // NOTE: Get the number of samples to process (be careful! we ask for number of shorts, not bytes!) + stb_vorbis_get_samples_short_interleaved(oggData, info.channels, (short *)wave.data, wave.frameCount*wave.channels); + stb_vorbis_close(oggData); + } + else TRACELOG(LOG_WARNING, "WAVE: Failed to load OGG data"); + } +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + else if ((strcmp(fileType, ".mp3") == 0) || (strcmp(fileType, ".MP3") == 0)) + { + drmp3_config config = { 0 }; + unsigned long long int totalFrameCount = 0; + + // NOTE: We are forcing conversion to 32bit float sample size on reading + wave.data = drmp3_open_memory_and_read_pcm_frames_f32(fileData, dataSize, &config, &totalFrameCount, NULL); + wave.sampleSize = 32; + + if (wave.data != NULL) + { + wave.channels = config.channels; + wave.sampleRate = config.sampleRate; + wave.frameCount = (int)totalFrameCount; + } + else TRACELOG(LOG_WARNING, "WAVE: Failed to load MP3 data"); + + } +#endif +#if defined(SUPPORT_FILEFORMAT_QOA) + else if ((strcmp(fileType, ".qoa") == 0) || (strcmp(fileType, ".QOA") == 0)) + { + qoa_desc qoa = { 0 }; + + // NOTE: Returned sample data is always 16 bit? + wave.data = qoa_decode(fileData, dataSize, &qoa); + wave.sampleSize = 16; + + if (wave.data != NULL) + { + wave.channels = qoa.channels; + wave.sampleRate = qoa.samplerate; + wave.frameCount = qoa.samples; + } + else TRACELOG(LOG_WARNING, "WAVE: Failed to load QOA data"); + + } +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if ((strcmp(fileType, ".flac") == 0) || (strcmp(fileType, ".FLAC") == 0)) + { + unsigned long long int totalFrameCount = 0; + + // NOTE: We are forcing conversion to 16bit sample size on reading + wave.data = drflac_open_memory_and_read_pcm_frames_s16(fileData, dataSize, &wave.channels, &wave.sampleRate, &totalFrameCount, NULL); + wave.sampleSize = 16; + + if (wave.data != NULL) wave.frameCount = (unsigned int)totalFrameCount; + else TRACELOG(LOG_WARNING, "WAVE: Failed to load FLAC data"); + } +#endif + else TRACELOG(LOG_WARNING, "WAVE: Data format not supported"); + + TRACELOG(LOG_INFO, "WAVE: Data loaded successfully (%i Hz, %i bit, %i channels)", wave.sampleRate, wave.sampleSize, wave.channels); + + return wave; +} + +// Checks if wave data is ready +bool IsWaveReady(Wave wave) +{ + return ((wave.data != NULL) && // Validate wave data available + (wave.frameCount > 0) && // Validate frame count + (wave.sampleRate > 0) && // Validate sample rate is supported + (wave.sampleSize > 0) && // Validate sample size is supported + (wave.channels > 0)); // Validate number of channels supported +} + +// Load sound from file +// NOTE: The entire file is loaded to memory to be played (no-streaming) +Sound LoadSound(const char *fileName) +{ + Wave wave = LoadWave(fileName); + + Sound sound = LoadSoundFromWave(wave); + + UnloadWave(wave); // Sound is loaded, we can unload wave + + return sound; +} + +// Load sound from wave data +// NOTE: Wave data must be unallocated manually +Sound LoadSoundFromWave(Wave wave) +{ + Sound sound = { 0 }; + + if (wave.data != NULL) + { + // When using miniaudio we need to do our own mixing. + // To simplify this we need convert the format of each sound to be consistent with + // the format used to open the playback AUDIO.System.device. We can do this two ways: + // + // 1) Convert the whole sound in one go at load time (here). + // 2) Convert the audio data in chunks at mixing time. + // + // First option has been selected, format conversion is done on the loading stage. + // The downside is that it uses more memory if the original sound is u8 or s16. + ma_format formatIn = ((wave.sampleSize == 8)? ma_format_u8 : ((wave.sampleSize == 16)? ma_format_s16 : ma_format_f32)); + ma_uint32 frameCountIn = wave.frameCount; + + ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, NULL, frameCountIn, formatIn, wave.channels, wave.sampleRate); + if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed to get frame count for format conversion"); + + AudioBuffer *audioBuffer = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, frameCount, AUDIO_BUFFER_USAGE_STATIC); + if (audioBuffer == NULL) + { + TRACELOG(LOG_WARNING, "SOUND: Failed to create buffer"); + return sound; // early return to avoid dereferencing the audioBuffer null pointer + } + + frameCount = (ma_uint32)ma_convert_frames(audioBuffer->data, frameCount, AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, wave.data, frameCountIn, formatIn, wave.channels, wave.sampleRate); + if (frameCount == 0) TRACELOG(LOG_WARNING, "SOUND: Failed format conversion"); + + sound.frameCount = frameCount; + sound.stream.sampleRate = AUDIO.System.device.sampleRate; + sound.stream.sampleSize = 32; + sound.stream.channels = AUDIO_DEVICE_CHANNELS; + sound.stream.buffer = audioBuffer; + } + + return sound; +} + +// Clone sound from existing sound data, clone does not own wave data +// NOTE: Wave data must be unallocated manually and will be shared across all clones +Sound LoadSoundAlias(Sound source) +{ + Sound sound = { 0 }; + + if (source.stream.buffer->data != NULL) + { + AudioBuffer* audioBuffer = LoadAudioBuffer(AUDIO_DEVICE_FORMAT, AUDIO_DEVICE_CHANNELS, AUDIO.System.device.sampleRate, 0, AUDIO_BUFFER_USAGE_STATIC); + if (audioBuffer == NULL) + { + TRACELOG(LOG_WARNING, "SOUND: Failed to create buffer"); + return sound; // early return to avoid dereferencing the audioBuffer null pointer + } + audioBuffer->sizeInFrames = source.stream.buffer->sizeInFrames; + audioBuffer->volume = source.stream.buffer->volume; + audioBuffer->data = source.stream.buffer->data; + + sound.frameCount = source.frameCount; + sound.stream.sampleRate = AUDIO.System.device.sampleRate; + sound.stream.sampleSize = 32; + sound.stream.channels = AUDIO_DEVICE_CHANNELS; + sound.stream.buffer = audioBuffer; + } + + return sound; +} + + +// Checks if a sound is ready +bool IsSoundReady(Sound sound) +{ + return ((sound.frameCount > 0) && // Validate frame count + (sound.stream.buffer != NULL) && // Validate stream buffer + (sound.stream.sampleRate > 0) && // Validate sample rate is supported + (sound.stream.sampleSize > 0) && // Validate sample size is supported + (sound.stream.channels > 0)); // Validate number of channels supported +} + +// Unload wave data +void UnloadWave(Wave wave) +{ + RL_FREE(wave.data); + //TRACELOG(LOG_INFO, "WAVE: Unloaded wave data from RAM"); +} + +// Unload sound +void UnloadSound(Sound sound) +{ + UnloadAudioBuffer(sound.stream.buffer); + //TRACELOG(LOG_INFO, "SOUND: Unloaded sound data from RAM"); +} + +void UnloadSoundAlias(Sound alias) +{ + // untrack and unload just the sound buffer, not the sample data, it is shared with the source for the alias + if (alias.stream.buffer != NULL) + { + ma_data_converter_uninit(&alias.stream.buffer->converter, NULL); + UntrackAudioBuffer(alias.stream.buffer); + RL_FREE(alias.stream.buffer); + } +} + +// Update sound buffer with new data +void UpdateSound(Sound sound, const void *data, int frameCount) +{ + if (sound.stream.buffer != NULL) + { + StopAudioBuffer(sound.stream.buffer); + + // TODO: May want to lock/unlock this since this data buffer is read at mixing time + memcpy(sound.stream.buffer->data, data, frameCount*ma_get_bytes_per_frame(sound.stream.buffer->converter.formatIn, sound.stream.buffer->converter.channelsIn)); + } +} + +// Export wave data to file +bool ExportWave(Wave wave, const char *fileName) +{ + bool success = false; + + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if (IsFileExtension(fileName, ".wav")) + { + drwav wav = { 0 }; + drwav_data_format format = { 0 }; + format.container = drwav_container_riff; + if (wave.sampleSize == 32) format.format = DR_WAVE_FORMAT_IEEE_FLOAT; + else format.format = DR_WAVE_FORMAT_PCM; + format.channels = wave.channels; + format.sampleRate = wave.sampleRate; + format.bitsPerSample = wave.sampleSize; + + void *fileData = NULL; + size_t fileDataSize = 0; + success = drwav_init_memory_write(&wav, &fileData, &fileDataSize, &format, NULL); + if (success) success = (int)drwav_write_pcm_frames(&wav, wave.frameCount, wave.data); + drwav_result result = drwav_uninit(&wav); + + if (result == DRWAV_SUCCESS) success = SaveFileData(fileName, (unsigned char *)fileData, (unsigned int)fileDataSize); + + drwav_free(fileData, NULL); + } +#endif +#if defined(SUPPORT_FILEFORMAT_QOA) + else if (IsFileExtension(fileName, ".qoa")) + { + if (wave.sampleSize == 16) + { + qoa_desc qoa = { 0 }; + qoa.channels = wave.channels; + qoa.samplerate = wave.sampleRate; + qoa.samples = wave.frameCount; + + int bytesWritten = qoa_write(fileName, wave.data, &qoa); + if (bytesWritten > 0) success = true; + } + else TRACELOG(LOG_WARNING, "AUDIO: Wave data must be 16 bit per sample for QOA format export"); + } +#endif + else if (IsFileExtension(fileName, ".raw")) + { + // Export raw sample data (without header) + // NOTE: It's up to the user to track wave parameters + success = SaveFileData(fileName, wave.data, wave.frameCount*wave.channels*wave.sampleSize/8); + } + + if (success) TRACELOG(LOG_INFO, "FILEIO: [%s] Wave data exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export wave data", fileName); + + return success; +} + +// Export wave sample data to code (.h) +bool ExportWaveAsCode(Wave wave, const char *fileName) +{ + bool success = false; + +#ifndef TEXT_BYTES_PER_LINE + #define TEXT_BYTES_PER_LINE 20 +#endif + + int waveDataSize = wave.frameCount*wave.channels*wave.sampleSize/8; + + // NOTE: Text data buffer size is estimated considering wave data size in bytes + // and requiring 6 char bytes for every byte: "0x00, " + char *txtData = (char *)RL_CALLOC(waveDataSize*6 + 2000, sizeof(char)); + + int byteCount = 0; + byteCount += sprintf(txtData + byteCount, "\n//////////////////////////////////////////////////////////////////////////////////\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// WaveAsCode exporter v1.1 - Wave data exported as an array of bytes //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// more info and bugs-report: github.com/raysan5/raylib //\n"); + byteCount += sprintf(txtData + byteCount, "// feedback and support: ray[at]raylib.com //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "// Copyright (c) 2018-2023 Ramon Santamaria (@raysan5) //\n"); + byteCount += sprintf(txtData + byteCount, "// //\n"); + byteCount += sprintf(txtData + byteCount, "//////////////////////////////////////////////////////////////////////////////////\n\n"); + + // Get file name from path and convert variable name to uppercase + char varFileName[256] = { 0 }; + strcpy(varFileName, GetFileNameWithoutExt(fileName)); + for (int i = 0; varFileName[i] != '\0'; i++) if (varFileName[i] >= 'a' && varFileName[i] <= 'z') { varFileName[i] = varFileName[i] - 32; } + + //Add wave information + byteCount += sprintf(txtData + byteCount, "// Wave data information\n"); + byteCount += sprintf(txtData + byteCount, "#define %s_FRAME_COUNT %u\n", varFileName, wave.frameCount); + byteCount += sprintf(txtData + byteCount, "#define %s_SAMPLE_RATE %u\n", varFileName, wave.sampleRate); + byteCount += sprintf(txtData + byteCount, "#define %s_SAMPLE_SIZE %u\n", varFileName, wave.sampleSize); + byteCount += sprintf(txtData + byteCount, "#define %s_CHANNELS %u\n\n", varFileName, wave.channels); + + // Write wave data as an array of values + // Wave data is exported as byte array for 8/16bit and float array for 32bit float data + // NOTE: Frame data exported is channel-interlaced: frame01[sampleChannel1, sampleChannel2, ...], frame02[], frame03[] + if (wave.sampleSize == 32) + { + byteCount += sprintf(txtData + byteCount, "static float %s_DATA[%i] = {\n", varFileName, waveDataSize/4); + for (int i = 1; i < waveDataSize/4; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "%.4ff,\n " : "%.4ff, "), ((float *)wave.data)[i - 1]); + byteCount += sprintf(txtData + byteCount, "%.4ff };\n", ((float *)wave.data)[waveDataSize/4 - 1]); + } + else + { + byteCount += sprintf(txtData + byteCount, "static unsigned char %s_DATA[%i] = { ", varFileName, waveDataSize); + for (int i = 1; i < waveDataSize; i++) byteCount += sprintf(txtData + byteCount, ((i%TEXT_BYTES_PER_LINE == 0)? "0x%x,\n " : "0x%x, "), ((unsigned char *)wave.data)[i - 1]); + byteCount += sprintf(txtData + byteCount, "0x%x };\n", ((unsigned char *)wave.data)[waveDataSize - 1]); + } + + // NOTE: Text data length exported is determined by '\0' (NULL) character + success = SaveFileText(fileName, txtData); + + RL_FREE(txtData); + + if (success != 0) TRACELOG(LOG_INFO, "FILEIO: [%s] Wave as code exported successfully", fileName); + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to export wave as code", fileName); + + return success; +} + +// Play a sound +void PlaySound(Sound sound) +{ + PlayAudioBuffer(sound.stream.buffer); +} + +// Pause a sound +void PauseSound(Sound sound) +{ + PauseAudioBuffer(sound.stream.buffer); +} + +// Resume a paused sound +void ResumeSound(Sound sound) +{ + ResumeAudioBuffer(sound.stream.buffer); +} + +// Stop reproducing a sound +void StopSound(Sound sound) +{ + StopAudioBuffer(sound.stream.buffer); +} + +// Check if a sound is playing +bool IsSoundPlaying(Sound sound) +{ + return IsAudioBufferPlaying(sound.stream.buffer); +} + +// Set volume for a sound +void SetSoundVolume(Sound sound, float volume) +{ + SetAudioBufferVolume(sound.stream.buffer, volume); +} + +// Set pitch for a sound +void SetSoundPitch(Sound sound, float pitch) +{ + SetAudioBufferPitch(sound.stream.buffer, pitch); +} + +// Set pan for a sound +void SetSoundPan(Sound sound, float pan) +{ + SetAudioBufferPan(sound.stream.buffer, pan); +} + +// Convert wave data to desired format +void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels) +{ + ma_format formatIn = ((wave->sampleSize == 8)? ma_format_u8 : ((wave->sampleSize == 16)? ma_format_s16 : ma_format_f32)); + ma_format formatOut = ((sampleSize == 8)? ma_format_u8 : ((sampleSize == 16)? ma_format_s16 : ma_format_f32)); + + ma_uint32 frameCountIn = wave->frameCount; + ma_uint32 frameCount = (ma_uint32)ma_convert_frames(NULL, 0, formatOut, channels, sampleRate, NULL, frameCountIn, formatIn, wave->channels, wave->sampleRate); + + if (frameCount == 0) + { + TRACELOG(LOG_WARNING, "WAVE: Failed to get frame count for format conversion"); + return; + } + + void *data = RL_MALLOC(frameCount*channels*(sampleSize/8)); + + frameCount = (ma_uint32)ma_convert_frames(data, frameCount, formatOut, channels, sampleRate, wave->data, frameCountIn, formatIn, wave->channels, wave->sampleRate); + if (frameCount == 0) + { + TRACELOG(LOG_WARNING, "WAVE: Failed format conversion"); + return; + } + + wave->frameCount = frameCount; + wave->sampleSize = sampleSize; + wave->sampleRate = sampleRate; + wave->channels = channels; + + RL_FREE(wave->data); + wave->data = data; +} + +// Copy a wave to a new wave +Wave WaveCopy(Wave wave) +{ + Wave newWave = { 0 }; + + newWave.data = RL_MALLOC(wave.frameCount*wave.channels*wave.sampleSize/8); + + if (newWave.data != NULL) + { + // NOTE: Size must be provided in bytes + memcpy(newWave.data, wave.data, wave.frameCount*wave.channels*wave.sampleSize/8); + + newWave.frameCount = wave.frameCount; + newWave.sampleRate = wave.sampleRate; + newWave.sampleSize = wave.sampleSize; + newWave.channels = wave.channels; + } + + return newWave; +} + +// Crop a wave to defined samples range +// NOTE: Security check in case of out-of-range +void WaveCrop(Wave *wave, int initSample, int finalSample) +{ + if ((initSample >= 0) && (initSample < finalSample) && ((unsigned int)finalSample < (wave->frameCount*wave->channels))) + { + int sampleCount = finalSample - initSample; + + void *data = RL_MALLOC(sampleCount*wave->sampleSize/8); + + memcpy(data, (unsigned char *)wave->data + (initSample*wave->channels*wave->sampleSize/8), sampleCount*wave->sampleSize/8); + + RL_FREE(wave->data); + wave->data = data; + } + else TRACELOG(LOG_WARNING, "WAVE: Crop range out of bounds"); +} + +// Load samples data from wave as a floats array +// NOTE 1: Returned sample values are normalized to range [-1..1] +// NOTE 2: Sample data allocated should be freed with UnloadWaveSamples() +float *LoadWaveSamples(Wave wave) +{ + float *samples = (float *)RL_MALLOC(wave.frameCount*wave.channels*sizeof(float)); + + // NOTE: sampleCount is the total number of interlaced samples (including channels) + + for (unsigned int i = 0; i < wave.frameCount*wave.channels; i++) + { + if (wave.sampleSize == 8) samples[i] = (float)(((unsigned char *)wave.data)[i] - 127)/256.0f; + else if (wave.sampleSize == 16) samples[i] = (float)(((short *)wave.data)[i])/32767.0f; + else if (wave.sampleSize == 32) samples[i] = ((float *)wave.data)[i]; + } + + return samples; +} + +// Unload samples data loaded with LoadWaveSamples() +void UnloadWaveSamples(float *samples) +{ + RL_FREE(samples); +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Music loading and stream playing +//---------------------------------------------------------------------------------- + +// Load music stream from file +Music LoadMusicStream(const char *fileName) +{ + Music music = { 0 }; + bool musicLoaded = false; + + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if (IsFileExtension(fileName, ".wav")) + { + drwav *ctxWav = RL_CALLOC(1, sizeof(drwav)); + bool success = drwav_init_file(ctxWav, fileName, NULL); + + music.ctxType = MUSIC_AUDIO_WAV; + music.ctxData = ctxWav; + + if (success) + { + int sampleSize = ctxWav->bitsPerSample; + if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() + + music.stream = LoadAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels); + music.frameCount = (unsigned int)ctxWav->totalPCMFrameCount; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + else if (IsFileExtension(fileName, ".ogg")) + { + // Open ogg audio stream + music.ctxType = MUSIC_AUDIO_OGG; + music.ctxData = stb_vorbis_open_filename(fileName, NULL, NULL); + + if (music.ctxData != NULL) + { + stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData); // Get Ogg file info + + // OGG bit rate defaults to 16 bit, it's enough for compressed format + music.stream = LoadAudioStream(info.sample_rate, 16, info.channels); + + // WARNING: It seems this function returns length in frames, not samples, so we multiply by channels + music.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData); + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + else if (IsFileExtension(fileName, ".mp3")) + { + drmp3 *ctxMp3 = RL_CALLOC(1, sizeof(drmp3)); + int result = drmp3_init_file(ctxMp3, fileName, NULL); + + music.ctxType = MUSIC_AUDIO_MP3; + music.ctxData = ctxMp3; + + if (result > 0) + { + music.stream = LoadAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels); + music.frameCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3); + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_QOA) + else if (IsFileExtension(fileName, ".qoa")) + { + qoaplay_desc *ctxQoa = qoaplay_open(fileName); + music.ctxType = MUSIC_AUDIO_QOA; + music.ctxData = ctxQoa; + + if (ctxQoa->file != NULL) + { + // NOTE: We are loading samples are 32bit float normalized data, so, + // we configure the output audio stream to also use float 32bit + music.stream = LoadAudioStream(ctxQoa->info.samplerate, 32, ctxQoa->info.channels); + music.frameCount = ctxQoa->info.samples; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (IsFileExtension(fileName, ".flac")) + { + music.ctxType = MUSIC_AUDIO_FLAC; + music.ctxData = drflac_open_file(fileName, NULL); + + if (music.ctxData != NULL) + { + drflac *ctxFlac = (drflac *)music.ctxData; + + music.stream = LoadAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); + music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + else if (IsFileExtension(fileName, ".xm")) + { + jar_xm_context_t *ctxXm = NULL; + int result = jar_xm_create_context_from_file(&ctxXm, AUDIO.System.device.sampleRate, fileName); + + music.ctxType = MUSIC_MODULE_XM; + music.ctxData = ctxXm; + + if (result == 0) // XM AUDIO.System.context created successfully + { + jar_xm_set_max_loop_count(ctxXm, 0); // Set infinite number of loops + + unsigned int bits = 32; + if (AUDIO_DEVICE_FORMAT == ma_format_s16) bits = 16; + else if (AUDIO_DEVICE_FORMAT == ma_format_u8) bits = 8; + + // NOTE: Only stereo is supported for XM + music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, bits, AUDIO_DEVICE_CHANNELS); + music.frameCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm); // NOTE: Always 2 channels (stereo) + music.looping = true; // Looping enabled by default + jar_xm_reset(ctxXm); // make sure we start at the beginning of the song + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + else if (IsFileExtension(fileName, ".mod")) + { + jar_mod_context_t *ctxMod = RL_CALLOC(1, sizeof(jar_mod_context_t)); + jar_mod_init(ctxMod); + int result = jar_mod_load_file(ctxMod, fileName); + + music.ctxType = MUSIC_MODULE_MOD; + music.ctxData = ctxMod; + + if (result > 0) + { + // NOTE: Only stereo is supported for MOD + music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, 16, AUDIO_DEVICE_CHANNELS); + music.frameCount = (unsigned int)jar_mod_max_samples(ctxMod); // NOTE: Always 2 channels (stereo) + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif + else TRACELOG(LOG_WARNING, "STREAM: [%s] File format not supported", fileName); + + if (!musicLoaded) + { + if (false) { } + #if defined(SUPPORT_FILEFORMAT_WAV) + else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_OGG) + else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_MP3) + else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } + #endif + #if defined(SUPPORT_FILEFORMAT_QOA) + else if (music.ctxType == MUSIC_AUDIO_QOA) qoaplay_close((qoaplay_desc *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_FLAC) + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); + #endif + #if defined(SUPPORT_FILEFORMAT_XM) + else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); + #endif + #if defined(SUPPORT_FILEFORMAT_MOD) + else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } + #endif + + music.ctxData = NULL; + TRACELOG(LOG_WARNING, "FILEIO: [%s] Music file could not be opened", fileName); + } + else + { + // Show some music stream info + TRACELOG(LOG_INFO, "FILEIO: [%s] Music file loaded successfully", fileName); + TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate); + TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize); + TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels, (music.stream.channels == 1)? "Mono" : (music.stream.channels == 2)? "Stereo" : "Multi"); + TRACELOG(LOG_INFO, " > Total frames: %i", music.frameCount); + } + + return music; +} + +// Load music stream from memory buffer, fileType refers to extension: i.e. ".wav" +// WARNING: File extension must be provided in lower-case +Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize) +{ + Music music = { 0 }; + bool musicLoaded = false; + + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if ((strcmp(fileType, ".wav") == 0) || (strcmp(fileType, ".WAV") == 0)) + { + drwav *ctxWav = RL_CALLOC(1, sizeof(drwav)); + + bool success = drwav_init_memory(ctxWav, (const void *)data, dataSize, NULL); + + music.ctxType = MUSIC_AUDIO_WAV; + music.ctxData = ctxWav; + + if (success) + { + int sampleSize = ctxWav->bitsPerSample; + if (ctxWav->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() + + music.stream = LoadAudioStream(ctxWav->sampleRate, sampleSize, ctxWav->channels); + music.frameCount = (unsigned int)ctxWav->totalPCMFrameCount; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + else if ((strcmp(fileType, ".ogg") == 0) || (strcmp(fileType, ".OGG") == 0)) + { + // Open ogg audio stream + music.ctxType = MUSIC_AUDIO_OGG; + //music.ctxData = stb_vorbis_open_filename(fileName, NULL, NULL); + music.ctxData = stb_vorbis_open_memory((const unsigned char *)data, dataSize, NULL, NULL); + + if (music.ctxData != NULL) + { + stb_vorbis_info info = stb_vorbis_get_info((stb_vorbis *)music.ctxData); // Get Ogg file info + + // OGG bit rate defaults to 16 bit, it's enough for compressed format + music.stream = LoadAudioStream(info.sample_rate, 16, info.channels); + + // WARNING: It seems this function returns length in frames, not samples, so we multiply by channels + music.frameCount = (unsigned int)stb_vorbis_stream_length_in_samples((stb_vorbis *)music.ctxData); + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + else if ((strcmp(fileType, ".mp3") == 0) || (strcmp(fileType, ".MP3") == 0)) + { + drmp3 *ctxMp3 = RL_CALLOC(1, sizeof(drmp3)); + int success = drmp3_init_memory(ctxMp3, (const void*)data, dataSize, NULL); + + music.ctxType = MUSIC_AUDIO_MP3; + music.ctxData = ctxMp3; + + if (success) + { + music.stream = LoadAudioStream(ctxMp3->sampleRate, 32, ctxMp3->channels); + music.frameCount = (unsigned int)drmp3_get_pcm_frame_count(ctxMp3); + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_QOA) + else if ((strcmp(fileType, ".qoa") == 0) || (strcmp(fileType, ".QOA") == 0)) + { + qoaplay_desc *ctxQoa = qoaplay_open_memory(data, dataSize); + music.ctxType = MUSIC_AUDIO_QOA; + music.ctxData = ctxQoa; + + if ((ctxQoa->file_data != NULL) && (ctxQoa->file_data_size != 0)) + { + // NOTE: We are loading samples are 32bit float normalized data, so, + // we configure the output audio stream to also use float 32bit + music.stream = LoadAudioStream(ctxQoa->info.samplerate, 32, ctxQoa->info.channels); + music.frameCount = ctxQoa->info.samples; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if ((strcmp(fileType, ".flac") == 0) || (strcmp(fileType, ".FLAC") == 0)) + { + music.ctxType = MUSIC_AUDIO_FLAC; + music.ctxData = drflac_open_memory((const void*)data, dataSize, NULL); + + if (music.ctxData != NULL) + { + drflac *ctxFlac = (drflac *)music.ctxData; + + music.stream = LoadAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); + music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount; + music.looping = true; // Looping enabled by default + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + else if ((strcmp(fileType, ".xm") == 0) || (strcmp(fileType, ".XM") == 0)) + { + jar_xm_context_t *ctxXm = NULL; + int result = jar_xm_create_context_safe(&ctxXm, (const char *)data, dataSize, AUDIO.System.device.sampleRate); + if (result == 0) // XM AUDIO.System.context created successfully + { + music.ctxType = MUSIC_MODULE_XM; + jar_xm_set_max_loop_count(ctxXm, 0); // Set infinite number of loops + + unsigned int bits = 32; + if (AUDIO_DEVICE_FORMAT == ma_format_s16) bits = 16; + else if (AUDIO_DEVICE_FORMAT == ma_format_u8) bits = 8; + + // NOTE: Only stereo is supported for XM + music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, bits, 2); + music.frameCount = (unsigned int)jar_xm_get_remaining_samples(ctxXm); // NOTE: Always 2 channels (stereo) + music.looping = true; // Looping enabled by default + jar_xm_reset(ctxXm); // make sure we start at the beginning of the song + + music.ctxData = ctxXm; + musicLoaded = true; + } + } +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + else if ((strcmp(fileType, ".mod") == 0) || (strcmp(fileType, ".MOD") == 0)) + { + jar_mod_context_t *ctxMod = (jar_mod_context_t *)RL_MALLOC(sizeof(jar_mod_context_t)); + int result = 0; + + jar_mod_init(ctxMod); + + // Copy data to allocated memory for default UnloadMusicStream + unsigned char *newData = (unsigned char *)RL_MALLOC(dataSize); + int it = dataSize/sizeof(unsigned char); + for (int i = 0; i < it; i++) newData[i] = data[i]; + + // Memory loaded version for jar_mod_load_file() + if (dataSize && (dataSize < 32*1024*1024)) + { + ctxMod->modfilesize = dataSize; + ctxMod->modfile = newData; + if (jar_mod_load(ctxMod, (void *)ctxMod->modfile, dataSize)) result = dataSize; + } + + if (result > 0) + { + music.ctxType = MUSIC_MODULE_MOD; + + // NOTE: Only stereo is supported for MOD + music.stream = LoadAudioStream(AUDIO.System.device.sampleRate, 16, 2); + music.frameCount = (unsigned int)jar_mod_max_samples(ctxMod); // NOTE: Always 2 channels (stereo) + music.looping = true; // Looping enabled by default + musicLoaded = true; + + music.ctxData = ctxMod; + musicLoaded = true; + } + } +#endif + else TRACELOG(LOG_WARNING, "STREAM: Data format not supported"); + + if (!musicLoaded) + { + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } +#endif +#if defined(SUPPORT_FILEFORMAT_QOA) + else if (music.ctxType == MUSIC_AUDIO_QOA) qoaplay_close((qoaplay_desc *)music.ctxData); +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } +#endif + + music.ctxData = NULL; + TRACELOG(LOG_WARNING, "FILEIO: Music data could not be loaded"); + } + else + { + // Show some music stream info + TRACELOG(LOG_INFO, "FILEIO: Music data loaded successfully"); + TRACELOG(LOG_INFO, " > Sample rate: %i Hz", music.stream.sampleRate); + TRACELOG(LOG_INFO, " > Sample size: %i bits", music.stream.sampleSize); + TRACELOG(LOG_INFO, " > Channels: %i (%s)", music.stream.channels, (music.stream.channels == 1)? "Mono" : (music.stream.channels == 2)? "Stereo" : "Multi"); + TRACELOG(LOG_INFO, " > Total frames: %i", music.frameCount); + } + + return music; +} + +// Checks if a music stream is ready +bool IsMusicReady(Music music) +{ + return ((music.ctxData != NULL) && // Validate context loaded + (music.frameCount > 0) && // Validate audio frame count + (music.stream.sampleRate > 0) && // Validate sample rate is supported + (music.stream.sampleSize > 0) && // Validate sample size is supported + (music.stream.channels > 0)); // Validate number of channels supported +} + +// Unload music stream +void UnloadMusicStream(Music music) +{ + UnloadAudioStream(music.stream); + + if (music.ctxData != NULL) + { + if (false) { } +#if defined(SUPPORT_FILEFORMAT_WAV) + else if (music.ctxType == MUSIC_AUDIO_WAV) drwav_uninit((drwav *)music.ctxData); +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + else if (music.ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close((stb_vorbis *)music.ctxData); +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + else if (music.ctxType == MUSIC_AUDIO_MP3) { drmp3_uninit((drmp3 *)music.ctxData); RL_FREE(music.ctxData); } +#endif +#if defined(SUPPORT_FILEFORMAT_QOA) + else if (music.ctxType == MUSIC_AUDIO_QOA) qoaplay_close((qoaplay_desc *)music.ctxData); +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + else if (music.ctxType == MUSIC_AUDIO_FLAC) drflac_free((drflac *)music.ctxData, NULL); +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + else if (music.ctxType == MUSIC_MODULE_XM) jar_xm_free_context((jar_xm_context_t *)music.ctxData); +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + else if (music.ctxType == MUSIC_MODULE_MOD) { jar_mod_unload((jar_mod_context_t *)music.ctxData); RL_FREE(music.ctxData); } +#endif + } +} + +// Start music playing (open stream) +void PlayMusicStream(Music music) +{ + if (music.stream.buffer != NULL) + { + // For music streams, we need to make sure we maintain the frame cursor position + // This is a hack for this section of code in UpdateMusicStream() + // NOTE: In case window is minimized, music stream is stopped, just make sure to + // play again on window restore: if (IsMusicStreamPlaying(music)) PlayMusicStream(music); + ma_uint32 frameCursorPos = music.stream.buffer->frameCursorPos; + PlayAudioStream(music.stream); // WARNING: This resets the cursor position. + music.stream.buffer->frameCursorPos = frameCursorPos; + } +} + +// Pause music playing +void PauseMusicStream(Music music) +{ + PauseAudioStream(music.stream); +} + +// Resume music playing +void ResumeMusicStream(Music music) +{ + ResumeAudioStream(music.stream); +} + +// Stop music playing (close stream) +void StopMusicStream(Music music) +{ + StopAudioStream(music.stream); + + switch (music.ctxType) + { +#if defined(SUPPORT_FILEFORMAT_WAV) + case MUSIC_AUDIO_WAV: drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); break; +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + case MUSIC_AUDIO_OGG: stb_vorbis_seek_start((stb_vorbis *)music.ctxData); break; +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + case MUSIC_AUDIO_MP3: drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData); break; +#endif +#if defined(SUPPORT_FILEFORMAT_QOA) + case MUSIC_AUDIO_QOA: qoaplay_rewind((qoaplay_desc *)music.ctxData); break; +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + case MUSIC_AUDIO_FLAC: drflac__seek_to_first_frame((drflac *)music.ctxData); break; +#endif +#if defined(SUPPORT_FILEFORMAT_XM) + case MUSIC_MODULE_XM: jar_xm_reset((jar_xm_context_t *)music.ctxData); break; +#endif +#if defined(SUPPORT_FILEFORMAT_MOD) + case MUSIC_MODULE_MOD: jar_mod_seek_start((jar_mod_context_t *)music.ctxData); break; +#endif + default: break; + } +} + +// Seek music to a certain position (in seconds) +void SeekMusicStream(Music music, float position) +{ + // Seeking is not supported in module formats + if ((music.ctxType == MUSIC_MODULE_XM) || (music.ctxType == MUSIC_MODULE_MOD)) return; + + unsigned int positionInFrames = (unsigned int)(position*music.stream.sampleRate); + + switch (music.ctxType) + { +#if defined(SUPPORT_FILEFORMAT_WAV) + case MUSIC_AUDIO_WAV: drwav_seek_to_pcm_frame((drwav *)music.ctxData, positionInFrames); break; +#endif +#if defined(SUPPORT_FILEFORMAT_OGG) + case MUSIC_AUDIO_OGG: stb_vorbis_seek_frame((stb_vorbis *)music.ctxData, positionInFrames); break; +#endif +#if defined(SUPPORT_FILEFORMAT_MP3) + case MUSIC_AUDIO_MP3: drmp3_seek_to_pcm_frame((drmp3 *)music.ctxData, positionInFrames); break; +#endif +#if defined(SUPPORT_FILEFORMAT_QOA) + case MUSIC_AUDIO_QOA: + { + int qoaFrame = positionInFrames/QOA_FRAME_LEN; + qoaplay_seek_frame((qoaplay_desc *)music.ctxData, qoaFrame); // Seeks to QOA frame, not PCM frame + + // We need to compute QOA frame number and update positionInFrames + positionInFrames = ((qoaplay_desc *)music.ctxData)->sample_position; + } break; +#endif +#if defined(SUPPORT_FILEFORMAT_FLAC) + case MUSIC_AUDIO_FLAC: drflac_seek_to_pcm_frame((drflac *)music.ctxData, positionInFrames); break; +#endif + default: break; + } + + music.stream.buffer->framesProcessed = positionInFrames; +} + +// Update (re-fill) music buffers if data already processed +void UpdateMusicStream(Music music) +{ + if (music.stream.buffer == NULL) return; + + unsigned int subBufferSizeInFrames = music.stream.buffer->sizeInFrames/2; + + // On first call of this function we lazily pre-allocated a temp buffer to read audio files/memory data in + int frameSize = music.stream.channels*music.stream.sampleSize/8; + unsigned int pcmSize = subBufferSizeInFrames*frameSize; + + if (AUDIO.System.pcmBufferSize < pcmSize) + { + RL_FREE(AUDIO.System.pcmBuffer); + AUDIO.System.pcmBuffer = RL_CALLOC(1, pcmSize); + AUDIO.System.pcmBufferSize = pcmSize; + } + + // Check both sub-buffers to check if they require refilling + for (int i = 0; i < 2; i++) + { + if ((music.stream.buffer != NULL) && !music.stream.buffer->isSubBufferProcessed[i]) continue; // No refilling required, move to next sub-buffer + + unsigned int framesLeft = music.frameCount - music.stream.buffer->framesProcessed; // Frames left to be processed + unsigned int framesToStream = 0; // Total frames to be streamed + + if ((framesLeft >= subBufferSizeInFrames) || music.looping) framesToStream = subBufferSizeInFrames; + else framesToStream = framesLeft; + + int frameCountStillNeeded = framesToStream; + int frameCountReadTotal = 0; + + switch (music.ctxType) + { + #if defined(SUPPORT_FILEFORMAT_WAV) + case MUSIC_AUDIO_WAV: + { + if (music.stream.sampleSize == 16) + { + while (true) + { + int frameCountRead = (int)drwav_read_pcm_frames_s16((drwav *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize)); + frameCountReadTotal += frameCountRead; + frameCountStillNeeded -= frameCountRead; + if (frameCountStillNeeded == 0) break; + else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); + } + } + else if (music.stream.sampleSize == 32) + { + while (true) + { + int frameCountRead = (int)drwav_read_pcm_frames_f32((drwav *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize)); + frameCountReadTotal += frameCountRead; + frameCountStillNeeded -= frameCountRead; + if (frameCountStillNeeded == 0) break; + else drwav_seek_to_first_pcm_frame((drwav *)music.ctxData); + } + } + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_OGG) + case MUSIC_AUDIO_OGG: + { + while (true) + { + int frameCountRead = stb_vorbis_get_samples_short_interleaved((stb_vorbis *)music.ctxData, music.stream.channels, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize), frameCountStillNeeded*music.stream.channels); + frameCountReadTotal += frameCountRead; + frameCountStillNeeded -= frameCountRead; + if (frameCountStillNeeded == 0) break; + else stb_vorbis_seek_start((stb_vorbis *)music.ctxData); + } + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_MP3) + case MUSIC_AUDIO_MP3: + { + while (true) + { + int frameCountRead = (int)drmp3_read_pcm_frames_f32((drmp3 *)music.ctxData, frameCountStillNeeded, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize)); + frameCountReadTotal += frameCountRead; + frameCountStillNeeded -= frameCountRead; + if (frameCountStillNeeded == 0) break; + else drmp3_seek_to_start_of_stream((drmp3 *)music.ctxData); + } + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_QOA) + case MUSIC_AUDIO_QOA: + { + unsigned int frameCountRead = qoaplay_decode((qoaplay_desc *)music.ctxData, (float *)AUDIO.System.pcmBuffer, framesToStream); + frameCountReadTotal += frameCountRead; + /* + while (true) + { + int frameCountRead = (int)qoaplay_decode((qoaplay_desc *)music.ctxData, (float *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize), frameCountStillNeeded); + frameCountReadTotal += frameCountRead; + frameCountStillNeeded -= frameCountRead; + if (frameCountStillNeeded == 0) break; + else qoaplay_rewind((qoaplay_desc *)music.ctxData); + } + */ + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_FLAC) + case MUSIC_AUDIO_FLAC: + { + while (true) + { + int frameCountRead = (int)drflac_read_pcm_frames_s16((drflac *)music.ctxData, frameCountStillNeeded, (short *)((char *)AUDIO.System.pcmBuffer + frameCountReadTotal*frameSize)); + frameCountReadTotal += frameCountRead; + frameCountStillNeeded -= frameCountRead; + if (frameCountStillNeeded == 0) break; + else drflac__seek_to_first_frame((drflac *)music.ctxData); + } + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_XM) + case MUSIC_MODULE_XM: + { + // NOTE: Internally we consider 2 channels generation, so sampleCount/2 + if (AUDIO_DEVICE_FORMAT == ma_format_f32) jar_xm_generate_samples((jar_xm_context_t *)music.ctxData, (float *)AUDIO.System.pcmBuffer, framesToStream); + else if (AUDIO_DEVICE_FORMAT == ma_format_s16) jar_xm_generate_samples_16bit((jar_xm_context_t *)music.ctxData, (short *)AUDIO.System.pcmBuffer, framesToStream); + else if (AUDIO_DEVICE_FORMAT == ma_format_u8) jar_xm_generate_samples_8bit((jar_xm_context_t *)music.ctxData, (char *)AUDIO.System.pcmBuffer, framesToStream); + //jar_xm_reset((jar_xm_context_t *)music.ctxData); + + } break; + #endif + #if defined(SUPPORT_FILEFORMAT_MOD) + case MUSIC_MODULE_MOD: + { + // NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples you want, so sampleCount/2 + jar_mod_fillbuffer((jar_mod_context_t *)music.ctxData, (short *)AUDIO.System.pcmBuffer, framesToStream, 0); + //jar_mod_seek_start((jar_mod_context_t *)music.ctxData); + + } break; + #endif + default: break; + } + + UpdateAudioStream(music.stream, AUDIO.System.pcmBuffer, framesToStream); + + music.stream.buffer->framesProcessed = music.stream.buffer->framesProcessed%music.frameCount; + + if (framesLeft <= subBufferSizeInFrames) + { + if (!music.looping) + { + // Streaming is ending, we filled latest frames from input + StopMusicStream(music); + return; + } + } + } + + // NOTE: In case window is minimized, music stream is stopped, + // just make sure to play again on window restore + if (IsMusicStreamPlaying(music)) PlayMusicStream(music); +} + +// Check if any music is playing +bool IsMusicStreamPlaying(Music music) +{ + return IsAudioStreamPlaying(music.stream); +} + +// Set volume for music +void SetMusicVolume(Music music, float volume) +{ + SetAudioStreamVolume(music.stream, volume); +} + +// Set pitch for music +void SetMusicPitch(Music music, float pitch) +{ + SetAudioBufferPitch(music.stream.buffer, pitch); +} + +// Set pan for a music +void SetMusicPan(Music music, float pan) +{ + SetAudioBufferPan(music.stream.buffer, pan); +} + +// Get music time length (in seconds) +float GetMusicTimeLength(Music music) +{ + float totalSeconds = 0.0f; + + totalSeconds = (float)music.frameCount/music.stream.sampleRate; + + return totalSeconds; +} + +// Get current music time played (in seconds) +float GetMusicTimePlayed(Music music) +{ + float secondsPlayed = 0.0f; + if (music.stream.buffer != NULL) + { +#if defined(SUPPORT_FILEFORMAT_XM) + if (music.ctxType == MUSIC_MODULE_XM) + { + uint64_t framesPlayed = 0; + + jar_xm_get_position(music.ctxData, NULL, NULL, NULL, &framesPlayed); + secondsPlayed = (float)framesPlayed/music.stream.sampleRate; + } + else +#endif + { + //ma_uint32 frameSizeInBytes = ma_get_bytes_per_sample(music.stream.buffer->dsp.formatConverterIn.config.formatIn)*music.stream.buffer->dsp.formatConverterIn.config.channels; + int framesProcessed = (int)music.stream.buffer->framesProcessed; + int subBufferSize = (int)music.stream.buffer->sizeInFrames/2; + int framesInFirstBuffer = music.stream.buffer->isSubBufferProcessed[0]? 0 : subBufferSize; + int framesInSecondBuffer = music.stream.buffer->isSubBufferProcessed[1]? 0 : subBufferSize; + int framesSentToMix = music.stream.buffer->frameCursorPos%subBufferSize; + int framesPlayed = (framesProcessed - framesInFirstBuffer - framesInSecondBuffer + framesSentToMix)%(int)music.frameCount; + if (framesPlayed < 0) framesPlayed += music.frameCount; + secondsPlayed = (float)framesPlayed/music.stream.sampleRate; + } + } + + return secondsPlayed; +} + +// Load audio stream (to stream audio pcm data) +AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels) +{ + AudioStream stream = { 0 }; + + stream.sampleRate = sampleRate; + stream.sampleSize = sampleSize; + stream.channels = channels; + + ma_format formatIn = ((stream.sampleSize == 8)? ma_format_u8 : ((stream.sampleSize == 16)? ma_format_s16 : ma_format_f32)); + + // The size of a streaming buffer must be at least double the size of a period + unsigned int periodSize = AUDIO.System.device.playback.internalPeriodSizeInFrames; + + // If the buffer is not set, compute one that would give us a buffer good enough for a decent frame rate + unsigned int subBufferSize = (AUDIO.Buffer.defaultSize == 0)? AUDIO.System.device.sampleRate/30 : AUDIO.Buffer.defaultSize; + + if (subBufferSize < periodSize) subBufferSize = periodSize; + + // Create a double audio buffer of defined size + stream.buffer = LoadAudioBuffer(formatIn, stream.channels, stream.sampleRate, subBufferSize*2, AUDIO_BUFFER_USAGE_STREAM); + + if (stream.buffer != NULL) + { + stream.buffer->looping = true; // Always loop for streaming buffers + TRACELOG(LOG_INFO, "STREAM: Initialized successfully (%i Hz, %i bit, %s)", stream.sampleRate, stream.sampleSize, (stream.channels == 1)? "Mono" : "Stereo"); + } + else TRACELOG(LOG_WARNING, "STREAM: Failed to load audio buffer, stream could not be created"); + + return stream; +} + +// Checks if an audio stream is ready +bool IsAudioStreamReady(AudioStream stream) +{ + return ((stream.buffer != NULL) && // Validate stream buffer + (stream.sampleRate > 0) && // Validate sample rate is supported + (stream.sampleSize > 0) && // Validate sample size is supported + (stream.channels > 0)); // Validate number of channels supported +} + +// Unload audio stream and free memory +void UnloadAudioStream(AudioStream stream) +{ + UnloadAudioBuffer(stream.buffer); + + TRACELOG(LOG_INFO, "STREAM: Unloaded audio stream data from RAM"); +} + +// Update audio stream buffers with data +// NOTE 1: Only updates one buffer of the stream source: dequeue -> update -> queue +// NOTE 2: To dequeue a buffer it needs to be processed: IsAudioStreamProcessed() +void UpdateAudioStream(AudioStream stream, const void *data, int frameCount) +{ + if (stream.buffer != NULL) + { + if (stream.buffer->isSubBufferProcessed[0] || stream.buffer->isSubBufferProcessed[1]) + { + ma_uint32 subBufferToUpdate = 0; + + if (stream.buffer->isSubBufferProcessed[0] && stream.buffer->isSubBufferProcessed[1]) + { + // Both buffers are available for updating. + // Update the first one and make sure the cursor is moved back to the front. + subBufferToUpdate = 0; + stream.buffer->frameCursorPos = 0; + } + else + { + // Just update whichever sub-buffer is processed. + subBufferToUpdate = (stream.buffer->isSubBufferProcessed[0])? 0 : 1; + } + + ma_uint32 subBufferSizeInFrames = stream.buffer->sizeInFrames/2; + unsigned char *subBuffer = stream.buffer->data + ((subBufferSizeInFrames*stream.channels*(stream.sampleSize/8))*subBufferToUpdate); + + // Total frames processed in buffer is always the complete size, filled with 0 if required + stream.buffer->framesProcessed += subBufferSizeInFrames; + + // Does this API expect a whole buffer to be updated in one go? + // Assuming so, but if not will need to change this logic. + if (subBufferSizeInFrames >= (ma_uint32)frameCount) + { + ma_uint32 framesToWrite = (ma_uint32)frameCount; + + ma_uint32 bytesToWrite = framesToWrite*stream.channels*(stream.sampleSize/8); + memcpy(subBuffer, data, bytesToWrite); + + // Any leftover frames should be filled with zeros. + ma_uint32 leftoverFrameCount = subBufferSizeInFrames - framesToWrite; + + if (leftoverFrameCount > 0) memset(subBuffer + bytesToWrite, 0, leftoverFrameCount*stream.channels*(stream.sampleSize/8)); + + stream.buffer->isSubBufferProcessed[subBufferToUpdate] = false; + } + else TRACELOG(LOG_WARNING, "STREAM: Attempting to write too many frames to buffer"); + } + else TRACELOG(LOG_WARNING, "STREAM: Buffer not available for updating"); + } +} + +// Check if any audio stream buffers requires refill +bool IsAudioStreamProcessed(AudioStream stream) +{ + if (stream.buffer == NULL) return false; + + return (stream.buffer->isSubBufferProcessed[0] || stream.buffer->isSubBufferProcessed[1]); +} + +// Play audio stream +void PlayAudioStream(AudioStream stream) +{ + PlayAudioBuffer(stream.buffer); +} + +// Play audio stream +void PauseAudioStream(AudioStream stream) +{ + PauseAudioBuffer(stream.buffer); +} + +// Resume audio stream playing +void ResumeAudioStream(AudioStream stream) +{ + ResumeAudioBuffer(stream.buffer); +} + +// Check if audio stream is playing. +bool IsAudioStreamPlaying(AudioStream stream) +{ + return IsAudioBufferPlaying(stream.buffer); +} + +// Stop audio stream +void StopAudioStream(AudioStream stream) +{ + StopAudioBuffer(stream.buffer); +} + +// Set volume for audio stream (1.0 is max level) +void SetAudioStreamVolume(AudioStream stream, float volume) +{ + SetAudioBufferVolume(stream.buffer, volume); +} + +// Set pitch for audio stream (1.0 is base level) +void SetAudioStreamPitch(AudioStream stream, float pitch) +{ + SetAudioBufferPitch(stream.buffer, pitch); +} + +// Set pan for audio stream +void SetAudioStreamPan(AudioStream stream, float pan) +{ + SetAudioBufferPan(stream.buffer, pan); +} + +// Default size for new audio streams +void SetAudioStreamBufferSizeDefault(int size) +{ + AUDIO.Buffer.defaultSize = size; +} + +// Audio thread callback to request new data +void SetAudioStreamCallback(AudioStream stream, AudioCallback callback) +{ + if (stream.buffer != NULL) stream.buffer->callback = callback; +} + +// Add processor to audio stream. Contrary to buffers, the order of processors is important. +// The new processor must be added at the end. As there aren't supposed to be a lot of processors attached to +// a given stream, we iterate through the list to find the end. That way we don't need a pointer to the last element. +void AttachAudioStreamProcessor(AudioStream stream, AudioCallback process) +{ + ma_mutex_lock(&AUDIO.System.lock); + + rAudioProcessor *processor = (rAudioProcessor *)RL_CALLOC(1, sizeof(rAudioProcessor)); + processor->process = process; + + rAudioProcessor *last = stream.buffer->processor; + + while (last && last->next) + { + last = last->next; + } + if (last) + { + processor->prev = last; + last->next = processor; + } + else stream.buffer->processor = processor; + + ma_mutex_unlock(&AUDIO.System.lock); +} + +// Remove processor from audio stream +void DetachAudioStreamProcessor(AudioStream stream, AudioCallback process) +{ + ma_mutex_lock(&AUDIO.System.lock); + + rAudioProcessor *processor = stream.buffer->processor; + + while (processor) + { + rAudioProcessor *next = processor->next; + rAudioProcessor *prev = processor->prev; + + if (processor->process == process) + { + if (stream.buffer->processor == processor) stream.buffer->processor = next; + if (prev) prev->next = next; + if (next) next->prev = prev; + + RL_FREE(processor); + } + + processor = next; + } + + ma_mutex_unlock(&AUDIO.System.lock); +} + +// Add processor to audio pipeline. Order of processors is important +// Works the same way as {Attach,Detach}AudioStreamProcessor() functions, except +// these two work on the already mixed output just before sending it to the sound hardware +void AttachAudioMixedProcessor(AudioCallback process) +{ + ma_mutex_lock(&AUDIO.System.lock); + + rAudioProcessor *processor = (rAudioProcessor *)RL_CALLOC(1, sizeof(rAudioProcessor)); + processor->process = process; + + rAudioProcessor *last = AUDIO.mixedProcessor; + + while (last && last->next) + { + last = last->next; + } + if (last) + { + processor->prev = last; + last->next = processor; + } + else AUDIO.mixedProcessor = processor; + + ma_mutex_unlock(&AUDIO.System.lock); +} + +// Remove processor from audio pipeline +void DetachAudioMixedProcessor(AudioCallback process) +{ + ma_mutex_lock(&AUDIO.System.lock); + + rAudioProcessor *processor = AUDIO.mixedProcessor; + + while (processor) + { + rAudioProcessor *next = processor->next; + rAudioProcessor *prev = processor->prev; + + if (processor->process == process) + { + if (AUDIO.mixedProcessor == processor) AUDIO.mixedProcessor = next; + if (prev) prev->next = next; + if (next) next->prev = prev; + + RL_FREE(processor); + } + + processor = next; + } + + ma_mutex_unlock(&AUDIO.System.lock); +} + + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +// Log callback function +static void OnLog(void *pUserData, ma_uint32 level, const char *pMessage) +{ + TRACELOG(LOG_WARNING, "miniaudio: %s", pMessage); // All log messages from miniaudio are errors +} + +// Reads audio data from an AudioBuffer object in internal format. +static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer, void *framesOut, ma_uint32 frameCount) +{ + // Using audio buffer callback + if (audioBuffer->callback) + { + audioBuffer->callback(framesOut, frameCount); + audioBuffer->framesProcessed += frameCount; + + return frameCount; + } + + ma_uint32 subBufferSizeInFrames = (audioBuffer->sizeInFrames > 1)? audioBuffer->sizeInFrames/2 : audioBuffer->sizeInFrames; + ma_uint32 currentSubBufferIndex = audioBuffer->frameCursorPos/subBufferSizeInFrames; + + if (currentSubBufferIndex > 1) return 0; + + // Another thread can update the processed state of buffers, so + // we just take a copy here to try and avoid potential synchronization problems + bool isSubBufferProcessed[2] = { 0 }; + isSubBufferProcessed[0] = audioBuffer->isSubBufferProcessed[0]; + isSubBufferProcessed[1] = audioBuffer->isSubBufferProcessed[1]; + + ma_uint32 frameSizeInBytes = ma_get_bytes_per_frame(audioBuffer->converter.formatIn, audioBuffer->converter.channelsIn); + + // Fill out every frame until we find a buffer that's marked as processed. Then fill the remainder with 0 + ma_uint32 framesRead = 0; + while (1) + { + // We break from this loop differently depending on the buffer's usage + // - For static buffers, we simply fill as much data as we can + // - For streaming buffers we only fill half of the buffer that are processed + // Unprocessed halves must keep their audio data in-tact + if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) + { + if (framesRead >= frameCount) break; + } + else + { + if (isSubBufferProcessed[currentSubBufferIndex]) break; + } + + ma_uint32 totalFramesRemaining = (frameCount - framesRead); + if (totalFramesRemaining == 0) break; + + ma_uint32 framesRemainingInOutputBuffer; + if (audioBuffer->usage == AUDIO_BUFFER_USAGE_STATIC) + { + framesRemainingInOutputBuffer = audioBuffer->sizeInFrames - audioBuffer->frameCursorPos; + } + else + { + ma_uint32 firstFrameIndexOfThisSubBuffer = subBufferSizeInFrames*currentSubBufferIndex; + framesRemainingInOutputBuffer = subBufferSizeInFrames - (audioBuffer->frameCursorPos - firstFrameIndexOfThisSubBuffer); + } + + ma_uint32 framesToRead = totalFramesRemaining; + if (framesToRead > framesRemainingInOutputBuffer) framesToRead = framesRemainingInOutputBuffer; + + memcpy((unsigned char *)framesOut + (framesRead*frameSizeInBytes), audioBuffer->data + (audioBuffer->frameCursorPos*frameSizeInBytes), framesToRead*frameSizeInBytes); + audioBuffer->frameCursorPos = (audioBuffer->frameCursorPos + framesToRead)%audioBuffer->sizeInFrames; + framesRead += framesToRead; + + // If we've read to the end of the buffer, mark it as processed + if (framesToRead == framesRemainingInOutputBuffer) + { + audioBuffer->isSubBufferProcessed[currentSubBufferIndex] = true; + isSubBufferProcessed[currentSubBufferIndex] = true; + + currentSubBufferIndex = (currentSubBufferIndex + 1)%2; + + // We need to break from this loop if we're not looping + if (!audioBuffer->looping) + { + StopAudioBuffer(audioBuffer); + break; + } + } + } + + // Zero-fill excess + ma_uint32 totalFramesRemaining = (frameCount - framesRead); + if (totalFramesRemaining > 0) + { + memset((unsigned char *)framesOut + (framesRead*frameSizeInBytes), 0, totalFramesRemaining*frameSizeInBytes); + + // For static buffers we can fill the remaining frames with silence for safety, but we don't want + // to report those frames as "read". The reason for this is that the caller uses the return value + // to know whether a non-looping sound has finished playback. + if (audioBuffer->usage != AUDIO_BUFFER_USAGE_STATIC) framesRead += totalFramesRemaining; + } + + return framesRead; +} + +// Reads audio data from an AudioBuffer object in device format. Returned data will be in a format appropriate for mixing. +static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, float *framesOut, ma_uint32 frameCount) +{ + // What's going on here is that we're continuously converting data from the AudioBuffer's internal format to the mixing format, which + // should be defined by the output format of the data converter. We do this until frameCount frames have been output. The important + // detail to remember here is that we never, ever attempt to read more input data than is required for the specified number of output + // frames. This can be achieved with ma_data_converter_get_required_input_frame_count(). + ma_uint8 inputBuffer[4096] = { 0 }; + ma_uint32 inputBufferFrameCap = sizeof(inputBuffer)/ma_get_bytes_per_frame(audioBuffer->converter.formatIn, audioBuffer->converter.channelsIn); + + ma_uint32 totalOutputFramesProcessed = 0; + while (totalOutputFramesProcessed < frameCount) + { + ma_uint64 outputFramesToProcessThisIteration = frameCount - totalOutputFramesProcessed; + ma_uint64 inputFramesToProcessThisIteration = 0; + + (void)ma_data_converter_get_required_input_frame_count(&audioBuffer->converter, outputFramesToProcessThisIteration, &inputFramesToProcessThisIteration); + if (inputFramesToProcessThisIteration > inputBufferFrameCap) + { + inputFramesToProcessThisIteration = inputBufferFrameCap; + } + + float *runningFramesOut = framesOut + (totalOutputFramesProcessed*audioBuffer->converter.channelsOut); + + /* At this point we can convert the data to our mixing format. */ + ma_uint64 inputFramesProcessedThisIteration = ReadAudioBufferFramesInInternalFormat(audioBuffer, inputBuffer, (ma_uint32)inputFramesToProcessThisIteration); /* Safe cast. */ + ma_uint64 outputFramesProcessedThisIteration = outputFramesToProcessThisIteration; + ma_data_converter_process_pcm_frames(&audioBuffer->converter, inputBuffer, &inputFramesProcessedThisIteration, runningFramesOut, &outputFramesProcessedThisIteration); + + totalOutputFramesProcessed += (ma_uint32)outputFramesProcessedThisIteration; /* Safe cast. */ + + if (inputFramesProcessedThisIteration < inputFramesToProcessThisIteration) + { + break; /* Ran out of input data. */ + } + + /* This should never be hit, but will add it here for safety. Ensures we get out of the loop when no input nor output frames are processed. */ + if (inputFramesProcessedThisIteration == 0 && outputFramesProcessedThisIteration == 0) + { + break; + } + } + + return totalOutputFramesProcessed; +} + +// Sending audio data to device callback function +// This function will be called when miniaudio needs more data +// NOTE: All the mixing takes place here +static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount) +{ + (void)pDevice; + + // Mixing is basically just an accumulation, we need to initialize the output buffer to 0 + memset(pFramesOut, 0, frameCount*pDevice->playback.channels*ma_get_bytes_per_sample(pDevice->playback.format)); + + // Using a mutex here for thread-safety which makes things not real-time + // This is unlikely to be necessary for this project, but may want to consider how you might want to avoid this + ma_mutex_lock(&AUDIO.System.lock); + { + for (AudioBuffer *audioBuffer = AUDIO.Buffer.first; audioBuffer != NULL; audioBuffer = audioBuffer->next) + { + // Ignore stopped or paused sounds + if (!audioBuffer->playing || audioBuffer->paused) continue; + + ma_uint32 framesRead = 0; + + while (1) + { + if (framesRead >= frameCount) break; + + // Just read as much data as we can from the stream + ma_uint32 framesToRead = (frameCount - framesRead); + + while (framesToRead > 0) + { + float tempBuffer[1024] = { 0 }; // Frames for stereo + + ma_uint32 framesToReadRightNow = framesToRead; + if (framesToReadRightNow > sizeof(tempBuffer)/sizeof(tempBuffer[0])/AUDIO_DEVICE_CHANNELS) + { + framesToReadRightNow = sizeof(tempBuffer)/sizeof(tempBuffer[0])/AUDIO_DEVICE_CHANNELS; + } + + ma_uint32 framesJustRead = ReadAudioBufferFramesInMixingFormat(audioBuffer, tempBuffer, framesToReadRightNow); + if (framesJustRead > 0) + { + float *framesOut = (float *)pFramesOut + (framesRead*AUDIO.System.device.playback.channels); + float *framesIn = tempBuffer; + + // Apply processors chain if defined + rAudioProcessor *processor = audioBuffer->processor; + while (processor) + { + processor->process(framesIn, framesJustRead); + processor = processor->next; + } + + MixAudioFrames(framesOut, framesIn, framesJustRead, audioBuffer); + + framesToRead -= framesJustRead; + framesRead += framesJustRead; + } + + if (!audioBuffer->playing) + { + framesRead = frameCount; + break; + } + + // If we weren't able to read all the frames we requested, break + if (framesJustRead < framesToReadRightNow) + { + if (!audioBuffer->looping) + { + StopAudioBuffer(audioBuffer); + break; + } + else + { + // Should never get here, but just for safety, + // move the cursor position back to the start and continue the loop + audioBuffer->frameCursorPos = 0; + continue; + } + } + } + + // If for some reason we weren't able to read every frame we'll need to break from the loop + // Not doing this could theoretically put us into an infinite loop + if (framesToRead > 0) break; + } + } + } + + rAudioProcessor *processor = AUDIO.mixedProcessor; + while (processor) + { + processor->process(pFramesOut, frameCount); + processor = processor->next; + } + + ma_mutex_unlock(&AUDIO.System.lock); +} + +// Main mixing function, pretty simple in this project, just an accumulation +// NOTE: framesOut is both an input and an output, it is initially filled with zeros outside of this function +static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer) +{ + const float localVolume = buffer->volume; + const ma_uint32 channels = AUDIO.System.device.playback.channels; + + if (channels == 2) // We consider panning + { + const float left = buffer->pan; + const float right = 1.0f - left; + + // Fast sine approximation in [0..1] for pan law: y = 0.5f*x*(3 - x*x); + const float levels[2] = { localVolume*0.5f*left*(3.0f - left*left), localVolume*0.5f*right*(3.0f - right*right) }; + + float *frameOut = framesOut; + const float *frameIn = framesIn; + + for (ma_uint32 frame = 0; frame < frameCount; frame++) + { + frameOut[0] += (frameIn[0]*levels[0]); + frameOut[1] += (frameIn[1]*levels[1]); + + frameOut += 2; + frameIn += 2; + } + } + else // We do not consider panning + { + for (ma_uint32 frame = 0; frame < frameCount; frame++) + { + for (ma_uint32 c = 0; c < channels; c++) + { + float *frameOut = framesOut + (frame*channels); + const float *frameIn = framesIn + (frame*channels); + + // Output accumulates input multiplied by volume to provided output (usually 0) + frameOut[c] += (frameIn[c]*localVolume); + } + } + } +} + +// Some required functions for audio standalone module version +#if defined(RAUDIO_STANDALONE) +// Check file extension +static bool IsFileExtension(const char *fileName, const char *ext) +{ + bool result = false; + const char *fileExt; + + if ((fileExt = strrchr(fileName, '.')) != NULL) + { + if (strcmp(fileExt, ext) == 0) result = true; + } + + return result; +} + +// Get pointer to extension for a filename string (includes the dot: .png) +static const char *GetFileExtension(const char *fileName) +{ + const char *dot = strrchr(fileName, '.'); + + if (!dot || dot == fileName) return NULL; + + return dot; +} + +// Load data from file into a buffer +static unsigned char *LoadFileData(const char *fileName, int *dataSize) +{ + unsigned char *data = NULL; + *dataSize = 0; + + if (fileName != NULL) + { + FILE *file = fopen(fileName, "rb"); + + if (file != NULL) + { + // WARNING: On binary streams SEEK_END could not be found, + // using fseek() and ftell() could not work in some (rare) cases + fseek(file, 0, SEEK_END); + int size = ftell(file); + fseek(file, 0, SEEK_SET); + + if (size > 0) + { + data = (unsigned char *)RL_MALLOC(size*sizeof(unsigned char)); + + // NOTE: fread() returns number of read elements instead of bytes, so we read [1 byte, size elements] + unsigned int count = (unsigned int)fread(data, sizeof(unsigned char), size, file); + *dataSize = count; + + if (count != size) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially loaded", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] File loaded successfully", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to read file", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); + + return data; +} + +// Save data to file from buffer +static bool SaveFileData(const char *fileName, void *data, int dataSize) +{ + if (fileName != NULL) + { + FILE *file = fopen(fileName, "wb"); + + if (file != NULL) + { + unsigned int count = (unsigned int)fwrite(data, sizeof(unsigned char), dataSize, file); + + if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write file", fileName); + else if (count != dataSize) TRACELOG(LOG_WARNING, "FILEIO: [%s] File partially written", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] File saved successfully", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open file", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); +} + +// Save text data to file (write), string must be '\0' terminated +static bool SaveFileText(const char *fileName, char *text) +{ + if (fileName != NULL) + { + FILE *file = fopen(fileName, "wt"); + + if (file != NULL) + { + int count = fprintf(file, "%s", text); + + if (count == 0) TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to write text file", fileName); + else TRACELOG(LOG_INFO, "FILEIO: [%s] Text file saved successfully", fileName); + + fclose(file); + } + else TRACELOG(LOG_WARNING, "FILEIO: [%s] Failed to open text file", fileName); + } + else TRACELOG(LOG_WARNING, "FILEIO: File name provided is not valid"); +} +#endif + +#undef AudioBuffer + +#endif // SUPPORT_MODULE_RAUDIO \ No newline at end of file diff --git a/ref_cpp b/ref_cpp new file mode 100644 index 0000000..45ee06c --- /dev/null +++ b/ref_cpp @@ -0,0 +1,9263 @@ +// sol2 + +// The MIT License (MIT) + +// Copyright (c) 2013-2022 Rapptz, ThePhD and contributors + +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef SOL_STACK_CORE_HPP +#define SOL_STACK_CORE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace sol { + namespace detail { + struct with_function_tag { }; + struct as_reference_tag { }; + template + struct as_pointer_tag { }; + template + struct as_value_tag { }; + template + struct as_unique_tag { }; + template + struct as_table_tag { }; + + template + inline constexpr bool is_tagged_v + = meta::is_specialization_of_v || meta::is_specialization_of_v || meta::is_specialization_of_v || meta::is_specialization_of_v || std::is_same_v || std::is_same_v; + + using lua_reg_table = luaL_Reg[64]; + + using unique_destructor = void (*)(void*); + using unique_tag = detail::inheritance_unique_cast_function; + + inline void* alloc_newuserdata(lua_State* L, std::size_t bytesize) { +#if SOL_LUA_VERSION_I_ >= 504 + return lua_newuserdatauv(L, bytesize, 1); +#else + return lua_newuserdata(L, bytesize); +#endif + } + + constexpr std::uintptr_t align(std::size_t alignment, std::uintptr_t ptr, std::size_t& space) { + // this handles arbitrary alignments... + // make this into a power-of-2-only? + // actually can't: this is a C++14-compatible framework, + // power of 2 alignment is C++17 + std::uintptr_t offby = static_cast(ptr % alignment); + std::uintptr_t padding = (alignment - offby) % alignment; + ptr += padding; + space -= padding; + return ptr; + } + + inline void* align(std::size_t alignment, void* ptr, std::size_t& space) { + return reinterpret_cast(align(alignment, reinterpret_cast(ptr), space)); + } + + constexpr std::uintptr_t align_one(std::size_t alignment, std::size_t size, std::uintptr_t ptr) { + std::size_t space = (std::numeric_limits::max)(); + return align(alignment, ptr, space) + size; + } + + template + constexpr std::size_t aligned_space_for(std::uintptr_t ptr) { + std::uintptr_t end = ptr; + ((end = align_one(alignof(Args), sizeof(Args), end)), ...); + return static_cast(end - ptr); + } + + template + constexpr std::size_t aligned_space_for() { + static_assert(sizeof...(Args) > 0); + + constexpr std::size_t max_arg_alignment = (std::max)({ alignof(Args)... }); + if constexpr (max_arg_alignment <= alignof(std::max_align_t)) { + // If all types are `good enough`, simply calculate alignment in case of the worst allocator + std::size_t worst_required_size = 0; + for (std::size_t ptr = 0; ptr < max_arg_alignment; ptr++) { + worst_required_size = (std::max)(worst_required_size, aligned_space_for(ptr)); + } + return worst_required_size; + } + else { + // For over-aligned types let's assume that every Arg in Args starts at the worst aligned address + return (aligned_space_for(0x1) + ...); + } + } + + inline void* align_usertype_pointer(void* ptr) { + using use_align = std::integral_constant::value > 1) +#endif + >; + if (!use_align::value) { + return ptr; + } + std::size_t space = (std::numeric_limits::max)(); + return align(std::alignment_of::value, ptr, space); + } + + template + void* align_usertype_unique_destructor(void* ptr) { + using use_align = std::integral_constant::value > 1) +#endif + >; + if (!pre_aligned) { + ptr = align_usertype_pointer(ptr); + } + if (!pre_shifted) { + ptr = static_cast(static_cast(ptr) + sizeof(void*)); + } + if (!use_align::value) { + return static_cast(static_cast(ptr) + 1); + } + std::size_t space = (std::numeric_limits::max)(); + return align(std::alignment_of::value, ptr, space); + } + + template + void* align_usertype_unique_tag(void* ptr) { + using use_align = std::integral_constant::value > 1) +#endif + >; + if (!pre_aligned) { + ptr = align_usertype_unique_destructor(ptr); + } + if (!pre_shifted) { + ptr = static_cast(static_cast(ptr) + sizeof(unique_destructor)); + } + if (!use_align::value) { + return ptr; + } + std::size_t space = (std::numeric_limits::max)(); + return align(std::alignment_of::value, ptr, space); + } + + template + void* align_usertype_unique(void* ptr) { + typedef std::integral_constant > 1) +#endif + > + use_align; + if (!pre_aligned) { + ptr = align_usertype_unique_tag(ptr); + } + if (!pre_shifted) { + ptr = static_cast(static_cast(ptr) + sizeof(unique_tag)); + } + if (!use_align::value) { + return ptr; + } + std::size_t space = (std::numeric_limits::max)(); + return align(std::alignment_of_v, ptr, space); + } + + template + void* align_user(void* ptr) { + typedef std::integral_constant > 1) +#endif + > + use_align; + if (!use_align::value) { + return ptr; + } + std::size_t space = (std::numeric_limits::max)(); + return align(std::alignment_of_v, ptr, space); + } + + template + T** usertype_allocate_pointer(lua_State* L) { + typedef std::integral_constant::value > 1) +#endif + > + use_align; + if (!use_align::value) { + T** pointerpointer = static_cast(alloc_newuserdata(L, sizeof(T*))); + return pointerpointer; + } + constexpr std::size_t initial_size = aligned_space_for(); + + std::size_t allocated_size = initial_size; + void* unadjusted = alloc_newuserdata(L, initial_size); + void* adjusted = align(std::alignment_of::value, unadjusted, allocated_size); + if (adjusted == nullptr) { + // trash allocator can burn in hell + lua_pop(L, 1); + // luaL_error(L, "if you are the one that wrote this allocator you should feel bad for doing a + // worse job than malloc/realloc and should go read some books, yeah?"); + luaL_error(L, "cannot properly align memory for '%s'", detail::demangle().data()); + } + return static_cast(adjusted); + } + + inline bool attempt_alloc(lua_State* L, std::size_t ptr_align, std::size_t ptr_size, std::size_t value_align, + std::size_t allocated_size, void*& pointer_adjusted, void*& data_adjusted) { + void* adjusted = alloc_newuserdata(L, allocated_size); + pointer_adjusted = align(ptr_align, adjusted, allocated_size); + if (pointer_adjusted == nullptr) { + lua_pop(L, 1); + return false; + } + // subtract size of what we're going to allocate there + allocated_size -= ptr_size; + adjusted = static_cast(static_cast(pointer_adjusted) + ptr_size); + data_adjusted = align(value_align, adjusted, allocated_size); + if (data_adjusted == nullptr) { + lua_pop(L, 1); + return false; + } + return true; + } + + inline bool attempt_alloc_unique(lua_State* L, std::size_t ptr_align, std::size_t ptr_size, std::size_t real_align, + std::size_t allocated_size, void*& pointer_adjusted, void*& dx_adjusted, void*& id_adjusted, void*& data_adjusted) { + void* adjusted = alloc_newuserdata(L, allocated_size); + pointer_adjusted = align(ptr_align, adjusted, allocated_size); + if (pointer_adjusted == nullptr) { + lua_pop(L, 1); + return false; + } + allocated_size -= ptr_size; + + adjusted = static_cast(static_cast(pointer_adjusted) + ptr_size); + dx_adjusted = align(std::alignment_of_v, adjusted, allocated_size); + if (dx_adjusted == nullptr) { + lua_pop(L, 1); + return false; + } + allocated_size -= sizeof(unique_destructor); + + adjusted = static_cast(static_cast(dx_adjusted) + sizeof(unique_destructor)); + + id_adjusted = align(std::alignment_of_v, adjusted, allocated_size); + if (id_adjusted == nullptr) { + lua_pop(L, 1); + return false; + } + allocated_size -= sizeof(unique_tag); + + adjusted = static_cast(static_cast(id_adjusted) + sizeof(unique_tag)); + data_adjusted = align(real_align, adjusted, allocated_size); + if (data_adjusted == nullptr) { + lua_pop(L, 1); + return false; + } + return true; + } + + template + T* usertype_allocate(lua_State* L) { + typedef std::integral_constant::value > 1 || std::alignment_of_v > 1) +#endif + > + use_align; + if (!use_align::value) { + T** pointerpointer = static_cast(alloc_newuserdata(L, sizeof(T*) + sizeof(T))); + T*& pointerreference = *pointerpointer; + T* allocationtarget = reinterpret_cast(pointerpointer + 1); + pointerreference = allocationtarget; + return allocationtarget; + } + + constexpr std::size_t initial_size = aligned_space_for(); + + void* pointer_adjusted; + void* data_adjusted; + bool result + = attempt_alloc(L, std::alignment_of_v, sizeof(T*), std::alignment_of_v, initial_size, pointer_adjusted, data_adjusted); + if (!result) { + if (pointer_adjusted == nullptr) { + luaL_error(L, "aligned allocation of userdata block (pointer section) for '%s' failed", detail::demangle().c_str()); + } + else { + luaL_error(L, "aligned allocation of userdata block (data section) for '%s' failed", detail::demangle().c_str()); + } + return nullptr; + } + + T** pointerpointer = reinterpret_cast(pointer_adjusted); + T*& pointerreference = *pointerpointer; + T* allocationtarget = reinterpret_cast(data_adjusted); + pointerreference = allocationtarget; + return allocationtarget; + } + + template + Real* usertype_unique_allocate(lua_State* L, T**& pref, unique_destructor*& dx, unique_tag*& id) { + typedef std::integral_constant::value > 1 || std::alignment_of::value > 1 || std::alignment_of::value > 1 + || std::alignment_of::value > 1) +#endif + > + use_align; + if (!use_align::value) { + pref = static_cast(alloc_newuserdata(L, sizeof(T*) + sizeof(detail::unique_destructor) + sizeof(unique_tag) + sizeof(Real))); + dx = static_cast(static_cast(pref + 1)); + id = static_cast(static_cast(dx + 1)); + Real* mem = static_cast(static_cast(id + 1)); + return mem; + } + + constexpr std::size_t initial_size = aligned_space_for(); + + void* pointer_adjusted = nullptr; + void* dx_adjusted = nullptr; + void* id_adjusted = nullptr; + void* data_adjusted = nullptr; + bool result = attempt_alloc_unique(L, + std::alignment_of_v, + sizeof(T*), + std::alignment_of_v, + initial_size, + pointer_adjusted, + dx_adjusted, + id_adjusted, + data_adjusted); + if (!result) { + if (pointer_adjusted == nullptr) { + luaL_error(L, "aligned allocation of userdata block (pointer section) for '%s' failed", detail::demangle().c_str()); + } + else if (dx_adjusted == nullptr) { + luaL_error(L, "aligned allocation of userdata block (deleter section) for '%s' failed", detail::demangle().c_str()); + } + else { + luaL_error(L, "aligned allocation of userdata block (data section) for '%s' failed", detail::demangle().c_str()); + } + return nullptr; + } + + pref = static_cast(pointer_adjusted); + dx = static_cast(dx_adjusted); + id = static_cast(id_adjusted); + Real* mem = static_cast(data_adjusted); + return mem; + } + + template + T* user_allocate(lua_State* L) { + typedef std::integral_constant > 1) +#endif + > + use_align; + if (!use_align::value) { + T* pointer = static_cast(alloc_newuserdata(L, sizeof(T))); + return pointer; + } + + constexpr std::size_t initial_size = aligned_space_for(); + + std::size_t allocated_size = initial_size; + void* unadjusted = alloc_newuserdata(L, allocated_size); + void* adjusted = align(std::alignment_of_v, unadjusted, allocated_size); + if (adjusted == nullptr) { + lua_pop(L, 1); + luaL_error(L, "cannot properly align memory for '%s'", detail::demangle().data()); + } + return static_cast(adjusted); + } + + template + int usertype_alloc_destroy(lua_State* L) noexcept { + void* memory = lua_touserdata(L, 1); + memory = align_usertype_pointer(memory); + T** pdata = static_cast(memory); + T* data = *pdata; + std::allocator alloc {}; + std::allocator_traits>::destroy(alloc, data); + return 0; + } + + template + int unique_destroy(lua_State* L) noexcept { + void* memory = lua_touserdata(L, 1); + memory = align_usertype_unique_destructor(memory); + unique_destructor& dx = *static_cast(memory); + memory = align_usertype_unique_tag(memory); + (dx)(memory); + return 0; + } + + template + int user_alloc_destroy(lua_State* L) noexcept { + void* memory = lua_touserdata(L, 1); + void* aligned_memory = align_user(memory); + T* typed_memory = static_cast(aligned_memory); + std::allocator alloc; + std::allocator_traits>::destroy(alloc, typed_memory); + return 0; + } + + template + void usertype_unique_alloc_destroy(void* memory) { + void* aligned_memory = align_usertype_unique(memory); + Real* typed_memory = static_cast(aligned_memory); + std::allocator alloc; + std::allocator_traits>::destroy(alloc, typed_memory); + } + + template + int cannot_destroy(lua_State* L) { + return luaL_error(L, + "cannot call the destructor for '%s': it is either hidden (protected/private) or removed with '= " + "delete' and thusly this type is being destroyed without properly destroying, invoking undefined " + "behavior: please bind a usertype and specify a custom destructor to define the behavior properly", + detail::demangle().data()); + } + + template + void reserve(T&, std::size_t) { + } + + template + void reserve(std::vector& vec, std::size_t hint) { + vec.reserve(hint); + } + + template + void reserve(std::basic_string& str, std::size_t hint) { + str.reserve(hint); + } + + inline bool property_always_true(meta_function) { + return true; + } + + struct properties_enrollment_allowed { + int& times_through; + std::bitset<64>& properties; + automagic_enrollments& enrollments; + + properties_enrollment_allowed(int& times_through_, std::bitset<64>& properties_, automagic_enrollments& enrollments_) + : times_through(times_through_), properties(properties_), enrollments(enrollments_) { + } + + bool operator()(meta_function mf) const { + bool p = properties[static_cast(mf)]; + if (times_through > 0) { + return p; + } + switch (mf) { + case meta_function::length: + return enrollments.length_operator && !p; + case meta_function::pairs: + return enrollments.pairs_operator && !p; + case meta_function::call: + return enrollments.call_operator && !p; + case meta_function::less_than: + return enrollments.less_than_operator && !p; + case meta_function::less_than_or_equal_to: + return enrollments.less_than_or_equal_to_operator && !p; + case meta_function::equal_to: + return enrollments.equal_to_operator && !p; + default: + break; + } + return !p; + } + }; + + struct indexed_insert { + lua_reg_table& registration_table; + int& index; + + indexed_insert(lua_reg_table& registration_table_, int& index_ref_) : registration_table(registration_table_), index(index_ref_) { + } + void operator()(meta_function meta_function_name_, lua_CFunction c_function_) { + registration_table[index] = luaL_Reg { to_string(meta_function_name_).c_str(), c_function_ }; + ++index; + } + }; + } // namespace detail + + namespace stack { + + template + struct field_getter; + template + struct probe_field_getter; + + template + struct field_setter; + + template + struct unqualified_getter; + template + struct qualified_getter; + + template + struct qualified_interop_getter; + template + struct unqualified_interop_getter; + + template + struct popper; + + template + struct unqualified_pusher; + + template + struct unqualified_checker; + template + struct qualified_checker; + + template + struct unqualified_check_getter; + template + struct qualified_check_getter; + + struct probe { + bool success; + int levels; + + probe(bool s, int l) : success(s), levels(l) { + } + + operator bool() const { + return success; + }; + }; + + struct record { + int last; + int used; + + record() noexcept : last(), used() { + } + void use(int count) noexcept { + last = count; + used += count; + } + }; + + namespace stack_detail { + template + Function* get_function_pointer(lua_State*, int, record&) noexcept; + template + bool check_function_pointer(lua_State* L, int index, Handler&& handler, record& tracking) noexcept; + } // namespace stack_detail + + } // namespace stack + + namespace meta { namespace meta_detail { + template + using adl_sol_lua_get_test_t = decltype(sol_lua_get(types(), static_cast(nullptr), -1, std::declval())); + + template + using adl_sol_lua_interop_get_test_t + = decltype(sol_lua_interop_get(types(), static_cast(nullptr), -1, static_cast(nullptr), std::declval())); + + template + using adl_sol_lua_check_test_t = decltype(sol_lua_check(types(), static_cast(nullptr), -1, &no_panic, std::declval())); + + template + using adl_sol_lua_interop_check_test_t + = decltype(sol_lua_interop_check(types(), static_cast(nullptr), -1, type::none, &no_panic, std::declval())); + + template + using adl_sol_lua_check_get_test_t + = decltype(sol_lua_check_get(types(), static_cast(nullptr), -1, &no_panic, std::declval())); + + template + using adl_sol_lua_push_test_t = decltype(sol_lua_push(static_cast(nullptr), std::declval()...)); + + template + using adl_sol_lua_push_exact_test_t = decltype(sol_lua_push(types(), static_cast(nullptr), std::declval()...)); + + template + inline constexpr bool is_adl_sol_lua_get_v = meta::is_detected_v; + + template + inline constexpr bool is_adl_sol_lua_interop_get_v = meta::is_detected_v; + + template + inline constexpr bool is_adl_sol_lua_check_v = meta::is_detected_v; + + template + inline constexpr bool is_adl_sol_lua_interop_check_v = meta::is_detected_v; + + template + inline constexpr bool is_adl_sol_lua_check_get_v = meta::is_detected_v; + + template + inline constexpr bool is_adl_sol_lua_push_v = meta::is_detected_v; + + template + inline constexpr bool is_adl_sol_lua_push_exact_v = meta::is_detected_v; + }} // namespace meta::meta_detail + + + namespace stack { + namespace stack_detail { + constexpr const char* not_enough_stack_space = "not enough space left on Lua stack"; + constexpr const char* not_enough_stack_space_floating = "not enough space left on Lua stack for a floating point number"; + constexpr const char* not_enough_stack_space_integral = "not enough space left on Lua stack for an integral number"; + constexpr const char* not_enough_stack_space_string = "not enough space left on Lua stack for a string"; + constexpr const char* not_enough_stack_space_meta_function_name = "not enough space left on Lua stack for the name of a meta_function"; + constexpr const char* not_enough_stack_space_userdata = "not enough space left on Lua stack to create a sol2 userdata"; + constexpr const char* not_enough_stack_space_generic = "not enough space left on Lua stack to push valuees"; + constexpr const char* not_enough_stack_space_environment = "not enough space left on Lua stack to retrieve environment"; + + template + struct strip { + typedef T type; + }; + template + struct strip> { + typedef T& type; + }; + template + struct strip> { + typedef T& type; + }; + template + struct strip> { + typedef T type; + }; + template + using strip_t = typename strip::type; + + template + static int get_size_hint(C& c) { + return static_cast(c.size()); + } + + template + static int get_size_hint(const std::forward_list&) { + // forward_list makes me sad + return static_cast(32); + } + + template + decltype(auto) unchecked_unqualified_get(lua_State* L, int index, record& tracking) { + using Tu = meta::unqualified_t; + if constexpr (meta::meta_detail::is_adl_sol_lua_get_v) { + return sol_lua_get(types(), L, index, tracking); + } + else { + unqualified_getter g {}; + return g.get(L, index, tracking); + } + } + + template + decltype(auto) unchecked_get(lua_State* L, int index, record& tracking) { + if constexpr (meta::meta_detail::is_adl_sol_lua_get_v) { + return sol_lua_get(types(), L, index, tracking); + } + else { + qualified_getter g {}; + return g.get(L, index, tracking); + } + } + + template + decltype(auto) unqualified_interop_get(lua_State* L, int index, void* unadjusted_pointer, record& tracking) { + using Tu = meta::unqualified_t; + if constexpr (meta::meta_detail::is_adl_sol_lua_interop_get_v) { + return sol_lua_interop_get(types(), L, index, unadjusted_pointer, tracking); + } + else { + (void)L; + (void)index; + (void)unadjusted_pointer; + (void)tracking; + using Ti = stack_detail::strip_t; + return std::pair { false, nullptr }; + } + } + + template + decltype(auto) interop_get(lua_State* L, int index, void* unadjusted_pointer, record& tracking) { + if constexpr (meta::meta_detail::is_adl_sol_lua_interop_get_v) { + return sol_lua_interop_get(types(), L, index, unadjusted_pointer, tracking); + } + else { + return unqualified_interop_get(L, index, unadjusted_pointer, tracking); + } + } + + template + bool unqualified_interop_check(lua_State* L, int index, type index_type, Handler&& handler, record& tracking) { + using Tu = meta::unqualified_t; + if constexpr (meta::meta_detail::is_adl_sol_lua_interop_check_v) { + return sol_lua_interop_check(types(), L, index, index_type, std::forward(handler), tracking); + } + else { + (void)L; + (void)index; + (void)index_type; + (void)handler; + (void)tracking; + return false; + } + } + + template + bool interop_check(lua_State* L, int index, type index_type, Handler&& handler, record& tracking) { + if constexpr (meta::meta_detail::is_adl_sol_lua_interop_check_v) { + return sol_lua_interop_check(types(), L, index, index_type, std::forward(handler), tracking); + } + else { + return unqualified_interop_check(L, index, index_type, std::forward(handler), tracking); + } + } + + using undefined_method_func = void (*)(stack_reference); + + struct undefined_metatable { + lua_State* L; + const char* key; + undefined_method_func on_new_table; + + undefined_metatable(lua_State* l, const char* k, undefined_method_func umf) : L(l), key(k), on_new_table(umf) { + } + + void operator()() const { + if (luaL_newmetatable(L, key) == 1) { + on_new_table(stack_reference(L, -1)); + } + lua_setmetatable(L, -2); + } + }; + } // namespace stack_detail + + inline bool maybe_indexable(lua_State* L, int index = -1) { + type t = type_of(L, index); + return t == type::userdata || t == type::table; + } + + inline int top(lua_State* L) { + return lua_gettop(L); + } + + inline bool is_main_thread(lua_State* L) { + int ismainthread = lua_pushthread(L); + lua_pop(L, 1); + return ismainthread == 1; + } + + inline void coroutine_create_guard(lua_State* L) { + if (is_main_thread(L)) { + return; + } + int stacksize = lua_gettop(L); + if (stacksize < 1) { + return; + } + if (type_of(L, 1) != type::function) { + return; + } + // well now we're screwed... + // we can clean the stack and pray it doesn't destroy anything? + lua_pop(L, stacksize); + } + + inline void clear(lua_State* L, int table_index) { + lua_pushnil(L); + while (lua_next(L, table_index) != 0) { + // remove value + lua_pop(L, 1); + // duplicate key to protect form rawset + lua_pushvalue(L, -1); + // push new value + lua_pushnil(L); + // table_index%[key] = nil + lua_rawset(L, table_index); + } + } + + inline void clear(reference& r) { + auto pp = push_pop(r); + int stack_index = pp.index_of(r); + clear(r.lua_state(), stack_index); + } + + inline void clear(stack_reference& r) { + clear(r.lua_state(), r.stack_index()); + } + + inline void clear(lua_State* L_, stateless_reference& r) { + r.push(L_); + int stack_index = absolute_index(L_, -1); + clear(L_, stack_index); + r.pop(L_); + } + + inline void clear(lua_State* L_, stateless_stack_reference& r) { + clear(L_, r.stack_index()); + } + + template + int push(lua_State* L, T&& t, Args&&... args) { + using Tu = meta::unqualified_t; + if constexpr (meta::meta_detail::is_adl_sol_lua_push_exact_v) { + return sol_lua_push(types(), L, std::forward(t), std::forward(args)...); + } + else if constexpr (meta::meta_detail::is_adl_sol_lua_push_exact_v) { + return sol_lua_push(types(), L, std::forward(t), std::forward(args)...); + } + else if constexpr (meta::meta_detail::is_adl_sol_lua_push_v) { + return sol_lua_push(L, std::forward(t), std::forward(args)...); + } + else { + unqualified_pusher p {}; + return p.push(L, std::forward(t), std::forward(args)...); + } + } + + // overload allows to use a pusher of a specific type, but pass in any kind of args + template ::value>> + int push(lua_State* L, Arg&& arg, Args&&... args) { + using Tu = meta::unqualified_t; + if constexpr (meta::meta_detail::is_adl_sol_lua_push_exact_v) { + return sol_lua_push(types(), L, std::forward(arg), std::forward(args)...); + } + else if constexpr (meta::meta_detail::is_adl_sol_lua_push_exact_v) { + return sol_lua_push(types(), L, std::forward(arg), std::forward(args)...); + } + else if constexpr (meta::meta_detail::is_adl_sol_lua_push_v && !detail::is_tagged_v) { + return sol_lua_push(L, std::forward(arg), std::forward(args)...); + } + else { + unqualified_pusher p {}; + return p.push(L, std::forward(arg), std::forward(args)...); + } + } + + template + int push_userdata(lua_State* L, T&& t, Args&&... args) { + using U = meta::unqualified_t; + using Tr = meta::conditional_t, + detail::as_pointer_tag>, + meta::conditional_t, detail::as_unique_tag, detail::as_value_tag>>; + return stack::push(L, std::forward(t), std::forward(args)...); + } + + template + int push_userdata(lua_State* L, Arg&& arg, Args&&... args) { + using U = meta::unqualified_t; + using Tr = meta::conditional_t, + detail::as_pointer_tag>, + meta::conditional_t, detail::as_unique_tag, detail::as_value_tag>>; + return stack::push(L, std::forward(arg), std::forward(args)...); + } + + namespace stack_detail { + + template + int push_reference(lua_State* L, Arg&& arg, Args&&... args) { + // clang-format off + using use_reference_tag = + meta::all< + meta::neg> +#if SOL_IS_OFF(SOL_FUNCTION_CALL_VALUE_SEMANTICS) + , std::is_lvalue_reference, + meta::neg>>, + meta::neg>>, + meta::neg>> +#endif + >; + // clang-format on + using Tr = meta::conditional_t>; + return stack::push(L, std::forward(arg), std::forward(args)...); + } + + } // namespace stack_detail + + template + int push_reference(lua_State* L, T&& t, Args&&... args) { + return stack_detail::push_reference(L, std::forward(t), std::forward(args)...); + } + + template + int push_reference(lua_State* L, Arg&& arg, Args&&... args) { + return stack_detail::push_reference(L, std::forward(arg), std::forward(args)...); + } + + inline int multi_push(lua_State*) { + // do nothing + return 0; + } + + template + int multi_push(lua_State* L, T&& t, Args&&... args) { + int pushcount = push(L, std::forward(t)); + void(detail::swallow { (pushcount += stack::push(L, std::forward(args)), 0)... }); + return pushcount; + } + + inline int multi_push_reference(lua_State*) { + // do nothing + return 0; + } + + template + int multi_push_reference(lua_State* L, T&& t, Args&&... args) { + int pushcount = stack::push_reference(L, std::forward(t)); + void(detail::swallow { (pushcount += stack::push_reference(L, std::forward(args)), 0)... }); + return pushcount; + } + + template + bool unqualified_check(lua_State* L, int index, Handler&& handler, record& tracking) { + using Tu = meta::unqualified_t; + if constexpr (meta::meta_detail::is_adl_sol_lua_check_v) { + return sol_lua_check(types(), L, index, std::forward(handler), tracking); + } + else { + unqualified_checker> c{}; + return c.check(L, index, std::forward(handler), tracking); + } + } + + template + bool unqualified_check(lua_State* L, int index, Handler&& handler) { + record tracking {}; + return unqualified_check(L, index, std::forward(handler), tracking); + } + + template + bool unqualified_check(lua_State* L, int index = -lua_size>::value) { + auto handler = &no_panic; + return unqualified_check(L, index, handler); + } + + template + bool check(lua_State* L, int index, Handler&& handler, record& tracking) { + if constexpr (meta::meta_detail::is_adl_sol_lua_check_v) { + return sol_lua_check(types(), L, index, std::forward(handler), tracking); + } + else { + using Tu = meta::unqualified_t; + qualified_checker> c{}; + return c.check(L, index, std::forward(handler), tracking); + } + } + + template + bool check(lua_State* L, int index, Handler&& handler) { + record tracking {}; + return check(L, index, std::forward(handler), tracking); + } + + template + bool check(lua_State* L, int index = -lua_size>::value) { + auto handler = &no_panic; + return check(L, index, handler); + } + + template + bool check_usertype(lua_State* L, int index, type, Handler&& handler, record& tracking) { + using Tu = meta::unqualified_t; + using detail_t = meta::conditional_t, detail::as_pointer_tag, detail::as_value_tag>; + return check(L, index, std::forward(handler), tracking); + } + + template + bool check_usertype(lua_State* L, int index, Handler&& handler, record& tracking) { + using Tu = meta::unqualified_t; + using detail_t = meta::conditional_t, detail::as_pointer_tag, detail::as_value_tag>; + return check(L, index, std::forward(handler), tracking); + } + + template + bool check_usertype(lua_State* L, int index, Handler&& handler) { + record tracking {}; + return check_usertype(L, index, std::forward(handler), tracking); + } + + template + bool check_usertype(lua_State* L, int index = -lua_size>::value) { + auto handler = &no_panic; + return check_usertype(L, index, handler); + } + + template + decltype(auto) unqualified_check_get(lua_State* L, int index, Handler&& handler, record& tracking) { + using Tu = meta::unqualified_t; + if constexpr (meta::meta_detail::is_adl_sol_lua_check_get_v) { + return sol_lua_check_get(types(), L, index, std::forward(handler), tracking); + } + else if constexpr (meta::meta_detail::is_adl_sol_lua_check_get_v) { + return sol_lua_check_get(types(), L, index, std::forward(handler), tracking); + } + else { + unqualified_check_getter cg {}; + return cg.get(L, index, std::forward(handler), tracking); + } + } + + template + decltype(auto) unqualified_check_get(lua_State* L, int index, Handler&& handler) { + record tracking {}; + return unqualified_check_get(L, index, handler, tracking); + } + + template + decltype(auto) unqualified_check_get(lua_State* L, int index = -lua_size>::value) { + auto handler = &no_panic; + return unqualified_check_get(L, index, handler); + } + + template + decltype(auto) check_get(lua_State* L, int index, Handler&& handler, record& tracking) { + if constexpr (meta::meta_detail::is_adl_sol_lua_check_get_v) { + return sol_lua_check_get(types(), L, index, std::forward(handler), tracking); + } + else { + qualified_check_getter cg {}; + return cg.get(L, index, std::forward(handler), tracking); + } + } + + template + decltype(auto) check_get(lua_State* L, int index, Handler&& handler) { + record tracking {}; + return check_get(L, index, handler, tracking); + } + + template + decltype(auto) check_get(lua_State* L, int index = -lua_size>::value) { + auto handler = &no_panic; + return check_get(L, index, handler); + } + + namespace stack_detail { + + template + bool check_types(lua_State*, int, Handler&&, record&) { + return true; + } + + template + bool check_types(lua_State* L, int firstargument, Handler&& handler, record& tracking) { + if (!stack::check(L, firstargument + tracking.used, handler, tracking)) + return false; + return check_types(L, firstargument, std::forward(handler), tracking); + } + + template + bool check_types(types, lua_State* L, int index, Handler&& handler, record& tracking) { + return check_types(L, index, std::forward(handler), tracking); + } + + } // namespace stack_detail + + template + bool multi_check(lua_State* L, int index, Handler&& handler, record& tracking) { + return stack_detail::check_types(L, index, std::forward(handler), tracking); + } + + template + bool multi_check(lua_State* L, int index, Handler&& handler) { + record tracking {}; + return multi_check(L, index, std::forward(handler), tracking); + } + + template + bool multi_check(lua_State* L, int index) { + return multi_check(L, index); + } + + template + auto unqualified_get(lua_State* L, int index, record& tracking) -> decltype(stack_detail::unchecked_unqualified_get(L, index, tracking)) { +#if SOL_IS_ON(SOL_SAFE_GETTER) + static constexpr bool is_op = meta::is_optional_v; + if constexpr (is_op) { + return stack_detail::unchecked_unqualified_get(L, index, tracking); + } + else { + if (is_lua_reference::value) { + return stack_detail::unchecked_unqualified_get(L, index, tracking); + } + auto op = unqualified_check_get(L, index, type_panic_c_str, tracking); + return *std::move(op); + } +#else + return stack_detail::unchecked_unqualified_get(L, index, tracking); +#endif + } + + template + decltype(auto) unqualified_get(lua_State* L, int index = -lua_size>::value) { + record tracking {}; + return unqualified_get(L, index, tracking); + } + + template + auto get(lua_State* L, int index, record& tracking) -> decltype(stack_detail::unchecked_get(L, index, tracking)) { +#if SOL_IS_ON(SOL_SAFE_GETTER) + static constexpr bool is_op = meta::is_optional_v; + if constexpr (is_op) { + return stack_detail::unchecked_get(L, index, tracking); + } + else { + if (is_lua_reference::value) { + return stack_detail::unchecked_get(L, index, tracking); + } + auto op = check_get(L, index, type_panic_c_str, tracking); + return *std::move(op); + } +#else + return stack_detail::unchecked_get(L, index, tracking); +#endif + } + + template + decltype(auto) get(lua_State* L, int index = -lua_size>::value) { + record tracking {}; + return get(L, index, tracking); + } + + template + decltype(auto) get_usertype(lua_State* L, int index, record& tracking) { + using UT = meta::conditional_t::value, detail::as_pointer_tag>, detail::as_value_tag>; + return get(L, index, tracking); + } + + template + decltype(auto) get_usertype(lua_State* L, int index = -lua_size_v>) { + record tracking {}; + return get_usertype(L, index, tracking); + } + + template + decltype(auto) pop(lua_State* L) { + return popper {}.pop(L); + } + + template + void get_field(lua_State* L, Key&& key) { + field_getter, global, raw> {}.get(L, std::forward(key)); + } + + template + void get_field(lua_State* L, Key&& key, int tableindex) { + field_getter, global, raw> {}.get(L, std::forward(key), tableindex); + } + + template + void raw_get_field(lua_State* L, Key&& key) { + get_field(L, std::forward(key)); + } + + template + void raw_get_field(lua_State* L, Key&& key, int tableindex) { + get_field(L, std::forward(key), tableindex); + } + + template + probe probe_get_field(lua_State* L, Key&& key) { + return probe_field_getter, C, global, raw> {}.get(L, std::forward(key)); + } + + template + probe probe_get_field(lua_State* L, Key&& key, int tableindex) { + return probe_field_getter, C, global, raw> {}.get(L, std::forward(key), tableindex); + } + + template + probe probe_raw_get_field(lua_State* L, Key&& key) { + return probe_get_field(L, std::forward(key)); + } + + template + probe probe_raw_get_field(lua_State* L, Key&& key, int tableindex) { + return probe_get_field(L, std::forward(key), tableindex); + } + + template + void set_field(lua_State* L, Key&& key, Value&& value) { + field_setter, global, raw> {}.set(L, std::forward(key), std::forward(value)); + } + + template + void set_field(lua_State* L, Key&& key, Value&& value, int tableindex) { + field_setter, global, raw> {}.set(L, std::forward(key), std::forward(value), tableindex); + } + + template + void raw_set_field(lua_State* L, Key&& key, Value&& value) { + set_field(L, std::forward(key), std::forward(value)); + } + + template + void raw_set_field(lua_State* L, Key&& key, Value&& value, int tableindex) { + set_field(L, std::forward(key), std::forward(value), tableindex); + } + + template + void modify_unique_usertype_as(const stack_reference& obj, F&& f) { + void* raw = lua_touserdata(obj.lua_state(), obj.stack_index()); + void* ptr_memory = detail::align_usertype_pointer(raw); + void* uu_memory = detail::align_usertype_unique(raw); + T& uu = *static_cast(uu_memory); + f(uu); + *static_cast(ptr_memory) = static_cast(detail::unique_get(obj.lua_state(), uu)); + } + + template + void modify_unique_usertype(const stack_reference& obj, F&& f) { + using bt = meta::bind_traits>; + using T = typename bt::template arg_at<0>; + using Tu = meta::unqualified_t; + modify_unique_usertype_as(obj, std::forward(f)); + } + + namespace stack_detail { + template + decltype(auto) check_get_arg(lua_State* L_, int index_, Handler&& handler_, record& tracking_) { + if constexpr (meta::meta_detail::is_adl_sol_lua_check_access_v) { + sol_lua_check_access(types>(), L_, index_, tracking_); + } + return check_get(L_, index_, std::forward(handler_), tracking_); + } + + template + decltype(auto) unchecked_get_arg(lua_State* L_, int index_, record& tracking_) { + if constexpr (meta::meta_detail::is_adl_sol_lua_check_access_v) { + sol_lua_check_access(types>(), L_, index_, tracking_); + } + return unchecked_get(L_, index_, tracking_); + } + } // namespace stack_detail + + } // namespace stack + + namespace detail { + + template + lua_CFunction make_destructor(std::true_type) { + if constexpr (is_unique_usertype_v) { + return &unique_destroy; + } + else if constexpr (!std::is_pointer_v) { + return &usertype_alloc_destroy; + } + else { + return &cannot_destroy; + } + } + + template + lua_CFunction make_destructor(std::false_type) { + return &cannot_destroy; + } + + template + lua_CFunction make_destructor() { + return make_destructor(std::is_destructible()); + } + + struct no_comp { + template + bool operator()(A&&, B&&) const { + return false; + } + }; + + template + int is_check(lua_State* L) { + return stack::push(L, stack::check(L, 1, &no_panic)); + } + + template + int member_default_to_string(std::true_type, lua_State* L) { + decltype(auto) ts = stack::get(L, 1).to_string(); + return stack::push(L, std::forward(ts)); + } + + template + int member_default_to_string(std::false_type, lua_State* L) { + return luaL_error(L, + "cannot perform to_string on '%s': no 'to_string' overload in namespace, 'to_string' member " + "function, or operator<<(ostream&, ...) present", + detail::demangle().data()); + } + + template + int adl_default_to_string(std::true_type, lua_State* L) { + using namespace std; + decltype(auto) ts = to_string(stack::get(L, 1)); + return stack::push(L, std::forward(ts)); + } + + template + int adl_default_to_string(std::false_type, lua_State* L) { + return member_default_to_string(meta::supports_to_string_member(), L); + } + + template + int oss_default_to_string(std::true_type, lua_State* L) { + std::ostringstream oss; + oss << stack::unqualified_get(L, 1); + return stack::push(L, oss.str()); + } + + template + int oss_default_to_string(std::false_type, lua_State* L) { + return adl_default_to_string(meta::supports_adl_to_string(), L); + } + + template + int default_to_string(lua_State* L) { + return oss_default_to_string(meta::supports_op_left_shift(), L); + } + + template + int default_size(lua_State* L) { + decltype(auto) self = stack::unqualified_get(L, 1); + return stack::push(L, self.size()); + } + + template + int comparsion_operator_wrap(lua_State* L) { + if constexpr (std::is_void_v) { + return stack::push(L, false); + } + else { + auto maybel = stack::unqualified_check_get(L, 1); + if (!maybel) { + return stack::push(L, false); + } + auto mayber = stack::unqualified_check_get(L, 2); + if (!mayber) { + return stack::push(L, false); + } + decltype(auto) l = *maybel; + decltype(auto) r = *mayber; + if constexpr (std::is_same_v) { + std::equal_to<> op; + return stack::push(L, op(detail::ptr(l), detail::ptr(r))); + } + else { + if constexpr (std::is_same_v, Op> // clang-format hack + || std::is_same_v, Op> // + || std::is_same_v, Op>) { // + if (detail::ptr(l) == detail::ptr(r)) { + return stack::push(L, true); + } + } + Op op; + return stack::push(L, op(detail::deref(l), detail::deref(r))); + } + } + } + + template + void insert_default_registrations(IFx&& ifx, Fx&& fx); + + template + struct get_is_primitive : is_lua_primitive { }; + + template + struct get_is_primitive + : meta::neg(), nullptr, -1, std::declval()))>> { }; + + template + struct get_is_primitive + : meta::neg>(), nullptr, -1, std::declval()))>> { }; + + template + struct get_is_primitive : get_is_primitive { }; + + } // namespace detail + + template + struct is_proxy_primitive + : detail::get_is_primitive, meta::meta_detail::is_adl_sol_lua_get_v>> { }; + +} // namespace sol + +#endif // SOL_STACK_CORE_HPP + +// sol2 + +// The MIT License (MIT) + +// Copyright (c) 2013-2022 Rapptz, ThePhD and contributors + +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef SOL_TIE_HPP +#define SOL_TIE_HPP + +#include + +namespace sol { + + namespace detail { + template + struct is_speshul : std::false_type { }; + } // namespace detail + + template + struct tie_size : std::tuple_size { }; + + template + struct is_tieable : std::integral_constant::value > 0)> { }; + + template + struct tie_t : public std::tuple...> { + private: + typedef std::tuple...> base_t; + + template + void set(std::false_type, T&& target) { + std::get<0>(*this) = std::forward(target); + } + + template + void set(std::true_type, T&& target) { + typedef tie_size> value_size; + typedef tie_size> tie_size; + typedef meta::conditional_t<(value_size::value < tie_size::value), value_size, tie_size> indices_size; + typedef std::make_index_sequence indices; + set_extra(detail::is_speshul>(), indices(), std::forward(target)); + } + + template + void set_extra(std::true_type, std::index_sequence, T&& target) { + using std::get; + (void)detail::swallow { 0, (get(static_cast(*this)) = get(types(), target), 0)..., 0 }; + } + + template + void set_extra(std::false_type, std::index_sequence, T&& target) { + using std::get; + (void)detail::swallow { 0, (get(static_cast(*this)) = get(target), 0)..., 0 }; + } + + public: + using base_t::base_t; + + template + tie_t& operator=(T&& value) { + typedef is_tieable> tieable; + set(tieable(), std::forward(value)); + return *this; + } + }; + + template + struct tie_size> : std::tuple_size> { }; + + namespace adl_barrier_detail { + template + inline tie_t...> tie(Tn&&... argn) { + return tie_t...>(std::forward(argn)...); + } + } // namespace adl_barrier_detail + + using namespace adl_barrier_detail; + +} // namespace sol + +#endif // SOL_TIE_HPP + +// sol2 + +// The MIT License (MIT) + +// Copyright (c) 2013-2022 Rapptz, ThePhD and contributors + +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef SOL_USERTYPE_CONTAINER_HPP +#define SOL_USERTYPE_CONTAINER_HPP + +#include +#include +#include + +namespace sol { + + template + struct usertype_container; + + namespace container_detail { + + template + struct has_clear_test { + private: + template + static meta::sfinae_yes_t test(decltype(&C::clear)); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_empty_test { + private: + template + static meta::sfinae_yes_t test(decltype(&C::empty)); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_erase_after_test { + private: + template + static meta::sfinae_yes_t test( + decltype(std::declval().erase_after(std::declval>()))*); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_find_test { + private: + template + static meta::sfinae_yes_t test(decltype(std::declval().find(std::declval>()))*); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_find_test::value>> { + private: + template + static meta::sfinae_yes_t test(decltype(std::declval().find(std::declval>()))*); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_erase_test { + private: + template + static meta::sfinae_yes_t test(decltype(std::declval().erase(std::declval()))*); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_erase_key_test { + private: + template + static meta::sfinae_yes_t test(decltype(std::declval().erase(std::declval()))*); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_traits_find_test { + private: + template + static meta::sfinae_yes_t test(decltype(&C::find)); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_traits_index_of_test { + private: + template + static meta::sfinae_yes_t test(decltype(&C::index_of)); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_traits_insert_test { + private: + template + static meta::sfinae_yes_t test(decltype(&C::insert)); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_traits_erase_test { + private: + template + static meta::sfinae_yes_t test(decltype(&C::erase)); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_traits_index_set_test { + private: + template + static meta::sfinae_yes_t test(decltype(&C::index_set)); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_traits_index_get_test { + private: + template + static meta::sfinae_yes_t test(decltype(&C::index_get)); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_traits_set_test { + private: + template + static meta::sfinae_yes_t test(decltype(&C::set)); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_traits_get_test { + private: + template + static meta::sfinae_yes_t test(decltype(&C::get)); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_traits_at_test { + private: + template + static meta::sfinae_yes_t test(decltype(&C::at)); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_traits_pairs_test { + private: + template + static meta::sfinae_yes_t test(decltype(&C::pairs)); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_traits_ipairs_test { + private: + template + static meta::sfinae_yes_t test(decltype(&C::ipairs)); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_traits_next_test { + private: + template + static meta::sfinae_yes_t test(decltype(&C::next)); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_traits_add_test { + private: + template + static meta::sfinae_yes_t test(decltype(&C::add)); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + struct has_traits_size_test { + private: + template + static meta::sfinae_yes_t test(decltype(&C::size)); + template + static meta::sfinae_no_t test(...); + + public: + static constexpr bool value = std::is_same_v(0)), meta::sfinae_yes_t>; + }; + + template + using has_clear = meta::boolean::value>; + + template + using has_empty = meta::boolean::value>; + + template + using has_find = meta::boolean::value>; + + template + using has_erase = meta::boolean::value>; + + template + using has_erase_key = meta::boolean::value>; + + template + using has_erase_after = meta::boolean::value>; + + template + using has_traits_get = meta::boolean::value>; + + template + using has_traits_at = meta::boolean::value>; + + template + using has_traits_set = meta::boolean::value>; + + template + using has_traits_index_get = meta::boolean::value>; + + template + using has_traits_index_set = meta::boolean::value>; + + template + using has_traits_pairs = meta::boolean::value>; + + template + using has_traits_ipairs = meta::boolean::value>; + + template + using has_traits_next = meta::boolean::value>; + + template + using has_traits_add = meta::boolean::value>; + + template + using has_traits_size = meta::boolean::value>; + + template + using has_traits_clear = has_clear; + + template + using has_traits_empty = has_empty; + + template + using has_traits_find = meta::boolean::value>; + + template + using has_traits_index_of = meta::boolean::value>; + + template + using has_traits_insert = meta::boolean::value>; + + template + using has_traits_erase = meta::boolean::value>; + + template + struct is_forced_container : is_container { }; + + template + struct is_forced_container> : std::true_type { }; + + template + struct container_decay { + typedef T type; + }; + + template + struct container_decay> { + typedef T type; + }; + + template + using container_decay_t = typename container_decay>::type; + + template + decltype(auto) get_key(std::false_type, T&& t) { + return std::forward(t); + } + + template + decltype(auto) get_key(std::true_type, T&& t) { + return t.first; + } + + template + decltype(auto) get_value(std::false_type, T&& t) { + return std::forward(t); + } + + template + decltype(auto) get_value(std::true_type, T&& t) { + return t.second; + } + + template + struct usertype_container_default { + private: + typedef std::remove_pointer_t> T; + + public: + typedef lua_nil_t iterator; + typedef iterator sentinel; + typedef lua_nil_t value_type; + + static int at(lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'at(index)' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + } + + static int get(lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'get(key)' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + } + + static int index_get(lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'container[key]' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + } + + static int set(lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'set(key, value)' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + } + + static int index_set(lua_State* L_) { + return luaL_error( + L_, "sol: cannot call 'container[key] = value' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + } + + static int add(lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'add' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + } + + static int insert(lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'insert' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + } + + static int find(lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'find' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + } + + static int index_of(lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'index_of' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + } + + static int size(lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'end' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + } + + static int clear(lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'clear' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + } + + static int empty(lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'empty' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + } + + static int erase(lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'erase' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + } + + static int next(lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'next' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + } + + static int pairs(lua_State* L_) { + return luaL_error(L_, "sol: cannot call '__pairs/pairs' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + } + + static int ipairs(lua_State* L_) { + return luaL_error(L_, "sol: cannot call '__ipairs' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + } + + static iterator begin(lua_State* L_, T&) { + luaL_error(L_, "sol: cannot call 'being' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + return lua_nil; + } + + static sentinel end(lua_State* L_, T&) { + luaL_error(L_, "sol: cannot call 'end' on type '%s': it is not recognized as a container", detail::demangle().c_str()); + return lua_nil; + } + }; + + template + struct usertype_container_default>, meta::has_value_type>>, + meta::has_iterator>>>::value>> { + private: + using T = std::remove_pointer_t>>; + + private: + using deferred_uc = usertype_container; + using is_associative = meta::is_associative; + using is_lookup = meta::is_lookup; + using is_ordered = meta::is_ordered; + using is_matched_lookup = meta::is_matched_lookup; + using iterator = typename T::iterator; + using sentinel = meta::sentinel_or_t; + using value_type = typename T::value_type; + typedef meta::conditional_t, + meta::conditional_t>> + KV; + typedef typename KV::first_type K; + typedef typename KV::second_type V; + typedef meta::conditional_t next_K; + typedef decltype(*std::declval()) iterator_return; + typedef meta::conditional_t, + meta::conditional_t> + captured_type; + typedef typename meta::iterator_tag::type iterator_category; + typedef std::is_same is_input_iterator; + typedef meta::conditional_t()))> push_type; + typedef std::is_copy_assignable is_copyable; + typedef meta::neg, std::is_const>, meta::neg>> is_writable; + typedef meta::unqualified_t>()))> key_type; + typedef meta::all, meta::neg>> is_linear_integral; + + struct iter : detail::ebco, detail::ebco { + using it_base = detail::ebco; + using sen_base = detail::ebco; + main_reference keep_alive; + std::size_t index; + + iter(lua_State* L_, int stack_index_, iterator it_, sentinel sen_) noexcept + : it_base(std::move(it_)), sen_base(std::move(sen_)), keep_alive(L_, stack_index_), index(0) { + } + + iterator& it() noexcept { + return it_base::value(); + } + + const iterator& it() const noexcept { + return it_base::value(); + } + + sentinel& sen() noexcept { + return sen_base::value(); + } + + const sentinel& sen() const noexcept { + return sen_base::value(); + } + }; + + static auto& get_src(lua_State* L_) { +#if SOL_IS_ON(SOL_SAFE_USERTYPE) + auto p = stack::unqualified_check_get(L_, 1); + if (!p) { + luaL_error(L_, + "sol: 'self' is not of type '%s' (pass 'self' as first argument with ':' or call on proper type)", + detail::demangle().c_str()); + } + if (p.value() == nullptr) { + luaL_error( + L_, "sol: 'self' argument is nil (pass 'self' as first argument with ':' or call on a '%s' type)", detail::demangle().c_str()); + } + return *p.value(); +#else + return stack::unqualified_get(L_, 1); +#endif // Safe getting with error + } + + static detail::error_result at_category(std::input_iterator_tag, lua_State* L_, T& self, std::ptrdiff_t pos) { + pos += deferred_uc::index_adjustment(L_, self); + if (pos < 0) { + return stack::push(L_, lua_nil); + } + auto it = deferred_uc::begin(L_, self); + auto e = deferred_uc::end(L_, self); + if (it == e) { + return stack::push(L_, lua_nil); + } + while (pos > 0) { + --pos; + ++it; + if (it == e) { + return stack::push(L_, lua_nil); + } + } + return get_associative(is_associative(), L_, it); + } + + static detail::error_result at_category(std::random_access_iterator_tag, lua_State* L_, T& self, std::ptrdiff_t pos) { + std::ptrdiff_t len = static_cast(size_start(L_, self)); + pos += deferred_uc::index_adjustment(L_, self); + if (pos < 0 || pos >= len) { + return stack::push(L_, lua_nil); + } + auto it = std::next(deferred_uc::begin(L_, self), pos); + return get_associative(is_associative(), L_, it); + } + + static detail::error_result at_start(lua_State* L_, T& self, std::ptrdiff_t pos) { + return at_category(iterator_category(), L_, self, pos); + } + + template + static detail::error_result get_associative(std::true_type, lua_State* L_, Iter& it) { + decltype(auto) v = *it; + return stack::stack_detail::push_reference(L_, detail::deref_move_only(v.second)); + } + + template + static detail::error_result get_associative(std::false_type, lua_State* L_, Iter& it) { + return stack::stack_detail::push_reference(L_, detail::deref_move_only(*it)); + } + + static detail::error_result get_category(std::input_iterator_tag, lua_State* L_, T& self, K& key) { + key = static_cast(key + deferred_uc::index_adjustment(L_, self)); + if (key < 0) { + return stack::push(L_, lua_nil); + } + auto it = deferred_uc::begin(L_, self); + auto e = deferred_uc::end(L_, self); + if (it == e) { + return stack::push(L_, lua_nil); + } + while (key > 0) { + --key; + ++it; + if (it == e) { + return stack::push(L_, lua_nil); + } + } + return get_associative(is_associative(), L_, it); + } + + static detail::error_result get_category(std::random_access_iterator_tag, lua_State* L_, T& self, K& key) { + std::ptrdiff_t len = static_cast(size_start(L_, self)); + key = static_cast(static_cast(key) + deferred_uc::index_adjustment(L_, self)); + if (key < 0 || key >= len) { + return stack::push(L_, lua_nil); + } + auto it = std::next(deferred_uc::begin(L_, self), key); + return get_associative(is_associative(), L_, it); + } + + static detail::error_result get_it(std::true_type, lua_State* L_, T& self, K& key) { + return get_category(iterator_category(), L_, self, key); + } + + static detail::error_result get_comparative(std::true_type, lua_State* L_, T& self, K& key) { + auto fx = [&](const value_type& r) -> bool { return key == get_key(is_associative(), r); }; + auto e = deferred_uc::end(L_, self); + auto it = std::find_if(deferred_uc::begin(L_, self), e, std::ref(fx)); + if (it == e) { + return stack::push(L_, lua_nil); + } + return get_associative(is_associative(), L_, it); + } + + static detail::error_result get_comparative(std::false_type, lua_State*, T&, K&) { + return detail::error_result("cannot get this key on '%s': no suitable way to increment iterator and compare to key value '%s'", + detail::demangle().data(), + detail::demangle().data()); + } + + static detail::error_result get_it(std::false_type, lua_State* L_, T& self, K& key) { + return get_comparative(meta::supports_op_equal(), L_, self, key); + } + + static detail::error_result set_associative(std::true_type, iterator& it, stack_object value) { + decltype(auto) v = *it; + v.second = value.as(); + return {}; + } + + static detail::error_result set_associative(std::false_type, iterator& it, stack_object value) { + decltype(auto) v = *it; + v = value.as(); + return {}; + } + + static detail::error_result set_writable(std::true_type, lua_State*, T&, iterator& it, stack_object value) { + return set_associative(is_associative(), it, std::move(value)); + } + + static detail::error_result set_writable(std::false_type, lua_State*, T&, iterator&, stack_object) { + return detail::error_result( + "cannot perform a 'set': '%s's iterator reference is not writable (non-copy-assignable or const)", detail::demangle().data()); + } + + static detail::error_result set_category(std::input_iterator_tag, lua_State* L_, T& self, stack_object okey, stack_object value) { + decltype(auto) key = okey.as(); + key = static_cast(static_cast(key) + deferred_uc::index_adjustment(L_, self)); + auto e = deferred_uc::end(L_, self); + auto it = deferred_uc::begin(L_, self); + auto backit = it; + for (; key > 0 && it != e; --key, ++it) { + backit = it; + } + if (it == e) { + if (key == 0) { + return add_copyable(is_copyable(), L_, self, std::move(value), meta::has_insert_after::value ? backit : it); + } + return detail::error_result("out of bounds (too big) for set on '%s'", detail::demangle().c_str()); + } + return set_writable(is_writable(), L_, self, it, std::move(value)); + } + + static detail::error_result set_category(std::random_access_iterator_tag, lua_State* L_, T& self, stack_object okey, stack_object value) { + decltype(auto) key = okey.as(); + key = static_cast(static_cast(key) + deferred_uc::index_adjustment(L_, self)); + if (key < 0) { + return detail::error_result("sol: out of bounds (too small) for set on '%s'", detail::demangle().c_str()); + } + std::ptrdiff_t len = static_cast(size_start(L_, self)); + if (key == len) { + return add_copyable(is_copyable(), L_, self, std::move(value)); + } + else if (key >= len) { + return detail::error_result("sol: out of bounds (too big) for set on '%s'", detail::demangle().c_str()); + } + auto it = std::next(deferred_uc::begin(L_, self), key); + return set_writable(is_writable(), L_, self, it, std::move(value)); + } + + static detail::error_result set_comparative(std::true_type, lua_State* L_, T& self, stack_object okey, stack_object value) { + decltype(auto) key = okey.as(); + if (!is_writable::value) { + return detail::error_result( + "cannot perform a 'set': '%s's iterator reference is not writable (non-copy-assignable or const)", detail::demangle().data()); + } + auto fx = [&](const value_type& r) -> bool { return key == get_key(is_associative(), r); }; + auto e = deferred_uc::end(L_, self); + auto it = std::find_if(deferred_uc::begin(L_, self), e, std::ref(fx)); + if (it == e) { + return {}; + } + return set_writable(is_writable(), L_, self, it, std::move(value)); + } + + static detail::error_result set_comparative(std::false_type, lua_State*, T&, stack_object, stack_object) { + return detail::error_result("cannot set this value on '%s': no suitable way to increment iterator or compare to '%s' key", + detail::demangle().data(), + detail::demangle().data()); + } + + template + static detail::error_result set_associative_insert(std::true_type, lua_State*, T& self, Iter& it, K& key, stack_object value) { + if constexpr (meta::has_insert_with_iterator::value) { + self.insert(it, value_type(key, value.as())); + return {}; + } + else if constexpr (meta::has_insert::value) { + self.insert(value_type(key, value.as())); + return {}; + } + else { + (void)self; + (void)it; + (void)key; + return detail::error_result( + "cannot call 'set' on '%s': there is no 'insert' function on this associative type", detail::demangle().c_str()); + } + } + + template + static detail::error_result set_associative_insert(std::false_type, lua_State*, T& self, Iter& it, K& key, stack_object) { + if constexpr (meta::has_insert_with_iterator::value) { + self.insert(it, key); + return {}; + } + else if constexpr (meta::has_insert::value) { + self.insert(key); + return {}; + } + else { + (void)self; + (void)it; + (void)key; + return detail::error_result( + "cannot call 'set' on '%s': there is no 'insert' function on this non-associative type", detail::demangle().c_str()); + } + } + + static detail::error_result set_associative_find(std::true_type, lua_State* L_, T& self, stack_object okey, stack_object value) { + decltype(auto) key = okey.as(); + auto it = self.find(key); + if (it == deferred_uc::end(L_, self)) { + return set_associative_insert(is_associative(), L_, self, it, key, std::move(value)); + } + return set_writable(is_writable(), L_, self, it, std::move(value)); + } + + static detail::error_result set_associative_find(std::false_type, lua_State* L_, T& self, stack_object key, stack_object value) { + return set_comparative(meta::supports_op_equal(), L_, self, std::move(key), std::move(value)); + } + + static detail::error_result set_it(std::true_type, lua_State* L_, T& self, stack_object key, stack_object value) { + return set_category(iterator_category(), L_, self, std::move(key), std::move(value)); + } + + static detail::error_result set_it(std::false_type, lua_State* L_, T& self, stack_object key, stack_object value) { + return set_associative_find(meta::all, meta::any>(), L_, self, std::move(key), std::move(value)); + } + + template + static detail::error_result find_has_associative_lookup(std::true_type, lua_State* L_, T& self) { + if constexpr (!is_ordered::value && idx_of) { + (void)L_; + (void)self; + return detail::error_result("cannot perform an 'index_of': '%s's is not an ordered container", detail::demangle().data()); + } + else { + decltype(auto) key = stack::unqualified_get(L_, 2); + auto it = self.find(key); + if (it == deferred_uc::end(L_, self)) { + return stack::push(L_, lua_nil); + } + if constexpr (idx_of) { + auto dist = std::distance(deferred_uc::begin(L_, self), it); + dist -= deferred_uc::index_adjustment(L_, self); + return stack::push(L_, dist); + } + else { + return get_associative(is_associative(), L_, it); + } + } + } + + template + static detail::error_result find_has_associative_lookup(std::false_type, lua_State* L_, T& self) { + if constexpr (!is_ordered::value && idx_of) { + (void)L_; + (void)self; + return detail::error_result("cannot perform an 'index_of': '%s's is not an ordered container", detail::demangle().data()); + } + else { + decltype(auto) value = stack::unqualified_get(L_, 2); + auto it = self.find(value); + if (it == deferred_uc::end(L_, self)) { + return stack::push(L_, lua_nil); + } + if constexpr (idx_of) { + auto dist = std::distance(deferred_uc::begin(L_, self), it); + dist -= deferred_uc::index_adjustment(L_, self); + return stack::push(L_, dist); + } + else { + return get_associative(is_associative(), L_, it); + } + } + } + + template + static detail::error_result find_has(std::true_type, lua_State* L_, T& self) { + return find_has_associative_lookup(meta::any(), L_, self); + } + + template + static detail::error_result find_associative_lookup(std::true_type, lua_State* L_, T&, Iter& it, std::size_t) { + return get_associative(is_associative(), L_, it); + } + + template + static detail::error_result find_associative_lookup(std::false_type, lua_State* L_, T& self, Iter&, std::size_t idx) { + idx = static_cast(static_cast(idx) - deferred_uc::index_adjustment(L_, self)); + return stack::push(L_, idx); + } + + template + static detail::error_result find_comparative(std::false_type, lua_State*, T&) { + return detail::error_result("cannot call 'find' on '%s': there is no 'find' function and the value_type is not equality comparable", + detail::demangle().c_str()); + } + + template + static detail::error_result find_comparative(std::true_type, lua_State* L_, T& self) { + decltype(auto) value = stack::unqualified_get(L_, 2); + auto it = deferred_uc::begin(L_, self); + auto e = deferred_uc::end(L_, self); + std::size_t idx = 0; + for (;; ++it, ++idx) { + if (it == e) { + return stack::push(L_, lua_nil); + } + if (value == get_value(is_associative(), *it)) { + break; + } + } + return find_associative_lookup(meta::all, meta::any>(), L_, self, it, idx); + } + + template + static detail::error_result find_has(std::false_type, lua_State* L_, T& self) { + return find_comparative(meta::supports_op_equal(), L_, self); + } + + template + static detail::error_result add_insert_after(std::false_type, lua_State* L_, T& self, stack_object value, Iter&) { + return add_insert_after(std::false_type(), L_, self, value); + } + + static detail::error_result add_insert_after(std::false_type, lua_State*, T&, stack_object) { + return detail::error_result("cannot call 'add' on type '%s': no suitable insert/push_back C++ functions", detail::demangle().data()); + } + + template + static detail::error_result add_insert_after(std::true_type, lua_State*, T& self, stack_object value, Iter& pos) { + self.insert_after(pos, value.as()); + return {}; + } + + static detail::error_result add_insert_after(std::true_type, lua_State* L_, T& self, stack_object value) { + auto backit = self.before_begin(); + { + auto e = deferred_uc::end(L_, self); + for (auto it = deferred_uc::begin(L_, self); it != e; ++backit, ++it) { } + } + return add_insert_after(std::true_type(), L_, self, value, backit); + } + + template + static detail::error_result add_insert(std::true_type, lua_State*, T& self, stack_object value, Iter& pos) { + self.insert(pos, value.as()); + return {}; + } + + static detail::error_result add_insert(std::true_type, lua_State* L_, T& self, stack_object value) { + auto pos = deferred_uc::end(L_, self); + return add_insert(std::true_type(), L_, self, value, pos); + } + + template + static detail::error_result add_insert(std::false_type, lua_State* L_, T& self, stack_object value, Iter& pos) { + return add_insert_after(meta::has_insert_after(), L_, self, std::move(value), pos); + } + + static detail::error_result add_insert(std::false_type, lua_State* L_, T& self, stack_object value) { + return add_insert_after(meta::has_insert_after(), L_, self, std::move(value)); + } + + template + static detail::error_result add_push_back(std::true_type, lua_State*, T& self, stack_object value, Iter&) { + self.push_back(value.as()); + return {}; + } + + static detail::error_result add_push_back(std::true_type, lua_State*, T& self, stack_object value) { + self.push_back(value.as()); + return {}; + } + + template + static detail::error_result add_push_back(std::false_type, lua_State* L_, T& self, stack_object value, Iter& pos) { + return add_insert( + std::integral_constant < bool, meta::has_insert::value || meta::has_insert_with_iterator::value > (), L_, self, value, pos); + } + + static detail::error_result add_push_back(std::false_type, lua_State* L_, T& self, stack_object value) { + return add_insert( + std::integral_constant < bool, meta::has_insert::value || meta::has_insert_with_iterator::value > (), L_, self, value); + } + + template + static detail::error_result add_associative(std::true_type, lua_State* L_, T& self, stack_object key, Iter& pos) { + if constexpr (meta::has_insert_with_iterator::value) { + self.insert(pos, value_type(key.as(), stack::unqualified_get(L_, 3))); + return {}; + } + else if constexpr (meta::has_insert::value) { + self.insert(value_type(key.as(), stack::unqualified_get(L_, 3))); + return {}; + } + else { + (void)L_; + (void)self; + (void)key; + (void)pos; + return detail::error_result( + "cannot call 'insert' on '%s': there is no 'insert' function on this associative type", detail::demangle().c_str()); + } + } + + static detail::error_result add_associative(std::true_type, lua_State* L_, T& self, stack_object key) { + auto pos = deferred_uc::end(L_, self); + return add_associative(std::true_type(), L_, self, std::move(key), pos); + } + + template + static detail::error_result add_associative(std::false_type, lua_State* L_, T& self, stack_object value, Iter& pos) { + return add_push_back(meta::has_push_back(), L_, self, value, pos); + } + + static detail::error_result add_associative(std::false_type, lua_State* L_, T& self, stack_object value) { + return add_push_back(meta::has_push_back(), L_, self, value); + } + + template + static detail::error_result add_copyable(std::true_type, lua_State* L_, T& self, stack_object value, Iter& pos) { + return add_associative(is_associative(), L_, self, std::move(value), pos); + } + + static detail::error_result add_copyable(std::true_type, lua_State* L_, T& self, stack_object value) { + return add_associative(is_associative(), L_, self, value); + } + + template + static detail::error_result add_copyable(std::false_type, lua_State* L_, T& self, stack_object value, Iter&) { + return add_copyable(std::false_type(), L_, self, std::move(value)); + } + + static detail::error_result add_copyable(std::false_type, lua_State*, T&, stack_object) { + return detail::error_result("cannot call 'add' on '%s': value_type is non-copyable", detail::demangle().data()); + } + + static detail::error_result insert_lookup(std::true_type, lua_State* L_, T& self, stack_object, stack_object value) { + // TODO: should we warn or error about someone calling insert on an ordered / lookup container with no associativity? + return add_copyable(std::true_type(), L_, self, std::move(value)); + } + + static detail::error_result insert_lookup(std::false_type, lua_State* L_, T& self, stack_object where, stack_object value) { + auto it = deferred_uc::begin(L_, self); + auto key = where.as(); + key = static_cast(static_cast(key) + deferred_uc::index_adjustment(L_, self)); + std::advance(it, key); + self.insert(it, value.as()); + return {}; + } + + static detail::error_result insert_after_has(std::true_type, lua_State* L_, T& self, stack_object where, stack_object value) { + auto key = where.as(); + auto backit = self.before_begin(); + { + key = static_cast(static_cast(key) + deferred_uc::index_adjustment(L_, self)); + auto e = deferred_uc::end(L_, self); + for (auto it = deferred_uc::begin(L_, self); key > 0; ++backit, ++it, --key) { + if (backit == e) { + return detail::error_result("sol: out of bounds (too big) for set on '%s'", detail::demangle().c_str()); + } + } + } + self.insert_after(backit, value.as()); + return {}; + } + + static detail::error_result insert_after_has(std::false_type, lua_State*, T&, stack_object, stack_object) { + return detail::error_result( + "cannot call 'insert' on '%s': no suitable or similar functionality detected on this container", detail::demangle().data()); + } + + static detail::error_result insert_has(std::true_type, lua_State* L_, T& self, stack_object key, stack_object value) { + return insert_lookup(meta::any(), L_, self, std::move(key), std::move(value)); + } + + static detail::error_result insert_has(std::false_type, lua_State* L_, T& self, stack_object where, stack_object value) { + return insert_after_has(meta::has_insert_after(), L_, self, where, value); + } + + static detail::error_result insert_copyable(std::true_type, lua_State* L_, T& self, stack_object key, stack_object value) { + return insert_has(std::integral_constant < bool, + meta::has_insert::value || meta::has_insert_with_iterator::value > (), + L_, + self, + std::move(key), + std::move(value)); + } + + static detail::error_result insert_copyable(std::false_type, lua_State*, T&, stack_object, stack_object) { + return detail::error_result("cannot call 'insert' on '%s': value_type is non-copyable", detail::demangle().data()); + } + + static detail::error_result erase_integral(std::true_type, lua_State* L_, T& self, K& key) { + auto it = deferred_uc::begin(L_, self); + key = (static_cast(key) + deferred_uc::index_adjustment(L_, self)); + std::advance(it, key); + self.erase(it); + + return {}; + } + + static detail::error_result erase_integral(std::false_type, lua_State* L_, T& self, const K& key) { + auto fx = [&](const value_type& r) -> bool { return key == r; }; + auto e = deferred_uc::end(L_, self); + auto it = std::find_if(deferred_uc::begin(L_, self), e, std::ref(fx)); + if (it == e) { + return {}; + } + self.erase(it); + + return {}; + } + + static detail::error_result erase_associative_lookup(std::true_type, lua_State*, T& self, const K& key) { + self.erase(key); + return {}; + } + + static detail::error_result erase_associative_lookup(std::false_type, lua_State* L_, T& self, K& key) { + return erase_integral(std::is_integral(), L_, self, key); + } + + static detail::error_result erase_after_has(std::true_type, lua_State* L_, T& self, K& key) { + auto backit = self.before_begin(); + { + key = static_cast(static_cast(key) + deferred_uc::index_adjustment(L_, self)); + auto e = deferred_uc::end(L_, self); + for (auto it = deferred_uc::begin(L_, self); key > 0; ++backit, ++it, --key) { + if (backit == e) { + return detail::error_result("sol: out of bounds for erase on '%s'", detail::demangle().c_str()); + } + } + } + self.erase_after(backit); + return {}; + } + + static detail::error_result erase_after_has(std::false_type, lua_State*, T&, const K&) { + return detail::error_result("sol: cannot call erase on '%s'", detail::demangle().c_str()); + } + + static detail::error_result erase_key_has(std::true_type, lua_State* L_, T& self, K& key) { + return erase_associative_lookup(meta::any(), L_, self, key); + } + + static detail::error_result erase_key_has(std::false_type, lua_State* L_, T& self, K& key) { + return erase_after_has(has_erase_after(), L_, self, key); + } + + static detail::error_result erase_has(std::true_type, lua_State* L_, T& self, K& key) { + return erase_associative_lookup(meta::any(), L_, self, key); + } + + static detail::error_result erase_has(std::false_type, lua_State* L_, T& self, K& key) { + return erase_key_has(has_erase_key(), L_, self, key); + } + + static auto size_has(std::false_type, lua_State* L_, T& self) { + return std::distance(deferred_uc::begin(L_, self), deferred_uc::end(L_, self)); + } + + static auto size_has(std::true_type, lua_State*, T& self) { + return self.size(); + } + + static void clear_has(std::true_type, lua_State*, T& self) { + self.clear(); + } + + static void clear_has(std::false_type, lua_State* L_, T&) { + luaL_error(L_, "sol: cannot call clear on '%s'", detail::demangle().c_str()); + } + + static bool empty_has(std::true_type, lua_State*, T& self) { + return self.empty(); + } + + static bool empty_has(std::false_type, lua_State* L_, T& self) { + return deferred_uc::begin(L_, self) == deferred_uc::end(L_, self); + } + + static detail::error_result get_associative_find(std::true_type, lua_State* L_, T& self, K& key) { + auto it = self.find(key); + if (it == deferred_uc::end(L_, self)) { + stack::push(L_, lua_nil); + return {}; + } + return get_associative(std::true_type(), L_, it); + } + + static detail::error_result get_associative_find(std::false_type, lua_State* L_, T& self, K& key) { + return get_it(is_linear_integral(), L_, self, key); + } + + static detail::error_result get_start(lua_State* L_, T& self, K& key) { + return get_associative_find(std::integral_constant < bool, is_associative::value&& has_find::value > (), L_, self, key); + } + + static detail::error_result set_start(lua_State* L_, T& self, stack_object key, stack_object value) { + return set_it(is_linear_integral(), L_, self, std::move(key), std::move(value)); + } + + static std::size_t size_start(lua_State* L_, T& self) { + return static_cast(size_has(meta::has_size(), L_, self)); + } + + static void clear_start(lua_State* L_, T& self) { + clear_has(has_clear(), L_, self); + } + + static bool empty_start(lua_State* L_, T& self) { + return empty_has(has_empty(), L_, self); + } + + static detail::error_result erase_start(lua_State* L_, T& self, K& key) { + return erase_has(has_erase(), L_, self, key); + } + + template + static int next_associative(std::true_type, lua_State* L_) { + iter& i = stack::unqualified_get>(L_, 1); + auto& it = i.it; + auto& end = i.end; + if (it == end) { + return stack::push(L_, lua_nil); + } + int p; + if constexpr (ip) { + ++i.index; + p = stack::push_reference(L_, i.index); + } + else { + p = stack::push_reference(L_, it->first); + } + p += stack::stack_detail::push_reference(L_, detail::deref_move_only(it->second)); + std::advance(it, 1); + return p; + } + + template + static int next_associative(std::false_type, lua_State* L_) { + iter& i = stack::unqualified_get>(L_, 1); + auto& it = i.it(); + auto& end = i.sen(); + next_K k = stack::unqualified_get(L_, 2); + if (it == end) { + return stack::push(L_, lua_nil); + } + int p; + if constexpr (std::is_integral_v) { + p = stack::push_reference(L_, k + 1); + } + else { + p = stack::stack_detail::push_reference(L_, k + 1); + } + p += stack::stack_detail::push_reference(L_, detail::deref_move_only(*it)); + std::advance(it, 1); + return p; + } + + template + static int next_iter(lua_State* L_) { + typedef meta::any>> is_assoc; + return next_associative(is_assoc(), L_); + } + + template + static int pairs_associative(std::true_type, lua_State* L_) { + auto& src = get_src(L_); + stack::push(L_, next_iter); + stack::push>(L_, L_, 1, deferred_uc::begin(L_, src), deferred_uc::begin(L_, src)); + stack::push(L_, lua_nil); + return 3; + } + + template + static int pairs_associative(std::false_type, lua_State* L_) { + auto& src = get_src(L_); + stack::push(L_, next_iter); + stack::push>(L_, L_, 1, deferred_uc::begin(L_, src), deferred_uc::end(L_, src)); + stack::push(L_, 0); + return 3; + } + + public: + static int at(lua_State* L_) { + auto& self = get_src(L_); + detail::error_result er; + { + std::ptrdiff_t pos = stack::unqualified_get(L_, 2); + er = at_start(L_, self, pos); + } + return handle_errors(L_, er); + } + + static int get(lua_State* L_) { + auto& self = get_src(L_); + detail::error_result er; + { + decltype(auto) key = stack::unqualified_get(L_); + er = get_start(L_, self, key); + } + return handle_errors(L_, er); + } + + static int index_get(lua_State* L_) { + return get(L_); + } + + static int set(lua_State* L_) { + stack_object value = stack_object(L_, raw_index(3)); + if constexpr (is_linear_integral::value) { + // for non-associative containers, + // erasure only happens if it is the + // last index in the container + auto key = stack::get(L_, 2); + auto self_size = deferred_uc::size(L_); + if (key == static_cast(self_size)) { + if (type_of(L_, 3) == type::lua_nil) { + return erase(L_); + } + } + } + else { + if (type_of(L_, 3) == type::lua_nil) { + return erase(L_); + } + } + auto& self = get_src(L_); + detail::error_result er = set_start(L_, self, stack_object(L_, raw_index(2)), std::move(value)); + return handle_errors(L_, er); + } + + static int index_set(lua_State* L_) { + return set(L_); + } + + static int add(lua_State* L_) { + auto& self = get_src(L_); + detail::error_result er = add_copyable(is_copyable(), L_, self, stack_object(L_, raw_index(2))); + return handle_errors(L_, er); + } + + static int insert(lua_State* L_) { + auto& self = get_src(L_); + detail::error_result er = insert_copyable(is_copyable(), L_, self, stack_object(L_, raw_index(2)), stack_object(L_, raw_index(3))); + return handle_errors(L_, er); + } + + static int find(lua_State* L_) { + auto& self = get_src(L_); + detail::error_result er = find_has(has_find(), L_, self); + return handle_errors(L_, er); + } + + static int index_of(lua_State* L_) { + auto& self = get_src(L_); + detail::error_result er = find_has(has_find(), L_, self); + return handle_errors(L_, er); + } + + static iterator begin(lua_State*, T& self) { + if constexpr (meta::has_begin_end_v) { + return self.begin(); + } + else { + using std::begin; + return begin(self); + } + } + + static sentinel end(lua_State*, T& self) { + if constexpr (meta::has_begin_end_v) { + return self.end(); + } + else { + using std::end; + return end(self); + } + } + + static int size(lua_State* L_) { + auto& self = get_src(L_); + std::size_t r = size_start(L_, self); + return stack::push(L_, r); + } + + static int clear(lua_State* L_) { + auto& self = get_src(L_); + clear_start(L_, self); + return 0; + } + + static int erase(lua_State* L_) { + auto& self = get_src(L_); + detail::error_result er; + { + decltype(auto) key = stack::unqualified_get(L_, 2); + er = erase_start(L_, self, key); + } + return handle_errors(L_, er); + } + + static int empty(lua_State* L_) { + auto& self = get_src(L_); + return stack::push(L_, empty_start(L_, self)); + } + + static std::ptrdiff_t index_adjustment(lua_State*, T&) { + return static_cast((SOL_CONTAINER_START_INDEX_I_) == 0 ? 0 : -(SOL_CONTAINER_START_INDEX_I_)); + } + + static int pairs(lua_State* L_) { + typedef meta::any>> is_assoc; + return pairs_associative(is_assoc(), L_); + } + + static int ipairs(lua_State* L_) { + typedef meta::any>> is_assoc; + return pairs_associative(is_assoc(), L_); + } + + static int next(lua_State* L_) { + return stack::push(L_, next_iter); + } + }; + + template + struct usertype_container_default>>::value>> { + private: + typedef std::remove_pointer_t> T; + typedef usertype_container deferred_uc; + + public: + typedef std::remove_extent_t value_type; + typedef value_type* iterator; + typedef iterator sentinel; + + private: + struct iter : detail::ebco, detail::ebco { + using it_base = detail::ebco; + using sen_base = detail::ebco; + reference keep_alive; + + iter(lua_State* L_, int stack_index_, iterator it_, sentinel sen_) noexcept + : it_base(std::move(it_)), sen_base(std::move(sen_)), keep_alive(sol::main_thread(L_, L_), stack_index_) { + } + + iterator& it() noexcept { + return it_base::value(); + } + + const iterator& it() const noexcept { + return it_base::value(); + } + + sentinel& sen() noexcept { + return sen_base::value(); + } + + const sentinel& sen() const noexcept { + return sen_base::value(); + } + }; + + static auto& get_src(lua_State* L_) { + auto p = stack::unqualified_check_get(L_, 1); +#if SOL_IS_ON(SOL_SAFE_USERTYPE) + if (!p) { + luaL_error(L_, + "sol: 'self' is not of type '%s' (pass 'self' as first argument with ':' or call on proper type)", + detail::demangle().c_str()); + } + if (p.value() == nullptr) { + luaL_error( + L_, "sol: 'self' argument is nil (pass 'self' as first argument with ':' or call on a '%s' type)", detail::demangle().c_str()); + } +#endif // Safe getting with error + return *p.value(); + } + + static int find(std::true_type, lua_State* L_) { + T& self = get_src(L_); + decltype(auto) value = stack::unqualified_get(L_, 2); + std::size_t N = std::extent::value; + for (std::size_t idx = 0; idx < N; ++idx) { + using v_t = std::add_const_t; + v_t v = self[idx]; + if (v == value) { + idx = static_cast(static_cast(idx) - deferred_uc::index_adjustment(L_, self)); + return stack::push(L_, idx); + } + } + return stack::push(L_, lua_nil); + } + + static int find(std::false_type, lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'find' on '%s': no supported comparison operator for the value type", detail::demangle().c_str()); + } + + static int next_iter(lua_State* L_) { + iter& i = stack::unqualified_get>(L_, 1); + auto& it = i.it(); + auto& end = i.sen(); + std::size_t k = stack::unqualified_get(L_, 2); + if (it == end) { + return 0; + } + int p; + p = stack::push(L_, k + 1); + p += stack::push_reference(L_, detail::deref_move_only(*it)); + std::advance(it, 1); + return p; + } + + public: + static int clear(lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'clear' on type '%s': cannot remove all items from a fixed array", detail::demangle().c_str()); + } + + static int erase(lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'erase' on type '%s': cannot remove an item from fixed arrays", detail::demangle().c_str()); + } + + static int add(lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'add' on type '%s': cannot add to fixed arrays", detail::demangle().c_str()); + } + + static int insert(lua_State* L_) { + return luaL_error(L_, "sol: cannot call 'insert' on type '%s': cannot insert new entries into fixed arrays", detail::demangle().c_str()); + } + + static int at(lua_State* L_) { + return get(L_); + } + + static int get(lua_State* L_) { + T& self = get_src(L_); + std::ptrdiff_t idx = stack::unqualified_get(L_, 2); + idx += deferred_uc::index_adjustment(L_, self); + if (idx >= static_cast(std::extent::value) || idx < 0) { + return stack::push(L_, lua_nil); + } + return stack::push_reference(L_, detail::deref_move_only(self[idx])); + } + + static int index_get(lua_State* L_) { + return get(L_); + } + + static int set(lua_State* L_) { + T& self = get_src(L_); + std::ptrdiff_t idx = stack::unqualified_get(L_, 2); + idx += deferred_uc::index_adjustment(L_, self); + if (idx >= static_cast(std::extent::value)) { + return luaL_error(L_, "sol: index out of bounds (too big) for set on '%s'", detail::demangle().c_str()); + } + if (idx < 0) { + return luaL_error(L_, "sol: index out of bounds (too small) for set on '%s'", detail::demangle().c_str()); + } + self[idx] = stack::unqualified_get(L_, 3); + return 0; + } + + static int index_set(lua_State* L_) { + return set(L_); + } + + static int index_of(lua_State* L_) { + return find(L_); + } + + static int find(lua_State* L_) { + return find(meta::supports_op_equal(), L_); + } + + static int size(lua_State* L_) { + return stack::push(L_, std::extent::value); + } + + static int empty(lua_State* L_) { + return stack::push(L_, std::extent::value > 0); + } + + static int pairs(lua_State* L_) { + auto& src = get_src(L_); + stack::push(L_, next_iter); + stack::push>(L_, L_, 1, deferred_uc::begin(L_, src), deferred_uc::end(L_, src)); + stack::push(L_, 0); + return 3; + } + + static int ipairs(lua_State* L_) { + return pairs(L_); + } + + static int next(lua_State* L_) { + return stack::push(L_, next_iter); + } + + static std::ptrdiff_t index_adjustment(lua_State*, T&) { + return (SOL_CONTAINER_START_INDEX_I_) == 0 ? 0 : -(SOL_CONTAINER_START_INDEX_I_); + } + + static iterator begin(lua_State*, T& self) { + return std::addressof(self[0]); + } + + static sentinel end(lua_State*, T& self) { + return std::addressof(self[0]) + std::extent::value; + } + }; + + template + struct usertype_container_default> : usertype_container_default { }; + } // namespace container_detail + + template + struct usertype_container : container_detail::usertype_container_default { }; + +} // namespace sol + +#endif // SOL_USERTYPE_CONTAINER_HPP + +// sol2 + +// The MIT License (MIT) + +// Copyright (c) 2013-2022 Rapptz, ThePhD and contributors + +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#ifndef SOL_POINTER_LIKE_HPP +#define SOL_POINTER_LIKE_HPP + +#include + +#include +#include +#include + +namespace sol { + + namespace meta { + namespace meta_detail { + template + using is_dereferenceable_test = decltype(*std::declval()); + + template + using is_explicitly_dereferenceable_test = decltype(std::declval().operator*()); + } // namespace meta_detail + + template + using is_pointer_like = std::integral_constant && (std::is_pointer_v || is_detected_v)>; + + template + constexpr inline bool is_pointer_like_v = is_pointer_like::value; + } // namespace meta + + namespace detail { + + template + auto unwrap(T&& item) -> decltype(std::forward(item)) { + return std::forward(item); + } + + template + T& unwrap(std::reference_wrapper arg) { + return arg.get(); + } + + template + inline decltype(auto) deref(T&& item) { + using Tu = meta::unqualified_t; + if constexpr (meta::is_pointer_like_v) { + return *std::forward(item); + } + else { + return std::forward(item); + } + } + + template + inline decltype(auto) deref_move_only(T&& item) { + using Tu = meta::unqualified_t; + if constexpr (meta::is_pointer_like_v && !std::is_pointer_v && !std::is_copy_constructible_v) { + return *std::forward(item); + } + else { + return std::forward(item); + } + } + + template + inline T* ptr(T& val) { + return std::addressof(val); + } + + template + inline T* ptr(std::reference_wrapper val) { + return std::addressof(val.get()); + } + + template + inline T* ptr(T* val) { + return val; + } + } // namespace detail +} // namespace sol + +#endif // SOL_POINTER_LIKE_HPP + +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_VECTOR +#define _LIBCPP_VECTOR + +/* + vector synopsis + +namespace std +{ + +template > +class vector +{ +public: + typedef T value_type; + typedef Allocator allocator_type; + typedef typename allocator_type::reference reference; + typedef typename allocator_type::const_reference const_reference; + typedef implementation-defined iterator; + typedef implementation-defined const_iterator; + typedef typename allocator_type::size_type size_type; + typedef typename allocator_type::difference_type difference_type; + typedef typename allocator_type::pointer pointer; + typedef typename allocator_type::const_pointer const_pointer; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + + vector() + noexcept(is_nothrow_default_constructible::value); + explicit vector(const allocator_type&); + explicit vector(size_type n); + explicit vector(size_type n, const allocator_type&); // C++14 + vector(size_type n, const value_type& value, const allocator_type& = allocator_type()); + template + vector(InputIterator first, InputIterator last, const allocator_type& = allocator_type()); + vector(const vector& x); + vector(vector&& x) + noexcept(is_nothrow_move_constructible::value); + vector(initializer_list il); + vector(initializer_list il, const allocator_type& a); + ~vector(); + vector& operator=(const vector& x); + vector& operator=(vector&& x) + noexcept( + allocator_type::propagate_on_container_move_assignment::value || + allocator_type::is_always_equal::value); // C++17 + vector& operator=(initializer_list il); + template + void assign(InputIterator first, InputIterator last); + void assign(size_type n, const value_type& u); + void assign(initializer_list il); + + allocator_type get_allocator() const noexcept; + + iterator begin() noexcept; + const_iterator begin() const noexcept; + iterator end() noexcept; + const_iterator end() const noexcept; + + reverse_iterator rbegin() noexcept; + const_reverse_iterator rbegin() const noexcept; + reverse_iterator rend() noexcept; + const_reverse_iterator rend() const noexcept; + + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + const_reverse_iterator crbegin() const noexcept; + const_reverse_iterator crend() const noexcept; + + size_type size() const noexcept; + size_type max_size() const noexcept; + size_type capacity() const noexcept; + bool empty() const noexcept; + void reserve(size_type n); + void shrink_to_fit() noexcept; + + reference operator[](size_type n); + const_reference operator[](size_type n) const; + reference at(size_type n); + const_reference at(size_type n) const; + + reference front(); + const_reference front() const; + reference back(); + const_reference back() const; + + value_type* data() noexcept; + const value_type* data() const noexcept; + + void push_back(const value_type& x); + void push_back(value_type&& x); + template + reference emplace_back(Args&&... args); // reference in C++17 + void pop_back(); + + template iterator emplace(const_iterator position, Args&&... args); + iterator insert(const_iterator position, const value_type& x); + iterator insert(const_iterator position, value_type&& x); + iterator insert(const_iterator position, size_type n, const value_type& x); + template + iterator insert(const_iterator position, InputIterator first, InputIterator last); + iterator insert(const_iterator position, initializer_list il); + + iterator erase(const_iterator position); + iterator erase(const_iterator first, const_iterator last); + + void clear() noexcept; + + void resize(size_type sz); + void resize(size_type sz, const value_type& c); + + void swap(vector&) + noexcept(allocator_traits::propagate_on_container_swap::value || + allocator_traits::is_always_equal::value); // C++17 + + bool __invariants() const; +}; + +template > +class vector +{ +public: + typedef bool value_type; + typedef Allocator allocator_type; + typedef implementation-defined iterator; + typedef implementation-defined const_iterator; + typedef typename allocator_type::size_type size_type; + typedef typename allocator_type::difference_type difference_type; + typedef iterator pointer; + typedef const_iterator const_pointer; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + + class reference + { + public: + reference(const reference&) noexcept; + operator bool() const noexcept; + reference& operator=(bool x) noexcept; + reference& operator=(const reference& x) noexcept; + iterator operator&() const noexcept; + void flip() noexcept; + }; + + class const_reference + { + public: + const_reference(const reference&) noexcept; + operator bool() const noexcept; + const_iterator operator&() const noexcept; + }; + + vector() + noexcept(is_nothrow_default_constructible::value); + explicit vector(const allocator_type&); + explicit vector(size_type n, const allocator_type& a = allocator_type()); // C++14 + vector(size_type n, const value_type& value, const allocator_type& = allocator_type()); + template + vector(InputIterator first, InputIterator last, const allocator_type& = allocator_type()); + vector(const vector& x); + vector(vector&& x) + noexcept(is_nothrow_move_constructible::value); + vector(initializer_list il); + vector(initializer_list il, const allocator_type& a); + ~vector(); + vector& operator=(const vector& x); + vector& operator=(vector&& x) + noexcept( + allocator_type::propagate_on_container_move_assignment::value || + allocator_type::is_always_equal::value); // C++17 + vector& operator=(initializer_list il); + template + void assign(InputIterator first, InputIterator last); + void assign(size_type n, const value_type& u); + void assign(initializer_list il); + + allocator_type get_allocator() const noexcept; + + iterator begin() noexcept; + const_iterator begin() const noexcept; + iterator end() noexcept; + const_iterator end() const noexcept; + + reverse_iterator rbegin() noexcept; + const_reverse_iterator rbegin() const noexcept; + reverse_iterator rend() noexcept; + const_reverse_iterator rend() const noexcept; + + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + const_reverse_iterator crbegin() const noexcept; + const_reverse_iterator crend() const noexcept; + + size_type size() const noexcept; + size_type max_size() const noexcept; + size_type capacity() const noexcept; + bool empty() const noexcept; + void reserve(size_type n); + void shrink_to_fit() noexcept; + + reference operator[](size_type n); + const_reference operator[](size_type n) const; + reference at(size_type n); + const_reference at(size_type n) const; + + reference front(); + const_reference front() const; + reference back(); + const_reference back() const; + + void push_back(const value_type& x); + template reference emplace_back(Args&&... args); // C++14; reference in C++17 + void pop_back(); + + template iterator emplace(const_iterator position, Args&&... args); // C++14 + iterator insert(const_iterator position, const value_type& x); + iterator insert(const_iterator position, size_type n, const value_type& x); + template + iterator insert(const_iterator position, InputIterator first, InputIterator last); + iterator insert(const_iterator position, initializer_list il); + + iterator erase(const_iterator position); + iterator erase(const_iterator first, const_iterator last); + + void clear() noexcept; + + void resize(size_type sz); + void resize(size_type sz, value_type x); + + void swap(vector&) + noexcept(allocator_traits::propagate_on_container_swap::value || + allocator_traits::is_always_equal::value); // C++17 + void flip() noexcept; + + bool __invariants() const; +}; + +template ::value_type>> + vector(InputIterator, InputIterator, Allocator = Allocator()) + -> vector::value_type, Allocator>; // C++17 + +template struct hash>; + +template bool operator==(const vector& x, const vector& y); +template bool operator< (const vector& x, const vector& y); +template bool operator!=(const vector& x, const vector& y); +template bool operator> (const vector& x, const vector& y); +template bool operator>=(const vector& x, const vector& y); +template bool operator<=(const vector& x, const vector& y); + +template +void swap(vector& x, vector& y) + noexcept(noexcept(x.swap(y))); + +template +typename vector::size_type +erase(vector& c, const U& value); // C++20 +template +typename vector::size_type +erase_if(vector& c, Predicate pred); // C++20 + + +template + inline constexpr bool is-vector-bool-reference = see below; // exposition only, since C++23 + +template requires is-vector-bool-reference // Since C++23 + struct formatter; + +} // std + +*/ + +#include <__algorithm/copy.h> +#include <__algorithm/equal.h> +#include <__algorithm/fill_n.h> +#include <__algorithm/lexicographical_compare.h> +#include <__algorithm/remove.h> +#include <__algorithm/remove_if.h> +#include <__algorithm/rotate.h> +#include <__algorithm/unwrap_iter.h> +#include <__assert> // all public C++ headers provide the assertion handler +#include <__bit_reference> +#include <__concepts/same_as.h> +#include <__config> +#include <__debug> +#include <__format/enable_insertable.h> +#include <__format/formatter.h> +#include <__functional/hash.h> +#include <__functional/unary_function.h> +#include <__iterator/advance.h> +#include <__iterator/iterator_traits.h> +#include <__iterator/reverse_iterator.h> +#include <__iterator/wrap_iter.h> +#include <__memory/allocate_at_least.h> +#include <__memory/pointer_traits.h> +#include <__memory/swap_allocator.h> +#include <__memory/temp_value.h> +#include <__memory/uninitialized_algorithms.h> +#include <__memory_resource/polymorphic_allocator.h> +#include <__split_buffer> +#include <__type_traits/is_allocator.h> +#include <__type_traits/noexcept_move_assign_container.h> +#include <__utility/exception_guard.h> +#include <__utility/forward.h> +#include <__utility/move.h> +#include <__utility/swap.h> +#include +#include +#include +#include // for forward declaration of vector +#include +#include +#include +#include + +// standard-mandated includes + +// [iterator.range] +#include <__iterator/access.h> +#include <__iterator/data.h> +#include <__iterator/empty.h> +#include <__iterator/reverse_access.h> +#include <__iterator/size.h> + +// [vector.syn] +#include +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + + +_LIBCPP_BEGIN_NAMESPACE_STD + +template */> +class _LIBCPP_TEMPLATE_VIS vector +{ +private: + typedef allocator<_Tp> __default_allocator_type; +public: + typedef vector __self; + typedef _Tp value_type; + typedef _Allocator allocator_type; + typedef allocator_traits __alloc_traits; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef typename __alloc_traits::size_type size_type; + typedef typename __alloc_traits::difference_type difference_type; + typedef typename __alloc_traits::pointer pointer; + typedef typename __alloc_traits::const_pointer const_pointer; + // TODO: Implement iterator bounds checking without requiring the global database. + typedef __wrap_iter iterator; + typedef __wrap_iter const_iterator; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + + static_assert((is_same::value), + "Allocator::value_type must be same type as value_type"); + + static_assert(is_same >::value, + "[allocator.requirements] states that rebinding an allocator to the same type should result in the " + "original allocator"); + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + vector() _NOEXCEPT_(is_nothrow_default_constructible::value) + { + std::__debug_db_insert_c(this); + } + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI explicit vector(const allocator_type& __a) +#if _LIBCPP_STD_VER <= 14 + _NOEXCEPT_(is_nothrow_copy_constructible::value) +#else + _NOEXCEPT +#endif + : __end_cap_(nullptr, __a) + { + std::__debug_db_insert_c(this); + } + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI explicit vector(size_type __n); +#if _LIBCPP_STD_VER > 11 + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI explicit vector(size_type __n, const allocator_type& __a); +#endif + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI vector(size_type __n, const value_type& __x); + + template ::value> > + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + vector(size_type __n, const value_type& __x, const allocator_type& __a) + : __end_cap_(nullptr, __a) + { + std::__debug_db_insert_c(this); + if (__n > 0) + { + __vallocate(__n); + __construct_at_end(__n, __x); + } + } + + template ::value && + is_constructible::reference>::value, + int> = 0> + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI vector(_InputIterator __first, _InputIterator __last); + template ::value && + is_constructible::reference>::value, + int> = 0> + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + vector(_InputIterator __first, _InputIterator __last, const allocator_type& __a); + + template < + class _ForwardIterator, + __enable_if_t<__is_cpp17_forward_iterator<_ForwardIterator>::value && + is_constructible::reference>::value, + int> = 0> + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI vector(_ForwardIterator __first, _ForwardIterator __last); + + template ::value && + is_constructible::reference>::value, + int> = 0> + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + vector(_ForwardIterator __first, _ForwardIterator __last, const allocator_type& __a); + +private: + class __destroy_vector { + public: + _LIBCPP_CONSTEXPR __destroy_vector(vector& __vec) : __vec_(__vec) {} + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void operator()() { + __vec_.__annotate_delete(); + std::__debug_db_erase_c(std::addressof(__vec_)); + + if (__vec_.__begin_ != nullptr) { + __vec_.__clear(); + __alloc_traits::deallocate(__vec_.__alloc(), __vec_.__begin_, __vec_.capacity()); + } + } + + private: + vector& __vec_; + }; + +public: + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI ~vector() { __destroy_vector(*this)(); } + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI vector(const vector& __x); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI vector(const vector& __x, const __type_identity_t& __a); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + vector& operator=(const vector& __x); + +#ifndef _LIBCPP_CXX03_LANG + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + vector(initializer_list __il); + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + vector(initializer_list __il, const allocator_type& __a); + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + vector& operator=(initializer_list __il) + {assign(__il.begin(), __il.end()); return *this;} +#endif // !_LIBCPP_CXX03_LANG + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + vector(vector&& __x) +#if _LIBCPP_STD_VER > 14 + noexcept; +#else + _NOEXCEPT_(is_nothrow_move_constructible::value); +#endif + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + vector(vector&& __x, const __type_identity_t& __a); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + vector& operator=(vector&& __x) + _NOEXCEPT_((__noexcept_move_assign_container<_Allocator, __alloc_traits>::value)); + + template ::value && + is_constructible::reference>::value, + int> = 0> + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void assign(_InputIterator __first, _InputIterator __last); + template < + class _ForwardIterator, + __enable_if_t<__is_cpp17_forward_iterator<_ForwardIterator>::value && + is_constructible::reference>::value, + int> = 0> + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void assign(_ForwardIterator __first, _ForwardIterator __last); + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void assign(size_type __n, const_reference __u); + +#ifndef _LIBCPP_CXX03_LANG + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void assign(initializer_list __il) + {assign(__il.begin(), __il.end());} +#endif + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + allocator_type get_allocator() const _NOEXCEPT + {return this->__alloc();} + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator begin() _NOEXCEPT; + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_iterator begin() const _NOEXCEPT; + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator end() _NOEXCEPT; + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_iterator end() const _NOEXCEPT; + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + reverse_iterator rbegin() _NOEXCEPT + {return reverse_iterator(end());} + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + const_reverse_iterator rbegin() const _NOEXCEPT + {return const_reverse_iterator(end());} + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + reverse_iterator rend() _NOEXCEPT + {return reverse_iterator(begin());} + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + const_reverse_iterator rend() const _NOEXCEPT + {return const_reverse_iterator(begin());} + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + const_iterator cbegin() const _NOEXCEPT + {return begin();} + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + const_iterator cend() const _NOEXCEPT + {return end();} + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + const_reverse_iterator crbegin() const _NOEXCEPT + {return rbegin();} + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + const_reverse_iterator crend() const _NOEXCEPT + {return rend();} + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + size_type size() const _NOEXCEPT + {return static_cast(this->__end_ - this->__begin_);} + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + size_type capacity() const _NOEXCEPT + {return static_cast(__end_cap() - this->__begin_);} + _LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + bool empty() const _NOEXCEPT + {return this->__begin_ == this->__end_;} + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type max_size() const _NOEXCEPT; + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void reserve(size_type __n); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void shrink_to_fit() _NOEXCEPT; + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI reference operator[](size_type __n) _NOEXCEPT; + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_reference operator[](size_type __n) const _NOEXCEPT; + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI reference at(size_type __n); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_reference at(size_type __n) const; + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI reference front() _NOEXCEPT + { + _LIBCPP_ASSERT(!empty(), "front() called on an empty vector"); + return *this->__begin_; + } + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_reference front() const _NOEXCEPT + { + _LIBCPP_ASSERT(!empty(), "front() called on an empty vector"); + return *this->__begin_; + } + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI reference back() _NOEXCEPT + { + _LIBCPP_ASSERT(!empty(), "back() called on an empty vector"); + return *(this->__end_ - 1); + } + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_reference back() const _NOEXCEPT + { + _LIBCPP_ASSERT(!empty(), "back() called on an empty vector"); + return *(this->__end_ - 1); + } + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + value_type* data() _NOEXCEPT + {return std::__to_address(this->__begin_);} + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + const value_type* data() const _NOEXCEPT + {return std::__to_address(this->__begin_);} + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void push_back(const_reference __x); + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void push_back(value_type&& __x); + + template + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI +#if _LIBCPP_STD_VER > 14 + reference emplace_back(_Args&&... __args); +#else + void emplace_back(_Args&&... __args); +#endif + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void pop_back(); + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator insert(const_iterator __position, const_reference __x); + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator insert(const_iterator __position, value_type&& __x); + template + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator emplace(const_iterator __position, _Args&&... __args); + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + iterator insert(const_iterator __position, size_type __n, const_reference __x); + + template ::value && + is_constructible< value_type, typename iterator_traits<_InputIterator>::reference>::value, + int> = 0> + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator + insert(const_iterator __position, _InputIterator __first, _InputIterator __last); + + template < + class _ForwardIterator, + __enable_if_t<__is_cpp17_forward_iterator<_ForwardIterator>::value && + is_constructible< value_type, typename iterator_traits<_ForwardIterator>::reference>::value, + int> = 0> + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator + insert(const_iterator __position, _ForwardIterator __first, _ForwardIterator __last); + +#ifndef _LIBCPP_CXX03_LANG + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + iterator insert(const_iterator __position, initializer_list __il) + {return insert(__position, __il.begin(), __il.end());} +#endif + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __position); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator erase(const_iterator __first, const_iterator __last); + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void clear() _NOEXCEPT + { + size_type __old_size = size(); + __clear(); + __annotate_shrink(__old_size); + std::__debug_db_invalidate_all(this); + } + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void resize(size_type __sz); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void resize(size_type __sz, const_reference __x); + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void swap(vector&) +#if _LIBCPP_STD_VER >= 14 + _NOEXCEPT; +#else + _NOEXCEPT_(!__alloc_traits::propagate_on_container_swap::value || + __is_nothrow_swappable::value); +#endif + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool __invariants() const; + +#ifdef _LIBCPP_ENABLE_DEBUG_MODE + + bool __dereferenceable(const const_iterator* __i) const; + bool __decrementable(const const_iterator* __i) const; + bool __addable(const const_iterator* __i, ptrdiff_t __n) const; + bool __subscriptable(const const_iterator* __i, ptrdiff_t __n) const; + +#endif // _LIBCPP_ENABLE_DEBUG_MODE + +private: + pointer __begin_ = nullptr; + pointer __end_ = nullptr; + __compressed_pair __end_cap_ = + __compressed_pair(nullptr, __default_init_tag()); + + _LIBCPP_HIDE_FROM_ABI void __invalidate_iterators_past(pointer __new_last); + + // Allocate space for __n objects + // throws length_error if __n > max_size() + // throws (probably bad_alloc) if memory run out + // Precondition: __begin_ == __end_ == __end_cap() == 0 + // Precondition: __n > 0 + // Postcondition: capacity() >= __n + // Postcondition: size() == 0 + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __vallocate(size_type __n) { + if (__n > max_size()) + __throw_length_error(); + auto __allocation = std::__allocate_at_least(__alloc(), __n); + __begin_ = __allocation.ptr; + __end_ = __allocation.ptr; + __end_cap() = __begin_ + __allocation.count; + __annotate_new(0); + } + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __vdeallocate() _NOEXCEPT; + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type __recommend(size_type __new_size) const; + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __construct_at_end(size_type __n); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void __construct_at_end(size_type __n, const_reference __x); + + template ::value, int> = 0> + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void + __construct_at_end(_ForwardIterator __first, _ForwardIterator __last, size_type __n); + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __append(size_type __n); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __append(size_type __n, const_reference __x); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + iterator __make_iter(pointer __p) _NOEXCEPT { return iterator(this, __p); } + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + const_iterator __make_iter(const_pointer __p) const _NOEXCEPT { return const_iterator(this, __p); } + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __swap_out_circular_buffer(__split_buffer& __v); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI pointer __swap_out_circular_buffer(__split_buffer& __v, pointer __p); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __move_range(pointer __from_s, pointer __from_e, pointer __to); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __move_assign(vector& __c, true_type) + _NOEXCEPT_(is_nothrow_move_assignable::value); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void __move_assign(vector& __c, false_type) + _NOEXCEPT_(__alloc_traits::is_always_equal::value); + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void __destruct_at_end(pointer __new_last) _NOEXCEPT + { + if (!__libcpp_is_constant_evaluated()) + __invalidate_iterators_past(__new_last); + size_type __old_size = size(); + __base_destruct_at_end(__new_last); + __annotate_shrink(__old_size); + } + + template + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + inline void __push_back_slow_path(_Up&& __x); + + template + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + inline void __emplace_back_slow_path(_Args&&... __args); + + // The following functions are no-ops outside of AddressSanitizer mode. + // We call annotatations only for the default Allocator because other allocators + // may not meet the AddressSanitizer alignment constraints. + // See the documentation for __sanitizer_annotate_contiguous_container for more details. +#ifndef _LIBCPP_HAS_NO_ASAN + _LIBCPP_CONSTEXPR_SINCE_CXX20 + void __annotate_contiguous_container(const void *__beg, const void *__end, + const void *__old_mid, + const void *__new_mid) const + { + + if (!__libcpp_is_constant_evaluated() && __beg && is_same::value) + __sanitizer_annotate_contiguous_container(__beg, __end, __old_mid, __new_mid); + } +#else + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void __annotate_contiguous_container(const void*, const void*, const void*, + const void*) const _NOEXCEPT {} +#endif + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void __annotate_new(size_type __current_size) const _NOEXCEPT { + __annotate_contiguous_container(data(), data() + capacity(), + data() + capacity(), data() + __current_size); + } + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void __annotate_delete() const _NOEXCEPT { + __annotate_contiguous_container(data(), data() + capacity(), + data() + size(), data() + capacity()); + } + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void __annotate_increase(size_type __n) const _NOEXCEPT + { + __annotate_contiguous_container(data(), data() + capacity(), + data() + size(), data() + size() + __n); + } + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void __annotate_shrink(size_type __old_size) const _NOEXCEPT + { + __annotate_contiguous_container(data(), data() + capacity(), + data() + __old_size, data() + size()); + } + + struct _ConstructTransaction { + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + explicit _ConstructTransaction(vector &__v, size_type __n) + : __v_(__v), __pos_(__v.__end_), __new_end_(__v.__end_ + __n) { +#ifndef _LIBCPP_HAS_NO_ASAN + __v_.__annotate_increase(__n); +#endif + } + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI ~_ConstructTransaction() { + __v_.__end_ = __pos_; +#ifndef _LIBCPP_HAS_NO_ASAN + if (__pos_ != __new_end_) { + __v_.__annotate_shrink(__new_end_ - __v_.__begin_); + } +#endif + } + + vector &__v_; + pointer __pos_; + const_pointer const __new_end_; + + private: + _ConstructTransaction(_ConstructTransaction const&) = delete; + _ConstructTransaction& operator=(_ConstructTransaction const&) = delete; + }; + + template + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void __construct_one_at_end(_Args&& ...__args) { + _ConstructTransaction __tx(*this, 1); + __alloc_traits::construct(this->__alloc(), std::__to_address(__tx.__pos_), + std::forward<_Args>(__args)...); + ++__tx.__pos_; + } + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + allocator_type& __alloc() _NOEXCEPT + {return this->__end_cap_.second();} + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + const allocator_type& __alloc() const _NOEXCEPT + {return this->__end_cap_.second();} + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + pointer& __end_cap() _NOEXCEPT + {return this->__end_cap_.first();} + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + const pointer& __end_cap() const _NOEXCEPT + {return this->__end_cap_.first();} + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void __clear() _NOEXCEPT {__base_destruct_at_end(this->__begin_);} + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void __base_destruct_at_end(pointer __new_last) _NOEXCEPT { + pointer __soon_to_be_end = this->__end_; + while (__new_last != __soon_to_be_end) + __alloc_traits::destroy(__alloc(), std::__to_address(--__soon_to_be_end)); + this->__end_ = __new_last; + } + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void __copy_assign_alloc(const vector& __c) + {__copy_assign_alloc(__c, integral_constant());} + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void __move_assign_alloc(vector& __c) + _NOEXCEPT_( + !__alloc_traits::propagate_on_container_move_assignment::value || + is_nothrow_move_assignable::value) + {__move_assign_alloc(__c, integral_constant());} + + _LIBCPP_NORETURN _LIBCPP_HIDE_FROM_ABI + void __throw_length_error() const { + std::__throw_length_error("vector"); + } + + _LIBCPP_NORETURN _LIBCPP_HIDE_FROM_ABI + void __throw_out_of_range() const { + std::__throw_out_of_range("vector"); + } + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void __copy_assign_alloc(const vector& __c, true_type) + { + if (__alloc() != __c.__alloc()) + { + __clear(); + __alloc_traits::deallocate(__alloc(), this->__begin_, capacity()); + this->__begin_ = this->__end_ = __end_cap() = nullptr; + } + __alloc() = __c.__alloc(); + } + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void __copy_assign_alloc(const vector&, false_type) + {} + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void __move_assign_alloc(vector& __c, true_type) + _NOEXCEPT_(is_nothrow_move_assignable::value) + { + __alloc() = std::move(__c.__alloc()); + } + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI + void __move_assign_alloc(vector&, false_type) + _NOEXCEPT + {} +}; + +#if _LIBCPP_STD_VER >= 17 +template>, + class = enable_if_t<__is_cpp17_input_iterator<_InputIterator>::value>, + class = enable_if_t<__is_allocator<_Alloc>::value> + > +vector(_InputIterator, _InputIterator) + -> vector<__iter_value_type<_InputIterator>, _Alloc>; + +template::value>, + class = enable_if_t<__is_allocator<_Alloc>::value> + > +vector(_InputIterator, _InputIterator, _Alloc) + -> vector<__iter_value_type<_InputIterator>, _Alloc>; +#endif + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +void +vector<_Tp, _Allocator>::__swap_out_circular_buffer(__split_buffer& __v) +{ + __annotate_delete(); + using _RevIter = std::reverse_iterator; + __v.__begin_ = std::__uninitialized_allocator_move_if_noexcept( + __alloc(), _RevIter(__end_), _RevIter(__begin_), _RevIter(__v.__begin_)) + .base(); + std::swap(this->__begin_, __v.__begin_); + std::swap(this->__end_, __v.__end_); + std::swap(this->__end_cap(), __v.__end_cap()); + __v.__first_ = __v.__begin_; + __annotate_new(size()); + std::__debug_db_invalidate_all(this); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +typename vector<_Tp, _Allocator>::pointer +vector<_Tp, _Allocator>::__swap_out_circular_buffer(__split_buffer& __v, pointer __p) +{ + __annotate_delete(); + pointer __r = __v.__begin_; + using _RevIter = std::reverse_iterator; + __v.__begin_ = std::__uninitialized_allocator_move_if_noexcept( + __alloc(), _RevIter(__p), _RevIter(__begin_), _RevIter(__v.__begin_)) + .base(); + __v.__end_ = std::__uninitialized_allocator_move_if_noexcept(__alloc(), __p, __end_, __v.__end_); + std::swap(this->__begin_, __v.__begin_); + std::swap(this->__end_, __v.__end_); + std::swap(this->__end_cap(), __v.__end_cap()); + __v.__first_ = __v.__begin_; + __annotate_new(size()); + std::__debug_db_invalidate_all(this); + return __r; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +void +vector<_Tp, _Allocator>::__vdeallocate() _NOEXCEPT +{ + if (this->__begin_ != nullptr) + { + clear(); + __alloc_traits::deallocate(this->__alloc(), this->__begin_, capacity()); + this->__begin_ = this->__end_ = this->__end_cap() = nullptr; + } +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +typename vector<_Tp, _Allocator>::size_type +vector<_Tp, _Allocator>::max_size() const _NOEXCEPT +{ + return std::min(__alloc_traits::max_size(this->__alloc()), + numeric_limits::max()); +} + +// Precondition: __new_size > capacity() +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +typename vector<_Tp, _Allocator>::size_type +vector<_Tp, _Allocator>::__recommend(size_type __new_size) const +{ + const size_type __ms = max_size(); + if (__new_size > __ms) + this->__throw_length_error(); + const size_type __cap = capacity(); + if (__cap >= __ms / 2) + return __ms; + return std::max(2 * __cap, __new_size); +} + +// Default constructs __n objects starting at __end_ +// throws if construction throws +// Precondition: __n > 0 +// Precondition: size() + __n <= capacity() +// Postcondition: size() == size() + __n +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +void +vector<_Tp, _Allocator>::__construct_at_end(size_type __n) +{ + _ConstructTransaction __tx(*this, __n); + const_pointer __new_end = __tx.__new_end_; + for (pointer __pos = __tx.__pos_; __pos != __new_end; __tx.__pos_ = ++__pos) { + __alloc_traits::construct(this->__alloc(), std::__to_address(__pos)); + } +} + +// Copy constructs __n objects starting at __end_ from __x +// throws if construction throws +// Precondition: __n > 0 +// Precondition: size() + __n <= capacity() +// Postcondition: size() == old size() + __n +// Postcondition: [i] == __x for all i in [size() - __n, __n) +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline +void +vector<_Tp, _Allocator>::__construct_at_end(size_type __n, const_reference __x) +{ + _ConstructTransaction __tx(*this, __n); + const_pointer __new_end = __tx.__new_end_; + for (pointer __pos = __tx.__pos_; __pos != __new_end; __tx.__pos_ = ++__pos) { + __alloc_traits::construct(this->__alloc(), std::__to_address(__pos), __x); + } +} + +template +template ::value, int> > +_LIBCPP_CONSTEXPR_SINCE_CXX20 void +vector<_Tp, _Allocator>::__construct_at_end(_ForwardIterator __first, _ForwardIterator __last, size_type __n) +{ + _ConstructTransaction __tx(*this, __n); + __tx.__pos_ = std::__uninitialized_allocator_copy(__alloc(), __first, __last, __tx.__pos_); +} + +// Default constructs __n objects starting at __end_ +// throws if construction throws +// Postcondition: size() == size() + __n +// Exception safety: strong. +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +void +vector<_Tp, _Allocator>::__append(size_type __n) +{ + if (static_cast(this->__end_cap() - this->__end_) >= __n) + this->__construct_at_end(__n); + else + { + allocator_type& __a = this->__alloc(); + __split_buffer __v(__recommend(size() + __n), size(), __a); + __v.__construct_at_end(__n); + __swap_out_circular_buffer(__v); + } +} + +// Default constructs __n objects starting at __end_ +// throws if construction throws +// Postcondition: size() == size() + __n +// Exception safety: strong. +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +void +vector<_Tp, _Allocator>::__append(size_type __n, const_reference __x) +{ + if (static_cast(this->__end_cap() - this->__end_) >= __n) + this->__construct_at_end(__n, __x); + else + { + allocator_type& __a = this->__alloc(); + __split_buffer __v(__recommend(size() + __n), size(), __a); + __v.__construct_at_end(__n, __x); + __swap_out_circular_buffer(__v); + } +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector<_Tp, _Allocator>::vector(size_type __n) +{ + auto __guard = std::__make_exception_guard(__destroy_vector(*this)); + std::__debug_db_insert_c(this); + if (__n > 0) + { + __vallocate(__n); + __construct_at_end(__n); + } + __guard.__complete(); +} + +#if _LIBCPP_STD_VER > 11 +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector<_Tp, _Allocator>::vector(size_type __n, const allocator_type& __a) + : __end_cap_(nullptr, __a) +{ + auto __guard = std::__make_exception_guard(__destroy_vector(*this)); + std::__debug_db_insert_c(this); + if (__n > 0) + { + __vallocate(__n); + __construct_at_end(__n); + } + __guard.__complete(); +} +#endif + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector<_Tp, _Allocator>::vector(size_type __n, const value_type& __x) +{ + auto __guard = std::__make_exception_guard(__destroy_vector(*this)); + std::__debug_db_insert_c(this); + if (__n > 0) + { + __vallocate(__n); + __construct_at_end(__n, __x); + } + __guard.__complete(); +} + +template +template ::value && + is_constructible<_Tp, typename iterator_traits<_InputIterator>::reference>::value, + int> > +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector<_Tp, _Allocator>::vector(_InputIterator __first, _InputIterator __last) +{ + auto __guard = std::__make_exception_guard(__destroy_vector(*this)); + std::__debug_db_insert_c(this); + for (; __first != __last; ++__first) + emplace_back(*__first); + __guard.__complete(); +} + +template +template ::value && + is_constructible<_Tp, typename iterator_traits<_InputIterator>::reference>::value, + int> > +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector<_Tp, _Allocator>::vector(_InputIterator __first, _InputIterator __last, const allocator_type& __a) + : __end_cap_(nullptr, __a) +{ + auto __guard = std::__make_exception_guard(__destroy_vector(*this)); + std::__debug_db_insert_c(this); + for (; __first != __last; ++__first) + emplace_back(*__first); + __guard.__complete(); +} + +template +template ::value && + is_constructible<_Tp, typename iterator_traits<_ForwardIterator>::reference>::value, + int> > +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector<_Tp, _Allocator>::vector(_ForwardIterator __first, _ForwardIterator __last) +{ + auto __guard = std::__make_exception_guard(__destroy_vector(*this)); + std::__debug_db_insert_c(this); + size_type __n = static_cast(std::distance(__first, __last)); + if (__n > 0) + { + __vallocate(__n); + __construct_at_end(__first, __last, __n); + } + __guard.__complete(); +} + +template +template ::value && + is_constructible<_Tp, typename iterator_traits<_ForwardIterator>::reference>::value, + int> > +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector<_Tp, _Allocator>::vector(_ForwardIterator __first, _ForwardIterator __last, const allocator_type& __a) + : __end_cap_(nullptr, __a) +{ + auto __guard = std::__make_exception_guard(__destroy_vector(*this)); + std::__debug_db_insert_c(this); + size_type __n = static_cast(std::distance(__first, __last)); + if (__n > 0) + { + __vallocate(__n); + __construct_at_end(__first, __last, __n); + } + __guard.__complete(); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector<_Tp, _Allocator>::vector(const vector& __x) + : __end_cap_(nullptr, __alloc_traits::select_on_container_copy_construction(__x.__alloc())) +{ + auto __guard = std::__make_exception_guard(__destroy_vector(*this)); + std::__debug_db_insert_c(this); + size_type __n = __x.size(); + if (__n > 0) + { + __vallocate(__n); + __construct_at_end(__x.__begin_, __x.__end_, __n); + } + __guard.__complete(); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector<_Tp, _Allocator>::vector(const vector& __x, const __type_identity_t& __a) + : __end_cap_(nullptr, __a) +{ + auto __guard = std::__make_exception_guard(__destroy_vector(*this)); + std::__debug_db_insert_c(this); + size_type __n = __x.size(); + if (__n > 0) + { + __vallocate(__n); + __construct_at_end(__x.__begin_, __x.__end_, __n); + } + __guard.__complete(); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +vector<_Tp, _Allocator>::vector(vector&& __x) +#if _LIBCPP_STD_VER > 14 + noexcept +#else + _NOEXCEPT_(is_nothrow_move_constructible::value) +#endif + : __end_cap_(nullptr, std::move(__x.__alloc())) +{ + std::__debug_db_insert_c(this); + std::__debug_db_swap(this, std::addressof(__x)); + this->__begin_ = __x.__begin_; + this->__end_ = __x.__end_; + this->__end_cap() = __x.__end_cap(); + __x.__begin_ = __x.__end_ = __x.__end_cap() = nullptr; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +vector<_Tp, _Allocator>::vector(vector&& __x, const __type_identity_t& __a) + : __end_cap_(nullptr, __a) +{ + std::__debug_db_insert_c(this); + if (__a == __x.__alloc()) + { + this->__begin_ = __x.__begin_; + this->__end_ = __x.__end_; + this->__end_cap() = __x.__end_cap(); + __x.__begin_ = __x.__end_ = __x.__end_cap() = nullptr; + std::__debug_db_swap(this, std::addressof(__x)); + } + else + { + typedef move_iterator _Ip; + auto __guard = std::__make_exception_guard(__destroy_vector(*this)); + assign(_Ip(__x.begin()), _Ip(__x.end())); + __guard.__complete(); + } +} + +#ifndef _LIBCPP_CXX03_LANG + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +vector<_Tp, _Allocator>::vector(initializer_list __il) +{ + auto __guard = std::__make_exception_guard(__destroy_vector(*this)); + std::__debug_db_insert_c(this); + if (__il.size() > 0) + { + __vallocate(__il.size()); + __construct_at_end(__il.begin(), __il.end(), __il.size()); + } + __guard.__complete(); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +vector<_Tp, _Allocator>::vector(initializer_list __il, const allocator_type& __a) + : __end_cap_(nullptr, __a) +{ + auto __guard = std::__make_exception_guard(__destroy_vector(*this)); + std::__debug_db_insert_c(this); + if (__il.size() > 0) + { + __vallocate(__il.size()); + __construct_at_end(__il.begin(), __il.end(), __il.size()); + } + __guard.__complete(); +} + +#endif // _LIBCPP_CXX03_LANG + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +vector<_Tp, _Allocator>& +vector<_Tp, _Allocator>::operator=(vector&& __x) + _NOEXCEPT_((__noexcept_move_assign_container<_Allocator, __alloc_traits>::value)) +{ + __move_assign(__x, integral_constant()); + return *this; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +void +vector<_Tp, _Allocator>::__move_assign(vector& __c, false_type) + _NOEXCEPT_(__alloc_traits::is_always_equal::value) +{ + if (__alloc() != __c.__alloc()) + { + typedef move_iterator _Ip; + assign(_Ip(__c.begin()), _Ip(__c.end())); + } + else + __move_assign(__c, true_type()); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +void +vector<_Tp, _Allocator>::__move_assign(vector& __c, true_type) + _NOEXCEPT_(is_nothrow_move_assignable::value) +{ + __vdeallocate(); + __move_assign_alloc(__c); // this can throw + this->__begin_ = __c.__begin_; + this->__end_ = __c.__end_; + this->__end_cap() = __c.__end_cap(); + __c.__begin_ = __c.__end_ = __c.__end_cap() = nullptr; + std::__debug_db_swap(this, std::addressof(__c)); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +vector<_Tp, _Allocator>& +vector<_Tp, _Allocator>::operator=(const vector& __x) +{ + if (this != std::addressof(__x)) + { + __copy_assign_alloc(__x); + assign(__x.__begin_, __x.__end_); + } + return *this; +} + +template +template ::value && + is_constructible<_Tp, typename iterator_traits<_InputIterator>::reference>::value, + int> > +_LIBCPP_CONSTEXPR_SINCE_CXX20 void +vector<_Tp, _Allocator>::assign(_InputIterator __first, _InputIterator __last) +{ + clear(); + for (; __first != __last; ++__first) + emplace_back(*__first); +} + +template +template ::value && + is_constructible<_Tp, typename iterator_traits<_ForwardIterator>::reference>::value, + int> > +_LIBCPP_CONSTEXPR_SINCE_CXX20 void +vector<_Tp, _Allocator>::assign(_ForwardIterator __first, _ForwardIterator __last) +{ + size_type __new_size = static_cast(std::distance(__first, __last)); + if (__new_size <= capacity()) + { + _ForwardIterator __mid = __last; + bool __growing = false; + if (__new_size > size()) + { + __growing = true; + __mid = __first; + std::advance(__mid, size()); + } + pointer __m = std::copy(__first, __mid, this->__begin_); + if (__growing) + __construct_at_end(__mid, __last, __new_size - size()); + else + this->__destruct_at_end(__m); + } + else + { + __vdeallocate(); + __vallocate(__recommend(__new_size)); + __construct_at_end(__first, __last, __new_size); + } + std::__debug_db_invalidate_all(this); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +void +vector<_Tp, _Allocator>::assign(size_type __n, const_reference __u) +{ + if (__n <= capacity()) + { + size_type __s = size(); + std::fill_n(this->__begin_, std::min(__n, __s), __u); + if (__n > __s) + __construct_at_end(__n - __s, __u); + else + this->__destruct_at_end(this->__begin_ + __n); + } + else + { + __vdeallocate(); + __vallocate(__recommend(static_cast(__n))); + __construct_at_end(__n, __u); + } + std::__debug_db_invalidate_all(this); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +typename vector<_Tp, _Allocator>::iterator +vector<_Tp, _Allocator>::begin() _NOEXCEPT +{ + return __make_iter(this->__begin_); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +typename vector<_Tp, _Allocator>::const_iterator +vector<_Tp, _Allocator>::begin() const _NOEXCEPT +{ + return __make_iter(this->__begin_); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +typename vector<_Tp, _Allocator>::iterator +vector<_Tp, _Allocator>::end() _NOEXCEPT +{ + return __make_iter(this->__end_); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +typename vector<_Tp, _Allocator>::const_iterator +vector<_Tp, _Allocator>::end() const _NOEXCEPT +{ + return __make_iter(this->__end_); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +typename vector<_Tp, _Allocator>::reference +vector<_Tp, _Allocator>::operator[](size_type __n) _NOEXCEPT +{ + _LIBCPP_ASSERT(__n < size(), "vector[] index out of bounds"); + return this->__begin_[__n]; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +typename vector<_Tp, _Allocator>::const_reference +vector<_Tp, _Allocator>::operator[](size_type __n) const _NOEXCEPT +{ + _LIBCPP_ASSERT(__n < size(), "vector[] index out of bounds"); + return this->__begin_[__n]; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +typename vector<_Tp, _Allocator>::reference +vector<_Tp, _Allocator>::at(size_type __n) +{ + if (__n >= size()) + this->__throw_out_of_range(); + return this->__begin_[__n]; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +typename vector<_Tp, _Allocator>::const_reference +vector<_Tp, _Allocator>::at(size_type __n) const +{ + if (__n >= size()) + this->__throw_out_of_range(); + return this->__begin_[__n]; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +void +vector<_Tp, _Allocator>::reserve(size_type __n) +{ + if (__n > capacity()) + { + if (__n > max_size()) + this->__throw_length_error(); + allocator_type& __a = this->__alloc(); + __split_buffer __v(__n, size(), __a); + __swap_out_circular_buffer(__v); + } +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +void +vector<_Tp, _Allocator>::shrink_to_fit() _NOEXCEPT +{ + if (capacity() > size()) + { +#ifndef _LIBCPP_NO_EXCEPTIONS + try + { +#endif // _LIBCPP_NO_EXCEPTIONS + allocator_type& __a = this->__alloc(); + __split_buffer __v(size(), size(), __a); + __swap_out_circular_buffer(__v); +#ifndef _LIBCPP_NO_EXCEPTIONS + } + catch (...) + { + } +#endif // _LIBCPP_NO_EXCEPTIONS + } +} + +template +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +void +vector<_Tp, _Allocator>::__push_back_slow_path(_Up&& __x) +{ + allocator_type& __a = this->__alloc(); + __split_buffer __v(__recommend(size() + 1), size(), __a); + // __v.push_back(std::forward<_Up>(__x)); + __alloc_traits::construct(__a, std::__to_address(__v.__end_), std::forward<_Up>(__x)); + __v.__end_++; + __swap_out_circular_buffer(__v); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +void +vector<_Tp, _Allocator>::push_back(const_reference __x) +{ + if (this->__end_ != this->__end_cap()) + { + __construct_one_at_end(__x); + } + else + __push_back_slow_path(__x); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +void +vector<_Tp, _Allocator>::push_back(value_type&& __x) +{ + if (this->__end_ < this->__end_cap()) + { + __construct_one_at_end(std::move(__x)); + } + else + __push_back_slow_path(std::move(__x)); +} + +template +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +void +vector<_Tp, _Allocator>::__emplace_back_slow_path(_Args&&... __args) +{ + allocator_type& __a = this->__alloc(); + __split_buffer __v(__recommend(size() + 1), size(), __a); +// __v.emplace_back(std::forward<_Args>(__args)...); + __alloc_traits::construct(__a, std::__to_address(__v.__end_), std::forward<_Args>(__args)...); + __v.__end_++; + __swap_out_circular_buffer(__v); +} + +template +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline +#if _LIBCPP_STD_VER > 14 +typename vector<_Tp, _Allocator>::reference +#else +void +#endif +vector<_Tp, _Allocator>::emplace_back(_Args&&... __args) +{ + if (this->__end_ < this->__end_cap()) + { + __construct_one_at_end(std::forward<_Args>(__args)...); + } + else + __emplace_back_slow_path(std::forward<_Args>(__args)...); +#if _LIBCPP_STD_VER > 14 + return this->back(); +#endif +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline +void +vector<_Tp, _Allocator>::pop_back() +{ + _LIBCPP_ASSERT(!empty(), "vector::pop_back called on an empty vector"); + this->__destruct_at_end(this->__end_ - 1); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +typename vector<_Tp, _Allocator>::iterator +vector<_Tp, _Allocator>::erase(const_iterator __position) +{ + _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(std::addressof(__position)) == this, + "vector::erase(iterator) called with an iterator not referring to this vector"); + _LIBCPP_ASSERT(__position != end(), + "vector::erase(iterator) called with a non-dereferenceable iterator"); + difference_type __ps = __position - cbegin(); + pointer __p = this->__begin_ + __ps; + this->__destruct_at_end(std::move(__p + 1, this->__end_, __p)); + if (!__libcpp_is_constant_evaluated()) + this->__invalidate_iterators_past(__p - 1); + return __make_iter(__p); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +typename vector<_Tp, _Allocator>::iterator +vector<_Tp, _Allocator>::erase(const_iterator __first, const_iterator __last) +{ + _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(std::addressof(__first)) == this, + "vector::erase(iterator, iterator) called with an iterator not referring to this vector"); + _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(std::addressof(__last)) == this, + "vector::erase(iterator, iterator) called with an iterator not referring to this vector"); + + _LIBCPP_ASSERT(__first <= __last, "vector::erase(first, last) called with invalid range"); + pointer __p = this->__begin_ + (__first - begin()); + if (__first != __last) { + this->__destruct_at_end(std::move(__p + (__last - __first), this->__end_, __p)); + if (!__libcpp_is_constant_evaluated()) + this->__invalidate_iterators_past(__p - 1); + } + return __make_iter(__p); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +void +vector<_Tp, _Allocator>::__move_range(pointer __from_s, pointer __from_e, pointer __to) +{ + pointer __old_last = this->__end_; + difference_type __n = __old_last - __to; + { + pointer __i = __from_s + __n; + _ConstructTransaction __tx(*this, __from_e - __i); + for (pointer __pos = __tx.__pos_; __i < __from_e; + ++__i, (void) ++__pos, __tx.__pos_ = __pos) { + __alloc_traits::construct(this->__alloc(), + std::__to_address(__pos), + std::move(*__i)); + } + } + std::move_backward(__from_s, __from_s + __n, __old_last); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +typename vector<_Tp, _Allocator>::iterator +vector<_Tp, _Allocator>::insert(const_iterator __position, const_reference __x) +{ + _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(std::addressof(__position)) == this, + "vector::insert(iterator, x) called with an iterator not referring to this vector"); + pointer __p = this->__begin_ + (__position - begin()); + // We can't compare unrelated pointers inside constant expressions + if (!__libcpp_is_constant_evaluated() && this->__end_ < this->__end_cap()) + { + if (__p == this->__end_) + { + __construct_one_at_end(__x); + } + else + { + __move_range(__p, this->__end_, __p + 1); + const_pointer __xr = pointer_traits::pointer_to(__x); + if (__p <= __xr && __xr < this->__end_) + ++__xr; + *__p = *__xr; + } + } + else + { + allocator_type& __a = this->__alloc(); + __split_buffer __v(__recommend(size() + 1), __p - this->__begin_, __a); + __v.push_back(__x); + __p = __swap_out_circular_buffer(__v, __p); + } + return __make_iter(__p); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +typename vector<_Tp, _Allocator>::iterator +vector<_Tp, _Allocator>::insert(const_iterator __position, value_type&& __x) +{ + _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(std::addressof(__position)) == this, + "vector::insert(iterator, x) called with an iterator not referring to this vector"); + pointer __p = this->__begin_ + (__position - begin()); + if (this->__end_ < this->__end_cap()) + { + if (__p == this->__end_) + { + __construct_one_at_end(std::move(__x)); + } + else + { + __move_range(__p, this->__end_, __p + 1); + *__p = std::move(__x); + } + } + else + { + allocator_type& __a = this->__alloc(); + __split_buffer __v(__recommend(size() + 1), __p - this->__begin_, __a); + __v.push_back(std::move(__x)); + __p = __swap_out_circular_buffer(__v, __p); + } + return __make_iter(__p); +} + +template +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +typename vector<_Tp, _Allocator>::iterator +vector<_Tp, _Allocator>::emplace(const_iterator __position, _Args&&... __args) +{ + _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(std::addressof(__position)) == this, + "vector::emplace(iterator, x) called with an iterator not referring to this vector"); + pointer __p = this->__begin_ + (__position - begin()); + if (this->__end_ < this->__end_cap()) + { + if (__p == this->__end_) + { + __construct_one_at_end(std::forward<_Args>(__args)...); + } + else + { + __temp_value __tmp(this->__alloc(), std::forward<_Args>(__args)...); + __move_range(__p, this->__end_, __p + 1); + *__p = std::move(__tmp.get()); + } + } + else + { + allocator_type& __a = this->__alloc(); + __split_buffer __v(__recommend(size() + 1), __p - this->__begin_, __a); + __v.emplace_back(std::forward<_Args>(__args)...); + __p = __swap_out_circular_buffer(__v, __p); + } + return __make_iter(__p); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +typename vector<_Tp, _Allocator>::iterator +vector<_Tp, _Allocator>::insert(const_iterator __position, size_type __n, const_reference __x) +{ + _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(std::addressof(__position)) == this, + "vector::insert(iterator, n, x) called with an iterator not referring to this vector"); + pointer __p = this->__begin_ + (__position - begin()); + if (__n > 0) + { + // We can't compare unrelated pointers inside constant expressions + if (!__libcpp_is_constant_evaluated() && __n <= static_cast(this->__end_cap() - this->__end_)) + { + size_type __old_n = __n; + pointer __old_last = this->__end_; + if (__n > static_cast(this->__end_ - __p)) + { + size_type __cx = __n - (this->__end_ - __p); + __construct_at_end(__cx, __x); + __n -= __cx; + } + if (__n > 0) + { + __move_range(__p, __old_last, __p + __old_n); + const_pointer __xr = pointer_traits::pointer_to(__x); + if (__p <= __xr && __xr < this->__end_) + __xr += __old_n; + std::fill_n(__p, __n, *__xr); + } + } + else + { + allocator_type& __a = this->__alloc(); + __split_buffer __v(__recommend(size() + __n), __p - this->__begin_, __a); + __v.__construct_at_end(__n, __x); + __p = __swap_out_circular_buffer(__v, __p); + } + } + return __make_iter(__p); +} + +template +template ::value && + is_constructible<_Tp, typename iterator_traits<_InputIterator>::reference>::value, + int> > +_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator +vector<_Tp, _Allocator>::insert(const_iterator __position, _InputIterator __first, _InputIterator __last) +{ + _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(std::addressof(__position)) == this, + "vector::insert(iterator, range) called with an iterator not referring to this vector"); + difference_type __off = __position - begin(); + pointer __p = this->__begin_ + __off; + allocator_type& __a = this->__alloc(); + pointer __old_last = this->__end_; + for (; this->__end_ != this->__end_cap() && __first != __last; ++__first) + { + __construct_one_at_end(*__first); + } + __split_buffer __v(__a); + if (__first != __last) + { +#ifndef _LIBCPP_NO_EXCEPTIONS + try + { +#endif // _LIBCPP_NO_EXCEPTIONS + __v.__construct_at_end(__first, __last); + difference_type __old_size = __old_last - this->__begin_; + difference_type __old_p = __p - this->__begin_; + reserve(__recommend(size() + __v.size())); + __p = this->__begin_ + __old_p; + __old_last = this->__begin_ + __old_size; +#ifndef _LIBCPP_NO_EXCEPTIONS + } + catch (...) + { + erase(__make_iter(__old_last), end()); + throw; + } +#endif // _LIBCPP_NO_EXCEPTIONS + } + __p = std::rotate(__p, __old_last, this->__end_); + insert(__make_iter(__p), std::make_move_iterator(__v.begin()), + std::make_move_iterator(__v.end())); + return begin() + __off; +} + +template +template ::value && + is_constructible<_Tp, typename iterator_traits<_ForwardIterator>::reference>::value, + int> > +_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator +vector<_Tp, _Allocator>::insert(const_iterator __position, _ForwardIterator __first, _ForwardIterator __last) +{ + _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(std::addressof(__position)) == this, + "vector::insert(iterator, range) called with an iterator not referring to this vector"); + pointer __p = this->__begin_ + (__position - begin()); + difference_type __n = std::distance(__first, __last); + if (__n > 0) + { + if (__n <= this->__end_cap() - this->__end_) + { + size_type __old_n = __n; + pointer __old_last = this->__end_; + _ForwardIterator __m = __last; + difference_type __dx = this->__end_ - __p; + if (__n > __dx) + { + __m = __first; + difference_type __diff = this->__end_ - __p; + std::advance(__m, __diff); + __construct_at_end(__m, __last, __n - __diff); + __n = __dx; + } + if (__n > 0) + { + __move_range(__p, __old_last, __p + __old_n); + std::copy(__first, __m, __p); + } + } + else + { + allocator_type& __a = this->__alloc(); + __split_buffer __v(__recommend(size() + __n), __p - this->__begin_, __a); + __v.__construct_at_end(__first, __last); + __p = __swap_out_circular_buffer(__v, __p); + } + } + return __make_iter(__p); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +void +vector<_Tp, _Allocator>::resize(size_type __sz) +{ + size_type __cs = size(); + if (__cs < __sz) + this->__append(__sz - __cs); + else if (__cs > __sz) + this->__destruct_at_end(this->__begin_ + __sz); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +void +vector<_Tp, _Allocator>::resize(size_type __sz, const_reference __x) +{ + size_type __cs = size(); + if (__cs < __sz) + this->__append(__sz - __cs, __x); + else if (__cs > __sz) + this->__destruct_at_end(this->__begin_ + __sz); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +void +vector<_Tp, _Allocator>::swap(vector& __x) +#if _LIBCPP_STD_VER >= 14 + _NOEXCEPT +#else + _NOEXCEPT_(!__alloc_traits::propagate_on_container_swap::value || + __is_nothrow_swappable::value) +#endif +{ + _LIBCPP_ASSERT(__alloc_traits::propagate_on_container_swap::value || + this->__alloc() == __x.__alloc(), + "vector::swap: Either propagate_on_container_swap must be true" + " or the allocators must compare equal"); + std::swap(this->__begin_, __x.__begin_); + std::swap(this->__end_, __x.__end_); + std::swap(this->__end_cap(), __x.__end_cap()); + std::__swap_allocator(this->__alloc(), __x.__alloc(), + integral_constant()); + std::__debug_db_swap(this, std::addressof(__x)); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +bool +vector<_Tp, _Allocator>::__invariants() const +{ + if (this->__begin_ == nullptr) + { + if (this->__end_ != nullptr || this->__end_cap() != nullptr) + return false; + } + else + { + if (this->__begin_ > this->__end_) + return false; + if (this->__begin_ == this->__end_cap()) + return false; + if (this->__end_ > this->__end_cap()) + return false; + } + return true; +} + +#ifdef _LIBCPP_ENABLE_DEBUG_MODE + +template +bool +vector<_Tp, _Allocator>::__dereferenceable(const const_iterator* __i) const +{ + return this->__begin_ <= __i->base() && __i->base() < this->__end_; +} + +template +bool +vector<_Tp, _Allocator>::__decrementable(const const_iterator* __i) const +{ + return this->__begin_ < __i->base() && __i->base() <= this->__end_; +} + +template +bool +vector<_Tp, _Allocator>::__addable(const const_iterator* __i, ptrdiff_t __n) const +{ + const_pointer __p = __i->base() + __n; + return this->__begin_ <= __p && __p <= this->__end_; +} + +template +bool +vector<_Tp, _Allocator>::__subscriptable(const const_iterator* __i, ptrdiff_t __n) const +{ + const_pointer __p = __i->base() + __n; + return this->__begin_ <= __p && __p < this->__end_; +} + +#endif // _LIBCPP_ENABLE_DEBUG_MODE + +template +inline _LIBCPP_HIDE_FROM_ABI +void +vector<_Tp, _Allocator>::__invalidate_iterators_past(pointer __new_last) { +#ifdef _LIBCPP_ENABLE_DEBUG_MODE + __c_node* __c = __get_db()->__find_c_and_lock(this); + for (__i_node** __p = __c->end_; __p != __c->beg_; ) { + --__p; + const_iterator* __i = static_cast((*__p)->__i_); + if (__i->base() > __new_last) { + (*__p)->__c_ = nullptr; + if (--__c->end_ != __p) + std::memmove(__p, __p+1, (__c->end_ - __p)*sizeof(__i_node*)); + } + } + __get_db()->unlock(); +#else + ((void)__new_last); +#endif +} + +// vector + +template class vector; + +template struct hash >; + +template +struct __has_storage_type > +{ + static const bool value = true; +}; + +template +class _LIBCPP_TEMPLATE_VIS vector +{ +public: + typedef vector __self; + typedef bool value_type; + typedef _Allocator allocator_type; + typedef allocator_traits __alloc_traits; + typedef typename __alloc_traits::size_type size_type; + typedef typename __alloc_traits::difference_type difference_type; + typedef size_type __storage_type; + typedef __bit_iterator pointer; + typedef __bit_iterator const_pointer; + typedef pointer iterator; + typedef const_pointer const_iterator; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + +private: + typedef __rebind_alloc<__alloc_traits, __storage_type> __storage_allocator; + typedef allocator_traits<__storage_allocator> __storage_traits; + typedef typename __storage_traits::pointer __storage_pointer; + typedef typename __storage_traits::const_pointer __const_storage_pointer; + + __storage_pointer __begin_; + size_type __size_; + __compressed_pair __cap_alloc_; +public: + typedef __bit_reference reference; +#ifdef _LIBCPP_ABI_BITSET_VECTOR_BOOL_CONST_SUBSCRIPT_RETURN_BOOL + using const_reference = bool; +#else + typedef __bit_const_reference const_reference; +#endif +private: + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + size_type& __cap() _NOEXCEPT + {return __cap_alloc_.first();} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + const size_type& __cap() const _NOEXCEPT + {return __cap_alloc_.first();} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + __storage_allocator& __alloc() _NOEXCEPT + {return __cap_alloc_.second();} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + const __storage_allocator& __alloc() const _NOEXCEPT + {return __cap_alloc_.second();} + + static const unsigned __bits_per_word = static_cast(sizeof(__storage_type) * CHAR_BIT); + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + static size_type __internal_cap_to_external(size_type __n) _NOEXCEPT + {return __n * __bits_per_word;} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + static size_type __external_cap_to_internal(size_type __n) _NOEXCEPT + {return (__n - 1) / __bits_per_word + 1;} + +public: + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + vector() _NOEXCEPT_(is_nothrow_default_constructible::value); + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 explicit vector(const allocator_type& __a) +#if _LIBCPP_STD_VER <= 14 + _NOEXCEPT_(is_nothrow_copy_constructible::value); +#else + _NOEXCEPT; +#endif + +private: + class __destroy_vector { + public: + _LIBCPP_CONSTEXPR __destroy_vector(vector& __vec) : __vec_(__vec) {} + + _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void operator()() { + if (__vec_.__begin_ != nullptr) + __storage_traits::deallocate(__vec_.__alloc(), __vec_.__begin_, __vec_.__cap()); + std::__debug_db_invalidate_all(this); + } + + private: + vector& __vec_; + }; + +public: + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 ~vector() { __destroy_vector(*this)(); } + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 explicit vector(size_type __n); +#if _LIBCPP_STD_VER > 11 + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 explicit vector(size_type __n, const allocator_type& __a); +#endif + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 vector(size_type __n, const value_type& __v); + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 vector(size_type __n, const value_type& __v, const allocator_type& __a); + template + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 vector(_InputIterator __first, _InputIterator __last, + typename enable_if<__is_exactly_cpp17_input_iterator<_InputIterator>::value>::type* = 0); + template + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 vector(_InputIterator __first, _InputIterator __last, const allocator_type& __a, + typename enable_if<__is_exactly_cpp17_input_iterator<_InputIterator>::value>::type* = 0); + template + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 vector(_ForwardIterator __first, _ForwardIterator __last, + typename enable_if<__is_cpp17_forward_iterator<_ForwardIterator>::value>::type* = 0); + template + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 vector(_ForwardIterator __first, _ForwardIterator __last, const allocator_type& __a, + typename enable_if<__is_cpp17_forward_iterator<_ForwardIterator>::value>::type* = 0); + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 vector(const vector& __v); + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 vector(const vector& __v, const allocator_type& __a); + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 vector& operator=(const vector& __v); + +#ifndef _LIBCPP_CXX03_LANG + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 vector(initializer_list __il); + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 vector(initializer_list __il, const allocator_type& __a); + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + vector& operator=(initializer_list __il) + {assign(__il.begin(), __il.end()); return *this;} + +#endif // !_LIBCPP_CXX03_LANG + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + vector(vector&& __v) +#if _LIBCPP_STD_VER > 14 + noexcept; +#else + _NOEXCEPT_(is_nothrow_move_constructible::value); +#endif + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 vector(vector&& __v, const __type_identity_t& __a); + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + vector& operator=(vector&& __v) + _NOEXCEPT_((__noexcept_move_assign_container<_Allocator, __alloc_traits>::value)); + + template + typename enable_if <__is_exactly_cpp17_input_iterator<_InputIterator>::value, + void + >::type + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 assign(_InputIterator __first, _InputIterator __last); + template + typename enable_if + < + __is_cpp17_forward_iterator<_ForwardIterator>::value, + void + >::type + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 assign(_ForwardIterator __first, _ForwardIterator __last); + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void assign(size_type __n, const value_type& __x); + +#ifndef _LIBCPP_CXX03_LANG + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + void assign(initializer_list __il) + {assign(__il.begin(), __il.end());} +#endif + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 allocator_type get_allocator() const _NOEXCEPT + {return allocator_type(this->__alloc());} + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type max_size() const _NOEXCEPT; + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + size_type capacity() const _NOEXCEPT + {return __internal_cap_to_external(__cap());} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + size_type size() const _NOEXCEPT + {return __size_;} + _LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + bool empty() const _NOEXCEPT + {return __size_ == 0;} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void reserve(size_type __n); + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void shrink_to_fit() _NOEXCEPT; + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + iterator begin() _NOEXCEPT + {return __make_iter(0);} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + const_iterator begin() const _NOEXCEPT + {return __make_iter(0);} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + iterator end() _NOEXCEPT + {return __make_iter(__size_);} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + const_iterator end() const _NOEXCEPT + {return __make_iter(__size_);} + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + reverse_iterator rbegin() _NOEXCEPT + {return reverse_iterator(end());} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + const_reverse_iterator rbegin() const _NOEXCEPT + {return const_reverse_iterator(end());} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + reverse_iterator rend() _NOEXCEPT + {return reverse_iterator(begin());} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + const_reverse_iterator rend() const _NOEXCEPT + {return const_reverse_iterator(begin());} + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + const_iterator cbegin() const _NOEXCEPT + {return __make_iter(0);} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + const_iterator cend() const _NOEXCEPT + {return __make_iter(__size_);} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + const_reverse_iterator crbegin() const _NOEXCEPT + {return rbegin();} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + const_reverse_iterator crend() const _NOEXCEPT + {return rend();} + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 reference operator[](size_type __n) {return __make_ref(__n);} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 const_reference operator[](size_type __n) const {return __make_ref(__n);} + _LIBCPP_HIDE_FROM_ABI reference at(size_type __n); + _LIBCPP_HIDE_FROM_ABI const_reference at(size_type __n) const; + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 reference front() {return __make_ref(0);} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 const_reference front() const {return __make_ref(0);} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 reference back() {return __make_ref(__size_ - 1);} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 const_reference back() const {return __make_ref(__size_ - 1);} + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void push_back(const value_type& __x); +#if _LIBCPP_STD_VER > 11 + template +#if _LIBCPP_STD_VER > 14 + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 reference emplace_back(_Args&&... __args) +#else + _LIBCPP_HIDE_FROM_ABI void emplace_back(_Args&&... __args) +#endif + { + push_back ( value_type ( std::forward<_Args>(__args)... )); +#if _LIBCPP_STD_VER > 14 + return this->back(); +#endif + } +#endif + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void pop_back() {--__size_;} + +#if _LIBCPP_STD_VER > 11 + template + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 iterator emplace(const_iterator __position, _Args&&... __args) + { return insert ( __position, value_type ( std::forward<_Args>(__args)... )); } +#endif + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 iterator insert(const_iterator __position, const value_type& __x); + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 iterator insert(const_iterator __position, size_type __n, const value_type& __x); + template + typename enable_if <__is_exactly_cpp17_input_iterator<_InputIterator>::value, + iterator + >::type + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 insert(const_iterator __position, _InputIterator __first, _InputIterator __last); + template + typename enable_if + < + __is_cpp17_forward_iterator<_ForwardIterator>::value, + iterator + >::type + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 insert(const_iterator __position, _ForwardIterator __first, _ForwardIterator __last); + +#ifndef _LIBCPP_CXX03_LANG + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + iterator insert(const_iterator __position, initializer_list __il) + {return insert(__position, __il.begin(), __il.end());} +#endif + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 iterator erase(const_iterator __position); + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 iterator erase(const_iterator __first, const_iterator __last); + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + void clear() _NOEXCEPT {__size_ = 0;} + + _LIBCPP_CONSTEXPR_SINCE_CXX20 void swap(vector&) +#if _LIBCPP_STD_VER >= 14 + _NOEXCEPT; +#else + _NOEXCEPT_(!__alloc_traits::propagate_on_container_swap::value || + __is_nothrow_swappable::value); +#endif + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 static void swap(reference __x, reference __y) _NOEXCEPT { std::swap(__x, __y); } + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void resize(size_type __sz, value_type __x = false); + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void flip() _NOEXCEPT; + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool __invariants() const; + +private: + _LIBCPP_NORETURN _LIBCPP_HIDE_FROM_ABI + void __throw_length_error() const { + std::__throw_length_error("vector"); + } + + _LIBCPP_NORETURN _LIBCPP_HIDE_FROM_ABI + void __throw_out_of_range() const { + std::__throw_out_of_range("vector"); + } + + // Allocate space for __n objects + // throws length_error if __n > max_size() + // throws (probably bad_alloc) if memory run out + // Precondition: __begin_ == __end_ == __cap() == 0 + // Precondition: __n > 0 + // Postcondition: capacity() >= __n + // Postcondition: size() == 0 + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __vallocate(size_type __n) { + if (__n > max_size()) + __throw_length_error(); + auto __allocation = std::__allocate_at_least(__alloc(), __external_cap_to_internal(__n)); + __begin_ = __allocation.ptr; + __size_ = 0; + __cap() = __allocation.count; + if (__libcpp_is_constant_evaluated()) { + for (size_type __i = 0; __i != __cap(); ++__i) + std::__construct_at(std::__to_address(__begin_) + __i); + } + } + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __vdeallocate() _NOEXCEPT; + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + static size_type __align_it(size_type __new_size) _NOEXCEPT + {return (__new_size + (__bits_per_word-1)) & ~((size_type)__bits_per_word-1);} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_type __recommend(size_type __new_size) const; + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __construct_at_end(size_type __n, bool __x); + template + typename enable_if + < + __is_cpp17_forward_iterator<_ForwardIterator>::value, + void + >::type + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 __construct_at_end(_ForwardIterator __first, _ForwardIterator __last); + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __append(size_type __n, const_reference __x); + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + reference __make_ref(size_type __pos) _NOEXCEPT + {return reference(__begin_ + __pos / __bits_per_word, __storage_type(1) << __pos % __bits_per_word);} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + const_reference __make_ref(size_type __pos) const _NOEXCEPT { + return __bit_const_reference(__begin_ + __pos / __bits_per_word, + __storage_type(1) << __pos % __bits_per_word); + } + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + iterator __make_iter(size_type __pos) _NOEXCEPT + {return iterator(__begin_ + __pos / __bits_per_word, static_cast(__pos % __bits_per_word));} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + const_iterator __make_iter(size_type __pos) const _NOEXCEPT + {return const_iterator(__begin_ + __pos / __bits_per_word, static_cast(__pos % __bits_per_word));} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + iterator __const_iterator_cast(const_iterator __p) _NOEXCEPT + {return begin() + (__p - cbegin());} + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + void __copy_assign_alloc(const vector& __v) + {__copy_assign_alloc(__v, integral_constant());} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + void __copy_assign_alloc(const vector& __c, true_type) + { + if (__alloc() != __c.__alloc()) + __vdeallocate(); + __alloc() = __c.__alloc(); + } + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + void __copy_assign_alloc(const vector&, false_type) + {} + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __move_assign(vector& __c, false_type); + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void __move_assign(vector& __c, true_type) + _NOEXCEPT_(is_nothrow_move_assignable::value); + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + void __move_assign_alloc(vector& __c) + _NOEXCEPT_( + !__storage_traits::propagate_on_container_move_assignment::value || + is_nothrow_move_assignable::value) + {__move_assign_alloc(__c, integral_constant());} + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + void __move_assign_alloc(vector& __c, true_type) + _NOEXCEPT_(is_nothrow_move_assignable::value) + { + __alloc() = std::move(__c.__alloc()); + } + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + void __move_assign_alloc(vector&, false_type) + _NOEXCEPT + {} + + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 size_t __hash_code() const _NOEXCEPT; + + friend class __bit_reference; + friend class __bit_const_reference; + friend class __bit_iterator; + friend class __bit_iterator; + friend struct __bit_array; + friend struct _LIBCPP_TEMPLATE_VIS hash; +}; + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 void +vector::__vdeallocate() _NOEXCEPT +{ + if (this->__begin_ != nullptr) + { + __storage_traits::deallocate(this->__alloc(), this->__begin_, __cap()); + std::__debug_db_invalidate_all(this); + this->__begin_ = nullptr; + this->__size_ = this->__cap() = 0; + } +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +typename vector::size_type +vector::max_size() const _NOEXCEPT +{ + size_type __amax = __storage_traits::max_size(__alloc()); + size_type __nmax = numeric_limits::max() / 2; // end() >= begin(), always + if (__nmax / __bits_per_word <= __amax) + return __nmax; + return __internal_cap_to_external(__amax); +} + +// Precondition: __new_size > capacity() +template +inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 +typename vector::size_type +vector::__recommend(size_type __new_size) const +{ + const size_type __ms = max_size(); + if (__new_size > __ms) + this->__throw_length_error(); + const size_type __cap = capacity(); + if (__cap >= __ms / 2) + return __ms; + return std::max(2 * __cap, __align_it(__new_size)); +} + +// Default constructs __n objects starting at __end_ +// Precondition: __n > 0 +// Precondition: size() + __n <= capacity() +// Postcondition: size() == size() + __n +template +inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 +void +vector::__construct_at_end(size_type __n, bool __x) +{ + size_type __old_size = this->__size_; + this->__size_ += __n; + if (__old_size == 0 || ((__old_size - 1) / __bits_per_word) != ((this->__size_ - 1) / __bits_per_word)) + { + if (this->__size_ <= __bits_per_word) + this->__begin_[0] = __storage_type(0); + else + this->__begin_[(this->__size_ - 1) / __bits_per_word] = __storage_type(0); + } + std::fill_n(__make_iter(__old_size), __n, __x); +} + +template +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +typename enable_if +< + __is_cpp17_forward_iterator<_ForwardIterator>::value, + void +>::type +vector::__construct_at_end(_ForwardIterator __first, _ForwardIterator __last) +{ + size_type __old_size = this->__size_; + this->__size_ += std::distance(__first, __last); + if (__old_size == 0 || ((__old_size - 1) / __bits_per_word) != ((this->__size_ - 1) / __bits_per_word)) + { + if (this->__size_ <= __bits_per_word) + this->__begin_[0] = __storage_type(0); + else + this->__begin_[(this->__size_ - 1) / __bits_per_word] = __storage_type(0); + } + std::copy(__first, __last, __make_iter(__old_size)); +} + +template +inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 +vector::vector() + _NOEXCEPT_(is_nothrow_default_constructible::value) + : __begin_(nullptr), + __size_(0), + __cap_alloc_(0, __default_init_tag()) +{ +} + +template +inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 +vector::vector(const allocator_type& __a) +#if _LIBCPP_STD_VER <= 14 + _NOEXCEPT_(is_nothrow_copy_constructible::value) +#else + _NOEXCEPT +#endif + : __begin_(nullptr), + __size_(0), + __cap_alloc_(0, static_cast<__storage_allocator>(__a)) +{ +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector::vector(size_type __n) + : __begin_(nullptr), + __size_(0), + __cap_alloc_(0, __default_init_tag()) +{ + if (__n > 0) + { + __vallocate(__n); + __construct_at_end(__n, false); + } +} + +#if _LIBCPP_STD_VER > 11 +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector::vector(size_type __n, const allocator_type& __a) + : __begin_(nullptr), + __size_(0), + __cap_alloc_(0, static_cast<__storage_allocator>(__a)) +{ + if (__n > 0) + { + __vallocate(__n); + __construct_at_end(__n, false); + } +} +#endif + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector::vector(size_type __n, const value_type& __x) + : __begin_(nullptr), + __size_(0), + __cap_alloc_(0, __default_init_tag()) +{ + if (__n > 0) + { + __vallocate(__n); + __construct_at_end(__n, __x); + } +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector::vector(size_type __n, const value_type& __x, const allocator_type& __a) + : __begin_(nullptr), + __size_(0), + __cap_alloc_(0, static_cast<__storage_allocator>(__a)) +{ + if (__n > 0) + { + __vallocate(__n); + __construct_at_end(__n, __x); + } +} + +template +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector::vector(_InputIterator __first, _InputIterator __last, + typename enable_if<__is_exactly_cpp17_input_iterator<_InputIterator>::value>::type*) + : __begin_(nullptr), + __size_(0), + __cap_alloc_(0, __default_init_tag()) +{ +#ifndef _LIBCPP_NO_EXCEPTIONS + try + { +#endif // _LIBCPP_NO_EXCEPTIONS + for (; __first != __last; ++__first) + push_back(*__first); +#ifndef _LIBCPP_NO_EXCEPTIONS + } + catch (...) + { + if (__begin_ != nullptr) + __storage_traits::deallocate(__alloc(), __begin_, __cap()); + std::__debug_db_invalidate_all(this); + throw; + } +#endif // _LIBCPP_NO_EXCEPTIONS +} + +template +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector::vector(_InputIterator __first, _InputIterator __last, const allocator_type& __a, + typename enable_if<__is_exactly_cpp17_input_iterator<_InputIterator>::value>::type*) + : __begin_(nullptr), + __size_(0), + __cap_alloc_(0, static_cast<__storage_allocator>(__a)) +{ +#ifndef _LIBCPP_NO_EXCEPTIONS + try + { +#endif // _LIBCPP_NO_EXCEPTIONS + for (; __first != __last; ++__first) + push_back(*__first); +#ifndef _LIBCPP_NO_EXCEPTIONS + } + catch (...) + { + if (__begin_ != nullptr) + __storage_traits::deallocate(__alloc(), __begin_, __cap()); + std::__debug_db_invalidate_all(this); + throw; + } +#endif // _LIBCPP_NO_EXCEPTIONS +} + +template +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector::vector(_ForwardIterator __first, _ForwardIterator __last, + typename enable_if<__is_cpp17_forward_iterator<_ForwardIterator>::value>::type*) + : __begin_(nullptr), + __size_(0), + __cap_alloc_(0, __default_init_tag()) +{ + auto __guard = std::__make_exception_guard(__destroy_vector(*this)); + size_type __n = static_cast(std::distance(__first, __last)); + if (__n > 0) + { + __vallocate(__n); + __construct_at_end(__first, __last); + } + __guard.__complete(); +} + +template +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector::vector(_ForwardIterator __first, _ForwardIterator __last, const allocator_type& __a, + typename enable_if<__is_cpp17_forward_iterator<_ForwardIterator>::value>::type*) + : __begin_(nullptr), + __size_(0), + __cap_alloc_(0, static_cast<__storage_allocator>(__a)) +{ + auto __guard = std::__make_exception_guard(__destroy_vector(*this)); + size_type __n = static_cast(std::distance(__first, __last)); + if (__n > 0) + { + __vallocate(__n); + __construct_at_end(__first, __last); + } + __guard.__complete(); +} + +#ifndef _LIBCPP_CXX03_LANG + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector::vector(initializer_list __il) + : __begin_(nullptr), + __size_(0), + __cap_alloc_(0, __default_init_tag()) +{ + size_type __n = static_cast(__il.size()); + if (__n > 0) + { + __vallocate(__n); + __construct_at_end(__il.begin(), __il.end()); + } +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector::vector(initializer_list __il, const allocator_type& __a) + : __begin_(nullptr), + __size_(0), + __cap_alloc_(0, static_cast<__storage_allocator>(__a)) +{ + size_type __n = static_cast(__il.size()); + if (__n > 0) + { + __vallocate(__n); + __construct_at_end(__il.begin(), __il.end()); + } +} + +#endif // _LIBCPP_CXX03_LANG + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector::vector(const vector& __v) + : __begin_(nullptr), + __size_(0), + __cap_alloc_(0, __storage_traits::select_on_container_copy_construction(__v.__alloc())) +{ + if (__v.size() > 0) + { + __vallocate(__v.size()); + __construct_at_end(__v.begin(), __v.end()); + } +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector::vector(const vector& __v, const allocator_type& __a) + : __begin_(nullptr), + __size_(0), + __cap_alloc_(0, __a) +{ + if (__v.size() > 0) + { + __vallocate(__v.size()); + __construct_at_end(__v.begin(), __v.end()); + } +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector& +vector::operator=(const vector& __v) +{ + if (this != std::addressof(__v)) + { + __copy_assign_alloc(__v); + if (__v.__size_) + { + if (__v.__size_ > capacity()) + { + __vdeallocate(); + __vallocate(__v.__size_); + } + std::copy(__v.__begin_, __v.__begin_ + __external_cap_to_internal(__v.__size_), __begin_); + } + __size_ = __v.__size_; + } + return *this; +} + +template +inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 vector::vector(vector&& __v) +#if _LIBCPP_STD_VER > 14 + _NOEXCEPT +#else + _NOEXCEPT_(is_nothrow_move_constructible::value) +#endif + : __begin_(__v.__begin_), + __size_(__v.__size_), + __cap_alloc_(std::move(__v.__cap_alloc_)) { + __v.__begin_ = nullptr; + __v.__size_ = 0; + __v.__cap() = 0; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +vector::vector(vector&& __v, const __type_identity_t& __a) + : __begin_(nullptr), + __size_(0), + __cap_alloc_(0, __a) +{ + if (__a == allocator_type(__v.__alloc())) + { + this->__begin_ = __v.__begin_; + this->__size_ = __v.__size_; + this->__cap() = __v.__cap(); + __v.__begin_ = nullptr; + __v.__cap() = __v.__size_ = 0; + } + else if (__v.size() > 0) + { + __vallocate(__v.size()); + __construct_at_end(__v.begin(), __v.end()); + } +} + +template +inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 +vector& +vector::operator=(vector&& __v) + _NOEXCEPT_((__noexcept_move_assign_container<_Allocator, __alloc_traits>::value)) +{ + __move_assign(__v, integral_constant()); + return *this; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 void +vector::__move_assign(vector& __c, false_type) +{ + if (__alloc() != __c.__alloc()) + assign(__c.begin(), __c.end()); + else + __move_assign(__c, true_type()); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 void +vector::__move_assign(vector& __c, true_type) + _NOEXCEPT_(is_nothrow_move_assignable::value) +{ + __vdeallocate(); + __move_assign_alloc(__c); + this->__begin_ = __c.__begin_; + this->__size_ = __c.__size_; + this->__cap() = __c.__cap(); + __c.__begin_ = nullptr; + __c.__cap() = __c.__size_ = 0; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 void +vector::assign(size_type __n, const value_type& __x) +{ + __size_ = 0; + if (__n > 0) + { + size_type __c = capacity(); + if (__n <= __c) + __size_ = __n; + else + { + vector __v(get_allocator()); + __v.reserve(__recommend(__n)); + __v.__size_ = __n; + swap(__v); + } + std::fill_n(begin(), __n, __x); + } + std::__debug_db_invalidate_all(this); +} + +template +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 typename enable_if <__is_exactly_cpp17_input_iterator<_InputIterator>::value, + void +>::type +vector::assign(_InputIterator __first, _InputIterator __last) +{ + clear(); + for (; __first != __last; ++__first) + push_back(*__first); +} + +template +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +typename enable_if +< + __is_cpp17_forward_iterator<_ForwardIterator>::value, + void +>::type +vector::assign(_ForwardIterator __first, _ForwardIterator __last) +{ + clear(); + difference_type __ns = std::distance(__first, __last); + _LIBCPP_ASSERT(__ns >= 0, "invalid range specified"); + const size_t __n = static_cast(__ns); + if (__n) + { + if (__n > capacity()) + { + __vdeallocate(); + __vallocate(__n); + } + __construct_at_end(__first, __last); + } +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 void +vector::reserve(size_type __n) +{ + if (__n > capacity()) + { + if (__n > max_size()) + this->__throw_length_error(); + vector __v(this->get_allocator()); + __v.__vallocate(__n); + __v.__construct_at_end(this->begin(), this->end()); + swap(__v); + std::__debug_db_invalidate_all(this); + } +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 void +vector::shrink_to_fit() _NOEXCEPT +{ + if (__external_cap_to_internal(size()) > __cap()) + { +#ifndef _LIBCPP_NO_EXCEPTIONS + try + { +#endif // _LIBCPP_NO_EXCEPTIONS + vector(*this, allocator_type(__alloc())).swap(*this); +#ifndef _LIBCPP_NO_EXCEPTIONS + } + catch (...) + { + } +#endif // _LIBCPP_NO_EXCEPTIONS + } +} + +template +typename vector::reference +vector::at(size_type __n) +{ + if (__n >= size()) + this->__throw_out_of_range(); + return (*this)[__n]; +} + +template +typename vector::const_reference +vector::at(size_type __n) const +{ + if (__n >= size()) + this->__throw_out_of_range(); + return (*this)[__n]; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 void +vector::push_back(const value_type& __x) +{ + if (this->__size_ == this->capacity()) + reserve(__recommend(this->__size_ + 1)); + ++this->__size_; + back() = __x; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector::iterator +vector::insert(const_iterator __position, const value_type& __x) +{ + iterator __r; + if (size() < capacity()) + { + const_iterator __old_end = end(); + ++__size_; + std::copy_backward(__position, __old_end, end()); + __r = __const_iterator_cast(__position); + } + else + { + vector __v(get_allocator()); + __v.reserve(__recommend(__size_ + 1)); + __v.__size_ = __size_ + 1; + __r = std::copy(cbegin(), __position, __v.begin()); + std::copy_backward(__position, cend(), __v.end()); + swap(__v); + } + *__r = __x; + return __r; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector::iterator +vector::insert(const_iterator __position, size_type __n, const value_type& __x) +{ + iterator __r; + size_type __c = capacity(); + if (__n <= __c && size() <= __c - __n) + { + const_iterator __old_end = end(); + __size_ += __n; + std::copy_backward(__position, __old_end, end()); + __r = __const_iterator_cast(__position); + } + else + { + vector __v(get_allocator()); + __v.reserve(__recommend(__size_ + __n)); + __v.__size_ = __size_ + __n; + __r = std::copy(cbegin(), __position, __v.begin()); + std::copy_backward(__position, cend(), __v.end()); + swap(__v); + } + std::fill_n(__r, __n, __x); + return __r; +} + +template +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 typename enable_if <__is_exactly_cpp17_input_iterator<_InputIterator>::value, + typename vector::iterator +>::type +vector::insert(const_iterator __position, _InputIterator __first, _InputIterator __last) +{ + difference_type __off = __position - begin(); + iterator __p = __const_iterator_cast(__position); + iterator __old_end = end(); + for (; size() != capacity() && __first != __last; ++__first) + { + ++this->__size_; + back() = *__first; + } + vector __v(get_allocator()); + if (__first != __last) + { +#ifndef _LIBCPP_NO_EXCEPTIONS + try + { +#endif // _LIBCPP_NO_EXCEPTIONS + __v.assign(__first, __last); + difference_type __old_size = static_cast(__old_end - begin()); + difference_type __old_p = __p - begin(); + reserve(__recommend(size() + __v.size())); + __p = begin() + __old_p; + __old_end = begin() + __old_size; +#ifndef _LIBCPP_NO_EXCEPTIONS + } + catch (...) + { + erase(__old_end, end()); + throw; + } +#endif // _LIBCPP_NO_EXCEPTIONS + } + __p = std::rotate(__p, __old_end, end()); + insert(__p, __v.begin(), __v.end()); + return begin() + __off; +} + +template +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +typename enable_if +< + __is_cpp17_forward_iterator<_ForwardIterator>::value, + typename vector::iterator +>::type +vector::insert(const_iterator __position, _ForwardIterator __first, _ForwardIterator __last) +{ + const difference_type __n_signed = std::distance(__first, __last); + _LIBCPP_ASSERT(__n_signed >= 0, "invalid range specified"); + const size_type __n = static_cast(__n_signed); + iterator __r; + size_type __c = capacity(); + if (__n <= __c && size() <= __c - __n) + { + const_iterator __old_end = end(); + __size_ += __n; + std::copy_backward(__position, __old_end, end()); + __r = __const_iterator_cast(__position); + } + else + { + vector __v(get_allocator()); + __v.reserve(__recommend(__size_ + __n)); + __v.__size_ = __size_ + __n; + __r = std::copy(cbegin(), __position, __v.begin()); + std::copy_backward(__position, cend(), __v.end()); + swap(__v); + } + std::copy(__first, __last, __r); + return __r; +} + +template +inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 +typename vector::iterator +vector::erase(const_iterator __position) +{ + iterator __r = __const_iterator_cast(__position); + std::copy(__position + 1, this->cend(), __r); + --__size_; + return __r; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +typename vector::iterator +vector::erase(const_iterator __first, const_iterator __last) +{ + iterator __r = __const_iterator_cast(__first); + difference_type __d = __last - __first; + std::copy(__last, this->cend(), __r); + __size_ -= __d; + return __r; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 void +vector::swap(vector& __x) +#if _LIBCPP_STD_VER >= 14 + _NOEXCEPT +#else + _NOEXCEPT_(!__alloc_traits::propagate_on_container_swap::value || + __is_nothrow_swappable::value) +#endif +{ + std::swap(this->__begin_, __x.__begin_); + std::swap(this->__size_, __x.__size_); + std::swap(this->__cap(), __x.__cap()); + std::__swap_allocator(this->__alloc(), __x.__alloc(), + integral_constant()); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 void +vector::resize(size_type __sz, value_type __x) +{ + size_type __cs = size(); + if (__cs < __sz) + { + iterator __r; + size_type __c = capacity(); + size_type __n = __sz - __cs; + if (__n <= __c && __cs <= __c - __n) + { + __r = end(); + __size_ += __n; + } + else + { + vector __v(get_allocator()); + __v.reserve(__recommend(__size_ + __n)); + __v.__size_ = __size_ + __n; + __r = std::copy(cbegin(), cend(), __v.begin()); + swap(__v); + } + std::fill_n(__r, __n, __x); + } + else + __size_ = __sz; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 void +vector::flip() _NOEXCEPT +{ + // do middle whole words + size_type __n = __size_; + __storage_pointer __p = __begin_; + for (; __n >= __bits_per_word; ++__p, __n -= __bits_per_word) + *__p = ~*__p; + // do last partial word + if (__n > 0) + { + __storage_type __m = ~__storage_type(0) >> (__bits_per_word - __n); + __storage_type __b = *__p & __m; + *__p &= ~__m; + *__p |= ~__b & __m; + } +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 bool +vector::__invariants() const +{ + if (this->__begin_ == nullptr) + { + if (this->__size_ != 0 || this->__cap() != 0) + return false; + } + else + { + if (this->__cap() == 0) + return false; + if (this->__size_ > this->capacity()) + return false; + } + return true; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 size_t +vector::__hash_code() const _NOEXCEPT +{ + size_t __h = 0; + // do middle whole words + size_type __n = __size_; + __storage_pointer __p = __begin_; + for (; __n >= __bits_per_word; ++__p, __n -= __bits_per_word) + __h ^= *__p; + // do last partial word + if (__n > 0) + { + const __storage_type __m = ~__storage_type(0) >> (__bits_per_word - __n); + __h ^= *__p & __m; + } + return __h; +} + +template +struct _LIBCPP_TEMPLATE_VIS hash > + : public __unary_function, size_t> +{ + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 + size_t operator()(const vector& __vec) const _NOEXCEPT + {return __vec.__hash_code();} +}; + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +bool +operator==(const vector<_Tp, _Allocator>& __x, const vector<_Tp, _Allocator>& __y) +{ + const typename vector<_Tp, _Allocator>::size_type __sz = __x.size(); + return __sz == __y.size() && std::equal(__x.begin(), __x.end(), __y.begin()); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +bool +operator!=(const vector<_Tp, _Allocator>& __x, const vector<_Tp, _Allocator>& __y) +{ + return !(__x == __y); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +bool +operator< (const vector<_Tp, _Allocator>& __x, const vector<_Tp, _Allocator>& __y) +{ + return std::lexicographical_compare(__x.begin(), __x.end(), __y.begin(), __y.end()); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +bool +operator> (const vector<_Tp, _Allocator>& __x, const vector<_Tp, _Allocator>& __y) +{ + return __y < __x; +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +bool +operator>=(const vector<_Tp, _Allocator>& __x, const vector<_Tp, _Allocator>& __y) +{ + return !(__x < __y); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +bool +operator<=(const vector<_Tp, _Allocator>& __x, const vector<_Tp, _Allocator>& __y) +{ + return !(__y < __x); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI +void +swap(vector<_Tp, _Allocator>& __x, vector<_Tp, _Allocator>& __y) + _NOEXCEPT_(_NOEXCEPT_(__x.swap(__y))) +{ + __x.swap(__y); +} + +#if _LIBCPP_STD_VER > 17 +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI typename vector<_Tp, _Allocator>::size_type +erase(vector<_Tp, _Allocator>& __c, const _Up& __v) { + auto __old_size = __c.size(); + __c.erase(std::remove(__c.begin(), __c.end(), __v), __c.end()); + return __old_size - __c.size(); +} + +template +_LIBCPP_CONSTEXPR_SINCE_CXX20 +inline _LIBCPP_HIDE_FROM_ABI typename vector<_Tp, _Allocator>::size_type +erase_if(vector<_Tp, _Allocator>& __c, _Predicate __pred) { + auto __old_size = __c.size(); + __c.erase(std::remove_if(__c.begin(), __c.end(), __pred), __c.end()); + return __old_size - __c.size(); +} + +template <> +inline constexpr bool __format::__enable_insertable> = true; +#ifndef _LIBCPP_HAS_NO_WIDE_CHARACTERS +template <> +inline constexpr bool __format::__enable_insertable> = true; +#endif + +#endif // _LIBCPP_STD_VER > 17 + +#if _LIBCPP_STD_VER > 20 +template +// Since is-vector-bool-reference is only used once it's inlined here. + requires same_as> +struct _LIBCPP_TEMPLATE_VIS _LIBCPP_AVAILABILITY_FORMAT formatter<_Tp, CharT> { +private: + formatter __underlying_; + +public: + template + _LIBCPP_HIDE_FROM_ABI constexpr typename _ParseContext::iterator parse(_ParseContext& __ctx) { + return __underlying_.parse(__ctx); + } + + template + _LIBCPP_HIDE_FROM_ABI typename _FormatContext::iterator format(const _Tp& __ref, _FormatContext& __ctx) const { + return __underlying_.format(__ref, __ctx); + } +}; +#endif // _LIBCPP_STD_VER > 20 + +_LIBCPP_END_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 14 +_LIBCPP_BEGIN_NAMESPACE_STD +namespace pmr { +template +using vector = std::vector<_ValueT, polymorphic_allocator<_ValueT>>; +} // namespace pmr +_LIBCPP_END_NAMESPACE_STD +#endif + +_LIBCPP_POP_MACROS + +#if !defined(_LIBCPP_REMOVE_TRANSITIVE_INCLUDES) && _LIBCPP_STD_VER <= 20 +# include +# include +# include +# include +# include +#endif + +#endif // _LIBCPP_VECTOR + +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP_UNORDERED_MAP +#define _LIBCPP_UNORDERED_MAP + +/* + + unordered_map synopsis + +#include + +namespace std +{ + +template , class Pred = equal_to, + class Alloc = allocator>> +class unordered_map +{ +public: + // types + typedef Key key_type; + typedef T mapped_type; + typedef Hash hasher; + typedef Pred key_equal; + typedef Alloc allocator_type; + typedef pair value_type; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef typename allocator_traits::pointer pointer; + typedef typename allocator_traits::const_pointer const_pointer; + typedef typename allocator_traits::size_type size_type; + typedef typename allocator_traits::difference_type difference_type; + + typedef /unspecified/ iterator; + typedef /unspecified/ const_iterator; + typedef /unspecified/ local_iterator; + typedef /unspecified/ const_local_iterator; + + typedef unspecified node_type; // C++17 + typedef INSERT_RETURN_TYPE insert_return_type; // C++17 + + unordered_map() + noexcept( + is_nothrow_default_constructible::value && + is_nothrow_default_constructible::value && + is_nothrow_default_constructible::value); + explicit unordered_map(size_type n, const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); + template + unordered_map(InputIterator f, InputIterator l, + size_type n = 0, const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); + explicit unordered_map(const allocator_type&); + unordered_map(const unordered_map&); + unordered_map(const unordered_map&, const Allocator&); + unordered_map(unordered_map&&) + noexcept( + is_nothrow_move_constructible::value && + is_nothrow_move_constructible::value && + is_nothrow_move_constructible::value); + unordered_map(unordered_map&&, const Allocator&); + unordered_map(initializer_list, size_type n = 0, + const hasher& hf = hasher(), const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); + unordered_map(size_type n, const allocator_type& a) + : unordered_map(n, hasher(), key_equal(), a) {} // C++14 + unordered_map(size_type n, const hasher& hf, const allocator_type& a) + : unordered_map(n, hf, key_equal(), a) {} // C++14 + template + unordered_map(InputIterator f, InputIterator l, size_type n, const allocator_type& a) + : unordered_map(f, l, n, hasher(), key_equal(), a) {} // C++14 + template + unordered_map(InputIterator f, InputIterator l, size_type n, const hasher& hf, + const allocator_type& a) + : unordered_map(f, l, n, hf, key_equal(), a) {} // C++14 + unordered_map(initializer_list il, size_type n, const allocator_type& a) + : unordered_map(il, n, hasher(), key_equal(), a) {} // C++14 + unordered_map(initializer_list il, size_type n, const hasher& hf, + const allocator_type& a) + : unordered_map(il, n, hf, key_equal(), a) {} // C++14 + ~unordered_map(); + unordered_map& operator=(const unordered_map&); + unordered_map& operator=(unordered_map&&) + noexcept( + allocator_type::propagate_on_container_move_assignment::value && + is_nothrow_move_assignable::value && + is_nothrow_move_assignable::value && + is_nothrow_move_assignable::value); + unordered_map& operator=(initializer_list); + + allocator_type get_allocator() const noexcept; + + bool empty() const noexcept; + size_type size() const noexcept; + size_type max_size() const noexcept; + + iterator begin() noexcept; + iterator end() noexcept; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + template + pair emplace(Args&&... args); + template + iterator emplace_hint(const_iterator position, Args&&... args); + pair insert(const value_type& obj); + template + pair insert(P&& obj); + iterator insert(const_iterator hint, const value_type& obj); + template + iterator insert(const_iterator hint, P&& obj); + template + void insert(InputIterator first, InputIterator last); + void insert(initializer_list); + + node_type extract(const_iterator position); // C++17 + node_type extract(const key_type& x); // C++17 + insert_return_type insert(node_type&& nh); // C++17 + iterator insert(const_iterator hint, node_type&& nh); // C++17 + + template + pair try_emplace(const key_type& k, Args&&... args); // C++17 + template + pair try_emplace(key_type&& k, Args&&... args); // C++17 + template + iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args); // C++17 + template + iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args); // C++17 + template + pair insert_or_assign(const key_type& k, M&& obj); // C++17 + template + pair insert_or_assign(key_type&& k, M&& obj); // C++17 + template + iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj); // C++17 + template + iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj); // C++17 + + iterator erase(const_iterator position); + iterator erase(iterator position); // C++14 + size_type erase(const key_type& k); + iterator erase(const_iterator first, const_iterator last); + void clear() noexcept; + + template + void merge(unordered_map& source); // C++17 + template + void merge(unordered_map&& source); // C++17 + template + void merge(unordered_multimap& source); // C++17 + template + void merge(unordered_multimap&& source); // C++17 + + void swap(unordered_map&) + noexcept( + (!allocator_type::propagate_on_container_swap::value || + __is_nothrow_swappable::value) && + __is_nothrow_swappable::value && + __is_nothrow_swappable::value); + + hasher hash_function() const; + key_equal key_eq() const; + + iterator find(const key_type& k); + const_iterator find(const key_type& k) const; + template + iterator find(const K& x); // C++20 + template + const_iterator find(const K& x) const; // C++20 + size_type count(const key_type& k) const; + template + size_type count(const K& k) const; // C++20 + bool contains(const key_type& k) const; // C++20 + template + bool contains(const K& k) const; // C++20 + pair equal_range(const key_type& k); + pair equal_range(const key_type& k) const; + template + pair equal_range(const K& k); // C++20 + template + pair equal_range(const K& k) const; // C++20 + + mapped_type& operator[](const key_type& k); + mapped_type& operator[](key_type&& k); + + mapped_type& at(const key_type& k); + const mapped_type& at(const key_type& k) const; + + size_type bucket_count() const noexcept; + size_type max_bucket_count() const noexcept; + + size_type bucket_size(size_type n) const; + size_type bucket(const key_type& k) const; + + local_iterator begin(size_type n); + local_iterator end(size_type n); + const_local_iterator begin(size_type n) const; + const_local_iterator end(size_type n) const; + const_local_iterator cbegin(size_type n) const; + const_local_iterator cend(size_type n) const; + + float load_factor() const noexcept; + float max_load_factor() const noexcept; + void max_load_factor(float z); + void rehash(size_type n); + void reserve(size_type n); +}; + +template>, class Pred = equal_to>, + class Allocator = allocator>> +unordered_map(InputIterator, InputIterator, typename see below::size_type = see below, + Hash = Hash(), Pred = Pred(), Allocator = Allocator()) + -> unordered_map, iter_value_t, Hash, Pred, + Allocator>; // C++17 + +template, + class Pred = equal_to, class Allocator = allocator>> +unordered_map(initializer_list>, typename see below::size_type = see below, + Hash = Hash(), Pred = Pred(), Allocator = Allocator()) + -> unordered_map; // C++17 + +template +unordered_map(InputIterator, InputIterator, typename see below::size_type, Allocator) + -> unordered_map, iter_val_t, + hash>, equal_to>, Allocator>; // C++17 + +template +unordered_map(InputIterator, InputIterator, Allocator) + -> unordered_map, iter_val_t, + hash>, equal_to>, Allocator>; // C++17 + +template +unordered_map(InputIterator, InputIterator, typename see below::size_type, Hash, Allocator) + -> unordered_map, iter_val_t, Hash, + equal_to>, Allocator>; // C++17 + +template +unordered_map(initializer_list>, typename see below::size_type, Allocator) + -> unordered_map, equal_to, Allocator>; // C++17 + +template +unordered_map(initializer_list>, Allocator) + -> unordered_map, equal_to, Allocator>; // C++17 + +template +unordered_map(initializer_list>, typename see below::size_type, Hash, Allocator) + -> unordered_map, Allocator>; // C++17 + +template + void swap(unordered_map& x, + unordered_map& y) + noexcept(noexcept(x.swap(y))); + +template + bool + operator==(const unordered_map& x, + const unordered_map& y); + +template + bool + operator!=(const unordered_map& x, + const unordered_map& y); + +template , class Pred = equal_to, + class Alloc = allocator>> +class unordered_multimap +{ +public: + // types + typedef Key key_type; + typedef T mapped_type; + typedef Hash hasher; + typedef Pred key_equal; + typedef Alloc allocator_type; + typedef pair value_type; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef typename allocator_traits::pointer pointer; + typedef typename allocator_traits::const_pointer const_pointer; + typedef typename allocator_traits::size_type size_type; + typedef typename allocator_traits::difference_type difference_type; + + typedef /unspecified/ iterator; + typedef /unspecified/ const_iterator; + typedef /unspecified/ local_iterator; + typedef /unspecified/ const_local_iterator; + + typedef unspecified node_type; // C++17 + + unordered_multimap() + noexcept( + is_nothrow_default_constructible::value && + is_nothrow_default_constructible::value && + is_nothrow_default_constructible::value); + explicit unordered_multimap(size_type n, const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); + template + unordered_multimap(InputIterator f, InputIterator l, + size_type n = 0, const hasher& hf = hasher(), + const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); + explicit unordered_multimap(const allocator_type&); + unordered_multimap(const unordered_multimap&); + unordered_multimap(const unordered_multimap&, const Allocator&); + unordered_multimap(unordered_multimap&&) + noexcept( + is_nothrow_move_constructible::value && + is_nothrow_move_constructible::value && + is_nothrow_move_constructible::value); + unordered_multimap(unordered_multimap&&, const Allocator&); + unordered_multimap(initializer_list, size_type n = 0, + const hasher& hf = hasher(), const key_equal& eql = key_equal(), + const allocator_type& a = allocator_type()); + unordered_multimap(size_type n, const allocator_type& a) + : unordered_multimap(n, hasher(), key_equal(), a) {} // C++14 + unordered_multimap(size_type n, const hasher& hf, const allocator_type& a) + : unordered_multimap(n, hf, key_equal(), a) {} // C++14 + template + unordered_multimap(InputIterator f, InputIterator l, size_type n, const allocator_type& a) + : unordered_multimap(f, l, n, hasher(), key_equal(), a) {} // C++14 + template + unordered_multimap(InputIterator f, InputIterator l, size_type n, const hasher& hf, + const allocator_type& a) + : unordered_multimap(f, l, n, hf, key_equal(), a) {} // C++14 + unordered_multimap(initializer_list il, size_type n, const allocator_type& a) + : unordered_multimap(il, n, hasher(), key_equal(), a) {} // C++14 + unordered_multimap(initializer_list il, size_type n, const hasher& hf, + const allocator_type& a) + : unordered_multimap(il, n, hf, key_equal(), a) {} // C++14 + ~unordered_multimap(); + unordered_multimap& operator=(const unordered_multimap&); + unordered_multimap& operator=(unordered_multimap&&) + noexcept( + allocator_type::propagate_on_container_move_assignment::value && + is_nothrow_move_assignable::value && + is_nothrow_move_assignable::value && + is_nothrow_move_assignable::value); + unordered_multimap& operator=(initializer_list); + + allocator_type get_allocator() const noexcept; + + bool empty() const noexcept; + size_type size() const noexcept; + size_type max_size() const noexcept; + + iterator begin() noexcept; + iterator end() noexcept; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + template + iterator emplace(Args&&... args); + template + iterator emplace_hint(const_iterator position, Args&&... args); + iterator insert(const value_type& obj); + template + iterator insert(P&& obj); + iterator insert(const_iterator hint, const value_type& obj); + template + iterator insert(const_iterator hint, P&& obj); + template + void insert(InputIterator first, InputIterator last); + void insert(initializer_list); + + node_type extract(const_iterator position); // C++17 + node_type extract(const key_type& x); // C++17 + iterator insert(node_type&& nh); // C++17 + iterator insert(const_iterator hint, node_type&& nh); // C++17 + + iterator erase(const_iterator position); + iterator erase(iterator position); // C++14 + size_type erase(const key_type& k); + iterator erase(const_iterator first, const_iterator last); + void clear() noexcept; + + template + void merge(unordered_multimap& source); // C++17 + template + void merge(unordered_multimap&& source); // C++17 + template + void merge(unordered_map& source); // C++17 + template + void merge(unordered_map&& source); // C++17 + + void swap(unordered_multimap&) + noexcept( + (!allocator_type::propagate_on_container_swap::value || + __is_nothrow_swappable::value) && + __is_nothrow_swappable::value && + __is_nothrow_swappable::value); + + hasher hash_function() const; + key_equal key_eq() const; + + iterator find(const key_type& k); + const_iterator find(const key_type& k) const; + template + iterator find(const K& x); // C++20 + template + const_iterator find(const K& x) const; // C++20 + size_type count(const key_type& k) const; + template + size_type count(const K& k) const; // C++20 + bool contains(const key_type& k) const; // C++20 + template + bool contains(const K& k) const; // C++20 + pair equal_range(const key_type& k); + pair equal_range(const key_type& k) const; + template + pair equal_range(const K& k); // C++20 + template + pair equal_range(const K& k) const; // C++20 + + size_type bucket_count() const noexcept; + size_type max_bucket_count() const noexcept; + + size_type bucket_size(size_type n) const; + size_type bucket(const key_type& k) const; + + local_iterator begin(size_type n); + local_iterator end(size_type n); + const_local_iterator begin(size_type n) const; + const_local_iterator end(size_type n) const; + const_local_iterator cbegin(size_type n) const; + const_local_iterator cend(size_type n) const; + + float load_factor() const noexcept; + float max_load_factor() const noexcept; + void max_load_factor(float z); + void rehash(size_type n); + void reserve(size_type n); +}; + +template>, class Pred = equal_to>, + class Allocator = allocator>> +unordered_multimap(InputIterator, InputIterator, typename see below::size_type = see below, + Hash = Hash(), Pred = Pred(), Allocator = Allocator()) + -> unordered_multimap, iter_value_t, Hash, Pred, + Allocator>; // C++17 + +template, + class Pred = equal_to, class Allocator = allocator>> +unordered_multimap(initializer_list>, typename see below::size_type = see below, + Hash = Hash(), Pred = Pred(), Allocator = Allocator()) + -> unordered_multimap; // C++17 + +template +unordered_multimap(InputIterator, InputIterator, typename see below::size_type, Allocator) + -> unordered_multimap, iter_val_t, + hash>, equal_to>, Allocator>; // C++17 + +template +unordered_multimap(InputIterator, InputIterator, Allocator) + -> unordered_multimap, iter_val_t, + hash>, equal_to>, Allocator>; // C++17 + +template +unordered_multimap(InputIterator, InputIterator, typename see below::size_type, Hash, Allocator) + -> unordered_multimap, iter_val_t, Hash, + equal_to>, Allocator>; // C++17 + +template +unordered_multimap(initializer_list>, typename see below::size_type, Allocator) + -> unordered_multimap, equal_to, Allocator>; // C++17 + +template +unordered_multimap(initializer_list>, Allocator) + -> unordered_multimap, equal_to, Allocator>; // C++17 + +template +unordered_multimap(initializer_list>, typename see below::size_type, Hash, + Allocator) + -> unordered_multimap, Allocator>; // C++17 + +template + void swap(unordered_multimap& x, + unordered_multimap& y) + noexcept(noexcept(x.swap(y))); + +template + typename unordered_map::size_type + erase_if(unordered_map& c, Predicate pred); // C++20 + +template + typename unordered_multimap::size_type + erase_if(unordered_multimap& c, Predicate pred); // C++20 + +template + bool + operator==(const unordered_multimap& x, + const unordered_multimap& y); + +template + bool + operator!=(const unordered_multimap& x, + const unordered_multimap& y); + +} // std + +*/ + +#include <__algorithm/is_permutation.h> +#include <__assert> // all public C++ headers provide the assertion handler +#include <__config> +#include <__debug> +#include <__functional/is_transparent.h> +#include <__functional/operations.h> +#include <__hash_table> +#include <__iterator/distance.h> +#include <__iterator/erase_if_container.h> +#include <__iterator/iterator_traits.h> +#include <__memory/addressof.h> +#include <__memory/allocator.h> +#include <__memory_resource/polymorphic_allocator.h> +#include <__node_handle> +#include <__type_traits/is_allocator.h> +#include <__utility/forward.h> +#include +#include +#include + +// standard-mandated includes + +// [iterator.range] +#include <__iterator/access.h> +#include <__iterator/data.h> +#include <__iterator/empty.h> +#include <__iterator/reverse_access.h> +#include <__iterator/size.h> + +// [unord.map.syn] +#include +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +template ::value && !__libcpp_is_final<_Hash>::value> +class __unordered_map_hasher + : private _Hash +{ +public: + _LIBCPP_INLINE_VISIBILITY + __unordered_map_hasher() + _NOEXCEPT_(is_nothrow_default_constructible<_Hash>::value) + : _Hash() {} + _LIBCPP_INLINE_VISIBILITY + __unordered_map_hasher(const _Hash& __h) + _NOEXCEPT_(is_nothrow_copy_constructible<_Hash>::value) + : _Hash(__h) {} + _LIBCPP_INLINE_VISIBILITY + const _Hash& hash_function() const _NOEXCEPT {return *this;} + _LIBCPP_INLINE_VISIBILITY + size_t operator()(const _Cp& __x) const + {return static_cast(*this)(__x.__get_value().first);} + _LIBCPP_INLINE_VISIBILITY + size_t operator()(const _Key& __x) const + {return static_cast(*this)(__x);} +#if _LIBCPP_STD_VER > 17 + template + _LIBCPP_INLINE_VISIBILITY + size_t operator()(const _K2& __x) const + {return static_cast(*this)(__x);} +#endif + _LIBCPP_INLINE_VISIBILITY + void swap(__unordered_map_hasher& __y) + _NOEXCEPT_(__is_nothrow_swappable<_Hash>::value) + { + using _VSTD::swap; + swap(static_cast<_Hash&>(*this), static_cast<_Hash&>(__y)); + } +}; + +template +class __unordered_map_hasher<_Key, _Cp, _Hash, _Pred, false> +{ + _Hash __hash_; +public: + _LIBCPP_INLINE_VISIBILITY + __unordered_map_hasher() + _NOEXCEPT_(is_nothrow_default_constructible<_Hash>::value) + : __hash_() {} + _LIBCPP_INLINE_VISIBILITY + __unordered_map_hasher(const _Hash& __h) + _NOEXCEPT_(is_nothrow_copy_constructible<_Hash>::value) + : __hash_(__h) {} + _LIBCPP_INLINE_VISIBILITY + const _Hash& hash_function() const _NOEXCEPT {return __hash_;} + _LIBCPP_INLINE_VISIBILITY + size_t operator()(const _Cp& __x) const + {return __hash_(__x.__get_value().first);} + _LIBCPP_INLINE_VISIBILITY + size_t operator()(const _Key& __x) const + {return __hash_(__x);} +#if _LIBCPP_STD_VER > 17 + template + _LIBCPP_INLINE_VISIBILITY + size_t operator()(const _K2& __x) const + {return __hash_(__x);} +#endif + _LIBCPP_INLINE_VISIBILITY + void swap(__unordered_map_hasher& __y) + _NOEXCEPT_(__is_nothrow_swappable<_Hash>::value) + { + using _VSTD::swap; + swap(__hash_, __y.__hash_); + } +}; + +template +inline _LIBCPP_INLINE_VISIBILITY +void +swap(__unordered_map_hasher<_Key, _Cp, _Hash, _Pred, __b>& __x, + __unordered_map_hasher<_Key, _Cp, _Hash, _Pred, __b>& __y) + _NOEXCEPT_(_NOEXCEPT_(__x.swap(__y))) +{ + __x.swap(__y); +} + +template ::value && !__libcpp_is_final<_Pred>::value> +class __unordered_map_equal + : private _Pred +{ +public: + _LIBCPP_INLINE_VISIBILITY + __unordered_map_equal() + _NOEXCEPT_(is_nothrow_default_constructible<_Pred>::value) + : _Pred() {} + _LIBCPP_INLINE_VISIBILITY + __unordered_map_equal(const _Pred& __p) + _NOEXCEPT_(is_nothrow_copy_constructible<_Pred>::value) + : _Pred(__p) {} + _LIBCPP_INLINE_VISIBILITY + const _Pred& key_eq() const _NOEXCEPT {return *this;} + _LIBCPP_INLINE_VISIBILITY + bool operator()(const _Cp& __x, const _Cp& __y) const + {return static_cast(*this)(__x.__get_value().first, __y.__get_value().first);} + _LIBCPP_INLINE_VISIBILITY + bool operator()(const _Cp& __x, const _Key& __y) const + {return static_cast(*this)(__x.__get_value().first, __y);} + _LIBCPP_INLINE_VISIBILITY + bool operator()(const _Key& __x, const _Cp& __y) const + {return static_cast(*this)(__x, __y.__get_value().first);} +#if _LIBCPP_STD_VER > 17 + template + _LIBCPP_INLINE_VISIBILITY + bool operator()(const _Cp& __x, const _K2& __y) const + {return static_cast(*this)(__x.__get_value().first, __y);} + template + _LIBCPP_INLINE_VISIBILITY + bool operator()(const _K2& __x, const _Cp& __y) const + {return static_cast(*this)(__x, __y.__get_value().first);} + template + _LIBCPP_INLINE_VISIBILITY + bool operator()(const _Key& __x, const _K2& __y) const + {return static_cast(*this)(__x, __y);} + template + _LIBCPP_INLINE_VISIBILITY + bool operator()(const _K2& __x, const _Key& __y) const + {return static_cast(*this)(__x, __y);} +#endif + _LIBCPP_INLINE_VISIBILITY + void swap(__unordered_map_equal& __y) + _NOEXCEPT_(__is_nothrow_swappable<_Pred>::value) + { + using _VSTD::swap; + swap(static_cast<_Pred&>(*this), static_cast<_Pred&>(__y)); + } +}; + +template +class __unordered_map_equal<_Key, _Cp, _Pred, _Hash, false> +{ + _Pred __pred_; +public: + _LIBCPP_INLINE_VISIBILITY + __unordered_map_equal() + _NOEXCEPT_(is_nothrow_default_constructible<_Pred>::value) + : __pred_() {} + _LIBCPP_INLINE_VISIBILITY + __unordered_map_equal(const _Pred& __p) + _NOEXCEPT_(is_nothrow_copy_constructible<_Pred>::value) + : __pred_(__p) {} + _LIBCPP_INLINE_VISIBILITY + const _Pred& key_eq() const _NOEXCEPT {return __pred_;} + _LIBCPP_INLINE_VISIBILITY + bool operator()(const _Cp& __x, const _Cp& __y) const + {return __pred_(__x.__get_value().first, __y.__get_value().first);} + _LIBCPP_INLINE_VISIBILITY + bool operator()(const _Cp& __x, const _Key& __y) const + {return __pred_(__x.__get_value().first, __y);} + _LIBCPP_INLINE_VISIBILITY + bool operator()(const _Key& __x, const _Cp& __y) const + {return __pred_(__x, __y.__get_value().first);} +#if _LIBCPP_STD_VER > 17 + template + _LIBCPP_INLINE_VISIBILITY + bool operator()(const _Cp& __x, const _K2& __y) const + {return __pred_(__x.__get_value().first, __y);} + template + _LIBCPP_INLINE_VISIBILITY + bool operator()(const _K2& __x, const _Cp& __y) const + {return __pred_(__x, __y.__get_value().first);} + template + _LIBCPP_INLINE_VISIBILITY + bool operator()(const _Key& __x, const _K2& __y) const + {return __pred_(__x, __y);} + template + _LIBCPP_INLINE_VISIBILITY + bool operator()(const _K2& __x, const _Key& __y) const + {return __pred_(__x, __y);} +#endif + _LIBCPP_INLINE_VISIBILITY + void swap(__unordered_map_equal& __y) + _NOEXCEPT_(__is_nothrow_swappable<_Pred>::value) + { + using _VSTD::swap; + swap(__pred_, __y.__pred_); + } +}; + +template +inline _LIBCPP_INLINE_VISIBILITY +void +swap(__unordered_map_equal<_Key, _Cp, _Pred, _Hash, __b>& __x, + __unordered_map_equal<_Key, _Cp, _Pred, _Hash, __b>& __y) + _NOEXCEPT_(_NOEXCEPT_(__x.swap(__y))) +{ + __x.swap(__y); +} + +template +class __hash_map_node_destructor +{ + typedef _Alloc allocator_type; + typedef allocator_traits __alloc_traits; + +public: + + typedef typename __alloc_traits::pointer pointer; +private: + + allocator_type& __na_; + + __hash_map_node_destructor& operator=(const __hash_map_node_destructor&); + +public: + bool __first_constructed; + bool __second_constructed; + + _LIBCPP_INLINE_VISIBILITY + explicit __hash_map_node_destructor(allocator_type& __na) _NOEXCEPT + : __na_(__na), + __first_constructed(false), + __second_constructed(false) + {} + +#ifndef _LIBCPP_CXX03_LANG + _LIBCPP_INLINE_VISIBILITY + __hash_map_node_destructor(__hash_node_destructor&& __x) + _NOEXCEPT + : __na_(__x.__na_), + __first_constructed(__x.__value_constructed), + __second_constructed(__x.__value_constructed) + { + __x.__value_constructed = false; + } +#else // _LIBCPP_CXX03_LANG + _LIBCPP_INLINE_VISIBILITY + __hash_map_node_destructor(const __hash_node_destructor& __x) + : __na_(__x.__na_), + __first_constructed(__x.__value_constructed), + __second_constructed(__x.__value_constructed) + { + const_cast(__x.__value_constructed) = false; + } +#endif // _LIBCPP_CXX03_LANG + + _LIBCPP_INLINE_VISIBILITY + void operator()(pointer __p) _NOEXCEPT + { + if (__second_constructed) + __alloc_traits::destroy(__na_, _VSTD::addressof(__p->__value_.__get_value().second)); + if (__first_constructed) + __alloc_traits::destroy(__na_, _VSTD::addressof(__p->__value_.__get_value().first)); + if (__p) + __alloc_traits::deallocate(__na_, __p, 1); + } +}; + +#ifndef _LIBCPP_CXX03_LANG +template +struct _LIBCPP_STANDALONE_DEBUG __hash_value_type +{ + typedef _Key key_type; + typedef _Tp mapped_type; + typedef pair value_type; + typedef pair __nc_ref_pair_type; + typedef pair __nc_rref_pair_type; + +private: + value_type __cc_; + +public: + _LIBCPP_INLINE_VISIBILITY + value_type& __get_value() + { +#if _LIBCPP_STD_VER > 14 + return *_VSTD::launder(_VSTD::addressof(__cc_)); +#else + return __cc_; +#endif + } + + _LIBCPP_INLINE_VISIBILITY + const value_type& __get_value() const + { +#if _LIBCPP_STD_VER > 14 + return *_VSTD::launder(_VSTD::addressof(__cc_)); +#else + return __cc_; +#endif + } + + _LIBCPP_INLINE_VISIBILITY + __nc_ref_pair_type __ref() + { + value_type& __v = __get_value(); + return __nc_ref_pair_type(const_cast(__v.first), __v.second); + } + + _LIBCPP_INLINE_VISIBILITY + __nc_rref_pair_type __move() + { + value_type& __v = __get_value(); + return __nc_rref_pair_type( + _VSTD::move(const_cast(__v.first)), + _VSTD::move(__v.second)); + } + + _LIBCPP_INLINE_VISIBILITY + __hash_value_type& operator=(const __hash_value_type& __v) + { + __ref() = __v.__get_value(); + return *this; + } + + _LIBCPP_INLINE_VISIBILITY + __hash_value_type& operator=(__hash_value_type&& __v) + { + __ref() = __v.__move(); + return *this; + } + + template ::value> + > + _LIBCPP_INLINE_VISIBILITY + __hash_value_type& operator=(_ValueTp&& __v) + { + __ref() = _VSTD::forward<_ValueTp>(__v); + return *this; + } + +private: + __hash_value_type(const __hash_value_type& __v) = delete; + __hash_value_type(__hash_value_type&& __v) = delete; + template + explicit __hash_value_type(_Args&& ...__args) = delete; + + ~__hash_value_type() = delete; +}; + +#else + +template +struct __hash_value_type +{ + typedef _Key key_type; + typedef _Tp mapped_type; + typedef pair value_type; + +private: + value_type __cc_; + +public: + _LIBCPP_INLINE_VISIBILITY + value_type& __get_value() { return __cc_; } + _LIBCPP_INLINE_VISIBILITY + const value_type& __get_value() const { return __cc_; } + +private: + ~__hash_value_type(); +}; + +#endif + +template +class _LIBCPP_TEMPLATE_VIS __hash_map_iterator +{ + _HashIterator __i_; + + typedef __hash_node_types_from_iterator<_HashIterator> _NodeTypes; + +public: + typedef forward_iterator_tag iterator_category; + typedef typename _NodeTypes::__map_value_type value_type; + typedef typename _NodeTypes::difference_type difference_type; + typedef value_type& reference; + typedef typename _NodeTypes::__map_value_type_pointer pointer; + + _LIBCPP_INLINE_VISIBILITY + __hash_map_iterator() _NOEXCEPT {} + + _LIBCPP_INLINE_VISIBILITY + __hash_map_iterator(_HashIterator __i) _NOEXCEPT : __i_(__i) {} + + _LIBCPP_INLINE_VISIBILITY + reference operator*() const {return __i_->__get_value();} + _LIBCPP_INLINE_VISIBILITY + pointer operator->() const {return pointer_traits::pointer_to(__i_->__get_value());} + + _LIBCPP_INLINE_VISIBILITY + __hash_map_iterator& operator++() {++__i_; return *this;} + _LIBCPP_INLINE_VISIBILITY + __hash_map_iterator operator++(int) + { + __hash_map_iterator __t(*this); + ++(*this); + return __t; + } + + friend _LIBCPP_INLINE_VISIBILITY + bool operator==(const __hash_map_iterator& __x, const __hash_map_iterator& __y) + {return __x.__i_ == __y.__i_;} + friend _LIBCPP_INLINE_VISIBILITY + bool operator!=(const __hash_map_iterator& __x, const __hash_map_iterator& __y) + {return __x.__i_ != __y.__i_;} + + template friend class _LIBCPP_TEMPLATE_VIS unordered_map; + template friend class _LIBCPP_TEMPLATE_VIS unordered_multimap; + template friend class _LIBCPP_TEMPLATE_VIS __hash_const_iterator; + template friend class _LIBCPP_TEMPLATE_VIS __hash_const_local_iterator; + template friend class _LIBCPP_TEMPLATE_VIS __hash_map_const_iterator; +}; + +template +class _LIBCPP_TEMPLATE_VIS __hash_map_const_iterator +{ + _HashIterator __i_; + + typedef __hash_node_types_from_iterator<_HashIterator> _NodeTypes; + +public: + typedef forward_iterator_tag iterator_category; + typedef typename _NodeTypes::__map_value_type value_type; + typedef typename _NodeTypes::difference_type difference_type; + typedef const value_type& reference; + typedef typename _NodeTypes::__const_map_value_type_pointer pointer; + + _LIBCPP_INLINE_VISIBILITY + __hash_map_const_iterator() _NOEXCEPT {} + + _LIBCPP_INLINE_VISIBILITY + __hash_map_const_iterator(_HashIterator __i) _NOEXCEPT : __i_(__i) {} + _LIBCPP_INLINE_VISIBILITY + __hash_map_const_iterator( + __hash_map_iterator __i) + _NOEXCEPT + : __i_(__i.__i_) {} + + _LIBCPP_INLINE_VISIBILITY + reference operator*() const {return __i_->__get_value();} + _LIBCPP_INLINE_VISIBILITY + pointer operator->() const {return pointer_traits::pointer_to(__i_->__get_value());} + + _LIBCPP_INLINE_VISIBILITY + __hash_map_const_iterator& operator++() {++__i_; return *this;} + _LIBCPP_INLINE_VISIBILITY + __hash_map_const_iterator operator++(int) + { + __hash_map_const_iterator __t(*this); + ++(*this); + return __t; + } + + friend _LIBCPP_INLINE_VISIBILITY + bool operator==(const __hash_map_const_iterator& __x, const __hash_map_const_iterator& __y) + {return __x.__i_ == __y.__i_;} + friend _LIBCPP_INLINE_VISIBILITY + bool operator!=(const __hash_map_const_iterator& __x, const __hash_map_const_iterator& __y) + {return __x.__i_ != __y.__i_;} + + template friend class _LIBCPP_TEMPLATE_VIS unordered_map; + template friend class _LIBCPP_TEMPLATE_VIS unordered_multimap; + template friend class _LIBCPP_TEMPLATE_VIS __hash_const_iterator; + template friend class _LIBCPP_TEMPLATE_VIS __hash_const_local_iterator; +}; + +template +class unordered_multimap; + +template , class _Pred = equal_to<_Key>, + class _Alloc = allocator > > +class _LIBCPP_TEMPLATE_VIS unordered_map +{ +public: + // types + typedef _Key key_type; + typedef _Tp mapped_type; + typedef __type_identity_t<_Hash> hasher; + typedef __type_identity_t<_Pred> key_equal; + typedef __type_identity_t<_Alloc> allocator_type; + typedef pair value_type; + typedef value_type& reference; + typedef const value_type& const_reference; + static_assert((is_same::value), + "Invalid allocator::value_type"); + +private: + typedef __hash_value_type __value_type; + typedef __unordered_map_hasher __hasher; + typedef __unordered_map_equal __key_equal; + typedef __rebind_alloc, __value_type> __allocator_type; + + typedef __hash_table<__value_type, __hasher, + __key_equal, __allocator_type> __table; + + __table __table_; + + typedef typename __table::_NodeTypes _NodeTypes; + typedef typename __table::__node_pointer __node_pointer; + typedef typename __table::__node_const_pointer __node_const_pointer; + typedef typename __table::__node_traits __node_traits; + typedef typename __table::__node_allocator __node_allocator; + typedef typename __table::__node __node; + typedef __hash_map_node_destructor<__node_allocator> _Dp; + typedef unique_ptr<__node, _Dp> __node_holder; + typedef allocator_traits __alloc_traits; + + static_assert(is_same >::value, + "[allocator.requirements] states that rebinding an allocator to the same type should result in the " + "original allocator"); + + static_assert((is_same::value), ""); + static_assert((is_same::value), ""); +public: + typedef typename __alloc_traits::pointer pointer; + typedef typename __alloc_traits::const_pointer const_pointer; + typedef typename __table::size_type size_type; + typedef typename __table::difference_type difference_type; + + typedef __hash_map_iterator iterator; + typedef __hash_map_const_iterator const_iterator; + typedef __hash_map_iterator local_iterator; + typedef __hash_map_const_iterator const_local_iterator; + +#if _LIBCPP_STD_VER > 14 + typedef __map_node_handle<__node, allocator_type> node_type; + typedef __insert_return_type insert_return_type; +#endif + + template + friend class _LIBCPP_TEMPLATE_VIS unordered_map; + template + friend class _LIBCPP_TEMPLATE_VIS unordered_multimap; + + _LIBCPP_INLINE_VISIBILITY + unordered_map() + _NOEXCEPT_(is_nothrow_default_constructible<__table>::value) + { + _VSTD::__debug_db_insert_c(this); + } + explicit unordered_map(size_type __n, const hasher& __hf = hasher(), + const key_equal& __eql = key_equal()); + unordered_map(size_type __n, const hasher& __hf, + const key_equal& __eql, + const allocator_type& __a); + template + unordered_map(_InputIterator __first, _InputIterator __last); + template + unordered_map(_InputIterator __first, _InputIterator __last, + size_type __n, const hasher& __hf = hasher(), + const key_equal& __eql = key_equal()); + template + unordered_map(_InputIterator __first, _InputIterator __last, + size_type __n, const hasher& __hf, + const key_equal& __eql, + const allocator_type& __a); + _LIBCPP_INLINE_VISIBILITY + explicit unordered_map(const allocator_type& __a); + unordered_map(const unordered_map& __u); + unordered_map(const unordered_map& __u, const allocator_type& __a); +#ifndef _LIBCPP_CXX03_LANG + _LIBCPP_INLINE_VISIBILITY + unordered_map(unordered_map&& __u) + _NOEXCEPT_(is_nothrow_move_constructible<__table>::value); + unordered_map(unordered_map&& __u, const allocator_type& __a); + unordered_map(initializer_list __il); + unordered_map(initializer_list __il, size_type __n, + const hasher& __hf = hasher(), const key_equal& __eql = key_equal()); + unordered_map(initializer_list __il, size_type __n, + const hasher& __hf, const key_equal& __eql, + const allocator_type& __a); +#endif // _LIBCPP_CXX03_LANG +#if _LIBCPP_STD_VER > 11 + _LIBCPP_INLINE_VISIBILITY + unordered_map(size_type __n, const allocator_type& __a) + : unordered_map(__n, hasher(), key_equal(), __a) {} + _LIBCPP_INLINE_VISIBILITY + unordered_map(size_type __n, const hasher& __hf, const allocator_type& __a) + : unordered_map(__n, __hf, key_equal(), __a) {} + template + _LIBCPP_INLINE_VISIBILITY + unordered_map(_InputIterator __first, _InputIterator __last, size_type __n, const allocator_type& __a) + : unordered_map(__first, __last, __n, hasher(), key_equal(), __a) {} + template + _LIBCPP_INLINE_VISIBILITY + unordered_map(_InputIterator __first, _InputIterator __last, size_type __n, const hasher& __hf, + const allocator_type& __a) + : unordered_map(__first, __last, __n, __hf, key_equal(), __a) {} + _LIBCPP_INLINE_VISIBILITY + unordered_map(initializer_list __il, size_type __n, const allocator_type& __a) + : unordered_map(__il, __n, hasher(), key_equal(), __a) {} + _LIBCPP_INLINE_VISIBILITY + unordered_map(initializer_list __il, size_type __n, const hasher& __hf, + const allocator_type& __a) + : unordered_map(__il, __n, __hf, key_equal(), __a) {} +#endif + _LIBCPP_INLINE_VISIBILITY + ~unordered_map() { + static_assert(sizeof(std::__diagnose_unordered_container_requirements<_Key, _Hash, _Pred>(0)), ""); + } + + _LIBCPP_INLINE_VISIBILITY + unordered_map& operator=(const unordered_map& __u) + { +#ifndef _LIBCPP_CXX03_LANG + __table_ = __u.__table_; +#else + if (this != _VSTD::addressof(__u)) { + __table_.clear(); + __table_.hash_function() = __u.__table_.hash_function(); + __table_.key_eq() = __u.__table_.key_eq(); + __table_.max_load_factor() = __u.__table_.max_load_factor(); + __table_.__copy_assign_alloc(__u.__table_); + insert(__u.begin(), __u.end()); + } +#endif + return *this; + } +#ifndef _LIBCPP_CXX03_LANG + _LIBCPP_INLINE_VISIBILITY + unordered_map& operator=(unordered_map&& __u) + _NOEXCEPT_(is_nothrow_move_assignable<__table>::value); + _LIBCPP_INLINE_VISIBILITY + unordered_map& operator=(initializer_list __il); +#endif // _LIBCPP_CXX03_LANG + + _LIBCPP_INLINE_VISIBILITY + allocator_type get_allocator() const _NOEXCEPT + {return allocator_type(__table_.__node_alloc());} + + _LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_INLINE_VISIBILITY + bool empty() const _NOEXCEPT {return __table_.size() == 0;} + _LIBCPP_INLINE_VISIBILITY + size_type size() const _NOEXCEPT {return __table_.size();} + _LIBCPP_INLINE_VISIBILITY + size_type max_size() const _NOEXCEPT {return __table_.max_size();} + + _LIBCPP_INLINE_VISIBILITY + iterator begin() _NOEXCEPT {return __table_.begin();} + _LIBCPP_INLINE_VISIBILITY + iterator end() _NOEXCEPT {return __table_.end();} + _LIBCPP_INLINE_VISIBILITY + const_iterator begin() const _NOEXCEPT {return __table_.begin();} + _LIBCPP_INLINE_VISIBILITY + const_iterator end() const _NOEXCEPT {return __table_.end();} + _LIBCPP_INLINE_VISIBILITY + const_iterator cbegin() const _NOEXCEPT {return __table_.begin();} + _LIBCPP_INLINE_VISIBILITY + const_iterator cend() const _NOEXCEPT {return __table_.end();} + + _LIBCPP_INLINE_VISIBILITY + pair insert(const value_type& __x) + {return __table_.__insert_unique(__x);} + + iterator insert(const_iterator __p, const value_type& __x) { + _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(_VSTD::addressof(__p)) == this, + "unordered_map::insert(const_iterator, const value_type&) called with an iterator not " + "referring to this unordered_map"); + ((void)__p); + return insert(__x).first; + } + + template + _LIBCPP_INLINE_VISIBILITY + void insert(_InputIterator __first, _InputIterator __last); + +#ifndef _LIBCPP_CXX03_LANG + _LIBCPP_INLINE_VISIBILITY + void insert(initializer_list __il) + {insert(__il.begin(), __il.end());} + + _LIBCPP_INLINE_VISIBILITY + pair insert(value_type&& __x) + {return __table_.__insert_unique(_VSTD::move(__x));} + + iterator insert(const_iterator __p, value_type&& __x) { + _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(_VSTD::addressof(__p)) == this, + "unordered_map::insert(const_iterator, const value_type&) called with an iterator not" + " referring to this unordered_map"); + ((void)__p); + return __table_.__insert_unique(_VSTD::move(__x)).first; + } + + template ::value> > + _LIBCPP_INLINE_VISIBILITY + pair insert(_Pp&& __x) + {return __table_.__insert_unique(_VSTD::forward<_Pp>(__x));} + + template ::value> > + _LIBCPP_INLINE_VISIBILITY + iterator insert(const_iterator __p, _Pp&& __x) + { + _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(_VSTD::addressof(__p)) == this, + "unordered_map::insert(const_iterator, value_type&&) called with an iterator not" + " referring to this unordered_map"); + ((void)__p); + return insert(_VSTD::forward<_Pp>(__x)).first; + } + + template + _LIBCPP_INLINE_VISIBILITY + pair emplace(_Args&&... __args) { + return __table_.__emplace_unique(_VSTD::forward<_Args>(__args)...); + } + + template + _LIBCPP_INLINE_VISIBILITY + iterator emplace_hint(const_iterator __p, _Args&&... __args) { + _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(_VSTD::addressof(__p)) == this, + "unordered_map::emplace_hint(const_iterator, args...) called with an iterator not" + " referring to this unordered_map"); + ((void)__p); + return __table_.__emplace_unique(_VSTD::forward<_Args>(__args)...).first; + } + +#endif // _LIBCPP_CXX03_LANG + +#if _LIBCPP_STD_VER > 14 + template + _LIBCPP_INLINE_VISIBILITY + pair try_emplace(const key_type& __k, _Args&&... __args) + { + return __table_.__emplace_unique_key_args(__k, piecewise_construct, + _VSTD::forward_as_tuple(__k), + _VSTD::forward_as_tuple(_VSTD::forward<_Args>(__args)...)); + } + + template + _LIBCPP_INLINE_VISIBILITY + pair try_emplace(key_type&& __k, _Args&&... __args) + { + return __table_.__emplace_unique_key_args(__k, piecewise_construct, + _VSTD::forward_as_tuple(_VSTD::move(__k)), + _VSTD::forward_as_tuple(_VSTD::forward<_Args>(__args)...)); + } + + template + _LIBCPP_INLINE_VISIBILITY + iterator try_emplace(const_iterator __h, const key_type& __k, _Args&&... __args) + { + _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(_VSTD::addressof(__h)) == this, + "unordered_map::try_emplace(const_iterator, key, args...) called with an iterator not" + " referring to this unordered_map"); + ((void)__h); + return try_emplace(__k, _VSTD::forward<_Args>(__args)...).first; + } + + template + _LIBCPP_INLINE_VISIBILITY + iterator try_emplace(const_iterator __h, key_type&& __k, _Args&&... __args) + { + _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(_VSTD::addressof(__h)) == this, + "unordered_map::try_emplace(const_iterator, key, args...) called with an iterator not" + " referring to this unordered_map"); + ((void)__h); + return try_emplace(_VSTD::move(__k), _VSTD::forward<_Args>(__args)...).first; + } + + template + _LIBCPP_INLINE_VISIBILITY + pair insert_or_assign(const key_type& __k, _Vp&& __v) + { + pair __res = __table_.__emplace_unique_key_args(__k, + __k, _VSTD::forward<_Vp>(__v)); + if (!__res.second) { + __res.first->second = _VSTD::forward<_Vp>(__v); + } + return __res; + } + + template + _LIBCPP_INLINE_VISIBILITY + pair insert_or_assign(key_type&& __k, _Vp&& __v) + { + pair __res = __table_.__emplace_unique_key_args(__k, + _VSTD::move(__k), _VSTD::forward<_Vp>(__v)); + if (!__res.second) { + __res.first->second = _VSTD::forward<_Vp>(__v); + } + return __res; + } + + template + _LIBCPP_INLINE_VISIBILITY + iterator insert_or_assign(const_iterator, const key_type& __k, _Vp&& __v) + { + // FIXME: Add debug mode checking for the iterator input + return insert_or_assign(__k, _VSTD::forward<_Vp>(__v)).first; + } + + template + _LIBCPP_INLINE_VISIBILITY + iterator insert_or_assign(const_iterator, key_type&& __k, _Vp&& __v) + { + // FIXME: Add debug mode checking for the iterator input + return insert_or_assign(_VSTD::move(__k), _VSTD::forward<_Vp>(__v)).first; + } +#endif // _LIBCPP_STD_VER > 14 + + _LIBCPP_INLINE_VISIBILITY + iterator erase(const_iterator __p) {return __table_.erase(__p.__i_);} + _LIBCPP_INLINE_VISIBILITY + iterator erase(iterator __p) {return __table_.erase(__p.__i_);} + _LIBCPP_INLINE_VISIBILITY + size_type erase(const key_type& __k) {return __table_.__erase_unique(__k);} + _LIBCPP_INLINE_VISIBILITY + iterator erase(const_iterator __first, const_iterator __last) + {return __table_.erase(__first.__i_, __last.__i_);} + _LIBCPP_INLINE_VISIBILITY + void clear() _NOEXCEPT {__table_.clear();} + +#if _LIBCPP_STD_VER > 14 + _LIBCPP_INLINE_VISIBILITY + insert_return_type insert(node_type&& __nh) + { + _LIBCPP_ASSERT(__nh.empty() || __nh.get_allocator() == get_allocator(), + "node_type with incompatible allocator passed to unordered_map::insert()"); + return __table_.template __node_handle_insert_unique< + node_type, insert_return_type>(_VSTD::move(__nh)); + } + _LIBCPP_INLINE_VISIBILITY + iterator insert(const_iterator __hint, node_type&& __nh) + { + _LIBCPP_ASSERT(__nh.empty() || __nh.get_allocator() == get_allocator(), + "node_type with incompatible allocator passed to unordered_map::insert()"); + return __table_.template __node_handle_insert_unique( + __hint.__i_, _VSTD::move(__nh)); + } + _LIBCPP_INLINE_VISIBILITY + node_type extract(key_type const& __key) + { + return __table_.template __node_handle_extract(__key); + } + _LIBCPP_INLINE_VISIBILITY + node_type extract(const_iterator __it) + { + return __table_.template __node_handle_extract( + __it.__i_); + } + + template + _LIBCPP_INLINE_VISIBILITY + void merge(unordered_map& __source) + { + _LIBCPP_ASSERT(__source.get_allocator() == get_allocator(), + "merging container with incompatible allocator"); + return __table_.__node_handle_merge_unique(__source.__table_); + } + template + _LIBCPP_INLINE_VISIBILITY + void merge(unordered_map&& __source) + { + _LIBCPP_ASSERT(__source.get_allocator() == get_allocator(), + "merging container with incompatible allocator"); + return __table_.__node_handle_merge_unique(__source.__table_); + } + template + _LIBCPP_INLINE_VISIBILITY + void merge(unordered_multimap& __source) + { + _LIBCPP_ASSERT(__source.get_allocator() == get_allocator(), + "merging container with incompatible allocator"); + return __table_.__node_handle_merge_unique(__source.__table_); + } + template + _LIBCPP_INLINE_VISIBILITY + void merge(unordered_multimap&& __source) + { + _LIBCPP_ASSERT(__source.get_allocator() == get_allocator(), + "merging container with incompatible allocator"); + return __table_.__node_handle_merge_unique(__source.__table_); + } +#endif + + _LIBCPP_INLINE_VISIBILITY + void swap(unordered_map& __u) + _NOEXCEPT_(__is_nothrow_swappable<__table>::value) + { __table_.swap(__u.__table_);} + + _LIBCPP_INLINE_VISIBILITY + hasher hash_function() const + {return __table_.hash_function().hash_function();} + _LIBCPP_INLINE_VISIBILITY + key_equal key_eq() const + {return __table_.key_eq().key_eq();} + + _LIBCPP_INLINE_VISIBILITY + iterator find(const key_type& __k) {return __table_.find(__k);} + _LIBCPP_INLINE_VISIBILITY + const_iterator find(const key_type& __k) const {return __table_.find(__k);} +#if _LIBCPP_STD_VER > 17 + template ::value && __is_transparent::value>* = nullptr> + _LIBCPP_INLINE_VISIBILITY + iterator find(const _K2& __k) {return __table_.find(__k);} + template ::value && __is_transparent::value>* = nullptr> + _LIBCPP_INLINE_VISIBILITY + const_iterator find(const _K2& __k) const {return __table_.find(__k);} +#endif // _LIBCPP_STD_VER > 17 + + _LIBCPP_INLINE_VISIBILITY + size_type count(const key_type& __k) const {return __table_.__count_unique(__k);} +#if _LIBCPP_STD_VER > 17 + template ::value && __is_transparent::value>* = nullptr> + _LIBCPP_INLINE_VISIBILITY + size_type count(const _K2& __k) const {return __table_.__count_unique(__k);} +#endif // _LIBCPP_STD_VER > 17 + +#if _LIBCPP_STD_VER > 17 + _LIBCPP_INLINE_VISIBILITY + bool contains(const key_type& __k) const {return find(__k) != end();} + + template ::value && __is_transparent::value>* = nullptr> + _LIBCPP_INLINE_VISIBILITY + bool contains(const _K2& __k) const {return find(__k) != end();} +#endif // _LIBCPP_STD_VER > 17 + + _LIBCPP_INLINE_VISIBILITY + pair equal_range(const key_type& __k) + {return __table_.__equal_range_unique(__k);} + _LIBCPP_INLINE_VISIBILITY + pair equal_range(const key_type& __k) const + {return __table_.__equal_range_unique(__k);} +#if _LIBCPP_STD_VER > 17 + template ::value && __is_transparent::value>* = nullptr> + _LIBCPP_INLINE_VISIBILITY + pair equal_range(const _K2& __k) + {return __table_.__equal_range_unique(__k);} + template ::value && __is_transparent::value>* = nullptr> + _LIBCPP_INLINE_VISIBILITY + pair equal_range(const _K2& __k) const + {return __table_.__equal_range_unique(__k);} +#endif // _LIBCPP_STD_VER > 17 + + mapped_type& operator[](const key_type& __k); +#ifndef _LIBCPP_CXX03_LANG + mapped_type& operator[](key_type&& __k); +#endif + + mapped_type& at(const key_type& __k); + const mapped_type& at(const key_type& __k) const; + + _LIBCPP_INLINE_VISIBILITY + size_type bucket_count() const _NOEXCEPT {return __table_.bucket_count();} + _LIBCPP_INLINE_VISIBILITY + size_type max_bucket_count() const _NOEXCEPT {return __table_.max_bucket_count();} + + _LIBCPP_INLINE_VISIBILITY + size_type bucket_size(size_type __n) const + {return __table_.bucket_size(__n);} + _LIBCPP_INLINE_VISIBILITY + size_type bucket(const key_type& __k) const {return __table_.bucket(__k);} + + _LIBCPP_INLINE_VISIBILITY + local_iterator begin(size_type __n) {return __table_.begin(__n);} + _LIBCPP_INLINE_VISIBILITY + local_iterator end(size_type __n) {return __table_.end(__n);} + _LIBCPP_INLINE_VISIBILITY + const_local_iterator begin(size_type __n) const {return __table_.cbegin(__n);} + _LIBCPP_INLINE_VISIBILITY + const_local_iterator end(size_type __n) const {return __table_.cend(__n);} + _LIBCPP_INLINE_VISIBILITY + const_local_iterator cbegin(size_type __n) const {return __table_.cbegin(__n);} + _LIBCPP_INLINE_VISIBILITY + const_local_iterator cend(size_type __n) const {return __table_.cend(__n);} + + _LIBCPP_INLINE_VISIBILITY + float load_factor() const _NOEXCEPT {return __table_.load_factor();} + _LIBCPP_INLINE_VISIBILITY + float max_load_factor() const _NOEXCEPT {return __table_.max_load_factor();} + _LIBCPP_INLINE_VISIBILITY + void max_load_factor(float __mlf) {__table_.max_load_factor(__mlf);} + _LIBCPP_INLINE_VISIBILITY + void rehash(size_type __n) {__table_.__rehash_unique(__n);} + _LIBCPP_INLINE_VISIBILITY + void reserve(size_type __n) {__table_.__reserve_unique(__n);} + +#ifdef _LIBCPP_ENABLE_DEBUG_MODE + + bool __dereferenceable(const const_iterator* __i) const + {return __table_.__dereferenceable(_VSTD::addressof(__i->__i_));} + bool __decrementable(const const_iterator* __i) const + {return __table_.__decrementable(_VSTD::addressof(__i->__i_));} + bool __addable(const const_iterator* __i, ptrdiff_t __n) const + {return __table_.__addable(_VSTD::addressof(__i->__i_), __n);} + bool __subscriptable(const const_iterator* __i, ptrdiff_t __n) const + {return __table_.__addable(_VSTD::addressof(__i->__i_), __n);} + +#endif // _LIBCPP_ENABLE_DEBUG_MODE + +private: + +#ifdef _LIBCPP_CXX03_LANG + __node_holder __construct_node_with_key(const key_type& __k); +#endif +}; + +#if _LIBCPP_STD_VER >= 17 +template>, + class _Pred = equal_to<__iter_key_type<_InputIterator>>, + class _Allocator = allocator<__iter_to_alloc_type<_InputIterator>>, + class = enable_if_t<__is_cpp17_input_iterator<_InputIterator>::value>, + class = enable_if_t::value>, + class = enable_if_t::value>, + class = enable_if_t::value>, + class = enable_if_t<__is_allocator<_Allocator>::value>> +unordered_map(_InputIterator, _InputIterator, typename allocator_traits<_Allocator>::size_type = 0, + _Hash = _Hash(), _Pred = _Pred(), _Allocator = _Allocator()) + -> unordered_map<__iter_key_type<_InputIterator>, __iter_mapped_type<_InputIterator>, _Hash, _Pred, _Allocator>; + +template>, + class _Pred = equal_to>, + class _Allocator = allocator>, + class = enable_if_t::value>, + class = enable_if_t::value>, + class = enable_if_t::value>, + class = enable_if_t<__is_allocator<_Allocator>::value>> +unordered_map(initializer_list>, typename allocator_traits<_Allocator>::size_type = 0, + _Hash = _Hash(), _Pred = _Pred(), _Allocator = _Allocator()) + -> unordered_map, _Tp, _Hash, _Pred, _Allocator>; + +template::value>, + class = enable_if_t<__is_allocator<_Allocator>::value>> +unordered_map(_InputIterator, _InputIterator, typename allocator_traits<_Allocator>::size_type, _Allocator) + -> unordered_map<__iter_key_type<_InputIterator>, __iter_mapped_type<_InputIterator>, + hash<__iter_key_type<_InputIterator>>, equal_to<__iter_key_type<_InputIterator>>, _Allocator>; + +template::value>, + class = enable_if_t<__is_allocator<_Allocator>::value>> +unordered_map(_InputIterator, _InputIterator, _Allocator) + -> unordered_map<__iter_key_type<_InputIterator>, __iter_mapped_type<_InputIterator>, + hash<__iter_key_type<_InputIterator>>, equal_to<__iter_key_type<_InputIterator>>, _Allocator>; + +template::value>, + class = enable_if_t::value>, + class = enable_if_t::value>, + class = enable_if_t<__is_allocator<_Allocator>::value>> +unordered_map(_InputIterator, _InputIterator, typename allocator_traits<_Allocator>::size_type, _Hash, _Allocator) + -> unordered_map<__iter_key_type<_InputIterator>, __iter_mapped_type<_InputIterator>, + _Hash, equal_to<__iter_key_type<_InputIterator>>, _Allocator>; + +template::value>> +unordered_map(initializer_list>, typename allocator_traits<_Allocator>::size_type, _Allocator) + -> unordered_map, _Tp, + hash>, + equal_to>, _Allocator>; + +template::value>> +unordered_map(initializer_list>, _Allocator) + -> unordered_map, _Tp, + hash>, + equal_to>, _Allocator>; + +template::value>, + class = enable_if_t::value>, + class = enable_if_t<__is_allocator<_Allocator>::value>> +unordered_map(initializer_list>, typename allocator_traits<_Allocator>::size_type, _Hash, _Allocator) + -> unordered_map, _Tp, _Hash, + equal_to>, _Allocator>; +#endif + +template +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map( + size_type __n, const hasher& __hf, const key_equal& __eql) + : __table_(__hf, __eql) +{ + _VSTD::__debug_db_insert_c(this); + __table_.__rehash_unique(__n); +} + +template +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map( + size_type __n, const hasher& __hf, const key_equal& __eql, + const allocator_type& __a) + : __table_(__hf, __eql, typename __table::allocator_type(__a)) +{ + _VSTD::__debug_db_insert_c(this); + __table_.__rehash_unique(__n); +} + +template +inline +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map( + const allocator_type& __a) + : __table_(typename __table::allocator_type(__a)) +{ + _VSTD::__debug_db_insert_c(this); +} + +template +template +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map( + _InputIterator __first, _InputIterator __last) +{ + _VSTD::__debug_db_insert_c(this); + insert(__first, __last); +} + +template +template +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map( + _InputIterator __first, _InputIterator __last, size_type __n, + const hasher& __hf, const key_equal& __eql) + : __table_(__hf, __eql) +{ + _VSTD::__debug_db_insert_c(this); + __table_.__rehash_unique(__n); + insert(__first, __last); +} + +template +template +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map( + _InputIterator __first, _InputIterator __last, size_type __n, + const hasher& __hf, const key_equal& __eql, const allocator_type& __a) + : __table_(__hf, __eql, typename __table::allocator_type(__a)) +{ + _VSTD::__debug_db_insert_c(this); + __table_.__rehash_unique(__n); + insert(__first, __last); +} + +template +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map( + const unordered_map& __u) + : __table_(__u.__table_) +{ + _VSTD::__debug_db_insert_c(this); + __table_.__rehash_unique(__u.bucket_count()); + insert(__u.begin(), __u.end()); +} + +template +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map( + const unordered_map& __u, const allocator_type& __a) + : __table_(__u.__table_, typename __table::allocator_type(__a)) +{ + _VSTD::__debug_db_insert_c(this); + __table_.__rehash_unique(__u.bucket_count()); + insert(__u.begin(), __u.end()); +} + +#ifndef _LIBCPP_CXX03_LANG + +template +inline +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map( + unordered_map&& __u) + _NOEXCEPT_(is_nothrow_move_constructible<__table>::value) + : __table_(_VSTD::move(__u.__table_)) +{ + _VSTD::__debug_db_insert_c(this); + std::__debug_db_swap(this, std::addressof(__u)); +} + +template +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map( + unordered_map&& __u, const allocator_type& __a) + : __table_(_VSTD::move(__u.__table_), typename __table::allocator_type(__a)) +{ + _VSTD::__debug_db_insert_c(this); + if (__a != __u.get_allocator()) + { + iterator __i = __u.begin(); + while (__u.size() != 0) { + __table_.__emplace_unique( + __u.__table_.remove((__i++).__i_)->__value_.__move()); + } + } + else + std::__debug_db_swap(this, std::addressof(__u)); +} + +template +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map( + initializer_list __il) +{ + _VSTD::__debug_db_insert_c(this); + insert(__il.begin(), __il.end()); +} + +template +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map( + initializer_list __il, size_type __n, const hasher& __hf, + const key_equal& __eql) + : __table_(__hf, __eql) +{ + _VSTD::__debug_db_insert_c(this); + __table_.__rehash_unique(__n); + insert(__il.begin(), __il.end()); +} + +template +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_map( + initializer_list __il, size_type __n, const hasher& __hf, + const key_equal& __eql, const allocator_type& __a) + : __table_(__hf, __eql, typename __table::allocator_type(__a)) +{ + _VSTD::__debug_db_insert_c(this); + __table_.__rehash_unique(__n); + insert(__il.begin(), __il.end()); +} + +template +inline +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>& +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::operator=(unordered_map&& __u) + _NOEXCEPT_(is_nothrow_move_assignable<__table>::value) +{ + __table_ = _VSTD::move(__u.__table_); + return *this; +} + +template +inline +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>& +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::operator=( + initializer_list __il) +{ + __table_.__assign_unique(__il.begin(), __il.end()); + return *this; +} + +#endif // _LIBCPP_CXX03_LANG + +template +template +inline +void +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::insert(_InputIterator __first, + _InputIterator __last) +{ + for (; __first != __last; ++__first) + __table_.__insert_unique(*__first); +} + +#ifndef _LIBCPP_CXX03_LANG + +template +_Tp& +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::operator[](const key_type& __k) +{ + return __table_.__emplace_unique_key_args(__k, + piecewise_construct, _VSTD::forward_as_tuple(__k), + _VSTD::forward_as_tuple()).first->__get_value().second; +} + +template +_Tp& +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::operator[](key_type&& __k) +{ + return __table_.__emplace_unique_key_args(__k, + piecewise_construct, _VSTD::forward_as_tuple(_VSTD::move(__k)), + _VSTD::forward_as_tuple()).first->__get_value().second; +} +#else // _LIBCPP_CXX03_LANG + +template +typename unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::__node_holder +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::__construct_node_with_key(const key_type& __k) +{ + __node_allocator& __na = __table_.__node_alloc(); + __node_holder __h(__node_traits::allocate(__na, 1), _Dp(__na)); + __node_traits::construct(__na, _VSTD::addressof(__h->__value_.__get_value().first), __k); + __h.get_deleter().__first_constructed = true; + __node_traits::construct(__na, _VSTD::addressof(__h->__value_.__get_value().second)); + __h.get_deleter().__second_constructed = true; + return __h; +} + +template +_Tp& +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::operator[](const key_type& __k) +{ + iterator __i = find(__k); + if (__i != end()) + return __i->second; + __node_holder __h = __construct_node_with_key(__k); + pair __r = __table_.__node_insert_unique(__h.get()); + __h.release(); + return __r.first->second; +} + +#endif // _LIBCPP_CXX03_LANG + +template +_Tp& +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::at(const key_type& __k) +{ + iterator __i = find(__k); + if (__i == end()) + __throw_out_of_range("unordered_map::at: key not found"); + return __i->second; +} + +template +const _Tp& +unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::at(const key_type& __k) const +{ + const_iterator __i = find(__k); + if (__i == end()) + __throw_out_of_range("unordered_map::at: key not found"); + return __i->second; +} + +template +inline _LIBCPP_INLINE_VISIBILITY +void +swap(unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>& __x, + unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>& __y) + _NOEXCEPT_(_NOEXCEPT_(__x.swap(__y))) +{ + __x.swap(__y); +} + +#if _LIBCPP_STD_VER > 17 +template +inline _LIBCPP_INLINE_VISIBILITY + typename unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::size_type + erase_if(unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>& __c, + _Predicate __pred) { + return _VSTD::__libcpp_erase_if_container(__c, __pred); +} +#endif + +template +_LIBCPP_HIDE_FROM_ABI bool +operator==(const unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>& __x, + const unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>& __y) +{ + if (__x.size() != __y.size()) + return false; + typedef typename unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>::const_iterator + const_iterator; + for (const_iterator __i = __x.begin(), __ex = __x.end(), __ey = __y.end(); + __i != __ex; ++__i) + { + const_iterator __j = __y.find(__i->first); + if (__j == __ey || !(*__i == *__j)) + return false; + } + return true; +} + +template +inline _LIBCPP_INLINE_VISIBILITY +bool +operator!=(const unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>& __x, + const unordered_map<_Key, _Tp, _Hash, _Pred, _Alloc>& __y) +{ + return !(__x == __y); +} + +template , class _Pred = equal_to<_Key>, + class _Alloc = allocator > > +class _LIBCPP_TEMPLATE_VIS unordered_multimap +{ +public: + // types + typedef _Key key_type; + typedef _Tp mapped_type; + typedef __type_identity_t<_Hash> hasher; + typedef __type_identity_t<_Pred> key_equal; + typedef __type_identity_t<_Alloc> allocator_type; + typedef pair value_type; + typedef value_type& reference; + typedef const value_type& const_reference; + static_assert((is_same::value), + "Invalid allocator::value_type"); + +private: + typedef __hash_value_type __value_type; + typedef __unordered_map_hasher __hasher; + typedef __unordered_map_equal __key_equal; + typedef __rebind_alloc, __value_type> __allocator_type; + + typedef __hash_table<__value_type, __hasher, + __key_equal, __allocator_type> __table; + + __table __table_; + + typedef typename __table::_NodeTypes _NodeTypes; + typedef typename __table::__node_traits __node_traits; + typedef typename __table::__node_allocator __node_allocator; + typedef typename __table::__node __node; + typedef __hash_map_node_destructor<__node_allocator> _Dp; + typedef unique_ptr<__node, _Dp> __node_holder; + typedef allocator_traits __alloc_traits; + static_assert((is_same::value), + "Allocator uses different size_type for different types"); + + static_assert(is_same >::value, + "[allocator.requirements] states that rebinding an allocator to the same type should result in the " + "original allocator"); + + public: + typedef typename __alloc_traits::pointer pointer; + typedef typename __alloc_traits::const_pointer const_pointer; + typedef typename __table::size_type size_type; + typedef typename __table::difference_type difference_type; + + typedef __hash_map_iterator iterator; + typedef __hash_map_const_iterator const_iterator; + typedef __hash_map_iterator local_iterator; + typedef __hash_map_const_iterator const_local_iterator; + +#if _LIBCPP_STD_VER > 14 + typedef __map_node_handle<__node, allocator_type> node_type; +#endif + + template + friend class _LIBCPP_TEMPLATE_VIS unordered_map; + template + friend class _LIBCPP_TEMPLATE_VIS unordered_multimap; + + _LIBCPP_INLINE_VISIBILITY + unordered_multimap() + _NOEXCEPT_(is_nothrow_default_constructible<__table>::value) + { + _VSTD::__debug_db_insert_c(this); + } + explicit unordered_multimap(size_type __n, const hasher& __hf = hasher(), + const key_equal& __eql = key_equal()); + unordered_multimap(size_type __n, const hasher& __hf, + const key_equal& __eql, + const allocator_type& __a); + template + unordered_multimap(_InputIterator __first, _InputIterator __last); + template + unordered_multimap(_InputIterator __first, _InputIterator __last, + size_type __n, const hasher& __hf = hasher(), + const key_equal& __eql = key_equal()); + template + unordered_multimap(_InputIterator __first, _InputIterator __last, + size_type __n, const hasher& __hf, + const key_equal& __eql, + const allocator_type& __a); + _LIBCPP_INLINE_VISIBILITY + explicit unordered_multimap(const allocator_type& __a); + unordered_multimap(const unordered_multimap& __u); + unordered_multimap(const unordered_multimap& __u, const allocator_type& __a); +#ifndef _LIBCPP_CXX03_LANG + _LIBCPP_INLINE_VISIBILITY + unordered_multimap(unordered_multimap&& __u) + _NOEXCEPT_(is_nothrow_move_constructible<__table>::value); + unordered_multimap(unordered_multimap&& __u, const allocator_type& __a); + unordered_multimap(initializer_list __il); + unordered_multimap(initializer_list __il, size_type __n, + const hasher& __hf = hasher(), + const key_equal& __eql = key_equal()); + unordered_multimap(initializer_list __il, size_type __n, + const hasher& __hf, const key_equal& __eql, + const allocator_type& __a); +#endif // _LIBCPP_CXX03_LANG +#if _LIBCPP_STD_VER > 11 + _LIBCPP_INLINE_VISIBILITY + unordered_multimap(size_type __n, const allocator_type& __a) + : unordered_multimap(__n, hasher(), key_equal(), __a) {} + _LIBCPP_INLINE_VISIBILITY + unordered_multimap(size_type __n, const hasher& __hf, const allocator_type& __a) + : unordered_multimap(__n, __hf, key_equal(), __a) {} + template + _LIBCPP_INLINE_VISIBILITY + unordered_multimap(_InputIterator __first, _InputIterator __last, size_type __n, const allocator_type& __a) + : unordered_multimap(__first, __last, __n, hasher(), key_equal(), __a) {} + template + _LIBCPP_INLINE_VISIBILITY + unordered_multimap(_InputIterator __first, _InputIterator __last, size_type __n, const hasher& __hf, + const allocator_type& __a) + : unordered_multimap(__first, __last, __n, __hf, key_equal(), __a) {} + _LIBCPP_INLINE_VISIBILITY + unordered_multimap(initializer_list __il, size_type __n, const allocator_type& __a) + : unordered_multimap(__il, __n, hasher(), key_equal(), __a) {} + _LIBCPP_INLINE_VISIBILITY + unordered_multimap(initializer_list __il, size_type __n, const hasher& __hf, + const allocator_type& __a) + : unordered_multimap(__il, __n, __hf, key_equal(), __a) {} +#endif + _LIBCPP_INLINE_VISIBILITY + ~unordered_multimap() { + static_assert(sizeof(std::__diagnose_unordered_container_requirements<_Key, _Hash, _Pred>(0)), ""); + } + + _LIBCPP_INLINE_VISIBILITY + unordered_multimap& operator=(const unordered_multimap& __u) + { +#ifndef _LIBCPP_CXX03_LANG + __table_ = __u.__table_; +#else + if (this != _VSTD::addressof(__u)) { + __table_.clear(); + __table_.hash_function() = __u.__table_.hash_function(); + __table_.key_eq() = __u.__table_.key_eq(); + __table_.max_load_factor() = __u.__table_.max_load_factor(); + __table_.__copy_assign_alloc(__u.__table_); + insert(__u.begin(), __u.end()); + } +#endif + return *this; + } +#ifndef _LIBCPP_CXX03_LANG + _LIBCPP_INLINE_VISIBILITY + unordered_multimap& operator=(unordered_multimap&& __u) + _NOEXCEPT_(is_nothrow_move_assignable<__table>::value); + _LIBCPP_INLINE_VISIBILITY + unordered_multimap& operator=(initializer_list __il); +#endif // _LIBCPP_CXX03_LANG + + _LIBCPP_INLINE_VISIBILITY + allocator_type get_allocator() const _NOEXCEPT + {return allocator_type(__table_.__node_alloc());} + + _LIBCPP_NODISCARD_AFTER_CXX17 _LIBCPP_INLINE_VISIBILITY + bool empty() const _NOEXCEPT {return __table_.size() == 0;} + _LIBCPP_INLINE_VISIBILITY + size_type size() const _NOEXCEPT {return __table_.size();} + _LIBCPP_INLINE_VISIBILITY + size_type max_size() const _NOEXCEPT {return __table_.max_size();} + + _LIBCPP_INLINE_VISIBILITY + iterator begin() _NOEXCEPT {return __table_.begin();} + _LIBCPP_INLINE_VISIBILITY + iterator end() _NOEXCEPT {return __table_.end();} + _LIBCPP_INLINE_VISIBILITY + const_iterator begin() const _NOEXCEPT {return __table_.begin();} + _LIBCPP_INLINE_VISIBILITY + const_iterator end() const _NOEXCEPT {return __table_.end();} + _LIBCPP_INLINE_VISIBILITY + const_iterator cbegin() const _NOEXCEPT {return __table_.begin();} + _LIBCPP_INLINE_VISIBILITY + const_iterator cend() const _NOEXCEPT {return __table_.end();} + + _LIBCPP_INLINE_VISIBILITY + iterator insert(const value_type& __x) {return __table_.__insert_multi(__x);} + + _LIBCPP_INLINE_VISIBILITY + iterator insert(const_iterator __p, const value_type& __x) + {return __table_.__insert_multi(__p.__i_, __x);} + + template + _LIBCPP_INLINE_VISIBILITY + void insert(_InputIterator __first, _InputIterator __last); + +#ifndef _LIBCPP_CXX03_LANG + _LIBCPP_INLINE_VISIBILITY + void insert(initializer_list __il) + {insert(__il.begin(), __il.end());} + _LIBCPP_INLINE_VISIBILITY + iterator insert(value_type&& __x) {return __table_.__insert_multi(_VSTD::move(__x));} + + _LIBCPP_INLINE_VISIBILITY + iterator insert(const_iterator __p, value_type&& __x) + {return __table_.__insert_multi(__p.__i_, _VSTD::move(__x));} + + template ::value> > + _LIBCPP_INLINE_VISIBILITY + iterator insert(_Pp&& __x) + {return __table_.__insert_multi(_VSTD::forward<_Pp>(__x));} + + template ::value> > + _LIBCPP_INLINE_VISIBILITY + iterator insert(const_iterator __p, _Pp&& __x) + {return __table_.__insert_multi(__p.__i_, _VSTD::forward<_Pp>(__x));} + + template + iterator emplace(_Args&&... __args) { + return __table_.__emplace_multi(_VSTD::forward<_Args>(__args)...); + } + + template + iterator emplace_hint(const_iterator __p, _Args&&... __args) { + return __table_.__emplace_hint_multi(__p.__i_, _VSTD::forward<_Args>(__args)...); + } +#endif // _LIBCPP_CXX03_LANG + + + _LIBCPP_INLINE_VISIBILITY + iterator erase(const_iterator __p) {return __table_.erase(__p.__i_);} + _LIBCPP_INLINE_VISIBILITY + iterator erase(iterator __p) {return __table_.erase(__p.__i_);} + _LIBCPP_INLINE_VISIBILITY + size_type erase(const key_type& __k) {return __table_.__erase_multi(__k);} + _LIBCPP_INLINE_VISIBILITY + iterator erase(const_iterator __first, const_iterator __last) + {return __table_.erase(__first.__i_, __last.__i_);} + _LIBCPP_INLINE_VISIBILITY + void clear() _NOEXCEPT {__table_.clear();} + +#if _LIBCPP_STD_VER > 14 + _LIBCPP_INLINE_VISIBILITY + iterator insert(node_type&& __nh) + { + _LIBCPP_ASSERT(__nh.empty() || __nh.get_allocator() == get_allocator(), + "node_type with incompatible allocator passed to unordered_multimap::insert()"); + return __table_.template __node_handle_insert_multi( + _VSTD::move(__nh)); + } + _LIBCPP_INLINE_VISIBILITY + iterator insert(const_iterator __hint, node_type&& __nh) + { + _LIBCPP_ASSERT(__nh.empty() || __nh.get_allocator() == get_allocator(), + "node_type with incompatible allocator passed to unordered_multimap::insert()"); + return __table_.template __node_handle_insert_multi( + __hint.__i_, _VSTD::move(__nh)); + } + _LIBCPP_INLINE_VISIBILITY + node_type extract(key_type const& __key) + { + return __table_.template __node_handle_extract(__key); + } + _LIBCPP_INLINE_VISIBILITY + node_type extract(const_iterator __it) + { + return __table_.template __node_handle_extract( + __it.__i_); + } + + template + _LIBCPP_INLINE_VISIBILITY + void merge(unordered_multimap& __source) + { + _LIBCPP_ASSERT(__source.get_allocator() == get_allocator(), + "merging container with incompatible allocator"); + return __table_.__node_handle_merge_multi(__source.__table_); + } + template + _LIBCPP_INLINE_VISIBILITY + void merge(unordered_multimap&& __source) + { + _LIBCPP_ASSERT(__source.get_allocator() == get_allocator(), + "merging container with incompatible allocator"); + return __table_.__node_handle_merge_multi(__source.__table_); + } + template + _LIBCPP_INLINE_VISIBILITY + void merge(unordered_map& __source) + { + _LIBCPP_ASSERT(__source.get_allocator() == get_allocator(), + "merging container with incompatible allocator"); + return __table_.__node_handle_merge_multi(__source.__table_); + } + template + _LIBCPP_INLINE_VISIBILITY + void merge(unordered_map&& __source) + { + _LIBCPP_ASSERT(__source.get_allocator() == get_allocator(), + "merging container with incompatible allocator"); + return __table_.__node_handle_merge_multi(__source.__table_); + } +#endif + + _LIBCPP_INLINE_VISIBILITY + void swap(unordered_multimap& __u) + _NOEXCEPT_(__is_nothrow_swappable<__table>::value) + {__table_.swap(__u.__table_);} + + _LIBCPP_INLINE_VISIBILITY + hasher hash_function() const + {return __table_.hash_function().hash_function();} + _LIBCPP_INLINE_VISIBILITY + key_equal key_eq() const + {return __table_.key_eq().key_eq();} + + _LIBCPP_INLINE_VISIBILITY + iterator find(const key_type& __k) {return __table_.find(__k);} + _LIBCPP_INLINE_VISIBILITY + const_iterator find(const key_type& __k) const {return __table_.find(__k);} +#if _LIBCPP_STD_VER > 17 + template ::value && __is_transparent::value>* = nullptr> + _LIBCPP_INLINE_VISIBILITY + iterator find(const _K2& __k) {return __table_.find(__k);} + template ::value && __is_transparent::value>* = nullptr> + _LIBCPP_INLINE_VISIBILITY + const_iterator find(const _K2& __k) const {return __table_.find(__k);} +#endif // _LIBCPP_STD_VER > 17 + + _LIBCPP_INLINE_VISIBILITY + size_type count(const key_type& __k) const {return __table_.__count_multi(__k);} +#if _LIBCPP_STD_VER > 17 + template ::value && __is_transparent::value>* = nullptr> + _LIBCPP_INLINE_VISIBILITY + size_type count(const _K2& __k) const {return __table_.__count_multi(__k);} +#endif // _LIBCPP_STD_VER > 17 + +#if _LIBCPP_STD_VER > 17 + _LIBCPP_INLINE_VISIBILITY + bool contains(const key_type& __k) const {return find(__k) != end();} + + template ::value && __is_transparent::value>* = nullptr> + _LIBCPP_INLINE_VISIBILITY + bool contains(const _K2& __k) const {return find(__k) != end();} +#endif // _LIBCPP_STD_VER > 17 + + _LIBCPP_INLINE_VISIBILITY + pair equal_range(const key_type& __k) + {return __table_.__equal_range_multi(__k);} + _LIBCPP_INLINE_VISIBILITY + pair equal_range(const key_type& __k) const + {return __table_.__equal_range_multi(__k);} +#if _LIBCPP_STD_VER > 17 + template ::value && __is_transparent::value>* = nullptr> + _LIBCPP_INLINE_VISIBILITY + pair equal_range(const _K2& __k) + {return __table_.__equal_range_multi(__k);} + template ::value && __is_transparent::value>* = nullptr> + _LIBCPP_INLINE_VISIBILITY + pair equal_range(const _K2& __k) const + {return __table_.__equal_range_multi(__k);} +#endif // _LIBCPP_STD_VER > 17 + + _LIBCPP_INLINE_VISIBILITY + size_type bucket_count() const _NOEXCEPT {return __table_.bucket_count();} + _LIBCPP_INLINE_VISIBILITY + size_type max_bucket_count() const _NOEXCEPT + {return __table_.max_bucket_count();} + + _LIBCPP_INLINE_VISIBILITY + size_type bucket_size(size_type __n) const + {return __table_.bucket_size(__n);} + _LIBCPP_INLINE_VISIBILITY + size_type bucket(const key_type& __k) const {return __table_.bucket(__k);} + + _LIBCPP_INLINE_VISIBILITY + local_iterator begin(size_type __n) {return __table_.begin(__n);} + _LIBCPP_INLINE_VISIBILITY + local_iterator end(size_type __n) {return __table_.end(__n);} + _LIBCPP_INLINE_VISIBILITY + const_local_iterator begin(size_type __n) const {return __table_.cbegin(__n);} + _LIBCPP_INLINE_VISIBILITY + const_local_iterator end(size_type __n) const {return __table_.cend(__n);} + _LIBCPP_INLINE_VISIBILITY + const_local_iterator cbegin(size_type __n) const {return __table_.cbegin(__n);} + _LIBCPP_INLINE_VISIBILITY + const_local_iterator cend(size_type __n) const {return __table_.cend(__n);} + + _LIBCPP_INLINE_VISIBILITY + float load_factor() const _NOEXCEPT {return __table_.load_factor();} + _LIBCPP_INLINE_VISIBILITY + float max_load_factor() const _NOEXCEPT {return __table_.max_load_factor();} + _LIBCPP_INLINE_VISIBILITY + void max_load_factor(float __mlf) {__table_.max_load_factor(__mlf);} + _LIBCPP_INLINE_VISIBILITY + void rehash(size_type __n) {__table_.__rehash_multi(__n);} + _LIBCPP_INLINE_VISIBILITY + void reserve(size_type __n) {__table_.__reserve_multi(__n);} + +#ifdef _LIBCPP_ENABLE_DEBUG_MODE + + bool __dereferenceable(const const_iterator* __i) const + {return __table_.__dereferenceable(_VSTD::addressof(__i->__i_));} + bool __decrementable(const const_iterator* __i) const + {return __table_.__decrementable(_VSTD::addressof(__i->__i_));} + bool __addable(const const_iterator* __i, ptrdiff_t __n) const + {return __table_.__addable(_VSTD::addressof(__i->__i_), __n);} + bool __subscriptable(const const_iterator* __i, ptrdiff_t __n) const + {return __table_.__addable(_VSTD::addressof(__i->__i_), __n);} + +#endif // _LIBCPP_ENABLE_DEBUG_MODE + + +}; + +#if _LIBCPP_STD_VER >= 17 +template>, + class _Pred = equal_to<__iter_key_type<_InputIterator>>, + class _Allocator = allocator<__iter_to_alloc_type<_InputIterator>>, + class = enable_if_t<__is_cpp17_input_iterator<_InputIterator>::value>, + class = enable_if_t::value>, + class = enable_if_t::value>, + class = enable_if_t::value>, + class = enable_if_t<__is_allocator<_Allocator>::value>> +unordered_multimap(_InputIterator, _InputIterator, typename allocator_traits<_Allocator>::size_type = 0, + _Hash = _Hash(), _Pred = _Pred(), _Allocator = _Allocator()) + -> unordered_multimap<__iter_key_type<_InputIterator>, __iter_mapped_type<_InputIterator>, _Hash, _Pred, _Allocator>; + +template>, + class _Pred = equal_to>, + class _Allocator = allocator>, + class = enable_if_t::value>, + class = enable_if_t::value>, + class = enable_if_t::value>, + class = enable_if_t<__is_allocator<_Allocator>::value>> +unordered_multimap(initializer_list>, typename allocator_traits<_Allocator>::size_type = 0, + _Hash = _Hash(), _Pred = _Pred(), _Allocator = _Allocator()) + -> unordered_multimap, _Tp, _Hash, _Pred, _Allocator>; + +template::value>, + class = enable_if_t<__is_allocator<_Allocator>::value>> +unordered_multimap(_InputIterator, _InputIterator, typename allocator_traits<_Allocator>::size_type, _Allocator) + -> unordered_multimap<__iter_key_type<_InputIterator>, __iter_mapped_type<_InputIterator>, + hash<__iter_key_type<_InputIterator>>, equal_to<__iter_key_type<_InputIterator>>, _Allocator>; + +template::value>, + class = enable_if_t<__is_allocator<_Allocator>::value>> +unordered_multimap(_InputIterator, _InputIterator, _Allocator) + -> unordered_multimap<__iter_key_type<_InputIterator>, __iter_mapped_type<_InputIterator>, + hash<__iter_key_type<_InputIterator>>, equal_to<__iter_key_type<_InputIterator>>, _Allocator>; + +template::value>, + class = enable_if_t::value>, + class = enable_if_t::value>, + class = enable_if_t<__is_allocator<_Allocator>::value>> +unordered_multimap(_InputIterator, _InputIterator, typename allocator_traits<_Allocator>::size_type, _Hash, _Allocator) + -> unordered_multimap<__iter_key_type<_InputIterator>, __iter_mapped_type<_InputIterator>, + _Hash, equal_to<__iter_key_type<_InputIterator>>, _Allocator>; + +template::value>> +unordered_multimap(initializer_list>, typename allocator_traits<_Allocator>::size_type, _Allocator) + -> unordered_multimap, _Tp, + hash>, + equal_to>, _Allocator>; + +template::value>> +unordered_multimap(initializer_list>, _Allocator) + -> unordered_multimap, _Tp, + hash>, + equal_to>, _Allocator>; + +template::value>, + class = enable_if_t::value>, + class = enable_if_t<__is_allocator<_Allocator>::value>> +unordered_multimap(initializer_list>, typename allocator_traits<_Allocator>::size_type, _Hash, _Allocator) + -> unordered_multimap, _Tp, _Hash, + equal_to>, _Allocator>; +#endif + +template +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap( + size_type __n, const hasher& __hf, const key_equal& __eql) + : __table_(__hf, __eql) +{ + _VSTD::__debug_db_insert_c(this); + __table_.__rehash_multi(__n); +} + +template +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap( + size_type __n, const hasher& __hf, const key_equal& __eql, + const allocator_type& __a) + : __table_(__hf, __eql, typename __table::allocator_type(__a)) +{ + _VSTD::__debug_db_insert_c(this); + __table_.__rehash_multi(__n); +} + +template +template +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap( + _InputIterator __first, _InputIterator __last) +{ + _VSTD::__debug_db_insert_c(this); + insert(__first, __last); +} + +template +template +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap( + _InputIterator __first, _InputIterator __last, size_type __n, + const hasher& __hf, const key_equal& __eql) + : __table_(__hf, __eql) +{ + _VSTD::__debug_db_insert_c(this); + __table_.__rehash_multi(__n); + insert(__first, __last); +} + +template +template +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap( + _InputIterator __first, _InputIterator __last, size_type __n, + const hasher& __hf, const key_equal& __eql, const allocator_type& __a) + : __table_(__hf, __eql, typename __table::allocator_type(__a)) +{ + _VSTD::__debug_db_insert_c(this); + __table_.__rehash_multi(__n); + insert(__first, __last); +} + +template +inline +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap( + const allocator_type& __a) + : __table_(typename __table::allocator_type(__a)) +{ + _VSTD::__debug_db_insert_c(this); +} + +template +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap( + const unordered_multimap& __u) + : __table_(__u.__table_) +{ + _VSTD::__debug_db_insert_c(this); + __table_.__rehash_multi(__u.bucket_count()); + insert(__u.begin(), __u.end()); +} + +template +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap( + const unordered_multimap& __u, const allocator_type& __a) + : __table_(__u.__table_, typename __table::allocator_type(__a)) +{ + _VSTD::__debug_db_insert_c(this); + __table_.__rehash_multi(__u.bucket_count()); + insert(__u.begin(), __u.end()); +} + +#ifndef _LIBCPP_CXX03_LANG + +template +inline +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap( + unordered_multimap&& __u) + _NOEXCEPT_(is_nothrow_move_constructible<__table>::value) + : __table_(_VSTD::move(__u.__table_)) +{ + _VSTD::__debug_db_insert_c(this); + std::__debug_db_swap(this, std::addressof(__u)); +} + +template +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap( + unordered_multimap&& __u, const allocator_type& __a) + : __table_(_VSTD::move(__u.__table_), typename __table::allocator_type(__a)) +{ + _VSTD::__debug_db_insert_c(this); + if (__a != __u.get_allocator()) + { + iterator __i = __u.begin(); + while (__u.size() != 0) + { + __table_.__insert_multi( + __u.__table_.remove((__i++).__i_)->__value_.__move()); + } + } + else + std::__debug_db_swap(this, std::addressof(__u)); +} + +template +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap( + initializer_list __il) +{ + _VSTD::__debug_db_insert_c(this); + insert(__il.begin(), __il.end()); +} + +template +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap( + initializer_list __il, size_type __n, const hasher& __hf, + const key_equal& __eql) + : __table_(__hf, __eql) +{ + _VSTD::__debug_db_insert_c(this); + __table_.__rehash_multi(__n); + insert(__il.begin(), __il.end()); +} + +template +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::unordered_multimap( + initializer_list __il, size_type __n, const hasher& __hf, + const key_equal& __eql, const allocator_type& __a) + : __table_(__hf, __eql, typename __table::allocator_type(__a)) +{ + _VSTD::__debug_db_insert_c(this); + __table_.__rehash_multi(__n); + insert(__il.begin(), __il.end()); +} + +template +inline +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>& +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::operator=(unordered_multimap&& __u) + _NOEXCEPT_(is_nothrow_move_assignable<__table>::value) +{ + __table_ = _VSTD::move(__u.__table_); + return *this; +} + +template +inline +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>& +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::operator=( + initializer_list __il) +{ + __table_.__assign_multi(__il.begin(), __il.end()); + return *this; +} + +#endif // _LIBCPP_CXX03_LANG + + + +template +template +inline +void +unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::insert(_InputIterator __first, + _InputIterator __last) +{ + for (; __first != __last; ++__first) + __table_.__insert_multi(*__first); +} + +template +inline _LIBCPP_INLINE_VISIBILITY +void +swap(unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>& __x, + unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>& __y) + _NOEXCEPT_(_NOEXCEPT_(__x.swap(__y))) +{ + __x.swap(__y); +} + +#if _LIBCPP_STD_VER > 17 +template +inline _LIBCPP_INLINE_VISIBILITY + typename unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::size_type + erase_if(unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>& __c, + _Predicate __pred) { + return _VSTD::__libcpp_erase_if_container(__c, __pred); +} +#endif + +template +_LIBCPP_HIDE_FROM_ABI bool +operator==(const unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>& __x, + const unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>& __y) +{ + if (__x.size() != __y.size()) + return false; + typedef typename unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>::const_iterator + const_iterator; + typedef pair _EqRng; + for (const_iterator __i = __x.begin(), __ex = __x.end(); __i != __ex;) + { + _EqRng __xeq = __x.equal_range(__i->first); + _EqRng __yeq = __y.equal_range(__i->first); + if (_VSTD::distance(__xeq.first, __xeq.second) != + _VSTD::distance(__yeq.first, __yeq.second) || + !_VSTD::is_permutation(__xeq.first, __xeq.second, __yeq.first)) + return false; + __i = __xeq.second; + } + return true; +} + +template +inline _LIBCPP_INLINE_VISIBILITY +bool +operator!=(const unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>& __x, + const unordered_multimap<_Key, _Tp, _Hash, _Pred, _Alloc>& __y) +{ + return !(__x == __y); +} + +_LIBCPP_END_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 14 +_LIBCPP_BEGIN_NAMESPACE_STD +namespace pmr { +template , class _PredT = std::equal_to<_KeyT>> +using unordered_map = + std::unordered_map<_KeyT, _ValueT, _HashT, _PredT, polymorphic_allocator>>; + +template , class _PredT = std::equal_to<_KeyT>> +using unordered_multimap = + std::unordered_multimap<_KeyT, _ValueT, _HashT, _PredT, polymorphic_allocator>>; +} // namespace pmr +_LIBCPP_END_NAMESPACE_STD +#endif + +#if !defined(_LIBCPP_REMOVE_TRANSITIVE_INCLUDES) && _LIBCPP_STD_VER <= 20 +# include +# include +# include +# include +#endif + +#endif // _LIBCPP_UNORDERED_MAP \ No newline at end of file diff --git a/ref_java b/ref_java new file mode 100644 index 0000000..b8362cd --- /dev/null +++ b/ref_java @@ -0,0 +1,9761 @@ +package rocks.palaiologos.cask; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Objects; + +public class CaskBootstrap { + public static void main(String[] args) { + InputStream manifest = CaskBootstrap.class.getClassLoader().getResourceAsStream("MANIFEST"); + if(manifest == null) { + throw new RuntimeException("The MANIFEST file for Cask is missing."); + } + String data; + try { + data = new String(manifest.readAllBytes()); + manifest.close(); + } catch (IOException e) { + throw new RuntimeException("The MANIFEST file for Cask is not readable."); + } + // Primarily just future-proofing. + HashMap manifestMap = new HashMap<>(); + String[] properties = data.replace("\r", "").split("\n"); + for (String property : properties) { + int index = property.indexOf(":"); + if (index == -1) { + throw new RuntimeException("Invalid property in MANIFEST: " + property); + } + manifestMap.put(property.substring(0, index).trim(), property.substring(index + 1).trim()); + } + + String mainClass = manifestMap.get("Main-Class"); + String caskFile = manifestMap.get("Cask-File"); + if (mainClass == null || caskFile == null) { + throw new RuntimeException("The MANIFEST file for Cask is missing required properties."); + } + + try { + CaskClassLoader caskClassLoader = new CaskClassLoader(Objects.requireNonNull(CaskBootstrap.class.getClassLoader().getResourceAsStream(caskFile))); + Class mainClassObject = caskClassLoader.loadClass(mainClass); + Thread.currentThread().setContextClassLoader(caskClassLoader); + System.setProperty("java.system.class.loader", CaskClassLoader.class.getName()); + mainClassObject.getMethod("main", String[].class).invoke(null, (Object) args); + } catch (Exception e) { + throw new RuntimeException("Could not start Cask application.", e); + } + } +} + +package palaiologos.kamilalisp.runtime.array; + +import com.google.common.collect.Lists; +import palaiologos.kamilalisp.atom.Atom; +import palaiologos.kamilalisp.atom.Environment; +import palaiologos.kamilalisp.atom.Lambda; +import palaiologos.kamilalisp.atom.PrimitiveFunction; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +public class Encode extends PrimitiveFunction implements Lambda { + @Override + public Atom apply(Environment env, List args) { + assertArity(args, 2); + BigInteger base = args.get(0).getInteger(); + BigInteger n = args.get(1).getInteger(); + List encoding = new ArrayList<>(); + while (n.compareTo(BigInteger.ZERO) > 0) { + encoding.add(new Atom(n.mod(base))); + n = n.divide(base); + } + if (encoding.isEmpty()) + encoding.add(new Atom(BigInteger.ZERO)); + return new Atom(Lists.reverse(encoding)); + } + + @Override + protected String name() { + return "encode"; + } +} + +package palaiologos.kamilalisp.runtime.net; + +import palaiologos.kamilalisp.atom.Atom; +import palaiologos.kamilalisp.atom.Environment; +import palaiologos.kamilalisp.atom.Lambda; +import palaiologos.kamilalisp.atom.PrimitiveFunction; +import palaiologos.kamilalisp.runtime.dataformat.BufferAtomList; + +import java.io.InputStream; +import java.net.URL; +import java.util.List; + +public class Wget extends PrimitiveFunction implements Lambda { + @Override + public Atom apply(Environment env, List args) { + assertArity(args, 1); + try { + URL url = new URL(args.get(0).getString()); + InputStream data = url.openStream(); + byte[] bytes = data.readAllBytes(); + data.close(); + return new Atom(BufferAtomList.from(bytes)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + protected String name() { + return "net:wget"; + } +} + +package mindustry.net; + +import arc.*; +import arc.func.*; +import arc.math.*; +import arc.net.*; +import arc.net.FrameworkMessage.*; +import arc.net.dns.*; +import arc.struct.*; +import arc.util.*; +import arc.util.Log.*; +import arc.util.io.*; +import mindustry.*; +import mindustry.game.EventType.*; +import mindustry.net.Administration.*; +import mindustry.net.Net.*; +import mindustry.net.Packets.*; +import net.jpountz.lz4.*; + +import java.io.*; +import java.net.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.concurrent.*; + +import static mindustry.Vars.*; + +public class ArcNetProvider implements NetProvider{ + final Client client; + final Prov packetSupplier = () -> new DatagramPacket(new byte[512], 512); + + final Server server; + final CopyOnWriteArrayList connections = new CopyOnWriteArrayList<>(); + Thread serverThread; + + private static final LZ4FastDecompressor decompressor = LZ4Factory.fastestInstance().fastDecompressor(); + private static final LZ4Compressor compressor = LZ4Factory.fastestInstance().fastCompressor(); + + private volatile int playerLimitCache, packetSpamLimit; + + public ArcNetProvider(){ + ArcNet.errorHandler = e -> { + if(Log.level == LogLevel.debug){ + Log.debug(Strings.getStackTrace(e)); + } + }; + + //fetch this in the main thread to prevent threading issues + Events.run(Trigger.update, () -> { + playerLimitCache = netServer.admins.getPlayerLimit(); + packetSpamLimit = Config.packetSpamLimit.num(); + }); + + client = new Client(8192, 16384, new PacketSerializer()); + client.setDiscoveryPacket(packetSupplier); + client.addListener(new NetListener(){ + @Override + public void connected(Connection connection){ + Connect c = new Connect(); + c.addressTCP = connection.getRemoteAddressTCP().getAddress().getHostAddress(); + if(connection.getRemoteAddressTCP() != null) c.addressTCP = connection.getRemoteAddressTCP().toString(); + + Core.app.post(() -> net.handleClientReceived(c)); + } + + @Override + public void disconnected(Connection connection, DcReason reason){ + if(connection.getLastProtocolError() != null){ + netClient.setQuiet(); + } + + Disconnect c = new Disconnect(); + c.reason = reason.toString(); + Core.app.post(() -> net.handleClientReceived(c)); + } + + @Override + public void received(Connection connection, Object object){ + if(!(object instanceof Packet p)) return; + + Core.app.post(() -> { + try{ + net.handleClientReceived(p); + }catch(Throwable e){ + net.handleException(e); + } + }); + + } + }); + + server = new Server(32768, 16384, new PacketSerializer()); + server.setMulticast(multicastGroup, multicastPort); + server.setDiscoveryHandler((address, handler) -> { + ByteBuffer buffer = NetworkIO.writeServerData(); + buffer.position(0); + handler.respond(buffer); + }); + + server.addListener(new NetListener(){ + + @Override + public void connected(Connection connection){ + String ip = connection.getRemoteAddressTCP().getAddress().getHostAddress(); + + //kill connections above the limit to prevent spam + if((playerLimitCache > 0 && server.getConnections().length > playerLimitCache) || netServer.admins.isDosBlacklisted(ip)){ + connection.close(DcReason.closed); + return; + } + + ArcConnection kn = new ArcConnection(ip, connection); + + Connect c = new Connect(); + c.addressTCP = ip; + + Log.debug("&bReceived connection: @", c.addressTCP); + + connection.setArbitraryData(kn); + connections.add(kn); + Core.app.post(() -> net.handleServerReceived(kn, c)); + } + + @Override + public void disconnected(Connection connection, DcReason reason){ + if(!(connection.getArbitraryData() instanceof ArcConnection k)) return; + + Disconnect c = new Disconnect(); + c.reason = reason.toString(); + + Core.app.post(() -> { + net.handleServerReceived(k, c); + connections.remove(k); + }); + } + + @Override + public void received(Connection connection, Object object){ + if(!(connection.getArbitraryData() instanceof ArcConnection k) || !(object instanceof Packet pack)) return; + + if(packetSpamLimit > 0 && !k.packetRate.allow(3000, packetSpamLimit)){ + Log.warn("Blacklisting IP '@' as potential DOS attack - packet spam.", k.address); + connection.close(DcReason.closed); + netServer.admins.blacklistDos(k.address); + return; + } + + Core.app.post(() -> { + try{ + net.handleServerReceived(k, pack); + }catch(Throwable e){ + Log.err(e); + } + }); + } + }); + } + + @Override + public void setConnectFilter(Server.ServerConnectFilter connectFilter){ + server.setConnectFilter(connectFilter); + } + + private static boolean isLocal(InetAddress addr){ + if(addr.isAnyLocalAddress() || addr.isLoopbackAddress()) return true; + + try{ + return NetworkInterface.getByInetAddress(addr) != null; + }catch(Exception e){ + return false; + } + } + + @Override + public void connectClient(String ip, int port, Runnable success){ + Threads.daemon(() -> { + try{ + //just in case + client.stop(); + + Threads.daemon("Net Client", () -> { + try{ + client.run(); + }catch(Exception e){ + if(!(e instanceof ClosedSelectorException)) net.handleException(e); + } + }); + + client.connect(5000, ip, port, port); + success.run(); + }catch(Exception e){ + if(netClient.isConnecting()){ + net.handleException(e); + } + } + }); + } + + @Override + public void disconnectClient(){ + client.close(); + } + + @Override + public void sendClient(Object object, boolean reliable){ + try{ + if(reliable){ + client.sendTCP(object); + }else{ + client.sendUDP(object); + } + //sending things can cause an under/overflow, catch it and disconnect instead of crashing + }catch(BufferOverflowException | BufferUnderflowException e){ + net.showError(e); + } + } + + @Override + public void pingHost(String address, int port, Cons valid, Cons invalid){ + try{ + var host = pingHostImpl(address, port); + Core.app.post(() -> valid.get(host)); + }catch(IOException e){ + if(port == Vars.port){ + for(var record : ArcDns.getSrvRecords("_mindustry._tcp." + address)){ + try{ + var host = pingHostImpl(record.target, record.port); + Core.app.post(() -> valid.get(host)); + return; + }catch(IOException ignored){ + } + } + } + Core.app.post(() -> invalid.get(e)); + } + } + + private Host pingHostImpl(String address, int port) throws IOException{ + try(DatagramSocket socket = new DatagramSocket()){ + long time = Time.millis(); + + socket.send(new DatagramPacket(new byte[]{-2, 1}, 2, InetAddress.getByName(address), port)); + socket.setSoTimeout(2000); + + DatagramPacket packet = packetSupplier.get(); + socket.receive(packet); + + ByteBuffer buffer = ByteBuffer.wrap(packet.getData()); + Host host = NetworkIO.readServerData((int)Time.timeSinceMillis(time), packet.getAddress().getHostAddress(), buffer); + host.port = port; + return host; + } + } + + @Override + public void discoverServers(Cons callback, Runnable done){ + Seq foundAddresses = new Seq<>(); + long time = Time.millis(); + + client.discoverHosts(port, multicastGroup, multicastPort, 3000, packet -> { + synchronized(foundAddresses){ + try{ + if(foundAddresses.contains(address -> address.equals(packet.getAddress()) || (isLocal(address) && isLocal(packet.getAddress())))){ + return; + } + ByteBuffer buffer = ByteBuffer.wrap(packet.getData()); + Host host = NetworkIO.readServerData((int)Time.timeSinceMillis(time), packet.getAddress().getHostAddress(), buffer); + Core.app.post(() -> callback.get(host)); + foundAddresses.add(packet.getAddress()); + }catch(Exception e){ + //don't crash when there's an error pinging a server or parsing data + e.printStackTrace(); + } + } + }, () -> Core.app.post(done)); + } + + @Override + public void dispose(){ + disconnectClient(); + closeServer(); + try{ + client.dispose(); + }catch(IOException ignored){ + } + } + + @Override + public Iterable getConnections(){ + return connections; + } + + @Override + public void hostServer(int port) throws IOException{ + connections.clear(); + server.bind(port, port); + + serverThread = new Thread(() -> { + try{ + server.run(); + }catch(Throwable e){ + if(!(e instanceof ClosedSelectorException)) Threads.throwAppException(e); + } + }, "Net Server"); + serverThread.setDaemon(true); + serverThread.start(); + } + + @Override + public void closeServer(){ + connections.clear(); + mainExecutor.submit(server::stop); + } + + class ArcConnection extends NetConnection{ + public final Connection connection; + + public ArcConnection(String address, Connection connection){ + super(address); + this.connection = connection; + } + + @Override + public boolean isConnected(){ + return connection.isConnected(); + } + + @Override + public void sendStream(Streamable stream){ + connection.addListener(new InputStreamSender(stream.stream, 512){ + int id; + + @Override + protected void start(){ + //send an object so the receiving side knows how to handle the following chunks + StreamBegin begin = new StreamBegin(); + begin.total = stream.stream.available(); + begin.type = Net.getPacketId(stream); + connection.sendTCP(begin); + id = begin.id; + } + + @Override + protected Object next(byte[] bytes){ + StreamChunk chunk = new StreamChunk(); + chunk.id = id; + chunk.data = bytes; + return chunk; //wrap the byte[] with an object so the receiving side knows how to handle it. + } + }); + } + + @Override + public void send(Object object, boolean reliable){ + try{ + if(reliable){ + connection.sendTCP(object); + }else{ + connection.sendUDP(object); + } + }catch(Exception e){ + Log.err(e); + Log.info("Error sending packet. Disconnecting invalid client!"); + connection.close(DcReason.error); + + if(connection.getArbitraryData() instanceof ArcConnection k){ + connections.remove(k); + } + } + } + + @Override + public void close(){ + if(connection.isConnected()) connection.close(DcReason.closed); + } + } + + public static class PacketSerializer implements NetSerializer{ + //for debugging total read/write speeds + private static final boolean debug = false; + + ThreadLocal decompressBuffer = Threads.local(() -> ByteBuffer.allocate(32768)); + ThreadLocal reads = Threads.local(() -> new Reads(new ByteBufferInput(decompressBuffer.get()))); + ThreadLocal writes = Threads.local(() -> new Writes(new ByteBufferOutput(decompressBuffer.get()))); + + //for debugging network write counts + static WindowedMean upload = new WindowedMean(5), download = new WindowedMean(5); + static long lastUpload, lastDownload, uploadAccum, downloadAccum; + static int lastPos; + + @Override + public Object read(ByteBuffer byteBuffer){ + if(debug){ + if(Time.timeSinceMillis(lastDownload) >= 1000){ + lastDownload = Time.millis(); + download.add(downloadAccum); + downloadAccum = 0; + Log.info("Download: @ b/s", download.mean()); + } + downloadAccum += byteBuffer.remaining(); + } + + byte id = byteBuffer.get(); + if(id == -2){ + return readFramework(byteBuffer); + }else{ + //read length int, followed by compressed lz4 data + Packet packet = Net.newPacket(id); + var buffer = decompressBuffer.get(); + int length = byteBuffer.getShort() & 0xffff; + byte compression = byteBuffer.get(); + + //no compression, copy over buffer + if(compression == 0){ + buffer.position(0).limit(length); + buffer.put(byteBuffer.array(), byteBuffer.position(), length); + buffer.position(0); + packet.read(reads.get(), length); + //move read packets forward + byteBuffer.position(byteBuffer.position() + buffer.position()); + }else{ + //decompress otherwise + int read = decompressor.decompress(byteBuffer, byteBuffer.position(), buffer, 0, length); + + buffer.position(0); + buffer.limit(length); + packet.read(reads.get(), length); + //move buffer forward based on bytes read by decompressor + byteBuffer.position(byteBuffer.position() + read); + } + + return packet; + } + } + + @Override + public void write(ByteBuffer byteBuffer, Object o){ + if(debug){ + lastPos = byteBuffer.position(); + } + + //write raw buffer + if(o instanceof ByteBuffer raw){ + byteBuffer.put(raw); + }else if(o instanceof FrameworkMessage msg){ + byteBuffer.put((byte)-2); //code for framework message + writeFramework(byteBuffer, msg); + }else{ + if(!(o instanceof Packet pack)) throw new RuntimeException("All sent objects must implement be Packets! Class: " + o.getClass()); + byte id = Net.getPacketId(pack); + byteBuffer.put(id); + + var temp = decompressBuffer.get(); + temp.position(0); + temp.limit(temp.capacity()); + pack.write(writes.get()); + + short length = (short)temp.position(); + + //write length, uncompressed + byteBuffer.putShort(length); + + //don't bother with small packets + if(length < 36 || pack instanceof StreamChunk){ + //write direct contents... + byteBuffer.put((byte)0); //0 = no compression + byteBuffer.put(temp.array(), 0, length); + }else{ + byteBuffer.put((byte)1); //1 = compression + //write compressed data; this does not modify position! + int written = compressor.compress(temp, 0, temp.position(), byteBuffer, byteBuffer.position(), byteBuffer.remaining()); + //skip to indicate the written, compressed data + byteBuffer.position(byteBuffer.position() + written); + } + } + + if(debug){ + if(Time.timeSinceMillis(lastUpload) >= 1000){ + lastUpload = Time.millis(); + upload.add(uploadAccum); + uploadAccum = 0; + Log.info("Upload: @ b/s", upload.mean()); + } + uploadAccum += byteBuffer.position() - lastPos; + } + } + + public void writeFramework(ByteBuffer buffer, FrameworkMessage message){ + if(message instanceof Ping p){ + buffer.put((byte)0); + buffer.putInt(p.id); + buffer.put(p.isReply ? 1 : (byte)0); + }else if(message instanceof DiscoverHost){ + buffer.put((byte)1); + }else if(message instanceof KeepAlive){ + buffer.put((byte)2); + }else if(message instanceof RegisterUDP p){ + buffer.put((byte)3); + buffer.putInt(p.connectionID); + }else if(message instanceof RegisterTCP p){ + buffer.put((byte)4); + buffer.putInt(p.connectionID); + } + } + + public FrameworkMessage readFramework(ByteBuffer buffer){ + byte id = buffer.get(); + + if(id == 0){ + Ping p = new Ping(); + p.id = buffer.getInt(); + p.isReply = buffer.get() == 1; + return p; + }else if(id == 1){ + return FrameworkMessage.discoverHost; + }else if(id == 2){ + return FrameworkMessage.keepAlive; + }else if(id == 3){ + RegisterUDP p = new RegisterUDP(); + p.connectionID = buffer.getInt(); + return p; + }else if(id == 4){ + RegisterTCP p = new RegisterTCP(); + p.connectionID = buffer.getInt(); + return p; + }else{ + throw new RuntimeException("Unknown framework message!"); + } + } + } + +} + +package mindustry.net; + +import mindustry.net.Packets.*; + +import java.io.*; + +public class Streamable extends Packet{ + public transient ByteArrayInputStream stream; + + @Override + public int getPriority(){ + return priorityHigh; + } + + public static class StreamBuilder{ + public final int id; + public final byte type; + public final int total; + public final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + + public StreamBuilder(StreamBegin begin){ + id = begin.id; + type = begin.type; + total = begin.total; + } + + public float progress(){ + return (float)stream.size() / total; + } + + public void add(byte[] bytes){ + try{ + stream.write(bytes); + }catch(IOException e){ + throw new RuntimeException(e); + } + } + + public Streamable build(){ + Streamable s = Net.newPacket(type); + s.stream = new ByteArrayInputStream(stream.toByteArray()); + return s; + } + + public boolean isDone(){ + return stream.size() >= total; + } + } +} + +/* + * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * Portions Copyright (c) 1995 Colin Plumb. All rights reserved. + */ + +package java.math; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamField; +import java.io.ObjectStreamException; +import java.util.Arrays; +import java.util.Objects; +import java.util.Random; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinWorkerThread; +import java.util.concurrent.RecursiveTask; +import java.util.concurrent.ThreadLocalRandom; + +import jdk.internal.math.DoubleConsts; +import jdk.internal.math.FloatConsts; +import jdk.internal.vm.annotation.ForceInline; +import jdk.internal.vm.annotation.IntrinsicCandidate; +import jdk.internal.vm.annotation.Stable; + +/** + * Immutable arbitrary-precision integers. All operations behave as if + * BigIntegers were represented in two's-complement notation (like Java's + * primitive integer types). BigInteger provides analogues to all of Java's + * primitive integer operators, and all relevant methods from java.lang.Math. + * Additionally, BigInteger provides operations for modular arithmetic, GCD + * calculation, primality testing, prime generation, bit manipulation, + * and a few other miscellaneous operations. + * + *

Semantics of arithmetic operations exactly mimic those of Java's integer + * arithmetic operators, as defined in The Java Language Specification. + * For example, division by zero throws an {@code ArithmeticException}, and + * division of a negative by a positive yields a negative (or zero) remainder. + * + *

Semantics of shift operations extend those of Java's shift operators + * to allow for negative shift distances. A right-shift with a negative + * shift distance results in a left shift, and vice-versa. The unsigned + * right shift operator ({@code >>>}) is omitted since this operation + * only makes sense for a fixed sized word and not for a + * representation conceptually having an infinite number of leading + * virtual sign bits. + * + *

Semantics of bitwise logical operations exactly mimic those of Java's + * bitwise integer operators. The binary operators ({@code and}, + * {@code or}, {@code xor}) implicitly perform sign extension on the shorter + * of the two operands prior to performing the operation. + * + *

Comparison operations perform signed integer comparisons, analogous to + * those performed by Java's relational and equality operators. + * + *

Modular arithmetic operations are provided to compute residues, perform + * exponentiation, and compute multiplicative inverses. These methods always + * return a non-negative result, between {@code 0} and {@code (modulus - 1)}, + * inclusive. + * + *

Bit operations operate on a single bit of the two's-complement + * representation of their operand. If necessary, the operand is sign-extended + * so that it contains the designated bit. None of the single-bit + * operations can produce a BigInteger with a different sign from the + * BigInteger being operated on, as they affect only a single bit, and the + * arbitrarily large abstraction provided by this class ensures that conceptually + * there are infinitely many "virtual sign bits" preceding each BigInteger. + * + *

For the sake of brevity and clarity, pseudo-code is used throughout the + * descriptions of BigInteger methods. The pseudo-code expression + * {@code (i + j)} is shorthand for "a BigInteger whose value is + * that of the BigInteger {@code i} plus that of the BigInteger {@code j}." + * The pseudo-code expression {@code (i == j)} is shorthand for + * "{@code true} if and only if the BigInteger {@code i} represents the same + * value as the BigInteger {@code j}." Other pseudo-code expressions are + * interpreted similarly. + * + *

All methods and constructors in this class throw + * {@code NullPointerException} when passed + * a null object reference for any input parameter. + * + * BigInteger must support values in the range + * -2{@code Integer.MAX_VALUE} (exclusive) to + * +2{@code Integer.MAX_VALUE} (exclusive) + * and may support values outside of that range. + * + * An {@code ArithmeticException} is thrown when a BigInteger + * constructor or method would generate a value outside of the + * supported range. + * + * The range of probable prime values is limited and may be less than + * the full supported positive range of {@code BigInteger}. + * The range must be at least 1 to 2500000000. + * + * @apiNote + * As {@code BigInteger} values are + * arbitrary precision integers, the algorithmic complexity of the + * methods of this class varies and may be superlinear in the size of + * the input. For example, a method like {@link intValue()} would be + * expected to run in O(1), that is constant time, since with + * the current internal representation only a fixed-size component of + * the {@code BigInteger} needs to be accessed to perform the + * conversion to {@code int}. In contrast, a method like {@link not()} + * would be expected to run in O(n) time where n + * is the size of the {@code BigInteger} in bits, that is, to run in + * time proportional to the size of the input. For multiplying two + * {@code BigInteger} values of size n, a naive multiplication + * algorithm would run in time O(n2) and + * theoretical results indicate a multiplication algorithm for numbers + * using this category of representation must run in at least + * O(n log n). Common multiplication + * algorithms between the bounds of the naive and theoretical cases + * include the Karatsuba multiplication + * (O(n1.585)) and 3-way Toom-Cook + * multiplication (O(n1.465)). + * + *

A particular implementation of {@link multiply(BigInteger) + * multiply} is free to switch between different algorithms for + * different inputs, such as to improve actual running time to produce + * the product by using simpler algorithms for smaller inputs even if + * the simpler algorithm has a larger asymptotic complexity. + * + *

Operations may also allocate and compute on intermediate + * results, potentially those allocations may be as large as in + * proportion to the running time of the algorithm. + * + *

Users of {@code BigInteger} concerned with bounding the running + * time or space of operations can screen out {@code BigInteger} + * values above a chosen magnitude. + * + * @implNote + * In the reference implementation, BigInteger constructors and + * operations throw {@code ArithmeticException} when the result is out + * of the supported range of + * -2{@code Integer.MAX_VALUE} (exclusive) to + * +2{@code Integer.MAX_VALUE} (exclusive). + * + * @see BigDecimal + * @jls 4.2.2 Integer Operations + * @author Josh Bloch + * @author Michael McCloskey + * @author Alan Eliasen + * @author Timothy Buktu + * @since 1.1 + */ + +public class BigInteger extends Number implements Comparable { + /** + * The signum of this BigInteger: -1 for negative, 0 for zero, or + * 1 for positive. Note that the BigInteger zero must have + * a signum of 0. This is necessary to ensures that there is exactly one + * representation for each BigInteger value. + */ + final int signum; + + /** + * The magnitude of this BigInteger, in big-endian order: the + * zeroth element of this array is the most-significant int of the + * magnitude. The magnitude must be "minimal" in that the most-significant + * int ({@code mag[0]}) must be non-zero. This is necessary to + * ensure that there is exactly one representation for each BigInteger + * value. Note that this implies that the BigInteger zero has a + * zero-length mag array. + */ + final int[] mag; + + // The following fields are stable variables. A stable variable's value + // changes at most once from the default zero value to a non-zero stable + // value. A stable value is calculated lazily on demand. + + /** + * One plus the bitCount of this BigInteger. This is a stable variable. + * + * @see #bitCount + */ + private int bitCountPlusOne; + + /** + * One plus the bitLength of this BigInteger. This is a stable variable. + * (either value is acceptable). + * + * @see #bitLength() + */ + private int bitLengthPlusOne; + + /** + * Two plus the lowest set bit of this BigInteger. This is a stable variable. + * + * @see #getLowestSetBit + */ + private int lowestSetBitPlusTwo; + + /** + * Two plus the index of the lowest-order int in the magnitude of this + * BigInteger that contains a nonzero int. This is a stable variable. The + * least significant int has int-number 0, the next int in order of + * increasing significance has int-number 1, and so forth. + * + *

Note: never used for a BigInteger with a magnitude of zero. + * + * @see #firstNonzeroIntNum() + */ + private int firstNonzeroIntNumPlusTwo; + + /** + * This mask is used to obtain the value of an int as if it were unsigned. + */ + static final long LONG_MASK = 0xffffffffL; + + /** + * This constant limits {@code mag.length} of BigIntegers to the supported + * range. + */ + private static final int MAX_MAG_LENGTH = Integer.MAX_VALUE / Integer.SIZE + 1; // (1 << 26) + + /** + * Bit lengths larger than this constant can cause overflow in searchLen + * calculation and in BitSieve.singleSearch method. + */ + private static final int PRIME_SEARCH_BIT_LENGTH_LIMIT = 500000000; + + /** + * The threshold value for using Karatsuba multiplication. If the number + * of ints in both mag arrays are greater than this number, then + * Karatsuba multiplication will be used. This value is found + * experimentally to work well. + */ + private static final int KARATSUBA_THRESHOLD = 80; + + /** + * The threshold value for using 3-way Toom-Cook multiplication. + * If the number of ints in each mag array is greater than the + * Karatsuba threshold, and the number of ints in at least one of + * the mag arrays is greater than this threshold, then Toom-Cook + * multiplication will be used. + */ + private static final int TOOM_COOK_THRESHOLD = 240; + + /** + * The threshold value for using Karatsuba squaring. If the number + * of ints in the number are larger than this value, + * Karatsuba squaring will be used. This value is found + * experimentally to work well. + */ + private static final int KARATSUBA_SQUARE_THRESHOLD = 128; + + /** + * The threshold value for using Toom-Cook squaring. If the number + * of ints in the number are larger than this value, + * Toom-Cook squaring will be used. This value is found + * experimentally to work well. + */ + private static final int TOOM_COOK_SQUARE_THRESHOLD = 216; + + /** + * The threshold value for using Burnikel-Ziegler division. If the number + * of ints in the divisor are larger than this value, Burnikel-Ziegler + * division may be used. This value is found experimentally to work well. + */ + static final int BURNIKEL_ZIEGLER_THRESHOLD = 80; + + /** + * The offset value for using Burnikel-Ziegler division. If the number + * of ints in the divisor exceeds the Burnikel-Ziegler threshold, and the + * number of ints in the dividend is greater than the number of ints in the + * divisor plus this value, Burnikel-Ziegler division will be used. This + * value is found experimentally to work well. + */ + static final int BURNIKEL_ZIEGLER_OFFSET = 40; + + /** + * The threshold value for using Schoenhage recursive base conversion. If + * the number of ints in the number are larger than this value, + * the Schoenhage algorithm will be used. In practice, it appears that the + * Schoenhage routine is faster for any threshold down to 2, and is + * relatively flat for thresholds between 2-25, so this choice may be + * varied within this range for very small effect. + */ + private static final int SCHOENHAGE_BASE_CONVERSION_THRESHOLD = 20; + + /** + * The threshold value for using squaring code to perform multiplication + * of a {@code BigInteger} instance by itself. If the number of ints in + * the number are larger than this value, {@code multiply(this)} will + * return {@code square()}. + */ + private static final int MULTIPLY_SQUARE_THRESHOLD = 20; + + /** + * The threshold for using an intrinsic version of + * implMontgomeryXXX to perform Montgomery multiplication. If the + * number of ints in the number is more than this value we do not + * use the intrinsic. + */ + private static final int MONTGOMERY_INTRINSIC_THRESHOLD = 512; + + + // Constructors + + /** + * Translates a byte sub-array containing the two's-complement binary + * representation of a BigInteger into a BigInteger. The sub-array is + * specified via an offset into the array and a length. The sub-array is + * assumed to be in big-endian byte-order: the most significant + * byte is the element at index {@code off}. The {@code val} array is + * assumed to be unchanged for the duration of the constructor call. + * + * An {@code IndexOutOfBoundsException} is thrown if the length of the array + * {@code val} is non-zero and either {@code off} is negative, {@code len} + * is negative, or {@code off+len} is greater than the length of + * {@code val}. + * + * @param val byte array containing a sub-array which is the big-endian + * two's-complement binary representation of a BigInteger. + * @param off the start offset of the binary representation. + * @param len the number of bytes to use. + * @throws NumberFormatException {@code val} is zero bytes long. + * @throws IndexOutOfBoundsException if the provided array offset and + * length would cause an index into the byte array to be + * negative or greater than or equal to the array length. + * @since 9 + */ + public BigInteger(byte[] val, int off, int len) { + if (val.length == 0) { + throw new NumberFormatException("Zero length BigInteger"); + } + Objects.checkFromIndexSize(off, len, val.length); + if (len == 0) { + mag = ZERO.mag; + signum = ZERO.signum; + return; + } + + int b = val[off]; + if (b < 0) { + mag = makePositive(b, val, off, len); + signum = -1; + } else { + mag = stripLeadingZeroBytes(b, val, off, len); + signum = (mag.length == 0 ? 0 : 1); + } + if (mag.length >= MAX_MAG_LENGTH) { + checkRange(); + } + } + + /** + * Translates a byte array containing the two's-complement binary + * representation of a BigInteger into a BigInteger. The input array is + * assumed to be in big-endian byte-order: the most significant + * byte is in the zeroth element. The {@code val} array is assumed to be + * unchanged for the duration of the constructor call. + * + * @param val big-endian two's-complement binary representation of a + * BigInteger. + * @throws NumberFormatException {@code val} is zero bytes long. + */ + public BigInteger(byte[] val) { + this(val, 0, val.length); + } + + /** + * This private constructor translates an int array containing the + * two's-complement binary representation of a BigInteger into a + * BigInteger. The input array is assumed to be in big-endian + * int-order: the most significant int is in the zeroth element. The + * {@code val} array is assumed to be unchanged for the duration of + * the constructor call. + */ + private BigInteger(int[] val) { + if (val.length == 0) + throw new NumberFormatException("Zero length BigInteger"); + + if (val[0] < 0) { + mag = makePositive(val); + signum = -1; + } else { + mag = trustedStripLeadingZeroInts(val); + signum = (mag.length == 0 ? 0 : 1); + } + if (mag.length >= MAX_MAG_LENGTH) { + checkRange(); + } + } + + /** + * Translates the sign-magnitude representation of a BigInteger into a + * BigInteger. The sign is represented as an integer signum value: -1 for + * negative, 0 for zero, or 1 for positive. The magnitude is a sub-array of + * a byte array in big-endian byte-order: the most significant byte + * is the element at index {@code off}. A zero value of the length + * {@code len} is permissible, and will result in a BigInteger value of 0, + * whether signum is -1, 0 or 1. The {@code magnitude} array is assumed to + * be unchanged for the duration of the constructor call. + * + * An {@code IndexOutOfBoundsException} is thrown if the length of the array + * {@code magnitude} is non-zero and either {@code off} is negative, + * {@code len} is negative, or {@code off+len} is greater than the length of + * {@code magnitude}. + * + * @param signum signum of the number (-1 for negative, 0 for zero, 1 + * for positive). + * @param magnitude big-endian binary representation of the magnitude of + * the number. + * @param off the start offset of the binary representation. + * @param len the number of bytes to use. + * @throws NumberFormatException {@code signum} is not one of the three + * legal values (-1, 0, and 1), or {@code signum} is 0 and + * {@code magnitude} contains one or more non-zero bytes. + * @throws IndexOutOfBoundsException if the provided array offset and + * length would cause an index into the byte array to be + * negative or greater than or equal to the array length. + * @since 9 + */ + public BigInteger(int signum, byte[] magnitude, int off, int len) { + if (signum < -1 || signum > 1) { + throw(new NumberFormatException("Invalid signum value")); + } + Objects.checkFromIndexSize(off, len, magnitude.length); + + // stripLeadingZeroBytes() returns a zero length array if len == 0 + this.mag = stripLeadingZeroBytes(magnitude, off, len); + + if (this.mag.length == 0) { + this.signum = 0; + } else { + if (signum == 0) + throw(new NumberFormatException("signum-magnitude mismatch")); + this.signum = signum; + } + if (mag.length >= MAX_MAG_LENGTH) { + checkRange(); + } + } + + /** + * Translates the sign-magnitude representation of a BigInteger into a + * BigInteger. The sign is represented as an integer signum value: -1 for + * negative, 0 for zero, or 1 for positive. The magnitude is a byte array + * in big-endian byte-order: the most significant byte is the + * zeroth element. A zero-length magnitude array is permissible, and will + * result in a BigInteger value of 0, whether signum is -1, 0 or 1. The + * {@code magnitude} array is assumed to be unchanged for the duration of + * the constructor call. + * + * @param signum signum of the number (-1 for negative, 0 for zero, 1 + * for positive). + * @param magnitude big-endian binary representation of the magnitude of + * the number. + * @throws NumberFormatException {@code signum} is not one of the three + * legal values (-1, 0, and 1), or {@code signum} is 0 and + * {@code magnitude} contains one or more non-zero bytes. + */ + public BigInteger(int signum, byte[] magnitude) { + this(signum, magnitude, 0, magnitude.length); + } + + /** + * A constructor for internal use that translates the sign-magnitude + * representation of a BigInteger into a BigInteger. It checks the + * arguments and copies the magnitude so this constructor would be + * safe for external use. The {@code magnitude} array is assumed to be + * unchanged for the duration of the constructor call. + */ + private BigInteger(int signum, int[] magnitude) { + this.mag = stripLeadingZeroInts(magnitude); + + if (signum < -1 || signum > 1) + throw(new NumberFormatException("Invalid signum value")); + + if (this.mag.length == 0) { + this.signum = 0; + } else { + if (signum == 0) + throw(new NumberFormatException("signum-magnitude mismatch")); + this.signum = signum; + } + if (mag.length >= MAX_MAG_LENGTH) { + checkRange(); + } + } + + /** + * Translates the String representation of a BigInteger in the + * specified radix into a BigInteger. The String representation + * consists of an optional minus or plus sign followed by a + * sequence of one or more digits in the specified radix. The + * character-to-digit mapping is provided by {@link + * Character#digit(char, int) Character.digit}. The String may + * not contain any extraneous characters (whitespace, for + * example). + * + * @param val String representation of BigInteger. + * @param radix radix to be used in interpreting {@code val}. + * @throws NumberFormatException {@code val} is not a valid representation + * of a BigInteger in the specified radix, or {@code radix} is + * outside the range from {@link Character#MIN_RADIX} to + * {@link Character#MAX_RADIX}, inclusive. + */ + public BigInteger(String val, int radix) { + int cursor = 0, numDigits; + final int len = val.length(); + + if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) + throw new NumberFormatException("Radix out of range"); + if (len == 0) + throw new NumberFormatException("Zero length BigInteger"); + + // Check for at most one leading sign + int sign = 1; + int index1 = val.lastIndexOf('-'); + int index2 = val.lastIndexOf('+'); + if (index1 >= 0) { + if (index1 != 0 || index2 >= 0) { + throw new NumberFormatException("Illegal embedded sign character"); + } + sign = -1; + cursor = 1; + } else if (index2 >= 0) { + if (index2 != 0) { + throw new NumberFormatException("Illegal embedded sign character"); + } + cursor = 1; + } + if (cursor == len) + throw new NumberFormatException("Zero length BigInteger"); + + // Skip leading zeros and compute number of digits in magnitude + while (cursor < len && + Character.digit(val.charAt(cursor), radix) == 0) { + cursor++; + } + + if (cursor == len) { + signum = 0; + mag = ZERO.mag; + return; + } + + numDigits = len - cursor; + signum = sign; + + // Pre-allocate array of expected size. May be too large but can + // never be too small. Typically exact. + long numBits = ((numDigits * bitsPerDigit[radix]) >>> 10) + 1; + if (numBits + 31 >= (1L << 32)) { + reportOverflow(); + } + int numWords = (int) (numBits + 31) >>> 5; + int[] magnitude = new int[numWords]; + + // Process first (potentially short) digit group + int firstGroupLen = numDigits % digitsPerInt[radix]; + if (firstGroupLen == 0) + firstGroupLen = digitsPerInt[radix]; + String group = val.substring(cursor, cursor += firstGroupLen); + magnitude[numWords - 1] = Integer.parseInt(group, radix); + if (magnitude[numWords - 1] < 0) + throw new NumberFormatException("Illegal digit"); + + // Process remaining digit groups + int superRadix = intRadix[radix]; + int groupVal = 0; + while (cursor < len) { + group = val.substring(cursor, cursor += digitsPerInt[radix]); + groupVal = Integer.parseInt(group, radix); + if (groupVal < 0) + throw new NumberFormatException("Illegal digit"); + destructiveMulAdd(magnitude, superRadix, groupVal); + } + // Required for cases where the array was overallocated. + mag = trustedStripLeadingZeroInts(magnitude); + if (mag.length >= MAX_MAG_LENGTH) { + checkRange(); + } + } + + /* + * Constructs a new BigInteger using a char array with radix=10. + * Sign is precalculated outside and not allowed in the val. The {@code val} + * array is assumed to be unchanged for the duration of the constructor + * call. + */ + BigInteger(char[] val, int sign, int len) { + int cursor = 0, numDigits; + + // Skip leading zeros and compute number of digits in magnitude + while (cursor < len && Character.digit(val[cursor], 10) == 0) { + cursor++; + } + if (cursor == len) { + signum = 0; + mag = ZERO.mag; + return; + } + + numDigits = len - cursor; + signum = sign; + // Pre-allocate array of expected size + int numWords; + if (len < 10) { + numWords = 1; + } else { + long numBits = ((numDigits * bitsPerDigit[10]) >>> 10) + 1; + if (numBits + 31 >= (1L << 32)) { + reportOverflow(); + } + numWords = (int) (numBits + 31) >>> 5; + } + int[] magnitude = new int[numWords]; + + // Process first (potentially short) digit group + int firstGroupLen = numDigits % digitsPerInt[10]; + if (firstGroupLen == 0) + firstGroupLen = digitsPerInt[10]; + magnitude[numWords - 1] = parseInt(val, cursor, cursor += firstGroupLen); + + // Process remaining digit groups + while (cursor < len) { + int groupVal = parseInt(val, cursor, cursor += digitsPerInt[10]); + destructiveMulAdd(magnitude, intRadix[10], groupVal); + } + mag = trustedStripLeadingZeroInts(magnitude); + if (mag.length >= MAX_MAG_LENGTH) { + checkRange(); + } + } + + // Create an integer with the digits between the two indexes + // Assumes start < end. The result may be negative, but it + // is to be treated as an unsigned value. + private int parseInt(char[] source, int start, int end) { + int result = Character.digit(source[start++], 10); + if (result == -1) + throw new NumberFormatException(new String(source)); + + for (int index = start; index < end; index++) { + int nextVal = Character.digit(source[index], 10); + if (nextVal == -1) + throw new NumberFormatException(new String(source)); + result = 10*result + nextVal; + } + + return result; + } + + // bitsPerDigit in the given radix times 1024 + // Rounded up to avoid underallocation. + private static long bitsPerDigit[] = { 0, 0, + 1024, 1624, 2048, 2378, 2648, 2875, 3072, 3247, 3402, 3543, 3672, + 3790, 3899, 4001, 4096, 4186, 4271, 4350, 4426, 4498, 4567, 4633, + 4696, 4756, 4814, 4870, 4923, 4975, 5025, 5074, 5120, 5166, 5210, + 5253, 5295}; + + // Multiply x array times word y in place, and add word z + private static void destructiveMulAdd(int[] x, int y, int z) { + // Perform the multiplication word by word + long ylong = y & LONG_MASK; + long zlong = z & LONG_MASK; + int len = x.length; + + long product = 0; + long carry = 0; + for (int i = len-1; i >= 0; i--) { + product = ylong * (x[i] & LONG_MASK) + carry; + x[i] = (int)product; + carry = product >>> 32; + } + + // Perform the addition + long sum = (x[len-1] & LONG_MASK) + zlong; + x[len-1] = (int)sum; + carry = sum >>> 32; + for (int i = len-2; i >= 0; i--) { + sum = (x[i] & LONG_MASK) + carry; + x[i] = (int)sum; + carry = sum >>> 32; + } + } + + /** + * Translates the decimal String representation of a BigInteger + * into a BigInteger. The String representation consists of an + * optional minus or plus sign followed by a sequence of one or + * more decimal digits. The character-to-digit mapping is + * provided by {@link Character#digit(char, int) + * Character.digit}. The String may not contain any extraneous + * characters (whitespace, for example). + * + * @param val decimal String representation of BigInteger. + * @throws NumberFormatException {@code val} is not a valid representation + * of a BigInteger. + */ + public BigInteger(String val) { + this(val, 10); + } + + /** + * Constructs a randomly generated BigInteger, uniformly distributed over + * the range 0 to (2{@code numBits} - 1), inclusive. + * The uniformity of the distribution assumes that a fair source of random + * bits is provided in {@code rnd}. Note that this constructor always + * constructs a non-negative BigInteger. + * + * @param numBits maximum bitLength of the new BigInteger. + * @param rnd source of randomness to be used in computing the new + * BigInteger. + * @throws IllegalArgumentException {@code numBits} is negative. + * @see #bitLength() + */ + public BigInteger(int numBits, Random rnd) { + byte[] magnitude = randomBits(numBits, rnd); + + try { + // stripLeadingZeroBytes() returns a zero length array if len == 0 + this.mag = stripLeadingZeroBytes(magnitude, 0, magnitude.length); + + if (this.mag.length == 0) { + this.signum = 0; + } else { + this.signum = 1; + } + if (mag.length >= MAX_MAG_LENGTH) { + checkRange(); + } + } finally { + Arrays.fill(magnitude, (byte)0); + } + } + + private static byte[] randomBits(int numBits, Random rnd) { + if (numBits < 0) + throw new IllegalArgumentException("numBits must be non-negative"); + int numBytes = (int)(((long)numBits+7)/8); // avoid overflow + byte[] randomBits = new byte[numBytes]; + + // Generate random bytes and mask out any excess bits + if (numBytes > 0) { + rnd.nextBytes(randomBits); + int excessBits = 8*numBytes - numBits; + randomBits[0] &= (byte)((1 << (8-excessBits)) - 1); + } + return randomBits; + } + + /** + * Constructs a randomly generated positive BigInteger that is probably + * prime, with the specified bitLength. + * + * @apiNote It is recommended that the {@link #probablePrime probablePrime} + * method be used in preference to this constructor unless there + * is a compelling need to specify a certainty. + * + * @param bitLength bitLength of the returned BigInteger. + * @param certainty a measure of the uncertainty that the caller is + * willing to tolerate. The probability that the new BigInteger + * represents a prime number will exceed + * (1 - 1/2{@code certainty}). The execution time of + * this constructor is proportional to the value of this parameter. + * @param rnd source of random bits used to select candidates to be + * tested for primality. + * @throws ArithmeticException {@code bitLength < 2} or {@code bitLength} is too large. + * @see #bitLength() + */ + public BigInteger(int bitLength, int certainty, Random rnd) { + BigInteger prime; + + if (bitLength < 2) + throw new ArithmeticException("bitLength < 2"); + prime = (bitLength < SMALL_PRIME_THRESHOLD + ? smallPrime(bitLength, certainty, rnd) + : largePrime(bitLength, certainty, rnd)); + signum = 1; + mag = prime.mag; + } + + // Minimum size in bits that the requested prime number has + // before we use the large prime number generating algorithms. + // The cutoff of 95 was chosen empirically for best performance. + private static final int SMALL_PRIME_THRESHOLD = 95; + + // Certainty required to meet the spec of probablePrime + private static final int DEFAULT_PRIME_CERTAINTY = 100; + + /** + * Returns a positive BigInteger that is probably prime, with the + * specified bitLength. The probability that a BigInteger returned + * by this method is composite does not exceed 2-100. + * + * @param bitLength bitLength of the returned BigInteger. + * @param rnd source of random bits used to select candidates to be + * tested for primality. + * @return a BigInteger of {@code bitLength} bits that is probably prime + * @throws ArithmeticException {@code bitLength < 2} or {@code bitLength} is too large. + * @see #bitLength() + * @since 1.4 + */ + public static BigInteger probablePrime(int bitLength, Random rnd) { + if (bitLength < 2) + throw new ArithmeticException("bitLength < 2"); + + return (bitLength < SMALL_PRIME_THRESHOLD ? + smallPrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd) : + largePrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd)); + } + + /** + * Find a random number of the specified bitLength that is probably prime. + * This method is used for smaller primes, its performance degrades on + * larger bitlengths. + * + * This method assumes bitLength > 1. + */ + private static BigInteger smallPrime(int bitLength, int certainty, Random rnd) { + int magLen = (bitLength + 31) >>> 5; + int temp[] = new int[magLen]; + int highBit = 1 << ((bitLength+31) & 0x1f); // High bit of high int + int highMask = (highBit << 1) - 1; // Bits to keep in high int + + while (true) { + // Construct a candidate + for (int i=0; i < magLen; i++) + temp[i] = rnd.nextInt(); + temp[0] = (temp[0] & highMask) | highBit; // Ensure exact length + if (bitLength > 2) + temp[magLen-1] |= 1; // Make odd if bitlen > 2 + + BigInteger p = new BigInteger(temp, 1); + + // Do cheap "pre-test" if applicable + if (bitLength > 6) { + long r = p.remainder(SMALL_PRIME_PRODUCT).longValue(); + if ((r%3==0) || (r%5==0) || (r%7==0) || (r%11==0) || + (r%13==0) || (r%17==0) || (r%19==0) || (r%23==0) || + (r%29==0) || (r%31==0) || (r%37==0) || (r%41==0)) + continue; // Candidate is composite; try another + } + + // All candidates of bitLength 2 and 3 are prime by this point + if (bitLength < 4) + return p; + + // Do expensive test if we survive pre-test (or it's inapplicable) + if (p.primeToCertainty(certainty, rnd)) + return p; + } + } + + private static final BigInteger SMALL_PRIME_PRODUCT + = valueOf(3L*5*7*11*13*17*19*23*29*31*37*41); + + /** + * Find a random number of the specified bitLength that is probably prime. + * This method is more appropriate for larger bitlengths since it uses + * a sieve to eliminate most composites before using a more expensive + * test. + */ + private static BigInteger largePrime(int bitLength, int certainty, Random rnd) { + BigInteger p; + p = new BigInteger(bitLength, rnd).setBit(bitLength-1); + p.mag[p.mag.length-1] &= 0xfffffffe; + + // Use a sieve length likely to contain the next prime number + int searchLen = getPrimeSearchLen(bitLength); + BitSieve searchSieve = new BitSieve(p, searchLen); + BigInteger candidate = searchSieve.retrieve(p, certainty, rnd); + + while ((candidate == null) || (candidate.bitLength() != bitLength)) { + p = p.add(BigInteger.valueOf(2*searchLen)); + if (p.bitLength() != bitLength) + p = new BigInteger(bitLength, rnd).setBit(bitLength-1); + p.mag[p.mag.length-1] &= 0xfffffffe; + searchSieve = new BitSieve(p, searchLen); + candidate = searchSieve.retrieve(p, certainty, rnd); + } + return candidate; + } + + /** + * Returns the first integer greater than this {@code BigInteger} that + * is probably prime. The probability that the number returned by this + * method is composite does not exceed 2-100. This method will + * never skip over a prime when searching: if it returns {@code p}, there + * is no prime {@code q} such that {@code this < q < p}. + * + * @return the first integer greater than this {@code BigInteger} that + * is probably prime. + * @throws ArithmeticException {@code this < 0} or {@code this} is too large. + * @implNote Due to the nature of the underlying algorithm, + * and depending on the size of {@code this}, + * this method could consume a large amount of memory, up to + * exhaustion of available heap space, or could run for a long time. + * @since 1.5 + */ + public BigInteger nextProbablePrime() { + if (this.signum < 0) + throw new ArithmeticException("start < 0: " + this); + + // Handle trivial cases + if ((this.signum == 0) || this.equals(ONE)) + return TWO; + + BigInteger result = this.add(ONE); + + // Fastpath for small numbers + if (result.bitLength() < SMALL_PRIME_THRESHOLD) { + + // Ensure an odd number + if (!result.testBit(0)) + result = result.add(ONE); + + while (true) { + // Do cheap "pre-test" if applicable + if (result.bitLength() > 6) { + long r = result.remainder(SMALL_PRIME_PRODUCT).longValue(); + if ((r%3==0) || (r%5==0) || (r%7==0) || (r%11==0) || + (r%13==0) || (r%17==0) || (r%19==0) || (r%23==0) || + (r%29==0) || (r%31==0) || (r%37==0) || (r%41==0)) { + result = result.add(TWO); + continue; // Candidate is composite; try another + } + } + + // All candidates of bitLength 2 and 3 are prime by this point + if (result.bitLength() < 4) + return result; + + // The expensive test + if (result.primeToCertainty(DEFAULT_PRIME_CERTAINTY, null)) + return result; + + result = result.add(TWO); + } + } + + // Start at previous even number + if (result.testBit(0)) + result = result.subtract(ONE); + + // Looking for the next large prime + int searchLen = getPrimeSearchLen(result.bitLength()); + + while (true) { + BitSieve searchSieve = new BitSieve(result, searchLen); + BigInteger candidate = searchSieve.retrieve(result, + DEFAULT_PRIME_CERTAINTY, null); + if (candidate != null) + return candidate; + result = result.add(BigInteger.valueOf(2 * searchLen)); + } + } + + private static int getPrimeSearchLen(int bitLength) { + if (bitLength > PRIME_SEARCH_BIT_LENGTH_LIMIT + 1) { + throw new ArithmeticException("Prime search implementation restriction on bitLength"); + } + return bitLength / 20 * 64; + } + + /** + * Returns {@code true} if this BigInteger is probably prime, + * {@code false} if it's definitely composite. + * + * This method assumes bitLength > 2. + * + * @param certainty a measure of the uncertainty that the caller is + * willing to tolerate: if the call returns {@code true} + * the probability that this BigInteger is prime exceeds + * (1 - 1/2certainty). The execution time of + * this method is proportional to the value of this parameter. + * @return {@code true} if this BigInteger is probably prime, + * {@code false} if it's definitely composite. + */ + boolean primeToCertainty(int certainty, Random random) { + int rounds = 0; + int n = (Math.min(certainty, Integer.MAX_VALUE-1)+1)/2; + + // The relationship between the certainty and the number of rounds + // we perform is given in the draft standard ANSI X9.80, "PRIME + // NUMBER GENERATION, PRIMALITY TESTING, AND PRIMALITY CERTIFICATES". + int sizeInBits = this.bitLength(); + if (sizeInBits < 100) { + rounds = 50; + rounds = n < rounds ? n : rounds; + return passesMillerRabin(rounds, random); + } + + if (sizeInBits < 256) { + rounds = 27; + } else if (sizeInBits < 512) { + rounds = 15; + } else if (sizeInBits < 768) { + rounds = 8; + } else if (sizeInBits < 1024) { + rounds = 4; + } else { + rounds = 2; + } + rounds = n < rounds ? n : rounds; + + return passesMillerRabin(rounds, random) && passesLucasLehmer(); + } + + /** + * Returns true iff this BigInteger is a Lucas-Lehmer probable prime. + * + * The following assumptions are made: + * This BigInteger is a positive, odd number. + */ + private boolean passesLucasLehmer() { + BigInteger thisPlusOne = this.add(ONE); + + // Step 1 + int d = 5; + while (jacobiSymbol(d, this) != -1) { + // 5, -7, 9, -11, ... + d = (d < 0) ? Math.abs(d)+2 : -(d+2); + } + + // Step 2 + BigInteger u = lucasLehmerSequence(d, thisPlusOne, this); + + // Step 3 + return u.mod(this).equals(ZERO); + } + + /** + * Computes Jacobi(p,n). + * Assumes n positive, odd, n>=3. + */ + private static int jacobiSymbol(int p, BigInteger n) { + if (p == 0) + return 0; + + // Algorithm and comments adapted from Colin Plumb's C library. + int j = 1; + int u = n.mag[n.mag.length-1]; + + // Make p positive + if (p < 0) { + p = -p; + int n8 = u & 7; + if ((n8 == 3) || (n8 == 7)) + j = -j; // 3 (011) or 7 (111) mod 8 + } + + // Get rid of factors of 2 in p + while ((p & 3) == 0) + p >>= 2; + if ((p & 1) == 0) { + p >>= 1; + if (((u ^ (u>>1)) & 2) != 0) + j = -j; // 3 (011) or 5 (101) mod 8 + } + if (p == 1) + return j; + // Then, apply quadratic reciprocity + if ((p & u & 2) != 0) // p = u = 3 (mod 4)? + j = -j; + // And reduce u mod p + u = n.mod(BigInteger.valueOf(p)).intValue(); + + // Now compute Jacobi(u,p), u < p + while (u != 0) { + while ((u & 3) == 0) + u >>= 2; + if ((u & 1) == 0) { + u >>= 1; + if (((p ^ (p>>1)) & 2) != 0) + j = -j; // 3 (011) or 5 (101) mod 8 + } + if (u == 1) + return j; + // Now both u and p are odd, so use quadratic reciprocity + assert (u < p); + int t = u; u = p; p = t; + if ((u & p & 2) != 0) // u = p = 3 (mod 4)? + j = -j; + // Now u >= p, so it can be reduced + u %= p; + } + return 0; + } + + private static BigInteger lucasLehmerSequence(int z, BigInteger k, BigInteger n) { + BigInteger d = BigInteger.valueOf(z); + BigInteger u = ONE; BigInteger u2; + BigInteger v = ONE; BigInteger v2; + + for (int i=k.bitLength()-2; i >= 0; i--) { + u2 = u.multiply(v).mod(n); + + v2 = v.square().add(d.multiply(u.square())).mod(n); + if (v2.testBit(0)) + v2 = v2.subtract(n); + + v2 = v2.shiftRight(1); + + u = u2; v = v2; + if (k.testBit(i)) { + u2 = u.add(v).mod(n); + if (u2.testBit(0)) + u2 = u2.subtract(n); + + u2 = u2.shiftRight(1); + v2 = v.add(d.multiply(u)).mod(n); + if (v2.testBit(0)) + v2 = v2.subtract(n); + v2 = v2.shiftRight(1); + + u = u2; v = v2; + } + } + return u; + } + + /** + * Returns true iff this BigInteger passes the specified number of + * Miller-Rabin tests. This test is taken from the DSA spec (NIST FIPS + * 186-2). + * + * The following assumptions are made: + * This BigInteger is a positive, odd number greater than 2. + * iterations<=50. + */ + private boolean passesMillerRabin(int iterations, Random rnd) { + // Find a and m such that m is odd and this == 1 + 2**a * m + BigInteger thisMinusOne = this.subtract(ONE); + BigInteger m = thisMinusOne; + int a = m.getLowestSetBit(); + m = m.shiftRight(a); + + // Do the tests + if (rnd == null) { + rnd = ThreadLocalRandom.current(); + } + for (int i=0; i < iterations; i++) { + // Generate a uniform random on (1, this) + BigInteger b; + do { + b = new BigInteger(this.bitLength(), rnd); + } while (b.compareTo(ONE) <= 0 || b.compareTo(this) >= 0); + + int j = 0; + BigInteger z = b.modPow(m, this); + while (!((j == 0 && z.equals(ONE)) || z.equals(thisMinusOne))) { + if (j > 0 && z.equals(ONE) || ++j == a) + return false; + z = z.modPow(TWO, this); + } + } + return true; + } + + /** + * This internal constructor differs from its public cousin + * with the arguments reversed in two ways: it assumes that its + * arguments are correct, and it doesn't copy the magnitude array. + */ + BigInteger(int[] magnitude, int signum) { + this.signum = (magnitude.length == 0 ? 0 : signum); + this.mag = magnitude; + if (mag.length >= MAX_MAG_LENGTH) { + checkRange(); + } + } + + /** + * This private constructor is for internal use and assumes that its + * arguments are correct. The {@code magnitude} array is assumed to be + * unchanged for the duration of the constructor call. + */ + private BigInteger(byte[] magnitude, int signum) { + this.signum = (magnitude.length == 0 ? 0 : signum); + this.mag = stripLeadingZeroBytes(magnitude, 0, magnitude.length); + if (mag.length >= MAX_MAG_LENGTH) { + checkRange(); + } + } + + /** + * Throws an {@code ArithmeticException} if the {@code BigInteger} would be + * out of the supported range. + * + * @throws ArithmeticException if {@code this} exceeds the supported range. + */ + private void checkRange() { + if (mag.length > MAX_MAG_LENGTH || mag.length == MAX_MAG_LENGTH && mag[0] < 0) { + reportOverflow(); + } + } + + private static void reportOverflow() { + throw new ArithmeticException("BigInteger would overflow supported range"); + } + + //Static Factory Methods + + /** + * Returns a BigInteger whose value is equal to that of the + * specified {@code long}. + * + * @apiNote This static factory method is provided in preference + * to a ({@code long}) constructor because it allows for reuse of + * frequently used BigIntegers. + * + * @param val value of the BigInteger to return. + * @return a BigInteger with the specified value. + */ + public static BigInteger valueOf(long val) { + // If -MAX_CONSTANT < val < MAX_CONSTANT, return stashed constant + if (val == 0) + return ZERO; + if (val > 0 && val <= MAX_CONSTANT) + return posConst[(int) val]; + else if (val < 0 && val >= -MAX_CONSTANT) + return negConst[(int) -val]; + + return new BigInteger(val); + } + + /** + * Constructs a BigInteger with the specified value, which may not be zero. + */ + private BigInteger(long val) { + if (val < 0) { + val = -val; + signum = -1; + } else { + signum = 1; + } + + int highWord = (int)(val >>> 32); + if (highWord == 0) { + mag = new int[1]; + mag[0] = (int)val; + } else { + mag = new int[2]; + mag[0] = highWord; + mag[1] = (int)val; + } + } + + /** + * Returns a BigInteger with the given two's complement representation. + * Assumes that the input array will not be modified (the returned + * BigInteger will reference the input array if feasible). + */ + private static BigInteger valueOf(int[] val) { + return (val[0] > 0 ? new BigInteger(val, 1) : new BigInteger(val)); + } + + // Constants + + /** + * Initialize static constant array when class is loaded. + */ + private static final int MAX_CONSTANT = 16; + @Stable + private static final BigInteger[] posConst = new BigInteger[MAX_CONSTANT+1]; + @Stable + private static final BigInteger[] negConst = new BigInteger[MAX_CONSTANT+1]; + + /** + * The cache of powers of each radix. This allows us to not have to + * recalculate powers of radix^(2^n) more than once. This speeds + * Schoenhage recursive base conversion significantly. + */ + private static volatile BigInteger[][] powerCache; + + /** The cache of logarithms of radices for base conversion. */ + private static final double[] logCache; + + /** The natural log of 2. This is used in computing cache indices. */ + private static final double LOG_TWO = Math.log(2.0); + + static { + assert 0 < KARATSUBA_THRESHOLD + && KARATSUBA_THRESHOLD < TOOM_COOK_THRESHOLD + && TOOM_COOK_THRESHOLD < Integer.MAX_VALUE + && 0 < KARATSUBA_SQUARE_THRESHOLD + && KARATSUBA_SQUARE_THRESHOLD < TOOM_COOK_SQUARE_THRESHOLD + && TOOM_COOK_SQUARE_THRESHOLD < Integer.MAX_VALUE : + "Algorithm thresholds are inconsistent"; + + for (int i = 1; i <= MAX_CONSTANT; i++) { + int[] magnitude = new int[1]; + magnitude[0] = i; + posConst[i] = new BigInteger(magnitude, 1); + negConst[i] = new BigInteger(magnitude, -1); + } + + /* + * Initialize the cache of radix^(2^x) values used for base conversion + * with just the very first value. Additional values will be created + * on demand. + */ + BigInteger[][] cache = new BigInteger[Character.MAX_RADIX+1][]; + logCache = new double[Character.MAX_RADIX+1]; + + for (int i=Character.MIN_RADIX; i <= Character.MAX_RADIX; i++) { + cache[i] = new BigInteger[] { BigInteger.valueOf(i) }; + logCache[i] = Math.log(i); + } + BigInteger.powerCache = cache; + } + + /** + * The BigInteger constant zero. + * + * @since 1.2 + */ + public static final BigInteger ZERO = new BigInteger(new int[0], 0); + + /** + * The BigInteger constant one. + * + * @since 1.2 + */ + public static final BigInteger ONE = valueOf(1); + + /** + * The BigInteger constant two. + * + * @since 9 + */ + public static final BigInteger TWO = valueOf(2); + + /** + * The BigInteger constant -1. (Not exported.) + */ + private static final BigInteger NEGATIVE_ONE = valueOf(-1); + + /** + * The BigInteger constant ten. + * + * @since 1.5 + */ + public static final BigInteger TEN = valueOf(10); + + // Arithmetic Operations + + /** + * Returns a BigInteger whose value is {@code (this + val)}. + * + * @param val value to be added to this BigInteger. + * @return {@code this + val} + */ + public BigInteger add(BigInteger val) { + if (val.signum == 0) + return this; + if (signum == 0) + return val; + if (val.signum == signum) + return new BigInteger(add(mag, val.mag), signum); + + int cmp = compareMagnitude(val); + if (cmp == 0) + return ZERO; + int[] resultMag = (cmp > 0 ? subtract(mag, val.mag) + : subtract(val.mag, mag)); + resultMag = trustedStripLeadingZeroInts(resultMag); + + return new BigInteger(resultMag, cmp == signum ? 1 : -1); + } + + /** + * Package private methods used by BigDecimal code to add a BigInteger + * with a long. Assumes val is not equal to INFLATED. + */ + BigInteger add(long val) { + if (val == 0) + return this; + if (signum == 0) + return valueOf(val); + if (Long.signum(val) == signum) + return new BigInteger(add(mag, Math.abs(val)), signum); + int cmp = compareMagnitude(val); + if (cmp == 0) + return ZERO; + int[] resultMag = (cmp > 0 ? subtract(mag, Math.abs(val)) : subtract(Math.abs(val), mag)); + resultMag = trustedStripLeadingZeroInts(resultMag); + return new BigInteger(resultMag, cmp == signum ? 1 : -1); + } + + /** + * Adds the contents of the int array x and long value val. This + * method allocates a new int array to hold the answer and returns + * a reference to that array. Assumes x.length > 0 and val is + * non-negative + */ + private static int[] add(int[] x, long val) { + int[] y; + long sum = 0; + int xIndex = x.length; + int[] result; + int highWord = (int)(val >>> 32); + if (highWord == 0) { + result = new int[xIndex]; + sum = (x[--xIndex] & LONG_MASK) + val; + result[xIndex] = (int)sum; + } else { + if (xIndex == 1) { + result = new int[2]; + sum = val + (x[0] & LONG_MASK); + result[1] = (int)sum; + result[0] = (int)(sum >>> 32); + return result; + } else { + result = new int[xIndex]; + sum = (x[--xIndex] & LONG_MASK) + (val & LONG_MASK); + result[xIndex] = (int)sum; + sum = (x[--xIndex] & LONG_MASK) + (highWord & LONG_MASK) + (sum >>> 32); + result[xIndex] = (int)sum; + } + } + // Copy remainder of longer number while carry propagation is required + boolean carry = (sum >>> 32 != 0); + while (xIndex > 0 && carry) + carry = ((result[--xIndex] = x[xIndex] + 1) == 0); + // Copy remainder of longer number + while (xIndex > 0) + result[--xIndex] = x[xIndex]; + // Grow result if necessary + if (carry) { + int bigger[] = new int[result.length + 1]; + System.arraycopy(result, 0, bigger, 1, result.length); + bigger[0] = 0x01; + return bigger; + } + return result; + } + + /** + * Adds the contents of the int arrays x and y. This method allocates + * a new int array to hold the answer and returns a reference to that + * array. + */ + private static int[] add(int[] x, int[] y) { + // If x is shorter, swap the two arrays + if (x.length < y.length) { + int[] tmp = x; + x = y; + y = tmp; + } + + int xIndex = x.length; + int yIndex = y.length; + int result[] = new int[xIndex]; + long sum = 0; + if (yIndex == 1) { + sum = (x[--xIndex] & LONG_MASK) + (y[0] & LONG_MASK) ; + result[xIndex] = (int)sum; + } else { + // Add common parts of both numbers + while (yIndex > 0) { + sum = (x[--xIndex] & LONG_MASK) + + (y[--yIndex] & LONG_MASK) + (sum >>> 32); + result[xIndex] = (int)sum; + } + } + // Copy remainder of longer number while carry propagation is required + boolean carry = (sum >>> 32 != 0); + while (xIndex > 0 && carry) + carry = ((result[--xIndex] = x[xIndex] + 1) == 0); + + // Copy remainder of longer number + while (xIndex > 0) + result[--xIndex] = x[xIndex]; + + // Grow result if necessary + if (carry) { + int bigger[] = new int[result.length + 1]; + System.arraycopy(result, 0, bigger, 1, result.length); + bigger[0] = 0x01; + return bigger; + } + return result; + } + + private static int[] subtract(long val, int[] little) { + int highWord = (int)(val >>> 32); + if (highWord == 0) { + int result[] = new int[1]; + result[0] = (int)(val - (little[0] & LONG_MASK)); + return result; + } else { + int result[] = new int[2]; + if (little.length == 1) { + long difference = ((int)val & LONG_MASK) - (little[0] & LONG_MASK); + result[1] = (int)difference; + // Subtract remainder of longer number while borrow propagates + boolean borrow = (difference >> 32 != 0); + if (borrow) { + result[0] = highWord - 1; + } else { // Copy remainder of longer number + result[0] = highWord; + } + return result; + } else { // little.length == 2 + long difference = ((int)val & LONG_MASK) - (little[1] & LONG_MASK); + result[1] = (int)difference; + difference = (highWord & LONG_MASK) - (little[0] & LONG_MASK) + (difference >> 32); + result[0] = (int)difference; + return result; + } + } + } + + /** + * Subtracts the contents of the second argument (val) from the + * first (big). The first int array (big) must represent a larger number + * than the second. This method allocates the space necessary to hold the + * answer. + * assumes val >= 0 + */ + private static int[] subtract(int[] big, long val) { + int highWord = (int)(val >>> 32); + int bigIndex = big.length; + int result[] = new int[bigIndex]; + long difference = 0; + + if (highWord == 0) { + difference = (big[--bigIndex] & LONG_MASK) - val; + result[bigIndex] = (int)difference; + } else { + difference = (big[--bigIndex] & LONG_MASK) - (val & LONG_MASK); + result[bigIndex] = (int)difference; + difference = (big[--bigIndex] & LONG_MASK) - (highWord & LONG_MASK) + (difference >> 32); + result[bigIndex] = (int)difference; + } + + // Subtract remainder of longer number while borrow propagates + boolean borrow = (difference >> 32 != 0); + while (bigIndex > 0 && borrow) + borrow = ((result[--bigIndex] = big[bigIndex] - 1) == -1); + + // Copy remainder of longer number + while (bigIndex > 0) + result[--bigIndex] = big[bigIndex]; + + return result; + } + + /** + * Returns a BigInteger whose value is {@code (this - val)}. + * + * @param val value to be subtracted from this BigInteger. + * @return {@code this - val} + */ + public BigInteger subtract(BigInteger val) { + if (val.signum == 0) + return this; + if (signum == 0) + return val.negate(); + if (val.signum != signum) + return new BigInteger(add(mag, val.mag), signum); + + int cmp = compareMagnitude(val); + if (cmp == 0) + return ZERO; + int[] resultMag = (cmp > 0 ? subtract(mag, val.mag) + : subtract(val.mag, mag)); + resultMag = trustedStripLeadingZeroInts(resultMag); + return new BigInteger(resultMag, cmp == signum ? 1 : -1); + } + + /** + * Subtracts the contents of the second int arrays (little) from the + * first (big). The first int array (big) must represent a larger number + * than the second. This method allocates the space necessary to hold the + * answer. + */ + private static int[] subtract(int[] big, int[] little) { + int bigIndex = big.length; + int result[] = new int[bigIndex]; + int littleIndex = little.length; + long difference = 0; + + // Subtract common parts of both numbers + while (littleIndex > 0) { + difference = (big[--bigIndex] & LONG_MASK) - + (little[--littleIndex] & LONG_MASK) + + (difference >> 32); + result[bigIndex] = (int)difference; + } + + // Subtract remainder of longer number while borrow propagates + boolean borrow = (difference >> 32 != 0); + while (bigIndex > 0 && borrow) + borrow = ((result[--bigIndex] = big[bigIndex] - 1) == -1); + + // Copy remainder of longer number + while (bigIndex > 0) + result[--bigIndex] = big[bigIndex]; + + return result; + } + + /** + * Returns a BigInteger whose value is {@code (this * val)}. + * + * @implNote An implementation may offer better algorithmic + * performance when {@code val == this}. + * + * @param val value to be multiplied by this BigInteger. + * @return {@code this * val} + */ + public BigInteger multiply(BigInteger val) { + return multiply(val, false, false, 0); + } + + /** + * Returns a BigInteger whose value is {@code (this * val)}. + * When both {@code this} and {@code val} are large, typically + * in the thousands of bits, parallel multiply might be used. + * This method returns the exact same mathematical result as + * {@link #multiply}. + * + * @implNote This implementation may offer better algorithmic + * performance when {@code val == this}. + * + * @implNote Compared to {@link #multiply}, an implementation's + * parallel multiplication algorithm would typically use more + * CPU resources to compute the result faster, and may do so + * with a slight increase in memory consumption. + * + * @param val value to be multiplied by this BigInteger. + * @return {@code this * val} + * @see #multiply + * @since 19 + */ + public BigInteger parallelMultiply(BigInteger val) { + return multiply(val, false, true, 0); + } + + /** + * Returns a BigInteger whose value is {@code (this * val)}. If + * the invocation is recursive certain overflow checks are skipped. + * + * @param val value to be multiplied by this BigInteger. + * @param isRecursion whether this is a recursive invocation + * @param parallel whether the multiply should be done in parallel + * @return {@code this * val} + */ + private BigInteger multiply(BigInteger val, boolean isRecursion, boolean parallel, int depth) { + if (val.signum == 0 || signum == 0) + return ZERO; + + int xlen = mag.length; + + if (val == this && xlen > MULTIPLY_SQUARE_THRESHOLD) { + return square(true, parallel, depth); + } + + int ylen = val.mag.length; + + if ((xlen < KARATSUBA_THRESHOLD) || (ylen < KARATSUBA_THRESHOLD)) { + int resultSign = signum == val.signum ? 1 : -1; + if (val.mag.length == 1) { + return multiplyByInt(mag,val.mag[0], resultSign); + } + if (mag.length == 1) { + return multiplyByInt(val.mag,mag[0], resultSign); + } + int[] result = multiplyToLen(mag, xlen, + val.mag, ylen, null); + result = trustedStripLeadingZeroInts(result); + return new BigInteger(result, resultSign); + } else { + if ((xlen < TOOM_COOK_THRESHOLD) && (ylen < TOOM_COOK_THRESHOLD)) { + return multiplyKaratsuba(this, val); + } else { + // + // In "Hacker's Delight" section 2-13, p.33, it is explained + // that if x and y are unsigned 32-bit quantities and m and n + // are their respective numbers of leading zeros within 32 bits, + // then the number of leading zeros within their product as a + // 64-bit unsigned quantity is either m + n or m + n + 1. If + // their product is not to overflow, it cannot exceed 32 bits, + // and so the number of leading zeros of the product within 64 + // bits must be at least 32, i.e., the leftmost set bit is at + // zero-relative position 31 or less. + // + // From the above there are three cases: + // + // m + n leftmost set bit condition + // ----- ---------------- --------- + // >= 32 x <= 64 - 32 = 32 no overflow + // == 31 x >= 64 - 32 = 32 possible overflow + // <= 30 x >= 64 - 31 = 33 definite overflow + // + // The "possible overflow" condition cannot be detected by + // examning data lengths alone and requires further calculation. + // + // By analogy, if 'this' and 'val' have m and n as their + // respective numbers of leading zeros within 32*MAX_MAG_LENGTH + // bits, then: + // + // m + n >= 32*MAX_MAG_LENGTH no overflow + // m + n == 32*MAX_MAG_LENGTH - 1 possible overflow + // m + n <= 32*MAX_MAG_LENGTH - 2 definite overflow + // + // Note however that if the number of ints in the result + // were to be MAX_MAG_LENGTH and mag[0] < 0, then there would + // be overflow. As a result the leftmost bit (of mag[0]) cannot + // be used and the constraints must be adjusted by one bit to: + // + // m + n > 32*MAX_MAG_LENGTH no overflow + // m + n == 32*MAX_MAG_LENGTH possible overflow + // m + n < 32*MAX_MAG_LENGTH definite overflow + // + // The foregoing leading zero-based discussion is for clarity + // only. The actual calculations use the estimated bit length + // of the product as this is more natural to the internal + // array representation of the magnitude which has no leading + // zero elements. + // + if (!isRecursion) { + // The bitLength() instance method is not used here as we + // are only considering the magnitudes as non-negative. The + // Toom-Cook multiplication algorithm determines the sign + // at its end from the two signum values. + if ((long)bitLength(mag, mag.length) + + (long)bitLength(val.mag, val.mag.length) > + 32L*MAX_MAG_LENGTH) { + reportOverflow(); + } + } + + return multiplyToomCook3(this, val, parallel, depth); + } + } + } + + private static BigInteger multiplyByInt(int[] x, int y, int sign) { + if (Integer.bitCount(y) == 1) { + return new BigInteger(shiftLeft(x,Integer.numberOfTrailingZeros(y)), sign); + } + int xlen = x.length; + int[] rmag = new int[xlen + 1]; + long carry = 0; + long yl = y & LONG_MASK; + int rstart = rmag.length - 1; + for (int i = xlen - 1; i >= 0; i--) { + long product = (x[i] & LONG_MASK) * yl + carry; + rmag[rstart--] = (int)product; + carry = product >>> 32; + } + if (carry == 0L) { + rmag = java.util.Arrays.copyOfRange(rmag, 1, rmag.length); + } else { + rmag[rstart] = (int)carry; + } + return new BigInteger(rmag, sign); + } + + /** + * Package private methods used by BigDecimal code to multiply a BigInteger + * with a long. Assumes v is not equal to INFLATED. + */ + BigInteger multiply(long v) { + if (v == 0 || signum == 0) + return ZERO; + if (v == BigDecimal.INFLATED) + return multiply(BigInteger.valueOf(v)); + int rsign = (v > 0 ? signum : -signum); + if (v < 0) + v = -v; + long dh = v >>> 32; // higher order bits + long dl = v & LONG_MASK; // lower order bits + + int xlen = mag.length; + int[] value = mag; + int[] rmag = (dh == 0L) ? (new int[xlen + 1]) : (new int[xlen + 2]); + long carry = 0; + int rstart = rmag.length - 1; + for (int i = xlen - 1; i >= 0; i--) { + long product = (value[i] & LONG_MASK) * dl + carry; + rmag[rstart--] = (int)product; + carry = product >>> 32; + } + rmag[rstart] = (int)carry; + if (dh != 0L) { + carry = 0; + rstart = rmag.length - 2; + for (int i = xlen - 1; i >= 0; i--) { + long product = (value[i] & LONG_MASK) * dh + + (rmag[rstart] & LONG_MASK) + carry; + rmag[rstart--] = (int)product; + carry = product >>> 32; + } + rmag[0] = (int)carry; + } + if (carry == 0L) + rmag = java.util.Arrays.copyOfRange(rmag, 1, rmag.length); + return new BigInteger(rmag, rsign); + } + + /** + * Multiplies int arrays x and y to the specified lengths and places + * the result into z. There will be no leading zeros in the resultant array. + */ + private static int[] multiplyToLen(int[] x, int xlen, int[] y, int ylen, int[] z) { + multiplyToLenCheck(x, xlen); + multiplyToLenCheck(y, ylen); + return implMultiplyToLen(x, xlen, y, ylen, z); + } + + @IntrinsicCandidate + private static int[] implMultiplyToLen(int[] x, int xlen, int[] y, int ylen, int[] z) { + int xstart = xlen - 1; + int ystart = ylen - 1; + + if (z == null || z.length < (xlen+ ylen)) + z = new int[xlen+ylen]; + + long carry = 0; + for (int j=ystart, k=ystart+1+xstart; j >= 0; j--, k--) { + long product = (y[j] & LONG_MASK) * + (x[xstart] & LONG_MASK) + carry; + z[k] = (int)product; + carry = product >>> 32; + } + z[xstart] = (int)carry; + + for (int i = xstart-1; i >= 0; i--) { + carry = 0; + for (int j=ystart, k=ystart+1+i; j >= 0; j--, k--) { + long product = (y[j] & LONG_MASK) * + (x[i] & LONG_MASK) + + (z[k] & LONG_MASK) + carry; + z[k] = (int)product; + carry = product >>> 32; + } + z[i] = (int)carry; + } + return z; + } + + private static void multiplyToLenCheck(int[] array, int length) { + if (length <= 0) { + return; // not an error because multiplyToLen won't execute if len <= 0 + } + + Objects.requireNonNull(array); + + if (length > array.length) { + throw new ArrayIndexOutOfBoundsException(length - 1); + } + } + + /** + * Multiplies two BigIntegers using the Karatsuba multiplication + * algorithm. This is a recursive divide-and-conquer algorithm which is + * more efficient for large numbers than what is commonly called the + * "grade-school" algorithm used in multiplyToLen. If the numbers to be + * multiplied have length n, the "grade-school" algorithm has an + * asymptotic complexity of O(n^2). In contrast, the Karatsuba algorithm + * has complexity of O(n^(log2(3))), or O(n^1.585). It achieves this + * increased performance by doing 3 multiplies instead of 4 when + * evaluating the product. As it has some overhead, should be used when + * both numbers are larger than a certain threshold (found + * experimentally). + * + * See: http://en.wikipedia.org/wiki/Karatsuba_algorithm + */ + private static BigInteger multiplyKaratsuba(BigInteger x, BigInteger y) { + int xlen = x.mag.length; + int ylen = y.mag.length; + + // The number of ints in each half of the number. + int half = (Math.max(xlen, ylen)+1) / 2; + + // xl and yl are the lower halves of x and y respectively, + // xh and yh are the upper halves. + BigInteger xl = x.getLower(half); + BigInteger xh = x.getUpper(half); + BigInteger yl = y.getLower(half); + BigInteger yh = y.getUpper(half); + + BigInteger p1 = xh.multiply(yh); // p1 = xh*yh + BigInteger p2 = xl.multiply(yl); // p2 = xl*yl + + // p3=(xh+xl)*(yh+yl) + BigInteger p3 = xh.add(xl).multiply(yh.add(yl)); + + // result = p1 * 2^(32*2*half) + (p3 - p1 - p2) * 2^(32*half) + p2 + BigInteger result = p1.shiftLeft(32*half).add(p3.subtract(p1).subtract(p2)).shiftLeft(32*half).add(p2); + + if (x.signum != y.signum) { + return result.negate(); + } else { + return result; + } + } + + @SuppressWarnings("serial") + private abstract static sealed class RecursiveOp extends RecursiveTask { + /** + * The threshold until when we should continue forking recursive ops + * if parallel is true. This threshold is only relevant for Toom Cook 3 + * multiply and square. + */ + private static final int PARALLEL_FORK_DEPTH_THRESHOLD = + calculateMaximumDepth(ForkJoinPool.getCommonPoolParallelism()); + + private static final int calculateMaximumDepth(int parallelism) { + return 32 - Integer.numberOfLeadingZeros(parallelism); + } + + final boolean parallel; + /** + * The current recursing depth. Since it is a logarithmic algorithm, + * we do not need an int to hold the number. + */ + final byte depth; + + private RecursiveOp(boolean parallel, int depth) { + this.parallel = parallel; + this.depth = (byte) depth; + } + + private static int getParallelForkDepthThreshold() { + if (Thread.currentThread() instanceof ForkJoinWorkerThread fjwt) { + return calculateMaximumDepth(fjwt.getPool().getParallelism()); + } + else { + return PARALLEL_FORK_DEPTH_THRESHOLD; + } + } + + protected RecursiveTask forkOrInvoke() { + if (parallel && depth <= getParallelForkDepthThreshold()) fork(); + else invoke(); + return this; + } + + @SuppressWarnings("serial") + private static final class RecursiveMultiply extends RecursiveOp { + private final BigInteger a; + private final BigInteger b; + + public RecursiveMultiply(BigInteger a, BigInteger b, boolean parallel, int depth) { + super(parallel, depth); + this.a = a; + this.b = b; + } + + @Override + public BigInteger compute() { + return a.multiply(b, true, parallel, depth); + } + } + + @SuppressWarnings("serial") + private static final class RecursiveSquare extends RecursiveOp { + private final BigInteger a; + + public RecursiveSquare(BigInteger a, boolean parallel, int depth) { + super(parallel, depth); + this.a = a; + } + + @Override + public BigInteger compute() { + return a.square(true, parallel, depth); + } + } + + private static RecursiveTask multiply(BigInteger a, BigInteger b, boolean parallel, int depth) { + return new RecursiveMultiply(a, b, parallel, depth).forkOrInvoke(); + } + + private static RecursiveTask square(BigInteger a, boolean parallel, int depth) { + return new RecursiveSquare(a, parallel, depth).forkOrInvoke(); + } + } + + /** + * Multiplies two BigIntegers using a 3-way Toom-Cook multiplication + * algorithm. This is a recursive divide-and-conquer algorithm which is + * more efficient for large numbers than what is commonly called the + * "grade-school" algorithm used in multiplyToLen. If the numbers to be + * multiplied have length n, the "grade-school" algorithm has an + * asymptotic complexity of O(n^2). In contrast, 3-way Toom-Cook has a + * complexity of about O(n^1.465). It achieves this increased asymptotic + * performance by breaking each number into three parts and by doing 5 + * multiplies instead of 9 when evaluating the product. Due to overhead + * (additions, shifts, and one division) in the Toom-Cook algorithm, it + * should only be used when both numbers are larger than a certain + * threshold (found experimentally). This threshold is generally larger + * than that for Karatsuba multiplication, so this algorithm is generally + * only used when numbers become significantly larger. + * + * The algorithm used is the "optimal" 3-way Toom-Cook algorithm outlined + * by Marco Bodrato. + * + * See: http://bodrato.it/toom-cook/ + * http://bodrato.it/papers/#WAIFI2007 + * + * "Towards Optimal Toom-Cook Multiplication for Univariate and + * Multivariate Polynomials in Characteristic 2 and 0." by Marco BODRATO; + * In C.Carlet and B.Sunar, Eds., "WAIFI'07 proceedings", p. 116-133, + * LNCS #4547. Springer, Madrid, Spain, June 21-22, 2007. + * + */ + private static BigInteger multiplyToomCook3(BigInteger a, BigInteger b, boolean parallel, int depth) { + int alen = a.mag.length; + int blen = b.mag.length; + + int largest = Math.max(alen, blen); + + // k is the size (in ints) of the lower-order slices. + int k = (largest+2)/3; // Equal to ceil(largest/3) + + // r is the size (in ints) of the highest-order slice. + int r = largest - 2*k; + + // Obtain slices of the numbers. a2 and b2 are the most significant + // bits of the numbers a and b, and a0 and b0 the least significant. + BigInteger a0, a1, a2, b0, b1, b2; + a2 = a.getToomSlice(k, r, 0, largest); + a1 = a.getToomSlice(k, r, 1, largest); + a0 = a.getToomSlice(k, r, 2, largest); + b2 = b.getToomSlice(k, r, 0, largest); + b1 = b.getToomSlice(k, r, 1, largest); + b0 = b.getToomSlice(k, r, 2, largest); + + BigInteger v0, v1, v2, vm1, vinf, t1, t2, tm1, da1, db1; + + depth++; + var v0_task = RecursiveOp.multiply(a0, b0, parallel, depth); + da1 = a2.add(a0); + db1 = b2.add(b0); + var vm1_task = RecursiveOp.multiply(da1.subtract(a1), db1.subtract(b1), parallel, depth); + da1 = da1.add(a1); + db1 = db1.add(b1); + var v1_task = RecursiveOp.multiply(da1, db1, parallel, depth); + v2 = da1.add(a2).shiftLeft(1).subtract(a0).multiply( + db1.add(b2).shiftLeft(1).subtract(b0), true, parallel, depth); + vinf = a2.multiply(b2, true, parallel, depth); + v0 = v0_task.join(); + vm1 = vm1_task.join(); + v1 = v1_task.join(); + + // The algorithm requires two divisions by 2 and one by 3. + // All divisions are known to be exact, that is, they do not produce + // remainders, and all results are positive. The divisions by 2 are + // implemented as right shifts which are relatively efficient, leaving + // only an exact division by 3, which is done by a specialized + // linear-time algorithm. + t2 = v2.subtract(vm1).exactDivideBy3(); + tm1 = v1.subtract(vm1).shiftRight(1); + t1 = v1.subtract(v0); + t2 = t2.subtract(t1).shiftRight(1); + t1 = t1.subtract(tm1).subtract(vinf); + t2 = t2.subtract(vinf.shiftLeft(1)); + tm1 = tm1.subtract(t2); + + // Number of bits to shift left. + int ss = k*32; + + BigInteger result = vinf.shiftLeft(ss).add(t2).shiftLeft(ss).add(t1).shiftLeft(ss).add(tm1).shiftLeft(ss).add(v0); + + if (a.signum != b.signum) { + return result.negate(); + } else { + return result; + } + } + + + /** + * Returns a slice of a BigInteger for use in Toom-Cook multiplication. + * + * @param lowerSize The size of the lower-order bit slices. + * @param upperSize The size of the higher-order bit slices. + * @param slice The index of which slice is requested, which must be a + * number from 0 to size-1. Slice 0 is the highest-order bits, and slice + * size-1 are the lowest-order bits. Slice 0 may be of different size than + * the other slices. + * @param fullsize The size of the larger integer array, used to align + * slices to the appropriate position when multiplying different-sized + * numbers. + */ + private BigInteger getToomSlice(int lowerSize, int upperSize, int slice, + int fullsize) { + int start, end, sliceSize, len, offset; + + len = mag.length; + offset = fullsize - len; + + if (slice == 0) { + start = 0 - offset; + end = upperSize - 1 - offset; + } else { + start = upperSize + (slice-1)*lowerSize - offset; + end = start + lowerSize - 1; + } + + if (start < 0) { + start = 0; + } + if (end < 0) { + return ZERO; + } + + sliceSize = (end-start) + 1; + + if (sliceSize <= 0) { + return ZERO; + } + + // While performing Toom-Cook, all slices are positive and + // the sign is adjusted when the final number is composed. + if (start == 0 && sliceSize >= len) { + return this.abs(); + } + + int intSlice[] = new int[sliceSize]; + System.arraycopy(mag, start, intSlice, 0, sliceSize); + + return new BigInteger(trustedStripLeadingZeroInts(intSlice), 1); + } + + /** + * Does an exact division (that is, the remainder is known to be zero) + * of the specified number by 3. This is used in Toom-Cook + * multiplication. This is an efficient algorithm that runs in linear + * time. If the argument is not exactly divisible by 3, results are + * undefined. Note that this is expected to be called with positive + * arguments only. + */ + private BigInteger exactDivideBy3() { + int len = mag.length; + int[] result = new int[len]; + long x, w, q, borrow; + borrow = 0L; + for (int i=len-1; i >= 0; i--) { + x = (mag[i] & LONG_MASK); + w = x - borrow; + if (borrow > x) { // Did we make the number go negative? + borrow = 1L; + } else { + borrow = 0L; + } + + // 0xAAAAAAAB is the modular inverse of 3 (mod 2^32). Thus, + // the effect of this is to divide by 3 (mod 2^32). + // This is much faster than division on most architectures. + q = (w * 0xAAAAAAABL) & LONG_MASK; + result[i] = (int) q; + + // Now check the borrow. The second check can of course be + // eliminated if the first fails. + if (q >= 0x55555556L) { + borrow++; + if (q >= 0xAAAAAAABL) + borrow++; + } + } + result = trustedStripLeadingZeroInts(result); + return new BigInteger(result, signum); + } + + /** + * Returns a new BigInteger representing n lower ints of the number. + * This is used by Karatsuba multiplication and Karatsuba squaring. + */ + private BigInteger getLower(int n) { + int len = mag.length; + + if (len <= n) { + return abs(); + } + + int lowerInts[] = new int[n]; + System.arraycopy(mag, len-n, lowerInts, 0, n); + + return new BigInteger(trustedStripLeadingZeroInts(lowerInts), 1); + } + + /** + * Returns a new BigInteger representing mag.length-n upper + * ints of the number. This is used by Karatsuba multiplication and + * Karatsuba squaring. + */ + private BigInteger getUpper(int n) { + int len = mag.length; + + if (len <= n) { + return ZERO; + } + + int upperLen = len - n; + int upperInts[] = new int[upperLen]; + System.arraycopy(mag, 0, upperInts, 0, upperLen); + + return new BigInteger(trustedStripLeadingZeroInts(upperInts), 1); + } + + // Squaring + + /** + * Returns a BigInteger whose value is (this2). + * + * @return this2 + */ + private BigInteger square() { + return square(false, false, 0); + } + + /** + * Returns a BigInteger whose value is (this2). If + * the invocation is recursive certain overflow checks are skipped. + * + * @param isRecursion whether this is a recursive invocation + * @return this2 + */ + private BigInteger square(boolean isRecursion, boolean parallel, int depth) { + if (signum == 0) { + return ZERO; + } + int len = mag.length; + + if (len < KARATSUBA_SQUARE_THRESHOLD) { + int[] z = squareToLen(mag, len, null); + return new BigInteger(trustedStripLeadingZeroInts(z), 1); + } else { + if (len < TOOM_COOK_SQUARE_THRESHOLD) { + return squareKaratsuba(); + } else { + // + // For a discussion of overflow detection see multiply() + // + if (!isRecursion) { + if (bitLength(mag, mag.length) > 16L*MAX_MAG_LENGTH) { + reportOverflow(); + } + } + + return squareToomCook3(parallel, depth); + } + } + } + + /** + * Squares the contents of the int array x. The result is placed into the + * int array z. The contents of x are not changed. + */ + private static final int[] squareToLen(int[] x, int len, int[] z) { + int zlen = len << 1; + if (z == null || z.length < zlen) + z = new int[zlen]; + + // Execute checks before calling intrinsified method. + implSquareToLenChecks(x, len, z, zlen); + return implSquareToLen(x, len, z, zlen); + } + + /** + * Parameters validation. + */ + private static void implSquareToLenChecks(int[] x, int len, int[] z, int zlen) throws RuntimeException { + if (len < 1) { + throw new IllegalArgumentException("invalid input length: " + len); + } + if (len > x.length) { + throw new IllegalArgumentException("input length out of bound: " + + len + " > " + x.length); + } + if (len * 2 > z.length) { + throw new IllegalArgumentException("input length out of bound: " + + (len * 2) + " > " + z.length); + } + if (zlen < 1) { + throw new IllegalArgumentException("invalid input length: " + zlen); + } + if (zlen > z.length) { + throw new IllegalArgumentException("input length out of bound: " + + len + " > " + z.length); + } + } + + /** + * Java Runtime may use intrinsic for this method. + */ + @IntrinsicCandidate + private static final int[] implSquareToLen(int[] x, int len, int[] z, int zlen) { + /* + * The algorithm used here is adapted from Colin Plumb's C library. + * Technique: Consider the partial products in the multiplication + * of "abcde" by itself: + * + * a b c d e + * * a b c d e + * ================== + * ae be ce de ee + * ad bd cd dd de + * ac bc cc cd ce + * ab bb bc bd be + * aa ab ac ad ae + * + * Note that everything above the main diagonal: + * ae be ce de = (abcd) * e + * ad bd cd = (abc) * d + * ac bc = (ab) * c + * ab = (a) * b + * + * is a copy of everything below the main diagonal: + * de + * cd ce + * bc bd be + * ab ac ad ae + * + * Thus, the sum is 2 * (off the diagonal) + diagonal. + * + * This is accumulated beginning with the diagonal (which + * consist of the squares of the digits of the input), which is then + * divided by two, the off-diagonal added, and multiplied by two + * again. The low bit is simply a copy of the low bit of the + * input, so it doesn't need special care. + */ + + // Store the squares, right shifted one bit (i.e., divided by 2) + int lastProductLowWord = 0; + for (int j=0, i=0; j < len; j++) { + long piece = (x[j] & LONG_MASK); + long product = piece * piece; + z[i++] = (lastProductLowWord << 31) | (int)(product >>> 33); + z[i++] = (int)(product >>> 1); + lastProductLowWord = (int)product; + } + + // Add in off-diagonal sums + for (int i=len, offset=1; i > 0; i--, offset+=2) { + int t = x[i-1]; + t = mulAdd(z, x, offset, i-1, t); + addOne(z, offset-1, i, t); + } + + // Shift back up and set low bit + primitiveLeftShift(z, zlen, 1); + z[zlen-1] |= x[len-1] & 1; + + return z; + } + + /** + * Squares a BigInteger using the Karatsuba squaring algorithm. It should + * be used when both numbers are larger than a certain threshold (found + * experimentally). It is a recursive divide-and-conquer algorithm that + * has better asymptotic performance than the algorithm used in + * squareToLen. + */ + private BigInteger squareKaratsuba() { + int half = (mag.length+1) / 2; + + BigInteger xl = getLower(half); + BigInteger xh = getUpper(half); + + BigInteger xhs = xh.square(); // xhs = xh^2 + BigInteger xls = xl.square(); // xls = xl^2 + + // xh^2 << 64 + (((xl+xh)^2 - (xh^2 + xl^2)) << 32) + xl^2 + return xhs.shiftLeft(half*32).add(xl.add(xh).square().subtract(xhs.add(xls))).shiftLeft(half*32).add(xls); + } + + /** + * Squares a BigInteger using the 3-way Toom-Cook squaring algorithm. It + * should be used when both numbers are larger than a certain threshold + * (found experimentally). It is a recursive divide-and-conquer algorithm + * that has better asymptotic performance than the algorithm used in + * squareToLen or squareKaratsuba. + */ + private BigInteger squareToomCook3(boolean parallel, int depth) { + int len = mag.length; + + // k is the size (in ints) of the lower-order slices. + int k = (len+2)/3; // Equal to ceil(largest/3) + + // r is the size (in ints) of the highest-order slice. + int r = len - 2*k; + + // Obtain slices of the numbers. a2 is the most significant + // bits of the number, and a0 the least significant. + BigInteger a0, a1, a2; + a2 = getToomSlice(k, r, 0, len); + a1 = getToomSlice(k, r, 1, len); + a0 = getToomSlice(k, r, 2, len); + BigInteger v0, v1, v2, vm1, vinf, t1, t2, tm1, da1; + + depth++; + var v0_fork = RecursiveOp.square(a0, parallel, depth); + da1 = a2.add(a0); + var vm1_fork = RecursiveOp.square(da1.subtract(a1), parallel, depth); + da1 = da1.add(a1); + var v1_fork = RecursiveOp.square(da1, parallel, depth); + vinf = a2.square(true, parallel, depth); + v2 = da1.add(a2).shiftLeft(1).subtract(a0).square(true, parallel, depth); + v0 = v0_fork.join(); + vm1 = vm1_fork.join(); + v1 = v1_fork.join(); + + // The algorithm requires two divisions by 2 and one by 3. + // All divisions are known to be exact, that is, they do not produce + // remainders, and all results are positive. The divisions by 2 are + // implemented as right shifts which are relatively efficient, leaving + // only a division by 3. + // The division by 3 is done by an optimized algorithm for this case. + t2 = v2.subtract(vm1).exactDivideBy3(); + tm1 = v1.subtract(vm1).shiftRight(1); + t1 = v1.subtract(v0); + t2 = t2.subtract(t1).shiftRight(1); + t1 = t1.subtract(tm1).subtract(vinf); + t2 = t2.subtract(vinf.shiftLeft(1)); + tm1 = tm1.subtract(t2); + + // Number of bits to shift left. + int ss = k*32; + + return vinf.shiftLeft(ss).add(t2).shiftLeft(ss).add(t1).shiftLeft(ss).add(tm1).shiftLeft(ss).add(v0); + } + + // Division + + /** + * Returns a BigInteger whose value is {@code (this / val)}. + * + * @param val value by which this BigInteger is to be divided. + * @return {@code this / val} + * @throws ArithmeticException if {@code val} is zero. + */ + public BigInteger divide(BigInteger val) { + if (val.mag.length < BURNIKEL_ZIEGLER_THRESHOLD || + mag.length - val.mag.length < BURNIKEL_ZIEGLER_OFFSET) { + return divideKnuth(val); + } else { + return divideBurnikelZiegler(val); + } + } + + /** + * Returns a BigInteger whose value is {@code (this / val)} using an O(n^2) algorithm from Knuth. + * + * @param val value by which this BigInteger is to be divided. + * @return {@code this / val} + * @throws ArithmeticException if {@code val} is zero. + * @see MutableBigInteger#divideKnuth(MutableBigInteger, MutableBigInteger, boolean) + */ + private BigInteger divideKnuth(BigInteger val) { + MutableBigInteger q = new MutableBigInteger(), + a = new MutableBigInteger(this.mag), + b = new MutableBigInteger(val.mag); + + a.divideKnuth(b, q, false); + return q.toBigInteger(this.signum * val.signum); + } + + /** + * Returns an array of two BigIntegers containing {@code (this / val)} + * followed by {@code (this % val)}. + * + * @param val value by which this BigInteger is to be divided, and the + * remainder computed. + * @return an array of two BigIntegers: the quotient {@code (this / val)} + * is the initial element, and the remainder {@code (this % val)} + * is the final element. + * @throws ArithmeticException if {@code val} is zero. + */ + public BigInteger[] divideAndRemainder(BigInteger val) { + if (val.mag.length < BURNIKEL_ZIEGLER_THRESHOLD || + mag.length - val.mag.length < BURNIKEL_ZIEGLER_OFFSET) { + return divideAndRemainderKnuth(val); + } else { + return divideAndRemainderBurnikelZiegler(val); + } + } + + /** Long division */ + private BigInteger[] divideAndRemainderKnuth(BigInteger val) { + BigInteger[] result = new BigInteger[2]; + MutableBigInteger q = new MutableBigInteger(), + a = new MutableBigInteger(this.mag), + b = new MutableBigInteger(val.mag); + MutableBigInteger r = a.divideKnuth(b, q); + result[0] = q.toBigInteger(this.signum == val.signum ? 1 : -1); + result[1] = r.toBigInteger(this.signum); + return result; + } + + /** + * Returns a BigInteger whose value is {@code (this % val)}. + * + * @param val value by which this BigInteger is to be divided, and the + * remainder computed. + * @return {@code this % val} + * @throws ArithmeticException if {@code val} is zero. + */ + public BigInteger remainder(BigInteger val) { + if (val.mag.length < BURNIKEL_ZIEGLER_THRESHOLD || + mag.length - val.mag.length < BURNIKEL_ZIEGLER_OFFSET) { + return remainderKnuth(val); + } else { + return remainderBurnikelZiegler(val); + } + } + + /** Long division */ + private BigInteger remainderKnuth(BigInteger val) { + MutableBigInteger q = new MutableBigInteger(), + a = new MutableBigInteger(this.mag), + b = new MutableBigInteger(val.mag); + + return a.divideKnuth(b, q).toBigInteger(this.signum); + } + + /** + * Calculates {@code this / val} using the Burnikel-Ziegler algorithm. + * @param val the divisor + * @return {@code this / val} + */ + private BigInteger divideBurnikelZiegler(BigInteger val) { + return divideAndRemainderBurnikelZiegler(val)[0]; + } + + /** + * Calculates {@code this % val} using the Burnikel-Ziegler algorithm. + * @param val the divisor + * @return {@code this % val} + */ + private BigInteger remainderBurnikelZiegler(BigInteger val) { + return divideAndRemainderBurnikelZiegler(val)[1]; + } + + /** + * Computes {@code this / val} and {@code this % val} using the + * Burnikel-Ziegler algorithm. + * @param val the divisor + * @return an array containing the quotient and remainder + */ + private BigInteger[] divideAndRemainderBurnikelZiegler(BigInteger val) { + MutableBigInteger q = new MutableBigInteger(); + MutableBigInteger r = new MutableBigInteger(this).divideAndRemainderBurnikelZiegler(new MutableBigInteger(val), q); + BigInteger qBigInt = q.isZero() ? ZERO : q.toBigInteger(signum*val.signum); + BigInteger rBigInt = r.isZero() ? ZERO : r.toBigInteger(signum); + return new BigInteger[] {qBigInt, rBigInt}; + } + + /** + * Returns a BigInteger whose value is (thisexponent). + * Note that {@code exponent} is an integer rather than a BigInteger. + * + * @param exponent exponent to which this BigInteger is to be raised. + * @return thisexponent + * @throws ArithmeticException {@code exponent} is negative. (This would + * cause the operation to yield a non-integer value.) + */ + public BigInteger pow(int exponent) { + if (exponent < 0) { + throw new ArithmeticException("Negative exponent"); + } + if (signum == 0) { + return (exponent == 0 ? ONE : this); + } + + BigInteger partToSquare = this.abs(); + + // Factor out powers of two from the base, as the exponentiation of + // these can be done by left shifts only. + // The remaining part can then be exponentiated faster. The + // powers of two will be multiplied back at the end. + int powersOfTwo = partToSquare.getLowestSetBit(); + long bitsToShiftLong = (long)powersOfTwo * exponent; + if (bitsToShiftLong > Integer.MAX_VALUE) { + reportOverflow(); + } + int bitsToShift = (int)bitsToShiftLong; + + int remainingBits; + + // Factor the powers of two out quickly by shifting right, if needed. + if (powersOfTwo > 0) { + partToSquare = partToSquare.shiftRight(powersOfTwo); + remainingBits = partToSquare.bitLength(); + if (remainingBits == 1) { // Nothing left but +/- 1? + if (signum < 0 && (exponent&1) == 1) { + return NEGATIVE_ONE.shiftLeft(bitsToShift); + } else { + return ONE.shiftLeft(bitsToShift); + } + } + } else { + remainingBits = partToSquare.bitLength(); + if (remainingBits == 1) { // Nothing left but +/- 1? + if (signum < 0 && (exponent&1) == 1) { + return NEGATIVE_ONE; + } else { + return ONE; + } + } + } + + // This is a quick way to approximate the size of the result, + // similar to doing log2[n] * exponent. This will give an upper bound + // of how big the result can be, and which algorithm to use. + long scaleFactor = (long)remainingBits * exponent; + + // Use slightly different algorithms for small and large operands. + // See if the result will safely fit into a long. (Largest 2^63-1) + if (partToSquare.mag.length == 1 && scaleFactor <= 62) { + // Small number algorithm. Everything fits into a long. + int newSign = (signum <0 && (exponent&1) == 1 ? -1 : 1); + long result = 1; + long baseToPow2 = partToSquare.mag[0] & LONG_MASK; + + int workingExponent = exponent; + + // Perform exponentiation using repeated squaring trick + while (workingExponent != 0) { + if ((workingExponent & 1) == 1) { + result = result * baseToPow2; + } + + if ((workingExponent >>>= 1) != 0) { + baseToPow2 = baseToPow2 * baseToPow2; + } + } + + // Multiply back the powers of two (quickly, by shifting left) + if (powersOfTwo > 0) { + if (bitsToShift + scaleFactor <= 62) { // Fits in long? + return valueOf((result << bitsToShift) * newSign); + } else { + return valueOf(result*newSign).shiftLeft(bitsToShift); + } + } else { + return valueOf(result*newSign); + } + } else { + if ((long)bitLength() * exponent / Integer.SIZE > MAX_MAG_LENGTH) { + reportOverflow(); + } + + // Large number algorithm. This is basically identical to + // the algorithm above, but calls multiply() and square() + // which may use more efficient algorithms for large numbers. + BigInteger answer = ONE; + + int workingExponent = exponent; + // Perform exponentiation using repeated squaring trick + while (workingExponent != 0) { + if ((workingExponent & 1) == 1) { + answer = answer.multiply(partToSquare); + } + + if ((workingExponent >>>= 1) != 0) { + partToSquare = partToSquare.square(); + } + } + // Multiply back the (exponentiated) powers of two (quickly, + // by shifting left) + if (powersOfTwo > 0) { + answer = answer.shiftLeft(bitsToShift); + } + + if (signum < 0 && (exponent&1) == 1) { + return answer.negate(); + } else { + return answer; + } + } + } + + /** + * Returns the integer square root of this BigInteger. The integer square + * root of the corresponding mathematical integer {@code n} is the largest + * mathematical integer {@code s} such that {@code s*s <= n}. It is equal + * to the value of {@code floor(sqrt(n))}, where {@code sqrt(n)} denotes the + * real square root of {@code n} treated as a real. Note that the integer + * square root will be less than the real square root if the latter is not + * representable as an integral value. + * + * @return the integer square root of {@code this} + * @throws ArithmeticException if {@code this} is negative. (The square + * root of a negative integer {@code val} is + * {@code (i * sqrt(-val))} where i is the + * imaginary unit and is equal to + * {@code sqrt(-1)}.) + * @since 9 + */ + public BigInteger sqrt() { + if (this.signum < 0) { + throw new ArithmeticException("Negative BigInteger"); + } + + return new MutableBigInteger(this.mag).sqrt().toBigInteger(); + } + + /** + * Returns an array of two BigIntegers containing the integer square root + * {@code s} of {@code this} and its remainder {@code this - s*s}, + * respectively. + * + * @return an array of two BigIntegers with the integer square root at + * offset 0 and the remainder at offset 1 + * @throws ArithmeticException if {@code this} is negative. (The square + * root of a negative integer {@code val} is + * {@code (i * sqrt(-val))} where i is the + * imaginary unit and is equal to + * {@code sqrt(-1)}.) + * @see #sqrt() + * @since 9 + */ + public BigInteger[] sqrtAndRemainder() { + BigInteger s = sqrt(); + BigInteger r = this.subtract(s.square()); + assert r.compareTo(BigInteger.ZERO) >= 0; + return new BigInteger[] {s, r}; + } + + /** + * Returns a BigInteger whose value is the greatest common divisor of + * {@code abs(this)} and {@code abs(val)}. Returns 0 if + * {@code this == 0 && val == 0}. + * + * @param val value with which the GCD is to be computed. + * @return {@code GCD(abs(this), abs(val))} + */ + public BigInteger gcd(BigInteger val) { + if (val.signum == 0) + return this.abs(); + else if (this.signum == 0) + return val.abs(); + + MutableBigInteger a = new MutableBigInteger(this); + MutableBigInteger b = new MutableBigInteger(val); + + MutableBigInteger result = a.hybridGCD(b); + + return result.toBigInteger(1); + } + + /** + * Package private method to return bit length for an integer. + */ + static int bitLengthForInt(int n) { + return 32 - Integer.numberOfLeadingZeros(n); + } + + /** + * Left shift int array a up to len by n bits. Returns the array that + * results from the shift since space may have to be reallocated. + */ + private static int[] leftShift(int[] a, int len, int n) { + int nInts = n >>> 5; + int nBits = n&0x1F; + int bitsInHighWord = bitLengthForInt(a[0]); + + // If shift can be done without recopy, do so + if (n <= (32-bitsInHighWord)) { + primitiveLeftShift(a, len, nBits); + return a; + } else { // Array must be resized + if (nBits <= (32-bitsInHighWord)) { + int result[] = new int[nInts+len]; + System.arraycopy(a, 0, result, 0, len); + primitiveLeftShift(result, result.length, nBits); + return result; + } else { + int result[] = new int[nInts+len+1]; + System.arraycopy(a, 0, result, 0, len); + primitiveRightShift(result, result.length, 32 - nBits); + return result; + } + } + } + + // shifts a up to len right n bits assumes no leading zeros, 0>>= n; + } + + // shifts a up to len left n bits assumes no leading zeros, 0<=n<32 + static void primitiveLeftShift(int[] a, int len, int n) { + if (len == 0 || n == 0) + return; + Objects.checkFromToIndex(0, len, a.length); + shiftLeftImplWorker(a, a, 0, n, len-1); + a[len-1] <<= n; + } + + /** + * Calculate bitlength of contents of the first len elements an int array, + * assuming there are no leading zero ints. + */ + private static int bitLength(int[] val, int len) { + if (len == 0) + return 0; + return ((len - 1) << 5) + bitLengthForInt(val[0]); + } + + /** + * Returns a BigInteger whose value is the absolute value of this + * BigInteger. + * + * @return {@code abs(this)} + */ + public BigInteger abs() { + return (signum >= 0 ? this : this.negate()); + } + + /** + * Returns a BigInteger whose value is {@code (-this)}. + * + * @return {@code -this} + */ + public BigInteger negate() { + return new BigInteger(this.mag, -this.signum); + } + + /** + * Returns the signum function of this BigInteger. + * + * @return -1, 0 or 1 as the value of this BigInteger is negative, zero or + * positive. + */ + public int signum() { + return this.signum; + } + + // Modular Arithmetic Operations + + /** + * Returns a BigInteger whose value is {@code (this mod m}). This method + * differs from {@code remainder} in that it always returns a + * non-negative BigInteger. + * + * @param m the modulus. + * @return {@code this mod m} + * @throws ArithmeticException {@code m} ≤ 0 + * @see #remainder + */ + public BigInteger mod(BigInteger m) { + if (m.signum <= 0) + throw new ArithmeticException("BigInteger: modulus not positive"); + + BigInteger result = this.remainder(m); + return (result.signum >= 0 ? result : result.add(m)); + } + + /** + * Returns a BigInteger whose value is + * (thisexponent mod m). (Unlike {@code pow}, this + * method permits negative exponents.) + * + * @param exponent the exponent. + * @param m the modulus. + * @return thisexponent mod m + * @throws ArithmeticException {@code m} ≤ 0 or the exponent is + * negative and this BigInteger is not relatively + * prime to {@code m}. + * @see #modInverse + */ + public BigInteger modPow(BigInteger exponent, BigInteger m) { + if (m.signum <= 0) + throw new ArithmeticException("BigInteger: modulus not positive"); + + // Trivial cases + if (exponent.signum == 0) + return (m.equals(ONE) ? ZERO : ONE); + + if (this.equals(ONE)) + return (m.equals(ONE) ? ZERO : ONE); + + if (this.equals(ZERO) && exponent.signum >= 0) + return ZERO; + + if (this.equals(negConst[1]) && (!exponent.testBit(0))) + return (m.equals(ONE) ? ZERO : ONE); + + boolean invertResult; + if ((invertResult = (exponent.signum < 0))) + exponent = exponent.negate(); + + BigInteger base = (this.signum < 0 || this.compareTo(m) >= 0 + ? this.mod(m) : this); + BigInteger result; + if (m.testBit(0)) { // odd modulus + result = base.oddModPow(exponent, m); + } else { + /* + * Even modulus. Tear it into an "odd part" (m1) and power of two + * (m2), exponentiate mod m1, manually exponentiate mod m2, and + * use Chinese Remainder Theorem to combine results. + */ + + // Tear m apart into odd part (m1) and power of 2 (m2) + int p = m.getLowestSetBit(); // Max pow of 2 that divides m + + BigInteger m1 = m.shiftRight(p); // m/2**p + BigInteger m2 = ONE.shiftLeft(p); // 2**p + + // Calculate new base from m1 + BigInteger base2 = (this.signum < 0 || this.compareTo(m1) >= 0 + ? this.mod(m1) : this); + + // Calculate (base ** exponent) mod m1. + BigInteger a1 = (m1.equals(ONE) ? ZERO : + base2.oddModPow(exponent, m1)); + + // Calculate (this ** exponent) mod m2 + BigInteger a2 = base.modPow2(exponent, p); + + // Combine results using Chinese Remainder Theorem + BigInteger y1 = m2.modInverse(m1); + BigInteger y2 = m1.modInverse(m2); + + if (m.mag.length < MAX_MAG_LENGTH / 2) { + result = a1.multiply(m2).multiply(y1).add(a2.multiply(m1).multiply(y2)).mod(m); + } else { + MutableBigInteger t1 = new MutableBigInteger(); + new MutableBigInteger(a1.multiply(m2)).multiply(new MutableBigInteger(y1), t1); + MutableBigInteger t2 = new MutableBigInteger(); + new MutableBigInteger(a2.multiply(m1)).multiply(new MutableBigInteger(y2), t2); + t1.add(t2); + MutableBigInteger q = new MutableBigInteger(); + result = t1.divide(new MutableBigInteger(m), q).toBigInteger(); + } + } + + return (invertResult ? result.modInverse(m) : result); + } + + // Montgomery multiplication. These are wrappers for + // implMontgomeryXX routines which are expected to be replaced by + // virtual machine intrinsics. We don't use the intrinsics for + // very large operands: MONTGOMERY_INTRINSIC_THRESHOLD should be + // larger than any reasonable crypto key. + private static int[] montgomeryMultiply(int[] a, int[] b, int[] n, int len, long inv, + int[] product) { + implMontgomeryMultiplyChecks(a, b, n, len, product); + if (len > MONTGOMERY_INTRINSIC_THRESHOLD) { + // Very long argument: do not use an intrinsic + product = multiplyToLen(a, len, b, len, product); + return montReduce(product, n, len, (int)inv); + } else { + return implMontgomeryMultiply(a, b, n, len, inv, materialize(product, len)); + } + } + private static int[] montgomerySquare(int[] a, int[] n, int len, long inv, + int[] product) { + implMontgomeryMultiplyChecks(a, a, n, len, product); + if (len > MONTGOMERY_INTRINSIC_THRESHOLD) { + // Very long argument: do not use an intrinsic + product = squareToLen(a, len, product); + return montReduce(product, n, len, (int)inv); + } else { + return implMontgomerySquare(a, n, len, inv, materialize(product, len)); + } + } + + // Range-check everything. + private static void implMontgomeryMultiplyChecks + (int[] a, int[] b, int[] n, int len, int[] product) throws RuntimeException { + if (len % 2 != 0) { + throw new IllegalArgumentException("input array length must be even: " + len); + } + + if (len < 1) { + throw new IllegalArgumentException("invalid input length: " + len); + } + + if (len > a.length || + len > b.length || + len > n.length || + (product != null && len > product.length)) { + throw new IllegalArgumentException("input array length out of bound: " + len); + } + } + + // Make sure that the int array z (which is expected to contain + // the result of a Montgomery multiplication) is present and + // sufficiently large. + private static int[] materialize(int[] z, int len) { + if (z == null || z.length < len) + z = new int[len]; + return z; + } + + // These methods are intended to be replaced by virtual machine + // intrinsics. + @IntrinsicCandidate + private static int[] implMontgomeryMultiply(int[] a, int[] b, int[] n, int len, + long inv, int[] product) { + product = multiplyToLen(a, len, b, len, product); + return montReduce(product, n, len, (int)inv); + } + @IntrinsicCandidate + private static int[] implMontgomerySquare(int[] a, int[] n, int len, + long inv, int[] product) { + product = squareToLen(a, len, product); + return montReduce(product, n, len, (int)inv); + } + + static int[] bnExpModThreshTable = {7, 25, 81, 241, 673, 1793, + Integer.MAX_VALUE}; // Sentinel + + /** + * Returns a BigInteger whose value is x to the power of y mod z. + * Assumes: z is odd && x < z. + */ + private BigInteger oddModPow(BigInteger y, BigInteger z) { + /* + * The algorithm is adapted from Colin Plumb's C library. + * + * The window algorithm: + * The idea is to keep a running product of b1 = n^(high-order bits of exp) + * and then keep appending exponent bits to it. The following patterns + * apply to a 3-bit window (k = 3): + * To append 0: square + * To append 1: square, multiply by n^1 + * To append 10: square, multiply by n^1, square + * To append 11: square, square, multiply by n^3 + * To append 100: square, multiply by n^1, square, square + * To append 101: square, square, square, multiply by n^5 + * To append 110: square, square, multiply by n^3, square + * To append 111: square, square, square, multiply by n^7 + * + * Since each pattern involves only one multiply, the longer the pattern + * the better, except that a 0 (no multiplies) can be appended directly. + * We precompute a table of odd powers of n, up to 2^k, and can then + * multiply k bits of exponent at a time. Actually, assuming random + * exponents, there is on average one zero bit between needs to + * multiply (1/2 of the time there's none, 1/4 of the time there's 1, + * 1/8 of the time, there's 2, 1/32 of the time, there's 3, etc.), so + * you have to do one multiply per k+1 bits of exponent. + * + * The loop walks down the exponent, squaring the result buffer as + * it goes. There is a wbits+1 bit lookahead buffer, buf, that is + * filled with the upcoming exponent bits. (What is read after the + * end of the exponent is unimportant, but it is filled with zero here.) + * When the most-significant bit of this buffer becomes set, i.e. + * (buf & tblmask) != 0, we have to decide what pattern to multiply + * by, and when to do it. We decide, remember to do it in future + * after a suitable number of squarings have passed (e.g. a pattern + * of "100" in the buffer requires that we multiply by n^1 immediately; + * a pattern of "110" calls for multiplying by n^3 after one more + * squaring), clear the buffer, and continue. + * + * When we start, there is one more optimization: the result buffer + * is implcitly one, so squaring it or multiplying by it can be + * optimized away. Further, if we start with a pattern like "100" + * in the lookahead window, rather than placing n into the buffer + * and then starting to square it, we have already computed n^2 + * to compute the odd-powers table, so we can place that into + * the buffer and save a squaring. + * + * This means that if you have a k-bit window, to compute n^z, + * where z is the high k bits of the exponent, 1/2 of the time + * it requires no squarings. 1/4 of the time, it requires 1 + * squaring, ... 1/2^(k-1) of the time, it requires k-2 squarings. + * And the remaining 1/2^(k-1) of the time, the top k bits are a + * 1 followed by k-1 0 bits, so it again only requires k-2 + * squarings, not k-1. The average of these is 1. Add that + * to the one squaring we have to do to compute the table, + * and you'll see that a k-bit window saves k-2 squarings + * as well as reducing the multiplies. (It actually doesn't + * hurt in the case k = 1, either.) + */ + // Special case for exponent of one + if (y.equals(ONE)) + return this; + + // Special case for base of zero + if (signum == 0) + return ZERO; + + int[] base = mag.clone(); + int[] exp = y.mag; + int[] mod = z.mag; + int modLen = mod.length; + + // Make modLen even. It is conventional to use a cryptographic + // modulus that is 512, 768, 1024, or 2048 bits, so this code + // will not normally be executed. However, it is necessary for + // the correct functioning of the HotSpot intrinsics. + if ((modLen & 1) != 0) { + int[] x = new int[modLen + 1]; + System.arraycopy(mod, 0, x, 1, modLen); + mod = x; + modLen++; + } + + // Select an appropriate window size + int wbits = 0; + int ebits = bitLength(exp, exp.length); + // if exponent is 65537 (0x10001), use minimum window size + if ((ebits != 17) || (exp[0] != 65537)) { + while (ebits > bnExpModThreshTable[wbits]) { + wbits++; + } + } + + // Calculate appropriate table size + int tblmask = 1 << wbits; + + // Allocate table for precomputed odd powers of base in Montgomery form + int[][] table = new int[tblmask][]; + for (int i=0; i < tblmask; i++) + table[i] = new int[modLen]; + + // Compute the modular inverse of the least significant 64-bit + // digit of the modulus + long n0 = (mod[modLen-1] & LONG_MASK) + ((mod[modLen-2] & LONG_MASK) << 32); + long inv = -MutableBigInteger.inverseMod64(n0); + + // Convert base to Montgomery form + int[] a = leftShift(base, base.length, modLen << 5); + + MutableBigInteger q = new MutableBigInteger(), + a2 = new MutableBigInteger(a), + b2 = new MutableBigInteger(mod); + b2.normalize(); // MutableBigInteger.divide() assumes that its + // divisor is in normal form. + + MutableBigInteger r= a2.divide(b2, q); + table[0] = r.toIntArray(); + + // Pad table[0] with leading zeros so its length is at least modLen + if (table[0].length < modLen) { + int offset = modLen - table[0].length; + int[] t2 = new int[modLen]; + System.arraycopy(table[0], 0, t2, offset, table[0].length); + table[0] = t2; + } + + // Set b to the square of the base + int[] b = montgomerySquare(table[0], mod, modLen, inv, null); + + // Set t to high half of b + int[] t = Arrays.copyOf(b, modLen); + + // Fill in the table with odd powers of the base + for (int i=1; i < tblmask; i++) { + table[i] = montgomeryMultiply(t, table[i-1], mod, modLen, inv, null); + } + + // Pre load the window that slides over the exponent + int bitpos = 1 << ((ebits-1) & (32-1)); + + int buf = 0; + int elen = exp.length; + int eIndex = 0; + for (int i = 0; i <= wbits; i++) { + buf = (buf << 1) | (((exp[eIndex] & bitpos) != 0)?1:0); + bitpos >>>= 1; + if (bitpos == 0) { + eIndex++; + bitpos = 1 << (32-1); + elen--; + } + } + + int multpos = ebits; + + // The first iteration, which is hoisted out of the main loop + ebits--; + boolean isone = true; + + multpos = ebits - wbits; + while ((buf & 1) == 0) { + buf >>>= 1; + multpos++; + } + + int[] mult = table[buf >>> 1]; + + buf = 0; + if (multpos == ebits) + isone = false; + + // The main loop + while (true) { + ebits--; + // Advance the window + buf <<= 1; + + if (elen != 0) { + buf |= ((exp[eIndex] & bitpos) != 0) ? 1 : 0; + bitpos >>>= 1; + if (bitpos == 0) { + eIndex++; + bitpos = 1 << (32-1); + elen--; + } + } + + // Examine the window for pending multiplies + if ((buf & tblmask) != 0) { + multpos = ebits - wbits; + while ((buf & 1) == 0) { + buf >>>= 1; + multpos++; + } + mult = table[buf >>> 1]; + buf = 0; + } + + // Perform multiply + if (ebits == multpos) { + if (isone) { + b = mult.clone(); + isone = false; + } else { + t = b; + a = montgomeryMultiply(t, mult, mod, modLen, inv, a); + t = a; a = b; b = t; + } + } + + // Check if done + if (ebits == 0) + break; + + // Square the input + if (!isone) { + t = b; + a = montgomerySquare(t, mod, modLen, inv, a); + t = a; a = b; b = t; + } + } + + // Convert result out of Montgomery form and return + int[] t2 = new int[2*modLen]; + System.arraycopy(b, 0, t2, modLen, modLen); + + b = montReduce(t2, mod, modLen, (int)inv); + + t2 = Arrays.copyOf(b, modLen); + + return new BigInteger(1, t2); + } + + /** + * Montgomery reduce n, modulo mod. This reduces modulo mod and divides + * by 2^(32*mlen). Adapted from Colin Plumb's C library. + */ + private static int[] montReduce(int[] n, int[] mod, int mlen, int inv) { + int c=0; + int len = mlen; + int offset=0; + + do { + int nEnd = n[n.length-1-offset]; + int carry = mulAdd(n, mod, offset, mlen, inv * nEnd); + c += addOne(n, offset, mlen, carry); + offset++; + } while (--len > 0); + + while (c > 0) + c += subN(n, mod, mlen); + + while (intArrayCmpToLen(n, mod, mlen) >= 0) + subN(n, mod, mlen); + + return n; + } + + + /* + * Returns -1, 0 or +1 as big-endian unsigned int array arg1 is less than, + * equal to, or greater than arg2 up to length len. + */ + private static int intArrayCmpToLen(int[] arg1, int[] arg2, int len) { + for (int i=0; i < len; i++) { + long b1 = arg1[i] & LONG_MASK; + long b2 = arg2[i] & LONG_MASK; + if (b1 < b2) + return -1; + if (b1 > b2) + return 1; + } + return 0; + } + + /** + * Subtracts two numbers of same length, returning borrow. + */ + private static int subN(int[] a, int[] b, int len) { + long sum = 0; + + while (--len >= 0) { + sum = (a[len] & LONG_MASK) - + (b[len] & LONG_MASK) + (sum >> 32); + a[len] = (int)sum; + } + + return (int)(sum >> 32); + } + + /** + * Multiply an array by one word k and add to result, return the carry + */ + static int mulAdd(int[] out, int[] in, int offset, int len, int k) { + implMulAddCheck(out, in, offset, len, k); + return implMulAdd(out, in, offset, len, k); + } + + /** + * Parameters validation. + */ + private static void implMulAddCheck(int[] out, int[] in, int offset, int len, int k) { + if (len > in.length) { + throw new IllegalArgumentException("input length is out of bound: " + len + " > " + in.length); + } + if (offset < 0) { + throw new IllegalArgumentException("input offset is invalid: " + offset); + } + if (offset > (out.length - 1)) { + throw new IllegalArgumentException("input offset is out of bound: " + offset + " > " + (out.length - 1)); + } + if (len > (out.length - offset)) { + throw new IllegalArgumentException("input len is out of bound: " + len + " > " + (out.length - offset)); + } + } + + /** + * Java Runtime may use intrinsic for this method. + */ + @IntrinsicCandidate + private static int implMulAdd(int[] out, int[] in, int offset, int len, int k) { + long kLong = k & LONG_MASK; + long carry = 0; + + offset = out.length-offset - 1; + for (int j=len-1; j >= 0; j--) { + long product = (in[j] & LONG_MASK) * kLong + + (out[offset] & LONG_MASK) + carry; + out[offset--] = (int)product; + carry = product >>> 32; + } + return (int)carry; + } + + /** + * Add one word to the number a mlen words into a. Return the resulting + * carry. + */ + static int addOne(int[] a, int offset, int mlen, int carry) { + offset = a.length-1-mlen-offset; + long t = (a[offset] & LONG_MASK) + (carry & LONG_MASK); + + a[offset] = (int)t; + if ((t >>> 32) == 0) + return 0; + while (--mlen >= 0) { + if (--offset < 0) { // Carry out of number + return 1; + } else { + a[offset]++; + if (a[offset] != 0) + return 0; + } + } + return 1; + } + + /** + * Returns a BigInteger whose value is (this ** exponent) mod (2**p) + */ + private BigInteger modPow2(BigInteger exponent, int p) { + /* + * Perform exponentiation using repeated squaring trick, chopping off + * high order bits as indicated by modulus. + */ + BigInteger result = ONE; + BigInteger baseToPow2 = this.mod2(p); + int expOffset = 0; + + int limit = exponent.bitLength(); + + if (this.testBit(0)) + limit = (p-1) < limit ? (p-1) : limit; + + while (expOffset < limit) { + if (exponent.testBit(expOffset)) + result = result.multiply(baseToPow2).mod2(p); + expOffset++; + if (expOffset < limit) + baseToPow2 = baseToPow2.square().mod2(p); + } + + return result; + } + + /** + * Returns a BigInteger whose value is this mod(2**p). + * Assumes that this {@code BigInteger >= 0} and {@code p > 0}. + */ + private BigInteger mod2(int p) { + if (bitLength() <= p) + return this; + + // Copy remaining ints of mag + int numInts = (p + 31) >>> 5; + int[] mag = new int[numInts]; + System.arraycopy(this.mag, (this.mag.length - numInts), mag, 0, numInts); + + // Mask out any excess bits + int excessBits = (numInts << 5) - p; + mag[0] &= (int)((1L << (32-excessBits)) - 1); + + return (mag[0] == 0 ? new BigInteger(1, mag) : new BigInteger(mag, 1)); + } + + /** + * Returns a BigInteger whose value is {@code (this}-1 {@code mod m)}. + * + * @param m the modulus. + * @return {@code this}-1 {@code mod m}. + * @throws ArithmeticException {@code m} ≤ 0, or this BigInteger + * has no multiplicative inverse mod m (that is, this BigInteger + * is not relatively prime to m). + */ + public BigInteger modInverse(BigInteger m) { + if (m.signum != 1) + throw new ArithmeticException("BigInteger: modulus not positive"); + + if (m.equals(ONE)) + return ZERO; + + // Calculate (this mod m) + BigInteger modVal = this; + if (signum < 0 || (this.compareMagnitude(m) >= 0)) + modVal = this.mod(m); + + if (modVal.equals(ONE)) + return ONE; + + MutableBigInteger a = new MutableBigInteger(modVal); + MutableBigInteger b = new MutableBigInteger(m); + + MutableBigInteger result = a.mutableModInverse(b); + return result.toBigInteger(1); + } + + // Shift Operations + + /** + * Returns a BigInteger whose value is {@code (this << n)}. + * The shift distance, {@code n}, may be negative, in which case + * this method performs a right shift. + * (Computes floor(this * 2n).) + * + * @param n shift distance, in bits. + * @return {@code this << n} + * @see #shiftRight + */ + public BigInteger shiftLeft(int n) { + if (signum == 0) + return ZERO; + if (n > 0) { + return new BigInteger(shiftLeft(mag, n), signum); + } else if (n == 0) { + return this; + } else { + // Possible int overflow in (-n) is not a trouble, + // because shiftRightImpl considers its argument unsigned + return shiftRightImpl(-n); + } + } + + /** + * Returns a magnitude array whose value is {@code (mag << n)}. + * The shift distance, {@code n}, is considered unnsigned. + * (Computes this * 2n.) + * + * @param mag magnitude, the most-significant int ({@code mag[0]}) must be non-zero. + * @param n unsigned shift distance, in bits. + * @return {@code mag << n} + */ + private static int[] shiftLeft(int[] mag, int n) { + int nInts = n >>> 5; + int nBits = n & 0x1f; + int magLen = mag.length; + int newMag[] = null; + + if (nBits == 0) { + newMag = new int[magLen + nInts]; + System.arraycopy(mag, 0, newMag, 0, magLen); + } else { + int i = 0; + int nBits2 = 32 - nBits; + int highBits = mag[0] >>> nBits2; + if (highBits != 0) { + newMag = new int[magLen + nInts + 1]; + newMag[i++] = highBits; + } else { + newMag = new int[magLen + nInts]; + } + int numIter = magLen - 1; + Objects.checkFromToIndex(0, numIter + 1, mag.length); + Objects.checkFromToIndex(i, numIter + i + 1, newMag.length); + shiftLeftImplWorker(newMag, mag, i, nBits, numIter); + newMag[numIter + i] = mag[numIter] << nBits; + } + return newMag; + } + + @ForceInline + @IntrinsicCandidate + private static void shiftLeftImplWorker(int[] newArr, int[] oldArr, int newIdx, int shiftCount, int numIter) { + int shiftCountRight = 32 - shiftCount; + int oldIdx = 0; + while (oldIdx < numIter) { + newArr[newIdx++] = (oldArr[oldIdx++] << shiftCount) | (oldArr[oldIdx] >>> shiftCountRight); + } + } + + /** + * Returns a BigInteger whose value is {@code (this >> n)}. Sign + * extension is performed. The shift distance, {@code n}, may be + * negative, in which case this method performs a left shift. + * (Computes floor(this / 2n).) + * + * @param n shift distance, in bits. + * @return {@code this >> n} + * @see #shiftLeft + */ + public BigInteger shiftRight(int n) { + if (signum == 0) + return ZERO; + if (n > 0) { + return shiftRightImpl(n); + } else if (n == 0) { + return this; + } else { + // Possible int overflow in {@code -n} is not a trouble, + // because shiftLeft considers its argument unsigned + return new BigInteger(shiftLeft(mag, -n), signum); + } + } + + /** + * Returns a BigInteger whose value is {@code (this >> n)}. The shift + * distance, {@code n}, is considered unsigned. + * (Computes floor(this * 2-n).) + * + * @param n unsigned shift distance, in bits. + * @return {@code this >> n} + */ + private BigInteger shiftRightImpl(int n) { + int nInts = n >>> 5; + int nBits = n & 0x1f; + int magLen = mag.length; + int newMag[] = null; + + // Special case: entire contents shifted off the end + if (nInts >= magLen) + return (signum >= 0 ? ZERO : negConst[1]); + + if (nBits == 0) { + int newMagLen = magLen - nInts; + newMag = Arrays.copyOf(mag, newMagLen); + } else { + int i = 0; + int highBits = mag[0] >>> nBits; + if (highBits != 0) { + newMag = new int[magLen - nInts]; + newMag[i++] = highBits; + } else { + newMag = new int[magLen - nInts -1]; + } + int numIter = magLen - nInts - 1; + Objects.checkFromToIndex(0, numIter + 1, mag.length); + Objects.checkFromToIndex(i, numIter + i, newMag.length); + shiftRightImplWorker(newMag, mag, i, nBits, numIter); + } + + if (signum < 0) { + // Find out whether any one-bits were shifted off the end. + boolean onesLost = false; + for (int i=magLen-1, j=magLen-nInts; i >= j && !onesLost; i--) + onesLost = (mag[i] != 0); + if (!onesLost && nBits != 0) + onesLost = (mag[magLen - nInts - 1] << (32 - nBits) != 0); + + if (onesLost) + newMag = javaIncrement(newMag); + } + + return new BigInteger(newMag, signum); + } + + @ForceInline + @IntrinsicCandidate + private static void shiftRightImplWorker(int[] newArr, int[] oldArr, int newIdx, int shiftCount, int numIter) { + int shiftCountLeft = 32 - shiftCount; + int idx = numIter; + int nidx = (newIdx == 0) ? numIter - 1 : numIter; + while (nidx >= newIdx) { + newArr[nidx--] = (oldArr[idx--] >>> shiftCount) | (oldArr[idx] << shiftCountLeft); + } + } + + int[] javaIncrement(int[] val) { + int lastSum = 0; + for (int i=val.length-1; i >= 0 && lastSum == 0; i--) + lastSum = (val[i] += 1); + if (lastSum == 0) { + val = new int[val.length+1]; + val[0] = 1; + } + return val; + } + + // Bitwise Operations + + /** + * Returns a BigInteger whose value is {@code (this & val)}. (This + * method returns a negative BigInteger if and only if this and val are + * both negative.) + * + * @param val value to be AND'ed with this BigInteger. + * @return {@code this & val} + */ + public BigInteger and(BigInteger val) { + int[] result = new int[Math.max(intLength(), val.intLength())]; + for (int i=0; i < result.length; i++) + result[i] = (getInt(result.length-i-1) + & val.getInt(result.length-i-1)); + + return valueOf(result); + } + + /** + * Returns a BigInteger whose value is {@code (this | val)}. (This method + * returns a negative BigInteger if and only if either this or val is + * negative.) + * + * @param val value to be OR'ed with this BigInteger. + * @return {@code this | val} + */ + public BigInteger or(BigInteger val) { + int[] result = new int[Math.max(intLength(), val.intLength())]; + for (int i=0; i < result.length; i++) + result[i] = (getInt(result.length-i-1) + | val.getInt(result.length-i-1)); + + return valueOf(result); + } + + /** + * Returns a BigInteger whose value is {@code (this ^ val)}. (This method + * returns a negative BigInteger if and only if exactly one of this and + * val are negative.) + * + * @param val value to be XOR'ed with this BigInteger. + * @return {@code this ^ val} + */ + public BigInteger xor(BigInteger val) { + int[] result = new int[Math.max(intLength(), val.intLength())]; + for (int i=0; i < result.length; i++) + result[i] = (getInt(result.length-i-1) + ^ val.getInt(result.length-i-1)); + + return valueOf(result); + } + + /** + * Returns a BigInteger whose value is {@code (~this)}. (This method + * returns a negative value if and only if this BigInteger is + * non-negative.) + * + * @return {@code ~this} + */ + public BigInteger not() { + int[] result = new int[intLength()]; + for (int i=0; i < result.length; i++) + result[i] = ~getInt(result.length-i-1); + + return valueOf(result); + } + + /** + * Returns a BigInteger whose value is {@code (this & ~val)}. This + * method, which is equivalent to {@code and(val.not())}, is provided as + * a convenience for masking operations. (This method returns a negative + * BigInteger if and only if {@code this} is negative and {@code val} is + * positive.) + * + * @param val value to be complemented and AND'ed with this BigInteger. + * @return {@code this & ~val} + */ + public BigInteger andNot(BigInteger val) { + int[] result = new int[Math.max(intLength(), val.intLength())]; + for (int i=0; i < result.length; i++) + result[i] = (getInt(result.length-i-1) + & ~val.getInt(result.length-i-1)); + + return valueOf(result); + } + + + // Single Bit Operations + + /** + * Returns {@code true} if and only if the designated bit is set. + * (Computes {@code ((this & (1<>> 5) & (1 << (n & 31))) != 0; + } + + /** + * Returns a BigInteger whose value is equivalent to this BigInteger + * with the designated bit set. (Computes {@code (this | (1<>> 5; + int[] result = new int[Math.max(intLength(), intNum+2)]; + + for (int i=0; i < result.length; i++) + result[result.length-i-1] = getInt(i); + + result[result.length-intNum-1] |= (1 << (n & 31)); + + return valueOf(result); + } + + /** + * Returns a BigInteger whose value is equivalent to this BigInteger + * with the designated bit cleared. + * (Computes {@code (this & ~(1<>> 5; + int[] result = new int[Math.max(intLength(), ((n + 1) >>> 5) + 1)]; + + for (int i=0; i < result.length; i++) + result[result.length-i-1] = getInt(i); + + result[result.length-intNum-1] &= ~(1 << (n & 31)); + + return valueOf(result); + } + + /** + * Returns a BigInteger whose value is equivalent to this BigInteger + * with the designated bit flipped. + * (Computes {@code (this ^ (1<>> 5; + int[] result = new int[Math.max(intLength(), intNum+2)]; + + for (int i=0; i < result.length; i++) + result[result.length-i-1] = getInt(i); + + result[result.length-intNum-1] ^= (1 << (n & 31)); + + return valueOf(result); + } + + /** + * Returns the index of the rightmost (lowest-order) one bit in this + * BigInteger (the number of zero bits to the right of the rightmost + * one bit). Returns -1 if this BigInteger contains no one bits. + * (Computes {@code (this == 0? -1 : log2(this & -this))}.) + * + * @return index of the rightmost one bit in this BigInteger. + */ + public int getLowestSetBit() { + int lsb = lowestSetBitPlusTwo - 2; + if (lsb == -2) { // lowestSetBit not initialized yet + lsb = 0; + if (signum == 0) { + lsb -= 1; + } else { + // Search for lowest order nonzero int + int i,b; + for (i=0; (b = getInt(i)) == 0; i++) + ; + lsb += (i << 5) + Integer.numberOfTrailingZeros(b); + } + lowestSetBitPlusTwo = lsb + 2; + } + return lsb; + } + + + // Miscellaneous Bit Operations + + /** + * Returns the number of bits in the minimal two's-complement + * representation of this BigInteger, excluding a sign bit. + * For positive BigIntegers, this is equivalent to the number of bits in + * the ordinary binary representation. For zero this method returns + * {@code 0}. (Computes {@code (ceil(log2(this < 0 ? -this : this+1)))}.) + * + * @return number of bits in the minimal two's-complement + * representation of this BigInteger, excluding a sign bit. + */ + public int bitLength() { + int n = bitLengthPlusOne - 1; + if (n == -1) { // bitLength not initialized yet + int[] m = mag; + int len = m.length; + if (len == 0) { + n = 0; // offset by one to initialize + } else { + // Calculate the bit length of the magnitude + int magBitLength = ((len - 1) << 5) + bitLengthForInt(mag[0]); + if (signum < 0) { + // Check if magnitude is a power of two + boolean pow2 = (Integer.bitCount(mag[0]) == 1); + for (int i=1; i< len && pow2; i++) + pow2 = (mag[i] == 0); + + n = (pow2 ? magBitLength - 1 : magBitLength); + } else { + n = magBitLength; + } + } + bitLengthPlusOne = n + 1; + } + return n; + } + + /** + * Returns the number of bits in the two's complement representation + * of this BigInteger that differ from its sign bit. This method is + * useful when implementing bit-vector style sets atop BigIntegers. + * + * @return number of bits in the two's complement representation + * of this BigInteger that differ from its sign bit. + */ + public int bitCount() { + int bc = bitCountPlusOne - 1; + if (bc == -1) { // bitCount not initialized yet + bc = 0; // offset by one to initialize + // Count the bits in the magnitude + for (int i=0; i < mag.length; i++) + bc += Integer.bitCount(mag[i]); + if (signum < 0) { + // Count the trailing zeros in the magnitude + int magTrailingZeroCount = 0, j; + for (j=mag.length-1; mag[j] == 0; j--) + magTrailingZeroCount += 32; + magTrailingZeroCount += Integer.numberOfTrailingZeros(mag[j]); + bc += magTrailingZeroCount - 1; + } + bitCountPlusOne = bc + 1; + } + return bc; + } + + // Primality Testing + + /** + * Returns {@code true} if this BigInteger is probably prime, + * {@code false} if it's definitely composite. If + * {@code certainty} is ≤ 0, {@code true} is + * returned. + * + * @param certainty a measure of the uncertainty that the caller is + * willing to tolerate: if the call returns {@code true} + * the probability that this BigInteger is prime exceeds + * (1 - 1/2{@code certainty}). The execution time of + * this method is proportional to the value of this parameter. + * @return {@code true} if this BigInteger is probably prime, + * {@code false} if it's definitely composite. + * @throws ArithmeticException {@code this} is too large. + * @implNote Due to the nature of the underlying primality test algorithm, + * and depending on the size of {@code this} and {@code certainty}, + * this method could consume a large amount of memory, up to + * exhaustion of available heap space, or could run for a long time. + */ + public boolean isProbablePrime(int certainty) { + if (certainty <= 0) + return true; + BigInteger w = this.abs(); + if (w.equals(TWO)) + return true; + if (!w.testBit(0) || w.equals(ONE)) + return false; + if (w.bitLength() > PRIME_SEARCH_BIT_LENGTH_LIMIT + 1) { + throw new ArithmeticException("Primality test implementation restriction on bitLength"); + } + return w.primeToCertainty(certainty, null); + } + + // Comparison Operations + + /** + * Compares this BigInteger with the specified BigInteger. This + * method is provided in preference to individual methods for each + * of the six boolean comparison operators ({@literal <}, ==, + * {@literal >}, {@literal >=}, !=, {@literal <=}). The suggested + * idiom for performing these comparisons is: {@code + * (x.compareTo(y)} <op> {@code 0)}, where + * <op> is one of the six comparison operators. + * + * @param val BigInteger to which this BigInteger is to be compared. + * @return -1, 0 or 1 as this BigInteger is numerically less than, equal + * to, or greater than {@code val}. + */ + public int compareTo(BigInteger val) { + if (signum == val.signum) { + return switch (signum) { + case 1 -> compareMagnitude(val); + case -1 -> val.compareMagnitude(this); + default -> 0; + }; + } + return signum > val.signum ? 1 : -1; + } + + /** + * Compares the magnitude array of this BigInteger with the specified + * BigInteger's. This is the version of compareTo ignoring sign. + * + * @param val BigInteger whose magnitude array to be compared. + * @return -1, 0 or 1 as this magnitude array is less than, equal to or + * greater than the magnitude array for the specified BigInteger's. + */ + final int compareMagnitude(BigInteger val) { + int[] m1 = mag; + int len1 = m1.length; + int[] m2 = val.mag; + int len2 = m2.length; + if (len1 < len2) + return -1; + if (len1 > len2) + return 1; + for (int i = 0; i < len1; i++) { + int a = m1[i]; + int b = m2[i]; + if (a != b) + return ((a & LONG_MASK) < (b & LONG_MASK)) ? -1 : 1; + } + return 0; + } + + /** + * Version of compareMagnitude that compares magnitude with long value. + * val can't be Long.MIN_VALUE. + */ + final int compareMagnitude(long val) { + assert val != Long.MIN_VALUE; + int[] m1 = mag; + int len = m1.length; + if (len > 2) { + return 1; + } + if (val < 0) { + val = -val; + } + int highWord = (int)(val >>> 32); + if (highWord == 0) { + if (len < 1) + return -1; + if (len > 1) + return 1; + int a = m1[0]; + int b = (int)val; + if (a != b) { + return ((a & LONG_MASK) < (b & LONG_MASK))? -1 : 1; + } + return 0; + } else { + if (len < 2) + return -1; + int a = m1[0]; + int b = highWord; + if (a != b) { + return ((a & LONG_MASK) < (b & LONG_MASK))? -1 : 1; + } + a = m1[1]; + b = (int)val; + if (a != b) { + return ((a & LONG_MASK) < (b & LONG_MASK))? -1 : 1; + } + return 0; + } + } + + /** + * Compares this BigInteger with the specified Object for equality. + * + * @param x Object to which this BigInteger is to be compared. + * @return {@code true} if and only if the specified Object is a + * BigInteger whose value is numerically equal to this BigInteger. + */ + public boolean equals(Object x) { + // This test is just an optimization, which may or may not help + if (x == this) + return true; + + if (!(x instanceof BigInteger xInt)) + return false; + + if (xInt.signum != signum) + return false; + + int[] m = mag; + int len = m.length; + int[] xm = xInt.mag; + if (len != xm.length) + return false; + + for (int i = 0; i < len; i++) + if (xm[i] != m[i]) + return false; + + return true; + } + + /** + * Returns the minimum of this BigInteger and {@code val}. + * + * @param val value with which the minimum is to be computed. + * @return the BigInteger whose value is the lesser of this BigInteger and + * {@code val}. If they are equal, either may be returned. + */ + public BigInteger min(BigInteger val) { + return (compareTo(val) < 0 ? this : val); + } + + /** + * Returns the maximum of this BigInteger and {@code val}. + * + * @param val value with which the maximum is to be computed. + * @return the BigInteger whose value is the greater of this and + * {@code val}. If they are equal, either may be returned. + */ + public BigInteger max(BigInteger val) { + return (compareTo(val) > 0 ? this : val); + } + + + // Hash Function + + /** + * Returns the hash code for this BigInteger. + * + * @return hash code for this BigInteger. + */ + public int hashCode() { + int hashCode = 0; + + for (int i=0; i < mag.length; i++) + hashCode = (int)(31*hashCode + (mag[i] & LONG_MASK)); + + return hashCode * signum; + } + + /** + * Returns the String representation of this BigInteger in the + * given radix. If the radix is outside the range from {@link + * Character#MIN_RADIX} to {@link Character#MAX_RADIX} inclusive, + * it will default to 10 (as is the case for + * {@code Integer.toString}). The digit-to-character mapping + * provided by {@code Character.forDigit} is used, and a minus + * sign is prepended if appropriate. (This representation is + * compatible with the {@link #BigInteger(String, int) (String, + * int)} constructor.) + * + * @param radix radix of the String representation. + * @return String representation of this BigInteger in the given radix. + * @see Integer#toString + * @see Character#forDigit + * @see #BigInteger(java.lang.String, int) + */ + public String toString(int radix) { + if (signum == 0) + return "0"; + if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) + radix = 10; + + BigInteger abs = this.abs(); + + // Ensure buffer capacity sufficient to contain string representation + // floor(bitLength*log(2)/log(radix)) + 1 + // plus an additional character for the sign if negative. + int b = abs.bitLength(); + int numChars = (int)(Math.floor(b*LOG_TWO/logCache[radix]) + 1) + + (signum < 0 ? 1 : 0); + StringBuilder sb = new StringBuilder(numChars); + + if (signum < 0) { + sb.append('-'); + } + + // Use recursive toString. + toString(abs, sb, radix, 0); + + return sb.toString(); + } + + /** + * If {@code numZeros > 0}, appends that many zeros to the + * specified StringBuilder; otherwise, does nothing. + * + * @param buf The StringBuilder that will be appended to. + * @param numZeros The number of zeros to append. + */ + private static void padWithZeros(StringBuilder buf, int numZeros) { + while (numZeros >= NUM_ZEROS) { + buf.append(ZEROS); + numZeros -= NUM_ZEROS; + } + if (numZeros > 0) { + buf.append(ZEROS, 0, numZeros); + } + } + + /** + * This method is used to perform toString when arguments are small. + * The value must be non-negative. If {@code digits <= 0} no padding + * (pre-pending with zeros) will be effected. + * + * @param radix The base to convert to. + * @param buf The StringBuilder that will be appended to in place. + * @param digits The minimum number of digits to pad to. + */ + private void smallToString(int radix, StringBuilder buf, int digits) { + assert signum >= 0; + + if (signum == 0) { + padWithZeros(buf, digits); + return; + } + + // Compute upper bound on number of digit groups and allocate space + int maxNumDigitGroups = (4*mag.length + 6)/7; + long[] digitGroups = new long[maxNumDigitGroups]; + + // Translate number to string, a digit group at a time + BigInteger tmp = this; + int numGroups = 0; + while (tmp.signum != 0) { + BigInteger d = longRadix[radix]; + + MutableBigInteger q = new MutableBigInteger(), + a = new MutableBigInteger(tmp.mag), + b = new MutableBigInteger(d.mag); + MutableBigInteger r = a.divide(b, q); + BigInteger q2 = q.toBigInteger(tmp.signum * d.signum); + BigInteger r2 = r.toBigInteger(tmp.signum * d.signum); + + digitGroups[numGroups++] = r2.longValue(); + tmp = q2; + } + + // Get string version of first digit group + String s = Long.toString(digitGroups[numGroups-1], radix); + + // Pad with internal zeros if necessary. + padWithZeros(buf, digits - (s.length() + + (numGroups - 1)*digitsPerLong[radix])); + + // Put first digit group into result buffer + buf.append(s); + + // Append remaining digit groups each padded with leading zeros + for (int i=numGroups-2; i >= 0; i--) { + // Prepend (any) leading zeros for this digit group + s = Long.toString(digitGroups[i], radix); + int numLeadingZeros = digitsPerLong[radix] - s.length(); + if (numLeadingZeros != 0) { + buf.append(ZEROS, 0, numLeadingZeros); + } + buf.append(s); + } + } + + /** + * Converts the specified BigInteger to a string and appends to + * {@code sb}. This implements the recursive Schoenhage algorithm + * for base conversions. This method can only be called for non-negative + * numbers. + *

+ * See Knuth, Donald, _The Art of Computer Programming_, Vol. 2, + * Answers to Exercises (4.4) Question 14. + * + * @param u The number to convert to a string. + * @param sb The StringBuilder that will be appended to in place. + * @param radix The base to convert to. + * @param digits The minimum number of digits to pad to. + */ + private static void toString(BigInteger u, StringBuilder sb, + int radix, int digits) { + assert u.signum() >= 0; + + // If we're smaller than a certain threshold, use the smallToString + // method, padding with leading zeroes when necessary unless we're + // at the beginning of the string or digits <= 0. As u.signum() >= 0, + // smallToString() will not prepend a negative sign. + if (u.mag.length <= SCHOENHAGE_BASE_CONVERSION_THRESHOLD) { + u.smallToString(radix, sb, digits); + return; + } + + // Calculate a value for n in the equation radix^(2^n) = u + // and subtract 1 from that value. This is used to find the + // cache index that contains the best value to divide u. + int b = u.bitLength(); + int n = (int) Math.round(Math.log(b * LOG_TWO / logCache[radix]) / + LOG_TWO - 1.0); + + BigInteger v = getRadixConversionCache(radix, n); + BigInteger[] results; + results = u.divideAndRemainder(v); + + int expectedDigits = 1 << n; + + // Now recursively build the two halves of each number. + toString(results[0], sb, radix, digits - expectedDigits); + toString(results[1], sb, radix, expectedDigits); + } + + /** + * Returns the value radix^(2^exponent) from the cache. + * If this value doesn't already exist in the cache, it is added. + *

+ * This could be changed to a more complicated caching method using + * {@code Future}. + */ + private static BigInteger getRadixConversionCache(int radix, int exponent) { + BigInteger[] cacheLine = powerCache[radix]; // volatile read + if (exponent < cacheLine.length) { + return cacheLine[exponent]; + } + + int oldLength = cacheLine.length; + cacheLine = Arrays.copyOf(cacheLine, exponent + 1); + for (int i = oldLength; i <= exponent; i++) { + cacheLine[i] = cacheLine[i - 1].pow(2); + } + + BigInteger[][] pc = powerCache; // volatile read again + if (exponent >= pc[radix].length) { + pc = pc.clone(); + pc[radix] = cacheLine; + powerCache = pc; // volatile write, publish + } + return cacheLine[exponent]; + } + + /* Size of ZEROS string. */ + private static int NUM_ZEROS = 63; + + /* ZEROS is a string of NUM_ZEROS consecutive zeros. */ + private static final String ZEROS = "0".repeat(NUM_ZEROS); + + /** + * Returns the decimal String representation of this BigInteger. + * The digit-to-character mapping provided by + * {@code Character.forDigit} is used, and a minus sign is + * prepended if appropriate. (This representation is compatible + * with the {@link #BigInteger(String) (String)} constructor, and + * allows for String concatenation with Java's + operator.) + * + * @return decimal String representation of this BigInteger. + * @see Character#forDigit + * @see #BigInteger(java.lang.String) + */ + public String toString() { + return toString(10); + } + + /** + * Returns a byte array containing the two's-complement + * representation of this BigInteger. The byte array will be in + * big-endian byte-order: the most significant byte is in + * the zeroth element. The array will contain the minimum number + * of bytes required to represent this BigInteger, including at + * least one sign bit, which is {@code (ceil((this.bitLength() + + * 1)/8))}. (This representation is compatible with the + * {@link #BigInteger(byte[]) (byte[])} constructor.) + * + * @return a byte array containing the two's-complement representation of + * this BigInteger. + * @see #BigInteger(byte[]) + */ + public byte[] toByteArray() { + int byteLen = bitLength()/8 + 1; + byte[] byteArray = new byte[byteLen]; + + for (int i=byteLen-1, bytesCopied=4, nextInt=0, intIndex=0; i >= 0; i--) { + if (bytesCopied == 4) { + nextInt = getInt(intIndex++); + bytesCopied = 1; + } else { + nextInt >>>= 8; + bytesCopied++; + } + byteArray[i] = (byte)nextInt; + } + return byteArray; + } + + /** + * Converts this BigInteger to an {@code int}. This + * conversion is analogous to a + * narrowing primitive conversion from {@code long} to + * {@code int} as defined in + * The Java Language Specification: + * if this BigInteger is too big to fit in an + * {@code int}, only the low-order 32 bits are returned. + * Note that this conversion can lose information about the + * overall magnitude of the BigInteger value as well as return a + * result with the opposite sign. + * + * @return this BigInteger converted to an {@code int}. + * @see #intValueExact() + * @jls 5.1.3 Narrowing Primitive Conversion + */ + public int intValue() { + int result = 0; + result = getInt(0); + return result; + } + + /** + * Converts this BigInteger to a {@code long}. This + * conversion is analogous to a + * narrowing primitive conversion from {@code long} to + * {@code int} as defined in + * The Java Language Specification: + * if this BigInteger is too big to fit in a + * {@code long}, only the low-order 64 bits are returned. + * Note that this conversion can lose information about the + * overall magnitude of the BigInteger value as well as return a + * result with the opposite sign. + * + * @return this BigInteger converted to a {@code long}. + * @see #longValueExact() + * @jls 5.1.3 Narrowing Primitive Conversion + */ + public long longValue() { + long result = 0; + + for (int i=1; i >= 0; i--) + result = (result << 32) + (getInt(i) & LONG_MASK); + return result; + } + + /** + * Converts this BigInteger to a {@code float}. This + * conversion is similar to the + * narrowing primitive conversion from {@code double} to + * {@code float} as defined in + * The Java Language Specification: + * if this BigInteger has too great a magnitude + * to represent as a {@code float}, it will be converted to + * {@link Float#NEGATIVE_INFINITY} or {@link + * Float#POSITIVE_INFINITY} as appropriate. Note that even when + * the return value is finite, this conversion can lose + * information about the precision of the BigInteger value. + * + * @return this BigInteger converted to a {@code float}. + * @jls 5.1.3 Narrowing Primitive Conversion + */ + public float floatValue() { + if (signum == 0) { + return 0.0f; + } + + int exponent = ((mag.length - 1) << 5) + bitLengthForInt(mag[0]) - 1; + + // exponent == floor(log2(abs(this))) + if (exponent < Long.SIZE - 1) { + return longValue(); + } else if (exponent > Float.MAX_EXPONENT) { + return signum > 0 ? Float.POSITIVE_INFINITY : Float.NEGATIVE_INFINITY; + } + + /* + * We need the top SIGNIFICAND_WIDTH bits, including the "implicit" + * one bit. To make rounding easier, we pick out the top + * SIGNIFICAND_WIDTH + 1 bits, so we have one to help us round up or + * down. twiceSignifFloor will contain the top SIGNIFICAND_WIDTH + 1 + * bits, and signifFloor the top SIGNIFICAND_WIDTH. + * + * It helps to consider the real number signif = abs(this) * + * 2^(SIGNIFICAND_WIDTH - 1 - exponent). + */ + int shift = exponent - FloatConsts.SIGNIFICAND_WIDTH; + + int twiceSignifFloor; + // twiceSignifFloor will be == abs().shiftRight(shift).intValue() + // We do the shift into an int directly to improve performance. + + int nBits = shift & 0x1f; + int nBits2 = 32 - nBits; + + if (nBits == 0) { + twiceSignifFloor = mag[0]; + } else { + twiceSignifFloor = mag[0] >>> nBits; + if (twiceSignifFloor == 0) { + twiceSignifFloor = (mag[0] << nBits2) | (mag[1] >>> nBits); + } + } + + int signifFloor = twiceSignifFloor >> 1; + signifFloor &= FloatConsts.SIGNIF_BIT_MASK; // remove the implied bit + + /* + * We round up if either the fractional part of signif is strictly + * greater than 0.5 (which is true if the 0.5 bit is set and any lower + * bit is set), or if the fractional part of signif is >= 0.5 and + * signifFloor is odd (which is true if both the 0.5 bit and the 1 bit + * are set). This is equivalent to the desired HALF_EVEN rounding. + */ + boolean increment = (twiceSignifFloor & 1) != 0 + && ((signifFloor & 1) != 0 || abs().getLowestSetBit() < shift); + int signifRounded = increment ? signifFloor + 1 : signifFloor; + int bits = ((exponent + FloatConsts.EXP_BIAS)) + << (FloatConsts.SIGNIFICAND_WIDTH - 1); + bits += signifRounded; + /* + * If signifRounded == 2^24, we'd need to set all of the significand + * bits to zero and add 1 to the exponent. This is exactly the behavior + * we get from just adding signifRounded to bits directly. If the + * exponent is Float.MAX_EXPONENT, we round up (correctly) to + * Float.POSITIVE_INFINITY. + */ + bits |= signum & FloatConsts.SIGN_BIT_MASK; + return Float.intBitsToFloat(bits); + } + + /** + * Converts this BigInteger to a {@code double}. This + * conversion is similar to the + * narrowing primitive conversion from {@code double} to + * {@code float} as defined in + * The Java Language Specification: + * if this BigInteger has too great a magnitude + * to represent as a {@code double}, it will be converted to + * {@link Double#NEGATIVE_INFINITY} or {@link + * Double#POSITIVE_INFINITY} as appropriate. Note that even when + * the return value is finite, this conversion can lose + * information about the precision of the BigInteger value. + * + * @return this BigInteger converted to a {@code double}. + * @jls 5.1.3 Narrowing Primitive Conversion + */ + public double doubleValue() { + if (signum == 0) { + return 0.0; + } + + int exponent = ((mag.length - 1) << 5) + bitLengthForInt(mag[0]) - 1; + + // exponent == floor(log2(abs(this))Double) + if (exponent < Long.SIZE - 1) { + return longValue(); + } else if (exponent > Double.MAX_EXPONENT) { + return signum > 0 ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY; + } + + /* + * We need the top SIGNIFICAND_WIDTH bits, including the "implicit" + * one bit. To make rounding easier, we pick out the top + * SIGNIFICAND_WIDTH + 1 bits, so we have one to help us round up or + * down. twiceSignifFloor will contain the top SIGNIFICAND_WIDTH + 1 + * bits, and signifFloor the top SIGNIFICAND_WIDTH. + * + * It helps to consider the real number signif = abs(this) * + * 2^(SIGNIFICAND_WIDTH - 1 - exponent). + */ + int shift = exponent - DoubleConsts.SIGNIFICAND_WIDTH; + + long twiceSignifFloor; + // twiceSignifFloor will be == abs().shiftRight(shift).longValue() + // We do the shift into a long directly to improve performance. + + int nBits = shift & 0x1f; + int nBits2 = 32 - nBits; + + int highBits; + int lowBits; + if (nBits == 0) { + highBits = mag[0]; + lowBits = mag[1]; + } else { + highBits = mag[0] >>> nBits; + lowBits = (mag[0] << nBits2) | (mag[1] >>> nBits); + if (highBits == 0) { + highBits = lowBits; + lowBits = (mag[1] << nBits2) | (mag[2] >>> nBits); + } + } + + twiceSignifFloor = ((highBits & LONG_MASK) << 32) + | (lowBits & LONG_MASK); + + long signifFloor = twiceSignifFloor >> 1; + signifFloor &= DoubleConsts.SIGNIF_BIT_MASK; // remove the implied bit + + /* + * We round up if either the fractional part of signif is strictly + * greater than 0.5 (which is true if the 0.5 bit is set and any lower + * bit is set), or if the fractional part of signif is >= 0.5 and + * signifFloor is odd (which is true if both the 0.5 bit and the 1 bit + * are set). This is equivalent to the desired HALF_EVEN rounding. + */ + boolean increment = (twiceSignifFloor & 1) != 0 + && ((signifFloor & 1) != 0 || abs().getLowestSetBit() < shift); + long signifRounded = increment ? signifFloor + 1 : signifFloor; + long bits = (long) ((exponent + DoubleConsts.EXP_BIAS)) + << (DoubleConsts.SIGNIFICAND_WIDTH - 1); + bits += signifRounded; + /* + * If signifRounded == 2^53, we'd need to set all of the significand + * bits to zero and add 1 to the exponent. This is exactly the behavior + * we get from just adding signifRounded to bits directly. If the + * exponent is Double.MAX_EXPONENT, we round up (correctly) to + * Double.POSITIVE_INFINITY. + */ + bits |= signum & DoubleConsts.SIGN_BIT_MASK; + return Double.longBitsToDouble(bits); + } + + /** + * Returns a copy of the input array stripped of any leading zero bytes. + */ + private static int[] stripLeadingZeroInts(int[] val) { + int vlen = val.length; + int keep; + + // Find first nonzero byte + for (keep = 0; keep < vlen && val[keep] == 0; keep++) + ; + return java.util.Arrays.copyOfRange(val, keep, vlen); + } + + /** + * Returns the input array stripped of any leading zero bytes. + * Since the source is trusted the copying may be skipped. + */ + private static int[] trustedStripLeadingZeroInts(int[] val) { + int vlen = val.length; + int keep; + + // Find first nonzero byte + for (keep = 0; keep < vlen && val[keep] == 0; keep++) + ; + return keep == 0 ? val : java.util.Arrays.copyOfRange(val, keep, vlen); + } + + private static int[] stripLeadingZeroBytes(byte[] a, int from, int len) { + return stripLeadingZeroBytes(Integer.MIN_VALUE, a, from, len); + } + + /* + * Returns a copy of the input array stripped of any leading zero bytes. + * The returned array is either empty, or its 0-th element is non-zero, + * meeting the "minimal" requirement for field mag (see comment on mag). + * + * The range [from, from + len) must be well-formed w.r.t. array a. + * + * b < -128 means that a[from] has not yet been read. + * Otherwise, b must be a[from], have been read only once before invoking + * this method, and len > 0 must hold. + */ + private static int[] stripLeadingZeroBytes(int b, byte[] a, int from, int len) { + /* + * Except for the first byte, each read access to the input array a + * is of the form a[from++]. + * The index from is never otherwise altered, except right below, + * and only increases in steps of 1, always up to index to. + * Hence, each byte in the array is read exactly once, from lower to + * higher indices (from most to least significant byte). + */ + if (len == 0) { + return ZERO.mag; + } + int to = from + len; + if (b < -128) { + b = a[from]; + } + /* Either way, a[from] has now been read exactly once, skip to next. */ + ++from; + /* + * Set up the shortest int[] for the sequence of the bytes + * b, a[from+1], ..., a[to-1] (len > 0) + * Shortest means first skipping leading zeros. + */ + for (; b == 0 && from < to; b = a[from++]) + ; //empty body + if (b == 0) { + /* Here, from == to as well. All bytes are zeros. */ + return ZERO.mag; + } + /* + * Allocate just enough ints to hold (to - from + 1) bytes, that is + * ((to - from + 1) + 3) / 4 = (to - from) / 4 + 1 + */ + int[] res = new int[((to - from) >> 2) + 1]; + /* + * A "digit" is a group of 4 adjacent bytes aligned w.r.t. index to. + * (Implied 0 bytes are prepended as needed.) + * b is the most significant byte not 0. + * Digit d0 spans the range of indices that includes current (from - 1). + */ + int d0 = b & 0xFF; + while (((to - from) & 0x3) != 0) { + d0 = d0 << 8 | a[from++] & 0xFF; + } + res[0] = d0; + /* + * Prepare the remaining digits. + * (to - from) is a multiple of 4, so prepare an int for every 4 bytes. + * This is a candidate for Unsafe.copy[Swap]Memory(). + */ + int i = 1; + while (from < to) { + res[i++] = a[from++] << 24 | (a[from++] & 0xFF) << 16 + | (a[from++] & 0xFF) << 8 | (a[from++] & 0xFF); + } + return res; + } + + /* + * Takes an array a representing a negative 2's-complement number and + * returns the minimal (no leading zero bytes) unsigned whose value is -a. + * + * len > 0 must hold. + * The range [from, from + len) must be well-formed w.r.t. array a. + * b is assumed to be the result of reading a[from] and to meet b < 0. + */ + private static int[] makePositive(int b, byte[] a, int from, int len) { + /* + * By assumption, b == a[from] < 0 and len > 0. + * + * First collect the bytes into the resulting array res. + * Then convert res to two's complement. + * + * Except for b == a[from], each read access to the input array a + * is of the form a[from++]. + * The index from is never otherwise altered, except right below, + * and only increases in steps of 1, always up to index to. + * Hence, each byte in the array is read exactly once, from lower to + * higher indices (from most to least significant byte). + */ + int to = from + len; + /* b == a[from] has been read exactly once, skip to next index. */ + ++from; + /* Skip leading -1 bytes. */ + for (; b == -1 && from < to; b = a[from++]) + ; //empty body + /* + * A "digit" is a group of 4 adjacent bytes aligned w.r.t. index to. + * b is the most significant byte not -1, or -1 only if from == to. + * Digit d0 spans the range of indices that includes current (from - 1). + * (Implied -1 bytes are prepended to array a as needed.) + * It usually corresponds to res[0], except for the special case below. + */ + int d0 = -1 << 8 | b & 0xFF; + while (((to - from) & 0x3) != 0) { + d0 = d0 << 8 | (b = a[from++]) & 0xFF; + } + int f = from; // keeps the current from for sizing purposes later + /* Skip zeros adjacent to d0, if at all. */ + for (; b == 0 && from < to; b = a[from++]) + ; //empty body + /* + * b is the most significant non-zero byte at or after (f - 1), + * or 0 only if from == to. + * Digit d spans the range of indices that includes (f - 1). + */ + int d = b & 0xFF; + while (((to - from) & 0x3) != 0) { + d = d << 8 | a[from++] & 0xFF; + } + /* + * If the situation here is like this: + * index: f to == from + * ..., -1,-1, 0,0,0,0, 0,0,0,0, ..., 0,0,0,0 + * digit: d0 d + * then, as shown, the number of zeros is a positive multiple of 4. + * The array res needs a minimal length of (1 + 1 + (to - f) / 4) + * to accommodate the two's complement, including a leading 1. + * In any other case, there is at least one byte that is non-zero. + * The array for the two's complement has length (0 + 1 + (to - f) / 4). + * c is 1, resp., 0 for the two situations. + */ + int c = (to - from | d0 | d) == 0 ? 1 : 0; + int[] res = new int[c + 1 + ((to - f) >> 2)]; + res[0] = c == 0 ? d0 : -1; + int i = res.length - ((to - from) >> 2); + if (i > 1) { + res[i - 1] = d; + } + /* + * Prepare the remaining digits. + * (to - from) is a multiple of 4, so prepare an int for every 4 bytes. + * This is a candidate for Unsafe.copy[Swap]Memory(). + */ + while (from < to) { + res[i++] = a[from++] << 24 | (a[from++] & 0xFF) << 16 + | (a[from++] & 0xFF) << 8 | (a[from++] & 0xFF); + } + /* Convert to two's complement. Here, i == res.length */ + while (--i >= 0 && res[i] == 0) + ; // empty body + res[i] = -res[i]; + while (--i >= 0) { + res[i] = ~res[i]; + } + return res; + } + + /** + * Takes an array a representing a negative 2's-complement number and + * returns the minimal (no leading zero ints) unsigned whose value is -a. + */ + private static int[] makePositive(int[] a) { + int keep, j; + + // Find first non-sign (0xffffffff) int of input + for (keep=0; keep < a.length && a[keep] == -1; keep++) + ; + + /* Allocate output array. If all non-sign ints are 0x00, we must + * allocate space for one extra output int. */ + for (j=keep; j < a.length && a[j] == 0; j++) + ; + int extraInt = (j == a.length ? 1 : 0); + int result[] = new int[a.length - keep + extraInt]; + + /* Copy one's complement of input into output, leaving extra + * int (if it exists) == 0x00 */ + for (int i = keep; i < a.length; i++) + result[i - keep + extraInt] = ~a[i]; + + // Add one to one's complement to generate two's complement + for (int i=result.length-1; ++result[i] == 0; i--) + ; + + return result; + } + + /* + * The following two arrays are used for fast String conversions. Both + * are indexed by radix. The first is the number of digits of the given + * radix that can fit in a Java long without "going negative", i.e., the + * highest integer n such that radix**n < 2**63. The second is the + * "long radix" that tears each number into "long digits", each of which + * consists of the number of digits in the corresponding element in + * digitsPerLong (longRadix[i] = i**digitPerLong[i]). Both arrays have + * nonsense values in their 0 and 1 elements, as radixes 0 and 1 are not + * used. + */ + private static int digitsPerLong[] = {0, 0, + 62, 39, 31, 27, 24, 22, 20, 19, 18, 18, 17, 17, 16, 16, 15, 15, 15, 14, + 14, 14, 14, 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 12}; + + private static BigInteger longRadix[] = {null, null, + valueOf(0x4000000000000000L), valueOf(0x383d9170b85ff80bL), + valueOf(0x4000000000000000L), valueOf(0x6765c793fa10079dL), + valueOf(0x41c21cb8e1000000L), valueOf(0x3642798750226111L), + valueOf(0x1000000000000000L), valueOf(0x12bf307ae81ffd59L), + valueOf( 0xde0b6b3a7640000L), valueOf(0x4d28cb56c33fa539L), + valueOf(0x1eca170c00000000L), valueOf(0x780c7372621bd74dL), + valueOf(0x1e39a5057d810000L), valueOf(0x5b27ac993df97701L), + valueOf(0x1000000000000000L), valueOf(0x27b95e997e21d9f1L), + valueOf(0x5da0e1e53c5c8000L), valueOf( 0xb16a458ef403f19L), + valueOf(0x16bcc41e90000000L), valueOf(0x2d04b7fdd9c0ef49L), + valueOf(0x5658597bcaa24000L), valueOf( 0x6feb266931a75b7L), + valueOf( 0xc29e98000000000L), valueOf(0x14adf4b7320334b9L), + valueOf(0x226ed36478bfa000L), valueOf(0x383d9170b85ff80bL), + valueOf(0x5a3c23e39c000000L), valueOf( 0x4e900abb53e6b71L), + valueOf( 0x7600ec618141000L), valueOf( 0xaee5720ee830681L), + valueOf(0x1000000000000000L), valueOf(0x172588ad4f5f0981L), + valueOf(0x211e44f7d02c1000L), valueOf(0x2ee56725f06e5c71L), + valueOf(0x41c21cb8e1000000L)}; + + /* + * These two arrays are the integer analogue of above. + */ + private static int digitsPerInt[] = {0, 0, 30, 19, 15, 13, 11, + 11, 10, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5}; + + private static int intRadix[] = {0, 0, + 0x40000000, 0x4546b3db, 0x40000000, 0x48c27395, 0x159fd800, + 0x75db9c97, 0x40000000, 0x17179149, 0x3b9aca00, 0xcc6db61, + 0x19a10000, 0x309f1021, 0x57f6c100, 0xa2f1b6f, 0x10000000, + 0x18754571, 0x247dbc80, 0x3547667b, 0x4c4b4000, 0x6b5a6e1d, + 0x6c20a40, 0x8d2d931, 0xb640000, 0xe8d4a51, 0x1269ae40, + 0x17179149, 0x1cb91000, 0x23744899, 0x2b73a840, 0x34e63b41, + 0x40000000, 0x4cfa3cc1, 0x5c13d840, 0x6d91b519, 0x39aa400 + }; + + /** + * These routines provide access to the two's complement representation + * of BigIntegers. + */ + + /** + * Returns the length of the two's complement representation in ints, + * including space for at least one sign bit. + */ + private int intLength() { + return (bitLength() >>> 5) + 1; + } + + /* Returns sign bit */ + private int signBit() { + return signum < 0 ? 1 : 0; + } + + /* Returns an int of sign bits */ + private int signInt() { + return signum < 0 ? -1 : 0; + } + + /** + * Returns the specified int of the little-endian two's complement + * representation (int 0 is the least significant). The int number can + * be arbitrarily high (values are logically preceded by infinitely many + * sign ints). + */ + private int getInt(int n) { + if (n < 0) + return 0; + if (n >= mag.length) + return signInt(); + + int magInt = mag[mag.length-n-1]; + + return (signum >= 0 ? magInt : + (n <= firstNonzeroIntNum() ? -magInt : ~magInt)); + } + + /** + * Returns the index of the int that contains the first nonzero int in the + * little-endian binary representation of the magnitude (int 0 is the + * least significant). If the magnitude is zero, return value is undefined. + * + *

Note: never used for a BigInteger with a magnitude of zero. + * @see #getInt + */ + private int firstNonzeroIntNum() { + int fn = firstNonzeroIntNumPlusTwo - 2; + if (fn == -2) { // firstNonzeroIntNum not initialized yet + // Search for the first nonzero int + int i; + int mlen = mag.length; + for (i = mlen - 1; i >= 0 && mag[i] == 0; i--) + ; + fn = mlen - i - 1; + firstNonzeroIntNumPlusTwo = fn + 2; // offset by two to initialize + } + return fn; + } + + /** use serialVersionUID from JDK 1.1. for interoperability */ + @java.io.Serial + private static final long serialVersionUID = -8287574255936472291L; + + /** + * Serializable fields for BigInteger. + * + * @serialField signum int + * signum of this BigInteger + * @serialField magnitude byte[] + * magnitude array of this BigInteger + * @serialField bitCount int + * appears in the serialized form for backward compatibility + * @serialField bitLength int + * appears in the serialized form for backward compatibility + * @serialField firstNonzeroByteNum int + * appears in the serialized form for backward compatibility + * @serialField lowestSetBit int + * appears in the serialized form for backward compatibility + */ + @java.io.Serial + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("signum", Integer.TYPE), + new ObjectStreamField("magnitude", byte[].class), + new ObjectStreamField("bitCount", Integer.TYPE), + new ObjectStreamField("bitLength", Integer.TYPE), + new ObjectStreamField("firstNonzeroByteNum", Integer.TYPE), + new ObjectStreamField("lowestSetBit", Integer.TYPE) + }; + + /** + * Reconstitute the {@code BigInteger} instance from a stream (that is, + * deserialize it). The magnitude is read in as an array of bytes + * for historical reasons, but it is converted to an array of ints + * and the byte array is discarded. + * Note: + * The current convention is to initialize the cache fields, bitCountPlusOne, + * bitLengthPlusOne and lowestSetBitPlusTwo, to 0 rather than some other + * marker value. Therefore, no explicit action to set these fields needs to + * be taken in readObject because those fields already have a 0 value by + * default since defaultReadObject is not being used. + * + * @param s the stream being read. + * @throws IOException if an I/O error occurs + * @throws ClassNotFoundException if a serialized class cannot be loaded + */ + @java.io.Serial + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + // prepare to read the alternate persistent fields + ObjectInputStream.GetField fields = s.readFields(); + + // Read and validate the alternate persistent fields that we + // care about, signum and magnitude + + // Read and validate signum + int sign = fields.get("signum", -2); + if (sign < -1 || sign > 1) { + String message = "BigInteger: Invalid signum value"; + if (fields.defaulted("signum")) + message = "BigInteger: Signum not present in stream"; + throw new java.io.StreamCorruptedException(message); + } + + // Read and validate magnitude + byte[] magnitude = (byte[])fields.get("magnitude", null); + magnitude = magnitude.clone(); // defensive copy + int[] mag = stripLeadingZeroBytes(magnitude, 0, magnitude.length); + if ((mag.length == 0) != (sign == 0)) { + String message = "BigInteger: signum-magnitude mismatch"; + if (fields.defaulted("magnitude")) + message = "BigInteger: Magnitude not present in stream"; + throw new java.io.StreamCorruptedException(message); + } + + // Equivalent to checkRange() on mag local without assigning + // this.mag field + if (mag.length > MAX_MAG_LENGTH || + (mag.length == MAX_MAG_LENGTH && mag[0] < 0)) { + throw new java.io.StreamCorruptedException("BigInteger: Out of the supported range"); + } + + // Commit final fields via Unsafe + UnsafeHolder.putSignAndMag(this, sign, mag); + } + + /** + * Serialization without data not supported for this class. + */ + @java.io.Serial + private void readObjectNoData() + throws ObjectStreamException { + throw new InvalidObjectException("Deserialized BigInteger objects need data"); + } + + // Support for resetting final fields while deserializing + private static class UnsafeHolder { + private static final jdk.internal.misc.Unsafe unsafe + = jdk.internal.misc.Unsafe.getUnsafe(); + private static final long signumOffset + = unsafe.objectFieldOffset(BigInteger.class, "signum"); + private static final long magOffset + = unsafe.objectFieldOffset(BigInteger.class, "mag"); + + static void putSignAndMag(BigInteger bi, int sign, int[] magnitude) { + unsafe.putInt(bi, signumOffset, sign); + unsafe.putReference(bi, magOffset, magnitude); + } + } + + /** + * Save the {@code BigInteger} instance to a stream. The magnitude of a + * {@code BigInteger} is serialized as a byte array for historical reasons. + * To maintain compatibility with older implementations, the integers + * -1, -1, -2, and -2 are written as the values of the obsolete fields + * {@code bitCount}, {@code bitLength}, {@code lowestSetBit}, and + * {@code firstNonzeroByteNum}, respectively. These values are compatible + * with older implementations, but will be ignored by current + * implementations. + * + * @param s the stream to serialize to. + * @throws IOException if an I/O error occurs + */ + @java.io.Serial + private void writeObject(ObjectOutputStream s) throws IOException { + // set the values of the Serializable fields + ObjectOutputStream.PutField fields = s.putFields(); + fields.put("signum", signum); + fields.put("magnitude", magSerializedForm()); + // The values written for cached fields are compatible with older + // versions, but are ignored in readObject so don't otherwise matter. + fields.put("bitCount", -1); + fields.put("bitLength", -1); + fields.put("lowestSetBit", -2); + fields.put("firstNonzeroByteNum", -2); + + // save them + s.writeFields(); + } + + /** + * Returns the mag array as an array of bytes. + */ + private byte[] magSerializedForm() { + int len = mag.length; + + int bitLen = (len == 0 ? 0 : ((len - 1) << 5) + bitLengthForInt(mag[0])); + int byteLen = (bitLen + 7) >>> 3; + byte[] result = new byte[byteLen]; + + for (int i = byteLen - 1, bytesCopied = 4, intIndex = len - 1, nextInt = 0; + i >= 0; i--) { + if (bytesCopied == 4) { + nextInt = mag[intIndex--]; + bytesCopied = 1; + } else { + nextInt >>>= 8; + bytesCopied++; + } + result[i] = (byte)nextInt; + } + return result; + } + + /** + * Converts this {@code BigInteger} to a {@code long}, checking + * for lost information. If the value of this {@code BigInteger} + * is out of the range of the {@code long} type, then an + * {@code ArithmeticException} is thrown. + * + * @return this {@code BigInteger} converted to a {@code long}. + * @throws ArithmeticException if the value of {@code this} will + * not exactly fit in a {@code long}. + * @see BigInteger#longValue + * @since 1.8 + */ + public long longValueExact() { + if (mag.length <= 2 && bitLength() <= 63) + return longValue(); + else + throw new ArithmeticException("BigInteger out of long range"); + } + + /** + * Converts this {@code BigInteger} to an {@code int}, checking + * for lost information. If the value of this {@code BigInteger} + * is out of the range of the {@code int} type, then an + * {@code ArithmeticException} is thrown. + * + * @return this {@code BigInteger} converted to an {@code int}. + * @throws ArithmeticException if the value of {@code this} will + * not exactly fit in an {@code int}. + * @see BigInteger#intValue + * @since 1.8 + */ + public int intValueExact() { + if (mag.length <= 1 && bitLength() <= 31) + return intValue(); + else + throw new ArithmeticException("BigInteger out of int range"); + } + + /** + * Converts this {@code BigInteger} to a {@code short}, checking + * for lost information. If the value of this {@code BigInteger} + * is out of the range of the {@code short} type, then an + * {@code ArithmeticException} is thrown. + * + * @return this {@code BigInteger} converted to a {@code short}. + * @throws ArithmeticException if the value of {@code this} will + * not exactly fit in a {@code short}. + * @see BigInteger#shortValue + * @since 1.8 + */ + public short shortValueExact() { + if (mag.length <= 1 && bitLength() <= 31) { + int value = intValue(); + if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) + return shortValue(); + } + throw new ArithmeticException("BigInteger out of short range"); + } + + /** + * Converts this {@code BigInteger} to a {@code byte}, checking + * for lost information. If the value of this {@code BigInteger} + * is out of the range of the {@code byte} type, then an + * {@code ArithmeticException} is thrown. + * + * @return this {@code BigInteger} converted to a {@code byte}. + * @throws ArithmeticException if the value of {@code this} will + * not exactly fit in a {@code byte}. + * @see BigInteger#byteValue + * @since 1.8 + */ + public byte byteValueExact() { + if (mag.length <= 1 && bitLength() <= 31) { + int value = intValue(); + if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) + return byteValue(); + } + throw new ArithmeticException("BigInteger out of byte range"); + } +} + + +/* + * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.RecordComponent; +import java.lang.reflect.UndeclaredThrowableException; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PermissionCollection; +import java.security.Permissions; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.security.ProtectionDomain; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import jdk.internal.misc.Unsafe; +import jdk.internal.reflect.CallerSensitive; +import jdk.internal.reflect.Reflection; +import jdk.internal.reflect.ReflectionFactory; +import jdk.internal.access.SharedSecrets; +import jdk.internal.access.JavaSecurityAccess; +import jdk.internal.util.ByteArray; +import sun.reflect.misc.ReflectUtil; + +/** + * Serialization's descriptor for classes. It contains the name and + * serialVersionUID of the class. The ObjectStreamClass for a specific class + * loaded in this Java VM can be found/created using the lookup method. + * + *

The algorithm to compute the SerialVersionUID is described in + * + * Java Object Serialization Specification, Section 4.6, "Stream Unique Identifiers". + * + * @spec serialization/index.html Java Object Serialization Specification + * @author Mike Warres + * @author Roger Riggs + * @see ObjectStreamField + * @see + * Java Object Serialization Specification, Section 4, "Class Descriptors" + * @since 1.1 + */ +public final class ObjectStreamClass implements Serializable { + + /** serialPersistentFields value indicating no serializable fields */ + public static final ObjectStreamField[] NO_FIELDS = + new ObjectStreamField[0]; + + @java.io.Serial + private static final long serialVersionUID = -6120832682080437368L; + /** + * {@code ObjectStreamClass} has no fields for default serialization. + */ + @java.io.Serial + private static final ObjectStreamField[] serialPersistentFields = + NO_FIELDS; + + /** reflection factory for obtaining serialization constructors */ + @SuppressWarnings("removal") + private static final ReflectionFactory reflFactory = + AccessController.doPrivileged( + new ReflectionFactory.GetReflectionFactoryAction()); + + private static class Caches { + /** cache mapping local classes -> descriptors */ + static final ClassCache localDescs = + new ClassCache<>() { + @Override + protected ObjectStreamClass computeValue(Class type) { + return new ObjectStreamClass(type); + } + }; + + /** cache mapping field group/local desc pairs -> field reflectors */ + static final ClassCache> reflectors = + new ClassCache<>() { + @Override + protected Map computeValue(Class type) { + return new ConcurrentHashMap<>(); + } + }; + } + + /** class associated with this descriptor (if any) */ + private Class cl; + /** name of class represented by this descriptor */ + private String name; + /** serialVersionUID of represented class (null if not computed yet) */ + private volatile Long suid; + + /** true if represents dynamic proxy class */ + private boolean isProxy; + /** true if represents enum type */ + private boolean isEnum; + /** true if represents record type */ + private boolean isRecord; + /** true if represented class implements Serializable */ + private boolean serializable; + /** true if represented class implements Externalizable */ + private boolean externalizable; + /** true if desc has data written by class-defined writeObject method */ + private boolean hasWriteObjectData; + /** + * true if desc has externalizable data written in block data format; this + * must be true by default to accommodate ObjectInputStream subclasses which + * override readClassDescriptor() to return class descriptors obtained from + * ObjectStreamClass.lookup() (see 4461737) + */ + private boolean hasBlockExternalData = true; + + /** + * Contains information about InvalidClassException instances to be thrown + * when attempting operations on an invalid class. Note that instances of + * this class are immutable and are potentially shared among + * ObjectStreamClass instances. + */ + private static class ExceptionInfo { + private final String className; + private final String message; + + ExceptionInfo(String cn, String msg) { + className = cn; + message = msg; + } + + /** + * Returns (does not throw) an InvalidClassException instance created + * from the information in this object, suitable for being thrown by + * the caller. + */ + InvalidClassException newInvalidClassException() { + return new InvalidClassException(className, message); + } + } + + /** exception (if any) thrown while attempting to resolve class */ + private ClassNotFoundException resolveEx; + /** exception (if any) to throw if non-enum deserialization attempted */ + private ExceptionInfo deserializeEx; + /** exception (if any) to throw if non-enum serialization attempted */ + private ExceptionInfo serializeEx; + /** exception (if any) to throw if default serialization attempted */ + private ExceptionInfo defaultSerializeEx; + + /** serializable fields */ + private ObjectStreamField[] fields; + /** aggregate marshalled size of primitive fields */ + private int primDataSize; + /** number of non-primitive fields */ + private int numObjFields; + /** reflector for setting/getting serializable field values */ + private FieldReflector fieldRefl; + /** data layout of serialized objects described by this class desc */ + private volatile ClassDataSlot[] dataLayout; + + /** serialization-appropriate constructor, or null if none */ + private Constructor cons; + /** record canonical constructor (shared among OSCs for same class), or null */ + private MethodHandle canonicalCtr; + /** cache of record deserialization constructors per unique set of stream fields + * (shared among OSCs for same class), or null */ + private DeserializationConstructorsCache deserializationCtrs; + /** session-cache of record deserialization constructor + * (in de-serialized OSC only), or null */ + private MethodHandle deserializationCtr; + /** protection domains that need to be checked when calling the constructor */ + private ProtectionDomain[] domains; + + /** class-defined writeObject method, or null if none */ + private Method writeObjectMethod; + /** class-defined readObject method, or null if none */ + private Method readObjectMethod; + /** class-defined readObjectNoData method, or null if none */ + private Method readObjectNoDataMethod; + /** class-defined writeReplace method, or null if none */ + private Method writeReplaceMethod; + /** class-defined readResolve method, or null if none */ + private Method readResolveMethod; + + /** local class descriptor for represented class (may point to self) */ + private ObjectStreamClass localDesc; + /** superclass descriptor appearing in stream */ + private ObjectStreamClass superDesc; + + /** true if, and only if, the object has been correctly initialized */ + private boolean initialized; + + /** + * Initializes native code. + */ + private static native void initNative(); + static { + initNative(); + } + + /** + * Find the descriptor for a class that can be serialized. Creates an + * ObjectStreamClass instance if one does not exist yet for class. Null is + * returned if the specified class does not implement java.io.Serializable + * or java.io.Externalizable. + * + * @param cl class for which to get the descriptor + * @return the class descriptor for the specified class + */ + public static ObjectStreamClass lookup(Class cl) { + return lookup(cl, false); + } + + /** + * Returns the descriptor for any class, regardless of whether it + * implements {@link Serializable}. + * + * @param cl class for which to get the descriptor + * @return the class descriptor for the specified class + * @since 1.6 + */ + public static ObjectStreamClass lookupAny(Class cl) { + return lookup(cl, true); + } + + /** + * Returns the name of the class described by this descriptor. + * This method returns the name of the class in the format that + * is used by the {@link Class#getName} method. + * + * @return a string representing the name of the class + */ + public String getName() { + return name; + } + + /** + * Return the serialVersionUID for this class. The serialVersionUID + * defines a set of classes all with the same name that have evolved from a + * common root class and agree to be serialized and deserialized using a + * common format. NonSerializable classes have a serialVersionUID of 0L. + * + * @return the SUID of the class described by this descriptor + */ + @SuppressWarnings("removal") + public long getSerialVersionUID() { + // REMIND: synchronize instead of relying on volatile? + if (suid == null) { + if (isRecord) + return 0L; + + suid = AccessController.doPrivileged( + new PrivilegedAction() { + public Long run() { + return computeDefaultSUID(cl); + } + } + ); + } + return suid.longValue(); + } + + /** + * Return the class in the local VM that this version is mapped to. Null + * is returned if there is no corresponding local class. + * + * @return the {@code Class} instance that this descriptor represents + */ + @SuppressWarnings("removal") + @CallerSensitive + public Class forClass() { + if (cl == null) { + return null; + } + requireInitialized(); + if (System.getSecurityManager() != null) { + Class caller = Reflection.getCallerClass(); + if (ReflectUtil.needsPackageAccessCheck(caller.getClassLoader(), cl.getClassLoader())) { + ReflectUtil.checkPackageAccess(cl); + } + } + return cl; + } + + /** + * Return an array of the fields of this serializable class. + * + * @return an array containing an element for each persistent field of + * this class. Returns an array of length zero if there are no + * fields. + * @since 1.2 + */ + public ObjectStreamField[] getFields() { + return getFields(true); + } + + /** + * Get the field of this class by name. + * + * @param name the name of the data field to look for + * @return The ObjectStreamField object of the named field or null if + * there is no such named field. + */ + public ObjectStreamField getField(String name) { + return getField(name, null); + } + + /** + * Return a string describing this ObjectStreamClass. + */ + public String toString() { + return name + ": static final long serialVersionUID = " + + getSerialVersionUID() + "L;"; + } + + /** + * Looks up and returns class descriptor for given class, or null if class + * is non-serializable and "all" is set to false. + * + * @param cl class to look up + * @param all if true, return descriptors for all classes; if false, only + * return descriptors for serializable classes + */ + static ObjectStreamClass lookup(Class cl, boolean all) { + if (!(all || Serializable.class.isAssignableFrom(cl))) { + return null; + } + return Caches.localDescs.get(cl); + } + + /** + * Creates local class descriptor representing given class. + */ + @SuppressWarnings("removal") + private ObjectStreamClass(final Class cl) { + this.cl = cl; + name = cl.getName(); + isProxy = Proxy.isProxyClass(cl); + isEnum = Enum.class.isAssignableFrom(cl); + isRecord = cl.isRecord(); + serializable = Serializable.class.isAssignableFrom(cl); + externalizable = Externalizable.class.isAssignableFrom(cl); + + Class superCl = cl.getSuperclass(); + superDesc = (superCl != null) ? lookup(superCl, false) : null; + localDesc = this; + + if (serializable) { + AccessController.doPrivileged(new PrivilegedAction<>() { + public Void run() { + if (isEnum) { + suid = 0L; + fields = NO_FIELDS; + return null; + } + if (cl.isArray()) { + fields = NO_FIELDS; + return null; + } + + suid = getDeclaredSUID(cl); + try { + fields = getSerialFields(cl); + computeFieldOffsets(); + } catch (InvalidClassException e) { + serializeEx = deserializeEx = + new ExceptionInfo(e.classname, e.getMessage()); + fields = NO_FIELDS; + } + + if (isRecord) { + canonicalCtr = canonicalRecordCtr(cl); + deserializationCtrs = new DeserializationConstructorsCache(); + } else if (externalizable) { + cons = getExternalizableConstructor(cl); + } else { + cons = getSerializableConstructor(cl); + writeObjectMethod = getPrivateMethod(cl, "writeObject", + new Class[] { ObjectOutputStream.class }, + Void.TYPE); + readObjectMethod = getPrivateMethod(cl, "readObject", + new Class[] { ObjectInputStream.class }, + Void.TYPE); + readObjectNoDataMethod = getPrivateMethod( + cl, "readObjectNoData", null, Void.TYPE); + hasWriteObjectData = (writeObjectMethod != null); + } + domains = getProtectionDomains(cons, cl); + writeReplaceMethod = getInheritableMethod( + cl, "writeReplace", null, Object.class); + readResolveMethod = getInheritableMethod( + cl, "readResolve", null, Object.class); + return null; + } + }); + } else { + suid = 0L; + fields = NO_FIELDS; + } + + try { + fieldRefl = getReflector(fields, this); + } catch (InvalidClassException ex) { + // field mismatches impossible when matching local fields vs. self + throw new InternalError(ex); + } + + if (deserializeEx == null) { + if (isEnum) { + deserializeEx = new ExceptionInfo(name, "enum type"); + } else if (cons == null && !isRecord) { + deserializeEx = new ExceptionInfo(name, "no valid constructor"); + } + } + if (isRecord && canonicalCtr == null) { + deserializeEx = new ExceptionInfo(name, "record canonical constructor not found"); + } else { + for (int i = 0; i < fields.length; i++) { + if (fields[i].getField() == null) { + defaultSerializeEx = new ExceptionInfo( + name, "unmatched serializable field(s) declared"); + } + } + } + initialized = true; + } + + /** + * Creates blank class descriptor which should be initialized via a + * subsequent call to initProxy(), initNonProxy() or readNonProxy(). + */ + ObjectStreamClass() { + } + + /** + * Creates a PermissionDomain that grants no permission. + */ + private ProtectionDomain noPermissionsDomain() { + PermissionCollection perms = new Permissions(); + perms.setReadOnly(); + return new ProtectionDomain(null, perms); + } + + /** + * Aggregate the ProtectionDomains of all the classes that separate + * a concrete class {@code cl} from its ancestor's class declaring + * a constructor {@code cons}. + * + * If {@code cl} is defined by the boot loader, or the constructor + * {@code cons} is declared by {@code cl}, or if there is no security + * manager, then this method does nothing and {@code null} is returned. + * + * @param cons A constructor declared by {@code cl} or one of its + * ancestors. + * @param cl A concrete class, which is either the class declaring + * the constructor {@code cons}, or a serializable subclass + * of that class. + * @return An array of ProtectionDomain representing the set of + * ProtectionDomain that separate the concrete class {@code cl} + * from its ancestor's declaring {@code cons}, or {@code null}. + */ + @SuppressWarnings("removal") + private ProtectionDomain[] getProtectionDomains(Constructor cons, + Class cl) { + ProtectionDomain[] domains = null; + if (cons != null && cl.getClassLoader() != null + && System.getSecurityManager() != null) { + Class cls = cl; + Class fnscl = cons.getDeclaringClass(); + Set pds = null; + while (cls != fnscl) { + ProtectionDomain pd = cls.getProtectionDomain(); + if (pd != null) { + if (pds == null) pds = new HashSet<>(); + pds.add(pd); + } + cls = cls.getSuperclass(); + if (cls == null) { + // that's not supposed to happen + // make a ProtectionDomain with no permission. + // should we throw instead? + if (pds == null) pds = new HashSet<>(); + else pds.clear(); + pds.add(noPermissionsDomain()); + break; + } + } + if (pds != null) { + domains = pds.toArray(new ProtectionDomain[0]); + } + } + return domains; + } + + /** + * Initializes class descriptor representing a proxy class. + */ + void initProxy(Class cl, + ClassNotFoundException resolveEx, + ObjectStreamClass superDesc) + throws InvalidClassException + { + ObjectStreamClass osc = null; + if (cl != null) { + osc = lookup(cl, true); + if (!osc.isProxy) { + throw new InvalidClassException( + "cannot bind proxy descriptor to a non-proxy class"); + } + } + this.cl = cl; + this.resolveEx = resolveEx; + this.superDesc = superDesc; + isProxy = true; + serializable = true; + suid = 0L; + fields = NO_FIELDS; + if (osc != null) { + localDesc = osc; + name = localDesc.name; + externalizable = localDesc.externalizable; + writeReplaceMethod = localDesc.writeReplaceMethod; + readResolveMethod = localDesc.readResolveMethod; + deserializeEx = localDesc.deserializeEx; + domains = localDesc.domains; + cons = localDesc.cons; + } + fieldRefl = getReflector(fields, localDesc); + initialized = true; + } + + /** + * Initializes class descriptor representing a non-proxy class. + */ + void initNonProxy(ObjectStreamClass model, + Class cl, + ClassNotFoundException resolveEx, + ObjectStreamClass superDesc) + throws InvalidClassException + { + long suid = model.getSerialVersionUID(); + ObjectStreamClass osc = null; + if (cl != null) { + osc = lookup(cl, true); + if (osc.isProxy) { + throw new InvalidClassException( + "cannot bind non-proxy descriptor to a proxy class"); + } + if (model.isEnum != osc.isEnum) { + throw new InvalidClassException(model.isEnum ? + "cannot bind enum descriptor to a non-enum class" : + "cannot bind non-enum descriptor to an enum class"); + } + + if (model.serializable == osc.serializable && + !cl.isArray() && !cl.isRecord() && + suid != osc.getSerialVersionUID()) { + throw new InvalidClassException(osc.name, + "local class incompatible: " + + "stream classdesc serialVersionUID = " + suid + + ", local class serialVersionUID = " + + osc.getSerialVersionUID()); + } + + if (!classNamesEqual(model.name, osc.name)) { + throw new InvalidClassException(osc.name, + "local class name incompatible with stream class " + + "name \"" + model.name + "\""); + } + + if (!model.isEnum) { + if ((model.serializable == osc.serializable) && + (model.externalizable != osc.externalizable)) { + throw new InvalidClassException(osc.name, + "Serializable incompatible with Externalizable"); + } + + if ((model.serializable != osc.serializable) || + (model.externalizable != osc.externalizable) || + !(model.serializable || model.externalizable)) { + deserializeEx = new ExceptionInfo( + osc.name, "class invalid for deserialization"); + } + } + } + + this.cl = cl; + this.resolveEx = resolveEx; + this.superDesc = superDesc; + name = model.name; + this.suid = suid; + isProxy = false; + isEnum = model.isEnum; + serializable = model.serializable; + externalizable = model.externalizable; + hasBlockExternalData = model.hasBlockExternalData; + hasWriteObjectData = model.hasWriteObjectData; + fields = model.fields; + primDataSize = model.primDataSize; + numObjFields = model.numObjFields; + + if (osc != null) { + localDesc = osc; + isRecord = localDesc.isRecord; + // canonical record constructor is shared + canonicalCtr = localDesc.canonicalCtr; + // cache of deserialization constructors is shared + deserializationCtrs = localDesc.deserializationCtrs; + writeObjectMethod = localDesc.writeObjectMethod; + readObjectMethod = localDesc.readObjectMethod; + readObjectNoDataMethod = localDesc.readObjectNoDataMethod; + writeReplaceMethod = localDesc.writeReplaceMethod; + readResolveMethod = localDesc.readResolveMethod; + if (deserializeEx == null) { + deserializeEx = localDesc.deserializeEx; + } + domains = localDesc.domains; + assert cl.isRecord() ? localDesc.cons == null : true; + cons = localDesc.cons; + } + + fieldRefl = getReflector(fields, localDesc); + // reassign to matched fields so as to reflect local unshared settings + fields = fieldRefl.getFields(); + + initialized = true; + } + + /** + * Reads non-proxy class descriptor information from given input stream. + * The resulting class descriptor is not fully functional; it can only be + * used as input to the ObjectInputStream.resolveClass() and + * ObjectStreamClass.initNonProxy() methods. + */ + void readNonProxy(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + name = in.readUTF(); + suid = in.readLong(); + isProxy = false; + + byte flags = in.readByte(); + hasWriteObjectData = + ((flags & ObjectStreamConstants.SC_WRITE_METHOD) != 0); + hasBlockExternalData = + ((flags & ObjectStreamConstants.SC_BLOCK_DATA) != 0); + externalizable = + ((flags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0); + boolean sflag = + ((flags & ObjectStreamConstants.SC_SERIALIZABLE) != 0); + if (externalizable && sflag) { + throw new InvalidClassException( + name, "serializable and externalizable flags conflict"); + } + serializable = externalizable || sflag; + isEnum = ((flags & ObjectStreamConstants.SC_ENUM) != 0); + if (isEnum && suid.longValue() != 0L) { + throw new InvalidClassException(name, + "enum descriptor has non-zero serialVersionUID: " + suid); + } + + int numFields = in.readShort(); + if (isEnum && numFields != 0) { + throw new InvalidClassException(name, + "enum descriptor has non-zero field count: " + numFields); + } + fields = (numFields > 0) ? + new ObjectStreamField[numFields] : NO_FIELDS; + for (int i = 0; i < numFields; i++) { + char tcode = (char) in.readByte(); + String fname = in.readUTF(); + String signature = ((tcode == 'L') || (tcode == '[')) ? + in.readTypeString() : String.valueOf(tcode); + try { + fields[i] = new ObjectStreamField(fname, signature, false); + } catch (RuntimeException e) { + throw new InvalidClassException(name, + "invalid descriptor for field " + + fname, e); + } + } + computeFieldOffsets(); + } + + /** + * Writes non-proxy class descriptor information to given output stream. + */ + void writeNonProxy(ObjectOutputStream out) throws IOException { + out.writeUTF(name); + out.writeLong(getSerialVersionUID()); + + byte flags = 0; + if (externalizable) { + flags |= ObjectStreamConstants.SC_EXTERNALIZABLE; + int protocol = out.getProtocolVersion(); + if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) { + flags |= ObjectStreamConstants.SC_BLOCK_DATA; + } + } else if (serializable) { + flags |= ObjectStreamConstants.SC_SERIALIZABLE; + } + if (hasWriteObjectData) { + flags |= ObjectStreamConstants.SC_WRITE_METHOD; + } + if (isEnum) { + flags |= ObjectStreamConstants.SC_ENUM; + } + out.writeByte(flags); + + out.writeShort(fields.length); + for (int i = 0; i < fields.length; i++) { + ObjectStreamField f = fields[i]; + out.writeByte(f.getTypeCode()); + out.writeUTF(f.getName()); + if (!f.isPrimitive()) { + out.writeTypeString(f.getTypeString()); + } + } + } + + /** + * Returns ClassNotFoundException (if any) thrown while attempting to + * resolve local class corresponding to this class descriptor. + */ + ClassNotFoundException getResolveException() { + return resolveEx; + } + + /** + * Throws InternalError if not initialized. + */ + private final void requireInitialized() { + if (!initialized) + throw new InternalError("Unexpected call when not initialized"); + } + + /** + * Throws InvalidClassException if not initialized. + * To be called in cases where an uninitialized class descriptor indicates + * a problem in the serialization stream. + */ + final void checkInitialized() throws InvalidClassException { + if (!initialized) { + throw new InvalidClassException("Class descriptor should be initialized"); + } + } + + /** + * Throws an InvalidClassException if object instances referencing this + * class descriptor should not be allowed to deserialize. This method does + * not apply to deserialization of enum constants. + */ + void checkDeserialize() throws InvalidClassException { + requireInitialized(); + if (deserializeEx != null) { + throw deserializeEx.newInvalidClassException(); + } + } + + /** + * Throws an InvalidClassException if objects whose class is represented by + * this descriptor should not be allowed to serialize. This method does + * not apply to serialization of enum constants. + */ + void checkSerialize() throws InvalidClassException { + requireInitialized(); + if (serializeEx != null) { + throw serializeEx.newInvalidClassException(); + } + } + + /** + * Throws an InvalidClassException if objects whose class is represented by + * this descriptor should not be permitted to use default serialization + * (e.g., if the class declares serializable fields that do not correspond + * to actual fields, and hence must use the GetField API). This method + * does not apply to deserialization of enum constants. + */ + void checkDefaultSerialize() throws InvalidClassException { + requireInitialized(); + if (defaultSerializeEx != null) { + throw defaultSerializeEx.newInvalidClassException(); + } + } + + /** + * Returns superclass descriptor. Note that on the receiving side, the + * superclass descriptor may be bound to a class that is not a superclass + * of the subclass descriptor's bound class. + */ + ObjectStreamClass getSuperDesc() { + requireInitialized(); + return superDesc; + } + + /** + * Returns the "local" class descriptor for the class associated with this + * class descriptor (i.e., the result of + * ObjectStreamClass.lookup(this.forClass())) or null if there is no class + * associated with this descriptor. + */ + ObjectStreamClass getLocalDesc() { + requireInitialized(); + return localDesc; + } + + /** + * Returns arrays of ObjectStreamFields representing the serializable + * fields of the represented class. If copy is true, a clone of this class + * descriptor's field array is returned, otherwise the array itself is + * returned. + */ + ObjectStreamField[] getFields(boolean copy) { + return copy ? fields.clone() : fields; + } + + /** + * Looks up a serializable field of the represented class by name and type. + * A specified type of null matches all types, Object.class matches all + * non-primitive types, and any other non-null type matches assignable + * types only. Returns matching field, or null if no match found. + */ + ObjectStreamField getField(String name, Class type) { + for (int i = 0; i < fields.length; i++) { + ObjectStreamField f = fields[i]; + if (f.getName().equals(name)) { + if (type == null || + (type == Object.class && !f.isPrimitive())) + { + return f; + } + Class ftype = f.getType(); + if (ftype != null && type.isAssignableFrom(ftype)) { + return f; + } + } + } + return null; + } + + /** + * Returns true if class descriptor represents a dynamic proxy class, false + * otherwise. + */ + boolean isProxy() { + requireInitialized(); + return isProxy; + } + + /** + * Returns true if class descriptor represents an enum type, false + * otherwise. + */ + boolean isEnum() { + requireInitialized(); + return isEnum; + } + + /** + * Returns true if class descriptor represents a record type, false + * otherwise. + */ + boolean isRecord() { + requireInitialized(); + return isRecord; + } + + /** + * Returns true if represented class implements Externalizable, false + * otherwise. + */ + boolean isExternalizable() { + requireInitialized(); + return externalizable; + } + + /** + * Returns true if represented class implements Serializable, false + * otherwise. + */ + boolean isSerializable() { + requireInitialized(); + return serializable; + } + + /** + * Returns true if class descriptor represents externalizable class that + * has written its data in 1.2 (block data) format, false otherwise. + */ + boolean hasBlockExternalData() { + requireInitialized(); + return hasBlockExternalData; + } + + /** + * Returns true if class descriptor represents serializable (but not + * externalizable) class which has written its data via a custom + * writeObject() method, false otherwise. + */ + boolean hasWriteObjectData() { + requireInitialized(); + return hasWriteObjectData; + } + + /** + * Returns true if represented class is serializable/externalizable and can + * be instantiated by the serialization runtime--i.e., if it is + * externalizable and defines a public no-arg constructor, or if it is + * non-externalizable and its first non-serializable superclass defines an + * accessible no-arg constructor. Otherwise, returns false. + */ + boolean isInstantiable() { + requireInitialized(); + return (cons != null); + } + + /** + * Returns true if represented class is serializable (but not + * externalizable) and defines a conformant writeObject method. Otherwise, + * returns false. + */ + boolean hasWriteObjectMethod() { + requireInitialized(); + return (writeObjectMethod != null); + } + + /** + * Returns true if represented class is serializable (but not + * externalizable) and defines a conformant readObject method. Otherwise, + * returns false. + */ + boolean hasReadObjectMethod() { + requireInitialized(); + return (readObjectMethod != null); + } + + /** + * Returns true if represented class is serializable (but not + * externalizable) and defines a conformant readObjectNoData method. + * Otherwise, returns false. + */ + boolean hasReadObjectNoDataMethod() { + requireInitialized(); + return (readObjectNoDataMethod != null); + } + + /** + * Returns true if represented class is serializable or externalizable and + * defines a conformant writeReplace method. Otherwise, returns false. + */ + boolean hasWriteReplaceMethod() { + requireInitialized(); + return (writeReplaceMethod != null); + } + + /** + * Returns true if represented class is serializable or externalizable and + * defines a conformant readResolve method. Otherwise, returns false. + */ + boolean hasReadResolveMethod() { + requireInitialized(); + return (readResolveMethod != null); + } + + /** + * Creates a new instance of the represented class. If the class is + * externalizable, invokes its public no-arg constructor; otherwise, if the + * class is serializable, invokes the no-arg constructor of the first + * non-serializable superclass. Throws UnsupportedOperationException if + * this class descriptor is not associated with a class, if the associated + * class is non-serializable or if the appropriate no-arg constructor is + * inaccessible/unavailable. + */ + @SuppressWarnings("removal") + Object newInstance() + throws InstantiationException, InvocationTargetException, + UnsupportedOperationException + { + requireInitialized(); + if (cons != null) { + try { + if (domains == null || domains.length == 0) { + return cons.newInstance(); + } else { + JavaSecurityAccess jsa = SharedSecrets.getJavaSecurityAccess(); + PrivilegedAction pea = () -> { + try { + return cons.newInstance(); + } catch (InstantiationException + | InvocationTargetException + | IllegalAccessException x) { + throw new UndeclaredThrowableException(x); + } + }; // Can't use PrivilegedExceptionAction with jsa + try { + return jsa.doIntersectionPrivilege(pea, + AccessController.getContext(), + new AccessControlContext(domains)); + } catch (UndeclaredThrowableException x) { + Throwable cause = x.getCause(); + if (cause instanceof InstantiationException ie) + throw ie; + if (cause instanceof InvocationTargetException ite) + throw ite; + if (cause instanceof IllegalAccessException iae) + throw iae; + // not supposed to happen + throw x; + } + } + } catch (IllegalAccessException ex) { + // should not occur, as access checks have been suppressed + throw new InternalError(ex); + } catch (InvocationTargetException ex) { + Throwable cause = ex.getCause(); + if (cause instanceof Error err) + throw err; + else + throw ex; + } catch (InstantiationError err) { + var ex = new InstantiationException(); + ex.initCause(err); + throw ex; + } + } else { + throw new UnsupportedOperationException(); + } + } + + /** + * Invokes the writeObject method of the represented serializable class. + * Throws UnsupportedOperationException if this class descriptor is not + * associated with a class, or if the class is externalizable, + * non-serializable or does not define writeObject. + */ + void invokeWriteObject(Object obj, ObjectOutputStream out) + throws IOException, UnsupportedOperationException + { + requireInitialized(); + if (writeObjectMethod != null) { + try { + writeObjectMethod.invoke(obj, new Object[]{ out }); + } catch (InvocationTargetException ex) { + Throwable th = ex.getCause(); + if (th instanceof IOException) { + throw (IOException) th; + } else { + throwMiscException(th); + } + } catch (IllegalAccessException ex) { + // should not occur, as access checks have been suppressed + throw new InternalError(ex); + } + } else { + throw new UnsupportedOperationException(); + } + } + + /** + * Invokes the readObject method of the represented serializable class. + * Throws UnsupportedOperationException if this class descriptor is not + * associated with a class, or if the class is externalizable, + * non-serializable or does not define readObject. + */ + void invokeReadObject(Object obj, ObjectInputStream in) + throws ClassNotFoundException, IOException, + UnsupportedOperationException + { + requireInitialized(); + if (readObjectMethod != null) { + try { + readObjectMethod.invoke(obj, new Object[]{ in }); + } catch (InvocationTargetException ex) { + Throwable th = ex.getCause(); + if (th instanceof ClassNotFoundException) { + throw (ClassNotFoundException) th; + } else if (th instanceof IOException) { + throw (IOException) th; + } else { + throwMiscException(th); + } + } catch (IllegalAccessException ex) { + // should not occur, as access checks have been suppressed + throw new InternalError(ex); + } + } else { + throw new UnsupportedOperationException(); + } + } + + /** + * Invokes the readObjectNoData method of the represented serializable + * class. Throws UnsupportedOperationException if this class descriptor is + * not associated with a class, or if the class is externalizable, + * non-serializable or does not define readObjectNoData. + */ + void invokeReadObjectNoData(Object obj) + throws IOException, UnsupportedOperationException + { + requireInitialized(); + if (readObjectNoDataMethod != null) { + try { + readObjectNoDataMethod.invoke(obj, (Object[]) null); + } catch (InvocationTargetException ex) { + Throwable th = ex.getCause(); + if (th instanceof ObjectStreamException) { + throw (ObjectStreamException) th; + } else { + throwMiscException(th); + } + } catch (IllegalAccessException ex) { + // should not occur, as access checks have been suppressed + throw new InternalError(ex); + } + } else { + throw new UnsupportedOperationException(); + } + } + + /** + * Invokes the writeReplace method of the represented serializable class and + * returns the result. Throws UnsupportedOperationException if this class + * descriptor is not associated with a class, or if the class is + * non-serializable or does not define writeReplace. + */ + Object invokeWriteReplace(Object obj) + throws IOException, UnsupportedOperationException + { + requireInitialized(); + if (writeReplaceMethod != null) { + try { + return writeReplaceMethod.invoke(obj, (Object[]) null); + } catch (InvocationTargetException ex) { + Throwable th = ex.getCause(); + if (th instanceof ObjectStreamException) { + throw (ObjectStreamException) th; + } else { + throwMiscException(th); + throw new InternalError(th); // never reached + } + } catch (IllegalAccessException ex) { + // should not occur, as access checks have been suppressed + throw new InternalError(ex); + } + } else { + throw new UnsupportedOperationException(); + } + } + + /** + * Invokes the readResolve method of the represented serializable class and + * returns the result. Throws UnsupportedOperationException if this class + * descriptor is not associated with a class, or if the class is + * non-serializable or does not define readResolve. + */ + Object invokeReadResolve(Object obj) + throws IOException, UnsupportedOperationException + { + requireInitialized(); + if (readResolveMethod != null) { + try { + return readResolveMethod.invoke(obj, (Object[]) null); + } catch (InvocationTargetException ex) { + Throwable th = ex.getCause(); + if (th instanceof ObjectStreamException) { + throw (ObjectStreamException) th; + } else { + throwMiscException(th); + throw new InternalError(th); // never reached + } + } catch (IllegalAccessException ex) { + // should not occur, as access checks have been suppressed + throw new InternalError(ex); + } + } else { + throw new UnsupportedOperationException(); + } + } + + /** + * Class representing the portion of an object's serialized form allotted + * to data described by a given class descriptor. If "hasData" is false, + * the object's serialized form does not contain data associated with the + * class descriptor. + */ + static class ClassDataSlot { + + /** class descriptor "occupying" this slot */ + final ObjectStreamClass desc; + /** true if serialized form includes data for this slot's descriptor */ + final boolean hasData; + + ClassDataSlot(ObjectStreamClass desc, boolean hasData) { + this.desc = desc; + this.hasData = hasData; + } + } + + /** + * Returns array of ClassDataSlot instances representing the data layout + * (including superclass data) for serialized objects described by this + * class descriptor. ClassDataSlots are ordered by inheritance with those + * containing "higher" superclasses appearing first. The final + * ClassDataSlot contains a reference to this descriptor. + */ + ClassDataSlot[] getClassDataLayout() throws InvalidClassException { + // REMIND: synchronize instead of relying on volatile? + if (dataLayout == null) { + dataLayout = getClassDataLayout0(); + } + return dataLayout; + } + + private ClassDataSlot[] getClassDataLayout0() + throws InvalidClassException + { + ArrayList slots = new ArrayList<>(); + Class start = cl, end = cl; + + // locate closest non-serializable superclass + while (end != null && Serializable.class.isAssignableFrom(end)) { + end = end.getSuperclass(); + } + + HashSet oscNames = new HashSet<>(3); + + for (ObjectStreamClass d = this; d != null; d = d.superDesc) { + if (oscNames.contains(d.name)) { + throw new InvalidClassException("Circular reference."); + } else { + oscNames.add(d.name); + } + + // search up inheritance hierarchy for class with matching name + String searchName = (d.cl != null) ? d.cl.getName() : d.name; + Class match = null; + for (Class c = start; c != end; c = c.getSuperclass()) { + if (searchName.equals(c.getName())) { + match = c; + break; + } + } + + // add "no data" slot for each unmatched class below match + if (match != null) { + for (Class c = start; c != match; c = c.getSuperclass()) { + slots.add(new ClassDataSlot( + ObjectStreamClass.lookup(c, true), false)); + } + start = match.getSuperclass(); + } + + // record descriptor/class pairing + slots.add(new ClassDataSlot(d.getVariantFor(match), true)); + } + + // add "no data" slot for any leftover unmatched classes + for (Class c = start; c != end; c = c.getSuperclass()) { + slots.add(new ClassDataSlot( + ObjectStreamClass.lookup(c, true), false)); + } + + // order slots from superclass -> subclass + Collections.reverse(slots); + return slots.toArray(new ClassDataSlot[slots.size()]); + } + + /** + * Returns aggregate size (in bytes) of marshalled primitive field values + * for represented class. + */ + int getPrimDataSize() { + return primDataSize; + } + + /** + * Returns number of non-primitive serializable fields of represented + * class. + */ + int getNumObjFields() { + return numObjFields; + } + + /** + * Fetches the serializable primitive field values of object obj and + * marshals them into byte array buf starting at offset 0. It is the + * responsibility of the caller to ensure that obj is of the proper type if + * non-null. + */ + void getPrimFieldValues(Object obj, byte[] buf) { + fieldRefl.getPrimFieldValues(obj, buf); + } + + /** + * Sets the serializable primitive fields of object obj using values + * unmarshalled from byte array buf starting at offset 0. It is the + * responsibility of the caller to ensure that obj is of the proper type if + * non-null. + */ + void setPrimFieldValues(Object obj, byte[] buf) { + fieldRefl.setPrimFieldValues(obj, buf); + } + + /** + * Fetches the serializable object field values of object obj and stores + * them in array vals starting at offset 0. It is the responsibility of + * the caller to ensure that obj is of the proper type if non-null. + */ + void getObjFieldValues(Object obj, Object[] vals) { + fieldRefl.getObjFieldValues(obj, vals); + } + + /** + * Checks that the given values, from array vals starting at offset 0, + * are assignable to the given serializable object fields. + * @throws ClassCastException if any value is not assignable + */ + void checkObjFieldValueTypes(Object obj, Object[] vals) { + fieldRefl.checkObjectFieldValueTypes(obj, vals); + } + + /** + * Sets the serializable object fields of object obj using values from + * array vals starting at offset 0. It is the responsibility of the caller + * to ensure that obj is of the proper type if non-null. + */ + void setObjFieldValues(Object obj, Object[] vals) { + fieldRefl.setObjFieldValues(obj, vals); + } + + /** + * Calculates and sets serializable field offsets, as well as primitive + * data size and object field count totals. Throws InvalidClassException + * if fields are illegally ordered. + */ + private void computeFieldOffsets() throws InvalidClassException { + primDataSize = 0; + numObjFields = 0; + int firstObjIndex = -1; + + for (int i = 0; i < fields.length; i++) { + ObjectStreamField f = fields[i]; + switch (f.getTypeCode()) { + case 'Z', 'B' -> f.setOffset(primDataSize++); + case 'C', 'S' -> { + f.setOffset(primDataSize); + primDataSize += 2; + } + case 'I', 'F' -> { + f.setOffset(primDataSize); + primDataSize += 4; + } + case 'J', 'D' -> { + f.setOffset(primDataSize); + primDataSize += 8; + } + case '[', 'L' -> { + f.setOffset(numObjFields++); + if (firstObjIndex == -1) { + firstObjIndex = i; + } + } + default -> throw new InternalError(); + } + } + if (firstObjIndex != -1 && + firstObjIndex + numObjFields != fields.length) + { + throw new InvalidClassException(name, "illegal field order"); + } + } + + /** + * If given class is the same as the class associated with this class + * descriptor, returns reference to this class descriptor. Otherwise, + * returns variant of this class descriptor bound to given class. + */ + private ObjectStreamClass getVariantFor(Class cl) + throws InvalidClassException + { + if (this.cl == cl) { + return this; + } + ObjectStreamClass desc = new ObjectStreamClass(); + if (isProxy) { + desc.initProxy(cl, null, superDesc); + } else { + desc.initNonProxy(this, cl, null, superDesc); + } + return desc; + } + + /** + * Returns public no-arg constructor of given class, or null if none found. + * Access checks are disabled on the returned constructor (if any), since + * the defining class may still be non-public. + */ + private static Constructor getExternalizableConstructor(Class cl) { + try { + Constructor cons = cl.getDeclaredConstructor((Class[]) null); + cons.setAccessible(true); + return ((cons.getModifiers() & Modifier.PUBLIC) != 0) ? + cons : null; + } catch (NoSuchMethodException ex) { + return null; + } + } + + /** + * Returns subclass-accessible no-arg constructor of first non-serializable + * superclass, or null if none found. Access checks are disabled on the + * returned constructor (if any). + */ + private static Constructor getSerializableConstructor(Class cl) { + return reflFactory.newConstructorForSerialization(cl); + } + + /** + * Returns the canonical constructor for the given record class, or null if + * the not found ( which should never happen for correctly generated record + * classes ). + */ + @SuppressWarnings("removal") + private static MethodHandle canonicalRecordCtr(Class cls) { + assert cls.isRecord() : "Expected record, got: " + cls; + PrivilegedAction pa = () -> { + Class[] paramTypes = Arrays.stream(cls.getRecordComponents()) + .map(RecordComponent::getType) + .toArray(Class[]::new); + try { + Constructor ctr = cls.getDeclaredConstructor(paramTypes); + ctr.setAccessible(true); + return MethodHandles.lookup().unreflectConstructor(ctr); + } catch (IllegalAccessException | NoSuchMethodException e) { + return null; + } + }; + return AccessController.doPrivileged(pa); + } + + /** + * Returns the canonical constructor, if the local class equivalent of this + * stream class descriptor is a record class, otherwise null. + */ + MethodHandle getRecordConstructor() { + return canonicalCtr; + } + + /** + * Returns non-static, non-abstract method with given signature provided it + * is defined by or accessible (via inheritance) by the given class, or + * null if no match found. Access checks are disabled on the returned + * method (if any). + */ + private static Method getInheritableMethod(Class cl, String name, + Class[] argTypes, + Class returnType) + { + Method meth = null; + Class defCl = cl; + while (defCl != null) { + try { + meth = defCl.getDeclaredMethod(name, argTypes); + break; + } catch (NoSuchMethodException ex) { + defCl = defCl.getSuperclass(); + } + } + + if ((meth == null) || (meth.getReturnType() != returnType)) { + return null; + } + meth.setAccessible(true); + int mods = meth.getModifiers(); + if ((mods & (Modifier.STATIC | Modifier.ABSTRACT)) != 0) { + return null; + } else if ((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) { + return meth; + } else if ((mods & Modifier.PRIVATE) != 0) { + return (cl == defCl) ? meth : null; + } else { + return packageEquals(cl, defCl) ? meth : null; + } + } + + /** + * Returns non-static private method with given signature defined by given + * class, or null if none found. Access checks are disabled on the + * returned method (if any). + */ + private static Method getPrivateMethod(Class cl, String name, + Class[] argTypes, + Class returnType) + { + try { + Method meth = cl.getDeclaredMethod(name, argTypes); + meth.setAccessible(true); + int mods = meth.getModifiers(); + return ((meth.getReturnType() == returnType) && + ((mods & Modifier.STATIC) == 0) && + ((mods & Modifier.PRIVATE) != 0)) ? meth : null; + } catch (NoSuchMethodException ex) { + return null; + } + } + + /** + * Returns true if classes are defined in the same runtime package, false + * otherwise. + */ + private static boolean packageEquals(Class cl1, Class cl2) { + return cl1.getClassLoader() == cl2.getClassLoader() && + cl1.getPackageName() == cl2.getPackageName(); + } + + /** + * Compares class names for equality, ignoring package names. Returns true + * if class names equal, false otherwise. + */ + private static boolean classNamesEqual(String name1, String name2) { + int idx1 = name1.lastIndexOf('.') + 1; + int idx2 = name2.lastIndexOf('.') + 1; + int len1 = name1.length() - idx1; + int len2 = name2.length() - idx2; + return len1 == len2 && + name1.regionMatches(idx1, name2, idx2, len1); + } + + /** + * Returns JVM type signature for given list of parameters and return type. + */ + private static String getMethodSignature(Class[] paramTypes, + Class retType) + { + StringBuilder sb = new StringBuilder(); + sb.append('('); + for (int i = 0; i < paramTypes.length; i++) { + sb.append(paramTypes[i].descriptorString()); + } + sb.append(')'); + sb.append(retType.descriptorString()); + return sb.toString(); + } + + /** + * Convenience method for throwing an exception that is either a + * RuntimeException, Error, or of some unexpected type (in which case it is + * wrapped inside an IOException). + */ + private static void throwMiscException(Throwable th) throws IOException { + if (th instanceof RuntimeException) { + throw (RuntimeException) th; + } else if (th instanceof Error) { + throw (Error) th; + } else { + throw new IOException("unexpected exception type", th); + } + } + + /** + * Returns ObjectStreamField array describing the serializable fields of + * the given class. Serializable fields backed by an actual field of the + * class are represented by ObjectStreamFields with corresponding non-null + * Field objects. Throws InvalidClassException if the (explicitly + * declared) serializable fields are invalid. + */ + private static ObjectStreamField[] getSerialFields(Class cl) + throws InvalidClassException + { + if (!Serializable.class.isAssignableFrom(cl)) + return NO_FIELDS; + + ObjectStreamField[] fields; + if (cl.isRecord()) { + fields = getDefaultSerialFields(cl); + Arrays.sort(fields); + } else if (!Externalizable.class.isAssignableFrom(cl) && + !Proxy.isProxyClass(cl) && + !cl.isInterface()) { + if ((fields = getDeclaredSerialFields(cl)) == null) { + fields = getDefaultSerialFields(cl); + } + Arrays.sort(fields); + } else { + fields = NO_FIELDS; + } + return fields; + } + + /** + * Returns serializable fields of given class as defined explicitly by a + * "serialPersistentFields" field, or null if no appropriate + * "serialPersistentFields" field is defined. Serializable fields backed + * by an actual field of the class are represented by ObjectStreamFields + * with corresponding non-null Field objects. For compatibility with past + * releases, a "serialPersistentFields" field with a null value is + * considered equivalent to not declaring "serialPersistentFields". Throws + * InvalidClassException if the declared serializable fields are + * invalid--e.g., if multiple fields share the same name. + */ + private static ObjectStreamField[] getDeclaredSerialFields(Class cl) + throws InvalidClassException + { + ObjectStreamField[] serialPersistentFields = null; + try { + Field f = cl.getDeclaredField("serialPersistentFields"); + int mask = Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL; + if ((f.getModifiers() & mask) == mask) { + f.setAccessible(true); + serialPersistentFields = (ObjectStreamField[]) f.get(null); + } + } catch (Exception ex) { + } + if (serialPersistentFields == null) { + return null; + } else if (serialPersistentFields.length == 0) { + return NO_FIELDS; + } + + ObjectStreamField[] boundFields = + new ObjectStreamField[serialPersistentFields.length]; + Set fieldNames = HashSet.newHashSet(serialPersistentFields.length); + + for (int i = 0; i < serialPersistentFields.length; i++) { + ObjectStreamField spf = serialPersistentFields[i]; + + String fname = spf.getName(); + if (fieldNames.contains(fname)) { + throw new InvalidClassException( + "multiple serializable fields named " + fname); + } + fieldNames.add(fname); + + try { + Field f = cl.getDeclaredField(fname); + if ((f.getType() == spf.getType()) && + ((f.getModifiers() & Modifier.STATIC) == 0)) + { + boundFields[i] = + new ObjectStreamField(f, spf.isUnshared(), true); + } + } catch (NoSuchFieldException ex) { + } + if (boundFields[i] == null) { + boundFields[i] = new ObjectStreamField( + fname, spf.getType(), spf.isUnshared()); + } + } + return boundFields; + } + + /** + * Returns array of ObjectStreamFields corresponding to all non-static + * non-transient fields declared by given class. Each ObjectStreamField + * contains a Field object for the field it represents. If no default + * serializable fields exist, NO_FIELDS is returned. + */ + private static ObjectStreamField[] getDefaultSerialFields(Class cl) { + Field[] clFields = cl.getDeclaredFields(); + ArrayList list = new ArrayList<>(); + int mask = Modifier.STATIC | Modifier.TRANSIENT; + + for (int i = 0; i < clFields.length; i++) { + if ((clFields[i].getModifiers() & mask) == 0) { + list.add(new ObjectStreamField(clFields[i], false, true)); + } + } + int size = list.size(); + return (size == 0) ? NO_FIELDS : + list.toArray(new ObjectStreamField[size]); + } + + /** + * Returns explicit serial version UID value declared by given class, or + * null if none. + */ + private static Long getDeclaredSUID(Class cl) { + try { + Field f = cl.getDeclaredField("serialVersionUID"); + int mask = Modifier.STATIC | Modifier.FINAL; + if ((f.getModifiers() & mask) == mask) { + f.setAccessible(true); + return f.getLong(null); + } + } catch (Exception ex) { + } + return null; + } + + /** + * Computes the default serial version UID value for the given class. + */ + private static long computeDefaultSUID(Class cl) { + if (!Serializable.class.isAssignableFrom(cl) || Proxy.isProxyClass(cl)) + { + return 0L; + } + + try { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + DataOutputStream dout = new DataOutputStream(bout); + + dout.writeUTF(cl.getName()); + + int classMods = cl.getModifiers() & + (Modifier.PUBLIC | Modifier.FINAL | + Modifier.INTERFACE | Modifier.ABSTRACT); + + /* + * compensate for javac bug in which ABSTRACT bit was set for an + * interface only if the interface declared methods + */ + Method[] methods = cl.getDeclaredMethods(); + if ((classMods & Modifier.INTERFACE) != 0) { + classMods = (methods.length > 0) ? + (classMods | Modifier.ABSTRACT) : + (classMods & ~Modifier.ABSTRACT); + } + dout.writeInt(classMods); + + if (!cl.isArray()) { + /* + * compensate for change in 1.2FCS in which + * Class.getInterfaces() was modified to return Cloneable and + * Serializable for array classes. + */ + Class[] interfaces = cl.getInterfaces(); + String[] ifaceNames = new String[interfaces.length]; + for (int i = 0; i < interfaces.length; i++) { + ifaceNames[i] = interfaces[i].getName(); + } + Arrays.sort(ifaceNames); + for (int i = 0; i < ifaceNames.length; i++) { + dout.writeUTF(ifaceNames[i]); + } + } + + Field[] fields = cl.getDeclaredFields(); + MemberSignature[] fieldSigs = new MemberSignature[fields.length]; + for (int i = 0; i < fields.length; i++) { + fieldSigs[i] = new MemberSignature(fields[i]); + } + Arrays.sort(fieldSigs, new Comparator<>() { + public int compare(MemberSignature ms1, MemberSignature ms2) { + return ms1.name.compareTo(ms2.name); + } + }); + for (int i = 0; i < fieldSigs.length; i++) { + MemberSignature sig = fieldSigs[i]; + int mods = sig.member.getModifiers() & + (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED | + Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE | + Modifier.TRANSIENT); + if (((mods & Modifier.PRIVATE) == 0) || + ((mods & (Modifier.STATIC | Modifier.TRANSIENT)) == 0)) + { + dout.writeUTF(sig.name); + dout.writeInt(mods); + dout.writeUTF(sig.signature); + } + } + + if (hasStaticInitializer(cl)) { + dout.writeUTF(""); + dout.writeInt(Modifier.STATIC); + dout.writeUTF("()V"); + } + + Constructor[] cons = cl.getDeclaredConstructors(); + MemberSignature[] consSigs = new MemberSignature[cons.length]; + for (int i = 0; i < cons.length; i++) { + consSigs[i] = new MemberSignature(cons[i]); + } + Arrays.sort(consSigs, new Comparator<>() { + public int compare(MemberSignature ms1, MemberSignature ms2) { + return ms1.signature.compareTo(ms2.signature); + } + }); + for (int i = 0; i < consSigs.length; i++) { + MemberSignature sig = consSigs[i]; + int mods = sig.member.getModifiers() & + (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED | + Modifier.STATIC | Modifier.FINAL | + Modifier.SYNCHRONIZED | Modifier.NATIVE | + Modifier.ABSTRACT | Modifier.STRICT); + if ((mods & Modifier.PRIVATE) == 0) { + dout.writeUTF(""); + dout.writeInt(mods); + dout.writeUTF(sig.signature.replace('/', '.')); + } + } + + MemberSignature[] methSigs = new MemberSignature[methods.length]; + for (int i = 0; i < methods.length; i++) { + methSigs[i] = new MemberSignature(methods[i]); + } + Arrays.sort(methSigs, new Comparator<>() { + public int compare(MemberSignature ms1, MemberSignature ms2) { + int comp = ms1.name.compareTo(ms2.name); + if (comp == 0) { + comp = ms1.signature.compareTo(ms2.signature); + } + return comp; + } + }); + for (int i = 0; i < methSigs.length; i++) { + MemberSignature sig = methSigs[i]; + int mods = sig.member.getModifiers() & + (Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED | + Modifier.STATIC | Modifier.FINAL | + Modifier.SYNCHRONIZED | Modifier.NATIVE | + Modifier.ABSTRACT | Modifier.STRICT); + if ((mods & Modifier.PRIVATE) == 0) { + dout.writeUTF(sig.name); + dout.writeInt(mods); + dout.writeUTF(sig.signature.replace('/', '.')); + } + } + + dout.flush(); + + MessageDigest md = MessageDigest.getInstance("SHA"); + byte[] hashBytes = md.digest(bout.toByteArray()); + long hash = 0; + for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) { + hash = (hash << 8) | (hashBytes[i] & 0xFF); + } + return hash; + } catch (IOException ex) { + throw new InternalError(ex); + } catch (NoSuchAlgorithmException ex) { + throw new SecurityException(ex.getMessage()); + } + } + + /** + * Returns true if the given class defines a static initializer method, + * false otherwise. + */ + private static native boolean hasStaticInitializer(Class cl); + + /** + * Class for computing and caching field/constructor/method signatures + * during serialVersionUID calculation. + */ + private static final class MemberSignature { + + public final Member member; + public final String name; + public final String signature; + + public MemberSignature(Field field) { + member = field; + name = field.getName(); + signature = field.getType().descriptorString(); + } + + public MemberSignature(Constructor cons) { + member = cons; + name = cons.getName(); + signature = getMethodSignature( + cons.getParameterTypes(), Void.TYPE); + } + + public MemberSignature(Method meth) { + member = meth; + name = meth.getName(); + signature = getMethodSignature( + meth.getParameterTypes(), meth.getReturnType()); + } + } + + /** + * Class for setting and retrieving serializable field values in batch. + */ + // REMIND: dynamically generate these? + private static final class FieldReflector { + + /** handle for performing unsafe operations */ + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + /** fields to operate on */ + private final ObjectStreamField[] fields; + /** number of primitive fields */ + private final int numPrimFields; + /** unsafe field keys for reading fields - may contain dupes */ + private final long[] readKeys; + /** unsafe fields keys for writing fields - no dupes */ + private final long[] writeKeys; + /** field data offsets */ + private final int[] offsets; + /** field type codes */ + private final char[] typeCodes; + /** field types */ + private final Class[] types; + + /** + * Constructs FieldReflector capable of setting/getting values from the + * subset of fields whose ObjectStreamFields contain non-null + * reflective Field objects. ObjectStreamFields with null Fields are + * treated as filler, for which get operations return default values + * and set operations discard given values. + */ + FieldReflector(ObjectStreamField[] fields) { + this.fields = fields; + int nfields = fields.length; + readKeys = new long[nfields]; + writeKeys = new long[nfields]; + offsets = new int[nfields]; + typeCodes = new char[nfields]; + ArrayList> typeList = new ArrayList<>(); + Set usedKeys = new HashSet<>(); + + + for (int i = 0; i < nfields; i++) { + ObjectStreamField f = fields[i]; + Field rf = f.getField(); + long key = (rf != null) ? + UNSAFE.objectFieldOffset(rf) : Unsafe.INVALID_FIELD_OFFSET; + readKeys[i] = key; + writeKeys[i] = usedKeys.add(key) ? + key : Unsafe.INVALID_FIELD_OFFSET; + offsets[i] = f.getOffset(); + typeCodes[i] = f.getTypeCode(); + if (!f.isPrimitive()) { + typeList.add((rf != null) ? rf.getType() : null); + } + } + + types = typeList.toArray(new Class[typeList.size()]); + numPrimFields = nfields - types.length; + } + + /** + * Returns list of ObjectStreamFields representing fields operated on + * by this reflector. The shared/unshared values and Field objects + * contained by ObjectStreamFields in the list reflect their bindings + * to locally defined serializable fields. + */ + ObjectStreamField[] getFields() { + return fields; + } + + /** + * Fetches the serializable primitive field values of object obj and + * marshals them into byte array buf starting at offset 0. The caller + * is responsible for ensuring that obj is of the proper type. + */ + void getPrimFieldValues(Object obj, byte[] buf) { + if (obj == null) { + throw new NullPointerException(); + } + /* assuming checkDefaultSerialize() has been called on the class + * descriptor this FieldReflector was obtained from, no field keys + * in array should be equal to Unsafe.INVALID_FIELD_OFFSET. + */ + for (int i = 0; i < numPrimFields; i++) { + long key = readKeys[i]; + int off = offsets[i]; + switch (typeCodes[i]) { + case 'Z' -> ByteArray.setBoolean(buf, off, UNSAFE.getBoolean(obj, key)); + case 'B' -> buf[off] = UNSAFE.getByte(obj, key); + case 'C' -> ByteArray.setChar(buf, off, UNSAFE.getChar(obj, key)); + case 'S' -> ByteArray.setShort(buf, off, UNSAFE.getShort(obj, key)); + case 'I' -> ByteArray.setInt(buf, off, UNSAFE.getInt(obj, key)); + case 'F' -> ByteArray.setFloat(buf, off, UNSAFE.getFloat(obj, key)); + case 'J' -> ByteArray.setLong(buf, off, UNSAFE.getLong(obj, key)); + case 'D' -> ByteArray.setDouble(buf, off, UNSAFE.getDouble(obj, key)); + default -> throw new InternalError(); + } + } + } + + /** + * Sets the serializable primitive fields of object obj using values + * unmarshalled from byte array buf starting at offset 0. The caller + * is responsible for ensuring that obj is of the proper type. + */ + void setPrimFieldValues(Object obj, byte[] buf) { + if (obj == null) { + throw new NullPointerException(); + } + for (int i = 0; i < numPrimFields; i++) { + long key = writeKeys[i]; + if (key == Unsafe.INVALID_FIELD_OFFSET) { + continue; // discard value + } + int off = offsets[i]; + switch (typeCodes[i]) { + case 'Z' -> UNSAFE.putBoolean(obj, key, ByteArray.getBoolean(buf, off)); + case 'B' -> UNSAFE.putByte(obj, key, buf[off]); + case 'C' -> UNSAFE.putChar(obj, key, ByteArray.getChar(buf, off)); + case 'S' -> UNSAFE.putShort(obj, key, ByteArray.getShort(buf, off)); + case 'I' -> UNSAFE.putInt(obj, key, ByteArray.getInt(buf, off)); + case 'F' -> UNSAFE.putFloat(obj, key, ByteArray.getFloat(buf, off)); + case 'J' -> UNSAFE.putLong(obj, key, ByteArray.getLong(buf, off)); + case 'D' -> UNSAFE.putDouble(obj, key, ByteArray.getDouble(buf, off)); + default -> throw new InternalError(); + } + } + } + + /** + * Fetches the serializable object field values of object obj and + * stores them in array vals starting at offset 0. The caller is + * responsible for ensuring that obj is of the proper type. + */ + void getObjFieldValues(Object obj, Object[] vals) { + if (obj == null) { + throw new NullPointerException(); + } + /* assuming checkDefaultSerialize() has been called on the class + * descriptor this FieldReflector was obtained from, no field keys + * in array should be equal to Unsafe.INVALID_FIELD_OFFSET. + */ + for (int i = numPrimFields; i < fields.length; i++) { + vals[offsets[i]] = switch (typeCodes[i]) { + case 'L', '[' -> UNSAFE.getReference(obj, readKeys[i]); + default -> throw new InternalError(); + }; + } + } + + /** + * Checks that the given values, from array vals starting at offset 0, + * are assignable to the given serializable object fields. + * @throws ClassCastException if any value is not assignable + */ + void checkObjectFieldValueTypes(Object obj, Object[] vals) { + setObjFieldValues(obj, vals, true); + } + + /** + * Sets the serializable object fields of object obj using values from + * array vals starting at offset 0. The caller is responsible for + * ensuring that obj is of the proper type; however, attempts to set a + * field with a value of the wrong type will trigger an appropriate + * ClassCastException. + */ + void setObjFieldValues(Object obj, Object[] vals) { + setObjFieldValues(obj, vals, false); + } + + private void setObjFieldValues(Object obj, Object[] vals, boolean dryRun) { + if (obj == null) { + throw new NullPointerException(); + } + for (int i = numPrimFields; i < fields.length; i++) { + long key = writeKeys[i]; + if (key == Unsafe.INVALID_FIELD_OFFSET) { + continue; // discard value + } + switch (typeCodes[i]) { + case 'L', '[' -> { + Object val = vals[offsets[i]]; + if (val != null && + !types[i - numPrimFields].isInstance(val)) + { + Field f = fields[i].getField(); + throw new ClassCastException( + "cannot assign instance of " + + val.getClass().getName() + " to field " + + f.getDeclaringClass().getName() + "." + + f.getName() + " of type " + + f.getType().getName() + " in instance of " + + obj.getClass().getName()); + } + if (!dryRun) + UNSAFE.putReference(obj, key, val); + } + default -> throw new InternalError(); + } + } + } + } + + /** + * Matches given set of serializable fields with serializable fields + * described by the given local class descriptor, and returns a + * FieldReflector instance capable of setting/getting values from the + * subset of fields that match (non-matching fields are treated as filler, + * for which get operations return default values and set operations + * discard given values). Throws InvalidClassException if unresolvable + * type conflicts exist between the two sets of fields. + */ + private static FieldReflector getReflector(ObjectStreamField[] fields, + ObjectStreamClass localDesc) + throws InvalidClassException + { + // class irrelevant if no fields + Class cl = (localDesc != null && fields.length > 0) ? + localDesc.cl : Void.class; + + var clReflectors = Caches.reflectors.get(cl); + var key = new FieldReflectorKey(fields); + var reflector = clReflectors.get(key); + if (reflector == null) { + reflector = new FieldReflector(matchFields(fields, localDesc)); + var oldReflector = clReflectors.putIfAbsent(key, reflector); + if (oldReflector != null) { + reflector = oldReflector; + } + } + return reflector; + } + + /** + * FieldReflector cache lookup key. Keys are considered equal if they + * refer to equivalent field formats. + */ + private static class FieldReflectorKey { + + private final String[] sigs; + private final int hash; + + FieldReflectorKey(ObjectStreamField[] fields) + { + sigs = new String[2 * fields.length]; + for (int i = 0, j = 0; i < fields.length; i++) { + ObjectStreamField f = fields[i]; + sigs[j++] = f.getName(); + sigs[j++] = f.getSignature(); + } + hash = Arrays.hashCode(sigs); + } + + public int hashCode() { + return hash; + } + + public boolean equals(Object obj) { + return obj == this || + obj instanceof FieldReflectorKey other && + Arrays.equals(sigs, other.sigs); + } + } + + /** + * Matches given set of serializable fields with serializable fields + * obtained from the given local class descriptor (which contain bindings + * to reflective Field objects). Returns list of ObjectStreamFields in + * which each ObjectStreamField whose signature matches that of a local + * field contains a Field object for that field; unmatched + * ObjectStreamFields contain null Field objects. Shared/unshared settings + * of the returned ObjectStreamFields also reflect those of matched local + * ObjectStreamFields. Throws InvalidClassException if unresolvable type + * conflicts exist between the two sets of fields. + */ + private static ObjectStreamField[] matchFields(ObjectStreamField[] fields, + ObjectStreamClass localDesc) + throws InvalidClassException + { + ObjectStreamField[] localFields = (localDesc != null) ? + localDesc.fields : NO_FIELDS; + + /* + * Even if fields == localFields, we cannot simply return localFields + * here. In previous implementations of serialization, + * ObjectStreamField.getType() returned Object.class if the + * ObjectStreamField represented a non-primitive field and belonged to + * a non-local class descriptor. To preserve this (questionable) + * behavior, the ObjectStreamField instances returned by matchFields + * cannot report non-primitive types other than Object.class; hence + * localFields cannot be returned directly. + */ + + ObjectStreamField[] matches = new ObjectStreamField[fields.length]; + for (int i = 0; i < fields.length; i++) { + ObjectStreamField f = fields[i], m = null; + for (int j = 0; j < localFields.length; j++) { + ObjectStreamField lf = localFields[j]; + if (f.getName().equals(lf.getName())) { + if ((f.isPrimitive() || lf.isPrimitive()) && + f.getTypeCode() != lf.getTypeCode()) + { + throw new InvalidClassException(localDesc.name, + "incompatible types for field " + f.getName()); + } + if (lf.getField() != null) { + m = new ObjectStreamField( + lf.getField(), lf.isUnshared(), false); + } else { + m = new ObjectStreamField( + lf.getName(), lf.getSignature(), lf.isUnshared()); + } + } + } + if (m == null) { + m = new ObjectStreamField( + f.getName(), f.getSignature(), false); + } + m.setOffset(f.getOffset()); + matches[i] = m; + } + return matches; + } + + /** + * A LRA cache of record deserialization constructors. + */ + @SuppressWarnings("serial") + private static final class DeserializationConstructorsCache + extends ConcurrentHashMap { + + // keep max. 10 cached entries - when the 11th element is inserted the oldest + // is removed and 10 remains - 11 is the biggest map size where internal + // table of 16 elements is sufficient (inserting 12th element would resize it to 32) + private static final int MAX_SIZE = 10; + private Key.Impl first, last; // first and last in FIFO queue + + DeserializationConstructorsCache() { + // start small - if there is more than one shape of ObjectStreamClass + // deserialized, there will typically be two (current version and previous version) + super(2); + } + + MethodHandle get(ObjectStreamField[] fields) { + return get(new Key.Lookup(fields)); + } + + synchronized MethodHandle putIfAbsentAndGet(ObjectStreamField[] fields, MethodHandle mh) { + Key.Impl key = new Key.Impl(fields); + var oldMh = putIfAbsent(key, mh); + if (oldMh != null) return oldMh; + // else we did insert new entry -> link the new key as last + if (last == null) { + last = first = key; + } else { + last = (last.next = key); + } + // may need to remove first + if (size() > MAX_SIZE) { + assert first != null; + remove(first); + first = first.next; + if (first == null) { + last = null; + } + } + return mh; + } + + // a key composed of ObjectStreamField[] names and types + abstract static class Key { + abstract int length(); + abstract String fieldName(int i); + abstract Class fieldType(int i); + + @Override + public final int hashCode() { + int n = length(); + int h = 0; + for (int i = 0; i < n; i++) h = h * 31 + fieldType(i).hashCode(); + for (int i = 0; i < n; i++) h = h * 31 + fieldName(i).hashCode(); + return h; + } + + @Override + public final boolean equals(Object obj) { + if (!(obj instanceof Key other)) return false; + int n = length(); + if (n != other.length()) return false; + for (int i = 0; i < n; i++) if (fieldType(i) != other.fieldType(i)) return false; + for (int i = 0; i < n; i++) if (!fieldName(i).equals(other.fieldName(i))) return false; + return true; + } + + // lookup key - just wraps ObjectStreamField[] + static final class Lookup extends Key { + final ObjectStreamField[] fields; + + Lookup(ObjectStreamField[] fields) { this.fields = fields; } + + @Override + int length() { return fields.length; } + + @Override + String fieldName(int i) { return fields[i].getName(); } + + @Override + Class fieldType(int i) { return fields[i].getType(); } + } + + // real key - copies field names and types and forms FIFO queue in cache + static final class Impl extends Key { + Impl next; + final String[] fieldNames; + final Class[] fieldTypes; + + Impl(ObjectStreamField[] fields) { + this.fieldNames = new String[fields.length]; + this.fieldTypes = new Class[fields.length]; + for (int i = 0; i < fields.length; i++) { + fieldNames[i] = fields[i].getName(); + fieldTypes[i] = fields[i].getType(); + } + } + + @Override + int length() { return fieldNames.length; } + + @Override + String fieldName(int i) { return fieldNames[i]; } + + @Override + Class fieldType(int i) { return fieldTypes[i]; } + } + } + } + + /** Record specific support for retrieving and binding stream field values. */ + static final class RecordSupport { + /** + * Returns canonical record constructor adapted to take two arguments: + * {@code (byte[] primValues, Object[] objValues)} + * and return + * {@code Object} + */ + @SuppressWarnings("removal") + static MethodHandle deserializationCtr(ObjectStreamClass desc) { + // check the cached value 1st + MethodHandle mh = desc.deserializationCtr; + if (mh != null) return mh; + mh = desc.deserializationCtrs.get(desc.getFields(false)); + if (mh != null) return desc.deserializationCtr = mh; + + // retrieve record components + RecordComponent[] recordComponents; + try { + Class cls = desc.forClass(); + PrivilegedExceptionAction pa = cls::getRecordComponents; + recordComponents = AccessController.doPrivileged(pa); + } catch (PrivilegedActionException e) { + throw new InternalError(e.getCause()); + } + + // retrieve the canonical constructor + // (T1, T2, ..., Tn):TR + mh = desc.getRecordConstructor(); + + // change return type to Object + // (T1, T2, ..., Tn):TR -> (T1, T2, ..., Tn):Object + mh = mh.asType(mh.type().changeReturnType(Object.class)); + + // drop last 2 arguments representing primValues and objValues arrays + // (T1, T2, ..., Tn):Object -> (T1, T2, ..., Tn, byte[], Object[]):Object + mh = MethodHandles.dropArguments(mh, mh.type().parameterCount(), byte[].class, Object[].class); + + for (int i = recordComponents.length-1; i >= 0; i--) { + String name = recordComponents[i].getName(); + Class type = recordComponents[i].getType(); + // obtain stream field extractor that extracts argument at + // position i (Ti+1) from primValues and objValues arrays + // (byte[], Object[]):Ti+1 + MethodHandle combiner = streamFieldExtractor(name, type, desc); + // fold byte[] privValues and Object[] objValues into argument at position i (Ti+1) + // (..., Ti, Ti+1, byte[], Object[]):Object -> (..., Ti, byte[], Object[]):Object + mh = MethodHandles.foldArguments(mh, i, combiner); + } + // what we are left with is a MethodHandle taking just the primValues + // and objValues arrays and returning the constructed record instance + // (byte[], Object[]):Object + + // store it into cache and return the 1st value stored + return desc.deserializationCtr = + desc.deserializationCtrs.putIfAbsentAndGet(desc.getFields(false), mh); + } + + /** Returns the number of primitive fields for the given descriptor. */ + private static int numberPrimValues(ObjectStreamClass desc) { + ObjectStreamField[] fields = desc.getFields(); + int primValueCount = 0; + for (int i = 0; i < fields.length; i++) { + if (fields[i].isPrimitive()) + primValueCount++; + else + break; // can be no more + } + return primValueCount; + } + + /** + * Returns extractor MethodHandle taking the primValues and objValues arrays + * and extracting the argument of canonical constructor with given name and type + * or producing default value for the given type if the field is absent. + */ + private static MethodHandle streamFieldExtractor(String pName, + Class pType, + ObjectStreamClass desc) { + ObjectStreamField[] fields = desc.getFields(false); + + for (int i = 0; i < fields.length; i++) { + ObjectStreamField f = fields[i]; + String fName = f.getName(); + if (!fName.equals(pName)) + continue; + + Class fType = f.getField().getType(); + if (!pType.isAssignableFrom(fType)) + throw new InternalError(fName + " unassignable, pType:" + pType + ", fType:" + fType); + + if (f.isPrimitive()) { + // (byte[], int):fType + MethodHandle mh = PRIM_VALUE_EXTRACTORS.get(fType); + if (mh == null) { + throw new InternalError("Unexpected type: " + fType); + } + // bind offset + // (byte[], int):fType -> (byte[]):fType + mh = MethodHandles.insertArguments(mh, 1, f.getOffset()); + // drop objValues argument + // (byte[]):fType -> (byte[], Object[]):fType + mh = MethodHandles.dropArguments(mh, 1, Object[].class); + // adapt return type to pType + // (byte[], Object[]):fType -> (byte[], Object[]):pType + if (pType != fType) { + mh = mh.asType(mh.type().changeReturnType(pType)); + } + return mh; + } else { // reference + // (Object[], int):Object + MethodHandle mh = MethodHandles.arrayElementGetter(Object[].class); + // bind index + // (Object[], int):Object -> (Object[]):Object + mh = MethodHandles.insertArguments(mh, 1, i - numberPrimValues(desc)); + // drop primValues argument + // (Object[]):Object -> (byte[], Object[]):Object + mh = MethodHandles.dropArguments(mh, 0, byte[].class); + // adapt return type to pType + // (byte[], Object[]):Object -> (byte[], Object[]):pType + if (pType != Object.class) { + mh = mh.asType(mh.type().changeReturnType(pType)); + } + return mh; + } + } + + // return default value extractor if no field matches pName + return MethodHandles.empty(MethodType.methodType(pType, byte[].class, Object[].class)); + } + + private static final Map, MethodHandle> PRIM_VALUE_EXTRACTORS; + static { + var lkp = MethodHandles.lookup(); + try { + PRIM_VALUE_EXTRACTORS = Map.of( + byte.class, MethodHandles.arrayElementGetter(byte[].class), + short.class, lkp.findStatic(ByteArray.class, "getShort", MethodType.methodType(short.class, byte[].class, int.class)), + int.class, lkp.findStatic(ByteArray.class, "getInt", MethodType.methodType(int.class, byte[].class, int.class)), + long.class, lkp.findStatic(ByteArray.class, "getLong", MethodType.methodType(long.class, byte[].class, int.class)), + float.class, lkp.findStatic(ByteArray.class, "getFloat", MethodType.methodType(float.class, byte[].class, int.class)), + double.class, lkp.findStatic(ByteArray.class, "getDouble", MethodType.methodType(double.class, byte[].class, int.class)), + char.class, lkp.findStatic(ByteArray.class, "getChar", MethodType.methodType(char.class, byte[].class, int.class)), + boolean.class, lkp.findStatic(ByteArray.class, "getBoolean", MethodType.methodType(boolean.class, byte[].class, int.class)) + ); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new InternalError("Can't lookup " + ByteArray.class.getName() + ".getXXX", e); + } + } + } +} + +/* + * Copyright (c) 1995, 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.net; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.channels.DatagramChannel; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; +import java.util.Enumeration; +import java.util.Objects; +import java.util.Set; +import java.util.Collections; + +/** + * A multicast datagram socket that delegates socket operations to a + * {@link DatagramSocketImpl}. + * + * This class overrides every public method defined by {@link DatagramSocket} + * and {@link MulticastSocket}. + */ +final class NetMulticastSocket extends MulticastSocket { + /** + * Various states of this socket. + */ + private boolean bound = false; + private boolean closed = false; + private volatile boolean created; + private final Object closeLock = new Object(); + + /* + * The implementation of this DatagramSocket. + */ + private final DatagramSocketImpl impl; + + /** + * Set when a socket is ST_CONNECTED until we are certain + * that any packets which might have been received prior + * to calling connect() but not read by the application + * have been read. During this time we check the source + * address of all packets received to be sure they are from + * the connected destination. Other packets are read but + * silently dropped. + */ + private boolean explicitFilter = false; + private int bytesLeftToFilter; + /* + * Connection state: + * ST_NOT_CONNECTED = socket not connected + * ST_CONNECTED = socket connected + */ + static final int ST_NOT_CONNECTED = 0; + static final int ST_CONNECTED = 1; + + int connectState = ST_NOT_CONNECTED; + + /* + * Connected address & port + */ + InetAddress connectedAddress = null; + int connectedPort = -1; + + /** + * This constructor is also used by {@link DatagramSocket#DatagramSocket(DatagramSocketImpl)}. + * @param impl The impl used in this instance. + */ + NetMulticastSocket(DatagramSocketImpl impl) { + super((MulticastSocket) null); + this.impl = Objects.requireNonNull(impl); + } + + /** + * Connects this socket to a remote socket address (IP address + port number). + * Binds socket if not already bound. + * + * @param address The remote address. + * @param port The remote port + * @throws SocketException if binding the socket fails. + */ + private synchronized void connectInternal(InetAddress address, int port) throws SocketException { + if (port < 0 || port > 0xFFFF) { + throw new IllegalArgumentException("connect: " + port); + } + if (address == null) { + throw new IllegalArgumentException("connect: null address"); + } + checkAddress(address, "connect"); + if (isClosed()) + return; + @SuppressWarnings("removal") + SecurityManager security = System.getSecurityManager(); + if (security != null) { + if (address.isMulticastAddress()) { + security.checkMulticast(address); + } else { + security.checkConnect(address.getHostAddress(), port); + security.checkAccept(address.getHostAddress(), port); + } + } + + if (port == 0) { + throw new SocketException("Can't connect to port 0"); + } + if (!isBound()) + bind(new InetSocketAddress(0)); + + getImpl().connect(address, port); + + // socket is now connected by the impl + connectState = ST_CONNECTED; + // Do we need to filter some packets? + int avail = getImpl().dataAvailable(); + if (avail == -1) { + throw new SocketException(); + } + explicitFilter = avail > 0; + if (explicitFilter) { + bytesLeftToFilter = getReceiveBufferSize(); + } + + connectedAddress = address; + connectedPort = port; + } + + /** + * Return the {@code DatagramSocketImpl} attached to this socket, + * creating the socket if not already created. + * + * @return the {@code DatagramSocketImpl} attached to that + * DatagramSocket + * @throws SocketException if creating the socket fails + * @since 1.4 + */ + final DatagramSocketImpl getImpl() throws SocketException { + if (!created) { + synchronized (this) { + if (!created) { + impl.create(); + created = true; + } + } + } + return impl; + } + + @Override + public synchronized void bind(SocketAddress addr) throws SocketException { + if (isClosed()) + throw new SocketException("Socket is closed"); + if (isBound()) + throw new SocketException("already bound"); + if (addr == null) + addr = new InetSocketAddress(0); + if (!(addr instanceof InetSocketAddress epoint)) + throw new IllegalArgumentException("Unsupported address type!"); + if (epoint.isUnresolved()) + throw new SocketException("Unresolved address"); + InetAddress iaddr = epoint.getAddress(); + int port = epoint.getPort(); + checkAddress(iaddr, "bind"); + @SuppressWarnings("removal") + SecurityManager sec = System.getSecurityManager(); + if (sec != null) { + sec.checkListen(port); + } + try { + getImpl().bind(port, iaddr); + } catch (SocketException e) { + getImpl().close(); + throw e; + } + bound = true; + } + + static void checkAddress(InetAddress addr, String op) { + if (addr == null) { + return; + } + if (!(addr instanceof Inet4Address || addr instanceof Inet6Address)) { + throw new IllegalArgumentException(op + ": invalid address type"); + } + } + + @Override + public void connect(InetAddress address, int port) { + try { + connectInternal(address, port); + } catch (SocketException se) { + throw new UncheckedIOException("connect failed", se); + } + } + + @Override + public void connect(SocketAddress addr) throws SocketException { + if (addr == null) + throw new IllegalArgumentException("Address can't be null"); + if (!(addr instanceof InetSocketAddress epoint)) + throw new IllegalArgumentException("Unsupported address type"); + if (epoint.isUnresolved()) + throw new SocketException("Unresolved address"); + connectInternal(epoint.getAddress(), epoint.getPort()); + } + + @Override + public void disconnect() { + synchronized (this) { + if (isClosed()) + return; + if (connectState == ST_CONNECTED) { + impl.disconnect(); + } + connectedAddress = null; + connectedPort = -1; + connectState = ST_NOT_CONNECTED; + explicitFilter = false; + } + } + + @Override + public boolean isBound() { + return bound; + } + + @Override + public boolean isConnected() { + return connectState != ST_NOT_CONNECTED; + } + + @Override + public InetAddress getInetAddress() { + return connectedAddress; + } + + @Override + public int getPort() { + return connectedPort; + } + + @Override + public SocketAddress getRemoteSocketAddress() { + if (!isConnected()) + return null; + return new InetSocketAddress(getInetAddress(), getPort()); + } + + @Override + public SocketAddress getLocalSocketAddress() { + if (isClosed()) + return null; + if (!isBound()) + return null; + return new InetSocketAddress(getLocalAddress(), getLocalPort()); + } + + @Override + public void send(DatagramPacket p) throws IOException { + synchronized (p) { + if (isClosed()) + throw new SocketException("Socket is closed"); + InetAddress packetAddress = p.getAddress(); + int packetPort = p.getPort(); + checkAddress(packetAddress, "send"); + if (connectState == ST_NOT_CONNECTED) { + if (packetAddress == null) { + throw new IllegalArgumentException("Address not set"); + } + if (packetPort < 0 || packetPort > 0xFFFF) + throw new IllegalArgumentException("port out of range: " + packetPort); + // check the address is ok with the security manager on every send. + @SuppressWarnings("removal") + SecurityManager security = System.getSecurityManager(); + + // The reason you want to synchronize on datagram packet + // is because you don't want an applet to change the address + // while you are trying to send the packet for example + // after the security check but before the send. + if (security != null) { + if (packetAddress.isMulticastAddress()) { + security.checkMulticast(packetAddress); + } else { + security.checkConnect(packetAddress.getHostAddress(), + packetPort); + } + } + if (packetPort == 0) { + throw new SocketException("Can't send to port 0"); + } + } else { + // we're connected + if (packetAddress == null) { + p.setAddress(connectedAddress); + p.setPort(connectedPort); + } else if ((!packetAddress.equals(connectedAddress)) || + packetPort != connectedPort) { + throw new IllegalArgumentException("connected address " + + "and packet address" + + " differ"); + } + } + // Check whether the socket is bound + if (!isBound()) + bind(new InetSocketAddress(0)); + // call the method to send + getImpl().send(p); + } + } + + @Override + public synchronized void receive(DatagramPacket p) throws IOException { + synchronized (p) { + if (!isBound()) + bind(new InetSocketAddress(0)); + if (connectState == ST_NOT_CONNECTED) { + // check the address is ok with the security manager before every recv. + @SuppressWarnings("removal") + SecurityManager security = System.getSecurityManager(); + if (security != null) { + while (true) { + int peekPort = 0; + // peek at the packet to see who it is from. + DatagramPacket peekPacket = new DatagramPacket(new byte[1], 1); + peekPort = getImpl().peekData(peekPacket); + String peekAd = peekPacket.getAddress().getHostAddress(); + try { + security.checkAccept(peekAd, peekPort); + // security check succeeded - so now break + // and recv the packet. + break; + } catch (SecurityException se) { + // Throw away the offending packet by consuming + // it in a tmp buffer. + DatagramPacket tmp = new DatagramPacket(new byte[1], 1); + getImpl().receive(tmp); + + // silently discard the offending packet + // and continue: unknown/malicious + // entities on nets should not make + // runtime throw security exception and + // disrupt the applet by sending random + // datagram packets. + continue; + } + } // end of while + } + } + DatagramPacket tmp = null; + if (explicitFilter) { + // We have to do the filtering the old fashioned way since + // the native impl doesn't support connect or the connect + // via the impl failed, or .. "explicitFilter" may be set when + // a socket is connected via the impl, for a period of time + // when packets from other sources might be queued on socket. + boolean stop = false; + while (!stop) { + // peek at the packet to see who it is from. + DatagramPacket peekPacket = new DatagramPacket(new byte[1], 1); + int peekPort = getImpl().peekData(peekPacket); + InetAddress peekAddress = peekPacket.getAddress(); + if ((!connectedAddress.equals(peekAddress)) || (connectedPort != peekPort)) { + // throw the packet away and silently continue + tmp = new DatagramPacket( + new byte[1024], 1024); + getImpl().receive(tmp); + if (explicitFilter) { + if (checkFiltering(tmp)) { + stop = true; + } + } + } else { + stop = true; + } + } + } + // If the security check succeeds, or the datagram is + // connected then receive the packet + getImpl().receive(p); + if (explicitFilter && tmp == null) { + // packet was not filtered, account for it here + checkFiltering(p); + } + } + } + + private boolean checkFiltering(DatagramPacket p) throws SocketException { + bytesLeftToFilter -= p.getLength(); + if (bytesLeftToFilter <= 0 || getImpl().dataAvailable() <= 0) { + explicitFilter = false; + return true; + } + return false; + } + + @Override + public InetAddress getLocalAddress() { + if (isClosed()) + return null; + InetAddress in; + try { + in = (InetAddress) getImpl().getOption(SocketOptions.SO_BINDADDR); + if (in.isAnyLocalAddress()) { + in = InetAddress.anyLocalAddress(); + } + @SuppressWarnings("removal") + SecurityManager s = System.getSecurityManager(); + if (s != null) { + s.checkConnect(in.getHostAddress(), -1); + } + } catch (Exception e) { + in = InetAddress.anyLocalAddress(); // "0.0.0.0" + } + return in; + } + + @Override + public int getLocalPort() { + if (isClosed()) + return -1; + try { + return getImpl().getLocalPort(); + } catch (Exception e) { + return 0; + } + } + + @Override + public synchronized void setSoTimeout(int timeout) throws SocketException { + if (isClosed()) + throw new SocketException("Socket is closed"); + if (timeout < 0) + throw new IllegalArgumentException("timeout < 0"); + getImpl().setOption(SocketOptions.SO_TIMEOUT, timeout); + } + + @Override + public synchronized int getSoTimeout() throws SocketException { + if (isClosed()) + throw new SocketException("Socket is closed"); + if (getImpl() == null) + return 0; + Object o = getImpl().getOption(SocketOptions.SO_TIMEOUT); + /* extra type safety */ + if (o instanceof Integer) { + return ((Integer) o).intValue(); + } else { + return 0; + } + } + + @Override + public synchronized void setSendBufferSize(int size) throws SocketException { + if (!(size > 0)) { + throw new IllegalArgumentException("negative send size"); + } + if (isClosed()) + throw new SocketException("Socket is closed"); + getImpl().setOption(SocketOptions.SO_SNDBUF, size); + } + + @Override + public synchronized int getSendBufferSize() throws SocketException { + if (isClosed()) + throw new SocketException("Socket is closed"); + int result = 0; + Object o = getImpl().getOption(SocketOptions.SO_SNDBUF); + if (o instanceof Integer) { + result = ((Integer) o).intValue(); + } + return result; + } + + @Override + public synchronized void setReceiveBufferSize(int size) throws SocketException { + if (size <= 0) { + throw new IllegalArgumentException("invalid receive size"); + } + if (isClosed()) + throw new SocketException("Socket is closed"); + getImpl().setOption(SocketOptions.SO_RCVBUF, size); + } + + @Override + public synchronized int getReceiveBufferSize() throws SocketException { + if (isClosed()) + throw new SocketException("Socket is closed"); + int result = 0; + Object o = getImpl().getOption(SocketOptions.SO_RCVBUF); + if (o instanceof Integer) { + result = ((Integer) o).intValue(); + } + return result; + } + + @Override + public synchronized void setReuseAddress(boolean on) throws SocketException { + if (isClosed()) + throw new SocketException("Socket is closed"); + getImpl().setOption(SocketOptions.SO_REUSEADDR, Boolean.valueOf(on)); + } + + @Override + public synchronized boolean getReuseAddress() throws SocketException { + if (isClosed()) + throw new SocketException("Socket is closed"); + Object o = getImpl().getOption(SocketOptions.SO_REUSEADDR); + return ((Boolean) o).booleanValue(); + } + + @Override + public synchronized void setBroadcast(boolean on) throws SocketException { + if (isClosed()) + throw new SocketException("Socket is closed"); + getImpl().setOption(SocketOptions.SO_BROADCAST, Boolean.valueOf(on)); + } + + @Override + public synchronized boolean getBroadcast() throws SocketException { + if (isClosed()) + throw new SocketException("Socket is closed"); + return ((Boolean) (getImpl().getOption(SocketOptions.SO_BROADCAST))).booleanValue(); + } + + @Override + public synchronized void setTrafficClass(int tc) throws SocketException { + if (tc < 0 || tc > 255) + throw new IllegalArgumentException("tc is not in range 0 -- 255"); + + if (isClosed()) + throw new SocketException("Socket is closed"); + try { + getImpl().setOption(SocketOptions.IP_TOS, tc); + } catch (SocketException se) { + // not supported if socket already connected + // Solaris returns error in such cases + if (!isConnected()) + throw se; + } + } + + @Override + public synchronized int getTrafficClass() throws SocketException { + if (isClosed()) + throw new SocketException("Socket is closed"); + return ((Integer) (getImpl().getOption(SocketOptions.IP_TOS))).intValue(); + } + + @Override + public void close() { + synchronized (closeLock) { + if (isClosed()) + return; + impl.close(); + closed = true; + } + } + + @Override + public boolean isClosed() { + synchronized (closeLock) { + return closed; + } + } + + @Override + public DatagramSocket setOption(SocketOption name, T value) + throws IOException + { + Objects.requireNonNull(name); + if (isClosed()) + throw new SocketException("Socket is closed"); + getImpl().setOption(name, value); + return this; + } + + @Override + public T getOption(SocketOption name) throws IOException { + Objects.requireNonNull(name); + if (isClosed()) + throw new SocketException("Socket is closed"); + return getImpl().getOption(name); + } + + private volatile Set> options; + private final Object optionsLock = new Object(); + + @Override + public Set> supportedOptions() { + Set> options = this.options; + if (options != null) + return options; + synchronized (optionsLock) { + options = this.options; + if (options != null) { + return options; + } + try { + DatagramSocketImpl impl = getImpl(); + options = Collections.unmodifiableSet(impl.supportedOptions()); + } catch (IOException e) { + options = Collections.emptySet(); + } + return this.options = options; + } + } + + // Multicast socket support + + /** + * Used on some platforms to record if an outgoing interface + * has been set for this socket. + */ + private boolean interfaceSet; + + /** + * The lock on the socket's TTL. This is for set/getTTL and + * send(packet,ttl). + */ + private final Object ttlLock = new Object(); + + /** + * The lock on the socket's interface - used by setInterface + * and getInterface + */ + private final Object infLock = new Object(); + + /** + * The "last" interface set by setInterface on this MulticastSocket + */ + private InetAddress infAddress = null; + + @Deprecated + @Override + public void setTTL(byte ttl) throws IOException { + if (isClosed()) + throw new SocketException("Socket is closed"); + getImpl().setTTL(ttl); + } + + @Override + public void setTimeToLive(int ttl) throws IOException { + if (ttl < 0 || ttl > 255) { + throw new IllegalArgumentException("ttl out of range"); + } + if (isClosed()) + throw new SocketException("Socket is closed"); + getImpl().setTimeToLive(ttl); + } + + @Deprecated + @Override + public byte getTTL() throws IOException { + if (isClosed()) + throw new SocketException("Socket is closed"); + return getImpl().getTTL(); + } + + @Override + public int getTimeToLive() throws IOException { + if (isClosed()) + throw new SocketException("Socket is closed"); + return getImpl().getTimeToLive(); + } + + @Override + @Deprecated + public void joinGroup(InetAddress mcastaddr) throws IOException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + + checkAddress(mcastaddr, "joinGroup"); + @SuppressWarnings("removal") + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkMulticast(mcastaddr); + } + + if (!mcastaddr.isMulticastAddress()) { + throw new SocketException("Not a multicast address"); + } + + /** + * required for some platforms where it's not possible to join + * a group without setting the interface first. + */ + NetworkInterface defaultInterface = NetworkInterface.getDefault(); + + if (!interfaceSet && defaultInterface != null) { + setNetworkInterface(defaultInterface); + } + + getImpl().join(mcastaddr); + } + + @Override + @Deprecated + public void leaveGroup(InetAddress mcastaddr) throws IOException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + + checkAddress(mcastaddr, "leaveGroup"); + @SuppressWarnings("removal") + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkMulticast(mcastaddr); + } + + if (!mcastaddr.isMulticastAddress()) { + throw new SocketException("Not a multicast address"); + } + + getImpl().leave(mcastaddr); + } + + @Override + public void joinGroup(SocketAddress mcastaddr, NetworkInterface netIf) + throws IOException { + if (isClosed()) + throw new SocketException("Socket is closed"); + + if (!(mcastaddr instanceof InetSocketAddress addr)) + throw new IllegalArgumentException("Unsupported address type"); + + checkAddress(addr.getAddress(), "joinGroup"); + @SuppressWarnings("removal") + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkMulticast(addr.getAddress()); + } + + if (!addr.getAddress().isMulticastAddress()) { + throw new SocketException("Not a multicast address"); + } + + getImpl().joinGroup(mcastaddr, netIf); + } + + @Override + public void leaveGroup(SocketAddress mcastaddr, NetworkInterface netIf) + throws IOException { + if (isClosed()) + throw new SocketException("Socket is closed"); + + if (!(mcastaddr instanceof InetSocketAddress addr)) + throw new IllegalArgumentException("Unsupported address type"); + + checkAddress(addr.getAddress(), "leaveGroup"); + @SuppressWarnings("removal") + SecurityManager security = System.getSecurityManager(); + if (security != null) { + security.checkMulticast(addr.getAddress()); + } + + if (!addr.getAddress().isMulticastAddress()) { + throw new SocketException("Not a multicast address"); + } + + getImpl().leaveGroup(mcastaddr, netIf); + } + + @Override + @Deprecated + public void setInterface(InetAddress inf) throws SocketException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + checkAddress(inf, "setInterface"); + synchronized (infLock) { + getImpl().setOption(SocketOptions.IP_MULTICAST_IF, inf); + infAddress = inf; + interfaceSet = true; + } + } + + @Override + @Deprecated + public InetAddress getInterface() throws SocketException { + if (isClosed()) { + throw new SocketException("Socket is closed"); + } + synchronized (infLock) { + InetAddress ia = + (InetAddress)getImpl().getOption(SocketOptions.IP_MULTICAST_IF); + + /** + * No previous setInterface or interface can be + * set using setNetworkInterface + */ + if (infAddress == null) { + return ia; + } + + /** + * Same interface set with setInterface? + */ + if (ia.equals(infAddress)) { + return ia; + } + + /** + * Different InetAddress from what we set with setInterface + * so enumerate the current interface to see if the + * address set by setInterface is bound to this interface. + */ + try { + NetworkInterface ni = NetworkInterface.getByInetAddress(ia); + Enumeration addrs = ni.getInetAddresses(); + while (addrs.hasMoreElements()) { + InetAddress addr = addrs.nextElement(); + if (addr.equals(infAddress)) { + return infAddress; + } + } + + /** + * No match so reset infAddress to indicate that the + * interface has changed via means + */ + infAddress = null; + return ia; + } catch (Exception e) { + return ia; + } + } + } + + @Override + public void setNetworkInterface(NetworkInterface netIf) + throws SocketException { + + synchronized (infLock) { + getImpl().setOption(SocketOptions.IP_MULTICAST_IF2, netIf); + infAddress = null; + interfaceSet = true; + } + } + + @Override + public NetworkInterface getNetworkInterface() throws SocketException { + NetworkInterface ni + = (NetworkInterface)getImpl().getOption(SocketOptions.IP_MULTICAST_IF2); + if (ni == null) { + InetAddress[] addrs = new InetAddress[1]; + addrs[0] = InetAddress.anyLocalAddress(); + return new NetworkInterface(addrs[0].getHostName(), 0, addrs); + } else { + return ni; + } + } + + @Override + @Deprecated + public void setLoopbackMode(boolean disable) throws SocketException { + getImpl().setOption(SocketOptions.IP_MULTICAST_LOOP, Boolean.valueOf(disable)); + } + + @Override + @Deprecated + public boolean getLoopbackMode() throws SocketException { + return ((Boolean)getImpl().getOption(SocketOptions.IP_MULTICAST_LOOP)).booleanValue(); + } + + @SuppressWarnings("removal") + @Deprecated + @Override + public void send(DatagramPacket p, byte ttl) + throws IOException { + if (isClosed()) + throw new SocketException("Socket is closed"); + synchronized(ttlLock) { + synchronized(p) { + InetAddress packetAddress = p.getAddress(); + checkAddress(packetAddress, "send"); + if (connectState == NetMulticastSocket.ST_NOT_CONNECTED) { + if (packetAddress == null) { + throw new IllegalArgumentException("Address not set"); + } + // Security manager makes sure that the multicast address + // is allowed one and that the ttl used is less + // than the allowed maxttl. + SecurityManager security = System.getSecurityManager(); + if (security != null) { + if (packetAddress.isMulticastAddress()) { + security.checkMulticast(packetAddress, ttl); + } else { + security.checkConnect(packetAddress.getHostAddress(), + p.getPort()); + } + } + } else { + // we're connected + if (packetAddress == null) { + p.setAddress(connectedAddress); + p.setPort(connectedPort); + } else if ((!packetAddress.equals(connectedAddress)) || + p.getPort() != connectedPort) { + throw new IllegalArgumentException("connected address and packet address" + + " differ"); + } + } + byte dttl = getTTL(); + try { + if (ttl != dttl) { + // set the ttl + getImpl().setTTL(ttl); + } + if (p.getPort() == 0) { + throw new SocketException("Can't send to port 0"); + } + // call the datagram method to send + getImpl().send(p); + } finally { + // set it back to default + if (ttl != dttl) { + getImpl().setTTL(dttl); + } + } + } // synch p + } //synch ttl + } //method +} + +/* + * Copyright (c) 1995, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.io; + +import java.util.Objects; + +/** + * A piped input stream should be connected + * to a piped output stream; the piped input + * stream then provides whatever data bytes + * are written to the piped output stream. + * Typically, data is read from a {@code PipedInputStream} + * object by one thread and data is written + * to the corresponding {@code PipedOutputStream} + * by some other thread. Attempting to use + * both objects from a single thread is not + * recommended, as it may deadlock the thread. + * The piped input stream contains a buffer, + * decoupling read operations from write operations, + * within limits. + * A pipe is said to be broken if a + * thread that was providing data bytes to the connected + * piped output stream is no longer alive. + * + * @author James Gosling + * @see java.io.PipedOutputStream + * @since 1.0 + */ +public class PipedInputStream extends InputStream { + boolean closedByWriter; + volatile boolean closedByReader; + boolean connected; + + /* REMIND: identification of the read and write sides needs to be + more sophisticated. Either using thread groups (but what about + pipes within a thread?) or using finalization (but it may be a + long time until the next GC). */ + Thread readSide; + Thread writeSide; + + private static final int DEFAULT_PIPE_SIZE = 1024; + + /** + * The default size of the pipe's circular input buffer. + * @since 1.1 + */ + // This used to be a constant before the pipe size was allowed + // to change. This field will continue to be maintained + // for backward compatibility. + protected static final int PIPE_SIZE = DEFAULT_PIPE_SIZE; + + /** + * The circular buffer into which incoming data is placed. + * @since 1.1 + */ + protected byte buffer[]; + + /** + * The index of the position in the circular buffer at which the + * next byte of data will be stored when received from the connected + * piped output stream. {@code in < 0} implies the buffer is empty, + * {@code in == out} implies the buffer is full + * @since 1.1 + */ + protected int in = -1; + + /** + * The index of the position in the circular buffer at which the next + * byte of data will be read by this piped input stream. + * @since 1.1 + */ + protected int out = 0; + + /** + * Creates a {@code PipedInputStream} so + * that it is connected to the piped output + * stream {@code src}. Data bytes written + * to {@code src} will then be available + * as input from this stream. + * + * @param src the stream to connect to. + * @throws IOException if an I/O error occurs. + */ + public PipedInputStream(PipedOutputStream src) throws IOException { + this(src, DEFAULT_PIPE_SIZE); + } + + /** + * Creates a {@code PipedInputStream} so that it is + * connected to the piped output stream + * {@code src} and uses the specified pipe size for + * the pipe's buffer. + * Data bytes written to {@code src} will then + * be available as input from this stream. + * + * @param src the stream to connect to. + * @param pipeSize the size of the pipe's buffer. + * @throws IOException if an I/O error occurs. + * @throws IllegalArgumentException if {@code pipeSize <= 0}. + * @since 1.6 + */ + public PipedInputStream(PipedOutputStream src, int pipeSize) + throws IOException { + initPipe(pipeSize); + connect(src); + } + + /** + * Creates a {@code PipedInputStream} so + * that it is not yet {@linkplain #connect(java.io.PipedOutputStream) + * connected}. + * It must be {@linkplain java.io.PipedOutputStream#connect( + * java.io.PipedInputStream) connected} to a + * {@code PipedOutputStream} before being used. + */ + public PipedInputStream() { + initPipe(DEFAULT_PIPE_SIZE); + } + + /** + * Creates a {@code PipedInputStream} so that it is not yet + * {@linkplain #connect(java.io.PipedOutputStream) connected} and + * uses the specified pipe size for the pipe's buffer. + * It must be {@linkplain java.io.PipedOutputStream#connect( + * java.io.PipedInputStream) + * connected} to a {@code PipedOutputStream} before being used. + * + * @param pipeSize the size of the pipe's buffer. + * @throws IllegalArgumentException if {@code pipeSize <= 0}. + * @since 1.6 + */ + public PipedInputStream(int pipeSize) { + initPipe(pipeSize); + } + + private void initPipe(int pipeSize) { + if (pipeSize <= 0) { + throw new IllegalArgumentException("Pipe Size <= 0"); + } + buffer = new byte[pipeSize]; + } + + /** + * Causes this piped input stream to be connected + * to the piped output stream {@code src}. + * If this object is already connected to some + * other piped output stream, an {@code IOException} + * is thrown. + *

+ * If {@code src} is an + * unconnected piped output stream and {@code snk} + * is an unconnected piped input stream, they + * may be connected by either the call: + * + * {@snippet lang=java : + * snk.connect(src) + * } + *

+ * or the call: + * + * {@snippet lang=java : + * src.connect(snk) + * } + *

+ * The two calls have the same effect. + * + * @param src The piped output stream to connect to. + * @throws IOException if an I/O error occurs. + */ + public void connect(PipedOutputStream src) throws IOException { + src.connect(this); + } + + /** + * Receives a byte of data. This method will block if no input is + * available. + * @param b the byte being received + * @throws IOException If the pipe is {@code broken}, + * {@link #connect(java.io.PipedOutputStream) unconnected}, + * closed, or if an I/O error occurs. + * @since 1.1 + */ + protected synchronized void receive(int b) throws IOException { + checkStateForReceive(); + writeSide = Thread.currentThread(); + if (in == out) + awaitSpace(); + if (in < 0) { + in = 0; + out = 0; + } + buffer[in++] = (byte)(b & 0xFF); + if (in >= buffer.length) { + in = 0; + } + } + + /** + * Receives data into an array of bytes. This method will + * block until some input is available. + * @param b the buffer into which the data is received + * @param off the start offset of the data + * @param len the maximum number of bytes received + * @throws IOException If the pipe is broken, + * {@link #connect(java.io.PipedOutputStream) unconnected}, + * closed, or if an I/O error occurs. + */ + synchronized void receive(byte[] b, int off, int len) throws IOException { + checkStateForReceive(); + writeSide = Thread.currentThread(); + int bytesToTransfer = len; + while (bytesToTransfer > 0) { + if (in == out) + awaitSpace(); + int nextTransferAmount = 0; + if (out < in) { + nextTransferAmount = buffer.length - in; + } else if (in < out) { + if (in == -1) { + in = out = 0; + nextTransferAmount = buffer.length - in; + } else { + nextTransferAmount = out - in; + } + } + if (nextTransferAmount > bytesToTransfer) + nextTransferAmount = bytesToTransfer; + assert(nextTransferAmount > 0); + System.arraycopy(b, off, buffer, in, nextTransferAmount); + bytesToTransfer -= nextTransferAmount; + off += nextTransferAmount; + in += nextTransferAmount; + if (in >= buffer.length) { + in = 0; + } + } + } + + private void checkStateForReceive() throws IOException { + if (!connected) { + throw new IOException("Pipe not connected"); + } else if (closedByWriter || closedByReader) { + throw new IOException("Pipe closed"); + } else if (readSide != null && !readSide.isAlive()) { + throw new IOException("Read end dead"); + } + } + + private void awaitSpace() throws IOException { + while (in == out) { + checkStateForReceive(); + + /* full: kick any waiting readers */ + notifyAll(); + try { + wait(1000); + } catch (InterruptedException ex) { + throw new java.io.InterruptedIOException(); + } + } + } + + /** + * Notifies all waiting threads that the last byte of data has been + * received. + */ + synchronized void receivedLast() { + closedByWriter = true; + notifyAll(); + } + + /** + * Reads the next byte of data from this piped input stream. The + * value byte is returned as an {@code int} in the range + * {@code 0} to {@code 255}. + * This method blocks until input data is available, the end of the + * stream is detected, or an exception is thrown. + * + * @return {@inheritDoc} + * @throws IOException if the pipe is + * {@link #connect(java.io.PipedOutputStream) unconnected}, + * {@code broken}, closed, + * or if an I/O error occurs. + */ + @Override + public synchronized int read() throws IOException { + if (!connected) { + throw new IOException("Pipe not connected"); + } else if (closedByReader) { + throw new IOException("Pipe closed"); + } else if (writeSide != null && !writeSide.isAlive() + && !closedByWriter && (in < 0)) { + throw new IOException("Write end dead"); + } + + readSide = Thread.currentThread(); + int trials = 2; + while (in < 0) { + if (closedByWriter) { + /* closed by writer, return EOF */ + return -1; + } + if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) { + throw new IOException("Pipe broken"); + } + /* might be a writer waiting */ + notifyAll(); + try { + wait(1000); + } catch (InterruptedException ex) { + throw new java.io.InterruptedIOException(); + } + } + int ret = buffer[out++] & 0xFF; + if (out >= buffer.length) { + out = 0; + } + if (in == out) { + /* now empty */ + in = -1; + } + + return ret; + } + + /** + * Reads up to {@code len} bytes of data from this piped input + * stream into an array of bytes. Less than {@code len} bytes + * will be read if the end of the data stream is reached or if + * {@code len} exceeds the pipe's buffer size. + * If {@code len } is zero, then no bytes are read and 0 is returned; + * otherwise, the method blocks until at least 1 byte of input is + * available, end of the stream has been detected, or an exception is + * thrown. + * + * @param b {@inheritDoc} + * @param off {@inheritDoc} + * @param len {@inheritDoc} + * @return {@inheritDoc} + * @throws NullPointerException {@inheritDoc} + * @throws IndexOutOfBoundsException {@inheritDoc} + * @throws IOException if the pipe is {@code broken}, + * {@link #connect(java.io.PipedOutputStream) unconnected}, + * closed, or if an I/O error occurs. + */ + @Override + public synchronized int read(byte[] b, int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } + Objects.checkFromIndexSize(off, len, b.length); + if (len == 0) { + return 0; + } + + /* possibly wait on the first character */ + int c = read(); + if (c < 0) { + return -1; + } + b[off] = (byte) c; + int rlen = 1; + while ((in >= 0) && (len > 1)) { + + int available; + + if (in > out) { + available = Math.min((buffer.length - out), (in - out)); + } else { + available = buffer.length - out; + } + + // A byte is read beforehand outside the loop + if (available > (len - 1)) { + available = len - 1; + } + System.arraycopy(buffer, out, b, off + rlen, available); + out += available; + rlen += available; + len -= available; + + if (out >= buffer.length) { + out = 0; + } + if (in == out) { + /* now empty */ + in = -1; + } + } + return rlen; + } + + /** + * Returns the number of bytes that can be read from this input + * stream without blocking. + * + * @return the number of bytes that can be read from this input stream + * without blocking, or {@code 0} if this input stream has been + * closed by invoking its {@link #close()} method, or if the pipe + * is {@link #connect(java.io.PipedOutputStream) unconnected}, or + * {@code broken}. + * + * @throws IOException {@inheritDoc} + * @since 1.0.2 + */ + @Override + public synchronized int available() throws IOException { + if(in < 0) + return 0; + else if(in == out) + return buffer.length; + else if (in > out) + return in - out; + else + return in + buffer.length - out; + } + + /** + * {@inheritDoc} + * + * @throws IOException {@inheritDoc} + */ + @Override + public void close() throws IOException { + closedByReader = true; + synchronized (this) { + in = -1; + } + } +} \ No newline at end of file diff --git a/ref_x86 b/ref_x86 new file mode 100644 index 0000000..5ade15c --- /dev/null +++ b/ref_x86 @@ -0,0 +1,5117 @@ +BITS 16 +org 0x7C00 +cli +xor ax, ax +mov ds, ax +mov es, ax +mov ss, ax +mov sp, 0x8000 +sti +mov ax, 3 +int 0x10 +mov bp, loading +mov ax, 0x1301 +mov bx, 7 +mov cx, 12 +mov dx, 0x0102 +int 0x10 +mov bl, 2 +mov ah, 2 +mov dx, 0x0201 +int 0x10 +mov ah, 9 +mov al, '.' +mov cx, 14 +int 0x10 +mov di, 0x0050 +mov ax, 19 +mov cx, 14 +call read +mov di, 0x0210 +mov ax, 1 +mov cx, 9 +call read +mov dx, 224 +mov bx, 0x0500 +search: + cld + mov si, filename + mov cx, 11 + mov di, bx + repe cmpsb + je found + add bx, 32 + dec dx + jz error + jmp search +read: + pusha + mov bl, 18 + div bl + mov cl, ah + inc cl + xor ah, ah + mov bl, 2 + div bl + mov ch, al + mov dh, ah + mov ax, cx + mov cx, 10 + .retry: + push es + push cx + mov cx, ax + push cx + xor ax, ax + push dx + int 0x13 + jc .failed + pop dx + pop cx + xor bx, bx + mov es, di + mov ax, 0x0201 + int 0x13 + jnc .success + .failed: + pop dx + pop ax + pop cx + pop es + loop .retry + mov bp, failure + mov ax, 0x1301 + mov bx, 4 + mov cx, 11 + mov dx, 0x0401 + int 0x10 + mov ah, 0 + int 0x16 + int 0x19 + .success: + pop cx + pop es + popa + add di, 32 + inc ax + loop read + ret +found: + mov bp, [bx+26] + mov di, 0x0800 + .block: + xor cx, cx + mov cl, 1 + mov si, bp + .contig: + mov ax, 3 + mul si + shr ax, 1 + mov bx, ax + mov ax, word [(0x2100+bx)] + jc .odd + and ax, 0x0FFF + jmp .next + .odd: + shr ax, 4 + .next: + inc si + cmp ax, si + jne .pass + inc cl + adc ch, 0 + jmp .contig + .pass: + xchg bp, ax + dec ax + dec ax + mov dx, 1 + mul dx + add ax, 33 + call read + cmp bp, 0x0FF8 + jb .block + +.386 +model flat + + extrn strlen:proc + extrn isalnum:proc + extrn isxdigit:proc + +.code + +; Short URL's are at most 12 characters long, and contain just +; alphanumeric characters. This validation has to be done, to +; evade any attempts of path traversal exploit. + +; Input: ecx +; Output: esi +; Registers preserved. +ValidateShortUrl proc near +; Push eax - we don't want to trash it after strlen() returns. + push eax +; As we don't want to alter ecx, preserve ebx - as it'll be a pointer we'll modify. + push ebx +; ebx = ecx; initialize the pointer + mov ebx, ecx +; eax = length of string pointed by ecx. + push ecx + call strlen +; Compare the length to 12 - if it's longer than 12, return a truthy value. + cmp eax, 12 + ja .skip +.loop: +; Move a byte pointed by ebx to esi and sign-extend it. + movsx esi, BYTE PTR [ebx] +; Check if esi is alphanumeric. + push esi + call isalnum +; Clean up after isalnum + add esp, 4 +; When isalnum returns 0 to eax, terminate the loop. + test eax, eax + je .skip +; Increment the pointer to point next character. + inc ebx + jmp .loop +.skip: +; Usual cleanup. + pop ecx + pop ebx + pop eax + ret +ValidateShortUrl endp + +; Adresses of various stuff on the stack. +; Will contain each character of a string. +character = 13 + +; First byte of %XX syntax. +b = 14 + +; Second byte of %XX syntax. +a = 15 + +; The same, but aligned to +12B stack frame +b12frame = 26 +a12frame = 27 + +; Will decode from edx to ecx +DecodeURL proc near +; As always, we don't want to overwrite edx and ecx, +; So make a copy of these registers and make use of another register. +; ecx (destination) => esi + push esi + mov esi, ecx +; edx (source) => ebx + push ebx + mov ebx, edx +; Reserve 20 bytes for a stack frame. + sub esp, 20 +.char_loop: +; Recall a byte from source, put it in al + mov al, BYTE PTR [ebx] +; Move it over to it's place in memory. + mov BYTE PTR [esp+character], al +; Quit if we already hit zero. + test al, al + je .quit +; Increment destination pointer now; will take care of it later. +; The code branches a couple of times and there are at least three +; ways it can loop; so one inc there will save space later on. + inc esi +; Did we hit %? If so, then we're about to parse a hex code. + cmp BYTE PTR [esp+character], '%' + jne .no_hexcode +; Recall first byte, store it in dl. + mov dl, BYTE PTR [ebx+1] + test dl, dl +; We implement a lax parser; if the client sends us incorrect urlencoded +; data we're going to deal with it anyway. This handles the case when % is +; at the end of the string. + je .invalid_percent +; Recall second byte, store it in cl. + mov cl, BYTE PTR [ebx+2] + test cl, cl +; Store b + mov BYTE PTR [esp+b], cl + je .invalid_percent +; We enter a new stack frame; therefore A adress is morphed and we'll write +; it to the memory only now. + sub esp, 12 +; Sign-extend dl to eax, to push it later for isxdigit. + movsx eax, dl + mov BYTE PTR [esp+a12frame], dl +; Invoke + push eax + call isxdigit +; Leave the stack frame. + add esp, 16 +; isxdigit returned zero? If so, we spotted an incorrect %XX code. + test eax, eax + je .invalid_percent +; The same code, but for B. + sub esp, 12 + movsx eax, BYTE PTR [esp+b12frame] + push eax + call isxdigit + add esp, 16 + test eax, eax + je .invalid_percent +; After all this; recall a and b values back. + mov dl, BYTE PTR [esp+a] + mov cl, BYTE PTR [esp+b] +; a lesser or equal to 'a'? + cmp dl, 'a' +; yep, move on. + jle .skip_acase +; Subtract 'a' - 'A' => 32. + sub edx, 32 +; a is still greater than 'A' (65), therefore we jump there. + jmp .gtA +; subtract '0' +.skip_acase: + lea eax, [edx-'0'] + cmp dl, 64 + jle .check_b +.gtA: +; Subtract 'A' - 10, that is, 55. + lea eax, [edx-55] +; The same procedure for this case; we just +; decode two hex digits. +.check_b: + cmp cl, 'a' + jle .maybe_gt + sub ecx, 32 + jmp .gtA2 +.maybe_gt: + lea edx, [ecx-'0'] + cmp cl, 64 + jle .make_number +.gtA2: + lea edx, [ecx-55] +.make_number: +; Make a number out of these; 16 * a + b; instead of multiplying +; we'll utilize leftshift. + sal eax, 4 + add eax, edx +; add 3 to source - we read 3 characters. + add ebx, 3 +; we incremented esi, therefore we reference [esi - 1]; store al there. + mov BYTE PTR [esi-1], al +; loop again + jmp .char_loop +.no_hexcode: +; No %XX, maybe we hit '+'? + cmp BYTE PTR [esp+character], '+' +; Nope; just copy over the character. + jne .invalid_percent +; Yep, replace it with a space. + mov BYTE PTR [esi-1], ' ' +; Increment the source pointer + inc ebx +; Loop again. + jmp .char_loop +.invalid_percent: +; Recall the character + mov al, BYTE PTR [esp+character] +; Increment the source pointer. + inc ebx +; Store the character back. + mov BYTE PTR [esi-1], al +; Loop again. + jmp .char_loop +.quit: +; Glue in the null-terminator. + mov BYTE PTR [esi], 0 +; And finally, leave the stackframe. + add esp, 20 + pop ebx + pop esi + ret +DecodeURL endp + + +format PE GUI 4.0 + +entry _start + +include 'win32ax.inc' + +; Node in memory: + +; ESI ESI+4 ESI+8 +; v v v +; +--------------------------+ +; | left | right | type | +; +--------------------------+ + +node.left equ 0 +node.right equ 4 +node.type equ 8 + +; SKI nodes +TYPE_S equ 0 +TYPE_K equ 1 +TYPE_I equ 2 + +; alloc_node node holding two other nodes. +TYPE_BI equ 3 + +section '.text' code readable executable writeable + ; Program entry point. + ; Create the dialog, hook up the dialog procedure, + ; enter an event loop. + proc _start + ; Create a heap, store it's handle in asmh. + invoke HeapCreate, 0, 0, 0 + mov DWORD [asmh], eax + ; Get the handle of the current module + invoke GetModuleHandleA, 0 + ; ... and use it to create a dialog box. + ; 1 here is the resource identifier for the form. + invoke CreateDialogParamA, eax, 1, 0, DialogProc, 0 + ; store the dialog handle in hDlg. + mov DWORD [hDlg], eax + ; show the dialog. + invoke ShowWindow, eax, SW_SHOW + ; window message loop. + .message_loop: + ; fetch the next message to msg. + invoke GetMessage, msg, 0, 0, 0 + ; GetMessage returns 0 => quit + test eax, eax + je .quit + ; if the return value != -1 + inc eax + jne .isdlg + ; return value == -1 => an error occured. + ; ExitProcess(1) + push 1 + jmp .die + .isdlg: + ; is it a dialog message? + invoke IsDialogMessageA, hDlg, msg + ; nope, ignore. + test eax, eax + jne .message_loop + ; Otherwise, translate and dispatch it. + invoke TranslateMessage, msg + invoke DispatchMessage, msg + jmp .message_loop + .quit: + ; ExitProcess(0) + push 0 + .die: + call [ExitProcess] + endp + + ; Dialog procedure - handling incoming messages. + proc DialogProc + ; Stack frame construction. + push ebp + mov ebp, esp + sub esp, 16 + mov edx, DWORD [ebp+12] + mov eax, DWORD [ebp+8] + mov ecx, DWORD [ebp+16] + ; handle WM_CLOSE + cmp edx, WM_CLOSE + je .close_handler + ; handle WM_COMMAND + cmp edx, WM_COMMAND + je .command_handler + ; don't handle everything not being WM_DESTROY + ; (return FALSE) + cmp edx, WM_DESTROY + jne .no_op + ; ... so we're handling WM_DESTROY here. + invoke PostQuitMessage, 0 + jmp .c_exit + .close_handler: + ; WM_CLOSE => pass around the WM_DESTROY message. + invoke DestroyWindow, eax + .c_exit: + ; common WM_DESTROY and WM_CLOSE fallthrough. + ; return TRUE. + xor ebx, ebx + inc ebx + ; the only way out is to + jmp .die + .command_handler: + ; 2 is the '&Quit' button ID. + ; If anything other has been pressed, branch. + cmp cx, 2 + jne .not_quit + ; Quit button pressed -> die + invoke DestroyWindow, eax + .no_op: + ; a RETURN FALSE stub for lacking handlers for + ; WM_COMMAND cases and unknown message ID's. + xor ebx, ebx + jmp .die + .not_quit: + ; '&Quit' wasn't pressed, so maybe it was '&Evaluate'? + ; return FALSE if LOWORD(ecx) != 1 + xor ebx, ebx + dec cx + jne .die + ; '&Evaluate' pressed, handle that. + ; Get the handle to the 3rd dialog item => the expression input + invoke GetDlgItem, eax, 3 + ; stuff it in wnd + mov DWORD [wnd], eax + ; get the text length to allocate approperiate amount of space on the stack + invoke GetWindowTextLengthA, eax + ; Save the esp + mov ecx, esp + ; Reserve space for the null terminator. + ; Basically, we're constructing a buffer on the stack + lea edx, [eax+1] + add eax, 17 + and eax, 0xFFFFFFF0 + sub ecx, eax + mov esp, ecx + ; While we're at it, null-terminate it. + mov BYTE [esp], 0 + ; Read the control data, put it in the buffer. + mov DWORD [ebp-12], ecx + invoke GetWindowTextA, DWORD [wnd], ecx, edx + mov ecx, DWORD [ebp-12] + ; Evaluate it. + call eval + ; Reset the control text. + invoke SetWindowText, DWORD [wnd], eax + .die: + ; Pop off the VLA + lea esp, [ebp-8] + ; Set the correct return value. + mov eax, ebx + ; Balance the stack + pop ebx + pop esi + pop ebp + ret 16 + endp + + ; Calculate the size of the tree, stringified. + ; Takes the tree in eax. + proc str_size + ; Preserve and clear eax, make a copy of the + ; pointer in ebx. + push esi ebx + xor esi, esi + mov ebx, eax + .loop: + ; if node.type == TYPE_BI, then it has two children + cmp DWORD [ebx+node.type], TYPE_BI + jne .quit + ; Apparently it does. + ; left-recurse to get the lhs size + mov eax, DWORD [ebx+node.left] + call str_size + ; eax contains the lhs size, so everything left + ; is the rhs size. loop on the right node. + mov ebx, DWORD [ebx+node.right] + ; add two bytes for '(' and ')' + lea esi, [esi+eax+2] + jmp .loop + .quit: + ; The node doesn't have two children - return 1 + ; (a single byte for either S, K or I) + lea eax, [esi+1] + pop ebx esi + ret + endp + + ; Stringify the tree. + ; Take it in eax. The buffer is static and + ; it's the callers' duty to allocate it. + proc stringify + ; copy the node pointer to ebx + push ebx + mov ebx, eax + ; first, take the node type. + mov edx, DWORD [eax+node.type] + ; because no matter where we branch the buffer will be used, + ; preload it. + mov eax, DWORD [buf] + ; increment the current buffer pointer stored in the variable + ; and hold own instance, which points to one byte before + inc eax + mov DWORD [buf], eax + dec eax + ; has two children? + cmp edx, TYPE_BI + jne .combinator + ; alloc_node tree starts with '(' + mov BYTE [eax], '(' + ; Recurse on the lhs and rhs + mov eax, DWORD [ebx+node.left] + call stringify + mov eax, DWORD [ebx+node.right] + call stringify + ; increment pointer, store the ')'. + mov eax, DWORD [buf] + mov BYTE [eax], ')' + inc eax + mov DWORD [buf], eax + dec eax + jmp .stop + .combinator: + ; stringify the combinator. + ; use the lookup string for that. + mov dl, BYTE [ski+edx] + ; store back the letter. + mov BYTE [eax], dl + ; the pointer is already incremented, so we fall thru to return + .stop: + pop ebx + ret + endp + + ; a wrapper over HeapFree, which frees the pointer in eax. + ; XXX: inline? + proc free + invoke HeapFree, DWORD [asmh], 0, eax + ret + endp + + ; free a tree recursively + proc free_tree + ; preserve ebx, make a copy of eax + push ebx + mov ebx, eax + ; has children? + cmp DWORD [eax+node.type], TYPE_BI + jne .no_children + ; recurse over children. + mov eax, DWORD [eax+node.left] + call free_tree + mov eax, DWORD [ebx+node.right] + call free_tree + .no_children: + ; take the copy, restore ebx, free the parent. + mov eax, ebx + pop ebx + jmp free + endp + + ; Allocate a new tree node. + ; takes new nodes' type in eax. + proc alloc_node + ; preserve eax, because it will get trashed by HeapAlloc + push ebx + mov ebx, eax + ; Call HeapAlloc, alloc 4 (left) + 4 (right) + 4 (type) B. + ; Zero the memory so we don't have to set left and right to NULL. + invoke HeapAlloc, DWORD [asmh], HEAP_ZERO_MEMORY, 4 + 4 + 4 + ; Set the type. + mov DWORD [eax+node.type], ebx + pop ebx + ret + endp + + ; read a node from the input buffer, and return it in eax. + proc read_node + ; preserve ebx + push ebx + ; load the code pointer + mov eax, DWORD [code] + ; increment it, store back + inc eax + mov DWORD [code], eax + dec eax + ; load a byte + mov al, BYTE [eax] + ; if al>'K' then al may be 'S' + cmp al, 'K' + je .read_k + jg .maybe_s + ; reading a tree + cmp al, '(' + je .read_bitree + ; if it's not 'I', spew out an error. + cmp al, 'I' + jne .parse_error + ; build an i-node + push TYPE_I + pop eax + jmp .build_node + .maybe_s: + ; if it's not 'S', spew out an error. + cmp al, 'S' + jne .parse_error + ; otherwise, clear eax (load TYPE_S) + ; and build a new node. + xor eax, eax + jmp .build_node + .read_bitree: + ; load the approperiate type and allocate a node + push TYPE_BI + pop eax + call alloc_node + mov ebx, eax + ; read the left node + call read_node + mov DWORD [ebx+node.left], eax + ; eax = 0 => return NULL to broadcast an error. + test eax, eax + je .nullify + ; read the right node + call read_node + mov DWORD [ebx+node.right], eax + test eax, eax + je .nullify + ; no errors - increment the code pointer to skip the trailing `)`. + inc DWORD [code] + jmp .die + .read_k: + ; set eax to 1 (loading TYPE_K) + ; and fall thru to construction of a new node. + xor eax, eax + inc eax + .build_node: + pop ebx + jmp alloc_node + .parse_error: + ; in case of a parse error, display a message and fall thru to returning NULL. + invoke MessageBoxA, 0, msge, 0, MB_OK + .nullify: + xor ebx, ebx + .die: + ; set the return value and quit + mov eax, ebx + pop ebx + ret + endp + + ; duplicate a tree in eax. + proc dup_tree + push esi ebx + mov ebx, eax + ; Make a new node with this node's type. + mov eax, DWORD [eax+node.type] + call alloc_node + ; if type != TYPE_BI then return that instance. + cmp DWORD [ebx+node.type], TYPE_BI + jne .shallow + ; else, clone recursively. copy the original + ; ptr, because it will get overwritten + mov esi, eax + ; clone the left node + mov eax, DWORD [ebx+node.left] + call dup_tree + mov DWORD [esi+node.left], eax + ; clone the right node + mov eax, DWORD [ebx+node.right] + call dup_tree + mov DWORD [esi+node.right], eax + ; restore eax + mov eax, esi + .shallow: + pop ebx esi + ret + endp + + proc eval_step + push edi esi ebx + mov ebx, eax + ; has one child? if node.left == NULL + mov eax, DWORD [eax+node.left] + test eax, eax + je .no_left + ; if the first child's type is I + cmp DWORD [eax+node.type], TYPE_I + jne .not_inode + ; got identity, so take the right node. + mov esi, DWORD [ebx+node.right] + ; free this node and the left node. + jmp .clean + .not_inode: + ; it's not I. eax is now orig->left + ; if orig->left->left->type == K + mov edx, DWORD [eax+node.left] + ; wait, maybe there is no left node + test edx, edx + je .no_left + ; check the type. + cmp DWORD [edx+node.type], TYPE_K + ; branch if it's not K either. + jne .not_knode + ; free orig->right and orig->left->left + ; keep and return orig->left->right + mov esi, DWORD [eax+node.right] + mov eax, DWORD [ebx+node.right] + call free_tree + mov eax, DWORD [ebx+node.left] + mov eax, DWORD [eax+node.left] + ; fallthru to free the orig->left->left node + .clean: + call free_tree + mov eax, ebx + call free + .yield_saved: + mov ebx, esi + jmp .done + .not_knode: + ; if it's not a K or I node, then for sure it's either + ; a node we have to evaluate recursively _or_ a S node. + ; check for existence of X = orig->left->left->left + mov edx, DWORD [edx] + test edx, edx + je .no_left + ; X->type != TYPE_S? + cmp DWORD [edx+node.type], TYPE_S + jne .no_left + ; ok, so definitely it's a S node. + ; to get ((Sx)y)z = ((xz)(yz)), first build the outer binode. + push TYPE_BI + pop eax + call alloc_node + ; OK, save it in esi + mov esi, eax + ; build two another binodes, and put them as the left and right + ; node of this tree. + push 3 + pop eax + call alloc_node + mov DWORD [esi+node.left], eax + push 3 + pop eax + call alloc_node + mov DWORD [esi+node.right], eax + ; now the magic happens. do the following: + ; (esi + node.left)->left = dup(orig->left->left->right) + ; (esi + node.left)->right = dup(orig->right) + ; (esi + node.right)->left = dup(orig->left->right) + ; (esi + node.right)->right = dup(orig->right) + ; I'm not sure if this many dup calls are required, but they + ; help to shave off some space and trouble needed to free the + ; correct elements of the trees. we're not really aiming for + ; performance here, so it's alright. + mov edi, DWORD [esi+node.left] + mov eax, DWORD [ebx+node.left] + mov eax, DWORD [eax+node.left] + mov eax, DWORD [eax+node.right] + call dup_tree + mov DWORD [edi+node.left], eax + mov eax, DWORD [ebx+node.right] + mov edi, DWORD [esi+node.left] + call dup_tree + mov DWORD [edi+node.right], eax + mov eax, DWORD [ebx+node.left] + mov edi, DWORD [esi+node.right] + mov eax, DWORD [eax+node.right] + call dup_tree + mov DWORD [edi+node.left], eax + mov eax, DWORD [ebx+node.right] + mov edi, DWORD [esi+node.right] + call dup_tree + mov DWORD [edi+node.right], eax + ; free the original tree + mov eax, ebx + call free_tree + jmp .yield_saved + .no_left: + ; maybe it's a binode, which we just need to evaluate + ; deeper to get some observable result? + cmp DWORD [ebx+node.type], TYPE_BI + jne .done + ; recurse twice. first set the left node, then the right node. + call eval_step + mov DWORD [ebx+node.left], eax + mov eax, DWORD [ebx+node.right] + call eval_step + mov DWORD [ebx+node.right], eax + .done: + mov eax, ebx + pop ebx esi edi + ret + endp + + ; the evaluation wrapper called by the DialogProc. + ; takes the input buffer in ecx. + eval: + push esi ebx + mov ebx, ecx + ; store the input in the code buffer. + mov DWORD [code], ecx + ; read the expression. + call read_node + ; if read_node returns null, then an error occured + test eax, eax + je .read_fail + ; call the evaluation procedure. + call eval_step + mov esi, eax + ; find out the size of the buffer, stringified. + call str_size + ; allocate it a byte of extra space. + inc eax + invoke HeapAlloc, DWORD [asmh], 0, eax + ; initialize the output buffer. + mov DWORD [buf], eax + ; save the output copy to ourselves to later return it. + mov ebx, eax + ; take back the saved buffer, stringify the input into it + mov eax, esi + call stringify + ; NULL terminate + mov eax, DWORD [buf] + mov BYTE [eax], 0 + ; free the original tree + mov eax, esi + call free_tree + .read_fail: + ; in any case, return the value we've got. + mov eax, ebx + pop ebx esi + ret + +wnd: dd 0 +msg MSG +hDlg: dd 0 +asmh: dd 0 +buf: dd 0 +code: dd 0 +ski: db 'SKI', 0 +msge: db '?', 0 + +section '.rsrc' resource data readable +directory RT_DIALOG, dialogs +resource dialogs, 1, LANG_ENGLISH+SUBLANG_DEFAULT, demo +dialog demo,'SKI calculus',70,70,330,20,WS_CAPTION+WS_POPUP+WS_SYSMENU+DS_MODALFRAME + dialogitem 'STATIC', '&Code: ', 4, 4, 5, 21, 9, WS_VISIBLE+WS_CHILD+WS_GROUP + dialogitem 'BUTTON', '&Quit', 2, 269, 4, 50, 11, BS_PUSHBUTTON+WS_CHILD+WS_VISIBLE+WS_GROUP + dialogitem 'BUTTON', '&Evaluate', 1, 218, 4, 50, 11, BS_DEFPUSHBUTTON+WS_CHILD+WS_VISIBLE+WS_GROUP + dialogitem 'EDIT', '', 3, 28, 3, 187, 14, ES_LEFT+WS_CHILD+WS_VISIBLE+WS_BORDER+WS_GROUP+ES_AUTOHSCROLL +enddialog + +section '.idata' import data readable writable +library kernel32, 'KERNEL32.DLL', \ + user32, 'USER32.DLL' + +include 'api\kernel32.inc' +include 'api\user32.inc' + + + +; flat assembler core +; Copyright (c) 1999-2022, Tomasz Grysztar. +; All rights reserved. + +assembler: + xor eax,eax + mov [stub_size],eax + mov [current_pass],ax + mov [resolver_flags],eax + mov [number_of_sections],eax + mov [actual_fixups_size],eax + assembler_loop: + mov eax,[labels_list] + mov [tagged_blocks],eax + mov eax,[additional_memory] + mov [free_additional_memory],eax + mov eax,[additional_memory_end] + mov [structures_buffer],eax + mov esi,[source_start] + mov edi,[code_start] + xor eax,eax + mov dword [adjustment],eax + mov dword [adjustment+4],eax + mov [addressing_space],eax + mov [error_line],eax + mov [counter],eax + mov [format_flags],eax + mov [number_of_relocations],eax + mov [undefined_data_end],eax + mov [file_extension],eax + mov [next_pass_needed],al + mov [output_format],al + mov [adjustment_sign],al + mov [evex_mode],al + mov [code_type],16 + call init_addressing_space + pass_loop: + call assemble_line + jnc pass_loop + mov eax,[additional_memory_end] + cmp eax,[structures_buffer] + je pass_done + sub eax,18h + mov eax,[eax+4] + mov [current_line],eax + jmp missing_end_directive + pass_done: + call close_pass + mov eax,[labels_list] + check_symbols: + cmp eax,[memory_end] + jae symbols_checked + test byte [eax+8],8 + jz symbol_defined_ok + mov cx,[current_pass] + cmp cx,[eax+18] + jne symbol_defined_ok + test byte [eax+8],1 + jz symbol_defined_ok + sub cx,[eax+16] + cmp cx,1 + jne symbol_defined_ok + and byte [eax+8],not 1 + or [next_pass_needed],-1 + symbol_defined_ok: + test byte [eax+8],10h + jz use_prediction_ok + mov cx,[current_pass] + and byte [eax+8],not 10h + test byte [eax+8],20h + jnz check_use_prediction + cmp cx,[eax+18] + jne use_prediction_ok + test byte [eax+8],8 + jz use_prediction_ok + jmp use_misprediction + check_use_prediction: + test byte [eax+8],8 + jz use_misprediction + cmp cx,[eax+18] + je use_prediction_ok + use_misprediction: + or [next_pass_needed],-1 + use_prediction_ok: + test byte [eax+8],40h + jz check_next_symbol + and byte [eax+8],not 40h + test byte [eax+8],4 + jnz define_misprediction + mov cx,[current_pass] + test byte [eax+8],80h + jnz check_define_prediction + cmp cx,[eax+16] + jne check_next_symbol + test byte [eax+8],1 + jz check_next_symbol + jmp define_misprediction + check_define_prediction: + test byte [eax+8],1 + jz define_misprediction + cmp cx,[eax+16] + je check_next_symbol + define_misprediction: + or [next_pass_needed],-1 + check_next_symbol: + add eax,LABEL_STRUCTURE_SIZE + jmp check_symbols + symbols_checked: + cmp [next_pass_needed],0 + jne next_pass + mov eax,[error_line] + or eax,eax + jz assemble_ok + mov [current_line],eax + cmp [error],undefined_symbol + jne error_confirmed + mov eax,[error_info] + or eax,eax + jz error_confirmed + test byte [eax+8],1 + jnz next_pass + error_confirmed: + call error_handler + error_handler: + mov eax,[error] + sub eax,error_handler + add [esp],eax + ret + next_pass: + inc [current_pass] + mov ax,[current_pass] + cmp ax,[passes_limit] + je code_cannot_be_generated + jmp assembler_loop + assemble_ok: + ret + +create_addressing_space: + mov ebx,[addressing_space] + test ebx,ebx + jz init_addressing_space + test byte [ebx+0Ah],1 + jnz illegal_instruction + mov eax,edi + sub eax,[ebx+18h] + mov [ebx+1Ch],eax + init_addressing_space: + mov ebx,[tagged_blocks] + mov dword [ebx-4],10h + mov dword [ebx-8],24h + sub ebx,8+24h + cmp ebx,edi + jbe out_of_memory + mov [tagged_blocks],ebx + mov [addressing_space],ebx + xor eax,eax + mov [ebx],edi + mov [ebx+4],eax + mov [ebx+8],eax + mov [ebx+10h],eax + mov [ebx+14h],eax + mov [ebx+18h],edi + mov [ebx+1Ch],eax + mov [ebx+20h],eax + ret + +assemble_line: + mov eax,[tagged_blocks] + sub eax,100h + cmp edi,eax + ja out_of_memory + lods byte [esi] + cmp al,1 + je assemble_instruction + jb source_end + cmp al,3 + jb define_label + je define_constant + cmp al,4 + je label_addressing_space + cmp al,0Fh + je new_line + cmp al,13h + je code_type_setting + cmp al,10h + jne illegal_instruction + lods byte [esi] + jmp segment_prefix + code_type_setting: + lods byte [esi] + mov [code_type],al + jmp instruction_assembled + new_line: + lods dword [esi] + mov [current_line],eax + and [prefix_flags],0 + cmp [symbols_file],0 + je continue_line + cmp [next_pass_needed],0 + jne continue_line + mov ebx,[tagged_blocks] + mov dword [ebx-4],1 + mov dword [ebx-8],14h + sub ebx,8+14h + cmp ebx,edi + jbe out_of_memory + mov [tagged_blocks],ebx + mov [ebx],eax + mov [ebx+4],edi + mov eax,[addressing_space] + mov [ebx+8],eax + mov al,[code_type] + mov [ebx+10h],al + continue_line: + cmp byte [esi],0Fh + je line_assembled + jmp assemble_line + define_label: + lods dword [esi] + cmp eax,0Fh + jb invalid_use_of_symbol + je reserved_word_used_as_symbol + mov ebx,eax + lods byte [esi] + mov [label_size],al + call make_label + jmp continue_line + make_label: + mov eax,edi + xor edx,edx + xor cl,cl + mov ebp,[addressing_space] + sub eax,[ds:ebp] + sbb edx,[ds:ebp+4] + sbb cl,[ds:ebp+8] + jp label_value_ok + call recoverable_overflow + label_value_ok: + mov [address_sign],cl + test byte [ds:ebp+0Ah],1 + jnz make_virtual_label + or byte [ebx+9],1 + xchg eax,[ebx] + xchg edx,[ebx+4] + mov ch,[ebx+9] + shr ch,1 + and ch,1 + neg ch + sub eax,[ebx] + sbb edx,[ebx+4] + sbb ch,cl + mov dword [adjustment],eax + mov dword [adjustment+4],edx + mov [adjustment_sign],ch + or al,ch + or eax,edx + setnz ah + jmp finish_label + make_virtual_label: + and byte [ebx+9],not 1 + cmp eax,[ebx] + mov [ebx],eax + setne ah + cmp edx,[ebx+4] + mov [ebx+4],edx + setne al + or ah,al + finish_label: + mov ebp,[addressing_space] + mov ch,[ds:ebp+9] + mov cl,[label_size] + mov edx,[ds:ebp+14h] + mov ebp,[ds:ebp+10h] + finish_label_symbol: + mov al,[address_sign] + xor al,[ebx+9] + and al,10b + or ah,al + xor [ebx+9],al + cmp cl,[ebx+10] + mov [ebx+10],cl + setne al + or ah,al + cmp ch,[ebx+11] + mov [ebx+11],ch + setne al + or ah,al + cmp ebp,[ebx+12] + mov [ebx+12],ebp + setne al + or ah,al + or ch,ch + jz label_symbol_ok + cmp edx,[ebx+20] + mov [ebx+20],edx + setne al + or ah,al + label_symbol_ok: + mov cx,[current_pass] + xchg [ebx+16],cx + mov edx,[current_line] + mov [ebx+28],edx + and byte [ebx+8],not 2 + test byte [ebx+8],1 + jz new_label + cmp cx,[ebx+16] + je symbol_already_defined + btr dword [ebx+8],10 + jc requalified_label + inc cx + sub cx,[ebx+16] + setnz al + or ah,al + jz label_made + test byte [ebx+8],8 + jz label_made + mov cx,[current_pass] + cmp cx,[ebx+18] + jne label_made + requalified_label: + or [next_pass_needed],-1 + pop [error_line] + ret + no_end_directive: + mov eax,[error_line] + mov [current_line],eax + jmp missing_end_directive + skip_repeat: + call find_end_repeat + jmp find_end_directive + skip_while: + call find_end_while + jmp find_end_directive + skip_if: + call skip_if_block + jmp find_end_directive + skip_if_block: + call find_else + jc if_block_skipped + cmp byte [esi],1 + jne skip_after_else + cmp word [esi+1],if_directive-instruction_handler + jne skip_after_else + add esi,4 + jmp skip_if_block + skip_after_else: + call find_end_if + if_block_skipped: + ret +end_directive: + lods byte [esi] + cmp al,1 + jne invalid_argument + lods word [esi] + inc esi + cmp ax,virtual_directive-instruction_handler + je end_virtual + cmp ax,repeat_directive-instruction_handler + je end_repeat + cmp ax,while_directive-instruction_handler + je end_while + cmp ax,if_directive-instruction_handler + je end_if + cmp ax,data_directive-instruction_handler + je end_data + jmp invalid_argument +break_directive: + mov ebx,[structures_buffer] + mov al,[esi] + or al,al + jz find_breakable_structure + cmp al,0Fh + jne extra_characters_on_line + find_breakable_structure: + cmp ebx,[additional_memory_end] + je unexpected_instruction + mov ax,[ebx] + cmp ax,repeat_directive-instruction_handler + je break_repeat + cmp ax,while_directive-instruction_handler + je break_while + cmp ax,if_directive-instruction_handler + je break_if + add ebx,18h + jmp find_breakable_structure + break_if: + push [current_line] + mov eax,[ebx+4] + mov [current_line],eax + call remove_structure_data + call skip_if_block + pop [current_line] + mov ebx,[structures_buffer] + jmp find_breakable_structure + break_repeat: + push ebx + call find_end_repeat + pop ebx + jmp stop_repeat + break_while: + push ebx + jmp stop_while + +define_data: + cmp edi,[tagged_blocks] + jae out_of_memory + cmp byte [esi],'(' + jne simple_data_value + mov ebx,esi + inc esi + call skip_expression + xchg esi,ebx + cmp byte [ebx],81h + jne simple_data_value + inc esi + call get_count_value + inc esi + or eax,eax + jz duplicate_zero_times + cmp byte [esi],91h + jne duplicate_single_data_value + inc esi + duplicate_data: + push eax esi + duplicated_values: + cmp edi,[tagged_blocks] + jae out_of_memory + clc + call near dword [esp+8] + lods byte [esi] + cmp al,',' + je duplicated_values + cmp al,92h + jne invalid_argument + pop ebx eax + dec eax + jz data_defined + mov esi,ebx + jmp duplicate_data + duplicate_single_data_value: + cmp edi,[tagged_blocks] + jae out_of_memory + push eax esi + clc + call near dword [esp+8] + pop ebx eax + dec eax + jz data_defined + mov esi,ebx + jmp duplicate_single_data_value + duplicate_zero_times: + cmp byte [esi],91h + jne skip_single_data_value + inc esi + skip_data_value: + call skip_symbol + jc invalid_argument + cmp byte [esi],92h + jne skip_data_value + inc esi + jmp data_defined + skip_single_data_value: + call skip_symbol + jmp data_defined + simple_data_value: + cmp edi,[tagged_blocks] + jae out_of_memory + clc + call near dword [esp] + data_defined: + lods byte [esi] + cmp al,',' + je define_data + dec esi + stc + ret +data_bytes: + call define_data + jc instruction_assembled + lods byte [esi] + cmp al,'(' + je get_byte + cmp al,'?' + jne invalid_argument + mov eax,edi + mov byte [edi],0 + inc edi + jmp undefined_data + get_byte: + cmp byte [esi],0 + je get_string + call get_byte_value + stos byte [edi] + ret + get_string: + inc esi + lods dword [esi] + mov ecx,eax + lea eax,[edi+ecx] + cmp eax,[tagged_blocks] + ja out_of_memory + rep movs byte [edi],[esi] + inc esi + ret + undefined_data: + mov ebp,[addressing_space] + test byte [ds:ebp+0Ah],1 + jz mark_undefined_data + ret + mark_undefined_data: + cmp eax,[undefined_data_end] + je undefined_data_ok + mov [undefined_data_start],eax + undefined_data_ok: + mov [undefined_data_end],edi + ret +data_unicode: + or [base_code],-1 + jmp define_words +data_words: + mov [base_code],0 + define_words: + call define_data + jc instruction_assembled + lods byte [esi] + cmp al,'(' + je get_word + cmp al,'?' + jne invalid_argument + mov eax,edi + and word [edi],0 + scas word [edi] + jmp undefined_data + ret + get_word: + cmp [base_code],0 + je word_data_value + cmp byte [esi],0 + je word_string + word_data_value: + call get_word_value + call mark_relocation + stos word [edi] + ret + word_string: + inc esi + lods dword [esi] + mov ecx,eax + jecxz word_string_ok + lea eax,[edi+ecx*2] + cmp eax,[tagged_blocks] + ja out_of_memory + xor ah,ah + copy_word_string: + lods byte [esi] + stos word [edi] + loop copy_word_string + word_string_ok: + inc esi + ret +data_dwords: + call define_data + jc instruction_assembled + lods byte [esi] + cmp al,'(' + je get_dword + cmp al,'?' + jne invalid_argument + mov eax,edi + and dword [edi],0 + scas dword [edi] + jmp undefined_data + get_dword: + push esi + call get_dword_value + pop ebx + cmp byte [esi],':' + je complex_dword + call mark_relocation + stos dword [edi] + ret + complex_dword: + mov esi,ebx + cmp byte [esi],'.' + je invalid_value + call get_word_value + push eax + inc esi + lods byte [esi] + cmp al,'(' + jne invalid_operand + mov al,[value_type] + push eax + cmp byte [esi],'.' + je invalid_value + call get_word_value + call mark_relocation + stos word [edi] + pop eax + mov [value_type],al + pop eax + call mark_relocation + stos word [edi] + ret +data_pwords: + call define_data + jc instruction_assembled + lods byte [esi] + cmp al,'(' + je get_pword + cmp al,'?' + jne invalid_argument + mov eax,edi + and dword [edi],0 + scas dword [edi] + and word [edi],0 + scas word [edi] + jmp undefined_data + get_pword: + push esi + call get_pword_value + pop ebx + cmp byte [esi],':' + je complex_pword + call mark_relocation + stos dword [edi] + mov ax,dx + stos word [edi] + ret + complex_pword: + mov esi,ebx + cmp byte [esi],'.' + je invalid_value + call get_word_value + push eax + inc esi + lods byte [esi] + cmp al,'(' + jne invalid_operand + mov al,[value_type] + push eax + cmp byte [esi],'.' + je invalid_value + call get_dword_value + call mark_relocation + stos dword [edi] + pop eax + mov [value_type],al + pop eax + call mark_relocation + stos word [edi] + ret +data_qwords: + call define_data + jc instruction_assembled + lods byte [esi] + cmp al,'(' + je get_qword + cmp al,'?' + jne invalid_argument + mov eax,edi + and dword [edi],0 + scas dword [edi] + and dword [edi],0 + scas dword [edi] + jmp undefined_data + get_qword: + call get_qword_value + call mark_relocation + stos dword [edi] + mov eax,edx + stos dword [edi] + ret +data_twords: + call define_data + jc instruction_assembled + lods byte [esi] + cmp al,'(' + je get_tword + cmp al,'?' + jne invalid_argument + mov eax,edi + and dword [edi],0 + scas dword [edi] + and dword [edi],0 + scas dword [edi] + and word [edi],0 + scas word [edi] + jmp undefined_data + get_tword: + cmp byte [esi],'.' + jne complex_tword + inc esi + cmp word [esi+8],8000h + je fp_zero_tword + mov eax,[esi] + stos dword [edi] + mov eax,[esi+4] + stos dword [edi] + mov ax,[esi+8] + add ax,3FFFh + jo value_out_of_range + cmp ax,7FFFh + jge value_out_of_range + cmp ax,0 + jg tword_exp_ok + mov cx,ax + neg cx + inc cx + cmp cx,64 + jae value_out_of_range + cmp cx,32 + ja large_shift + mov eax,[esi] + mov edx,[esi+4] + mov ebx,edx + shr edx,cl + shrd eax,ebx,cl + jmp tword_mantissa_shift_done + large_shift: + sub cx,32 + xor edx,edx + mov eax,[esi+4] + shr eax,cl + tword_mantissa_shift_done: + jnc store_shifted_mantissa + add eax,1 + adc edx,0 + store_shifted_mantissa: + mov [edi-8],eax + mov [edi-4],edx + xor ax,ax + test edx,1 shl 31 + jz tword_exp_ok + inc ax + tword_exp_ok: + mov bl,[esi+11] + shl bx,15 + or ax,bx + stos word [edi] + add esi,13 + ret + fp_zero_tword: + xor eax,eax + stos dword [edi] + stos dword [edi] + mov al,[esi+11] + shl ax,15 + stos word [edi] + add esi,13 + ret + complex_tword: + call get_word_value + push eax + cmp byte [esi],':' + jne invalid_operand + inc esi + lods byte [esi] + cmp al,'(' + jne invalid_operand + mov al,[value_type] + push eax + cmp byte [esi],'.' + je invalid_value + call get_qword_value + call mark_relocation + stos dword [edi] + mov eax,edx + stos dword [edi] + pop eax + mov [value_type],al + pop eax + call mark_relocation + stos word [edi] + ret +data_file: + lods word [esi] + cmp ax,'(' + jne invalid_argument + add esi,4 + call open_binary_file + mov eax,[esi-4] + lea esi,[esi+eax+1] + mov al,2 + xor edx,edx + call lseek + push eax + xor edx,edx + cmp byte [esi],':' + jne position_ok + inc esi + cmp byte [esi],'(' + jne invalid_argument + inc esi + cmp byte [esi],'.' + je invalid_value + push ebx + call get_count_value + pop ebx + mov edx,eax + sub [esp],edx + jc value_out_of_range + position_ok: + cmp byte [esi],',' + jne size_ok + inc esi + cmp byte [esi],'(' + jne invalid_argument + inc esi + cmp byte [esi],'.' + je invalid_value + push ebx edx + call get_count_value + pop edx ebx + cmp eax,[esp] + ja value_out_of_range + mov [esp],eax + size_ok: + xor al,al + call lseek + pop ecx + mov edx,edi + add edi,ecx + jc out_of_memory + cmp edi,[tagged_blocks] + ja out_of_memory + call read + jc error_reading_file + call close + lods byte [esi] + cmp al,',' + je data_file + dec esi + jmp instruction_assembled + open_binary_file: + push esi + push edi + mov eax,[current_line] + find_current_source_path: + mov esi,[eax] + test byte [eax+7],80h + jz get_current_path + mov eax,[eax+8] + jmp find_current_source_path + get_current_path: + lodsb + stosb + or al,al + jnz get_current_path + cut_current_path: + cmp edi,[esp] + je current_path_ok + cmp byte [edi-1],'\' + je current_path_ok + cmp byte [edi-1],'/' + je current_path_ok + dec edi + jmp cut_current_path + current_path_ok: + mov esi,[esp+4] + call expand_path + pop edx + mov esi,edx + call open + jnc file_opened + mov edx,[include_paths] + search_in_include_paths: + push edx esi + mov edi,esi + mov esi,[esp+4] + call get_include_directory + mov [esp+4],esi + mov esi,[esp+8] + call expand_path + pop edx + mov esi,edx + call open + pop edx + jnc file_opened + cmp byte [edx],0 + jne search_in_include_paths + mov edi,esi + mov esi,[esp] + push edi + call expand_path + pop edx + mov esi,edx + call open + jc file_not_found + file_opened: + mov edi,esi + pop esi + ret +reserve_bytes: + lods byte [esi] + cmp al,'(' + jne invalid_argument + cmp byte [esi],'.' + je invalid_value + call get_count_value + mov ecx,eax + mov edx,ecx + add edx,edi + jc out_of_memory + cmp edx,[tagged_blocks] + ja out_of_memory + push edi + cmp [next_pass_needed],0 + je zero_bytes + add edi,ecx + jmp reserved_data + zero_bytes: + xor eax,eax + shr ecx,1 + jnc bytes_stosb_ok + stos byte [edi] + bytes_stosb_ok: + shr ecx,1 + jnc bytes_stosw_ok + stos word [edi] + bytes_stosw_ok: + rep stos dword [edi] + reserved_data: + pop eax + call undefined_data + jmp instruction_assembled +reserve_words: + lods byte [esi] + cmp al,'(' + jne invalid_argument + cmp byte [esi],'.' + je invalid_value + call get_count_value + mov ecx,eax + mov edx,ecx + shl edx,1 + jc out_of_memory + add edx,edi + jc out_of_memory + cmp edx,[tagged_blocks] + ja out_of_memory + push edi + cmp [next_pass_needed],0 + je zero_words + lea edi,[edi+ecx*2] + jmp reserved_data + zero_words: + xor eax,eax + shr ecx,1 + jnc words_stosw_ok + stos word [edi] + words_stosw_ok: + rep stos dword [edi] + jmp reserved_data +reserve_dwords: + lods byte [esi] + cmp al,'(' + jne invalid_argument + cmp byte [esi],'.' + je invalid_value + call get_count_value + mov ecx,eax + mov edx,ecx + shl edx,1 + jc out_of_memory + shl edx,1 + jc out_of_memory + add edx,edi + jc out_of_memory + cmp edx,[tagged_blocks] + ja out_of_memory + push edi + cmp [next_pass_needed],0 + je zero_dwords + lea edi,[edi+ecx*4] + jmp reserved_data + zero_dwords: + xor eax,eax + rep stos dword [edi] + jmp reserved_data +reserve_pwords: + lods byte [esi] + cmp al,'(' + jne invalid_argument + cmp byte [esi],'.' + je invalid_value + call get_count_value + mov ecx,eax + shl ecx,1 + jc out_of_memory + add ecx,eax + mov edx,ecx + shl edx,1 + jc out_of_memory + add edx,edi + jc out_of_memory + cmp edx,[tagged_blocks] + ja out_of_memory + push edi + cmp [next_pass_needed],0 + je zero_words + lea edi,[edi+ecx*2] + jmp reserved_data +reserve_qwords: + lods byte [esi] + cmp al,'(' + jne invalid_argument + cmp byte [esi],'.' + je invalid_value + call get_count_value + mov ecx,eax + shl ecx,1 + jc out_of_memory + mov edx,ecx + shl edx,1 + jc out_of_memory + shl edx,1 + jc out_of_memory + add edx,edi + jc out_of_memory + cmp edx,[tagged_blocks] + ja out_of_memory + push edi + cmp [next_pass_needed],0 + je zero_dwords + lea edi,[edi+ecx*4] + jmp reserved_data +reserve_twords: + lods byte [esi] + cmp al,'(' + jne invalid_argument + cmp byte [esi],'.' + je invalid_value + call get_count_value + mov ecx,eax + shl ecx,2 + jc out_of_memory + add ecx,eax + mov edx,ecx + shl edx,1 + jc out_of_memory + add edx,edi + jc out_of_memory + cmp edx,[tagged_blocks] + ja out_of_memory + push edi + cmp [next_pass_needed],0 + je zero_words + lea edi,[edi+ecx*2] + jmp reserved_data +align_directive: + lods byte [esi] + cmp al,'(' + jne invalid_argument + cmp byte [esi],'.' + je invalid_value + call get_count_value + mov edx,eax + dec edx + test eax,edx + jnz invalid_align_value + or eax,eax + jz invalid_align_value + cmp eax,1 + je instruction_assembled + mov ecx,edi + mov ebp,[addressing_space] + sub ecx,[ds:ebp] + cmp dword [ds:ebp+10h],0 + jne section_not_aligned_enough + cmp byte [ds:ebp+9],0 + je make_alignment + cmp [output_format],3 + je pe_alignment + cmp [output_format],5 + jne object_alignment + test [format_flags],1 + jnz pe_alignment + object_alignment: + mov ebx,[ds:ebp+14h] + cmp byte [ebx],0 + jne section_not_aligned_enough + cmp eax,[ebx+10h] + jbe make_alignment + jmp section_not_aligned_enough + pe_alignment: + cmp eax,1000h + ja section_not_aligned_enough + make_alignment: + dec eax + and ecx,eax + jz instruction_assembled + neg ecx + add ecx,eax + inc ecx + mov edx,ecx + add edx,edi + jc out_of_memory + cmp edx,[tagged_blocks] + ja out_of_memory + push edi + cmp [next_pass_needed],0 + je nops + add edi,ecx + jmp reserved_data + invalid_align_value: + cmp [error_line],0 + jne instruction_assembled + mov eax,[current_line] + mov [error_line],eax + mov [error],invalid_value + jmp instruction_assembled + nops: + mov eax,90909090h + shr ecx,1 + jnc nops_stosb_ok + stos byte [edi] + nops_stosb_ok: + shr ecx,1 + jnc nops_stosw_ok + stos word [edi] + nops_stosw_ok: + rep stos dword [edi] + jmp reserved_data +err_directive: + mov al,[esi] + cmp al,0Fh + je invoked_error + or al,al + jz invoked_error + jmp extra_characters_on_line +assert_directive: + call calculate_logical_expression + or al,al + jnz instruction_assembled + cmp [error_line],0 + jne instruction_assembled + mov eax,[current_line] + mov [error_line],eax + mov [error],assertion_failed + jmp instruction_assembled + + hex_digit_skip: + dec esi + jmp get_hex_digit + get_oct_number: + xor bl,bl + get_oct_digit: + cmp esi,[number_start] + jb number_ok + movzx eax,byte [esi] + cmp al,27h + je oct_digit_skip + cmp al,'_' + je oct_digit_skip + sub al,30h + cmp al,7 + ja bad_number + oct_digit_ok: + xor edx,edx + mov cl,bl + dec esi + cmp bl,63 + ja oct_out_of_range + jne oct_range_ok + cmp al,1 + ja oct_out_of_range + oct_range_ok: + add bl,3 + cmp cl,30 + je oct_digit_wrap + ja oct_digit_high + shl eax,cl + or dword [edi],eax + jmp get_oct_digit + oct_digit_wrap: + shl eax,cl + adc dword [edi+4],0 + or dword [edi],eax + jmp get_oct_digit + oct_digit_high: + sub cl,32 + shl eax,cl + or dword [edi+4],eax + jmp get_oct_digit + oct_digit_skip: + dec esi + jmp get_oct_digit + oct_out_of_range: + or al,al + jz get_oct_digit + or ebp,-1 + jmp get_oct_digit + hex_number_ok: + dec esi + pascal_hex_ok: + cmp esi,[number_start] + jne bad_number + number_ok: + pop esi + number_done: + clc + ret + get_text_number: + lods dword [esi] + mov edx,eax + xor bl,bl + mov dword [edi],0 + mov dword [edi+4],0 + get_text_character: + sub edx,1 + jc number_done + movzx eax,byte [esi] + inc esi + mov cl,bl + cmp bl,64 + je text_out_of_range + add bl,8 + cmp cl,32 + jae text_character_high + shl eax,cl + or dword [edi],eax + jmp get_text_character + text_character_high: + sub cl,32 + shl eax,cl + or dword [edi+4],eax + jmp get_text_character + text_out_of_range: + or ebp,-1 + jmp get_text_character + cmp al,'~' + je separator + cmp al,'>' + je greater + cmp al,'<' + je less + cmp al,')' + je close_parenthesis + or al,al + jz contents_parsed + cmp al,'[' + je address_argument + cmp al,']' + je separator + cmp al,'{' + je open_decorator + cmp al,'}' + je close_decorator + cmp al,'#' + je unallowed_character + cmp al,'`' + je unallowed_character + cmp al,3Bh + je foreign_argument + cmp [decorator_symbols_allowed],0 + je not_a_separator + cmp al,'-' + je separator + not_a_separator: + dec esi + cmp al,1Ah + jne expression_argument + push edi + mov edi,directive_operators + call get_operator + or al,al + jnz operator_argument + inc esi + movzx ecx,byte [esi] + inc esi + call get_symbol + jnc symbol_argument + cmp ecx,1 + jne check_argument + cmp byte [esi],'?' + jne check_argument + pop edi + movs byte [edi],[esi] + jmp argument_parsed + foreign_argument: + dec esi + call skip_foreign_line + jmp contents_parsed + symbol_argument: + pop edi + stos word [edi] + cmp byte [esi],'+' + jne argument_parsed + and ax,0F0FFh + cmp ax,6010h + jne argument_parsed + movs byte [edi],[esi] + jmp argument_parsed + operator_argument: + pop edi + cmp al,85h + je ptr_argument + stos byte [edi] + cmp al,8Ch + je forced_expression + cmp al,81h + je forced_parenthesis + cmp al,80h + je parse_at_operator + cmp al,82h + je parse_from_operator + cmp al,89h + je parse_label_operator + cmp al,0F8h + je forced_expression + jmp argument_parsed + instruction_separator: + stos byte [edi] + allow_embedded_instruction: + cmp byte [esi],1Ah + jne parse_argument + push edi + inc esi + movzx ecx,byte [esi] + inc esi + call get_instruction + jnc embedded_instruction + call get_data_directive + jnc embedded_instruction + pop edi + sub esi,2 + jmp parse_argument + embedded_instruction: + pop edi + mov dl,al + mov al,1 + stos byte [edi] + mov ax,bx + stos word [edi] + mov al,dl + stos byte [edi] + jmp parse_instruction_arguments + parse_times_directive: + mov al,'(' + stos byte [edi] + call convert_expression + mov al,')' + stos byte [edi] + cmp byte [esi],':' + jne allow_embedded_instruction + movs byte [edi],[esi] + jmp allow_embedded_instruction + parse_segment_directive: + or [formatter_symbols_allowed],-1 + parse_label_directive: + cmp byte [esi],1Ah + jne argument_parsed + push esi + inc esi + movzx ecx,byte [esi] + inc esi + call identify_label + pop ebx + cmp eax,0Fh + je non_label_identified + mov byte [edi],2 + inc edi + stos dword [edi] + xor al,al + stos byte [edi] + jmp argument_parsed + non_label_identified: + mov esi,ebx + jmp argument_parsed + parse_load_directive: + cmp byte [esi],1Ah + jne argument_parsed + push esi + inc esi + movzx ecx,byte [esi] + inc esi + call get_label_id + pop ebx + cmp eax,0Fh + je non_label_identified + mov byte [edi],2 + inc edi + stos dword [edi] + xor al,al + stos byte [edi] + jmp argument_parsed + parse_public_directive: + cmp byte [esi],1Ah + jne parse_argument + inc esi + push esi + movzx ecx,byte [esi] + inc esi + push esi ecx + push edi + or [formatter_symbols_allowed],-1 + call get_symbol + mov [formatter_symbols_allowed],0 + pop edi + jc parse_public_label + cmp al,1Dh + jne parse_public_label + add esp,12 + stos word [edi] + jmp parse_public_directive + parse_public_label: + pop ecx esi + mov al,2 + stos byte [edi] + call get_label_id + stos dword [edi] + mov ax,8600h + stos word [edi] + pop ebx + push ebx esi edi + mov edi,directive_operators + call get_operator + pop edi edx ebx + cmp al,86h + je argument_parsed + mov esi,edx + xchg esi,ebx + movzx ecx,byte [esi] + inc esi + mov ax,'(' + stos word [edi] + mov eax,ecx + stos dword [edi] + rep movs byte [edi],[esi] + xor al,al + stos byte [edi] + xchg esi,ebx + jmp argument_parsed + parse_extrn_directive: + cmp byte [esi],22h + je parse_quoted_extrn + cmp byte [esi],1Ah + jne parse_argument + push esi + movzx ecx,byte [esi+1] + add esi,2 + mov ax,'(' + stos word [edi] + mov eax,ecx + stos dword [edi] + rep movs byte [edi],[esi] + mov ax,8600h + stos word [edi] + pop esi + parse_label_operator: + cmp byte [esi],1Ah + jne argument_parsed + inc esi + movzx ecx,byte [esi] + inc esi + mov al,2 + stos byte [edi] + call get_label_id + stos dword [edi] + xor al,al + stos byte [edi] + jmp argument_parsed + parse_from_operator: + cmp byte [esi],22h + je argument_parsed + parse_at_operator: + cmp byte [esi],':' + je argument_parsed + jmp forced_multipart_expression + parse_quoted_extrn: + inc esi + mov ax,'(' + stos word [edi] + lods dword [esi] + mov ecx,eax + stos dword [edi] + rep movs byte [edi],[esi] + xor al,al + stos byte [edi] + push esi edi + mov edi,directive_operators + call get_operator + mov edx,esi + pop edi esi + cmp al,86h + jne argument_parsed + stos byte [edi] + mov esi,edx + jmp parse_label_operator + ptr_argument: + call parse_address + jmp address_parsed + check_argument: + push esi ecx + sub esi,2 + mov edi,single_operand_operators + call get_operator + pop ecx esi + or al,al + jnz not_instruction + call get_instruction + jnc embedded_instruction + call get_data_directive + jnc embedded_instruction + not_instruction: + pop edi + sub esi,2 + expression_argument: + cmp byte [esi],22h + jne not_string + mov eax,[esi+1] + lea ebx,[esi+5+eax] + push ebx ecx esi edi + call parse_expression + pop eax edx ecx ebx + cmp esi,ebx + jne expression_argument_parsed + mov edi,eax + mov esi,edx + string_argument: + inc esi + mov ax,'(' + stos word [edi] + lods dword [esi] + mov ecx,eax + stos dword [edi] + shr ecx,1 + jnc string_movsb_ok + movs byte [edi],[esi] + string_movsb_ok: + shr ecx,1 + jnc string_movsw_ok + movs word [edi],[esi] + string_movsw_ok: + rep movs dword [edi],[esi] + xor al,al + stos byte [edi] + jmp expression_argument_parsed + parse_expression: + mov al,'(' + stos byte [edi] + call convert_expression + mov al,')' + stos byte [edi] + ret + not_string: + cmp byte [esi],'(' + jne expression + mov eax,esp + sub eax,[stack_limit] + cmp eax,100h + jb stack_overflow + push esi edi + inc esi + mov al,91h + stos byte [edi] + inc [parenthesis_stack] + jmp parse_argument + expression_comparator: + stos byte [edi] + jmp forced_expression + greater: + cmp byte [esi],'=' + jne separator + inc esi + mov al,0F2h + jmp separator + less: + cmp byte [edi-1],0F6h + je separator + cmp byte [esi],'>' + je not_equal + cmp byte [esi],'=' + jne separator + inc esi + mov al,0F3h + jmp separator + not_equal: + inc esi + mov al,0F1h + jmp expression_comparator + expression: + call parse_expression + jmp expression_argument_parsed + forced_expression: + xor al,al + xchg al,[formatter_symbols_allowed] + push eax + call parse_expression + forced_expression_parsed: + pop eax + mov [formatter_symbols_allowed],al + jmp argument_parsed + forced_multipart_expression: + xor al,al + xchg al,[formatter_symbols_allowed] + push eax + call parse_expression + cmp byte [esi],':' + jne forced_expression_parsed + movs byte [edi],[esi] + call parse_expression + jmp forced_expression_parsed + address_argument: + call parse_address + lods byte [esi] + cmp al,']' + je address_parsed + cmp al,',' + je divided_address + dec esi + mov al,')' + stos byte [edi] + jmp argument_parsed + divided_address: + mov ax,'),' + stos word [edi] + jmp expression + address_parsed: + mov al,']' + stos byte [edi] + jmp argument_parsed + parse_address: + mov al,'[' + stos byte [edi] + cmp word [esi],021Ah + jne convert_address + push esi + add esi,4 + lea ebx,[esi+1] + cmp byte [esi],':' + pop esi + jne convert_address + add esi,2 + mov ecx,2 + push ebx edi + call get_symbol + pop edi esi + jc unknown_segment_prefix + cmp al,10h + jne unknown_segment_prefix + mov al,ah + and ah,11110000b + cmp ah,30h + jne unknown_segment_prefix + add al,30h + stos byte [edi] + jmp convert_address + unknown_segment_prefix: + sub esi,5 + convert_address: + push edi + mov edi,address_sizes + call get_operator + pop edi + or al,al + jz convert_expression + add al,70h + stos byte [edi] + jmp convert_expression + forced_parenthesis: + cmp byte [esi],'(' + jne argument_parsed + inc esi + mov al,91h + jmp separator + unallowed_character: + mov al,0FFh + jmp separator + open_decorator: + inc [decorator_symbols_allowed] + jmp separator + close_decorator: + dec [decorator_symbols_allowed] + jmp separator + close_parenthesis: + mov al,92h + separator: + stos byte [edi] + argument_parsed: + cmp [parenthesis_stack],0 + je parse_argument + dec [parenthesis_stack] + add esp,8 + jmp argument_parsed + expression_argument_parsed: + cmp [parenthesis_stack],0 + je parse_argument + cmp byte [esi],')' + jne argument_parsed + dec [parenthesis_stack] + pop edi esi + jmp expression + contents_parsed: + cmp [parenthesis_stack],0 + je contents_ok + dec [parenthesis_stack] + add esp,8 + jmp contents_parsed + contents_ok: + ret + + mov al,2 + xor edx,edx + call lseek + mov edx,[esi+8] + sub eax,edx + jz line_data_displayed + push eax + xor al,al + call lseek + mov ecx,[esp] + mov edx,[additional_memory] + lea eax,[edx+ecx] + cmp eax,[additional_memory_end] + ja out_of_memory + call read + call close + pop ecx + mov esi,[additional_memory] + get_line_data: + mov al,[esi] + cmp al,0Ah + je display_line_data + cmp al,0Dh + je display_line_data + cmp al,1Ah + je display_line_data + or al,al + jz display_line_data + inc esi + loop get_line_data + display_line_data: + mov ecx,esi + mov esi,[additional_memory] + sub ecx,esi + call display_block + line_data_displayed: + mov esi,cr_lf + call display_string + pop ebx + or ebx,ebx + jnz display_error_line + cmp [preprocessing_done],0 + je display_error_message + mov esi,preprocessed_instruction_prefix + call display_string + mov esi,[current_line] + add esi,16 + mov edi,[additional_memory] + xor dl,dl + convert_instruction: + lodsb + cmp al,1Ah + je copy_symbol + cmp al,22h + je copy_symbol + cmp al,3Bh + je instruction_converted + stosb + or al,al + jz instruction_converted + xor dl,dl + jmp convert_instruction + copy_symbol: + or dl,dl + jz space_ok + mov byte [edi],20h + inc edi + space_ok: + cmp al,22h + je quoted + lodsb + movzx ecx,al + rep movsb + or dl,-1 + jmp convert_instruction + quoted: + mov al,27h + stosb + lodsd + mov ecx,eax + jecxz quoted_copied + copy_quoted: + lodsb + stosb + cmp al,27h + jne quote_ok + stosb + quote_ok: + loop copy_quoted + quoted_copied: + mov al,27h + stosb + or dl,-1 + jmp convert_instruction + instruction_converted: + xor al,al + stosb + mov esi,[additional_memory] + call display_string + mov esi,cr_lf + call display_string + display_error_message: + mov esi,error_prefix + call display_string + pop esi + call display_string + mov esi,error_suffix + call display_string + mov al,2 + jmp exit_program + +make_timestamp: + push buffer + call [GetSystemTime] + movzx ecx,word [buffer] + mov eax,ecx + sub eax,1970 + mov ebx,365 + mul ebx + mov ebp,eax + mov eax,ecx + sub eax,1969 + shr eax,2 + add ebp,eax + mov eax,ecx + sub eax,1901 + mov ebx,100 + div ebx + sub ebp,eax + mov eax,ecx + xor edx,edx + sub eax,1601 + mov ebx,400 + div ebx + add ebp,eax + movzx ecx,word [buffer+2] + mov eax,ecx + dec eax + mov ebx,30 + mul ebx + add ebp,eax + cmp ecx,8 + jbe months_correction + mov eax,ecx + sub eax,7 + shr eax,1 + add ebp,eax + mov ecx,8 + months_correction: + mov eax,ecx + shr eax,1 + add ebp,eax + cmp ecx,2 + jbe day_correction_ok + sub ebp,2 + movzx ecx,word [buffer] + test ecx,11b + jnz day_correction_ok + xor edx,edx + mov eax,ecx + mov ebx,100 + div ebx + or edx,edx + jnz day_correction + mov eax,ecx + mov ebx,400 + div ebx + or edx,edx + jnz day_correction_ok + day_correction: + inc ebp + day_correction_ok: + movzx eax,word [buffer+6] + dec eax + add eax,ebp + mov ebx,24 + mul ebx + movzx ecx,word [buffer+8] + add eax,ecx + mov ebx,60 + mul ebx + movzx ecx,word [buffer+10] + add eax,ecx + mov ebx,60 + mul ebx + movzx ecx,word [buffer+12] + add eax,ecx + adc edx,0 + ret + +error_prefix db 'error: ',0 +error_suffix db '.' +cr_lf db 0Dh,0Ah,0 +line_number_start db ' [',0 +line_data_start db ':',0Dh,0Ah,0 +preprocessed_instruction_prefix db 'processed: ',0 + + +; flat assembler interface for Linux +; Copyright (c) 1999-2022, Tomasz Grysztar. +; All rights reserved. + + format ELF executable 3 + entry start + +segment readable executable + +start: + + mov [con_handle],1 + mov esi,_logo + call display_string + + mov [command_line],esp + mov ecx,[esp] + lea ebx,[esp+4+ecx*4+4] + mov [environment],ebx + call get_params + jc information + + call init_memory + + mov esi,_memory_prefix + call display_string + mov eax,[memory_end] + sub eax,[memory_start] + add eax,[additional_memory_end] + sub eax,[additional_memory] + shr eax,10 + call display_number + mov esi,_memory_suffix + call display_string + + mov eax,78 + mov ebx,buffer + xor ecx,ecx + int 0x80 + mov eax,dword [buffer] + mov ecx,1000 + mul ecx + mov ebx,eax + mov eax,dword [buffer+4] + div ecx + add eax,ebx + mov [start_time],eax + + and [preprocessing_done],0 + call preprocessor + or [preprocessing_done],-1 + call parser + call assembler + call formatter + + call display_user_messages + movzx eax,[current_pass] + inc eax + call display_number + mov esi,_passes_suffix + call display_string + mov eax,78 + mov ebx,buffer + xor ecx,ecx + int 0x80 + mov eax,dword [buffer] + mov ecx,1000 + mul ecx + mov ebx,eax + mov eax,dword [buffer+4] + div ecx + add eax,ebx + sub eax,[start_time] + jnc time_ok + add eax,3600000 + time_ok: + xor edx,edx + mov ebx,100 + div ebx + or eax,eax + jz display_bytes_count + xor edx,edx + mov ebx,10 + div ebx + push edx + call display_number + mov dl,'.' + call display_character + pop eax + call display_number + mov esi,_seconds_suffix + call display_string + display_bytes_count: + mov eax,[written_size] + call display_number + mov esi,_bytes_suffix + call display_string + xor al,al + jmp exit_program + +information: + mov esi,_usage + call display_string + mov al,1 + jmp exit_program + +get_params: + mov ebx,[command_line] + mov [input_file],0 + mov [output_file],0 + mov [symbols_file],0 + mov [memory_setting],0 + mov [passes_limit],100 + mov ecx,[ebx] + add ebx,8 + dec ecx + jz bad_params + mov [definitions_pointer],predefinitions + get_param: + mov esi,[ebx] + mov al,[esi] + cmp al,'-' + je option_param + cmp [input_file],0 + jne get_output_file + mov [input_file],esi + jmp next_param + get_output_file: + cmp [output_file],0 + jne bad_params + mov [output_file],esi + jmp next_param + option_param: + inc esi + lodsb + cmp al,'m' + je memory_option + cmp al,'M' + je memory_option + cmp al,'p' + je passes_option + cmp al,'P' + je passes_option + cmp al,'d' + je definition_option + cmp al,'D' + je definition_option + cmp al,'s' + je symbols_option + cmp al,'S' + je symbols_option + bad_params: + stc + ret + memory_option: + cmp byte [esi],0 + jne get_memory_setting + dec ecx + jz bad_params + add ebx,4 + mov esi,[ebx] + get_memory_setting: + call get_option_value + or edx,edx + jz bad_params + cmp edx,1 shl (32-10) + jae bad_params + mov [memory_setting],edx + jmp next_param + passes_option: + cmp byte [esi],0 + jne get_passes_setting + dec ecx + jz bad_params + add ebx,4 + mov esi,[ebx] + get_passes_setting: + call get_option_value + or edx,edx + jz bad_params + cmp edx,10000h + ja bad_params + mov [passes_limit],dx + next_param: + add ebx,4 + dec ecx + jnz get_param + cmp [input_file],0 + je bad_params + mov eax,[definitions_pointer] + mov byte [eax],0 + mov [initial_definitions],predefinitions + clc + ret + definition_option: + cmp byte [esi],0 + jne get_definition + dec ecx + jz bad_params + add ebx,4 + mov esi,[ebx] + get_definition: + push edi + mov edi,[definitions_pointer] + call convert_definition_option + mov [definitions_pointer],edi + pop edi + jc bad_params + jmp next_param + symbols_option: + cmp byte [esi],0 + jne get_symbols_setting + dec ecx + jz bad_params + add ebx,4 + mov esi,[ebx] + get_symbols_setting: + mov [symbols_file],esi + jmp next_param + get_option_value: + xor eax,eax + mov edx,eax + get_option_digit: + lodsb + cmp al,20h + je option_value_ok + or al,al + jz option_value_ok + sub al,30h + jc invalid_option_value + cmp al,9 + ja invalid_option_value + imul edx,10 + jo invalid_option_value + add edx,eax + jc invalid_option_value + jmp get_option_digit + option_value_ok: + dec esi + clc + ret + invalid_option_value: + stc + ret + convert_definition_option: + mov edx,edi + cmp edi,predefinitions+1000h + jae bad_definition_option + xor al,al + stosb + copy_definition_name: + lodsb + cmp al,'=' + je copy_definition_value + cmp al,20h + je bad_definition_option + or al,al + jz bad_definition_option + cmp edi,predefinitions+1000h + jae bad_definition_option + stosb + inc byte [edx] + jnz copy_definition_name + bad_definition_option: + stc + ret + copy_definition_value: + lodsb + cmp al,20h + je definition_value_end + or al,al + jz definition_value_end + cmp edi,predefinitions+1000h + jae bad_definition_option + stosb + jmp copy_definition_value + definition_value_end: + dec esi + cmp edi,predefinitions+1000h + jae bad_definition_option + xor al,al + stosb + clc + ret + +include 'system.inc' + +include '..\version.inc' + +_copyright db 'Copyright (c) 1999-2022, Tomasz Grysztar',0xA,0 + +_logo db 'flat assembler version ',VERSION_STRING,0 +_usage db 0xA + db 'usage: fasm [output]',0xA + db 'optional settings:',0xA + db ' -m set the limit in kilobytes for the available memory',0xA + db ' -p set the maximum allowed number of passes',0xA + db ' -d = define symbolic variable',0xA + db ' -s dump symbolic information for debugging',0xA + db 0 +_memory_prefix db ' (',0 +_memory_suffix db ' kilobytes memory)',0xA,0 +_passes_suffix db ' passes, ',0 +_seconds_suffix db ' seconds, ',0 +_bytes_suffix db ' bytes.',0xA,0 + +include '..\errors.inc' +include '..\symbdump.inc' +include '..\preproce.inc' +include '..\parser.inc' +include '..\exprpars.inc' +include '..\assemble.inc' +include '..\exprcalc.inc' +include '..\formats.inc' +include '..\x86_64.inc' +include '..\avx.inc' + +include '..\tables.inc' +include '..\messages.inc' + +segment readable writeable + +align 4 + +include '..\variable.inc' + +command_line dd ? +memory_setting dd ? +definitions_pointer dd ? +environment dd ? +timestamp dq ? +start_time dd ? +con_handle dd ? +displayed_count dd ? +last_displayed db ? +character db ? +preprocessing_done db ? + +predefinitions rb 1000h +buffer rb 1000h + +push dword [BackgroundColour] + call _CreateSolidBrush@4 ; Create a brush for the window backgound + mov dword [BackgroundBrush], EAX + + mov dword [wc.cbSize], 48 ; [EBP - 80] + mov dword [wc.style], CS_HREDRAW | CS_VREDRAW | CS_BYTEALIGNWINDOW ; [EBP - 76] + mov dword [wc.lpfnWndProc], WndProc ; [EBP - 72] + mov dword [wc.cbClsExtra], NULL ; [EBP - 68] + mov dword [wc.cbWndExtra], NULL ; [EBP - 64] + mov EAX, dword [hInstance] + mov dword [wc.hInstance], EAX ; [EBP - 60] + + push LR_SHARED + push NULL + push NULL + push IMAGE_ICON + push IDI_APPLICATION + push NULL + call _LoadImageA@24 ; Large program icon + mov dword [wc.hIcon], EAX ; [EBP - 56] + + push LR_SHARED + push NULL + push NULL + push IMAGE_CURSOR + push IDC_ARROW + push NULL + call _LoadImageA@24 ; Cursor + mov dword [wc.hCursor], EAX ; [EBP - 52] + + mov EAX, dword [BackgroundBrush] + mov dword [wc.hbrBackground], EAX ; [EBP - 48] + mov dword [wc.lpszMenuName], NULL ; [EBP - 44] + mov dword [wc.lpszClassName], ClassName ; [EBP - 40] + + push LR_SHARED + push NULL + push NULL + push IMAGE_ICON + push IDI_APPLICATION + push NULL + call _LoadImageA@24 ; Small program icon + mov dword [wc.hIconSm], EAX ; [EBP - 36] + + lea EAX, [wc] ; [EBP - 80] + push EAX + call _RegisterClassExA@4 + + push SM_CXFULLSCREEN + call _GetSystemMetrics@4 ; Get the current screen width + mov dword [Screen.Width], EAX ; [EBP - 104] + + push SM_CYFULLSCREEN + call _GetSystemMetrics@4 ; Get the current screen height + mov dword [Screen.Height], EAX ; [EBP - 100] + + mov dword [ClientArea.left], 0 ; [EBP - 96] + mov dword [ClientArea.top], 0 ; [EBP - 92] + mov dword [ClientArea.right], WindowWidth ; [EBP - 88] + mov dword [ClientArea.bottom], WindowHeight ; [EBP - 84] + + push WS_EX_COMPOSITED ; Extended style + push NULL + push WS_OVERLAPPEDWINDOW ; Style + lea EAX, [ClientArea] ; [EBP - 96] + push EAX + call _AdjustWindowRectEx@16 ; Get window size for the desired client size + ; Size is returned in ClientArea + mov EAX, dword [ClientArea.top] ; [EBP - 92] + sub dword [ClientArea.bottom], EAX ; New Height = ClientArea.bottom - ClientArea.top + + mov EAX, dword [ClientArea.left] ; [EBP - 96] + sub dword [ClientArea.right], EAX ; New Width = ClientArea.right - ClientArea.left + + push NULL + push dword [hInstance] + push NULL + push NULL + + push dword [ClientArea.bottom] ; Height. [EBP - 84] + push dword [ClientArea.right] ; Width. [EBP - 88] + + xor ECX, ECX + mov EAX, dword [Screen.Height] ; [EBP - 100] + sub EAX, dword [ClientArea.bottom] ; Corrected window height. [EBP - 84] + cmovs EAX, ECX ; Clamp to 0 (top) if negative + shr EAX, 1 ; EAX = (Screen.Height - window height) / 2 + push EAX ; Y position, now centred + + mov EAX, dword [Screen.Width] ; [EBP - 104] + sub EAX, dword [ClientArea.right] ; Corrected window width. [EBP - 88] + cmovs EAX, ECX ; Clamp to 0 (left) if negative + shr EAX, 1 ; EAX = (Screen.Width - window width) / 2 + push EAX ; X position, now centred + + push WS_OVERLAPPEDWINDOW + push WindowName + push ClassName + push WS_EX_COMPOSITED + call _CreateWindowExA@48 + mov dword [hWnd], EAX ; [EBP - 4] + + push SW_SHOWNORMAL + push dword [hWnd] ; [EBP - 4] + call _ShowWindow@8 + + push dword [hWnd] ; [EBP - 4] + call _UpdateWindow@4 + +.MessageLoop: + push NULL + push NULL + push NULL + lea EAX, [msg] ; [EBP - 32] + push EAX + call _GetMessageA@16 + cmp EAX, 0 + je .Done + + lea EAX, [msg] ; [EBP - 32] + push EAX + push dword [hWnd] ; [EBP - 4] + call _IsDialogMessageA@8 ; For keyboard strokes + cmp EAX, 0 + jne .MessageLoop ; Skip TranslateMessage and DispatchMessage + + lea EAX, [msg] ; [EBP - 32] + push EAX + call _TranslateMessage@4 + + lea EAX, [msg] ; [EBP - 32] + push EAX + call _DispatchMessageA@4 + jmp .MessageLoop + +.Done: + xor EAX, EAX + mov ESP, EBP ; Remove the stack frame + pop EBP + ret + +WndProc: + push EBP ; Set up a stack frame + mov EBP, ESP + sub ESP, 68 ; 68 bytes for local variables + +%define hWnd EBP + 8 ; Location of the 4 passed parameters from +%define uMsg EBP + 12 ; the calling function +%define wParam EBP + 16 ; We can now access these parameters by name +%define lParam EBP + 20 + +%define ps EBP - 68 ; PAINTSTRUCT structure. 64 bytes +%define ps.hdc EBP - 68 +%define ps.fErase EBP - 64 +%define ps.rcPaint.left EBP - 60 +%define ps.rcPaint.top EBP - 56 +%define ps.rcPaint.right EBP - 52 +%define ps.rcPaint.bottom EBP - 48 +%define ps.Restore EBP - 44 +%define ps.fIncUpdate EBP - 40 +%define ps.rgbReserved EBP - 36 + +%define hdc EBP - 4 + + cmp dword [uMsg], WM_CLOSE ; [EBP + 12] + je WMCLOSE + + cmp dword [uMsg], WM_COMMAND ; [EBP + 12] + je WMCOMMAND + + cmp dword [uMsg], WM_CREATE ; [EBP + 12] + je WMCREATE + + cmp dword [uMsg], WM_CTLCOLOREDIT ; [EBP + 12] + je WMCTLCOLOREDIT + + cmp dword [uMsg], WM_CTLCOLORSTATIC ; [EBP + 12] + je WMCTLCOLORSTATIC + + cmp dword [uMsg], WM_DESTROY ; [EBP + 12] + je WMDESTROY + + cmp dword [uMsg], WM_PAINT ; [EBP + 12] + je WMPAINT + +DefaultMessage: + push dword [lParam] ; [EBP + 20] + push dword [wParam] ; [EBP + 16] + push dword [uMsg] ; [EBP + 12] + push dword [hWnd] ; [EBP + 8] + call _DefWindowProcA@16 + jmp Return + +WMCLOSE: + push MB_YESNO | MB_DEFBUTTON2 + push WindowName + push ExitText + push dword [hWnd] ; [EBP + 8] + call _MessageBoxA@16 + + cmp EAX, IDNO + je Return.WM_Processed + + push dword [hWnd] ; [EBP + 8] + call _DestroyWindow@4 ; Send a WM_DESTROY message + jmp Return.WM_Processed + +WMCOMMAND: + mov EAX, dword [wParam] ; EAX = ID. [EBP + 16] + + cmp AX, Static1ID + je .Static1 + + cmp AX, Static2ID + je .Static2 + + jmp Return.WM_Processed + +.Static1: + mov EAX, dword [Static1Colour] + mov EBX, dword [Static1ColourA] + mov dword [Static1Colour], EBX + mov dword [Static1ColourA], EAX ; Swap colours + + push TRUE + push NULL + push dword [lParam] ; Static1 handle. [EBP + 20] + call _InvalidateRect@12 ; Redraw control + jmp Return.WM_Processed + +.Static2: + mov EAX, dword [Static2Colour] + mov EBX, dword [Static2ColourA] + mov dword [Static2Colour], EBX + mov dword [Static2ColourA], EAX ; Swap colours + + push TRUE + push NULL + push dword [lParam] ; Static2 handle. [EBP + 20] + call _InvalidateRect@12 ; Redraw control + jmp Return.WM_Processed + +WMCREATE: + push NULL + push dword [hInstance] + push Static1ID + push dword [hWnd] ; [EBP + 8] + push 20 ; Height + push 400 ; Width + push 10 ; Y + push 120 ; X + push WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_CENTER + push Text1 ; Default text + push StaticClass + push NULL + call _CreateWindowExA@48 + mov dword [Static1], EAX + + push NULL + push dword [hInstance] + push Static2ID + push dword [hWnd] ; [EBP + 8] + push 20 ; Height + push 400 ; Width + push 40 ; Y + push 120 ; X + push WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_CENTER + push Text2 ; Default text + push StaticClass + push NULL + call _CreateWindowExA@48 + mov dword [Static2], EAX + + push NULL + push dword [hInstance] + push Edit1ID + push dword [hWnd] ; [EBP + 8] + push 20 ; Height + push 400 ; Width + push 70 ; Y + push 120 ; X + push WS_CHILD | WS_VISIBLE | ES_CENTER | WS_TABSTOP | ES_AUTOHSCROLL + push Text1 ; Default text + push EditClass + push NULL + call _CreateWindowExA@48 + mov dword [Edit1], EAX + + push NULL + push dword [hInstance] + push Edit2ID + push dword [hWnd] ; [EBP + 8] + push 20 ; Height + push 400 ; Width + push 100 ; Y + push 120 ; X + push WS_CHILD | WS_VISIBLE | ES_CENTER | WS_TABSTOP | ES_AUTOHSCROLL + push Text2 ; Default text + push EditClass + push NULL + call _CreateWindowExA@48 + mov dword [Edit2], EAX + + lea EAX, [SegoeUI] + push EAX + push DEFAULT_PITCH + push PROOF_QUALITY + push CLIP_DEFAULT_PRECIS + push OUT_DEFAULT_PRECIS + push ANSI_CHARSET + push NULL + push NULL + push NULL + push 400 ; Weight + push NULL + push NULL + push NULL + push 20 ; Size + call _CreateFontA@56 + mov dword [Font], EAX + + push FALSE + push dword [Font] + push WM_SETFONT + push dword [Static1] + call _SendMessageA@16 ; Set Static1 font + + push FALSE + push dword [Font] + push WM_SETFONT + push dword [Static2] + call _SendMessageA@16 ; Set Static2 font + + push FALSE + push dword [Font] + push WM_SETFONT + push dword [Edit1] + call _SendMessageA@16 ; Set Edit1 font + + push FALSE + push dword [Font] + push WM_SETFONT + push dword [Edit2] + call _SendMessageA@16 ; Set Edit2 font + + jmp Return.WM_Processed + +WMCTLCOLOREDIT: ; For colouring edit controls + push dword [lParam] ; [EBP + 20] + call _GetDlgCtrlID@4 ; EAX = ID + + cmp EAX, Edit1ID + je .Edit1 + + cmp EAX, Edit2ID + je .Edit2 + +.Default: + push NULL_BRUSH + call _GetStockObject@4 ; Return a brush + jmp Return + +.Edit1: + push dword [Edit1TextColour] + push dword [wParam] ; [EBP + 16] + call _SetTextColor@8 + + push OPAQUE + push dword [wParam] ; [EBP + 16] + call _SetBkMode@8 + + push dword [Edit1BackColour] + push dword [wParam] ; [EBP + 16] + call _SetBkColor@8 + + push NULL_BRUSH + call _GetStockObject@4 ; Return a brush + jmp Return + +.Edit2: + push dword [Edit2TextColour] + push dword [wParam] ; [EBP + 16] + call _SetTextColor@8 + + push OPAQUE + push dword [wParam] ; [EBP + 16] + call _SetBkMode@8 + + push dword [Edit2BackColour] + push dword [wParam] ; [EBP + 16] + call _SetBkColor@8 + + push NULL_BRUSH + call _GetStockObject@4 ; Return a brush + jmp Return + +WMCTLCOLORSTATIC: ; For colouring static controls + push dword [lParam] ; [EBP + 20] + call _GetDlgCtrlID@4 ; EAX = ID + + cmp EAX, Static1ID + je .Static1 + + cmp EAX, Static2ID + je .Static2 + +.Default: + push NULL_BRUSH + call _GetStockObject@4 ; Return a brush + jmp Return + +.Static1: + push dword [Static1Colour] + push dword [wParam] ; [EBP + 16] + call _SetTextColor@8 + + push OPAQUE + push dword [wParam] ; [EBP + 16] + call _SetBkMode@8 + + push 0604060h + push dword [wParam] ; [EBP + 16] + call _SetBkColor@8 + + push NULL_BRUSH + call _GetStockObject@4 ; Return a brush + jmp Return + +.Static2: + push dword [Static2Colour] + push dword [wParam] ; [EBP + 16] + call _SetTextColor@8 + + push OPAQUE + push dword [wParam] ; [EBP + 16] + call _SetBkMode@8 + + push 0005000h + push dword [wParam] ; [EBP + 16] + call _SetBkColor@8 + + push GRAY_BRUSH + call _GetStockObject@4 ; Return a brush + jmp Return + +WMDESTROY: + push dword [BackgroundBrush] + call _DeleteObject@4 + + push dword [Font] + call _DeleteObject@4 + + push NULL + call _PostQuitMessage@4 + jmp Return.WM_Processed + +WMPAINT: + lea EAX, [ps] ; Starting address of PAINTSTRUCT. [EBP - 68] + push EAX + push dword [hWnd] ; [EBP + 8] + call _BeginPaint@8 + mov dword [hdc], EAX + + push BLACKNESS ; Operation + push 0 ; Source Y + push 0 ; Source X + push dword [hdc] ; Source device context + push 20 ; Height + push 400 ; Width + push 130 ; Destination Y + push 120 ; Destination X + push dword [hdc] ; Destination device context + call _BitBlt@36 ; Blit a black rectangle + + lea EAX, [ps] ; [EBP - 68] + push EAX + push dword [hWnd] ; [EBP + 8] + call _EndPaint@8 + +Return.WM_Processed: + xor EAX, EAX ; WM_ has been processed, return 0 + +Return: + mov ESP, EBP ; Remove the stack frame + pop EBP + ret 16 ; Pop 4 parameters off the stack and return + +.586 +.MODEL flat, C + +printf PROTO, pString : PTR BYTE, args : VARARG +scanf PROTO, pFormat : PTR BYTE, args : VARARG + +.data + +prime_numbers dword 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 +number dword 0 + +string byte "Please enter number: ",0 +printf_string byte "%d", 0dh, 0ah, 0 +scanf_string byte "%d", 0 + +.code +asmMain PROC + +INVOKE printf, ADDR string +invoke scanf, addr scanf_string, addr number + +mov esi, offset prime_numbers +mov ecx, lengthof prime_numbers +L1 : +mov eax, number +mov edx, 0 +mov ebx, [esi] +div ebx +cmp edx, 0 +je not_prime_number +add esi, 4 +loop L1 + +prime_number : +mov eax, 1 +INVOKE printf, ADDR printf_string, eax +jmp L2 + +not_prime_number : +mov eax, 0 +INVOKE printf, ADDR printf_string, eax + +L2 : + +ret +asmMain ENDP +END + +TITLE CHKDSK - MS-DOS Disk consistancy checker + +; CHKDSK Version 2.30 +; Verifies and repairs MS-DOS disk directory. + + +; To build CHKDSK you need three modules: +; CHKDSK CHKPROC CHKMES +; They should be linked the that order as well. + + +; REVISION HISTORY + +;REV 1.1 +; 05/21/82 Added rev number + +;REV 1.5 +; Mod by NANCYP to report on extents +; Mod by AARONR to report volume ID + +;REV 2.0 +; Total rewrite for directories + +;REV 2.1 +; Added ^C and INT 24H handlers + +;REV 2.2 +; INTERNATIONAL support + +;REV 2.3 +; Split into two modules to allow assembly on a PC +; CHKDSK and CHKPROC + +FALSE EQU 0 +TRUE EQU NOT FALSE + +DRVCHAR EQU ":" + +;The following defines the ranges of DOS version numbers for which this CHKDSK +; is good + +DOSVER_LOW EQU 0136H ;1.54 in hex +DOSVER_HIGH EQU 020BH ;2.11 in hex + + + INCLUDE DOSSYM.ASM + +FCB EQU 5CH + +;Drive parameter block from DOS header + +SUBTTL Segments used in load order + +CODE SEGMENT PUBLIC +CODE ENDS + +CONST SEGMENT PUBLIC BYTE +CONST ENDS + +DATA SEGMENT PUBLIC WORD +DATA ENDS + +DG GROUP CODE,CONST,DATA + +SUBTTL Initialized Data +PAGE +CONST SEGMENT PUBLIC BYTE + + PUBLIC HECODE,SWITCHAR,NOISY,DOFIX,CONBUF,ORPHCNT,ORPHSIZ,DOFIX + PUBLIC HIDCNT,HIDSIZ,DIRCNT,DIRSIZ,FILCNT,FILSIZ,BADSIZ,LCLUS + PUBLIC DOTENT,HAVFIX,SECONDPASS,NUL,ALLFILE,PARSTR,ERRSUB,LCLUS + PUBLIC DIRTYFAT,BADSIZ,DDOTENT,CROSSCNT,ORPHFCB,ORPHEXT,ALLDRV + PUBLIC FRAGMENT,USERDIR,DIRBUF,USERDIR,FIXMFLG,DOTMES,DIRCHAR + + EXTRN IDMES1:BYTE,IDPOST:BYTE,VNAME:BYTE,MONTAB:BYTE + EXTRN TCHAR:BYTE,BADREAD_PRE:BYTE,BADREAD_POST:BYTE + EXTRN CRLF:BYTE,BADVER:BYTE,BADSUBDIR:BYTE,CENTRY:BYTE + EXTRN BADDRV:BYTE,BADCD:BYTE,BADRDMES:BYTE,OPNERR:BYTE + EXTRN CONTAINS:BYTE,EXTENTS:BYTE,NOEXTENTS:BYTE + EXTRN BADDRVM:BYTE,BADDRVM2:BYTE,BADIDBYT:BYTE + + +DIRBUF LABEL BYTE ;Entry buffer for searches +VOLID DB -1,0,0,0,0,0,8 ;Volume ID FCB +VOLNAM DB 0,"???????????" + DB 25 DUP(0) + +ALLFILE DB -1,0,0,0,0,0,1EH ;Extended FCB +ALLDRV DB 0,"???????????" + DB 25 DUP (?) + +ORPHFCB DB 0,"FILE0000" +ORPHEXT DB "CHK" + DB 25 DUP (?) + + +;Non-message data + +SWITCHAR DB "-" +ROOTSTR LABEL BYTE +DIRCHAR DB "/" +NUL DB 0 +PARSTR DB "..",0 +DOTMES DB ".",0 +DOTENT DB ". " +DDOTENT DB ".. " +HECODE DB ? +FIXMFLG DB 0 ;Flag for printing fixmes +ERRSUB DW 0 ;Flag for bad subdir error +FRAGMENT DB 0 ;Flag for extent processing +DIRTYFAT DB 0 ;Dirty flag for FAT +DIRCNT DW 0 ;# directories +DIRSIZ DW 0 ;# alloc units in directories +FILCNT DW 0 ;# reg files +FILSIZ DW 0 ;# alloc units in reg files +HIDCNT DW 0 ;# hidden files +HIDSIZ DW 0 ;# alloc units in hidden files +BADSIZ DW 0 ;# alloc units in bad sectors +ORPHCNT DW 0 ;# orphan files made +ORPHSIZ DW 0 ;# alloc units in orphan files +LCLUS DW 0 ;# alloc units in lost clusters +DISPFLG DB 0 ;used by number routines +CROSSCNT DW 0 ;# crosslinked files (first pass) +SECONDPASS DB 0 ;Pass flag +HAVFIX DB 0 ;non zero if any fixes +DOFIX DB 0 ;flag for F switch +NOISY DB 0 ;flag for V switch +USERDIR DB "/",0 ;Users current dir for drive + DB (DIRSTRLEN-1) DUP (?) +CONBUF DB 15,0 ;Input buffer + DB 15 DUP (?) + +CONST ENDS + +SUBTTL Un-initialized Data +PAGE +DATA SEGMENT PUBLIC WORD + + PUBLIC ZEROTRUNC,NAMBUF,MCLUS,THISDPB,STACKLIM,ERRCNT + PUBLIC SRFCBPT,ISCROSS,CSIZE,DSIZE,SSIZE,FAT,FATMAP + PUBLIC HARDCH,CONTCH,USERDEV,SECBUF,DOTSNOGOOD + +HARDCH DD ? ;Pointer to real INT 24 handler +CONTCH DD ? ;Pointer to real INT 23 handler +THISDPB DD ? ;Pointer to drive DPB +USERDEV DB ? ;Users current device +CSIZE DB ? ;Sectors per cluster +SSIZE DW ? ;bytes per sector +DSIZE DW ? ;# alloc units on disk +MCLUS DW ? ;DSIZE + 1 +NAMBUF DB 14 DUP (?) ;Buffer +DOTSNOGOOD DB ? ;. or .. error flag +ZEROTRUNC DB ? ;Trimming flag +ISCROSS DB ? ;Crosslink flag +OLDCLUS DW ? +SRFCBPT DW ? +FATMAP DW OFFSET DG:FAT ;Offset of FATMAP table +SECBUF DW ? ;Offset of sector buffer +ERRCNT DB ? ;Used by FATread and write +STACKLIM DW ? ;Stack growth limit + +INTERNATVARS internat_block <> + DB (internat_block_max - ($ - INTERNATVARS)) DUP (?) + +FAT LABEL WORD +DATA ENDS + + +SUBTTL Start of CHKDSK + +CODE SEGMENT PUBLIC +ASSUME CS:DG,DS:DG,ES:DG,SS:DG + + PUBLIC SUBERRP,DOTCOMBMES,FIGREC,FCB_TO_ASCZ,PRTCHR,EPRINT + PUBLIC PRINT,DOCRLF,DISP16BITS,DISP32BITS,DISPCLUS,CHECKFILES + + EXTRN RDSKERR:NEAR,SETSWITCH:NEAR,PROMPTYN:NEAR,REPORT:NEAR + EXTRN PRINTCURRDIRERR:NEAR,PRINTTHISEL2:NEAR,CHECKERR:NEAR + EXTRN INT_23:NEAR,INT_24:NEAR,FINDCHAIN:NEAR,DONE:NEAR,AMDONE:NEAR + EXTRN FATAL:NEAR,DIRPROC:NEAR,CHKMAP:NEAR,CHKCROSS:NEAR,UNPACK:NEAR + + ORG 100H + +CHKDSK: + JMP SHORT CHSTRT + +HEADER DB "Ver 2.30" + +CHSTRT: + +;Code to print header. +; PUSH AX +; MOV DX,OFFSET DG:HEADER +; CALL PRINT +; POP AX + + PUSH AX ;Save DRIVE validity info + MOV AH,GET_VERSION + INT 21H + XCHG AH,AL ;Turn it around to AH.AL + CMP AX,DOSVER_LOW + JB GOTBADDOS + CMP AX,DOSVER_HIGH + JBE OKDOS +GOTBADDOS: + MOV DX,OFFSET DG:BADVER + JMP CERROR + +OKDOS: + POP AX ;Get back drive info + MOV BX,0FFF0H + MOV DX,SP + CMP DX,BX + JAE STACKOK ;Lots of stack + MOV DX,DS:[2] ;High break + MOV CX,CS + SUB DX,CX + CMP DX,0FFFH + JAE SETSTACK ;Lots to grab + MOV CX,4 ;Suck up more stack (blast command) + SHL DX,CL + MOV BX,DX +SETSTACK: + CLI + MOV SP,BX + STI +STACKOK: + PUSH AX + MOV AH,DISK_RESET ;Flush everything, and invalidate + INT 21H + POP AX + CMP AL,0FFH ;Illegal drive specifier? + JNZ FILECHK ;No -- check for filename + +DRVERR: + MOV DX,OFFSET DG:BADDRV +CERROR: + PUSH CS ;Make sure DS is OK + POP DS + CALL PRINT ;Print error message + INT 20H + +CERROR2: + PUSH DX + CALL DONE ;Reset users disk + POP DX + JMP SHORT CERROR + +FILECHK: + MOV AX,(CHAR_OPER SHL 8) + INT 21H + MOV [SWITCHAR],DL + CMP DL,"/" + JNZ SLASHOK + MOV [DIRCHAR],"\" + MOV [USERDIR],"\" +SLASHOK: + CMP DS:(BYTE PTR FCB+1)," " ;Filename specified? + JZ DRVCHK ;No -- get the correct drive + MOV AL,[SWITCHAR] + CMP DS:(BYTE PTR FCB+1),AL ;Filename specified? + JZ DRVCHK ;No -- get the correct drive + MOV BYTE PTR [FRAGMENT],1 ;Set flag to perform fragment + ;check on specified files +DRVCHK: + CALL SETSWITCH ;Look for switches + MOV AH,GET_DEFAULT_DRIVE ;Get current drive + INT 21H + MOV [USERDEV],AL ;Save for later + MOV AH,AL + INC AH ;A = 1 + MOV BH,DS:(BYTE PTR FCB) ;See if drive specified + OR BH,BH + JZ SETDSK + MOV AL,BH + MOV AH,AL + DEC AL ;A = 0 +SETDSK: + MOV [ALLDRV],AH ;Target drive + MOV [VOLNAM],AH ;A = 1 + MOV [ORPHFCB],AH ;A = 1 + ADD [BADDRVM],AL ;A = 0 + ADD [BADDRVM2],AL ;A = 0 + MOV DL,AH ;A = 1 + MOV AH,GET_DPB ;Get the DPB + INT 21H +ASSUME DS:NOTHING + CMP AL,-1 + JNZ DRVISOK ;Bad drive (should always be ok) + MOV DX,OFFSET DG:BADDRV +CERROR2J: JMP CERROR2 + +DRVISOK: + DEC DL ;A = 0 + MOV AH,SET_DEFAULT_DRIVE ;Set Target + INT 21H + CMP [BX.dpb_current_dir],0 + JZ CURRISROOT ;Save users current dir for target + MOV SI,BX + ADD SI,dpb_dir_text + MOV DI,OFFSET DG:USERDIR + 1 +SETDIRLP: + LODSB + STOSB + OR AL,AL + JZ CURRISROOT + JMP SHORT SETDIRLP +CURRISROOT: + MOV WORD PTR [THISDPB+2],DS + PUSH CS + POP DS +ASSUME DS:DG + MOV WORD PTR [THISDPB],BX + MOV AX,(GET_INTERRUPT_VECTOR SHL 8) OR 23H + INT 21H + MOV WORD PTR [CONTCH],BX + MOV WORD PTR [CONTCH+2],ES + MOV AX,(SET_INTERRUPT_VECTOR SHL 8) OR 23H + MOV DX,OFFSET DG:INT_23 + INT 21H + MOV AX,(GET_INTERRUPT_VECTOR SHL 8) OR 24H + INT 21H + MOV WORD PTR [HARDCH],BX + MOV WORD PTR [HARDCH+2],ES + MOV AX,(SET_INTERRUPT_VECTOR SHL 8) OR 24H + MOV DX,OFFSET DG:INT_24 + INT 21H + PUSH CS + POP ES + MOV DX,OFFSET DG:ROOTSTR + MOV AH,CHDIR ;Start at root + INT 21H + MOV DX,OFFSET DG:BADCD + JC CERROR2J ;Couldn't get there + MOV DX,OFFSET DG:FAT ;Scratch space + MOV AH,SET_DMA + INT 21H + MOV DX,OFFSET DG:VOLID ;Look for VOL ID + MOV AH,DIR_SEARCH_FIRST + INT 21H + CMP AL,-1 + JZ NOTVOLID + CALL PRINTID ;Have a VOL ID +NOTVOLID: + LDS BX,[THISDPB] +ASSUME DS:NOTHING + MOV AX,[BX.dpb_sector_size] + MOV [SSIZE],AX ;Sector size in bytes + MOV AL,[BX.dpb_cluster_mask] + INC AL + MOV [CSIZE],AL ;Sectros per cluster + MOV AX,[BX.dpb_max_cluster] + MOV [MCLUS],AX ;Bound for FAT searching + DEC AX + MOV [DSIZE],AX ;Total data clusters on disk + MOV AL,[BX.dpb_FAT_size] ;Sectors for one fat + XOR AH,AH + MOV CX,AX + MUL [SSIZE] ;Bytes for FAT + ADD [FATMAP],AX ;Allocate FAT space + MOV AX,[FATMAP] + ADD AX,[MCLUS] + ADD AX,2 ;Insurance + MOV [SECBUF],AX ;Allocate FATMAP space + ADD AX,[SSIZE] + ADD AX,20 ;Insurance + MOV [STACKLIM],AX ;Limit on recursion + MOV DI,CX + MOV CL,[BX.dpb_FAT_count] ;Number of FATs + MOV DX,[BX.dpb_first_FAT] ;First sector of FAT + PUSH CS + POP DS +ASSUME DS:DG + MOV BX,OFFSET DG:FAT + MOV AL,[ALLDRV] + DEC AL + MOV AH,'1' +RDLOOP: + XCHG CX,DI + PUSH DX + PUSH CX + PUSH DI + PUSH AX + INT 25H ;Read in the FAT + MOV [HECODE],AL + POP AX ;Flags + JNC RDOK + MOV DX,OFFSET DG:BADREAD_PRE ;Barfed + CALL PRINT + POP AX + PUSH AX + MOV DL,AH + CALL PRTCHR + MOV DX,OFFSET DG:BADREAD_POST + CALL PRINT + POP AX + POP CX + POP DI + POP DX + INC AH + ADD DX,DI + LOOP RDLOOP ;Try next FAT + CALL RDSKERR + JNZ NORETRY1 + JMP NOTVOLID +NORETRY1: + MOV BX,OFFSET DG:BADRDMES + JMP FATAL ;Couldn't read any FAT, BARF + +RDOK: + POP AX ;Clean up + POP AX + POP AX + POP AX + MOV SI,OFFSET DG:FAT + LODSB ;Check FAT ID byte + CMP AL,0F8H + JAE IDOK + MOV DX,OFFSET DG:BADIDBYT ;FAT ID bad + CALL PROMPTYN ;Ask user + JZ IDOK + JMP ALLDONE ;User said stop +IDOK: + MOV DI,[FATMAP] + MOV CX,[MCLUS] + INC CX + XOR AL,AL + REP STOSB ;Initialize FATMAP to all free + MOV DX,OFFSET DG:DIRBUF ;FOR ALL SEARCHING + MOV AH,SET_DMA + INT 21H + XOR AX,AX + PUSH AX ;I am root + PUSH AX ;Parent is root + CALL DIRPROC + CALL CHKMAP ;Look for badsectors, orphans + CALL CHKCROSS ;Check for second pass + CALL DOCRLF + CALL REPORT + +ALLDONE: + CALL AMDONE + INT 20H ;Fini + + +ASSUME DS:DG + +SUBTTL Check for extents in specified files +PAGE +CHECKFILES: +;Search the directory for the files specified on the command line +;and report the number of fragmented allocation units found in +;each one. + CALL DOCRLF + MOV AH,SET_DMA + MOV DX,[FATMAP] ;Use the first free space available + MOV BP,DX + ADD BP,27 ;cluster in the directory entry + INT 21H + MOV AH,DIR_SEARCH_FIRST ;Look for the first file +FRAGCHK: + MOV DX,FCB + INT 21H + OR AL,AL ;Did we find it? + JNZ MSGCHK ;No -- we're done + XOR AX,AX ;Initialize the fragment counter + MOV SI,[BP] ;Get the first cluster + CALL UNPACK + CMP DI,0FF8H ;End-of-file? + JAE NXTCHK ;Yes -- go report the results + INC SI + CMP SI,DI + JZ EACHCLUS + INC AX +EACHCLUS: + MOV [OLDCLUS],DI ;Save the last cluster found + MOV SI,DI ;Get the next cluster + CALL UNPACK + INC [OLDCLUS] ;Bump the old cluster + CMP DI,[OLDCLUS] ;Are they the same? + JNZ LASTCLUS ;No -- check for end-of-file + JMP SHORT EACHCLUS ;Continue processing +LASTCLUS: + CMP DI,0FF8H ;End-of-file? + JAE NXTCHK ;Yes -- go report the results + INC AX ;No -- found a fragement + JMP SHORT EACHCLUS ;Continue processing + +NXTCHK: + OR AX,AX + JZ GETNXT + MOV [FRAGMENT],2 ;Signal that we output at least one file + PUSH AX ;Save count of fragments + MOV SI,[FATMAP] + INC SI + CALL PRINTTHISEL2 + CALL DOCRLF + MOV DX,OFFSET DG:CONTAINS ;Print message + CALL PRINT + POP SI ;Number of fragments found + INC SI ;Number non-contig blocks + XOR DI,DI + MOV BX,OFFSET DG:EXTENTS + PUSH BP + CALL DISP16BITS + POP BP +GETNXT: + MOV AH,DIR_SEARCH_NEXT ;Look for the next file + JMP FRAGCHK + +MSGCHK: + CMP AH,DIR_SEARCH_FIRST + JNZ FILSPOK + MOV SI,FCB + 1 ;File not found error + CALL PRINTTHISEL2 + CALL DOCRLF + MOV DX,OFFSET DG:OPNERR + CALL PRINT ;Bad file spec + RET +FILSPOK: + CMP BYTE PTR [FRAGMENT],2 + JZ CDONE + MOV DX,OFFSET DG:NOEXTENTS + CALL PRINT +CDONE: + RET + + +FIGREC: +;Convert cluster number in BX to sector # AH of cluster in DX + LDS DI,[THISDPB] +ASSUME DS:NOTHING + MOV CL,[DI.dpb_cluster_shift] + MOV DX,BX + DEC DX + DEC DX + SHL DX,CL + OR DL,AH + ADD DX,[DI.dpb_first_sector] + PUSH CS + POP DS +ASSUME DS:DG + RET + + +SUBTTL PRINTID - Print Volume ID info +PAGE +PRINTID: +ASSUME DS:DG + MOV DX,OFFSET DG:INTERNATVARS + MOV AX,INTERNATIONAL SHL 8 + INT 21H + MOV [DISPFLG],1 ;Don't sub spaces for leading zeros + MOV SI,OFFSET DG:FAT + 8 + MOV DI,OFFSET DG:VNAME + MOV CX,11 + REP MOVSB + MOV DX,OFFSET DG:IDMES1 + CALL PRINT ;Print ID message + ADD SI,13 + LODSW ;Get date + PUSH SI + MOV DX,AX + MOV AX,[INTERNATVARS.Date_tim_format] + OR AX,AX + JZ USPDAT + DEC AX + JZ EUPDAT + CALL P_YR + CALL P_DSEP + CALL P_MON + CALL P_DSEP + MOV CX,1000H ;Do not supress leading zeroes + CALL P_DAY + JMP P_TIME + +USPDAT: + CALL P_MONTH_NAM + MOV CX,1110H ;Supress at most 1 leading 0 + CALL P_DAY + PUSH DX + MOV DL,',' + CALL PRTCHR + MOV DL,' ' + CALL PRTCHR + POP DX +PYA: + CALL P_YR + JMP P_TIME + +EUPDAT: + MOV CX,1110H ;Supress at most 1 leading 0 + CALL P_DAY + PUSH DX + MOV DL,' ' + CALL PRTCHR + POP DX + CALL P_MONTH_NAM + JMP PYA + +P_DSEP: + PUSH DX + MOV DL,[INTERNATVARS.Date_sep] + CALL PRTCHR + POP DX + RET + +P_MONTH_NAM: + MOV AX,DX + PUSH DX + MOV CL,5 + SHR AX,CL + AND AX,0FH ;Month in AX + DEC AX ;Make 0 indexed + MOV CX,AX + SHL AX,1 + ADD AX,CX ;Mult by 3 chars/mo + MOV SI,OFFSET DG:MONTAB + ADD SI,AX + LODSB + MOV DL,AL + CALL PRTCHR + LODSB + MOV DL,AL + CALL PRTCHR + LODSB + MOV DL,AL + CALL PRTCHR + MOV DL,' ' + CALL PRTCHR + POP DX + RET + +P_MON: + MOV SI,DX + PUSH DX + MOV CL,5 + SHR SI,CL + AND SI,0FH ;Month in SI + CALL CONVERT + MOV DL,AL + MOV CX,1000H ;Do not supress leading 0 + CALL OUTBYTE ;Print month + POP DX + RET + +P_DAY: + MOV SI,DX + PUSH DX + PUSH CX + AND SI,01FH ;SI has day + CALL CONVERT + POP CX + MOV DL,AL + CALL OUTBYTE ;Print day + POP DX + RET + +P_YR: + MOV SI,DX + PUSH DX + MOV CL,9 + SHR SI,CL + AND SI,07FH ;SI has raw year + ADD SI,1980 ;Real year + CALL CONVERT + MOV CX,1000H ;Do not supress leading zeros + CALL OUTWORD ;Print year + POP DX + RET + +P_TIME: + MOV DL,' ' + CALL PRTCHR + POP SI + ADD SI,-4 + LODSW ;Get time + MOV DI,AX + MOV SI,DI + MOV CL,11 + SHR SI,CL + AND SI,01FH ;SI has hour + CMP [INTERNATVARS.Time_24],0 + JNZ ISOK2 ;24 hour time? + CMP SI,12 + JB ISOK ;Is AM + MOV [TCHAR],'p' + JZ ISOK ;Is 12-1p + SUB SI,12 ;Is PM +ISOK: + OR SI,SI + JNZ ISOK2 + MOV SI,12 ;0 is 12a +ISOK2: + CALL CONVERT + MOV CX,1110H ;Supress at most 1 leading 0 + MOV DL,AL + CALL OUTBYTE ;Print hour + MOV DL,BYTE PTR [INTERNATVARS.Time_sep] + CALL PRTCHR + MOV SI,DI + MOV CL,5 + SHR SI,CL + AND SI,03FH ;SI has minute + CALL CONVERT + MOV CX,1000H ;Do not supress leading zeroes + MOV DL,AL + CALL OUTBYTE ;Print minute + MOV DL,[TCHAR] + CMP [INTERNATVARS.Time_24],0 + JNZ NOAP ;24 hour time, no a or p + CALL PRTCHR ;Print a or p +NOAP: + MOV DX,OFFSET DG:IDPOST + CALL PRINT + MOV [DISPFLG],0 + RET + +CONVERT: + MOV CX,16 + XOR AX,AX +CNVLOOP: + SHL SI,1 + CALL CONVWRD + CLC + LOOP CNVLOOP + RET + +SUBTTL Misc Routines - Mostly I/O +PAGE +CONVWRD: + ADC AL,AL + DAA + XCHG AL,AH + ADC AL,AL + DAA + XCHG AL,AH +RET1: RET + +UNSCALE: + SHR CX,1 + JC RET1 + SHL SI,1 + RCL DI,1 + JMP SHORT UNSCALE + +DISP16BITS: + MOV BYTE PTR DISPFLG,1 + JMP SHORT DISP32BITS + +DISPCLUS: + MUL [SSIZE] + MOV CL,[CSIZE] + XOR CH,CH + MOV SI,AX + MOV DI,DX + CALL UNSCALE + +DISP32BITS: + PUSH BP + PUSH BX + XOR AX,AX + MOV BX,AX + MOV BP,AX + MOV CX,32 +CONVLP: + SHL SI,1 + RCL DI,1 + XCHG AX,BP + CALL CONVWRD + XCHG AX,BP + XCHG AX,BX + CALL CONVWRD + XCHG AX,BX + ADC AL,0 + LOOP CONVLP + ; Conversion complete + MOV CX,1310H ;Print 3-digit number with 2 leading blanks + CMP BYTE PTR DISPFLG,0 + JNZ FOURDIG + MOV CX,1810H ;Print 8-digit number with 2 leading blanks + XCHG DX,AX + CALL DIGIT + XCHG AX,BX + CALL OUTWORD +FOURDIG: + MOV AX,BP + CALL OUTWORD + MOV BYTE PTR DISPFLG,0 + POP DX + CALL PRINT + POP BP + RET + +OUTWORD: + PUSH AX + MOV DL,AH + CALL OUTBYTE + POP DX +OUTBYTE: + MOV DH,DL + SHR DL,1 + SHR DL,1 + SHR DL,1 + SHR DL,1 + CALL DIGIT + MOV DL,DH +DIGIT: + AND DL,0FH + JZ BLANKZER + MOV CL,0 +BLANKZER: + DEC CH + AND CL,CH + OR DL,30H + SUB DL,CL + CMP BYTE PTR DISPFLG,0 + JZ PRTCHR + CMP DL,30H + JL RET2 +PRTCHR: + MOV AH,STD_CON_OUTPUT + INT 21H +RET2: RET + +PRINTCNT: + LODSB + MOV DL,AL + INT 21H + LOOP PRINTCNT + RET + +EPRINT: + CALL CHECKERR + JNZ RET$1 + JMP SHORT PRINT + +DOCRLF: + MOV DX,OFFSET DG:CRLF +PRINT: + MOV AH,STD_CON_STRING_OUTPUT + INT 21H +RET$1: RET + +DOTCOMBMES: + CMP [NOISY],0 + JZ SUBERRP + PUSH DX + CALL PRINTCURRDIRERR + MOV DX,OFFSET DG:CENTRY + CALL EPRINT + POP DX + CALL EPRINT + CALL DOCRLF + RET + +SUBERRP: + MOV AL,1 + XCHG AL,[ERRSUB] + CMP AL,0 + JNZ RET32 + MOV SI,OFFSET DG:NUL + CALL PRINTCURRDIRERR + MOV DX,OFFSET DG:BADSUBDIR + CALL EPRINT +RET32: RET + + +FCB_TO_ASCZ: ;Convert DS:SI to ASCIIZ ES:DI + MOV CX,8 +MAINNAME: + LODSB + CMP AL,' ' + JZ SKIPSPC + STOSB +SKIPSPC: + LOOP MAINNAME + LODSB + CMP AL,' ' + JZ GOTNAME + MOV AH,AL + MOV AL,'.' + STOSB + XCHG AL,AH + STOSB + MOV CL,2 +EXTNAME: + LODSB + CMP AL,' ' + JZ GOTNAME + STOSB + LOOP EXTNAME + +GOTNAME: + XOR AL,AL + STOSB + RET + +CODE ENDS + END CHKDSK + + +; +; BASIC-DOS Driver/Application Interface Entry Points +; +; @author Jeff Parsons +; @copyright (c) 2020-2021 Jeff Parsons +; @license MIT +; +; This file is part of PCjs, a computer emulation software project at pcjs.org +; + include macros.inc + include 8086.inc + include bios.inc + include dos.inc + include dosapi.inc + +DOS segment word public 'CODE' + + EXTWORD + EXTABS + EXTWORD + EXTBYTE + EXTNEAR + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; dos_dverr (INT 00h) +; +; If a "divide exception" occurs, this default handler reports it and then +; aborts the current program. +; +DEFPROC dos_dverr,DOSFAR + IFDEF MAXDEBUG + DBGBRK + ENDIF + push ax + IFNDEF DEBUG + PRINTF <"Division error",13,10> + ELSE +; +; Print the 32-bit return address on the stack, and since it's already on +; the stack, we don't have to push it, which means PRINTF won't try to pop it +; either. However, since we had to push AX (the only register that PRINTF +; modifies), we must include a special PRINTF formatter (%U) that skips one +; 16-bit value on the stack. +; + PRINTF <"Division error @%U%08lx",13,10> + ENDIF + call msc_sigerr + pop ax + IFDEF DEBUG + iret + ELSE + mov ah,EXTYPE_DVERR + jmp dos_abort + ENDIF +ENDPROC dos_dverr + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; dos_sstep (INT 01h) +; +; If a trace interrupt (or an explicit INT 10h) occurs, and no debugger +; is currently running, we catch it here and ignore it. +; +DEFPROC dos_sstep,DOSFAR + iret +ENDPROC dos_sstep + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; dos_brkpt (INT 03h) +; +; If a breakpoint interrupt occurs, and no debugger is currently running, +; we catch it here and ignore it. +; +DEFPROC dos_brkpt,DOSFAR + iret +ENDPROC dos_brkpt + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; dos_oferr (INT 04h) +; +; If an "overflow exception" occurs, this default handler reports it and +; signals the error. +; +DEFPROC dos_oferr,DOSFAR + IFDEF MAXDEBUG + DBGBRK + ENDIF + push ax + IFNDEF DEBUG + PRINTF <"Overflow error",13,10> + ELSE +; +; Print the 32-bit return address on the stack, and since it's already on +; the stack, we don't have to push it, which means PRINTF won't try to pop it +; either. However, since we had to push AX (the only register that PRINTF +; modifies), we must include a special PRINTF formatter (%U) that skips one +; 16-bit value on the stack. +; + PRINTF <"Overflow error @%U%08lx",13,10> + ENDIF + call msc_sigerr + pop ax + IFDEF DEBUG + iret + ELSE + mov ah,EXTYPE_OVERR + jmp dos_abort + ENDIF +ENDPROC dos_oferr + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; dos_opchk (INT 06h) +; +; This interrupt is used by DEBUG builds to perform "operation checks", +; based on the byte that follows the INT 06h instruction; eg: +; +; CCh: breakpoint +; F9h: assertion failure +; FBh: 32-bit multiply check +; FCh: 32-bit division check +; +; If the 8086 emulation environment isn't set up to intercept INT 06h and +; perform these checks, this handler ensures the checks are harmless. +; +DEFPROC dos_opchk,DOSFAR + IFDEF DEBUG + push bp + mov bp,sp + push ax + push si + push ds + lds si,dword ptr [bp+2] ; DS:SI = CS:IP from stack + cld + lodsb + mov [bp+2],si ; update CS:IP to skip OPCHECK byte + cmp al,OP_ASSERT ; OP_ASSERT? + jnz oc9 ; no + sub si,3 ; display the address of the INT 06h + ; PRINTF <"Assertion failure @%08lx",13,10>,si,ds + DBGBRK +oc9: pop ds + pop si + pop ax + pop bp + ENDIF +; +; Even if you mistakenly run a DEBUG binary on a non-DEBUG system (which +; means all that's here is this IRET), any operation check should still be +; innocuous (but that's neither guaranteed nor recommended). +; + iret +ENDPROC dos_opchk + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; dos_term (INT 20h) +; +; NOTE: In PC DOS, this interrupt, as well as INT 21h AH=00h, apparently +; requires the call to be made from the segment containing the PSP (CS == PSP). +; We do not. Also, the underlying function here (DOS_PSP_TERM) sets a default +; exit code (we use zero), whereas DOS_PSP_RETURN (4Ch) allows any exit code +; to be returned. +; +DEFPROC dos_term,DOSFAR + mov ah,DOS_PSP_TERM + int 21h + iret +ENDPROC dos_term + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; dos_restart +; +; Default CTRLC response handler; if carry is set, call DOS_PSP_RETURN with +; (arbitrary) exit code -1. +; +; Inputs: +; Carry determines whether we exit the process or restart the DOS call +; +; Outputs: +; None +; +DEFPROC dos_restart,DOSFAR + jnc dos_func + mov ah,EXTYPE_CTRLC + DEFLBL dos_abort,near + mov al,0FFh ; AL = exit code + xchg dx,ax ; DL = exit code, DH = exit type + DOSUTIL TERM + ASSERT NEVER ; assert that we never get here +ENDPROC dos_restart + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; dos_func (INT 21h) +; +; Inputs: +; Varies +; +; Outputs: +; Varies +; +DEFPROC dos_func,DOSFAR + cld ; we assume CLD everywhere + sub sp,size WS_TEMP + push ax ; order of pushes must match REG_FRAME + push bx + push cx + push dx + DEFLBL dos_enter,near + push ds + push si + push es + push di + push bp + mov bp,sp + +dc0: IF REG_CHECK ; in DEBUG builds, use CALL to push + call dos_check ; a marker ("dos_check") onto the stack + ENDIF ; which REG_CHECK checks will verify + DEFLBL dos_check,near +; +; While we assign DS and ES to the DOS segment on DOS function entry, we +; do NOT assume they will still be set that way when the FUNCTBL call returns. +; + mov bx,cs + mov ds,bx + ASSUME DS:DOS + mov es,bx + ASSUME ES:DOS + + mov bx,[scb_active] + ASSERT STRUCT,[bx],SCB + inc [bx].SCB_INDOS +; +; Utility functions don't automatically re-enable interrupts, clear carry, +; or check for CTRLC, since some of them are called from interrupt handlers. +; + cmp ah,80h ; utility function? + jb dc1 ; no + sub ah,80h + cmp ah,UTILTBL_SIZE ; utility function within range? + jae dos_leave ; no + mov bl,ah + add bl,FUNCTBL_SIZE ; the utility function table + jmp short dc2 ; follows the DOS function table + +dc1: sti + and [bp].REG_FL,NOT FL_CARRY + cmp ah,FUNCTBL_SIZE + cmc + jb dc3 + + IFDEF MAXDEBUG + push ax + mov al,ah +; +; %P is a special formatter that prints the caller's REG_CS:REG_IP-2 in hex; +; "#010" ensures it's printed with "0x" and 8 digits with leading zeroes. +; + DPRINTF 'd',<"%#010P: DOS function %02bxh\r\n">,ax + pop ax + ENDIF ; MAXDEBUG +; +; If CTRLC checking is enabled for all (non-utility) functions and a CTRLC +; was detected (two conditions that we check with a single compare), signal it. +; + cmp word ptr [bx].SCB_CTRLC_ALL,0101h + je dc4 ; signal CTRLC + mov bl,ah +dc2: mov bh,0 ; BX = function # + add bx,bx ; convert function # to word offset +; +; For convenience, general-purpose registers AX, CX, DX, SI, DI, and SS +; contain their original values. +; + call FUNCTBL[bx] + ASSUME DS:NOTHING, ES:NOTHING +; +; We'd just as soon IRET to the caller (which also restores their D flag), +; so we now update FL_CARRY on the stack (which we already cleared on entry). +; +dc3: adc [bp].REG_FL,0 + + DEFLBL dos_leave,near + IF REG_CHECK ; in DEBUG builds, check the "marker" + pop bx ; that we pushed on entry + ASSERT Z, + ENDIF +; +; Whenever the session's INDOS count returns to zero, check for a pending +; SCSTAT_ABORT; if set AND we're not in the middle of a hardware interrupt, +; clear the ABORT condition and simulate a DOSUTIL TERM session abort. +; + mov bx,[scb_active] + ASSERT STRUCT,cs:[bx],SCB + dec cs:[bx].SCB_INDOS + ASSERT GE + jnz dos_leave2 + test cs:[bx].SCB_STATUS,SCSTAT_ABORT + jz dos_leave2 + cmp word ptr [scb_locked],-1; do NOT abort if session or driver + jne dos_leave2 ; lock levels are >= 0 + and cs:[bx].SCB_STATUS,NOT SCSTAT_ABORT +; +; WARNING: This simulation of DOSUTIL TERM takes a shortcut by not updating +; REG_AH or REG_DX in REG_FRAME, but neither utl_term nor psp_termcode rely +; on REG_FRAME for their inputs, so while this is not completely kosher, we'll +; be fine. The same is true for the termination code in int_leave. +; + mov dx,(EXTYPE_ABORT SHL 8) OR 0FFh + mov ah,DOS_UTL_TERM + 80h + jmp dc0 + +dc4: jmp msc_readctrlc + + DEFLBL dos_leave2,near + pop bp + pop di + pop es + ASSUME ES:NOTHING + pop si + pop ds + ASSUME DS:NOTHING + pop dx + pop cx + pop bx + pop ax + add sp,size WS_TEMP + iret +ENDPROC dos_func + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; dos_exit (INT 22h handler) +; +DEFPROC dos_exit,DOSFAR + ASSERT NEVER ; assert that we never get here + jmp near ptr dos_term +ENDPROC dos_exit + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; dos_ctrlc (INT 23h handler) +; +DEFPROC dos_ctrlc,DOSFAR + push ax + mov ah,DOS_DSK_RESET + int 21h + pop ax + stc ; set carry to indicate termination + ret +ENDPROC dos_ctrlc + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; dos_error (INT 24h handler) +; +; Outputs: +; AL = 0: ignore error +; AL = 1: retry operation +; AL = 2: abort program via INT 23h +; AL = 3: fail system call in progress +; +DEFPROC dos_error,DOSFAR + mov al,CRERR_ABORT ; default to 2 (abort via INT 23h) + iret +ENDPROC dos_error + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; dos_default (currently used for INT 28h and INT 2Ah-2Fh) +; +DEFPROC dos_default,DOSFAR + iret +ENDPROC dos_default + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; disk_read (INT 25h) +; +; TODO +; +DEFPROC disk_read,DOSFAR + iret +ENDPROC disk_read + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; disk_write (INT 26h) +; +; TODO +; +DEFPROC disk_write,DOSFAR + iret +ENDPROC disk_write + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; dos_tsr (INT 27h) +; +; TODO +; +DEFPROC dos_tsr,DOSFAR + iret +ENDPROC dos_tsr + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; dos_call5 (INT 30h) +; +; We typically arrive here via NEAR CALL 0005h to FAR CALL to FAR JMP in +; vector 30h. We should be able to transform that into an INT 21h by "simply" +; moving the NEAR CALL return address into the FAR CALL return address, then +; replacing the NEAR CALL return address with the current flags, and finally +; moving the DOS function # from CL to AH. +; +; Not being familiar with the CALL 0005h interface, whether that's actually +; sufficient remains to be seen. +; +DEFPROC dos_call5,DOSFAR + push bp + mov bp,sp + mov ax,[bp+6] + mov [bp+2],ax + pushf ; since we didn't arrive here via INT, + pop [bp+6] ; these flags should have interrupts on + pop bp + mov ah,cl + jmp near ptr dos_func +ENDPROC dos_call5 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; dos_util (INT 32h) +; +; We could jump straight to dos_func after adjusting the function number, +; but if a breakpoint has been set on dos_func, we'd rather not have dos_util +; calls triggering it as well; hence the redundant CLD and jmp + 1. +; +DEFPROC dos_util,DOSFAR + cld + add ah,80h + jmp near ptr dos_func + 1 ; avoid the same entry point as INT 21h +ENDPROC dos_util + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; int_enter +; +; DDINT_ENTER is "revectored" here by sysinit. +; +; Inputs: +; None +; +; Outputs: +; Carry clear (DOS interrupt processing enabled) +; +DEFPROC int_enter,DOSFAR + inc [int_level] + clc + ret +ENDPROC int_enter + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; int_leave +; +; DDINT_LEAVE is "revectored" here by sysinit. +; +; Inputs: +; Carry set to reschedule, assuming int_level has dropped below zero +; +; Outputs: +; None +; +DEFPROC int_leave,DOSFAR + cli + dec [int_level] + jge ddl9 + jnc ddl9 +; +; Enter DOS to perform a reschedule. +; +; However, we first take a peek at the current SCB's INDOS count and +; ABORT flag; if the count is zero and the flag is set, force termination. +; + cld + sub sp,size WS_TEMP + push ax + push bx + push cx + push dx + mov ah,DOS_UTL_YIELD + 80h + mov bx,cs:[scb_active] + cmp cs:[bx].SCB_INDOS,0 + jne ddl8 + test cs:[bx].SCB_STATUS,SCSTAT_ABORT + jz ddl8 + and cs:[bx].SCB_STATUS,NOT SCSTAT_ABORT + mov dx,(EXTYPE_ABORT SHL 8) OR 0FFh + mov ah,DOS_UTL_TERM + 80h +ddl8: jmp dos_enter +ddl9: iret +ENDPROC int_leave + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; +; func_none (handler for unimplemented DOS functions) +; +; Inputs: +; Varies +; +; Outputs: +; REG_AX = ERR_INVALID, carry set +; +DEFPROC func_none,DOS + IFDEF DEBUG + mov al,ah +; +; %P is a special formatter that prints the caller's REG_CS:REG_IP-2 in hex; +; "#010" ensures it's printed with "0x" and 8 digits with leading zeroes. +; + DPRINTF 'd',<"%#010P: unsupported DOS function %02bxh\r\n">,ax + ENDIF ; DEBUG + + mov [bp].REG_AX,ERR_INVALID + stc + ret +ENDPROC func_none + +DOS ends + + end \ No newline at end of file diff --git a/ref_z80 b/ref_z80 new file mode 100644 index 0000000..7bd5fb8 --- /dev/null +++ b/ref_z80 @@ -0,0 +1,9176 @@ + +.nolist +#include "tse.inc" +.list + +.org saferam1-3 +.db $BB,$6D + ret + +; Task block details +flags_base .equ 89F0h +flags_size .equ 60 +sp_size .equ 2 +task_sp .equ 0 +task_flags .equ task_sp + sp_size +task_end .equ task_flags + flags_size + +; Block size +maxblocksize .equ 100h + +start: + jr startkrnl + jp verinfo ; Library function 0, Get version/functions supported + jp chkprog ; Library function 1, Check program + jp exitshell ; Library function 2, Exit from shell + jp taskswitch ; Library function 3, Switch task + jp starttask ; Library function 4, Start task + jp endtask ; Library function 5, End task + jp forceyield ; Library function 6, Force yield + +verinfo + ld hl, %0000000001111111 +majorver .equ 1 +minorver .equ 6 + ld bc, (majorver*256)+minorver + ret + +startkrnl: + + ; Disable run indicator + bcall(_runIndicOff) + + ; Preserve flags + ld bc, flags_size + ld hl, flags_base + ld de, sysflags + ldir + + ; Set active program + ld hl, shellprog + ld de, cprogram + bcall(_mov9b) + + ; Look up the shell in the symbol table + ld hl, cprogram + rst 20h ;rMOV9TOOP1 + call starttask + cp 0 + ret nz + + ; Check to see if enough free RAM for block copy buffer + call chkEnoughForBuffer + + ; Preserve stack + ld (sysstack), sp + + ; Exec + jp taskenter +exitshell: + + ; Restore stack + ld hl, (sysstack) + ld sp, hl + + ; Copy out of active memory + call cpy_prgm_out + + ; End task + ld hl, shellprog + rst 20h ;rMOV9TOOP1 + call endtask + + ; Clear screen + bcall(_clrScrnFull) + + ; Display exit message + ld hl, 0 + ld (currow), hl + ld hl, msg + bcall(_puts) + + ; Disable ON flag + res onInterrupt, (iy+onFlags) + + ret + +; Force the program to yield +; +; Inputs - +; None +; +; Returns - +; Nothing +; +forceyield: + ld hl, shellprog + rst 20h ;rMOV9TOOP1 + call taskswitch + ret + +; Load a program into active memory and run it +; +; Inputs - +; OP1 should contain a program variable +; +; Returns - +; Nothing +; +taskswitch: + + ; Preserve sp + ld (tempword), sp + ld hl, (pptr_preserve) + push hl + ld de, (tempword) + ld (hl), e + inc hl + ld (hl), d + + ; Preserve var + ld hl, op1 + ld de, tprogram + bcall(_mov9b) + + ; Preserve flags + pop hl + inc hl + inc hl + ex de, hl + ld bc, flags_size + ld hl, flags_base + ldir + + ; Display message + ld hl, 0 + ld (currow), hl + set textInverse, (iy+textflags) + ld hl, plswait + bcall(_puts) + + ; Borrow system stack + ld hl, (sysstack) + ld sp, hl + + ; Check to see if enough RAM for block copy buffer + call chkEnoughForBuffer + + ; Copy current program out of memory + call cpy_prgm_out + + ; Copy program name to cprogram + ld hl, tprogram + ld de, cprogram + bcall(_mov9b) + +taskenter: + + ; Check to see if enough RAM for block copy buffer + call chkEnoughForBuffer + + ; Load new program in + call cpy_prgm_in + + ; Restore flags + ld hl, (pptr_preserve) + inc hl + inc hl + ld bc, flags_size + ld de, flags_base + ldir + + ; Restore sp + ld hl, (pptr_preserve) + bcall(_ldHLind) + ld sp, hl + + ; Exec program + ret + +; Remove a task block from the end of a program variable and update its +; status. +; +; Inputs - +; OP1 should contain a program variable +; +; Returns - +; A will equal 0 on sucess otherwise it's an error code +endtask: + + ; Find the program and set PTR_'s + call chkprog + cp 0 + ret nz + + ; Check to see if task block doesn't exist + ld hl, (fptr_prgtitle) + ld a, (hl) + cp ' ' + ld a, 4 ; Load error code 4 - Task block doesn't exist + ret z + + ; Calculate task block size + ld hl, (fptr_memreq) + bcall(_ldHLind) + ex de, hl + ld hl, task_end + add hl, de + push hl + push hl + + ; Find start of task block + ex de, hl + ld hl, (fptr_end) + or a + sbc hl, de + + ; Delete task block + pop de + bcall(_delmem) + + ; Update variable size field + ld hl, (fptr_varsize) + bcall(_ldHLind) + pop bc + or a + sbc hl, bc + ex de, hl + ld hl, (fptr_varsize) + ld (hl), e + inc hl + ld (hl), d + + ; Modify status byte and return + ld hl, (fptr_prgtitle) + ld a, ' ' + ld (hl), a + + ; Success + xor a + ret + +; Add a task block onto the end of a program variable and update its +; status. +; +; Inputs - +; OP1 should contain a program variable +; +; Returns - +; A will equal 0 on sucess otherwise it's an error code +starttask: + + ; Find the program and set PTR_'s + call chkprog + cp 0 + ret nz + + ; Check to see if task block already exists + ld hl, (fptr_prgtitle) + ld a, (hl) + cp '*' + ld a, 4 ; Load error code 4 - Task block already exists + ret z + + ; Check available memory + ld hl, (fptr_memreq) + bcall(_ldHLind) + ld bc, task_end + add hl, bc + push hl + push hl + bcall(_enoughmem) + ld a, 5 ; Load error code 5 - Insufficient memory + ret c + + ; Insert memory + ld de, (fptr_end) + pop hl + bcall(_insertmem) + + ; Update variable size field + ld hl, (fptr_varsize) + bcall(_ldHLind) + pop bc + add hl, bc + ex de, hl + ld hl, (fptr_varsize) + ld (hl), e + inc hl + ld (hl), d + + ; Get address of taskblock + ld hl, (fptr_memreq) + bcall(_ldHLind) + ld bc, (fptr_end) + add hl, bc + push hl + push hl + push hl + + ; Set start address + ld de, userMem + 4 + ld hl, (fptr_prgtitle) +findcodeaddr: + ld a, (hl) + cp 0 + inc hl + inc de + jr nz, findcodeaddr + inc de + inc de + pop hl + dec hl + ld (hl), d + dec hl + ld (hl), e + + ; Set sp + ld hl, (fptr_varsize) + bcall(_ldHLind) + ld bc, userMem - (3 + task_end + 2) + add hl, bc + ex de, hl + pop hl + ld (hl), e + inc hl + ld (hl), d + + ; Save flags into taskblock + pop hl + inc hl + inc hl + ex de, hl + ld bc, flags_size + ld hl, sysflags + ldir + + ; Update status bytes + ld hl, (fptr_prgtitle) + ld a, '*' + ld (hl), a + + ; Success + xor a + ret + +; Locate a TSE program in memory and set pointers. +; +; Inputs - +; OP1 should contain a program variable +; +; Returns - +; A will equal 0 on sucess otherwise it's an error code +; HL points to prgtitle +; (fptr_varsize), (fptr_code) and (fptr_end) set point to program +; +chkprog: + + ; Check to see if variable exists + bcall(_ChkFindSym) + ex de, hl + ld a, 1 ; Load error code 1 - Variable does not exist + ret c ; Return if error + + ; Check to see if variable is in RAM + ld a, b + cp 0 + ;OLD: ld a, 2 ; Load error code 2 - Variable is stored in FLASH-ROM + ;OLD: ret nz ; Return if error + jr z, notFlash + ld de, saferam5 + ld bc, 256 + bcall(_FlashToRam) + ld hl, saferam5 + 9 + ld c, (hl) + ld b, 0 + inc bc + add hl, bc + call notFlash + cp 0 + ret nz + ld a, 2 ; Load error code 2 - Variable is stored in FLASH-ROM + ret +notFlash: + + ; Get pointer to "variable size field" + ld (fptr_varsize), hl + + ; Get pointer to "end of variable" + push hl + bcall(_ldHLind) + ld de, (fptr_varsize) + inc de + inc de + add hl, de + ld (fptr_end), hl + pop hl + + ; Check that the TSE header is valid + ld b, 4 + ld de, 5 + add hl, de + ld de, tsevalid +cmpStr: + ld a, (de) + ld c, (hl) + cp c ; Compare strings + ld a, 3 ; Load error code 3 - Not a valid TSE program + ret nz ; Return if error + inc hl ; Next byte of header + inc de ; Next byte of tsevalid + djnz cmpStr + + ; Get pointer to "program title" + ld (fptr_prgtitle), hl + + ; Get pointer to "mem required" + call skipstr + ld (fptr_memreq), hl + + ; Load HL with program title + ld hl, (fptr_prgtitle) + + ; All done + xor a ; Load error code 0 - Sucess + ret + +; Moves the active program into a variable +; +; Inputs - +; (cprogram) should contain the name of the program to load in +; +; Returns - +; Program is moved into variable +; +cpy_prgm_out: + + ; Load prgm name into OP1 + ld hl, cprogram + rst 20h ;rMOV9TOOP1 + + ; Get src + ld hl, userMem + ld (cpysrc), hl + + ; Clear tempword + ld hl, 5 + ld (tempword), hl + + ; Get program size + ld hl, (progsize) + ld (bytes2go), hl + + ; Update size field of var + push hl + bcall(_chkfindsym) + ex de, hl + pop de + inc de + inc de + inc de + ld (hl), e + inc hl + ld (hl), d + +cpyout_loop: + + ; Get copy block size + call calcblock + + ; Calculate cpydest + bcall(_chkfindsym) + ex de, hl + ld bc, (tempword) + add hl, bc + ex de, hl + + ; Increase size of dest + ld hl, (blocksize) + bcall(_insertmem) + + ; Copy data + ld hl, (cpysrc) + ld bc, (blocksize) + ldir + + ; Increase tempword + ld hl, (tempword) + ld bc, (blocksize) + add hl, bc + ld (tempword), hl + + ; Decrease size of src + ld hl, (cpysrc) + ld de, (blocksize) + bcall(_delmem) + + ; Loop if not finished + ld hl, (bytes2go) + xor a + cp h + jr nz, cpyout_loop + cp l + jr nz, cpyout_loop + + ret + +; Moves the selected variable into the active program +; +; Inputs - +; (cprogram) should contain the name of the program to load in +; +; Returns - +; Program is moved into variable +; +cpy_prgm_in: + + ; Load prgm name into OP1 + ld hl, cprogram + rst 20h ;rMOV9TOOP1 + + ; Get program size + bcall(_chkfindsym) + ex de, hl + push hl + bcall(_ldhlind) + dec hl + dec hl + dec hl + ld (bytes2go), hl + ld (progsize), hl + + ; Update size field of var + pop hl + ld de, 3 + ld (hl), e + inc hl + ld (hl), d + + ; Get dest + ld hl, userMem + ld (cpydest), hl + +cpyin_loop: + + ; Get copy block size + call calcblock + + ; Increase size of dest + ld hl, (blocksize) + ld de, (cpydest) + bcall(_insertmem) + + ; Calculate cpysrc + bcall(_chkfindsym) + ex de, hl + ld bc, 5 + add hl, bc + push hl + + ; Copy data + ld bc, (blocksize) + ld de, (cpydest) + ldir + ld (cpydest), de + + ; Decrease size of src + pop hl + ld de, (blocksize) + bcall(_delmem) + + ; Loop if not finished + ld hl, (bytes2go) + xor a + cp h + jr nz, cpyin_loop + cp l + jr nz, cpyin_loop + + ; Set pptr_code + ld hl, userMem + 4 + call skipstr + inc hl + inc hl + ld (pptr_code), hl + + ; Set pptr_preserve + ld hl, (progsize) + ld bc, userMem - task_end + add hl, bc + ld (pptr_preserve), hl + + ret + +; Copy block size calulating routine +; +calcblock: + ld bc, maxblocksize +smaller: + dec bc + ld hl, (bytes2go) + or a + sbc hl, bc + jr c, smaller + ld (bytes2go), hl + ld (blocksize), bc + ret + +; Skip string routine +; +skipstr: + ld a, (hl) + cp 0 + inc hl + jr nz, skipstr + ret + +; Check to see if enough free RAM for block copy buffer +chkEnoughForBuffer: + ld hl, maxblocksize + bcall(_EnoughMem) + ret nc + pop hl ; Get return address off stack + ret + +; Please Wait +; +plswait: +.db "",0 + +; Exit Message +; +msg: +.db "TSE v1.6",0 + +; The 4 byte TSE header for use by the validation code +; +tsevalid: +.db "TSE", 1 + +; Name of program to load as shell +; +shellprog: +.db ProtProgObj, "UTOPIA",0,0 + +; Memory equates +; +data: + +; Memory equates (Temporary) +bytes2go .equ saferam2 + 400 +blocksize .equ bytes2go + 2 +cpysrc .equ blocksize + 2 +cpydest .equ cpysrc + 2 +tempword .equ cpydest + 2 +tprogram .equ tempword + 2 + +.end + + +.nolist +#ifdef VTI +.echo "\nAssembling VTI version\n\n" +#else +.echo "\nAssembling Real version\n\n" +#endif +#include "csx.inc" +.list + +#ifdef VTI +#define VERSION "CSX 0.27 beta - VTI" +#define MODEL "TI-83 Plus" +#define DATE "08-27-2004" +#define AUTHOR "Sean McLaughlin" +#define ORG "United-TI" +#define CONT1 "sigma_zk@yahoo.com" +#define CONT2 "www.unitedti.org" +#else +#define VERSION "CSX 0.27 beta" +#define MODEL "TI-83 Plus" +#define DATE "08-27-2004" +#define AUTHOR "Sean McLaughlin" +#define ORG "United-TI" +#define CONT1 "sigma_zk@yahoo.com" +#define CONT2 "www.unitedti.org" +#endif + +LIBS = 54 + +.module _page00 +; RST 00 - Reset +.org $0000 + jp init + +zp.deref_hl_alt: .export zp.deref_hl_alt + ld e, (hl) + inc hl + ld d, (hl) + inc hl + ret + +; RST 08 - Load HL indirect +#if $ > $0008 +!!! +.echo "OH NOES!!!111\n\n" +#endif +.org $0008 + push af + ld a, (hl) + inc hl + ld h, (hl) + ld l, a + pop af + ret + +; RST 10 - Compare HL and DE (destroys A) +#if $ > $0010 +!!! +.echo "OH NOES!!!111\n\n" +#endif +.org $0010 + ld a, h + cp d + ret nz + ld a, l + cp e + ret + +zp.call_ix: .export zp.call_ix + jp (ix) + +; RST 18 - Read keypad port +#if $ > $0018 +!!! +.echo "OH NOES!!!111\n\n" +#endif +.org $0018 + out (key_port), a + inc hl + dec hl + in a, (key_port) + ret + +zp.call_hl .export zp.call_hl + jp (hl) + +; RST 20 - Add accumulator to HL +#if $ > $0020 +!!! +.echo "OH NOES!!!111\n\n" +#endif +.org $0020 + add a, l + ld l, a + adc a, h + sub l + ld h, a + ret + +zp.call_iy: .export zp.call_iy + jp (iy) + +; RST 28 - Invoke user-defined SWI +#if $ > $0028 +!!! +.echo "OH NOES!!!111\n\n" +#endif +.org $0028 + push hl + ld hl, (psp.restart) + ex (sp), hl + ret + +; RST 30 - Off-page call (dead weight) +#if $ > $0030 +!!! +.echo "OH NOES!!!111\n\n" +#endif +.org $0030 + push hl ; for ROM page + push hl ; for return + push hl + push de + push af + jp os_func_cont + +; RST 38 - ISR +#if $ > $0038 +!!! +.echo "OH NOES!!!111\n\n" +#endif +.org $0038 + ex af, af' + exx + + ld hl, os.status_flags + + in a, (irq_stat_port) + and $08 + jp nz, isr.on_raised + + jp isr.continue + +; Boot code comes here +#if $ > $0053 +!!! +.echo "OH NOES!!!111\n\n" +#endif +.org $0053 + jp init + +; Validated OS +.dw $A55A + +; Non-destructive compare of HL and DE +zp.cmp_hlde_nd: .export zp.cmp_hlde_nd + push hl + or a + sbc hl, de + pop hl + ret + +; Non-destructive compare of HL and BC +zp.cmp_hlbc_nd: .export zp.cmp_hlbc_nd + push hl + or a + sbc hl, bc + pop hl + ret + +; Version number string +#if $ > $0064 +!!! +.echo "OH NOES!!!111\n\n" +#endif + +os.version: +.db VERSION, 0 +.db MODEL, 0 + +; Compare HL and BC (destroys A) +zp.cmp_hlbc: .export zp.cmp_hlbc + ld a, h + cp b + ret nz + ld a, l + cp c + ret + +; Continuation of RST 30 +os_func_cont: + ld hl, 11 ; get return address + add hl, sp + ld e, (hl) + dec hl + ld d, (hl) + + in a, (flash_pageA_port) ; save the ROM page + dec hl + dec hl + ld (hl), a + + dec hl ; set return address + ld (hl), os_func_exit & $FF + dec hl + ld (hl), os_func_exit >> 8 + + ex de, hl ; get vector address + ld a, (hl) + inc hl + ld h, (hl) + ld l, a + + ld a, 1 + out (flash_pageA_port), a + ld a, (hl) ; get routine page + inc hl + ld d, (hl) ; get routine address + inc hl + ld h, (hl) + ld l, d + + out (flash_pageA_port), a ; restore inputs and transfer + pop af + pop de + ex (sp), hl + ret + +os_func_exit: + ex (sp), hl ; restore ROM page + push af + ld a, h + out (flash_pageA_port), a + pop af + pop hl + ex (sp), hl ; advance return by 2 + inc hl + inc hl + ex (sp), hl + ret + +; Continuation of RST 38 +isr.continue: +; ON key has been pressed + bit calc_on, (hl) + jr nz, isr.power_on + + set calc_on, (hl) + pop af + + ld hl, APS_TIME + ld (os.aps_timer), hl + + lcd_busy + ld a, $03 + out (lcd_cmd_port), a + lcd_busy + ld a, $18 + out (lcd_cmd_port), a + +isr.wait_on: + in a, (irq_stat_port) + and $08 + jr z, isr.wait_on + +isr.on_raised: +; Look for timer interrupt + in a, (irq_stat_port) + and $02 + jr z, isr.timer_end + + bit calc_on, (hl) + jr z, isr.timer_end + + bit aps_on, (hl) + jr z, isr.timer_end + + ld hl, (os.aps_timer) + dec hl + ld a, h + or l + ld (os.aps_timer), hl + + call z, isr.force_off + +isr.timer_end: + ld a, (os.status_flags) + and bitmask(prgm_running) + jr z, isr.end + + ld hl, (psp.interrupt) + ld a, (hl) + cp $C7 + inc hl + call z, zp.call_hl + +isr.end: + xor a + out (irq_mask_port), a + ld a, $0B + out (irq_mask_port), a + exx + ex af, af' + ei + ret + +isr.power_on: + ld a, kg_func + rowread + cp invmask(kb_2nd) + jr nz, isr.2nd_raised + + res calc_on, (hl) + ld hl, isr.shut_down + push hl + jr isr.wait_on + +isr.force_off: + ld hl, os.status_flags + res calc_on, (hl) +isr.shut_down: + di + push af + lcd_busy + ld a, $02 + out (lcd_cmd_port), a + pop af + +isr.off_loop: + push af + ld a, $36 + out (irq_stat_port), a + ld a, $01 + out (irq_mask_port), a + pop af + ei + halt + jr isr.off_loop + +isr.2nd_raised: + ld hl, os.contrast + ld a, kg_arrows + rowread + bit kb_up, a + jr nz, isr.up_raised + +isr.wait_up: + ld a, kg_arrows + rowread + bit kb_up, a + jr z, isr.wait_up + + ld a, (hl) + inc a + jr z, isr.contrast_max + out (lcd_cmd_port), a + ld (hl), a +isr.contrast_max: + ld a, sk_up + ld (os.prev_key), a + jr isr.end + +isr.up_raised: + bit kb_down, a + jr nz, isr.end + +isr.wait_down: + ld a, kg_arrows + rowread + bit kb_down, a + jr z, isr.wait_down + + ld a, (hl) + cp $C0 + jr z, isr.contrast_min + dec a + out (lcd_cmd_port), a + ld (hl), a +isr.contrast_min: + ld a, sk_down + ld (os.prev_key), a + jr isr.end + + +init: + di + im 1 + in a, (hw_port) + or a + and $80 + jp nz, init.basic + +; Silver edition + ld a, $81 + out ($07), a + dec a + out ($08), a + xor a + out ($08), a + out ($05), a + out ($20), a + out ($30), a + out ($33), a + out ($36), a + ld a, $14 + out ($29), a + +init.basic: +; BE and SE + xor a + out (link_port), a + ld a, $01 + out (irq_mask_port), a + ld a, $76 + out (irq_stat_port), a + ld a, $41 + out ($07), a + ld sp, $0000 + + ld hl, init.lcd_out + ld b, 5 +init.lcd_loop: + lcd_busy + ld a, (hl) + inc hl + out (lcd_cmd_port), a + djnz init.lcd_loop + + ld hl, $8000 + ld de, $8001 + ld bc, $8000 + ld (hl), 0 + ldir + + ld a, $F0 + ld (os.contrast), a + + ld a, bitmask(calc_on) | bitmask(aps_on) + ld (os.status_flags), a + + ld hl, APS_TIME + ld (os.aps_timer), hl + + ld hl, user_ram + ld (os.free_mem_ptr), hl + + ld hl, $0000 + ld (os.temp_num), hl + + in a, (irq_stat_port) + and $08 + jr nz, $-4 + + call isr.force_off + + call zp.cclear + call zp.credraw + call zp.vbufdisp + ld hl, init.str_clear + call zp.cputs + call zp.cdisp + jp init.all_good + +init.lcd_out .db $18, $01, $F0, $40, $05 +init.str_clear .db "Mem Cleared", 0 + +#include "libsrc\\string\\tokenize.z80" +.echo "\n" + +init.all_good: + ei +zp.CONSOLE: .export zp.CONSOLE + ld hl, zp.CONSOLE + push hl + + ld hl, os.arg_ptrs + ld de, os.arg_ptrs + 1 + ld (hl), 0 + ld bc, 127 + ldir + + call zp.cgets + + ld hl, os.buffer + ld d, h + ld e, l + ld ix, os.arg_ptrs + call zp.tokenize + ld a, c + ld (os.arg_count), a + + ld hl, (arg(0)) + call zp.strupr + call zp.strlen + ld b, c + xor a + + +Hash: + add a, (hl) + inc hl + djnz Hash + and 15 ; A = HASH(x) + + ld hl, hash_table + add a, a + offset_hl + deref_hl + + ld a, (hl) ; # of strings + or a + jp z, err_syntax + ld b, a + inc hl + ld de, (arg(0)) + +Find_String: + call zp.strcmp + jr z, _Found_String + call zp.strnext + inc hl + inc hl + djnz Find_String + jp err_syntax +_Found_String: + call zp.strnext + deref_hl ; HL = address + ld (os.estack), sp + jp (hl) + + +err_syntax: + ld hl, str_esyntax1 + ld de, os.scrap + call zp.strcpy + + ld hl, os.scrap + ld de, (arg(0)) + call zp.strcat + + ld hl, os.scrap + ld de, str_esyntax2 + call zp.strcat + + ld hl, os.scrap + call zp.cputs + jp zp.cdisp + +str_esyntax1 .text "Unknown: \"\000" +str_esyntax2 .text "\"\000" + +hash_table: + .dw hash0, hash1, hash2, hash3, hash4, hash5, hash6, hash7 + .dw hash8, hash9, hashA, hashB, hashC, hashD, hashE, hashF + +; (byte) Commands hashing to this value +; (zstr) String1 +; (word) Vector1 +; (zstr) String2 +; (word) Vector2 +; etc. + +hash0 +hash1 +hash3 +hash4 +hash6 +hash7 +hash8 +hash9 +hashA +hashB +hashD .db 0 + +hash2 .db 1 + .db "CLS", 0 + .dw cmd_CLS + +hash5 .db 2 + .db "HEX", 0 + .dw cmd_HEX + .db "RUN", 0 + .dw cmd_RUN + +hashC .db 3 + .db "LIST", 0 + .dw cmd_LIST + .db "INFO", 0 + .dw cmd_INFO + .db "KILL", 0 + .dw cmd_KILL + +hashE .db 1 + .db "LINK", 0 + .dw cmd_LINK + +hashF .db 1 + .db "MEM", 0 + .dw cmd_MEM + +#include "commands\\cls.z80" +#include "commands\\hex.z80" +#include "commands\\info.z80" +#include "commands\\kill.z80" +#include "commands\\link.z80" +#include "commands\\list.z80" +#include "commands\\mem.z80" +#include "commands\\run.z80" + +gen_error: + ld hl, os.status_flags + bit prgm_running, (hl) + jr z, _system_error + ld hl, (psp.ehandler) + ld a, (hl) + cp $C7 + jr nz, _bad_handler + + inc hl + ld sp, (psp.stack) + ld a, (os.exception_num) + jp (hl) + +_bad_handler: + ld hl, str_eunhandled + call zp.cputs + +_system_error: + ld hl, str_exception + call zp.cputs + + ld de, os.console + (1*24) + 2 + ld a, (os.exception_num) + ld b, a + call zp.htoa_b + ld a, b + add a, a + ld hl, err_strings + offset_hl + deref_hl + inc de + call zp.strcpy + dec de + ld a, ' ' + ld (de), a + +; Dump values of AF - IY + ld de, os.console + (2*24) + 3 + ld b, 6 +_Dump_Regs: + pop hl + call zp.htoa_w + inc de + inc de + inc de + inc de + djnz _Dump_Regs + +; Dump PC - 3 + pop hl + dec hl + dec hl + dec hl + call zp.htoa_w + inc de + inc de + inc de + inc de + +; Dump SP + ld hl, 0 + add hl, sp + push hl + call zp.htoa_w + inc de + inc de + inc de + inc de + +; Dump I + ld a, i + call zp.htoa_b + +; Dump stack + pop hl + ld a, h + or l + jr z, _No_Stack_Dump + + ld de, 30 + add hl, de + ld b, 15 + jr nc, _XXX + + ld a, $1C + sub l + srl a + inc a + ld b, a + +_XXX: + ld c, 5 + ld de, os.console + (6*24) + ld hl, 0 + add hl, sp +_Dump_Stack: + push hl + deref_hl + push bc + call zp.htoa_w + pop bc + + dec c + jr z, _Dump_Stack1 + inc de +_Dump_Stack2: + pop hl + inc hl + inc hl + djnz _Dump_Stack + + +_No_Stack_Dump: + call zp.cdisp + ld sp, (os.estack) + ret + +_Dump_Stack1: + ld c, 5 + jr _Dump_Stack2 + +.module HEX_EDITOR +_address = os.scrap +_cur_y = os.scrap + 2 +_cur_x = os.scrap + 3 +_mode = os.scrap + 4 ; 0 = hex !0 = ascii +_string = os.scrap + 5 + +app.hexedit: .export app.hexedit + call zp.credraw + + ld hl, 0 + ld de, (arg(1)) + ld a, d + or e + jr z, _NoArg + ld b, 4 + call zp.atoh +_NoArg: + ld (_address), hl + ld hl, 0 + ld (_cur_y), hl + ld (_mode), hl + jp _Refresh + +_loop: + ld hl, _Refresh + push hl +_inkey: + call zp.getch + + cp k_enter + jp z, _Set + cp k_down + jr z, _CurDown + cp k_up + jr z, _CurUp + cp k_left + jr z, _CurLeft + cp k_right + jr z, _CurRight + + range_test('C','S',_inkey,_lc) + or $20 + +_lc: + cp 'c' + jp z, _Compare + cp 'e' + jp z, _Enter + cp 'f' + jp z, _Fill + cp 'g' + jp z, _Goto + cp 'k' + jp z, _PageBack + cp 'l' + jp z, _PageAhead + cp 'm' + jp z, _Mode + cp 'p' + jp z, _Page + cp 's' + jp z, _Search + cp 'q' + jr nz, _inkey + +; Exit +_Quit: + pop af + ret + +; Move the cursor down one row +_CurDown: + call _Func_PrepCursor +_CurDown_2: + ld a, l + cp 7 + jr z, _ScrollDown + inc l +_CurExit: + ld (_cur_y), hl + call _XORCursor + call zp.vbufdisp + jr _inkey +_ScrollDown: + ld de, 8 +_CurMod: + ld hl, (_address) + add hl, de + ld (_address), hl + ret + +; Move the cursor up one row +_CurUp: + call _Func_PrepCursor +_CurUp_2: + ld a, l + or a + jr z, _ScrollUp + dec l + jr _CurExit +_ScrollUp: + ld de, -8 + jr _CurMod + ret + +; Move the cursor left one byte +_CurLeft: + call _Func_PrepCursor + ld a, h + or a + jr z, _WrapLeft + dec h + jr _CurExit +_WrapLeft: + ld h, 7 + ld (_cur_y), hl + jr _CurUp_2 + +_CurRight: + call _Func_PrepCursor + ld a, h + sub 7 + jr z, _WrapRight + inc h + jr _CurExit +_WrapRight: + ld h, a + ld (_cur_y), hl + jr _CurDown_2 + + +; Set a byte +_Set: + call _Func_AddressTest + + ld hl, _str_set + call _DispCommand + + ld a, (_mode) + or a + jr z, _SHex + ld a, (os.buffer) + ld d, a + jr _SWrite + +_SHex: + ld de, os.buffer + ld b, 2 + call zp.atoh + jp c, _HexError + ld d, l + +_SWrite: + call _Get_Cur_Address + ld (hl), d + ret + + +_Mode: + ld a, (_mode) + cpl + ld (_mode), a + ret + + +_PageAhead: + ld hl, (_address) + ld de, 64 +_PageAhead_2: + add hl, de + ld (_address), hl + ret + +_PageBack: + ld hl, (_address) + ld de, -64 + jr _PageAhead_2 + + +; Warp to a specified address +_Goto: + ld hl, _str_goto + call _DispCommand + + ld de, os.buffer + ld b, 4 + call zp.atoh + jp c, _HexError + ld (_address), hl + ret + +_Page: + ld hl, _str_page + call _DispCommand + + ld de, os.buffer + ld b, 2 + call zp.atoh + jp c, _HexError + ld a, l + out (flash_pageA_port), a + ret + +_Compare: + ld hl, _str_comp + call _DispCommand + call _ParseC + call _Get_Cur_Address + ld bc, (arg(0)) + ld de, (arg(1)) + + ld a, b + or c + jp z, _inkey + +_CLoop: + ld a, (de) + inc de + cpi + jr nz, _CMismatch + jp pe, _CLoop + ld hl, _str_match + jp _HE2 +_CMismatch: + dec hl + ld (_address), hl + push de + ld hl, _str_mismatch +_C2: + ld de, _string + call zp.strcpy + dec de + pop hl + dec hl + call zp.htoa_w + xor a + ld (de), a + sbc hl, hl + ld (_cur_y), hl + ld hl, _string + jp _HE2 + + +_Enter: + call _Func_AddressTest + push hl + ld hl, _str_enter + call _DispCommand + ld de, os.buffer + ld a, (_mode) + or a + jr z, _EWriteH + + ex de, hl + pop de +_EWriteA: + ld a, (hl) + or a + ret z + ldi + jr _EWriteA + + +_EWriteH: + pop bc + push bc + ld b, 2 + call zp.atoh + pop bc + jp c, _HexError + ld a, l + ld (bc), a + inc bc + ld a, (de) + or a + jr nz, _EWriteH+1 + ret + +_Search: + ld hl, _str_search + call _DispCommand + + ld de, os.buffer + ld a, (_mode) + or a + jr nz, _SAsc + ld ix, os.buffer + ld c, 0 +_SConvHex: + ld b, 2 + call zp.atoh + jp c, _HexError + ld (ix), l + inc ix + inc c + ld a, (de) + or a + jr nz, _SConvHex + jr _SDoSearch + +_SAsc: + ld hl, os.buffer + call zp.strlen +_SDoSearch: + call _Get_Cur_Address + ld ixh, c + ld de, os.buffer + ld bc, 65535 +_SBegin: + ld a, (de) + cpir + push de + push ix +_SStep: + inc de + dec ixh + jr z, _SFound + ld a, (de) + cpi + jr z, _SStep +_SFound: + pop ix + pop de + jr nz, _SBegin + ld d, 0 + ld e, ixh + sbc hl, de + ld (_address), hl + inc hl + push hl + ld hl, _str_found + jp _C2 + +_Fill: + call _Func_AddressTest + + ld hl, _str_fill + call _DispCommand + call _ParseFS + call _Get_Cur_Address + ld bc, (arg(0)) + ld a, b + or c + jp z, _inkey + ld de, (arg(1)) +_Fill_Loop: + ld (hl), e + inc hl + dec bc + ld a, b + or c + jr nz, _Fill_Loop + ret + +_Get_Cur_Address: + ld hl, (_address) + ld a, (_cur_y) + add a, a + add a, a + add a, a + ld e, a + ld a, (_cur_x) + add a, e + offset_hl + ret + + pop af +_HexError: + ld a, CMD_PANE + call zp.clrline + ld hl, _str_hex +_HE2: + ld bc, CMD_PANE*256+0 + call zp.puts_con + ld a, CMD_PANE + call zp.displine + call zp.getch + ret + +; Parse for FIND and SEARCH: +; +_ParseFS: + call _Func_UnivParse + jr c, _HexError-1 + ld a, (_mode) + or a + ld a, (de) + ld l, a + ld b, 2 + call z, zp.atoh + jr c, _HexError-1 + ld (arg(1)), hl + ret + +; Parse for COMPARE: +; +_ParseC: + call _Func_UnivParse + jr c, _HexError-1 + ld b, 4 + call zp.atoh + jr c, _HexError-1 + ld ((arg(1)), hl + ret + + +; Rebuild the display from nothing +_Refresh: + ld a, MSG_PANE + call zp.clrline + ld a, CMD_PANE + call zp.clrline + + ld hl, (_address) + ld bc, 0 + +_Refresh_Line: + push bc + + ld de, _string + call zp.htoa_w + ld a, ':' + ld (de), a + inc de + ld a, ' ' + ld (de), a + inc de + + call _Func_Conv + call _Func_Conv + call _Func_Conv + call _Func_Conv + + pop bc + push bc + + ld de, _string +_Refresh_Write: + ld a, (de) + inc de + call zp.putch_con + inc c + ld a, c + cp 22 + jp nz, _Refresh_Write + + pop bc + ld a, b + add a, 6 + ld b, a + cp 8*6 + jp nz, _Refresh_Line + + call _XORCursor + call zp.vbufdisp + jp _loop + +_XORCursor: + ld hl, (_cur_y) + ld a, h + ld h, 0 + add hl, hl + add hl, hl + add hl, hl + ld d, h + ld e, l + add hl, hl + add hl, hl + add hl, hl + add hl, de + add a, 3 + offset_hl + ld de, os.lcd_mem + add hl, de + ld b, 6 + ld de, 12 +_InvLoop: + ld a, (hl) + cpl + ld (hl), a + add hl, de + djnz _InvLoop + ret + +_DispCommand: + ld a, MSG_PANE ; Display function name + call zp.clrline + ld bc, MSG_PANE*256 + call zp.puts_con + ld a, MSG_PANE + call zp.invline + ld a, MSG_PANE + call zp.displine + jp zp.cgets + + +; Bunches of silly functions to shorten code size + +; Code used by both parsing functions +_Func_UnivParse: + ld b, 2 + + ld hl, os.buffer + ld d, h + ld e, l + ld ix, os.arg_ptrs + call zp.tokenize + + ld de, (arg(0)) + ld b, 4 + call zp.atoh + ret c + ld (arg(0)), hl + ld de, (arg(1)) + ret + +_Func_PrepCursor: + call _XORCursor ; Erase cursor highlight + ld hl, (_cur_y) + ret + +; Convert bytes to ASCII or Hex +_Func_Conv: + ld a, (_mode) + or a + jr z, _Func_Conv_Hex + + ld a, ' ' + ldi + ld (de), a + inc de + ldi + ld (de), a + inc de + ret + +_Func_Conv_Hex: + ld b, (hl) + inc hl + ld c, (hl) + inc hl + push hl + ld h, b + ld l, c + call zp.htoa_w + pop hl + ret + +; Test if in Flash +_Func_AddressTest: + call _Get_Cur_Address + pop de + bit 7, h + jp z, _inkey + push de + ret +; "0 1 2 " +; "012345678901234567890123" +_str_comp .db "COMPARE MEMORY", 0 +_str_enter .db "ENTER STRING", 0 +_str_fill .db "FILL MEMORY", 0 +_str_goto .db "GO TO ADDRESS", 0 +_str_page .db "SET FLASH PAGE", 0 +_str_set .db "SET BYTE", 0 +_str_hex .db "Invalid Hex Value", 0 +_str_match .db "Memory Matches", 0 +_str_mismatch .db "Mismatch at ", 0 +_str_search .db "SEARCH", 0 +_str_found .db "String Found at ", 0 + +.echo "Hex Editor\t" +.echo $-app.hexedit +.echo "\n" + +vect_putch_con: + push hl + push de + push bc + + ld h, 0 + ld l, b + ld d, h + ld e, l + add hl, hl + add hl, de + add hl, hl + add hl, hl + + ld e, c + ld c, a ; C.0 font lsb + srl e + rl c ; C.1 font lsb C.0 = column lsb + add hl, de + + ld de, os.lcd_mem + add hl, de ; HL = address into lcd_mem + + push hl + rra + ld h, 0 + ld l, a + ld d, h + ld e, l + add hl, hl + add hl, de + add hl, hl + ld de, os.font_con + add hl, de ; HL = address of bitmap + ex (sp), hl + pop ix ; HL -> lcd_mem IX -> bitmap + + ld b, 6 + +_loop: + ld a, (ix) + bit 1, c + jr nz, _oddfont + rlca + rlca + rlca + rlca +_oddfont: + and $0F + ld e, $F0 + bit 0, c + jr nz, _oddcolumn + ld e, $0F + rlca + rlca + rlca + rlca +_oddcolumn: + push af + ld a, (hl) + and e + ld d, a + pop af + or d + ld (hl), a + inc ix + ld de, 12 + add hl, de + djnz _loop + pop bc + pop de + pop hl + ret + +.echo "putch_con\t" +.echo $-vect_putch_con +.echo "\n" + + +vect_f_load: + +_reloc_size = os.scrap +_load_size = os.scrap + 2 +_reloc_ptr = os.scrap + 4 +_load_ptr = os.scrap + 6 +_reloc_ptr2 = os.scrap + 8 +_reloc_endptr = os.scrap + 10 + + ld hl, user_ram + cmp_hlde + ret z + +; Get sizes of both files + call zp.f_size + ld (_reloc_size), bc + ex de, hl ; HL = & load file, DE = & reloc file + call zp.f_size + ld (_load_size), bc + ld (_load_ptr), hl + ld (_reloc_ptr), de + +; Compare sizes + ld d, b + ld e, c + ld hl, (_reloc_size) ; HL = size reloc, DE = size load +_CMP: + cmp_hlde + jr z, _Sizes_Are_Equal + jr c, _LoadFile_Is_Larger + +; reloc > load +; Do exchange of DE bytes + ld b, d + ld c, e + ld hl, (_load_ptr) + ld de, (_reloc_ptr) + call zp.memswap + add hl, bc + ex de, hl + add hl, bc + ex de, hl +; ----------------------------------------------------- +; | LOAD_FILE | RELOC_PT_2 | STUFF | RELOC_PT_1 | STUFF | +; ----------------------------------------------------- +; ^_reloc_ptr ^DE ^_load_ptr ^HL + +; Get size difference + ld (_reloc_ptr2), de + ld (_reloc_endptr), hl + ld hl, (_reloc_size) + ld de, (_load_size) + or a + sbc hl, de + +; Find HL / 1024 and HL % 1024 + ld a, h + and $03 + ld b, a ; BC = HL % 1024 == HL & 0x03FF + ld c, l + or c + jr z, _No_Remainder + +; Move HL % 1024 bytes: +; ------------------------------------------------------- +; | LOAD_FILE |***RELOC_PT_2 | STUFF | RELOC_PT_1 | STUFF | +; ------------------------------------------------------- +; | LOAD_FILE |<--RELOC_PT_2 | STUFF | RELOC_PT_1 | STUFF | +; ------------------------------------------------------- +; | LOAD_FILE | RELOC_PT_2 | STUFF | RELOC_PT_1***| STUFF | +; ------------------------------------------------------- + + push hl + call _Twiddle_Bytes + pop hl + +_No_Remainder: + srl h + srl h + ret z + ld a, h ; A = HL / 1024 == H >> 2 + +_Loop: +; Move 1024 bytes A times + ld bc, 1024 + call _Twiddle_Bytes + dec a + ret z + jr _Loop + + +_LoadFile_Is_Larger: +; load > reloc + +; Do exchange of HL bytes + ld b, h + ld c, l + push de + push hl + ld hl, (_load_ptr) + ld de, (_reloc_ptr) + call zp.memswap + add hl, bc + ex de, hl + add hl, bc + ex de, hl + +; ---------------------------------------------------------------- +; | LOAD_PT_1 | RELOC_II | RELOC_III | RELOC_I | LOAD_PT_2 | STUFF | +; ---------------------------------------------------------------- +; ^_reloc_ptr ^DE ^_load_ptr ^HL + +; Update vars and go through the whole rigmarole again + ld (_load_ptr), hl + ld (_reloc_ptr), de + ex de, hl + call zp.f_size + ld (_reloc_size), bc + pop de + pop hl + or a + sbc hl, de + ld (_load_size), hl + ex de, hl + ld h, b + ld l, c + jp _CMP + +_Sizes_Are_Equal: +; Exchange DE or HL bytes + ld b, h + ld c, l + ld hl, (_load_ptr) + ld de, (_reloc_ptr) + jp zp.memswap + add hl, bc + ex de, hl + add hl, bc + ex de, hl + +_Twiddle_Bytes: + ld hl, (_reloc_ptr2) + ld de, os.free + push bc + ldir + + ex de, hl + ld hl, (_reloc_endptr) + or a + sbc hl, de + ld b, h + ld c, l + ld hl, (_reloc_ptr2) + ex de, hl + ldir + + pop bc + ld hl, os.free + ldir + + ret + +.echo "f_load \t" +.echo $-vect_f_load +.echo "\n" + + +.module INPUT +vect_input: + push bc + push hl + + ld b, 8 ; counter + ld hl, TIMEOUT ; timer + ld a, RED1_WHITE1 + out (link_port), a ; Set W = 1, R = 1 + +_Start: + in a, (link_port) + and %00000011 + cp %00000011 + jr nz, _Get_Bit + call _Chk_Timeout + jr _Start + +_Get_Bit: + cp %00000010 ; If W = 1, R = 0, a zero is incoming + jr z, _Receive_0 + +_Receive_1: + srl c ; Shift accumulator and set bit + set 7, c + + ld a, RED0_WHITE1 + out (link_port), a + jr _End_Wait + +_Receive_0: + srl c + + ld a, RED1_WHITE0 + out (link_port), a + +_End_Wait: + call _Chk_Timeout + in a, (link_port) + and %00000011 + jr z, _End_Wait + ld a, RED1_WHITE1 + out (link_port), a + + ld hl, TIMEOUT + djnz _Start + + ld a, c + or a +_Exit: + pop hl + pop bc + ret + +_Chk_Timeout: + dec hl + ld a, h + or l + ret nz +_Error: + pop af + ld a, RED1_WHITE1 + out (link_port), a + scf + jr _Exit + +.echo "input \t" +.echo $-vect_input +.echo "\n" + + +_token: + ld a, ' ' + cpi + jr z, $-2 + dec hl + ld a, (hl) + ld bc, $2022 + cp c + jr nz, $+3 + ld b, $22 + inc hl +_tok2: + ld a, (hl) + ldi + inc bc + or a + scf + ret z + cp b + jr z, _tok3 + cp $5C + jr z, _escape + cp c + jr nz, _tok2 +_tok3: + dec de + xor a + ld (de), a + inc de + ret + +_escape: + ld a, (hl) + inc hl + cp $5C + jr z, _tok2 + or a + scf + ret z + dec de + cp c + jr nz, _esc2 + ld a, c +_esc3: + ld (de), a + inc de + jr _tok2 +_esc2: + cp 'n' + jr nz, _tok2 + ld a, $0A + jr _esc3 + +.echo "extract \t" +.echo $-vect_extract +.echo "\n" + +.NOLIST + +#define equ .equ +#define EQU .equ +#define end .end + +#include "ti83plus.inc" +#include "mirage.inc" +.LIST + +#DEFINE kDown 01h +#DEFINE kLeft 02h +#DEFINE kRight 03h +#DEFINE kUp 04h +#DEFINE kEnter 09h +#DEFINE kMode 37h +#DEFINE kDel 38h +#DEFINE kYEq 35h +#DEFINE k2nd 36h +#DEFINE kAlpha 30h + +#define xmm savesscreen ; Mega Man's x-coord +#define ymm savesscreen+2 ; Mega Man's y-coord +#define jrem savesscreen+3 ; remebers the y-coord where the jump started +#define jchk savesscreen+4 ; check for jump: 0=no jump 1=jump up 2=jump down +#define wchk savesscreen+5 ; check if ok to shoot again: 0=yes >0=no +#define dir savesscreen+6 ; direction Mega Man is facing: 0=left 1=right +#define x2 savesscreen+7 ; x-coord for many different things +#define y2 savesscreen+8 ; y-coord for many different things +#define xb savesscreen+9 ; x-coord for boss +#define yb savesscreen+10 ; y-coord for boss +#define wx1 savesscreen+11 ; x-coord for 1st shot +#define wy1 savesscreen+12 ; y-coord for 1st shot +#define wx2 savesscreen+13 ; x-coord for 2nd shot +#define wy2 savesscreen+14 ; y-coord for 2nd shot +#define wx3 savesscreen+15 ; x-coord for 3rd shot +#define wy3 savesscreen+16 ; y-coord for 3rd shot +#define bdir savesscreen+17 ; direction of boss: 0=left 1=right +#define wdir1 savesscreen+18 ; direction of 1st shot: 0=left 1=right +#define wdir2 savesscreen+19 ; direction of 2nd shot: 0=left 1=right +#define wdir3 savesscreen+20 ; direction of 3rd shot: 0=left 1=right +#define wchk1 savesscreen+21 ; check if bullet #1 is still on screen: 0=no 1=yes +#define wchk2 savesscreen+22 ; check if bullet #2 is still on screen: 0=no 1=yes +#define wchk3 savesscreen+23 ; check if bullet #3 is still on screen: 0=no 1=yes +#define curlvl savesscreen+24 ; pointer to the beginning of the level data +#define feet savesscreen+26 ; check for which running sprite to use: 0 or 1 +#define xscr savesscreen+27 ; x scroller +#define tempscr savesscreen+29 ; amount scrolled within incomplete block +#define schk savesscreen+31 ; scroll check: 0=no 1=yes +#define boss savesscreen+33 ; which boss u will face: 1 2 3=top row 4 5 6=middle row 7 8 9=bottom row +#define wpnspr savesscreen+34 ; current weapon sprite +#define smlspr savesscreen+36 ; current 8x8 sprite +#define curpic savesscreen+38 ; current pic to draw +#define bosspic savesscreen+40 ; current boss sprite +#define death savesscreen+42 ; check if dead: 0=no 1=yes +#define win savesscreen+44 ; check if level is completed: 0=no 1=yes +#define xe savesscreen+45 ; x-coord for enemy +#define ye savesscreen+46 ; y-coord for enemy +#define edir savesscreen+47 ; direction enemy is travelling +#define echk savesscreen+48 ; check for onscreen enemy: 0=no 1=i 2=t 3=dead +#define enspr savesscreen+49 ; current enemy sprite +#define ecount savesscreen+51 ; counts how many pixels enemy has moved (32 pixels allowed) +#define ecount2 savesscreen+52 ; counts how many pixels have scrolled since enemy appeared +#define bchk savesscreen+53 ; checks for boss: 0=no 1=yes +#define bwx savesscreen+54 ; x-coord of boss's shot +#define bwspr savesscreen+55 ; sprite for boss's shot +#define bwchk savesscreen+57 ; check for boss's shot onscreen +#define bjchk savesscreen+58 ; check for boss's jump: 0=no jump 1=jump down 2=jump up +#define lvldone savesscreen+59 ; checks levels completed: 0 1 2=top row 3 _ 4=middle row 5 6 7=bottom row +#define anpw savesscreen+60 ; checks for password dots corresponding to Anchorman +#define mapw savesscreen+61 ; checks for password dots corresponding to Mailman +#define mdpw savesscreen+62 ; checks for password dots corresponding to Madman +#define dopw savesscreen+63 ; checks for password dots corresponding to Doorman +#define popw savesscreen+64 ; checks for password dots corresponding to Policeman +#define frpw savesscreen+65 ; checks for password dots corresponding to Freshman +#define sepw savesscreen+66 ; checks for password dots corresponding to Top Secret Weapon +#define sapw savesscreen+67 ; checks for password dots corresponding to Salesman +#define mipw savesscreen+68 ; checks for password dots corresponding to Milkman +#define tankpw savesscreen+69 ; checks for password dots corresponding to energy tanks +#define lives savesscreen+70 ; the number of lives you have +#define tanks savesscreen+71 ; the number of energy tanks you have +#define remlvl savesscreen+72 ; remembers which level you were on before the special boss screens +#define wpn savesscreen+74 ; which weapon you are using: 1=an...5=??...9=mi +#define wrem1 savesscreen+75 ; remembers x-coord where short-range bullet #1 must stop +#define wrem2 savesscreen+76 ; remembers x-coord where short-range bullet #2 must stop +#define wrem3 savesscreen+77 ; remembers x-coord where short-range bullet #3 must stop +#define bcount savesscreen+78 ; special counting variable used for final bosses +#define item savesscreen+79 ; check for item in level: 0=no 1=yes 2=got +#define enp savesscreen+80 ; life energy +#define enan savesscreen+81 ; energy for anchor toss +#define enma savesscreen+82 ; energy for stamp throw +#define enmd savesscreen+83 ; energy for swearing scream +#define endo savesscreen+84 ; energy for splinter shot +#define ense savesscreen+85 ; energy for top secret weapon +#define enpo savesscreen+86 ; energy for badge boomerang +#define enfr savesscreen+87 ; energy for zit shield +#define ensa savesscreen+88 ; energy for bill charge +#define enmi savesscreen+89 ; energy for milk bubble +#define enwpn savesscreen+90 ; energy for current weapon +#define ene savesscreen+91 ; energy for enemy +#define enb savesscreen+92 ; energy for boss +#define hchk savesscreen+93 ; check if you've been hit: 0=no 1=yes +#define stack savesscreen+93 ; save stack for debugging (?) + + .org $9d93 ;Origin (set back two to account for AsmPrgm) + .db $BB,$6D ;Compiled AsmPrgm token + ret ;So TIOS wont run the program + .db 1 ;Identifier as MirageOS program + .db %00000000,%00000000 + .db %00000000,%00000000 + .db %00000011,%00000000 + .db %00000100,%10000000 + .db %00001111,%01000000 + .db %00001010,%11000000 + .db %00000100,%10000000 + .db %00000011,%00000000 + .db %00001100,%11100000 + .db %00001100,%11100000 + .db %00000011,%00000000 + .db %00000111,%11100000 + .db %00001010,%11100000 + .db %00001110,%00000000 + .db %00000000,%00000000 +Description: .db "Mega Man 83+",0 +prog_start: + bcall(_grbufclr) + ld a,19 + ld hl,djs + call centertext + ld a,25 + ld hl,and_inf + call centertext + ld a,31 + ld hl,djs2 + call centertext + ld a,37 + ld hl,pre + call centertext +djsp_loop: + call wait + call fastcopys + ld hl,intro + call draw_text +story_loop2: + bcall(_clrlcdfull) + bcall(_grbufclr) + xor a ; no levels completed + ld (lvldone),a + ld (tanks),a ; 0 energy tanks + inc a + ld (tankpw),a ; 0 energy tanks (for password system) + inc a + inc a + ld (lives),a ; 3 lives + ld hl,logo ; display title screen + ld de,PLOTSSCREEN + call disprle + call fastcopys + ld a,17 + ld (x2),a + ld a,43 + ld (y2),a + ld hl,pwdot + ld (smlspr),hl +menu_loop: + call xor_8x8 + call fastcopys + call xor_8x8 + bcall(_getcsc) + cp kUp + jr z,curs_change + cp kDown + jr z,curs_change + cp kEnter + jr z,title_done + cp kMode + jr nz,menu_loop + ret +curs_change: + ld a,(y2) + ld b,a + ld a,97 + sub b + ld (y2),a + jr menu_loop +title_done: + ld a,(y2) + cp 43 + jr z,cb_init + call enter_pw + cp kMode ; exit game if MODE was pressed on enter password screen + ret z +cb_init: + ld a,33 + ld (x2),a + ld a,22 + ld (y2),a + bcall(_clrlcdfull) +choose_boss: + bcall(_grbufclr) + ld hl,cbs + ld de,PLOTSSCREEN + call disprle + ld a,(lvldone) + ld c,a + rrc c + call c,no_an + rrc c + call c,no_ma + rrc c + call c,no_md + rrc c + call c,no_do + rrc c + call c,no_po + rrc c + call c,no_fr + rrc c + call c,no_sa + rrc c + call c,no_mi + call fastcopys +boss_loop: + call xor_corners + call fastcopys + call xor_corners + bcall(_getcsc) + cp kUp + jr z,bcurs_up + cp kDown + jr z,bcurs_down + cp kLeft + jr z,bcurs_left + cp kRight + jr z,bcurs_right + cp kEnter + jr z,find_boss + cp kMode + jr nz,boss_loop + bcall(_getcsc) ; clear the keybuffer + bcall(_grbufclr) + bcall(_clrlcdfull) + ret +bcurs_up: + ld a,(y2) + cp 1 + jr z,boss_loop + ld a,(y2) + sub 21 + ld (y2),a + jr boss_loop +bcurs_down: + ld a,(y2) + cp 43 + jp z,boss_loop + ld a,(y2) + add a,21 + ld (y2),a + jp boss_loop +bcurs_left: + ld a,(x2) + cp 2 + jp z,boss_loop + ld a,(x2) + sub 31 + ld (x2),a + jp boss_loop +bcurs_right: + ld a,(x2) + cp 64 + jp z,boss_loop + ld a,(x2) + add a,31 + ld (x2),a + jp boss_loop +find_boss: + ld a,(y2) + cp 1 + jr z,row1 + cp 22 + jr z,row2 + ld a,7 + jr found_row +row2: + ld a,4 + jr found_row +row1: + ld a,1 +found_row: + ld (boss),a + ld a,(x2) + cp 64 + jr z,add2 + cp 33 + jr nz,add0 + ld a,(boss) + inc a + jr add_done +add0: + ld a,(boss) + jr add_done +add2: + ld a,(boss) + add a,2 +add_done: + ld (boss),a + ld a,44 + ld (xb),a + ld a,27 + ld (yb),a + ld a,(boss) + cp 1 + jr z,an_init + cp 2 + jr z,ma_init + cp 3 + jr z,md_init + cp 4 + jp z,do_init + cp 5 + jp z,ti_init + cp 6 + jp z,po_init + cp 7 + jp z,fr_init + cp 8 + jp z,sa_init + cp 9 + jp z,mi_init +an_init: + ld a,(lvldone) + bit 0,a + jp c,choose_boss + ld hl,ansl + call disp_boss + ld hl,wpan + ld (bwspr),hl + ld a,54 + ld hl,a_n + call centertext + ld hl,lvlan + jp intro_done +ma_init: + ld a,(lvldone) + bit 1,a + jp nz,choose_boss + ld hl,masl + call disp_boss + ld hl,wpma + ld (bwspr),hl + ld a,54 + ld hl,m_a + call centertext + ld hl,lvlma + jp intro_done +md_init: + ld a,(lvldone) + bit 2,a + jp nz,choose_boss + ld hl,mdsl + call disp_boss + ld hl,wpmd + ld (bwspr),hl + ld a,54 + ld hl,m_d + call centertext + ld hl,lvlmd + jp intro_done +do_init: + ld a,(lvldone) + bit 3,a + jp nz,choose_boss + ld hl,dosl + call disp_boss + ld hl,wpdo + ld (bwspr),hl + ld a,54 + ld hl,d_o + call centertext + ld hl,lvldo + jp intro_done +ti_init: + ld a,(lvldone) + rrc a + jp nc,choose_boss + rrc a + jp nc,choose_boss + rrc a + jp nc,choose_boss + rrc a + jp nc,choose_boss + rrc a + jp nc,choose_boss + rrc a + jp nc,choose_boss + rrc a + jp nc,choose_boss + rrc a + jp nc,choose_boss + ld hl,tisl + call disp_boss + ld hl,wpp + ld (bwspr),hl + ld a,54 + ld hl,t_i + call centertext + ld hl,lvlti + jp intro_done +po_init: + ld a,(lvldone) + bit 4,a + jp nz,choose_boss + ld hl,posl + call disp_boss + ld hl,wppo + ld (bwspr),hl + ld a,54 + ld hl,p_o + call centertext + ld hl,lvlpo + jr intro_done +fr_init: + ld a,(lvldone) + bit 5,a + jp nz,choose_boss + ld hl,frsl + call disp_boss + ld hl,wpfr + ld (bwspr),hl + ld a,54 + ld hl,f_r + call centertext + ld hl,lvlfr + jr intro_done +sa_init: + ld a,(lvldone) + bit 6,a + jp nz,choose_boss + ld hl,sasl + call disp_boss + ld hl,wpsa + ld (bwspr),hl + ld a,54 + ld hl,s_a + call centertext + ld hl,lvlsa + jr intro_done +mi_init: + ld a,(lvldone) + bit 7,a + jp nz,choose_boss + ld hl,misl + call disp_boss + ld hl,wpmi + ld (bwspr),hl + ld a,54 + ld hl,m_i + call centertext + ld hl,lvlmi +intro_done: + ld (curlvl),hl ; remember the start of the level + ld (remlvl),hl + call delay + bcall(_clrlcdfull) + bcall(_grbufclr) + call wpn_fill +part1_init: + call init_vars + ld a,44 ; initialize some variables + ld (ymm),a + xor a + ld (jchk),a + ld (bchk),a + ld (wpn),a + inc a + ld (dir),a + ld (schk),a + ld hl,wpp + ld (wpnspr),hl + call ShowLevel + call main_loop + bcall(_getcsc) ; clear the keybuffer + bcall(_grbufclr) + bcall(_clrlcdfull) + ld a,(death) ; check if died + or a + jp nz,lost_life + ld a,(win) + or a + ret z ; exit loop if level has not been completed +part2_init: + ld hl,lvlb1 + xor a + ld (bchk),a + call init_lvlb + ld a,(death) ; check if died + or a + jp nz,lost_life + ld a,(win) + or a + ret z ; exit loop if level has not been completed + xor a ; initialize boss variables + ld (bdir),a + ld (bjchk),a + inc a + ld (bchk),a + ld a,28 + ld (enb),a + ld a,44 + ld (yb),a + ld a,76 + ld (xb),a + ld hl,lvlb2 + call init_lvlb + ld a,(death) ; check if died + or a + jp nz,lost_life + ld a,(win) + or a + ret z ; exit game if level has not been completed + ld a,(lvldone) + ld b,a + ld a,(boss) + cp 5 + jp z,final_boss + cp 1 + jr z,set_bit0 + cp 2 + jr z,set_bit1 + cp 3 + jr z,set_bit2 + cp 4 + jr z,set_bit3 + cp 6 + jr z,set_bit4 + cp 7 + jr z,set_bit5 + cp 8 + jr z,set_bit6 + set 7,b + jr set_bit_done +set_bit6: + set 6,b + jr set_bit_done +set_bit5: + set 5,b + jr set_bit_done +set_bit4: + set 4,b + jr set_bit_done +set_bit3: + set 3,b + jr set_bit_done +set_bit2: + set 2,b + jr set_bit_done +set_bit1: + set 1,b + jr set_bit_done +set_bit0: + set 0,b +set_bit_done: + ld a,b + ld (lvldone),a + call you_got ; show "you got x weapon" screen + cp kMode ; exit is MODE was pressed at you got screen + ret z + call show_pw ; show password + cp kMode ; exit if MODE was pressed at show password screen + ret z + jp cb_init +lost_life: + call wpn_fill + ld a,(lives) ; go to game over screen if all lives lost + or a + jr z,game_over + ld a,(schk) + or a + jp z,part2_init + jp part1_init +game_over: + call show_pw + cp kMode ; exit if MODE was pressed at show password screen + ret z + ld a,3 + ld (lives),a + bcall(_clrlcdfull) + bcall(_grbufclr) + ld hl,game_over_lindat + call draw + call fastcopys + ld de,18*256+31 + ld hl,gover + call setvputs + ld de,25*256+25 + ld hl,cont + call setvputs + ld de,32*256+14 + ld hl,stgsel + call setvputs + ld de,39*256+31 + ld hl,endgame + call setvputs + bcall(_grbufclr) +go_loop: + bcall(_getcsc) + cp k2nd + jr z,go_cont + cp kAlpha + jp z,cb_init + cp kMode + ret z + jr go_loop +go_cont: + ld hl,(remlvl) + ld (curlvl),hl + jp part1_init +final_boss: + xor a + ld (boss),a ;select boss 0 + ld (win),a ;you haven't won + call main_loop ;call the main loop + ld a,(death) ;check if you died + or a + jp nz,lost_life + ld a,(win) + or a + ret z ; exit game if level has not been completed +ending: + ld hl,endingtext + call draw_text + ld hl,creditext + call draw_text + ld a,11 + ld hl,pres + call centertext + ld a,22 + ld hl,djs + call centertext + ld a,28 + ld hl,and_inf + call centertext + ld a,34 + ld hl,djs2 + call centertext + call clrwait + ret +init_lvlb: + ld (curlvl),hl + call init_vars + ld (schk),a + ld (bcount),a + call ShowLevel + call main_loop + ld a,(win) ; see if you defeated TI's spaceship + cp 2 + jr z,ship_dead + bcall(_getcsc) ; clear the keybuffer + bcall(_grbufclr) + bcall(_clrlcdfull) + ret +init_vars: + ld hl,0 + ld (xscr),hl + ld (tempscr),hl + ld hl,8 ; initialize some variables + ld (xmm),hl + xor a + ld (wchk),a + ld (wchk1),a + ld (wchk2),a + ld (wchk3),a + ld (win),a + ld (death),a + ld (feet),a + ld (item),a + ld (echk),a + ld (bwchk),a + ld (hchk),a + ret +ship_dead: + ld a,28 + ld (enb),a + call check_spr + call xor_char + ld b,0 ; erase ship + call ship_init +ti_fall: ; TI falls to the bottom of the screen + ld a,(yb) + inc a + ld (yb),a + call xor_boss + call fastcopys + call xor_boss + ld a,(yb) + cp 44 + jr nz,ti_fall + call check_spr + call xor_char + ret +main_loop: + ld a,(hchk) + or a + call nz,thrown_back + ld hl,(xmm) + call load_x + ld a,(ymm) + add a,4 + push hl + push af + call GetBlock + call CheckItem + pop af + pop hl + ld de,7 + add hl,de + call GetBlock + call CheckItem + ld a,(echk) + or a + call nz,check_ehitu + ld a,(bchk) + or a + call nz,check_bhitu + ld a,(bwchk) + or a + call nz,check_bwhitu + call ShowLevel + ld a,(boss) ;draw spaceship if necessary + cp 5 + ld b,1 + call z,ship_init ;if at boss 5 (TI) draw the ship + call draw_nrg ; draw energy lines + call check_spr ; figure out which Mega Man sprite to use + call xor_char ; draw it + ld a,(wchk1) ; check if shots should be displayed and display as necessary + or a + call nz,xor_wpn1 + ld a,(wchk2) + or a + call nz,xor_wpn2 + ld a,(wchk3) + or a + call nz,xor_wpn3 + ld a,(echk) ;check for enemy and display as necessary + push af + cp 1 + call z,xor_en + pop af + cp 2 + call z,xor_en + ld a,(bwchk) ;check for boss's shot and display as necessary + or a + call nz,xor_bwpn + ld a,(bchk) ;check for boss and display as necessary + or a + call nz,xor_boss + call fastcopys ;copy everything to the buffer + ld a,(enp) + or a + call z,die + call xor_char ;erase the Megaman sprite + ld a,(wchk1) ;check for shots and erase as necessary + or a + call nz,shot1_move + ld a,(wchk2) + or a + call nz,shot2_move + ld a,(wchk3) + or a + call nz,shot3_move + ld a,(echk) ;check for enemy and erase as necessary + push af + cp 1 + call z,i_move + pop af + cp 2 + call z,t_move + ld a,(bwchk) ;check for boss's shot and erase as necessary + or a + call nz,boss_shot_move + ld a,(bchk) ;check for boss and erase as necessary + or a + call nz,boss_move + bcall(_grbufclr) + ld a,(wchk) + or a + call nz,inc_wchk + call find_spr + ld a,$FD ;check ENTER + out (1),a + in a,(1) + bit 0,a + call z,pause + ld a,$FE ;check arrow keys + out (1),a + in a,(1) + bit 1,a + call z,move_left + bit 2,a + call z,move_right + ld a,$BF ;check other keys + out (1),a + in a,(1) + bit 4,a + call z,sub_screen + bit 5,a + push af + call z,jump + call nz,jchk_now_2 + call fall + pop af + bit 6,a + ret z ;exit loop is MODE has been pressed + ld a,$DF ;check ALPHA + out (1),a + in a,(1) + bit 7,a + call z,shoot + ld a,(death) + or a + ret nz ;exit loop if dead + ld a,(win) + or a + jp z,main_loop ;exit loop if level completed + ret +pause: + call tiny_delay +pause_loop: + bcall(_getcsc) + cp kMode + ret z + cp kEnter + jr nz,pause_loop + call tiny_delay + ret +jchk_now_2: ; make sure you can't jump + push af + ld a,2 + ld (jchk),a + pop af + ret +move_right: + ld a,(feet) + and $02 + ld hl,mmrr2 + jr nz,rr2_spr + ld hl,mmrr +rr2_spr: + ld (curpic),hl + ld a,(xmm) + cp 80 + call z,won + call CheckRight + call z,CheckRight2 + call z,CheckRight3 + jr nz,no_right + call change_right + ld a,(xmm) + cp 44 + jr nz,no_scroll + ld a,(xscr) + cp 68 + jr z,no_scroll + ld a,(schk) + or a + jr z,no_scroll + call scroll + jr no_right +no_scroll: + ld hl,(xmm) + inc hl + ld (xmm),hl +no_right: + ld a,(dir) + or a + call z,change_right + ret +change_right: + call feet_inc + ld a,1 + ld (dir),a + ret +move_left: + push af + ld a,(feet) + and $02 + ld hl,mmrl2 + jr nz,rl2_spr + ld hl,mmrl +rl2_spr: + ld (curpic),hl + ld a,(xmm) + or a + jr z,no_left + call CheckLeft + call z,CheckLeft2 + call z,CheckLeft3 + jr nz,no_left + call change_left + ld hl,(xmm) + dec hl + ld (xmm),hl +no_left: + ld a,(dir) + or a + call nz,change_left + pop af + ret +change_left: + call feet_inc + xor a + ld (dir),a + ret +CheckRight: + ld hl,(xmm) + call load_x + ld de,8 + add hl,de + jr CheckHorz +CheckLeft: + ld hl,(xmm) + call load_x + dec hl +CheckHorz: + ld a,(ymm) + call GetBlock + call CheckTile + ret +CheckRight2: + ld hl,(xmm) + call load_x + ld de,8 + add hl,de + jr CheckHorz2 +CheckLeft2: + ld hl,(xmm) + call load_x + dec hl +CheckHorz2: + ld a,(ymm) + add a,11 + call GetBlock + call CheckTile + ret +CheckRight3: + ld hl,(xmm) + call load_x + ld de,8 + add hl,de + jr CheckHorz3 +CheckLeft3: + ld hl,(xmm) + call load_x + dec hl +CheckHorz3: + ld a,(ymm) + add a,6 + call GetBlock + call CheckTile + ret +GetBlock: ; Gets the block at (HL,A) -> A. HL = addr, B,C = coord + srl h + rr l + srl h + rr l + srl h + rr l ; Divide X with 8 + ld b,l + cp 64 + jr c,OnScreen + xor a +OnScreen: + srl a + srl a + srl a ; Divide Y with 8 + ld c,a + add hl,hl + add hl,hl + add hl,hl + ld d,0 + ld e,a + add hl,de ; HL = x*8+y + ld de,(curlvl) + add hl,de ; Add HL with the pointer to the level data + ld a,(hl) + ret +CheckTile: + or a + ret z + cp 5 + ret c + xor a + ret +CheckItem: + cp 5 + jr z,got_life + cp 6 + jr z,got_tank + ret +got_life: + ld a,(item) + cp 1 + ret nz + inc a + ld (item),a + ld a,(lives) + inc a + ld (lives),a + ret +got_tank: + ld a,(item) + cp 1 + ret nz + inc a + ld (item),a + ld a,(tanks) + inc a + ld (tanks),a + ret +jump: + push af + ld a,(jchk) + cp 2 + jr z,no_jump + or a + jr nz,jump_up + ld a,1 + ld (jchk),a + ld a,(ymm) + sub 10 + jr nc,jrem_not0 + ld a,1 +jrem_not0: + ld (jrem),a +jump_up: + ld a,(jrem) + ld b,a + ld a,(ymm) + cp b + jr c,end_jump + call CheckUp + call z,CheckUp2 + jr nz,end_jump + ld a,(ymm) + dec a + ld (ymm),a + jr no_jump +end_jump: + ld a,2 + ld (jchk),a +no_jump: + pop af + ret +fall: + push af + ld a,(jchk) + cp 1 + jr z,no_fall + call CheckDown + call z,CheckDown2 + jr nz,end_fall + ld a,(ymm) + inc a + ld (ymm),a + cp 52 + call z,die + jr no_fall +end_fall: + xor a + ld (jchk),a +no_fall: + pop af + ret +CheckUp: + ld a,(ymm) + dec a + jr CheckVert +CheckDown: + ld a,(ymm) + add a,12 +CheckVert: + ld hl,(xmm) + call load_x + call GetBlock + call CheckTile + ret +CheckUp2: + ld a,(ymm) + dec a + jr CheckVert2 +CheckDown2: + ld a,(ymm) + add a,12 +CheckVert2: + ld hl,(xmm) + call load_x + ld de,7 + add hl,de + call GetBlock + call CheckTile + ret +shoot_sa: + ld a,(xmm) + ld (wx1),a + ld a,(ymm) + add a,4 + ld (wy1),a + ld a,1 + ld (wchk1),a + ld a,(enwpn) + sub 4 + ld (enwpn),a + call check_spr + call xor_char + ret +shoot_fr: + ld a,(xmm) + ld (wx1),a + ld a,(ymm) + ld (wy1),a + call xor_wpn1 + ld a,(wy1) + add a,4 + ld (wy1),a + call xor_wpn1 + ld a,(wy1) + add a,4 + ld (wy1),a + ld a,1 + ld (wchk1),a + ld a,(enwpn) + sub 4 + ld (enwpn),a + call check_spr + call xor_char + ret +shoot_md: + ld a,(ymm) + inc a + ld (wy1),a + ld a,(dir) + or a + ld a,(xmm) + jr z,smd_left + add a,8 + jr smd_right +smd_left: + sub 8 +smd_right: + ld (wx1),a + ld a,1 + ld (wchk1),a + ld a,(enwpn) + sub 4 + ld (enwpn),a + ret +shoot_se: + ld a,(bchk) ; restrictions on using top secret weapon + or a + ret nz + ld a,(echk) + or a + ret z + cp 3 + ret z + ld a,(ymm) + cp 26 + ret c + ld a,(lives) + cp 3 + ret c + ld a,(tanks) + cp 4 + ret c + call won ; effect is beating the current part of the level if requirements are met + xor a + ld (enwpn),a + ret +shoot: + ld a,(wchk) + or a + ret nz + ld a,(wpn) + or a + jr z,no_check_enwpn + ld a,(enwpn) ; don't shoot if no weapon energy left + or a + ret z +no_check_enwpn: + ld a,(dir) ; make sure you aren't too close to edge of screen + or a + jr z,try_left + ld a,(xmm) + cp 80 + ret nc + jr shoot_cont +try_left: + ld a,(xmm) + cp 8 + ret c +shoot_cont: + ld a,(wpn) + cp 3 + jr z,shoot_md + cp 5 + jr z,shoot_se + cp 7 + jp z,shoot_fr + cp 8 + jp z,shoot_sa + ld a,1 + ld (wchk),a + ld a,(wpn) + or a + jr z,no_dec_enwpn + ld a,(enwpn) + dec a + ld (enwpn),a +no_dec_enwpn: + ld a,(wchk1) ; check for bullet #1 + or a ; if it doesn't exist, it does now + jr z,store_shot1 + ld a,(wchk2) ; check for bullet #2 + or a ; if it doesn't exist, it does now + jr z,store_shot2 + ld a,(wchk3) ; check for bullet #3 + or a ; if it doesn't exist, it does now + ret nz + ld a,(ymm) ; initialize shot #3 + add a,5 + ld (wy3),a + ld a,(dir) + ld (wdir3),a + or a + jr z,shoot_left3 + ld a,(xmm) + add a,8 + ld b,a + add a,12 + jr shoot_init3 +shoot_left3: + ld a,(xmm) + sub 8 + ld b,a + sub 12 +shoot_init3: + res 0,a + ld (wrem3),a + ld a,b + ld (wx3),a + ld a,1 + ld (wchk3),a + ret +store_shot2: + ld a,(ymm) ; initialize shot #2 + add a,5 + ld (wy2),a + ld a,(dir) + ld (wdir2),a + or a + jr z,shoot_left2 + ld a,(xmm) + add a,8 + ld b,a + add a,24 + jr shoot_init2 +shoot_left2: + ld a,(xmm) + sub 8 + ld b,a + sub 24 +shoot_init2: + res 0,a + ld (wrem2),a + ld a,b + ld (wx2),a + ld a,1 + ld (wchk2),a + ret +store_shot1: + ld a,(ymm) ; initialize shot #1 + add a,5 + ld (wy1),a + ld a,(dir) + ld (wdir1),a + or a + jr z,shoot_left1 + ld a,(xmm) + add a,8 + ld b,a + add a,24 + jr shoot_init1 +shoot_left1: + ld a,(xmm) + sub 8 + ld b,a + sub 24 +shoot_init1: + res 0,a + ld (wrem1),a + ld a,b + ld (wx1),a + ld a,1 + ld (wchk1),a + ret +shot3_move: + call xor_wpn3 + ld a,(xmm) + ld b,a + ld a,(wx3) + cp 4 + jr c,sm_end3 + cp 87 + jr nc,sm_end3 + res 0,a + cp b + jr z,sm_end3 + ld b,a + ld a,(wpn) + cp 1 + jr z,sm_short3 + cp 2 + jr z,sm_short3 + cp 6 + jr z,sm_short3 +sm_cont3: + ld a,(wdir3) + or a + ld a,(wpn) + jr z,sm_left3 + inc b + inc b + or a + jr z,smr_two3 + cp 4 + jr z,smr_two3 + cp 6 + jr z,smr_two3 + jr sm_store3 +smr_two3: + inc b + jr sm_store3 +sm_left3: + dec b + dec b + or a + jr z,sml_two3 + cp 4 + jr z,sml_two3 + cp 6 + jr z,sml_two3 +sml_two3: + dec b +sm_store3: + ld a,b + ld (wx3),a + ret +sm_short3: + ld a,(wrem3) + res 0,a + sub b + jr nz,sm_cont3 + ld a,(wpn) + cp 6 + jr z,sm_chg3 +sm_end3: + xor a ; shot is off the screen + ld (wchk3),a + ret +sm_chg3: + ld a,(wdir3) ; reverse dir shot #3 is moving + inc a + and $01 + ld (wdir3),a + jr sm_cont3 +shot2_move: + call xor_wpn2 + ld a,(xmm) + ld b,a + ld a,(wx2) + cp 4 + jr c,sm_end2 + cp 87 + jr nc,sm_end2 + res 0,a + cp b + jr z,sm_end2 + ld b,a + ld a,(wpn) + cp 1 + jr z,sm_short2 + cp 2 + jr z,sm_short2 + cp 6 + jr z,sm_short2 +sm_cont2: + ld a,(wdir2) + or a + ld a,(wpn) + jr z,sm_left2 + inc b + inc b + or a + jr z,smr_two2 + cp 4 + jr z,smr_two2 + cp 6 + jr z,smr_two2 + jr sm_store2 +smr_two2: + inc b + jr sm_store2 +sm_left2: + dec b + dec b + or a + jr z,sml_two2 + cp 4 + jr z,sml_two2 + cp 6 + jr z,sml_two2 +sml_two2: + dec b +sm_store2: + ld a,b + ld (wx2),a + ret +sm_short2: + ld a,(wrem2) + res 0,a + sub b + jr nz,sm_cont2 + ld a,(wpn) + cp 6 + jr z,sm_chg2 +sm_end2: + xor a ; shot is off the screen + ld (wchk2),a + ret +sm_chg2: + ld a,(wdir2) ; reverse dir shot #2 is moving + inc a + and $01 + ld (wdir2),a + jr sm_cont2 +ssa_move: + call check_spr + call xor_char +ssam_loop: + ld a,(wx1) + inc a + ld (wx1),a + call xor_wpn1 + call fastcopys + call xor_wpn1 + ld a,(wx1) + cp 88 + jr nz,ssam_loop +ssam_loop2: + ld a,(wx1) + dec a + ld (wx1),a + call xor_wpn1 + call fastcopys + call xor_wpn1 + ld a,(wx1) + or a + jr nz,ssam_loop2 +ssam_loop3: + ld a,(wx1) + inc a + ld (wx1),a + call xor_wpn1 + call fastcopys + call xor_wpn1 + ld a,(xmm) + ld b,a + ld a,(wx1) + cp b + jr nz,ssam_loop3 + xor a + ld (wchk1),a + ld a,(echk) + or a + jr z,ssam_bchk + cp 3 + jr z,ssam_bchk + ld a,(ye) + ld b,a + ld a,(wy1) + sub 7 + cp b + ret nc + add a,10 + cp b + ret c + xor a + ld a,(ene) + or a + sub 2 + jr nc,sahite_nokill + xor a +sahite_nokill: + ld (ene),a + or a + ret nz + ld a,3 + ld (echk),a + ret +ssam_bchk: + ld a,(bchk) + or a + ret z + ld a,(yb) + ld b,a + ld a,(wy1) + sub 11 + cp b + ret nc + add a,14 + cp b + ret c + call dam7_or1 + ld a,(enb) + or a + sub 1 + jr nc,sahitb_nokill + xor a +sahitb_nokill: + ld (enb),a + ret nz + call won + ret +sfr_move: + ld a,(wy1) + sub 4 + ld (wy1),a + call xor_wpn1 + ld a,(wy1) + sub 4 + ld (wy1),a + call xor_wpn1 + call short_delay + xor a + ld (wchk1),a + call check_spr + call xor_char + ld a,(echk) + or a + jr z,sfrm_bchk + cp 3 + jr z,sfrm_bchk + ld a,(xe) + ld b,a + ld a,(wx1) + sub 11 + cp b + ret nc + add a,22 + cp b + ret c + ld a,(ye) + ld b,a + ld a,(wy1) + sub 7 + cp b + ret nc + add a,10 + cp b + ret c + xor a + ld a,(ene) + or a + sub 2 + jr nc,frhite_nokill + xor a +frhite_nokill: + ld (ene),a + or a + ret nz + ld a,3 + ld (echk),a + ret +sfrm_bchk: + ld a,(bchk) + or a + ret z + ld a,(xb) + ld b,a + ld a,(wx1) + sub 11 + cp b + ret nc + add a,22 + cp b + ret c + ld a,(yb) + ld b,a + ld a,(wy1) + sub 11 + cp b + ret nc + add a,14 + cp b + ret c + call dam7_or1 + ld a,(enb) + or a + sub 1 + jr nc,frhitb_nokill + xor a +frhitb_nokill: + ld (enb),a + ret nz + call won + ret +smd_move: + call short_delay + xor a + ld (wchk1),a + ld a,(echk) + or a + jr z,smdm_bchk + cp 3 + jr z,smdm_bchk + call xor_en + ld a,3 + ld (echk),a + xor a + ld (ecount),a +smdm_bchk: + ld a,(bchk) + or a + ret z + call dam7_or1 + ld a,(enb) + or a + sub 1 + jr nc,mdhitb_nokill + xor a +mdhitb_nokill: + ld (enb),a + ret nz + call won + ret +shot1_move: + call xor_wpn1 + ld a,(wpn) + cp 3 + jr z,smd_move + cp 7 + jp z,sfr_move + cp 8 + jp z,ssa_move + ld a,(xmm) + ld b,a + ld a,(wx1) + cp 4 + jr c,sm_end1 + cp 87 + jr nc,sm_end1 + res 0,a + cp b + jr z,sm_end1 + ld b,a + ld a,(wpn) + cp 1 + jr z,sm_short1 + cp 2 + jr z,sm_short1 + cp 6 + jr z,sm_short1 +sm_cont1: + ld a,(wdir1) + or a + ld a,(wpn) + jr z,sm_left1 + inc b + inc b + or a + jr z,smr_two1 + cp 4 + jr z,smr_two1 + cp 6 + jr z,smr_two1 + jr sm_store1 +smr_two1: + inc b + jr sm_store1 +sm_left1: + dec b + dec b + or a + jr z,sml_two1 + cp 4 + jr z,sml_two1 + cp 6 + jr z,sml_two1 +sml_two1: + dec b +sm_store1: + ld a,b + ld (wx1),a + ret +sm_short1: + ld a,(wrem1) + res 0,a + sub b + jr nz,sm_cont1 + ld a,(wpn) + cp 6 + jr z,sm_chg1 +sm_end1: + xor a ; shot is off the screen + ld (wchk1),a + ret +sm_chg1: + ld a,(wdir1) ; reverse dir shot #1 is moving + inc a + and $01 + ld (wdir1),a + jr sm_cont1 +inc_wchk: ; lets you shoot again + inc a + cp 8 + jr nz,keep_wchk + xor a +keep_wchk: + ld (wchk),a + ret +die: + call short_delay + ld a,(lives) + dec a + ld (lives),a + ld a,1 + ld (death),a + ret +won: + ld a,1 + ld (win),a + ld a,(bchk) ; check for boss + or a + ret z + ld a,(boss) ; see if that boss was the spaceship + cp 5 + ret nz + ld a,2 ; if so, set win=2 + ld (win),a + ret +s1_dec: + ld a,(wx1) + dec a + ld (wx1),a + ld a,(wrem1) + dec a + ld (wrem1),a + ret +s2_dec: + ld a,(wx2) + dec a + ld (wx2),a + ld a,(wrem2) + dec a + ld (wrem2),a + ret +s3_dec: + ld a,(wx3) + dec a + ld (wx3),a + ld a,(wrem3) + dec a + ld (wrem3),a + ret +scroll: + bcall(_grbufclr) + ld a,(tempscr) + inc a + cp 8 + jr nz,scroll_cont + ld a,(xscr) + inc a + ld (xscr),a + xor a +scroll_cont: + ld (tempscr),a + ld a,(wchk1) + or a + call nz,s1_dec + ld a,(wchk2) + or a + call nz,s2_dec + ld a,(wchk3) + or a + call nz,s3_dec + ld a,(echk) + or a + call nz,next_en + ld a,(echk) + or a + ret z + cp 3 + ret z + ld a,(xe) ; decrease x-coord of enemy + dec a + ld (xe),a + or a + call z,en_dead + ret +ShowLevel: + ld hl,(xscr) + add hl,hl + add hl,hl + add hl,hl + push hl + pop de + ld hl,(curlvl) + add hl,de + ld bc,0 +RepShowLevel: ; displays the current full screen of the level + ld a,(hl) + cp 5 + call nc,item_init + inc hl + push hl + ld h,0 + ld l,a + add hl,hl + add hl,hl + add hl,hl + ld de,lvlspr + add hl,de + push bc + ld a,(tempscr) + ld d,a + ld a,b + sub d + jr c,skip_8x8 + ld (x2),a + ld a,c + ld (y2),a + ld (smlspr),hl + call xor_8x8 +skip_8x8: + pop bc + pop hl + ld a,c + add a,8 + ld c,a + cp 64 + jr nz,RepShowLevel + ld c,0 + ld a,b + add a,8 + ld b,a + cp 96 + jr nz,RepShowLevel + ret +item_init: + ld d,a + cp 7 + jr nc,t_init + ld a,(item) + cp 2 + jr z,no_item_init + ld a,1 + ld (item),a + ld a,d + ret +no_item_init: + xor a ; replace item sprite with blank sprite + ret +t_init: + ld a,(echk) ; check for existing enemy + or a + jr nz,no_en_init + ld hl,iup ; initialize enemy variables + xor a + ld (edir),a + ld (ecount),a + ld (ecount2),a + inc a + inc a + ld (ene),a + ld a,d + cp 9 + ld a,1 + jr z,i_init + ld a,3 + ld (ene),a + dec a + ld hl,tleft +i_init: + ld (echk),a + ld (enspr),hl + ld a,b + ld (xe),a + ld a,c + ld (ye),a +no_en_init: + xor a ; replace enemy sprite with blank sprite + ret +load_x: ; loads x location (input as hl) into hl, accounting for scrolling + push hl + ld hl,(xscr) + add hl,hl + add hl,hl + add hl,hl + pop de + add hl,de + push hl + ld hl,(tempscr) + pop de + add hl,de + ret +feet_inc: ; feet changes from 0 to 1 to 2 to 3 to 0 + ld a,(feet) + inc a + cp 4 + jr nz,store_feet + xor a +store_feet: + ld (feet),a + ret +sub_screen: + push af + bcall(_clrlcdfull) + bcall(_grbufclr) + call tiny_delay + xor a + ld (wchk1),a + ld (wchk2),a + ld (wchk3),a + ld a,(wpn) + or a + jr z,ss_cont + cp 1 + jr z,en_an + cp 2 + jr z,en_ma + cp 3 + jr z,en_md + cp 4 + jr z,en_do + cp 5 + jr z,en_se + cp 6 + jr z,en_po + cp 7 + jr z,en_fr + cp 8 + jr z,en_sa + cp 9 + jr z,en_mi +en_an: + ld a,(enwpn) + ld (enan),a + jr ss_cont +en_ma: + ld a,(enwpn) + ld (enma),a + jr ss_cont +en_md: + ld a,(enwpn) + ld (enmd),a + jr ss_cont +en_do: + ld a,(enwpn) + ld (endo),a + jr ss_cont +en_se: + ld a,(enwpn) + ld (ense),a + jr ss_cont +en_po: + ld a,(enwpn) + ld (enpo),a + jr ss_cont +en_fr: + ld a,(enwpn) + ld (enfr),a + jr ss_cont +en_sa: + ld a,(enwpn) + ld (ensa),a + jr ss_cont +en_mi: + ld a,(enwpn) + ld (enmi),a +ss_cont: + ld a,28 + ld (x2),a + ld a,53 + ld (y2),a + ld hl,tank + ld (smlspr),hl + call xor_8x8 + ld a,55 + ld (x2),a + ld hl,head + ld (smlspr),hl + call xor_8x8 + ld hl,ss_cont_lindat + call draw + xor a + ld (x2),a + inc a + ld (y2),a + ld hl,pwdot + ld (smlspr),hl +ss_loop_new: + call xor_8x8 + call fastcopys + ld a,(lvldone) + bit 0,a + jr z,ss_line1 + ld d,16 + ld e,15 + push af + ld a,(enan) + add a,17 + ld h,a + ld l,15 + ld a,1 + call fastline + pop af +ss_line1: + bit 1,a + jr z,ss_line2 + ld d,16 + ld e,25 + push af + ld a,(enma) + add a,17 + ld h,a + ld l,25 + ld a,1 + call fastline + pop af +ss_line2: + bit 2,a + jr z,ss_line3 + ld d,16 + ld e,35 + push af + ld a,(enmd) + add a,17 + ld h,a + ld l,35 + ld a,1 + call fastline + pop af +ss_line3: + bit 3,a + jr z,ss_line4 + ld d,16 + ld e,45 + push af + ld a,(endo) + add a,17 + ld h,a + ld l,45 + ld a,1 + call fastline + pop af +ss_line4: + bit 4,a + jr z,ss_line5 + ld d,63 + ld e,15 + push af + ld a,(enpo) + add a,64 + ld h,a + ld l,15 + ld a,1 + call fastline + pop af +ss_line5: + bit 5,a + jr z,ss_line6 + ld d,63 + ld e,5 + push af + ld a,(ense) + add a,64 + ld h,a + ld l,5 + ld a,1 + call fastline + ld d,63 + ld e,25 + ld a,(enfr) + add a,64 + ld h,a + ld l,25 + ld a,1 + call fastline + pop af +ss_line6: + bit 6,a + jr z,ss_line7 + ld d,63 + ld e,35 + push af + ld a,(ensa) + add a,64 + ld h,a + ld l,35 + ld a,1 + call fastline + pop af +ss_line7: + bit 7,a + jr z,ss_line_done + ld d,63 + ld e,45 + ld a,(enmi) + add a,64 + ld h,a + ld l,45 + ld a,1 + call fastline +ss_line_done: + ld d,16 + ld e,5 + ld a,(enp) + add a,17 + ld h,a + ld l,5 + ld a,1 + call fastline + call fastcopys + ld a,(lvldone) + bit 0,a + jr z,ss_bit1 + ld de,12*256+7 + ld hl,an + call setvputs +ss_bit1: + bit 1,a + jr z,ss_bit2 + ld de,22*256+7 + ld hl,ma + call setvputs +ss_bit2: + bit 2,a + jr z,ss_bit3 + ld de,32*256+7 + ld hl,md + call setvputs +ss_bit3: + bit 3,a + jr z,ss_bit4 + ld de,42*256+7 + ld hl,do + call setvputs +ss_bit4: + bit 4,a + jr z,ss_bit5 + ld de,12*256+54 + ld hl,po + call setvputs +ss_bit5: + bit 5,a + jr z,ss_bit6 + ld de,22*256+54 + ld hl,fr + call setvputs + ld de,2*256+54 + ld hl,se + call setvputs +ss_bit6: + bit 6,a + jr z,ss_bit7 + ld de,32*256+54 + ld hl,sa + call setvputs +ss_bit7: + bit 7,a + jr z,ss_loop_init + ld de,42*256+54 + ld hl,mi + call setvputs +ss_loop_init: + ld de,2*256+7 + ld hl,p + call setvputs + ld a,(tanks) + bcall(_setxxop1 + ld hl,54*256+40 + ld (pencol),hl + ld a,1 + bcall(_dispop1a) + ld a,(lives) + bcall(_setxxop1) + ld hl,54*256+67 + ld (pencol),hl + ld a,1 + bcall(_dispop1a) + call xor_8x8 +ss_loop: + bcall(_getcsc) + cp kUp + jp z,ss_up + cp kDown + jp z,ss_down + cp kLeft + jp z,ss_horz + cp kRight + jp z,ss_horz + cp k2nd + jp z,ss_tank + cp kMode + jp z,ss_mode + cp kYEq + jr nz,ss_loop + ld hl,wpp + ld a,(lvldone) + ld b,a + ld a,(x2) + or a + ld a,(y2) + jr z,wpn_left + cp 1 + jr z,ss_se + cp 11 + jp z,ss_po + cp 21 + jp z,ss_fr + cp 31 + jp z,ss_sa + jp ss_mi +wpn_left: + ld c,0 + ld hl,wpp + cp 1 + jp z,ss_exit + cp 11 + jr z,ss_an + cp 21 + jr z,ss_ma + cp 31 + jr z,ss_md + jr ss_do +ss_an: + bit 0,b + jr z,ss_loop + ld c,1 + ld a,(enan) + ld (enwpn),a + ld hl,wpan + jp ss_exit +ss_ma: + bit 1,b + jr z,ss_loop + ld c,2 + ld a,(enma) + ld (enwpn),a + ld hl,wpma + jr ss_exit +ss_md: + bit 2,b + jp z,ss_loop + ld c,3 + ld a,(enmd) + ld (enwpn),a + ld hl,wpmd + jr ss_exit +ss_do: + bit 3,b + jp z,ss_loop + ld c,4 + ld a,(endo) + ld (enwpn),a + ld hl,wpdo + jr ss_exit +ss_se: + bit 5,b + jp z,ss_loop + ld c,5 + ld a,(ense) + ld (enwpn),a + ld hl,wpse + jr ss_exit +ss_po: + bit 4,b + jp z,ss_loop + ld c,6 + ld a,(enpo) + ld (enwpn),a + ld hl,wppo + jr ss_exit +ss_fr: + bit 5,b + jp z,ss_loop + ld c,7 + ld a,(enfr) + ld (enwpn),a + ld hl,wpfr + jr ss_exit +ss_sa: + bit 6,b + jp z,ss_loop + ld c,8 + ld a,(ensa) + ld (enwpn),a + ld hl,wpsa + jr ss_exit +ss_mi: + bit 7,b + jp z,ss_loop + ld c,9 + ld a,(enmi) + ld (enwpn),a + ld hl,wpmi +ss_exit: + ld a,c + ld (wpn),a + ld (wpnspr),hl + bcall(_clrlcdfull) + bcall(_grbufclr) + call ShowLevel + pop af + call tiny_delay + ret +ss_mode: + pop bc ; pop to something other than af so it remembers that MODE has been pressed + ret +ss_up: + ld a,(y2) + cp 1 + jr z,ss_vert + sub 10 + ld (y2),a + jp ss_loop_new +ss_down: + ld a,(y2) + cp 41 + jr z,ss_vert + add a,10 + ld (y2),a + jp ss_loop_new +ss_vert: + ld b,a + ld a,42 + sub b + ld (y2),a + jp ss_loop_new +ss_horz: + ld a,(x2) + ld b,a + ld a,47 + sub b + ld (x2),a + jp ss_loop_new +ss_tank: + ld a,(tanks) + or a + jp z,ss_loop_new + dec a + ld (tanks),a + ld a,28 + ld (enp),a + jp ss_loop_new +xor_char: + ld ix,(curpic) + ld b,12 + ld a,(ymm) + ld l,a + ld a,(xmm) + call isprite + ret +check_spr: + ld a,(dir) + or a + jr z,check_spr_l +check_spr_r: + ld hl,mmhr + ld a,(hchk) + or a + jr nz,found_spr + ld a,(ymm) + add a,12 + call CheckVert + jr c,not_jump_r + ld hl,mmjr + ld a,(jchk) + or a + jr nz,found_spr +not_jump_r: + ld hl,mmwr + ld a,(wchk) + or a + jr nz,found_spr + ret +check_spr_l: + ld hl,mmhl + ld a,(hchk) + or a + jr nz,found_spr + ld a,(ymm) + add a,12 + call CheckVert + jr c,not_jump_l + ld hl,mmjl + ld a,(jchk) + or a + jr nz,found_spr +not_jump_l: + ld hl,mmwl + ld a,(wchk) + or a + jr nz,found_spr + ret +found_spr: + ld (curpic),hl + ret +find_spr: + ld a,(dir) + or a + jr z,left_spr + ld hl,mmsr + jr found_spr +left_spr: + ld hl,mmsl + jr found_spr +t_move: + call xor_en + call check_en_dist + ld a,(edir) + or a + ld a,(xe) + jr z,t_move_l + inc a + jr t_move_r +t_move_l: + dec a + or a + call z,en_dead +t_move_r: + ld (xe),a + ret +i_move: + call xor_en + call check_en_dist + ld a,(edir) + or a + ld a,(ye) + jr z,i_move_u + inc a + jr i_move_d +i_move_u: + dec a +i_move_d: + ld (ye),a + ret +ti_check: + ld a,(echk) + cp 3 + jr nz,tic_cont + xor a + ld (echk),a +tic_cont: + ld b,46 + ld c,46 + ld a,(bcount) + inc a + ld (bcount),a + cp 50 + ld a,7 + call z,item_init + ld a,(bcount) + cp 100 + ld a,9 + call z,item_init + ld a,(bcount) + cp 100 + ret nz + xor a + ld (bcount),a + ret +ship_check: + ld a,(bcount) + inc a + cp 50 + jr z,ship_laser + ld (bcount),a + ret +ship_laser: + xor a + ld (bcount),a + ld b,1 + call xor_laser + call short_delay + ld a,(ymm) + cp 38 + call c,die + ld b,0 + call xor_laser + ret +boss_move: + call xor_boss + ld a,(boss) + or a + jr z,ti_check + cp 5 + jr z,ship_check + ld a,(bjchk) + or a + jr nz,boss_jump + ld a,(bdir) + or a + ld a,(xb) + jr z,boss_move_l + inc a + cp 76 + jr boss_move_r +boss_move_l: + dec a + cp 56 +boss_move_r: + ld (xb),a + jr z,boss_turn + cp 66 + call z,boss_shoot + ret +boss_turn: + ld a,(bdir) ; reverse dir boss is moving + ld b,a + ld a,1 + sub b + ld (bdir),a + ld a,2 + ld (bjchk),a + ret +boss_jump: + ld a,(bjchk) + cp 1 + ld a,(yb) + jr z,boss_fall + dec a + cp 34 + jr boss_jump_end +boss_fall: + inc a + cp 44 +boss_jump_end: + ld (yb),a + ret nz + ld a,(bjchk) + dec a + ld (bjchk),a + ret +bshoot_sa: + ld a,(xb) + ld (bwx),a + call xor_boss + ret +bshoot_fr: + ld a,(xb) + ld (bwx),a + ret +bshoot_md: + ld a,(xb) + sub 8 + ld (bwx),a + ret +boss_shoot: + ld a,(bwchk) + or a + ret nz + inc a + ld (bwchk),a + ld a,(boss) + cp 3 + jr z,bshoot_md + cp 7 + jr z,bshoot_fr + cp 8 + jr z,bshoot_sa + ld a,58 + ld (bwx),a + ret +bssa_move: + call xor_boss + call check_spr + call xor_char +bssam_loop: + ld a,(bwx) + dec a + ld (bwx),a + call xor_bwpn + call fastcopys + call xor_bwpn + ld a,(bwx) + or a + jr nz,bssam_loop +bssam_loop2: + ld a,(bwx) + inc a + ld (bwx),a + call xor_bwpn + call fastcopys + call xor_bwpn + ld a,(bwx) + cp 88 + jr nz,bssam_loop2 +bssam_loop3: + ld a,(bwx) + dec a + ld (bwx),a + call xor_bwpn + call fastcopys + call xor_bwpn + ld a,(xb) + ld b,a + ld a,(bwx) + cp b + jr nz,bssam_loop3 + xor a + ld (bwchk),a + call check_spr + call xor_char + ld a,(ymm) + ld b,a + ld a,37 + cp b + ret nc + add a,16 + cp b + ret c + ld a,(enp) + or a + sub 4 + jr nc,bssam_nokill + xor a +bssam_nokill: + ld (enp),a + ret +bsfr_move: + call check_spr + call xor_char + call xor_boss + call xor_bwpn + call fastcopys + call xor_char + call xor_boss + call xor_bwpn + call short_delay + xor a + ld (bwchk),a + ld a,(xmm) + ld b,a + ld a,55 + cp b + ret nc + add a,22 + cp b + ret c + ld a,(ymm) + ld b,a + ld a,33 + cp b + ret nc + add a,11 + cp b + ret c + ld a,(enp) + or a + sub 4 + jr nc,bsfrm_nokill + xor a +bsfrm_nokill: + ld (enp),a + ret +bsmd_move: + call short_delay + ld a,(enp) + or a + sub 4 + jr nc,bsmdm_nokill + xor a +bsmdm_nokill: + ld (enp),a + xor a + ld (bwchk),a + ret +boss_shot_move: + call xor_bwpn + ld a,(boss) + cp 8 + jp z,bssa_move + cp 3 + jr z,bsmd_move + cp 7 + jr z,bsfr_move + ld a,(xb) + ld b,a + ld a,(bwx) + cp 2 + jr c,bsm_end + cp b + jr z,bsm_end + ld b,a +bsm_cont: + dec b + dec b + ld a,(boss) + cp 4 + jr nz,bsm_store + dec b +bsm_store: + ld a,b + ld (bwx),a + ret +bsm_end: + xor a ; boss's shot is off the screen + ld (bwchk),a + ret +check_en_dist: ; check distance enemy has moved and turn around if necessary + ld a,(ecount) + inc a + cp 33 + call z,en_turn + ld (ecount),a + ret +en_turn: + ld hl,(enspr) + ld de,8 + ld a,(edir) + or a + jr z,en_turn_r + sbc hl,de + dec a + jr en_turn_l +en_turn_r: + add hl,de + inc a +en_turn_l: + ld (enspr),hl + ld (edir),a + xor a + ret +en_dead: ; enemy is now dead + ld a,3 + ld (echk),a + xor a + ld (ecount),a + ret +next_en: ; check if previous enemy is clear so next enemy can initialize + ld a,(ecount2) + inc a + ld (ecount2),a + cp 96 + ret nz + xor a + ld (echk),a + ret +xor_en: + ld ix,(enspr) + ld b,8 + ld a,(ye) + ld l,a + ld a,(xe) + call isprite + ret +xor_corners: + ld hl,uprlft + ld (smlspr),hl + call xor_8x8 + ld a,(x2) + add a,22 + ld (x2),a + ld hl,uprrt + ld (smlspr),hl + call xor_8x8 + ld a,(y2) + add a,12 + ld (y2),a + ld hl,lwrrt + ld (smlspr),hl + call xor_8x8 + ld a,(x2) + sub 22 + ld (x2),a + ld hl,lwrlft + ld (smlspr),hl + call xor_8x8 + ld a,(y2) + sub 12 + ld (y2),a + ret +xor_8x8: + ld ix,(smlspr) + ld b,8 + ld a,(y2) + ld l,a + ld a,(x2) + call isprite + ret +xor_wpn1: + ld ix,(wpnspr) + ld b,4 + ld a,(wy1) + ld l,a + ld a,(wx1) + call isprite + ret +xor_wpn2: + ld ix,(wpnspr) + ld b,4 + ld a,(wy2) + ld l,a + ld a,(wx2) + call isprite + ret +xor_wpn3: + ld b,4 + ld a,(wy3) + ld l,a + ld a,(wx3) + ld ix,(wpnspr) + call isprite + ret +xor_bwpn: + ld b,4 + ld l,49 + ld a,(bwx) + ld ix,(bwspr) + call isprite + ret +tiny_delay: + ld b,20 + jr del_loop +short_delay: + ld b,50 + jr del_loop +long_delay: + call delay +med_delay: + ld b,100 + jr del_loop +delay: + ld b,200 +del_loop: + halt + halt + djnz del_loop + ret +disp_boss: + ld (bosspic),hl + bcall(_clrlcdfull) + bcall(_grbufclr) + xor a + ld (x2),a + ld a,16 + ld (y2),a + ld hl,bint + ld (smlspr),hl + ld b,12 +draw1: + push bc + call xor_8x8 + ld a,(x2) + add a,8 + ld (x2),a + pop bc + djnz draw1 + ld a,0 + ld (x2),a + ld a,40 + ld (y2),a + ld hl,bint + ld (smlspr),hl + ld b,12 +draw2: + push bc + call xor_8x8 + ld a,(x2) + add a,8 + ld (x2),a + pop bc + djnz draw2 + call xor_boss + call fastcopys + ret +xor_boss: + ld b,12 + ld a,(yb) + ld l,a + ld a,(xb) + ld ix,(bosspic) + call isprite + ret +no_an: + ld hl,no_an_lindat + call draw + ret +no_ma: + ld hl,no_ma_lindat + call draw + ret +no_md: + ld hl,no_md_lindat + call draw + ret +no_do: + ld hl,no_do_lindat + call draw + ret +no_po: + ld hl,no_po_lindat + call draw + ret +no_fr: + ld hl,no_fr_lindat + call draw + ret +no_sa: + ld hl,no_sa_lindat + call draw + ret +no_mi: + ld hl,no_mi_lindat + call draw + ret +you_got: + bcall(_clrlcdfull) + bcall(_grbufclr) + ld hl,you_got_lindat + call draw + ld hl,35 + ld (xmm),hl + ld a,50 + ld (ymm),a + ld hl,mmsr + ld (curpic),hl + ld a,43 + ld (wx1),a + ld a,51 + ld (wy1),a + ld a,(boss) + ld hl,wpmd + cp 3 + jr z,yg_cont + ld hl,mmwr + ld (curpic),hl + ld a,55 + ld (wx1),a + ld a,55 + ld (wy1),a + ld a,(boss) + ld hl,wpan + cp 1 + jr z,yg_cont + ld hl,wpma + cp 2 + jr z,yg_cont + ld hl,wpdo + cp 4 + jr z,yg_cont + ld hl,wppo + cp 6 + jr z,yg_cont + cp 9 + ld hl,wpmi + jr z,yg_cont + push af + ld a,35 + ld (wx1),a + ld a,54 + ld (wy1),a + call xor_char + pop af + ld hl,wpsa + cp 8 + jr z,yg_cont + ld hl,wpfr + ld (wpnspr),hl + call xor_wpn1 + ld a,50 + ld (wy1),a + call xor_wpn1 + ld a,58 + ld (wy1),a + ld hl,wpfr +yg_cont: + ld (wpnspr),hl + call xor_wpn1 + call xor_char + call fastcopys + ld de,10*256+35 + ld hl,yougot + call setvputs + ld a,(boss) + cp 1 + jr z,yg_an + cp 2 + jr z,yg_ma + cp 3 + jr z,yg_md + cp 4 + jr z,yg_do + cp 6 + jr z,yg_po + cp 7 + jr z,yg_fr + cp 8 + jr z,yg_sa + jr yg_mi +yg_an: + ld de,20*256+28 + ld hl,ygan + call setvputs + jr yg_loop +yg_ma: + ld de,20*256+25 + ld hl,ygma + call setvputs + jr yg_loop +yg_md: + ld de,20*256+20 + ld hl,ygmd + call setvputs + jr yg_loop +yg_do: + ld de,20*256+26 + ld hl,ygdo + call setvputs + jr yg_loop +yg_po: + ld de,20*256+18 + ld hl,ygpo + call setvputs + jr yg_loop +yg_fr: + ld de,20*256+31 + ld hl,ygfr + call setvputs + ld de,30*256+12 + ld hl,ygse + call setvputs + ld de,53*256+55 + ld hl,se + call setvputs + jr yg_loop +yg_sa: + ld de,20*256+28 + ld hl,ygsa + call setvputs + jr yg_loop +yg_mi: + ld de,20*256+28 + ld hl,ygmi + call setvputs +yg_loop: + bcall(_getcsc) + cp kMode + ret z + cp kEnter + jr nz,yg_loop + ret +draw_grid: + bcall(_clrlcdfull) + bcall(_grbufclr) + ld hl,draw_grid_lindat + call draw + ret +pw_text: + ld de,1*256+31 + ld hl,one + call setvputs + ld de,1*256+40 + ld hl,two + call setvputs + ld de,1*256+49 + ld hl,three + call setvputs + ld de,1*256+58 + ld hl,four + call setvputs + ld de,1*256+67 + ld hl,five + call setvputs + ld de,10*256+23 + ld hl,a + call setvputs + ld de,19*256+23 + ld hl,b + call setvputs + ld de,28*256+23 + ld hl,c + call setvputs + ld de,37*256+23 + ld hl,d + call setvputs + ld de,46*256+23 + ld hl,e + call setvputs + ld de,56*256+32 + ld hl,enter + call setvputs + ret +show_pw: + call draw_grid + ld a,(tanks) ; find correct password dot for number of tanks + ld b,1 + or a + jr z,spw_cont + ld b,2 + cp 1 + jr z,spw_cont + ld b,4 + cp 2 + jr z,spw_cont + ld b,8 + cp 3 + jr z,spw_cont + ld b,16 +spw_cont: + ld a,b + ld (tankpw),a + ld hl,pwdot + ld (smlspr),hl + ld a,(lvldone) + bit 0,a + jr z,no_an_dot + ld a,28 + jr an_dot +no_an_dot: + ld a,46 +an_dot: + ld (y2),a + ld a,47 + ld (x2),a + call xor_8x8 + ld a,(lvldone) + bit 1,a + jr z,no_ma_dot + ld a,10 + ld (y2),a + ld a,38 + jr ma_dot +no_ma_dot: + ld a,28 + ld (y2),a + ld a,65 +ma_dot: + ld (x2),a + call xor_8x8 + ld a,(lvldone) + bit 2,a + jr z,no_md_dot + ld a,46 + ld (y2),a + ld a,65 + jr md_dot +no_md_dot: + ld a,10 + ld (y2),a + ld a,56 +md_dot: + ld (x2),a + call xor_8x8 + ld a,(lvldone) + bit 3,a + jr z,no_do_dot + ld a,37 + ld (y2),a + ld a,47 + jr do_dot +no_do_dot: + ld a,46 + ld (y2),a + ld a,38 +do_dot: + ld (x2),a + call xor_8x8 + ld a,(lvldone) + bit 4,a + jr z,no_po_dot + ld a,37 + ld (y2),a + ld a,29 + jr po_dot +no_po_dot: + ld a,19 + ld (y2),a + ld a,65 +po_dot: + ld (x2),a + call xor_8x8 + ld a,(lvldone) + bit 5,a + jr z,no_fr_dot + ld a,56 + jr fr_dot +no_fr_dot: + ld a,29 +fr_dot: + ld (x2),a + ld a,19 + ld (y2),a + call xor_8x8 + ld a,(lvldone) + bit 5,a + jr z,no_se_dot + ld a,46 + ld (y2),a + ld a,29 + jr se_dot +no_se_dot: + ld a,28 + ld (y2),a + ld a,56 +se_dot: + ld (x2),a + call xor_8x8 + ld a,(lvldone) + bit 6,a + jr z,no_sa_dot + ld a,38 + jr sa_dot +no_sa_dot: + ld a,47 +sa_dot: + ld (x2),a + ld a,19 + ld (y2),a + call xor_8x8 + ld a,(lvldone) + bit 7,a + jr z,no_mi_dot + ld a,28 + ld (y2),a + ld a,38 + jr mi_dot +no_mi_dot: + ld a,37 + ld (y2),a + ld a,56 +mi_dot: + ld (x2),a + call xor_8x8 +tank_dot: + ld a,(tankpw) + ld b,a + ld a,10 + ld (y2),a + ld a,47 + ld (x2),a + bit 0,b + jr nz,tank_dot_found + ld a,46 + ld (y2),a + ld a,56 + ld (x2),a + bit 1,b + jr nz,tank_dot_found + ld a,28 + ld (y2),a + ld a,29 + ld (x2),a + bit 2,b + jr nz,tank_dot_found + ld a,10 + ld (y2),a + ld a,65 + ld (x2),a + bit 3,b + jr nz,tank_dot_found + ld a,37 + ld (y2),a + ld a,38 + ld (x2),a +tank_dot_found: + call xor_8x8 + call fastcopys + call pw_text +show_pw_loop: + bcall(_getcsc) + cp kMode + ret z + cp kEnter + jr nz,show_pw_loop + ret +enter_pw: + xor a + ld (anpw),a + ld (mapw),a + ld (mdpw),a + ld (dopw),a + ld (popw),a + ld (frpw),a + ld (sepw),a + ld (sapw),a + ld (mipw),a + ld (tankpw),a + call draw_grid + ld hl,pwcurs + ld (smlspr),hl + ld a,10 + ld (y2),a + ld a,29 + ld (x2),a +ep_loop_new: + call xor_8x8 + call fastcopys + call pw_text + call xor_8x8 +ep_loop: + bcall(_getcsc) + cp kLeft + jr z,ep_left + cp kRight + jr z,ep_right + cp kUp + jr z,ep_up + cp kDown + jr z,ep_down + cp k2nd + jr z,ep_dot + cp kEnter + jp z,check_pw + cp kMode + ret z + jr ep_loop +ep_left: + ld a,(x2) + cp 29 + jr z,ep_horz + sub 9 + ld (x2),a + jr ep_loop_new +ep_right: + ld a,(x2) + cp 65 + jr z,ep_horz + add a,9 + ld (x2),a + jr ep_loop_new +ep_horz: + ld b,a + ld a,94 + sub b + ld (x2),a + jr ep_loop_new +ep_up: + ld a,(y2) + cp 10 + jr z,ep_vert + sub 9 + ld (y2),a + jr ep_loop_new +ep_down: + ld a,(y2) + cp 46 + jr z,ep_vert + add a,9 + ld (y2),a + jr ep_loop_new +ep_vert: + ld b,a + ld a,56 + sub b + ld (y2),a + jr ep_loop_new +ep_dot: + ld hl,pwdot + ld (smlspr),hl + call xor_8x8 + ld hl,pwcurs + ld (smlspr),hl + ld a,(y2) + cp 10 + jr z,rowa + cp 19 + jr z,rowb + cp 28 + jp z,rowc + cp 37 + jp z,rowd + jp rowe +rowa: + ld a,(x2) + cp 29 + jr z,rowa_col1 + cp 38 + jr z,rowa_col2 + cp 47 + jr z,rowa_col3 + cp 56 + jr z,rowa_col4 + jr rowa_col5 +rowa_col1: + ld a,(tankpw) + bit 5,a + set 5,a + jp z,tankpw_done + res 5,a + jp tankpw_done +rowa_col2: + ld a,(mapw) + bit 1,a + set 1,a + jr z,mapw_done + res 1,a +mapw_done: + ld (mapw),a + jp ep_loop_new +rowa_col3: + ld a,(tankpw) + bit 0,a + set 0,a + jp z,tankpw_done + res 0,a + jp tankpw_done +rowa_col4: + ld a,(mdpw) + bit 0,a + set 0,a + jr z,mdpw_done + res 0,a +mdpw_done: + ld (mdpw),a + jp ep_loop_new +rowa_col5: + ld a,(tankpw) + bit 3,a + set 3,a + jp z,tankpw_done + res 3,a + jp tankpw_done +rowb: + ld a,(x2) + cp 29 + jr z,rowb_col1 + cp 38 + jr z,rowb_col2 + cp 47 + jr z,rowb_col3 + cp 56 + jr z,rowb_col4 + jr rowb_col5 +rowb_col1: + ld a,(frpw) + bit 0,a + set 0,a + jr z,frpw_done + res 0,a +frpw_done: + ld (frpw),a + jp ep_loop_new +rowb_col2: + ld a,(sapw) + bit 1,a + set 1,a + jr z,sapw_done + res 1,a +sapw_done: + ld (sapw),a + jp ep_loop_new +rowb_col3: + ld a,(sapw) + bit 0,a + set 0,a + jr z,sapw_done + res 0,a + jr sapw_done +rowb_col4: + ld a,(frpw) + bit 1,a + set 1,a + jr z,frpw_done + res 1,a + jr frpw_done +rowb_col5: + ld a,(popw) + bit 0,a + set 0,a + jr z,popw_done + res 0,a +popw_done: + ld (popw),a + jp ep_loop_new +rowc: + ld a,(x2) + cp 29 + jr z,rowc_col1 + cp 38 + jr z,rowc_col2 + cp 47 + jr z,rowc_col3 + cp 56 + jr z,rowc_col4 + jr rowc_col5 +rowc_col1: + ld a,(tankpw) + bit 2,a + set 2,a + jr z,tankpw_done + res 2,a +tankpw_done: + ld (tankpw),a + jp ep_loop_new +rowc_col2: + ld a,(mipw) + bit 1,a + set 1,a + jr z,mipw_done + res 1,a +mipw_done: + ld (mipw),a + jp ep_loop_new +rowc_col3: + ld a,(anpw) + bit 1,a + set 1,a + jp z,anpw_done + res 1,a + jp anpw_done +rowc_col4: + ld a,(sepw) + bit 0,a + set 0,a + jp z,sepw_done + res 0,a + jp sepw_done +rowc_col5: + ld a,(mapw) + bit 0,a + set 0,a + jp z,mapw_done + res 0,a + jp mapw_done +rowd: + ld a,(x2) + cp 29 + jr z,rowd_col1 + cp 38 + jr z,rowd_col2 + cp 47 + jr z,rowd_col3 + cp 56 + jr z,rowd_col4 + jr rowd_col5 +rowd_col1: + ld a,(popw) + bit 1,a + set 1,a + jp z,popw_done + res 1,a + jp popw_done +rowd_col2: + ld a,(tankpw) + bit 4,a + set 4,a + jr z,tankpw_done + res 4,a + jr tankpw_done +rowd_col3: + ld a,(dopw) + bit 1,a + set 1,a + jr z,dopw_done + res 1,a +dopw_done: + ld (dopw),a + jp ep_loop_new +rowd_col4: + ld a,(mipw) + bit 0,a + set 0,a + jr z,mipw_done + res 0,a + jp mipw_done +rowd_col5: + ld a,(tankpw) + bit 6,a + set 6,a + jp z,tankpw_done + res 6,a + jp tankpw_done +rowe: + ld a,(x2) + cp 29 + jr z,rowe_col1 + cp 38 + jr z,rowe_col2 + cp 47 + jr z,rowe_col3 + cp 56 + jr z,rowe_col4 + jr rowe_col5 +rowe_col1: + ld a,(sepw) + bit 1,a + set 1,a + jr z,sepw_done + res 1,a +sepw_done: + ld (sepw),a + jp ep_loop_new +rowe_col2: + ld a,(dopw) + bit 0,a + set 0,a + jr z,dopw_done + res 0,a + jr dopw_done +rowe_col3: + ld a,(anpw) + bit 0,a + set 0,a + jr z,anpw_done + res 0,a +anpw_done: + ld (anpw),a + jp ep_loop_new +rowe_col4: + ld a,(tankpw) + bit 1,a + set 1,a + jp z,tankpw_done + res 1,a + jp tankpw_done +rowe_col5: + ld a,(mdpw) + bit 1,a + set 1,a + jp z,mdpw_done + res 1,a + jp mdpw_done +check_pw: + ld a,(anpw) + or a + jr z,pw_error + cp 3 + jr z,pw_error + ld a,(mapw) + or a + jr z,pw_error + cp 3 + jr z,pw_error + ld a,(mdpw) + or a + jr z,pw_error + cp 3 + jr z,pw_error + ld a,(dopw) + or a + jr z,pw_error + cp 3 + jr z,pw_error + ld a,(popw) + or a + jr z,pw_error + cp 3 + jr z,pw_error + ld a,(frpw) + or a + jr z,pw_error + cp 3 + jr z,pw_error + ld a,(sepw) + or a + jr z,pw_error + cp 3 + jr z,pw_error + ld a,(sapw) + or a + jr z,pw_error + cp 3 + jr z,pw_error + ld a,(mipw) + or a + jr z,pw_error + cp 3 + jr z,pw_error + ld a,(tankpw) + ld b,0 + cp 1 + jp z,pw_good + inc b + cp 2 + jp z,pw_good + inc b + cp 4 + jp z,pw_good + inc b + cp 8 + jp z,pw_good + inc b + cp 16 + jp z,pw_good +pw_error: + bcall(_clrlcdfull) + bcall(_grbufclr) + ld hl,pw_error_lindat + call draw + call fastcopys + ld de,10*256+20 + ld hl,pwerr + call setvputs + ld de,40*256+30 + ld hl,pwtry + call setvputs + ld de,47*256+33 + ld hl,yes + call setvputs + ld de,54*256+31 + ld hl,no + call setvputs +pe_loop: + bcall(_getcsc) + cp k2nd + jp z,enter_pw + cp kAlpha + ret z + cp kMode + ret z + jr pe_loop +pw_good: + ld a,b + ld (tanks),a + ld a,(lvldone) + ld b,a + ld a,(anpw) + bit 1,a + jr z,pg1 + set 0,b +pg1: + ld a,(mapw) + bit 1,a + jr z,pg2 + set 1,b +pg2: + ld a,(mdpw) + bit 1,a + jr z,pg3 + set 2,b +pg3: + ld a,(dopw) + bit 1,a + jr z,pg4 + set 3,b +pg4: + ld a,(popw) + bit 1,a + jr z,pg5 + set 4,b +pg5: + ld a,(frpw) + bit 1,a + jr z,pg6 + set 5,b +pg6: + ld a,(sapw) + bit 1,a + jr z,pg7 + set 6,b +pg7: + ld a,(mipw) + bit 1,a + jr z,pg_done + set 7,b +pg_done: + ld a,b + ld (lvldone),a + ret +ship_init: + ld a,(bchk) + or a + ret z + ld hl,ship_init_lindat + call draw + ld a,56 + ld (xb),a + ld a,16 + ld (yb),a + ret +xor_laser: + ld hl,xor_laser_lindat + call draw + call check_spr + call xor_char + call xor_boss + call fastcopys + ld hl,xor_laser_lindat + call draw + call xor_char + call xor_boss + ret +wpn_fill: + ld a,28 + ld (enp),a + ld (enan),a + ld (enma),a + ld (enmd),a + ld (endo),a + ld (ense),a + ld (enpo),a + ld (enfr),a + ld (ensa),a + ld (enmi),a + ld (enwpn),a + ret +draw_nrg: + ld a,(bchk) + or a + call nz,draw_bnrg + ld d,10 + ld e,38 + ld h,10 + ld a,(enp) + ld b,a + ld a,37 + sub b + ld l,a + ld a,1 + call fastline + ld a,(wpn) + or a + ret z + ld d,12 ; draw weapon energy line + ld e,38 + ld h,12 + ld a,(enwpn) + ld b,a + ld a,37 + sub b + ld l,a + ld a,1 + call fastline + ret +draw_bnrg: + ld d,86 + ld e,38 + ld h,86 + ld a,(enb) + ld b,a + ld a,37 + sub b + ld l,a + ld a,1 + call fastline + ret +check_ehitu: + ld a,(echk) + cp 3 + ret z + ld a,(wx1) + ld d,a + ld a,(wy1) + ld e,a + ld a,(wchk1) + ld c,a + or a + call nz,check_white + ld a,c + ld (wchk1),a + ld a,(wx2) + ld d,a + ld a,(wy2) + ld e,a + ld a,(wchk2) + ld c,a + or a + call nz,check_white + ld a,c + ld (wchk2),a + ld a,(wx3) + ld d,a + ld a,(wy3) + ld e,a + ld a,(wchk3) + ld c,a + or a + call nz,check_white + ld a,c + ld (wchk3),a + ld a,(xmm) + ld b,a + ld a,(xe) + sub 7 + cp b + ret nc + add a,14 + cp b + ret c + ld a,(ymm) + ld b,a + ld a,(ye) + sub 11 + cp b + ret nc + add a,18 + cp b + ret c + ld a,(enp) + or a + sub 1 + jr nc,ehitu_nokill + xor a +ehitu_nokill: + ld (enp),a + ld a,1 + ld (hchk),a + ret +check_bhitu: + ld a,(wx1) + ld d,a + ld a,(wy1) + ld e,a + ld a,(wchk1) + ld c,a + or a + call nz,check_whitb + ld a,c + ld (wchk1),a + ld a,(wx2) + ld d,a + ld a,(wy2) + ld e,a + ld a,(wchk2) + ld c,a + or a + call nz,check_whitb + ld a,c + ld (wchk2),a + ld a,(wx3) + ld d,a + ld a,(wy3) + ld e,a + ld a,(wchk3) + ld c,a + or a + call nz,check_whitb + ld a,c + ld (wchk3),a + ld a,(boss) + cp 5 + jr z,check_shitu + ld a,(xmm) + ld b,a + ld a,(xb) + sub 7 + cp b + ret nc + add a,14 + cp b + ret c + ld a,(ymm) + ld b,a + ld a,(yb) + sub 11 + cp b + ret nc + add a,22 + cp b + ret c + ld a,(enp) + or a + sub 7 + jr nc,bhitu_nokill + xor a +bhitu_nokill: + ld (enp),a + ld a,1 + ld (hchk),a + ret +check_shitu: + ld a,(xmm) + ld b,a + ld a,28 + cp b + ret nc + xor a + ld (enp),a + ld a,1 + ld (hchk),a + ret +check_bwhitu: + ld a,(xmm) + ld b,a + ld a,(bwx) + sub 7 + cp b + ret nc + add a,14 + cp b + ret c + ld a,(ymm) + ld b,a + ld a,38 + cp b + ret nc + add a,14 + cp b + ret c + ld a,(enp) + or a + sub 2 + jr nc,bwhitu_nokill + xor a +bwhitu_nokill: + ld (enp),a + ld a,1 + ld (hchk),a + ret +check_white: + ld a,(xe) + ld b,a + ld a,d + sub 7 + cp b + ret nc + add a,14 + cp b + ret c + ld a,(ye) + ld b,a + ld a,e + sub 7 + cp b + ret nc + add a,10 + cp b + ret c + ld c,0 + ld a,(ene) + or a + sub 1 + jr nc,white_nokill + xor a +white_nokill: + ld (ene),a + or a + ret nz + ld a,3 + ld (echk),a + ret +check_whitb: + ld a,(boss) + cp 5 + jr z,check_whits + ld a,(xb) + ld b,a + ld a,d + sub 7 + cp b + ret nc + add a,14 + cp b + ret c + ld a,(yb) + ld b,a + ld a,e + sub 11 + cp b + ret nc + add a,14 + cp b + ret c + ld c,0 + call dam7_or1 + ld a,(enb) + or a + sub 1 + jr nc,whitb_nokill + xor a +whitb_nokill: + ld (enb),a + ret nz + call won + ret +check_whits: + ld b,35 + ld a,d + sub 7 + cp b + ret nc + add a,14 + cp b + ret c + ld b,42 + ld a,e + cp b + ret nc + add a,10 + cp b + ret c + ld c,0 + call dam7_or1 + ld a,(enb) + or a + sub 1 + jr nc,whits_nokill + xor a +whits_nokill: + ld (enb),a + or a + ret nz + call won + ret +dam7_or1: + ld a,(wpn) + or a + ret z + cp 5 + ret z + cp 1 + jr z,an_on_po + cp 2 + jr z,ma_on_do + cp 3 + jr z,md_on_sa + cp 4 + jr z,do_on_fr + cp 6 + jr z,po_on_md + cp 7 + jr z,fr_on_an + cp 8 + jr z,sa_on_mi + cp 9 + jr z,mi_on_ma + ret +an_on_po: + ld a,(boss) + cp 5 + jr z,damage7 + cp 6 + ret nz + jr damage7 +ma_on_do: + ld a,(boss) + or a + jr z,damage7 + cp 4 + ret nz + jr damage7 +md_on_sa: + ld a,(boss) + cp 8 + ret nz + jr damage7 +do_on_fr: + ld a,(boss) + cp 7 + ret nz + jr damage7 +po_on_md: + ld a,(boss) + cp 3 + ret nz + jr damage7 +fr_on_an: + ld a,(boss) + cp 1 + ret nz + jr damage7 +sa_on_mi: + ld a,(boss) + cp 9 + ret nz + jr damage7 +mi_on_ma: + ld a,(boss) + cp 2 + ret nz +damage7: + ld a,(enb) + or a + sub 6 + jr nc,dam7_nokill + call won + xor a +dam7_nokill: + ld (enb),a + ret +thrown_back: + xor a + ld (hchk),a + ld hl,(xmm) + call load_x + or a + ld de,8 + sbc hl,de + push hl + push hl + call CheckHorz + pop hl + call z,CheckHorz2 + pop hl + call z,CheckHorz3 + jr nz,thrown_short + ld a,(xmm) + sub 8 + ld (xmm),a + ret +thrown_short: + ld hl,(xmm) + call load_x + push hl + srl h + rr l + srl h + rr l + srl h + rr l ; divide x by 8 + add hl,hl + add hl,hl + add hl,hl ; multiply x by 8 + push hl + pop de + pop hl + or a + sbc hl,de + push hl + ld hl,(xmm) + pop de + or a + sbc hl,de + ld (xmm),hl + ret +draw_text: +;hl = pointer to text info + ld a,(hl) + ld e,a + inc hl + push de + push hl + bcall(_clrlcdfull) + bcall(_grbufclr) + ld a,1 + pop hl + pop de +draw_txt_loop: + push af + push de + push hl + call centertext + pop hl + pop de + pop af + add a,6 + cp 59 + call nc,reseta_draw + push af + ld a,$00 + ld b,255 + cpir + pop af + dec e + jr nz,draw_txt_loop + call clrwait + ret + +reseta_draw: + push af + push de + push hl + dec e + call nz,clrwait + inc e + pop hl + pop de + pop af + ld a,1 + ret +clrwait: + call wait + bcall(_clrlcdfull) + bcall(_grbufclr) + ret +wait: + bcall(_getcsc) + or a + jr z,wait + ret +quit_force: + +draw: + push af + push bc + push de + ld a,(hl) + ld b,a + inc hl + ld a,(hl) + inc hl +drawloop: +;hl = pointer to line data +;a = draw mode (0=off 1=on 2=xor 3=pattern) +;b = number of lines + push bc + ld c,(hl) + inc hl + ld b,(hl) + inc hl + ld e,(hl) + inc hl + ld d,(hl) + inc hl + push af + push hl + push bc + push de + pop de + pop hl + call fastline + pop hl + pop af + pop bc + djnz drawloop + pop de + pop bc + pop af + ret + +Description: .db "Mario Demo 83+",0 +prog_start: + bcall(_clrlcdfull) + bcall(_grbufclr) + xor a ; no levels completed + ld (lvldone),a + ld (tanks),a ; 0 energy tanks + inc a + ld (tankpw),a ; 0 energy tanks (for password system) + inc a + inc a + ld (lives),a ; 3 lives + ld a,17 + ld (x2),a + ld a,43 + ld (y2),a + ld hl,pwdot + ld (smlspr),hl + ld a,(y2) + cp 43 + jr z,cb_init + cp kMode ; exit game if MODE was pressed on enter password screen + ret z +cb_init: + ld a,33 + ld (x2),a + ld a,22 + ld (y2),a + bcall(_clrlcdfull) +boss_loop: + call xor_corners + call fastcopys + call xor_corners + jr find_boss + bcall(_getcsc) ; clear the keybuffer + bcall(_grbufclr) + bcall(_clrlcdfull) + ret +find_boss: + ld a,(y2) + cp 1 + jr z,row1 + cp 22 + jr z,row2 + ld a,7 + jr found_row +row2: + ld a,4 + jr found_row +row1: + ld a,1 +found_row: + ld (boss),a + ld a,(x2) + cp 64 + jr z,add2 + cp 33 + jr nz,add0 + ld a,(boss) + inc a + jr add_done +add0: + ld a,(boss) + jr add_done +add2: + ld a,(boss) + add a,2 +add_done: + ld (boss),a + ld a,44 + ld (xb),a + ld a,27 + ld (yb),a + ld a,(boss) + cp 1 + jr z,an_init +an_init: + ld a,(lvldone) + bit 0,a + jp c,boss_loop + ld hl,ansl + call disp_boss + ld hl,wpan + ld (bwspr),hl + ld a,54 + ld hl,a_n + call centertext + ld hl,lvlan +intro_done: + ld (curlvl),hl ; remember the start of the level + ld (remlvl),hl + call delay + bcall(_clrlcdfull) + bcall(_grbufclr) + call wpn_fill +part1_init: + call init_vars + ld a,44 ; initialize some variables + ld (ymm),a + xor a + ld (jchk),a + ld (bchk),a + ld (wpn),a + inc a + ld (dir),a + ld (schk),a + ld hl,wpp + ld (wpnspr),hl + call ShowLevel + call main_loop + bcall(_getcsc) ; clear the keybuffer + bcall(_grbufclr) + bcall(_clrlcdfull) + ld a,(death) ; check if died + or a + jp nz,lost_life + ld a,(win) + or a + ret z ; exit loop if level has not been completed +part2_init: + ld hl,lvlb1 + xor a + ld (bchk),a + call init_lvlb + ld a,(death) ; check if died + or a + jp nz,lost_life + ld a,(win) + or a + ret z ; exit loop if level has not been completed + xor a ; initialize boss variables + ld (bdir),a + ld (bjchk),a + inc a + ld (bchk),a + ld a,28 + ld (enb),a + ld a,44 + ld (yb),a + ld a,76 + ld (xb),a + ld hl,lvlb2 + call init_lvlb + ld a,(death) ; check if died + or a + jp nz,lost_life + ld a,(win) + or a + ret z ; exit game if level has not been completed + ld a,(lvldone) + ld b,a + ld a,(boss) + cp 1 + jr z,set_bit0 + set 7,b + jr set_bit_done +set_bit6: + set 6,b + jr set_bit_done +set_bit5: + set 5,b + jr set_bit_done +set_bit4: + set 4,b + jr set_bit_done +set_bit3: + set 3,b + jr set_bit_done +set_bit2: + set 2,b + jr set_bit_done +set_bit1: + set 1,b + jr set_bit_done +set_bit0: + set 0,b +set_bit_done: + ld a,b + ld (lvldone),a + jp cb_init +lost_life: + call wpn_fill + ld a,(lives) ; go to game over screen if all lives lost + or a + jr z,game_over + ld a,(schk) + or a + jp z,part2_init + jp part1_init +game_over: + cp kMode ; exit if MODE was pressed at show password screen + ret z + ld a,3 + ld (lives),a + bcall(_clrlcdfull) + bcall(_grbufclr) + call draw + call fastcopys + ld de,18*256+31 + ld hl,gover + call setvputs + ld de,25*256+25 + ld hl,cont + call setvputs + ld de,32*256+14 + ld hl,stgsel + call setvputs + ld de,39*256+31 + ld hl,endgame + call setvputs + bcall(_grbufclr) +go_loop: + bcall(_getcsc) + cp k2nd + jr z,go_cont + cp kMode + ret z + jr go_loop +go_cont: + ld hl,(remlvl) + ld (curlvl),hl + jp part1_init +final_boss: + xor a + ld (boss),a ;select boss 0 + ld (win),a ;you haven't won + call main_loop ;call the main loop + ld a,(death) ;check if you died + or a + jp nz,lost_life + ld a,(win) + or a + ret z ; exit game if level has not been completed +ending: + ld hl,endingtext + call draw_text + ld hl,creditext + call draw_text + ld a,11 + ld a,28 + ld hl,and_inf + call centertext + call clrwait + ret +init_lvlb: + ld (curlvl),hl + call init_vars + ld (schk),a + ld (bcount),a + call ShowLevel + call main_loop + ld a,(win) ; see if you defeated TI's spaceship + cp 2 + jr z,ship_dead + bcall(_getcsc) ; clear the keybuffer + bcall(_grbufclr) + bcall(_clrlcdfull) + ret +init_vars: + ld hl,0 + ld (xscr),hl + ld (tempscr),hl + ld hl,8 ; initialize some variables + ld (xmm),hl + xor a + ld (wchk),a + ld (wchk1),a + ld (wchk2),a + ld (wchk3),a + ld (win),a + ld (death),a + ld (feet),a + ld (item),a + ld (echk),a + ld (bwchk),a + ld (hchk),a + ret +ship_dead: + ld a,28 + ld (enb),a + call check_spr + call xor_char + ld b,0 ; erase ship + call ship_init +ti_fall: ; TI falls to the bottom of the screen + ld a,(yb) + inc a + ld (yb),a + call xor_boss + call fastcopys + call xor_boss + ld a,(yb) + cp 44 + jr nz,ti_fall + call check_spr + call xor_char + ret +main_loop: + ld hl,(xmm) + call load_x + ld a,(ymm) + add a,4 + push hl + push af + call GetBlock + pop af + pop hl + ld de,8 + add hl,de + call GetBlock + call ShowLevel + call check_spr ; figure out which Mega Man sprite to use + call xor_char ; draw it + call fastcopys ;copy everything to the buffer + call xor_char ;erase the Megaman sprite + ld a,(echk) ;check for enemy and erase as necessary + bcall(_grbufclr) + call find_spr + ld a,$FD ;check ENTER + out (1),a + in a,(1) + bit 0,a + call z,pause + ld a,$FE ;check arrow keys + out (1),a + in a,(1) + bit 1,a + call z,move_left + bit 2,a + call z,move_right + ld a,$BF ;check other keys + out (1),a + in a,(1) + bit 5,a + jr jump_button_down +main2: + bit 6,a + ret z ;exit loop is MODE has been pressed + ld a,(death) + or a + ret nz ;exit loop if dead + ld a,(win) + or a + jp z,main_loop ;exit loop if level completed + ret +jchk_now_2: ; make sure you can't jump + push af + ld a,2 + ld (jchk),a + pop af + ret +jump_button_down: + push af + ld a,1 + ld (maxjh),a + call z,jump + call nz,jchk_now_2 + call fall + pop af + jr main2 +move_right: + ld a,(feet) + and $02 + ld hl,mmrr2 + jr nz,rr2_spr + ld hl,mmrr +rr2_spr: + ld (curpic),hl + ld a,(xmm) + cp 80 + call z,won + call CheckRight + call z,CheckRight2 + call z,CheckRight3 + jr nz,no_right + ld a,(xmm) + cp 44 + jr nz,no_scroll + ld a,(xscr) + cp 68 + jr z,no_scroll + ld a,(schk) + or a + jr z,no_scroll + call scroll + jr no_right +no_scroll: + ld hl,(xmm) + inc hl + inc hl + ld (xmm),hl +no_right: + ld a,(dir) + or a + call z,change_right + ret +change_right: + ld a,1 + ld (dir),a + ret +move_left: + push af + ld a,(feet) + and $02 + ld hl,mmrl2 + jr nz,rl2_spr + ld hl,mmrl +rl2_spr: + ld (curpic),hl + ld a,(xmm) + or a + jr z,no_left + call CheckLeft + call z,CheckLeft2 + call z,CheckLeft3 + jr nz,no_left + call change_left + ld hl,(xmm) + dec hl + dec hl + ld (xmm),hl +no_left: + ld a,(dir) + or a + call nz,change_left + pop af + ret +change_left: + xor a + ld (dir),a + ret +CheckRight: + ld hl,(xmm) + call load_x + ld de,8 + add hl,de + jr CheckHorz +CheckLeft: + ld hl,(xmm) + call load_x + dec hl +CheckHorz: + ld a,(ymm) + call GetBlock + call CheckTile + ret +CheckRight2: + ld hl,(xmm) + call load_x + ld de,8 + add hl,de + jr CheckHorz2 +CheckLeft2: + ld hl,(xmm) + call load_x + dec hl +CheckHorz2: + ld a,(ymm) + add a,11 + call GetBlock + call CheckTile + ret +CheckRight3: + ld hl,(xmm) + call load_x + ld de,8 + add hl,de + jr CheckHorz3 +CheckLeft3: + ld hl,(xmm) + call load_x + dec hl +CheckHorz3: + ld a,(ymm) + add a,6 + call GetBlock + call CheckTile + ret +pause: + call tiny_delay +pause_loop: + bcall(_getcsc) + cp kMode + ret z + cp kEnter + jr nz,pause_loop + call tiny_delay + ret +GetBlock: ; Gets the block at (HL,A) -> A. HL = addr, B,C = coord + srl h + rr l + srl h + rr l + srl h + rr l ; Divide X with 8 + ld b,l + cp 64 + jr c,OnScreen + xor a +OnScreen: + srl a + srl a + srl a ; Divide Y with 8 + ld c,a + add hl,hl + add hl,hl + add hl,hl + ld d,0 + ld e,a + add hl,de ; HL = x*8+y + ld de,(curlvl) + add hl,de ; Add HL with the pointer to the level data + ld a,(hl) + ret +CheckTile: + or a + ret z + cp 8 + ret c + xor a + ret +jump: + push af + ld a,(jchk) + cp 2 + jr z,no_jump + or a + jr nz,jump_up + ld a,1 + ld (jchk),a + ld a,(ymm) + sub 16 + jr nc,jrem_not0 + ld a,1 +jrem_not0: + ld (jrem),a +jump_up: + ld a,(jrem) + ld b,a + ld a,(ymm) + cp b + jr c,end_jump + call CheckUp + call z,CheckUp2 + jr nz,end_jump + ld a,(ymm) + dec a + dec a + dec a + dec a + ld (ymm),a + jr no_jump +end_jump: + ld a,2 + ld (jchk),a +no_jump: + pop af + ret +fall: + push af + ld a,(jchk) + cp 1 + jr z,no_fall + call CheckDown + call z,CheckDown2 + jr nz,end_fall + ld a,(ymm) + inc a + inc a + inc a + inc a + ld (ymm),a + cp 52 + call z,die + jr no_fall +end_fall: + xor a + ld (jchk),a +no_fall: + pop af + ret +CheckUp: + ld a,(ymm) + dec a + jr CheckVert +CheckDown: + ld a,(ymm) + add a,12 +CheckVert: + ld hl,(xmm) + call load_x + call GetBlock + call CheckTile + ret +CheckUp2: + ld a,(ymm) + dec a + jr CheckVert2 +CheckDown2: + ld a,(ymm) + add a,12 +CheckVert2: + ld hl,(xmm) + call load_x + ld de,7 + add hl,de + call GetBlock + call CheckTile + ret +inc_wchk: ; lets you shoot again + inc a + cp 8 + jr nz,keep_wchk + xor a +keep_wchk: + ld (wchk),a + ret +die: + call short_delay + ld a,(lives) + dec a + ld (lives),a + ld a,1 + ld (death),a + ret +won: + ld a,1 + ld (win),a + ld a,(bchk) ; check for boss + or a + ret z + ld a,(boss) ; see if that boss was the spaceship + cp 5 + ret nz + ld a,2 ; if so, set win=2 + ld (win),a + ret +scroll: + bcall(_grbufclr) + ld a,(tempscr) + inc a + inc a + cp 8 + jr nz,scroll_cont + ld a,(xscr) + inc a + ld (xscr),a + xor a +scroll_cont: + ld (tempscr),a + ld a,(echk) + or a + call nz,next_en + ld a,(echk) + or a + ret z + cp 3 + ret z + ld a,(xe) ; decrease x-coord of enemy + dec a + ld (xe),a + or a + call z,en_dead + ret +ShowLevel: + ld hl,(xscr) + add hl,hl + add hl,hl + add hl,hl + push hl + pop de + ld hl,(curlvl) + add hl,de + ld bc,0 +RepShowLevel: ; displays the current full screen of the level + ld a,(hl) + cp 5 + call nc,item_init + inc hl + push hl + ld h,0 + ld l,a + add hl,hl + add hl,hl + add hl,hl + ld de,lvlspr + add hl,de + push bc + ld a,(tempscr) + ld d,a + ld a,b + sub d + jr c,skip_8x8 + ld (x2),a + ld a,c + ld (y2),a + ld (smlspr),hl + call xor_8x8 +skip_8x8: + pop bc + pop hl + ld a,c + add a,8 + ld c,a + cp 64 + jr nz,RepShowLevel + ld c,0 + ld a,b + add a,8 + ld b,a + cp 96 + jr nz,RepShowLevel + ret +item_init: + ld d,a + cp 8 + jr nc,no_en_init + ld a,(item) + cp 2 + jr z,no_item_init + ld a,1 + ld (item),a + ld a,d + ret +no_item_init: + xor a ; replace item sprite with blank sprite + ret +t_init: +i_init: +no_en_init: + xor a ; replace enemy sprite with blank sprite + ret +load_x: ; loads x location (input as hl) into hl, accounting for scrolling + push hl + ld hl,(xscr) + add hl,hl + add hl,hl + add hl,hl + pop de + add hl,de + push hl + ld hl,(tempscr) + pop de + add hl,de + ret +sub_screen: + push af + bcall(_clrlcdfull) + bcall(_grbufclr) + call tiny_delay + xor a + ld (wchk1),a + ld (wchk2),a + ld (wchk3),a + ld a,(wpn) + or a + jr z,ss_cont + cp 1 + jr z,en_an + cp 2 + jr z,en_ma + cp 3 + jr z,en_md + cp 4 + jr z,en_do + cp 5 + jr z,en_se + cp 6 + jr z,en_po + cp 7 + jr z,en_fr + cp 8 + jr z,en_sa + cp 9 + jr z,en_mi +en_an: + ld a,(enwpn) + ld (enan),a + jr ss_cont +en_ma: + ld a,(enwpn) + ld (enma),a + jr ss_cont +en_md: + ld a,(enwpn) + ld (enmd),a + jr ss_cont +en_do: + ld a,(enwpn) + ld (endo),a + jr ss_cont +en_se: + ld a,(enwpn) + ld (ense),a + jr ss_cont +en_po: + ld a,(enwpn) + ld (enpo),a + jr ss_cont +en_fr: + ld a,(enwpn) + ld (enfr),a + jr ss_cont +en_sa: + ld a,(enwpn) + ld (ensa),a + jr ss_cont +en_mi: + ld a,(enwpn) + ld (enmi),a +ss_cont: + ld a,28 + ld (x2),a + ld a,53 + ld (y2),a + ld a,55 + ld (x2),a + xor a + ld (x2),a + inc a + ld (y2),a + ld hl,pwdot + ld (smlspr),hl +ss_loop_new: + call xor_8x8 + call fastcopys + ld a,(lvldone) + bit 0,a + jr z,ss_line1 + ld d,16 + ld e,15 + push af + ld a,(enan) + add a,17 + ld h,a + ld l,15 + ld a,1 + call fastline + pop af +ss_line1: + bit 1,a + jr z,ss_line2 + ld d,16 + ld e,25 + push af + ld a,(enma) + add a,17 + ld h,a + ld l,25 + ld a,1 + call fastline + pop af +ss_line2: + bit 2,a + jr z,ss_line3 + ld d,16 + ld e,35 + push af + ld a,(enmd) + add a,17 + ld h,a + ld l,35 + ld a,1 + call fastline + pop af +ss_line3: + bit 3,a + jr z,ss_line4 + ld d,16 + ld e,45 + push af + ld a,(endo) + add a,17 + ld h,a + ld l,45 + ld a,1 + call fastline + pop af +ss_line4: + bit 4,a + jr z,ss_line5 + ld d,63 + ld e,15 + push af + ld a,(enpo) + add a,64 + ld h,a + ld l,15 + ld a,1 + call fastline + pop af +ss_line5: + bit 5,a + jr z,ss_line6 + ld d,63 + ld e,5 + push af + ld a,(ense) + add a,64 + ld h,a + ld l,5 + ld a,1 + call fastline + ld d,63 + ld e,25 + ld a,(enfr) + add a,64 + ld h,a + ld l,25 + ld a,1 + call fastline + pop af +ss_line6: + bit 6,a + jr z,ss_line7 + ld d,63 + ld e,35 + push af + ld a,(ensa) + add a,64 + ld h,a + ld l,35 + ld a,1 + call fastline + pop af +ss_line7: + bit 7,a + jr z,ss_line_done + ld d,63 + ld e,45 + ld a,(enmi) + add a,64 + ld h,a + ld l,45 + ld a,1 + call fastline +ss_line_done: + ld d,16 + ld e,5 + ld a,(enp) + add a,17 + ld h,a + ld l,5 + ld a,1 + call fastline + call fastcopys + ld a,(lvldone) + bit 0,a + jr z,ss_bit1 + ld de,12*256+7 + ld hl,an + call setvputs +ss_bit1: + bit 1,a + jr z,ss_bit2 + ld de,22*256+7 + ld hl,ma + call setvputs +ss_bit2: + bit 2,a + jr z,ss_bit3 + ld de,32*256+7 + ld hl,md + call setvputs +ss_bit3: + bit 3,a + jr z,ss_bit4 + ld de,42*256+7 + ld hl,do + call setvputs +ss_bit4: + bit 4,a + jr z,ss_bit5 + ld de,12*256+54 + ld hl,po + call setvputs +ss_bit5: + bit 5,a + jr z,ss_bit6 + ld de,22*256+54 + ld hl,fr + call setvputs + ld de,2*256+54 + ld hl,se + call setvputs +ss_bit6: + bit 6,a + jr z,ss_bit7 + ld de,32*256+54 + ld hl,sa + call setvputs +ss_bit7: + bit 7,a + jr z,ss_loop_init + ld de,42*256+54 + ld hl,mi + call setvputs +ss_loop_init: + ld de,2*256+7 + ld hl,p + call setvputs + ld a,(tanks) + bcall(_setxxop1 + ld hl,54*256+40 + ld (pencol),hl + ld a,1 + bcall(_dispop1a) + ld a,(lives) + bcall(_setxxop1) + ld hl,54*256+67 + ld (pencol),hl + ld a,1 + bcall(_dispop1a) + call xor_8x8 +ss_loop: + bcall(_getcsc) + cp kUp + jp z,ss_up + cp kDown + jp z,ss_down + cp kLeft + jp z,ss_horz + cp kRight + jp z,ss_horz + cp k2nd + jp z,ss_tank + cp kMode + jp z,ss_mode + cp kYEq + jr nz,ss_loop + ld hl,wpp + ld a,(lvldone) + ld b,a + ld a,(x2) + or a + ld a,(y2) + jr z,wpn_left +wpn_left: + ld c,0 + ld hl,wpp + cp 1 + jp z,ss_exit + cp 11 + jr z,ss_an +ss_an: + bit 0,b + jr z,ss_loop + ld c,1 + ld a,(enan) + ld (enwpn),a + ld hl,wpan + jp ss_exit +ss_exit: + ld a,c + ld (wpn),a + ld (wpnspr),hl + bcall(_clrlcdfull) + bcall(_grbufclr) + call ShowLevel + pop af + call tiny_delay + ret +ss_mode: + pop bc ; pop to something other than af so it remembers that MODE has been pressed + ret +ss_up: + ld a,(y2) + cp 1 + jr z,ss_vert + sub 10 + ld (y2),a + jp ss_loop_new +ss_down: + ld a,(y2) + cp 41 + jr z,ss_vert + add a,10 + ld (y2),a + jp ss_loop_new +ss_vert: + ld b,a + ld a,42 + sub b + ld (y2),a + jp ss_loop_new +ss_horz: + ld a,(x2) + ld b,a + ld a,47 + sub b + ld (x2),a + jp ss_loop_new +ss_tank: + ld a,(tanks) + or a + jp z,ss_loop_new + dec a + ld (tanks),a + ld a,28 + ld (enp),a + jp ss_loop_new +xor_char: + ld ix,(curpic) + ld b,12 + ld a,(ymm) + ld l,a + ld a,(xmm) + call isprite + ret +check_spr: + ld a,(dir) + or a + jr z,check_spr_l +check_spr_r: + ld hl,mmhr + ld a,(hchk) + or a + jr nz,found_spr + ld a,(ymm) + add a,12 + call CheckVert + jr c,not_jump_r + ld hl,mmjr + ld a,(jchk) + or a + jr nz,found_spr +not_jump_r: + ld hl,mmwr + ld a,(wchk) + or a + jr nz,found_spr + ret +check_spr_l: + ld hl,mmhl + ld a,(hchk) + or a + jr nz,found_spr + ld a,(ymm) + add a,12 + call CheckVert + jr c,not_jump_l + ld hl,mmjl + ld a,(jchk) + or a + jr nz,found_spr +not_jump_l: + ld hl,mmwl + ld a,(wchk) + or a + jr nz,found_spr + ret +found_spr: + ld (curpic),hl + ret +find_spr: + ld a,(dir) + or a + jr z,left_spr + ld hl,mmsr + jr found_spr +left_spr: + ld hl,mmsl + jr found_spr +t_move: + call xor_en + call check_en_dist + ld a,(edir) + or a + ld a,(xe) + jr z,t_move_l + inc a + jr t_move_r +t_move_l: + dec a + or a + call z,en_dead +t_move_r: + ld (xe),a + ret +i_move: + call xor_en + call check_en_dist + ld a,(edir) + or a + ld a,(ye) + jr z,i_move_u + inc a + jr i_move_d +i_move_u: + dec a +i_move_d: + ld (ye),a + ret +ti_check: + ld a,(echk) + cp 3 + jr nz,tic_cont + xor a + ld (echk),a +tic_cont: + ld b,46 + ld c,46 + ld a,(bcount) + inc a + ld (bcount),a + cp 50 + ld a,7 + call z,item_init + ld a,(bcount) + cp 100 + ld a,9 + call z,item_init + ld a,(bcount) + cp 100 + ret nz + xor a + ld (bcount),a + ret +ship_check: + ld a,(bcount) + inc a + cp 50 + jr z,ship_laser + ld (bcount),a + ret +ship_laser: + xor a + ld (bcount),a + ld b,1 + call xor_laser + call short_delay + ld a,(ymm) + cp 38 + call c,die + ld b,0 + call xor_laser + ret +boss_move: + call xor_boss + ld a,(boss) + or a + jr z,ti_check + cp 5 + jr z,ship_check + ld a,(bjchk) + or a + jr nz,boss_jump + ld a,(bdir) + or a + ld a,(xb) + jr z,boss_move_l + inc a + cp 76 + jr boss_move_r +boss_move_l: + dec a + cp 56 +boss_move_r: + ld (xb),a + jr z,boss_turn + cp 66 + call z,boss_shoot + ret +boss_turn: + ld a,(bdir) ; reverse dir boss is moving + ld b,a + ld a,1 + sub b + ld (bdir),a + ld a,2 + ld (bjchk),a + ret +boss_jump: + ld a,(bjchk) + cp 1 + ld a,(yb) + jr z,boss_fall + dec a + cp 34 + jr boss_jump_end +boss_fall: + inc a + cp 44 +boss_jump_end: + ld (yb),a + ret nz + ld a,(bjchk) + dec a + ld (bjchk),a + ret +bshoot_sa: + ld a,(xb) + ld (bwx),a + call xor_boss + ret +bshoot_fr: + ld a,(xb) + ld (bwx),a + ret +bshoot_md: + ld a,(xb) + sub 8 + ld (bwx),a + ret +boss_shoot: + ld a,(bwchk) + or a + ret nz + inc a + ld (bwchk),a + ld a,(boss) + cp 3 + jr z,bshoot_md + cp 7 + jr z,bshoot_fr + cp 8 + jr z,bshoot_sa + ld a,58 + ld (bwx),a + ret +bssa_move: + call xor_boss + call check_spr + call xor_char +bssam_loop: + ld a,(bwx) + dec a + ld (bwx),a + call xor_bwpn + call fastcopys + call xor_bwpn + ld a,(bwx) + or a + jr nz,bssam_loop +bssam_loop2: + ld a,(bwx) + inc a + ld (bwx),a + call xor_bwpn + call fastcopys + call xor_bwpn + ld a,(bwx) + cp 88 + jr nz,bssam_loop2 +bssam_loop3: + ld a,(bwx) + dec a + ld (bwx),a + call xor_bwpn + call fastcopys + call xor_bwpn + ld a,(xb) + ld b,a + ld a,(bwx) + cp b + jr nz,bssam_loop3 + xor a + ld (bwchk),a + call check_spr + call xor_char + ld a,(ymm) + ld b,a + ld a,37 + cp b + ret nc + add a,16 + cp b + ret c + ld a,(enp) + or a + sub 4 + jr nc,bssam_nokill + xor a +bssam_nokill: + ld (enp),a + ret +bsfr_move: + call check_spr + call xor_char + call xor_boss + call xor_bwpn + call fastcopys + call xor_char + call xor_boss + call xor_bwpn + call short_delay + xor a + ld (bwchk),a + ld a,(xmm) + ld b,a + ld a,55 + cp b + ret nc + add a,22 + cp b + ret c + ld a,(ymm) + ld b,a + ld a,33 + cp b + ret nc + add a,11 + cp b + ret c + ld a,(enp) + or a + sub 4 + jr nc,bsfrm_nokill + xor a +bsfrm_nokill: + ld (enp),a + ret +bsmd_move: + call short_delay + ld a,(enp) + or a + sub 4 + jr nc,bsmdm_nokill + xor a +bsmdm_nokill: + ld (enp),a + xor a + ld (bwchk),a + ret +boss_shot_move: + call xor_bwpn + ld a,(boss) + cp 8 + jp z,bssa_move + cp 3 + jr z,bsmd_move + cp 7 + jr z,bsfr_move + ld a,(xb) + ld b,a + ld a,(bwx) + cp 2 + jr c,bsm_end + cp b + jr z,bsm_end + ld b,a +bsm_cont: + dec b + dec b + ld a,(boss) + cp 4 + jr nz,bsm_store + dec b +bsm_store: + ld a,b + ld (bwx),a + ret +bsm_end: + xor a ; boss's shot is off the screen + ld (bwchk),a + ret +check_en_dist: ; check distance enemy has moved and turn around if necessary + ld a,(ecount) + inc a + cp 33 + call z,en_turn + ld (ecount),a + ret +en_turn: + ld hl,(enspr) + ld de,8 + ld a,(edir) + or a + jr z,en_turn_r + sbc hl,de + dec a + jr en_turn_l +en_turn_r: + add hl,de + inc a +en_turn_l: + ld (enspr),hl + ld (edir),a + xor a + ret +en_dead: ; enemy is now dead + ld a,3 + ld (echk),a + xor a + ld (ecount),a + ret +next_en: ; check if previous enemy is clear so next enemy can initialize + ld a,(ecount2) + inc a + ld (ecount2),a + cp 96 + ret nz + xor a + ld (echk),a + ret +xor_en: + ld ix,(enspr) + ld b,8 + ld a,(ye) + ld l,a + ld a,(xe) + call isprite + ret +xor_corners: + ld hl,uprlft + ld (smlspr),hl + call xor_8x8 + ld a,(x2) + add a,22 + ld (x2),a + ld hl,uprrt + ld (smlspr),hl + call xor_8x8 + ld a,(y2) + add a,12 + ld (y2),a + ld hl,lwrrt + ld (smlspr),hl + call xor_8x8 + ld a,(x2) + sub 22 + ld (x2),a + ld hl,lwrlft + ld (smlspr),hl + call xor_8x8 + ld a,(y2) + sub 12 + ld (y2),a + ret +xor_8x8: + ld ix,(smlspr) + ld b,8 + ld a,(y2) + ld l,a + ld a,(x2) + call isprite + ret +xor_wpn1: + ld ix,(wpnspr) + ld b,4 + ld a,(wy1) + ld l,a + ld a,(wx1) + call isprite + ret +xor_wpn2: + ld ix,(wpnspr) + ld b,4 + ld a,(wy2) + ld l,a + ld a,(wx2) + call isprite + ret +xor_wpn3: + ld b,4 + ld a,(wy3) + ld l,a + ld a,(wx3) + ld ix,(wpnspr) + call isprite + ret +xor_bwpn: + ld b,4 + ld l,49 + ld a,(bwx) + ld ix,(bwspr) + call isprite + ret +tiny_delay: + ld b,20 + jr del_loop +short_delay: + ld b,50 + jr del_loop +long_delay: + call delay +med_delay: + ld b,100 + jr del_loop +delay: + ld b,200 +del_loop: + halt + halt + djnz del_loop + ret +disp_boss: + ld (bosspic),hl + bcall(_clrlcdfull) + bcall(_grbufclr) + xor a + ld (x2),a + ld a,16 + ld (y2),a + ld hl,bint + ld (smlspr),hl + ld b,12 +draw1: + push bc + call xor_8x8 + ld a,(x2) + add a,8 + ld (x2),a + pop bc + djnz draw1 + ld a,0 + ld (x2),a + ld a,40 + ld (y2),a + ld hl,bint + ld (smlspr),hl + ld b,12 +draw2: + push bc + call xor_8x8 + ld a,(x2) + add a,8 + ld (x2),a + pop bc + djnz draw2 + call xor_boss + call fastcopys + ret +xor_boss: + ld b,12 + ld a,(yb) + ld l,a + ld a,(xb) + ld ix,(bosspic) + call isprite + ret +no_an: + call draw + ret +no_ma: + call draw + ret +no_md: + call draw + ret +no_do: + call draw + ret +no_po: + call draw + ret +no_fr: + call draw + ret +no_sa: + call draw + ret +no_mi: + call draw + ret +you_got: + bcall(_clrlcdfull) + bcall(_grbufclr) + call draw + ld hl,35 + ld (xmm),hl + ld a,50 + ld (ymm),a + ld hl,mmsr + ld (curpic),hl + ld a,43 + ld (wx1),a + ld a,51 + ld (wy1),a + ld a,(boss) + cp 3 + jr z,yg_cont + ld hl,mmwr + ld (curpic),hl + ld a,55 + ld (wx1),a + ld a,55 + ld (wy1),a + ld a,(boss) + ld hl,wpan + cp 1 + jr z,yg_cont + cp 2 + jr z,yg_cont + cp 4 + jr z,yg_cont + cp 6 + jr z,yg_cont + cp 9 + jr z,yg_cont + push af + ld a,35 + ld (wx1),a + ld a,54 + ld (wy1),a + call xor_char + pop af + cp 8 + jr z,yg_cont + ld (wpnspr),hl + call xor_wpn1 + ld a,50 + ld (wy1),a + call xor_wpn1 + ld a,58 + ld (wy1),a +yg_cont: + ld (wpnspr),hl + call xor_wpn1 + call xor_char + call fastcopys + ld de,10*256+35 + ld hl,yougot + call setvputs + ld a,(boss) + cp 1 + jr z,yg_an + jr yg_mi +yg_an: + ld de,20*256+28 + ld hl,ygan + call setvputs + jr yg_loop +yg_mi: + ld de,20*256+28 + ld hl,ygmi + call setvputs +yg_loop: + bcall(_getcsc) + cp kMode + ret z + cp kEnter + jr nz,yg_loop + ret +draw_grid: + bcall(_clrlcdfull) + bcall(_grbufclr) + call draw + ret +pw_text: + ld de,1*256+31 + ld hl,one + call setvputs + ld de,1*256+40 + ld hl,two + call setvputs + ld de,1*256+49 + ld hl,three + call setvputs + ld de,1*256+58 + ld hl,four + call setvputs + ld de,1*256+67 + ld hl,five + call setvputs + ld de,10*256+23 + ld hl,a + call setvputs + ld de,19*256+23 + ld hl,b + call setvputs + ld de,28*256+23 + ld hl,c + call setvputs + ld de,37*256+23 + ld hl,d + call setvputs + ld de,46*256+23 + ld hl,e + call setvputs + ld de,56*256+32 + ld hl,enter + call setvputs + ret +show_pw: + call draw_grid + ld a,(tanks) ; find correct password dot for number of tanks + ld b,1 + or a + jr z,spw_cont + ld b,2 + cp 1 + jr z,spw_cont + ld b,4 + cp 2 + jr z,spw_cont + ld b,8 + cp 3 + jr z,spw_cont + ld b,16 +spw_cont: + ld a,b + ld (tankpw),a + ld hl,pwdot + ld (smlspr),hl + ld a,(lvldone) + bit 0,a + jr z,no_an_dot + ld a,28 + jr an_dot +no_an_dot: + ld a,46 +an_dot: + ld (y2),a + ld a,47 + ld (x2),a + call xor_8x8 + ld a,(lvldone) + bit 1,a + jr z,no_ma_dot + ld a,10 + ld (y2),a + ld a,38 + jr ma_dot +no_ma_dot: + ld a,28 + ld (y2),a + ld a,65 +ma_dot: + ld (x2),a + call xor_8x8 + ld a,(lvldone) + bit 2,a + jr z,no_md_dot + ld a,46 + ld (y2),a + ld a,65 + jr md_dot +no_md_dot: + ld a,10 + ld (y2),a + ld a,56 +md_dot: + ld (x2),a + call xor_8x8 + ld a,(lvldone) + bit 3,a + jr z,no_do_dot + ld a,37 + ld (y2),a + ld a,47 + jr do_dot +no_do_dot: + ld a,46 + ld (y2),a + ld a,38 +do_dot: + ld (x2),a + call xor_8x8 + ld a,(lvldone) + bit 4,a + jr z,no_po_dot + ld a,37 + ld (y2),a + ld a,29 + jr po_dot +no_po_dot: + ld a,19 + ld (y2),a + ld a,65 +po_dot: + ld (x2),a + call xor_8x8 + ld a,(lvldone) + bit 5,a + jr z,no_fr_dot + ld a,56 + jr fr_dot +no_fr_dot: + ld a,29 +fr_dot: + ld (x2),a + ld a,19 + ld (y2),a + call xor_8x8 + ld a,(lvldone) + bit 5,a + jr z,no_se_dot + ld a,46 + ld (y2),a + ld a,29 + jr se_dot +no_se_dot: + ld a,28 + ld (y2),a + ld a,56 +se_dot: + ld (x2),a + call xor_8x8 + ld a,(lvldone) + bit 6,a + jr z,no_sa_dot + ld a,38 + jr sa_dot +no_sa_dot: + ld a,47 +sa_dot: + ld (x2),a + ld a,19 + ld (y2),a + call xor_8x8 + ld a,(lvldone) + bit 7,a + jr z,no_mi_dot + ld a,28 + ld (y2),a + ld a,38 + jr mi_dot +no_mi_dot: + ld a,37 + ld (y2),a + ld a,56 +mi_dot: + ld (x2),a + call xor_8x8 +tank_dot: + ld a,(tankpw) + ld b,a + ld a,10 + ld (y2),a + ld a,47 + ld (x2),a + bit 0,b + jr nz,tank_dot_found + ld a,46 + ld (y2),a + ld a,56 + ld (x2),a + bit 1,b + jr nz,tank_dot_found + ld a,28 + ld (y2),a + ld a,29 + ld (x2),a + bit 2,b + jr nz,tank_dot_found + ld a,10 + ld (y2),a + ld a,65 + ld (x2),a + bit 3,b + jr nz,tank_dot_found + ld a,37 + ld (y2),a + ld a,38 + ld (x2),a +tank_dot_found: + call xor_8x8 + call fastcopys + call pw_text +show_pw_loop: + bcall(_getcsc) + cp kMode + ret z + cp kEnter + jr nz,show_pw_loop + ret +ship_init: + ld a,(bchk) + or a + ret z + call draw + ld a,56 + ld (xb),a + ld a,16 + ld (yb),a + ret +xor_laser: + call draw + call check_spr + call xor_char + call xor_boss + call fastcopys + call draw + call xor_char + call xor_boss + ret +wpn_fill: + ld a,28 + ld (enp),a + ld (enan),a + ld (enma),a + ld (enmd),a + ld (endo),a + ld (ense),a + ld (enpo),a + ld (enfr),a + ld (ensa),a + ld (enmi),a + ld (enwpn),a + ret +draw_nrg: + ld a,(bchk) + or a + call nz,draw_bnrg + ld d,10 + ld e,38 + ld h,10 + ld a,(enp) + ld b,a + ld a,37 + sub b + ld l,a + ld a,1 + call fastline + ld a,(wpn) + or a + ret z + ld d,12 ; draw weapon energy line + ld e,38 + ld h,12 + ld a,(enwpn) + ld b,a + ld a,37 + sub b + ld l,a + ld a,1 + call fastline + ret +draw_bnrg: + ld d,86 + ld e,38 + ld h,86 + ld a,(enb) + ld b,a + ld a,37 + sub b + ld l,a + ld a,1 + call fastline + ret +check_ehitu: + ld a,(echk) + cp 3 + ret z + ld a,(wx1) + ld d,a + ld a,(wy1) + ld e,a + ld a,(wchk1) + ld c,a + or a + call nz,check_white + ld a,c + ld (wchk1),a + ld a,(wx2) + ld d,a + ld a,(wy2) + ld e,a + ld a,(wchk2) + ld c,a + or a + call nz,check_white + ld a,c + ld (wchk2),a + ld a,(wx3) + ld d,a + ld a,(wy3) + ld e,a + ld a,(wchk3) + ld c,a + or a + call nz,check_white + ld a,c + ld (wchk3),a + ld a,(xmm) + ld b,a + ld a,(xe) + sub 7 + cp b + ret nc + add a,14 + cp b + ret c + ld a,(ymm) + ld b,a + ld a,(ye) + sub 11 + cp b + ret nc + add a,18 + cp b + ret c + ld a,(enp) + or a + sub 1 + jr nc,ehitu_nokill + xor a +ehitu_nokill: + ld (enp),a + ld a,1 + ld (hchk),a + ret +check_bhitu: + ld a,(wx1) + ld d,a + ld a,(wy1) + ld e,a + ld a,(wchk1) + ld c,a + or a + call nz,check_whitb + ld a,c + ld (wchk1),a + ld a,(wx2) + ld d,a + ld a,(wy2) + ld e,a + ld a,(wchk2) + ld c,a + or a + call nz,check_whitb + ld a,c + ld (wchk2),a + ld a,(wx3) + ld d,a + ld a,(wy3) + ld e,a + ld a,(wchk3) + ld c,a + or a + call nz,check_whitb + ld a,c + ld (wchk3),a + ld a,(boss) + cp 5 + jr z,check_shitu + ld a,(xmm) + ld b,a + ld a,(xb) + sub 7 + cp b + ret nc + add a,14 + cp b + ret c + ld a,(ymm) + ld b,a + ld a,(yb) + sub 11 + cp b + ret nc + add a,22 + cp b + ret c + ld a,(enp) + or a + sub 7 + jr nc,bhitu_nokill + xor a +bhitu_nokill: + ld (enp),a + ld a,1 + ld (hchk),a + ret +check_shitu: + ld a,(xmm) + ld b,a + ld a,28 + cp b + ret nc + xor a + ld (enp),a + ld a,1 + ld (hchk),a + ret +check_bwhitu: + ld a,(xmm) + ld b,a + ld a,(bwx) + sub 7 + cp b + ret nc + add a,14 + cp b + ret c + ld a,(ymm) + ld b,a + ld a,38 + cp b + ret nc + add a,14 + cp b + ret c + ld a,(enp) + or a + sub 2 + jr nc,bwhitu_nokill + xor a +bwhitu_nokill: + ld (enp),a + ld a,1 + ld (hchk),a + ret +check_white: + ld a,(xe) + ld b,a + ld a,d + sub 7 + cp b + ret nc + add a,14 + cp b + ret c + ld a,(ye) + ld b,a + ld a,e + sub 7 + cp b + ret nc + add a,10 + cp b + ret c + ld c,0 + ld a,(ene) + or a + sub 1 + jr nc,white_nokill + xor a +white_nokill: + ld (ene),a + or a + ret nz + ld a,3 + ld (echk),a + ret +check_whitb: + ld a,(boss) + cp 5 + jr z,check_whits + ld a,(xb) + ld b,a + ld a,d + sub 7 + cp b + ret nc + add a,14 + cp b + ret c + ld a,(yb) + ld b,a + ld a,e + sub 11 + cp b + ret nc + add a,14 + cp b + ret c + ld c,0 + call dam7_or1 + ld a,(enb) + or a + sub 1 + jr nc,whitb_nokill + xor a +whitb_nokill: + ld (enb),a + ret nz + call won + ret +check_whits: + ld b,35 + ld a,d + sub 7 + cp b + ret nc + add a,14 + cp b + ret c + ld b,42 + ld a,e + cp b + ret nc + add a,10 + cp b + ret c + ld c,0 + call dam7_or1 + ld a,(enb) + or a + sub 1 + jr nc,whits_nokill + xor a +whits_nokill: + ld (enb),a + or a + ret nz + call won + ret +dam7_or1: + ld a,(wpn) + or a + ret z + cp 5 + ret z + cp 1 + jr z,an_on_po + cp 2 + jr z,ma_on_do + cp 3 + jr z,md_on_sa + cp 4 + jr z,do_on_fr + cp 6 + jr z,po_on_md + cp 7 + jr z,fr_on_an + cp 8 + jr z,sa_on_mi + cp 9 + jr z,mi_on_ma + ret +an_on_po: + ld a,(boss) + cp 5 + jr z,damage7 + cp 6 + ret nz + jr damage7 +ma_on_do: + ld a,(boss) + or a + jr z,damage7 + cp 4 + ret nz + jr damage7 +md_on_sa: + ld a,(boss) + cp 8 + ret nz + jr damage7 +do_on_fr: + ld a,(boss) + cp 7 + ret nz + jr damage7 +po_on_md: + ld a,(boss) + cp 3 + ret nz + jr damage7 +fr_on_an: + ld a,(boss) + cp 1 + ret nz + jr damage7 +sa_on_mi: + ld a,(boss) + cp 9 + ret nz + jr damage7 +mi_on_ma: + ld a,(boss) + cp 2 + ret nz +damage7: + ld a,(enb) + or a + sub 6 + jr nc,dam7_nokill + call won + xor a +dam7_nokill: + ld (enb),a + ret +thrown_back: + xor a + ld (hchk),a + ld hl,(xmm) + call load_x + or a + ld de,8 + sbc hl,de + push hl + push hl + call CheckHorz + pop hl + call z,CheckHorz2 + pop hl + call z,CheckHorz3 + jr nz,thrown_short + ld a,(xmm) + sub 8 + ld (xmm),a + ret +thrown_short: + ld hl,(xmm) + call load_x + push hl + srl h + rr l + srl h + rr l + srl h + rr l ; divide x by 8 + add hl,hl + add hl,hl + add hl,hl ; multiply x by 8 + push hl + pop de + pop hl + or a + sbc hl,de + push hl + ld hl,(xmm) + pop de + or a + sbc hl,de + ld (xmm),hl + ret +draw_text: +;hl = pointer to text info + ld a,(hl) + ld e,a + inc hl + push de + push hl + bcall(_clrlcdfull) + bcall(_grbufclr) + ld a,1 + pop hl + pop de +draw_txt_loop: + push af + push de + push hl + call centertext + pop hl + pop de + pop af + add a,6 + cp 59 + call nc,reseta_draw + push af + ld a,$00 + ld b,255 + cpir + pop af + dec e + jr nz,draw_txt_loop + call clrwait + ret + +reseta_draw: + push af + push de + push hl + dec e + call nz,clrwait + inc e + pop hl + pop de + pop af + ld a,1 + ret +clrwait: + call wait + bcall(_clrlcdfull) + bcall(_grbufclr) + ret +wait: + bcall(_getcsc) + or a + jr z,wait + ret +quit_force: + +draw: + push af + push bc + push de + ld a,(hl) + ld b,a + inc hl + ld a,(hl) + inc hl +drawloop: +;hl = pointer to line data +;a = draw mode (0=off 1=on 2=xor 3=pattern) +;b = number of lines + push bc + ld c,(hl) + inc hl + ld b,(hl) + inc hl + ld e,(hl) + inc hl + ld d,(hl) + inc hl + push af + push hl + push bc + push de + pop de + pop hl + call fastline + pop hl + pop af + pop bc + djnz drawloop + pop de + pop bc + pop af + ret \ No newline at end of file diff --git a/service.asm b/service.asm deleted file mode 100644 index ca0a713..0000000 --- a/service.asm +++ /dev/null @@ -1,1094 +0,0 @@ -;=============================================================================== -;Copyright (C) Andrzej Adamczyk (at https://blackdev.org/). All rights reserved. -;=============================================================================== - -; information for linker -section .rodata - -; align routine -align 0x08, db 0x00 -kernel_service_list: - dq kernel_service_exit - dq kernel_service_framebuffer - dq kernel_service_memory_alloc - dq kernel_service_memory_release - dq kernel_service_task_pid - dq kernel_service_driver_mouse - dq kernel_service_storage_read - dq kernel_service_exec - dq kernel_service_ipc_send - dq kernel_service_ipc_receive - dq kernel_service_memory_share - dq driver_ps2_keyboard_key_read - dq kernel_service_task_status - dq kernel_stream_out - dq kernel_stream_in - dq kernel_service_serial_char - dq kernel_service_serial_string - dq kernel_service_serial_value - dq driver_rtc_time - dq kernel_stream_set - dq kernel_stream_get - dq kernel_service_sleep - dq kernel_service_uptime - dq kernel_stream_out_value - dq kernel_service_task - dq kernel_service_memory - dq kernel_service_thread -kernel_service_list_end: - -; information for linker -section .text - -;------------------------------------------------------------------------------- -; in: -; rdi - pointer to Memory descriptor -kernel_service_memory: - ; preserve original registers - push rax - push r8 - - ; kernel environment variables/rountines base addrrax - mov r8, qword [kernel_environment_base_address] - - ; return information about - - ; all available pages - mov rax, qword [r8 + KERNEL_STRUCTURE.page_total] - mov qword [rdi + LIB_SYS_STRUCTURE_MEMORY.total], rax - - ; and currently free - mov rax, qword [r8 + KERNEL_STRUCTURE.page_available] - mov qword [rdi + LIB_SYS_STRUCTURE_MEMORY.available], rax - - ; restore original registers - pop r8 - pop rax - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; void -kernel_service_exit: - ; retrieve pointer to current task descriptor - call kernel_task_active - - ; mark task as closed and not active - or word [r9 + KERNEL_TASK_STRUCTURE.flags], KERNEL_TASK_FLAG_closed - and word [r9 + KERNEL_TASK_STRUCTURE.flags], ~KERNEL_TASK_FLAG_active - - ; release rest of AP time - int 0x20 - -;------------------------------------------------------------------------------- -; in: -; rdi - sleep amount in microtime -kernel_service_sleep: - ; preserve original registers - push rdi - push r8 - push r9 - - ; kernel environment variables/rountines base address - mov r8, qword [kernel_environment_base_address] - - ; retrieve pointer to current task descriptor - call kernel_task_active - - ; current uptime - add rdi, qword [r8 + KERNEL_STRUCTURE.hpet_microtime] - - ; go to sleep for N ticks - mov qword [r9 + KERNEL_TASK_STRUCTURE.sleep], rdi - - ; release the remaining CPU time - int 0x20 - - ; restore original registers - pop r9 - pop r8 - pop rdi - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; out: -; rax - current uptime in microtime -kernel_service_uptime: - ; return current microtime index - mov rax, qword [kernel_environment_base_address] - mov rax, qword [rax + KERNEL_STRUCTURE.hpet_microtime] - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; in: -; rdi - pointer to mouse descriptor -kernel_service_driver_mouse: - ; preserve original registers - push rax - push r8 - - ; kernel environment variables/rountines base address - mov r8, qword [kernel_environment_base_address] - - ; share information about mouse location and status - mov ax, word [r8 + KERNEL_STRUCTURE.driver_ps2_mouse_x] - mov word [rdi + LIB_SYS_STRUCTURE_MOUSE.x], ax - mov ax, word [r8 + KERNEL_STRUCTURE.driver_ps2_mouse_y] - mov word [rdi + LIB_SYS_STRUCTURE_MOUSE.y], ax - mov al, byte [r8 + KERNEL_STRUCTURE.driver_ps2_mouse_status] - mov byte [rdi + LIB_SYS_STRUCTURE_MOUSE.status], al - - ; restore original registers - pop r8 - pop rax - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; in: -; rdx - stream flags -; rsi - length of file name/path -; rdi - pointer to file name/path -; out: -; rax - process ID -kernel_service_exec: - ; preserve original registers - push rcx - push rsi - push rdi - push rbp - push r8 - - ; kernel environment variables/rountines base address - mov r8, qword [kernel_environment_base_address] - - ; reorganize registers - mov rcx, rsi ; length of string - mov rsi, rdx ; pointer to string - xchg rsi, rdi ; stream flags - - ; execute file from path - call kernel_exec - - ; restore original registers - pop r8 - pop rbp - pop rdi - pop rsi - pop rcx - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; in: -; rdi - pointer to framebuffer descriptor -kernel_service_framebuffer: - ; preserve original registers - push rax - push rcx - push rdx - push rsi - push r8 - push r9 - push r11 - - ; kernel environment variables/rountines base address - mov r8, qword [kernel_environment_base_address] - - ; return properties of framebuffer - - ; width in pixels - mov ax, word [r8 + KERNEL_STRUCTURE.framebuffer_width_pixel] - mov word [rdi + LIB_SYS_STRUCTURE_FRAMEBUFFER.width_pixel], ax - - ; height in pixels - mov ax, word [r8 + KERNEL_STRUCTURE.framebuffer_height_pixel] - mov word [rdi + LIB_SYS_STRUCTURE_FRAMEBUFFER.height_pixel], ax - - ; scanline in Bytes - mov eax, dword [r8 + KERNEL_STRUCTURE.framebuffer_scanline_byte] - mov dword [rdi + LIB_SYS_STRUCTURE_FRAMEBUFFER.scanline_byte], eax - - ; framebuffer manager - mov rax, qword [r8 + KERNEL_STRUCTURE.framebuffer_pid] - - ; framebuffer manager exist? - test rax, rax - jnz .return ; yes - - ; retrieve pointer to current task descriptor - call kernel_task_active - - ; calculate size of framebuffer space - mov eax, dword [rdi + LIB_SYS_STRUCTURE_FRAMEBUFFER.scanline_byte] - movzx ecx, word [rdi + LIB_SYS_STRUCTURE_FRAMEBUFFER.height_pixel] - mul rcx - - ; convert to pages - add rax, ~STATIC_PAGE_mask - shr rax, STATIC_PAGE_SIZE_shift - - ; share framebuffer memory space with process - xor ecx, ecx ; no framebuffer manager, if error on below function - xchg rcx, rax ; length of shared space in pages - mov rsi, qword [r8 + KERNEL_STRUCTURE.framebuffer_base_address] - mov r11, qword [r9 + KERNEL_TASK_STRUCTURE.cr3] - call kernel_memory_share - jc .return ; no enough memory? - - ; return pointer to shared memory of framebuffer - mov qword [rdi + LIB_SYS_STRUCTURE_FRAMEBUFFER.base_address], rax - - ; new framebuffer manager - mov rax, qword [r9 + KERNEL_TASK_STRUCTURE.pid] - mov qword [r8 + KERNEL_STRUCTURE.framebuffer_pid], rax - -.return: - ; inform about framebuffer manager - mov qword [rdi + LIB_SYS_STRUCTURE_FRAMEBUFFER.pid], rax - - ; restore original registers - pop r11 - pop r9 - pop r8 - pop rsi - pop rdx - pop rcx - pop rax - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; in: -; rdi - ID of target process -; rsi - pointer to message data -kernel_service_ipc_send: - ; preserve original registers - push rax - push rcx - push rsi - push rdi - push r8 - - ; kernel environment variables/rountines base address - mov r8, qword [kernel_environment_base_address] - -.lock: - ; request an exclusive access - mov cl, LOCK - lock xchg byte [r8 + KERNEL_STRUCTURE.ipc_semaphore], cl - - ; assigned? - test cl, cl - jnz .lock ; no - -.restart: - ; amount of entries - mov rcx, KERNEL_IPC_limit - - ; set pointer to first message - mov rdx, qword [r8 + KERNEL_STRUCTURE.ipc_base_address] - -.loop: - ; free entry? - mov rax, qword [r8 + KERNEL_STRUCTURE.hpet_microtime] - cmp qword [rdx + LIB_SYS_STRUCTURE_IPC.ttl], rax - jbe .found ; yes - - ; next entry from list - add rdx, LIB_SYS_STRUCTURE_IPC.SIZE - - ; end of message list? - dec rcx - jz .restart ; yes - - ; no - jmp .loop - -.found: - ; set message time out - add rax, KERNEL_IPC_timeout - mov qword [rdx + LIB_SYS_STRUCTURE_IPC.ttl], rax - - ; set message source - call kernel_task_pid - mov qword [rdx + LIB_SYS_STRUCTURE_IPC.source], rax - - ; set message target - mov qword [rdx + LIB_SYS_STRUCTURE_IPC.target], rdi - - ; load data into message - mov ecx, LIB_SYS_IPC_DATA_size_byte - mov rdi, rdx - add rdi, LIB_SYS_STRUCTURE_IPC.data - rep movsb - -.end: - ; release access - mov byte [r8 + KERNEL_STRUCTURE.ipc_semaphore], UNLOCK - - ; restore original registers - pop r8 - pop rdi - pop rsi - pop rcx - pop rax - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; in: -; rdi - pointer to message descriptor -; sil - message type -; out: -; TRUE if message retrieved -kernel_service_ipc_receive: - ; preserve original registers - push rbx - push rcx - push rdi - push r8 - push rsi - - ; kernel environment variables/rountines base address - mov r8, qword [kernel_environment_base_address] - -.lock: - ; request an exclusive access - mov cl, LOCK - lock xchg byte [r8 + KERNEL_STRUCTURE.ipc_semaphore], cl - - ; assigned? - test cl, cl - jnz .lock ; no - - ; retrieve ID of current process - call kernel_task_pid - - ; amount of entries - mov rcx, KERNEL_IPC_limit - - ; set pointer to first message - mov rsi, qword [r8 + KERNEL_STRUCTURE.ipc_base_address] - -.loop: - ; message alive? - mov rbx, qword [r8 + KERNEL_STRUCTURE.hpet_microtime] - cmp qword [rsi + LIB_SYS_STRUCTURE_IPC.ttl], rbx - ja .check ; yes - -.next: - ; next entry from list? - add rsi, LIB_SYS_STRUCTURE_IPC.SIZE - dec rcx - jnz .loop ; yes - - ; no message for us - xor eax, eax - - ; no - jmp .end - -.check: - ; message type selected? - cmp byte [rsp], LIB_SYS_IPC_TYPE_ANY - je .any ; no - - ; requested message type? - mov bl, byte [rsp] - cmp bl, byte [rsi + LIB_SYS_STRUCTURE_IPC.data + LIB_SYS_STRUCTURE_IPC_DEFAULT.type] - jne .next ; no - -.any: - ; message for us? - cmp qword [rsi + LIB_SYS_STRUCTURE_IPC.target], rax - jne .next ; no - - ; preserve original register - push rsi - - ; load message to process descriptor - mov ecx, LIB_SYS_STRUCTURE_IPC.SIZE - rep movsb - - ; restore original register - pop rsi - - ; release entry - mov qword [rsi + LIB_SYS_STRUCTURE_IPC.ttl], EMPTY - - ; message transferred - mov eax, TRUE - -.end: - ; release access - mov byte [r8 + KERNEL_STRUCTURE.ipc_semaphore], UNLOCK - - ; restore original registers - pop rsi - pop r8 - pop rdi - pop rcx - pop rbx - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; in: -; rdi - length of space in Bytes -; out: -; rax - pointer to allocated space -; or EMPTY if no enough memory -kernel_service_memory_alloc: - ; preserve original registers - push rbx - push rcx - push rsi - push rdi - push r8 - push r9 - push r11 - - ; convert size to pages (align up to page boundaries) - add rdi, ~STATIC_PAGE_mask - shr rdi, STATIC_PAGE_SIZE_shift - - ; retrieve pointer to current task descriptor - call kernel_task_active - - ; set pointer of process paging array - mov r11, qword [r9 + KERNEL_TASK_STRUCTURE.cr3] - - ; aquire memory space from process memory map - mov r9, qword [r9 + KERNEL_TASK_STRUCTURE.memory_map] - mov rcx, rdi ; number of pages - call kernel_memory_acquire - jc .error ; no enough memory - - ; convert first page number to logical address - shl rdi, STATIC_PAGE_SIZE_shift - - ; assign pages to allocated memory in process space - mov rax, rdi - mov bx, KERNEL_PAGE_FLAG_present | KERNEL_PAGE_FLAG_write | KERNEL_PAGE_FLAG_user | KERNEL_PAGE_FLAG_process - call kernel_page_alloc - jnc .allocated ; space allocated - - ; take back modifications - mov rsi, rcx - call kernel_service_memory_release - -.error: - ; no enough memory - xor eax, eax - - ; end - jmp .end - -.allocated: - ; retrieve pointer to current task descriptor - call kernel_task_active - - ; process memory usage - add qword [r9 + KERNEL_TASK_STRUCTURE.page], rcx - -.end: - ; restore original registers - pop r11 - pop r9 - pop r8 - pop rdi - pop rsi - pop rcx - pop rbx - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; in: -; rdi - pointer to allocated space -; rsi - length of space in Bytes -kernel_service_memory_release: - ; preserve original registers - push rax - push rcx - push rsi - push rdi - push r9 - push r11 - - ; retrieve pointer to current task descriptor - call kernel_task_active - - ; convert bytes to pages - add rsi, ~STATIC_PAGE_mask - shr rsi, STATIC_PAGE_SIZE_shift - - ; pointer and counter at place - mov rcx, rsi - mov rsi, rdi - -.loop: - ; delete first physical page from logical address - mov r11, qword [r9 + KERNEL_TASK_STRUCTURE.cr3] - call kernel_page_remove - - ; page removed? - test rax, rax - jnz .release ; yes - - ; convert to page number - shr rsi, STATIC_PAGE_SIZE_shift - - ; continue - jmp .next - -.release: - ; release page inside kernels binary memory map - mov rdi, rax - or rdi, qword [kernel_page_mirror] - call kernel_memory_release_page - - ; release page inside process binary memory map - shr rsi, STATIC_PAGE_SIZE_shift - mov rdi, qword [r9 + KERNEL_TASK_STRUCTURE.memory_map] - bts qword [rdi], rsi - - ; process memory usage - dec qword [r9 + KERNEL_TASK_STRUCTURE.page] - -.next: - ; next page from space - inc rsi - shl rsi, STATIC_PAGE_SIZE_shift - - ; another page? - dec rcx - jnz .loop ; yes - - ; restore original registers - pop r11 - pop r9 - pop rdi - pop rsi - pop rcx - pop rax - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; in: -; rdi - pointer to source memory space -; rsi - length of space in Bytes -; rdx - target process ID -; out: -; rax - pointer to shared memory between processes -kernel_service_memory_share: - ; preserve original registers - push rbx - push rcx - push rsi - push rdi - push r9 - push r11 - - ; convert Bytes to pages - mov rcx, rsi - add rcx, ~STATIC_PAGE_mask - shr rcx, STATIC_PAGE_SIZE_shift - - ; retrieve task paging structure pointer - call kernel_task_by_id - mov r11, qword [rbx + KERNEL_TASK_STRUCTURE.cr3] - - ; set source pointer in place - mov rsi, rdi - - ; acquire memory space from target process - mov r9, qword [rbx + KERNEL_TASK_STRUCTURE.memory_map] - call kernel_memory_acquire - - ; convert page number to offset - shl rdi, STATIC_PAGE_SIZE_shift - - ; connect memory space of parent process with child - mov rax, rdi - mov bx, KERNEL_PAGE_FLAG_present | KERNEL_PAGE_FLAG_write | KERNEL_PAGE_FLAG_user | KERNEL_PAGE_FLAG_process | KERNEL_PAGE_FLAG_shared - call kernel_page_clang - - ; restore original registers - pop r11 - pop r9 - pop rdi - pop rsi - pop rcx - pop rbx - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; out: -; rax - PID of current task -kernel_service_task_pid: - ; preserve original registers - push r9 - - ; retrieve pointer to current task descriptor - call kernel_task_active - - ; set pointer of process paging array - mov rax, qword [r9 + KERNEL_TASK_STRUCTURE.pid] - - ; restore original registers - pop r9 - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; in: -; rdi - process ID -; out: -; ax - task status -kernel_service_task_status: - ; preserve original registers - push rbx - push rdx - - ; retrieve pointer to current task descriptor - mov rdx, rdi - call kernel_task_by_id - - ; by default not found - xor ax, ax - - ; not found? - test rbx, rbx - jz .error ; yep - - ; set pointer of process paging array - mov ax, word [rbx + KERNEL_TASK_STRUCTURE.flags] - -.error: - ; restore original registers - pop rdx - pop rbx - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; in: -; rdi - ASCII character -kernel_service_serial_char: - ; preserve original register - push rax - - ; send character to serial - mov al, dil - call driver_serial_char - - ; restore original register - pop rax - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; in: -; rdi - pointer to string -; rsi - length of string in Bytes -kernel_service_serial_string: - ; preserve original registers - push rcx - push rsi - - ; send string to serial - mov rcx, rsi - mov rsi, rdi - call driver_serial_string - - ; restore original registers - pop rsi - pop rcx - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; in: -; rdi - value -; sil - base -; rdx - prefix length -; cl - TRUE/FALSE signed value? -kernel_service_serial_value: - ; preserve original registers - push rax - push rbx - push rcx - push rdx - - ; send value to serial - mov rax, rdi - movzx ebx, sil - xchg rcx, rdx - call driver_serial_value - - ; restore original registers - pop rdx - pop rcx - pop rbx - pop rax - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; in: -; rdi - pointer to file descriptor -kernel_service_storage_read: - ; preserve original registers - push rax - push rbx - push rcx - push rsi - push rbp - push r8 - push r9 - push r11 - push rdi - - ; kernel environment variables/rountines base address - mov r8, qword [kernel_environment_base_address] - - ; prepare space for file descriptor - sub rsp, KERNEL_STORAGE_STRUCTURE_FILE.SIZE - mov rbp, rsp ; pointer of file descriptor - - ; get file properties - movzx eax, byte [r8 + KERNEL_STRUCTURE.storage_root_id] - movzx ecx, byte [rdi + LIB_SYS_STRUCTURE_STORAGE.length] - lea rsi, [rdi + LIB_SYS_STRUCTURE_STORAGE.name] - call kernel_storage_file - - ; file found? - cmp qword [rbp + KERNEL_STORAGE_STRUCTURE_FILE.id], EMPTY - je .end ; no - - ; prepare space for file content - mov rdi, qword [rbp + KERNEL_STORAGE_STRUCTURE_FILE.size_byte] - call kernel_service_memory_alloc - - ; no enough memory? - test rax, rax - jz .end ; yes - - ; load file content into prepared space - mov rsi, qword [rbp + KERNEL_STORAGE_STRUCTURE_FILE.id] - mov rdi, rax - movzx eax, byte [r8 + KERNEL_STRUCTURE.storage_root_id] - call kernel_storage_read - - ; retrieve current task pointer - call kernel_task_active - - ; restore file descriptor - mov rax, qword [rsp + KERNEL_STORAGE_STRUCTURE_FILE.SIZE] - - ; inform process about file location and size - push qword [rbp + KERNEL_STORAGE_STRUCTURE_FILE.size_byte] - pop qword [rax + LIB_SYS_STRUCTURE_STORAGE.size_byte] - mov qword [rax + LIB_SYS_STRUCTURE_STORAGE.address], rdi - -.end: - ; remove file descriptor from stack - add rsp, KERNEL_STORAGE_STRUCTURE_FILE.SIZE - - ; restore original registers - pop rdi - pop r11 - pop r9 - pop r8 - pop rbp - pop rsi - pop rcx - pop rbx - pop rax - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; out: -; rax - pointer to list of first task descriptor -kernel_service_task: - ; preserve original registers - push rbx - push rcx - push rdx - push rsi - push rdi - push r8 - push r10 - - ; kernel environment variables/rountines base address - mov r8, qword [kernel_environment_base_address] - -.lock: - ; request an exclusive access - mov al, LOCK - lock xchg byte [r8 + KERNEL_STRUCTURE.task_queue_semaphore], al - - ; assigned? - test al, al - jnz .lock ; no - - ; length of tasks descriptors in Bytes - mov eax, LIB_SYS_STRUCTURE_TASK.SIZE - mul qword [r8 + KERNEL_STRUCTURE.task_count] - - ; assign place for task descriptor list - mov rdi, rax - add rdi, STATIC_QWORD_SIZE_byte << STATIC_MULTIPLE_BY_2_shift - call kernel_service_memory_alloc - - ; store information about size of this space - add rdi, ~STATIC_PAGE_mask - shr rdi, STATIC_PAGE_SIZE_shift - mov qword [rax], rdi - - ; parse every entry - mov rbx, KERNEL_TASK_limit - mov r10, qword [r8 + KERNEL_STRUCTURE.task_queue_address] - - ; preserve memory space pointer of tasks descriptors - add rax, STATIC_QWORD_SIZE_byte << STATIC_MULTIPLE_BY_2_shift - push rax - -.loop: - ; entry exist? - cmp word [r10 + KERNEL_TASK_STRUCTURE.flags], EMPTY - je .next ; no - - ; do not pass kernel entry - cmp qword [r10 + KERNEL_TASK_STRUCTURE.pid], EMPTY - je .next - - ; share default information about task - - ; process ID - mov rdx, qword [r10 + KERNEL_TASK_STRUCTURE.pid] - mov qword [rax + LIB_SYS_STRUCTURE_TASK.pid], rdx - - ; process parents ID - mov rdx, qword [r10 + KERNEL_TASK_STRUCTURE.pid_parent] - mov qword [rax + LIB_SYS_STRUCTURE_TASK.pid_parent], rdx - - ; wake up process micotime - mov rdx, qword [r10 + KERNEL_TASK_STRUCTURE.sleep] - mov qword [rax + LIB_SYS_STRUCTURE_TASK.sleep], rdx - - ; amount of pages used by process - mov rdx, qword [r10 + KERNEL_TASK_STRUCTURE.page] - mov qword [rax + LIB_SYS_STRUCTURE_TASK.page], rdx - - ; current task status - mov dx, word [r10 + KERNEL_TASK_STRUCTURE.flags] - mov word [rax + LIB_SYS_STRUCTURE_TASK.flags], dx - - ; taks name length - movzx ecx, byte [r10 + KERNEL_TASK_STRUCTURE.length] - mov byte [rax + LIB_SYS_STRUCTURE_TASK.length], cl - - ; task name itself - lea rsi, [r10 + KERNEL_TASK_STRUCTURE.name] - lea rdi, [rax + LIB_SYS_STRUCTURE_TASK.name] - rep movsb - - ; next task descriptor position - add rax, LIB_SYS_STRUCTURE_TASK.SIZE - -.next: - ; move pointer to next entry of task table - add r10, KERNEL_TASK_STRUCTURE.SIZE - - ; end of tasks inside table? - dec rbx - jnz .loop ; no - - ; last entry set as empty - mov qword [rax + LIB_SYS_STRUCTURE_TASK.pid], EMPTY - - ; return memory pointer of tasks descriptors - pop rax - - ; release access - mov byte [r8 + KERNEL_STRUCTURE.task_queue_semaphore], UNLOCK - - ; restore original registers - pop r10 - pop r8 - pop rdi - pop rsi - pop rdx - pop rcx - pop rbx - - ; return from routine - ret - -;------------------------------------------------------------------------------- -; in: -; rdi - pointer to function of current task to execute as thread -; rsi - pointer to string as name of thread -; rdx - length of that string -;out: -; rax - process ID of thread -kernel_service_thread: - ; preserve original registers - push rbx - push rcx - push rdx - push rsi - push r9 - push r10 - push r11 - push r15 - push rdi - - ;----------------------------------------------------------------------- - ; prepare task for execution - ;----------------------------------------------------------------------- - - ; register new task on queue - mov rcx, rdx - call kernel_task_add - - ;----------------------------------------------------------------------- - ; paging array of new process - ;----------------------------------------------------------------------- - - ; make space for the process paging table - call kernel_memory_alloc_page - - ; update task entry about paging array - mov qword [r10 + KERNEL_TASK_STRUCTURE.cr3], rdi - - ;----------------------------------------------------------------------- - ; context stack and return point (initialization entry) - ;----------------------------------------------------------------------- - - ; describe the space under context stack of process - mov rax, KERNEL_TASK_STACK_address - mov bx, KERNEL_PAGE_FLAG_present | KERNEL_PAGE_FLAG_write | KERNEL_PAGE_FLAG_process - mov ecx, KERNEL_TASK_STACK_SIZE_page - mov r11, rdi - call kernel_page_alloc - - ; set process context stack pointer - mov rsi, KERNEL_TASK_STACK_pointer - (KERNEL_EXEC_STRUCTURE_RETURN.SIZE + KERNEL_EXEC_STACK_OFFSET_registers) - mov qword [r10 + KERNEL_TASK_STRUCTURE.rsp], rsi - - ; prepare exception exit mode on context stack of process - mov rsi, KERNEL_TASK_STACK_pointer - STATIC_PAGE_SIZE_byte - call kernel_page_address - - ; set pointer to return descriptor - and rax, STATIC_PAGE_mask ; drop flags - add rax, qword [kernel_page_mirror] ; convert to logical address - add rax, STATIC_PAGE_SIZE_byte - KERNEL_EXEC_STRUCTURE_RETURN.SIZE - - ; set first instruction executed by thread - mov rdx, qword [rsp] - mov qword [rax + KERNEL_EXEC_STRUCTURE_RETURN.rip], rdx - - ; code descriptor - mov qword [rax + KERNEL_EXEC_STRUCTURE_RETURN.cs], KERNEL_GDT_STRUCTURE.cs_ring3 | 0x03 - - ; default processor state flags - mov qword [rax + KERNEL_EXEC_STRUCTURE_RETURN.eflags], KERNEL_TASK_EFLAGS_default - - ; default stack pointer - mov rdx, KERNEL_EXEC_STACK_pointer - 0x10 ; no args - mov qword [rax + KERNEL_EXEC_STRUCTURE_RETURN.rsp], rdx - - ; stack descriptor - mov qword [rax + KERNEL_EXEC_STRUCTURE_RETURN.ss], KERNEL_GDT_STRUCTURE.ds_ring3 | 0x03 - - ;----------------------------------------------------------------------- - ; stack - ;----------------------------------------------------------------------- - - ; alloc stack space - mov rcx, KERNEL_EXEC_STACK_SIZE_page - call kernel_memory_alloc - - ; map executable space to thread paging array - mov rax, KERNEL_EXEC_STACK_address - or bx, KERNEL_PAGE_FLAG_user - mov rsi, rdi - sub rsi, qword [kernel_page_mirror] - call kernel_page_map - - ; process memory usage - add qword [r10 + KERNEL_TASK_STRUCTURE.page], rcx - - ; process stack size - add qword [r10 + KERNEL_TASK_STRUCTURE.stack], rcx - - ; aquire parent task properties - call kernel_task_active - - ; threads use same memory map as parent - mov rax, qword [r9 + KERNEL_TASK_STRUCTURE.memory_map] - mov qword [r10 + KERNEL_TASK_STRUCTURE.memory_map], rax - - ; threads use same streams as parent - - ; in - mov rax, qword [r9 + KERNEL_TASK_STRUCTURE.stream_in] - inc qword [rax + KERNEL_STREAM_STRUCTURE.count] - mov qword [r10 + KERNEL_TASK_STRUCTURE.stream_in], rax - - ; out - mov rax, qword [r9 + KERNEL_TASK_STRUCTURE.stream_out] - inc qword [rax + KERNEL_STREAM_STRUCTURE.count] - mov qword [r10 + KERNEL_TASK_STRUCTURE.stream_out], rax - - ; map kernel space to process - mov r15, qword [r9 + KERNEL_TASK_STRUCTURE.cr3] - or r15, qword [kernel_page_mirror] - call kernel_page_merge - - ; mark thread as ready - or word [r10 + KERNEL_TASK_STRUCTURE.flags], KERNEL_TASK_FLAG_active | KERNEL_TASK_FLAG_thread | KERNEL_TASK_FLAG_init - - ; return process ID of new thread - mov rax, qword [r10 + KERNEL_TASK_STRUCTURE.pid] - - ; restore original registers - pop rdi - pop r15 - pop r11 - pop r10 - pop r9 - pop rsi - pop rdx - pop rcx - pop rbx - - ; end of routine - ret \ No newline at end of file