# HG changeset patch # User Mike Becker # Date 1664794588 -7200 # Node ID 7dd4fd1e70711d479364c9f241019e9ddbe10a35 # Parent 60decfc9ad135cb0a8266c9975898534ae6c8708 rewrite Makefile to work with different getopt implementations diff -r 60decfc9ad13 -r 7dd4fd1e7071 Makefile --- a/Makefile Mon Oct 03 12:27:10 2022 +0200 +++ b/Makefile Mon Oct 03 12:56:28 2022 +0200 @@ -47,23 +47,37 @@ build: $(MKDIR) $@ - -test: all - ./build/$(BIN) test/ctestfile.c -o build/ctest.html \ - -H test/header.html -F test/footer.html - ./build/$(BIN) -j test/javatestfile.java -o build/javatest.html \ - -H test/jheader.html -F test/footer.html - ./build/$(BIN) test/bigtestfile.c -o build/bigtest.html \ - -H test/header.html -F test/footer.html - ./build/$(BIN) -p test/plain.csp -o build/plain.html \ - -H test/header.html -F test/footer.html - ./build/$(BIN) -p test/emptyfile.c -o build/emptyfile.html \ - -H test/header.html -F test/footer.html - diff build/ctest.html test/golden-master/ctest.html && \ - diff build/javatest.html test/golden-master/javatest.html && \ - diff build/bigtest.html test/golden-master/bigtest.html && \ - diff build/plain.html test/golden-master/plain.html && \ - diff build/emptyfile.html test/golden-master/emptyfile.html + +test-c: all + for f in ctest bigtest empty ; do \ + echo "test/$$f.c" ; \ + ./build/$(BIN) -o "build/$$f.html" \ + -H test/header.html \ + -F test/footer.html \ + "test/$$f.c" && \ + diff "build/$$f.html" "test/golden-master/$$f.html" ; \ + done + +test-java: all + for f in javatest ; do \ + ./build/$(BIN) -j -o "build/$$f.html" \ + -H test/jheader.html \ + -F test/footer.html \ + "test/$$f.java" && \ + diff "build/$$f.html" "test/golden-master/$$f.html" ; \ + done + +test-plain: all + for f in plain ; do \ + ./build/$(BIN) -p -o "build/$$f.html" \ + -H test/header.html \ + -F test/footer.html \ + "test/$$f.txt" && \ + diff "build/$$f.html" "test/golden-master/$$f.html" ; \ + done + +test: test-c test-java test-plain + @echo "Tests successful." clean: $(RM) $(RMFLAGS) build diff -r 60decfc9ad13 -r 7dd4fd1e7071 test/bigtest.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/bigtest.c Mon Oct 03 12:56:28 2022 +0200 @@ -0,0 +1,808 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "rules.h" +#include "chess.h" +#include +#include +#include + +static GameState gamestate_copy_sim(GameState *gamestate) { + GameState simulation = *gamestate; + if (simulation.lastmove) { + MoveList *lastmovecopy = malloc(sizeof(MoveList)); + *lastmovecopy = *(simulation.lastmove); + simulation.movelist = simulation.lastmove = lastmovecopy; + } + + return simulation; +} + +void gamestate_init(GameState *gamestate) { + memset(gamestate, 0, sizeof(GameState)); + + Board initboard = { + {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK}, + {WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, 0, 0}, + {BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN}, + {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK} + }; + memcpy(gamestate->board, initboard, sizeof(Board)); +} + +void gamestate_cleanup(GameState *gamestate) { + MoveList *elem; + elem = gamestate->movelist; + while (elem) { + MoveList *cur = elem; + elem = elem->next; + free(cur); + }; +} + +/* MUST be called IMMEDIATLY after applying a move to work correctly */ +static void format_move(GameState *gamestate, Move *move) { + char *string = move->string; + + /* at least 8 characters should be available, wipe them out */ + memset(string, 0, 8); + + /* special formats for castling */ + if ((move->piece&PIECE_MASK) == KING && + abs(move->tofile-move->fromfile) == 2) { + if (move->tofile==fileidx('c')) { + memcpy(string, "O-O-O", 5); + } else { + memcpy(string, "O-O", 3); + } + } + + /* start by notating the piece character */ + string[0] = getpiecechr(move->piece); + int idx = string[0] ? 1 : 0; + + /* find out how many source information we do need */ + uint8_t piece = move->piece & PIECE_MASK; + if (piece == PAWN) { + if (move->capture) { + string[idx++] = filechr(move->fromfile); + } + } else if (piece != KING) { + Move threats[16]; + uint8_t threatcount; + get_real_threats(gamestate, move->torow, move->tofile, + move->piece&COLOR_MASK, threats, &threatcount); + if (threatcount > 1) { + int ambrows = 0, ambfiles = 0; + for (uint8_t i = 0 ; i < threatcount ; i++) { + if (threats[i].fromrow == move->fromrow) { + ambrows++; + } + if (threats[i].fromfile == move->fromfile) { + ambfiles++; + } + } + /* ambiguous row, name file */ + if (ambrows > 1) { + string[idx++] = filechr(move->fromfile); + } + /* ambiguous file, name row */ + if (ambfiles > 1) { + string[idx++] = filechr(move->fromrow); + } + } + } + + /* capturing? */ + if (move->capture) { + string[idx++] = 'x'; + } + + /* destination */ + string[idx++] = filechr(move->tofile); + string[idx++] = rowchr(move->torow); + + /* promotion? */ + if (move->promotion) { + string[idx++] = '='; + string[idx++] = getpiecechr(move->promotion); + } + + /* check? */ + if (move->check) { + /* works only, if this function is called when applying the move */ + string[idx++] = gamestate->checkmate?'#':'+'; + } +} + +static void addmove(GameState* gamestate, Move *move) { + MoveList *elem = malloc(sizeof(MoveList)); + elem->next = NULL; + elem->move = *move; + + struct timeval curtimestamp; + gettimeofday(&curtimestamp, NULL); + elem->move.timestamp.tv_sec = curtimestamp.tv_sec; + elem->move.timestamp.tv_usec = curtimestamp.tv_usec; + + if (gamestate->lastmove) { + struct movetimeval *lasttstamp = &(gamestate->lastmove->move.timestamp); + uint64_t sec = curtimestamp.tv_sec - lasttstamp->tv_sec; + suseconds_t micros; + if (curtimestamp.tv_usec < lasttstamp->tv_usec) { + micros = 1e6L-(lasttstamp->tv_usec - curtimestamp.tv_usec); + sec--; + } else { + micros = curtimestamp.tv_usec - lasttstamp->tv_usec; + } + + elem->move.movetime.tv_sec = sec; + elem->move.movetime.tv_usec = micros; + + gamestate->lastmove->next = elem; + gamestate->lastmove = elem; + } else { + elem->move.movetime.tv_usec = 0; + elem->move.movetime.tv_sec = 0; + gamestate->movelist = gamestate->lastmove = elem; + } +} + +char getpiecechr(uint8_t piece) { + switch (piece & PIECE_MASK) { + case ROOK: return 'R'; + case KNIGHT: return 'N'; + case BISHOP: return 'B'; + case QUEEN: return 'Q'; + case KING: return 'K'; + default: return '\0'; + } +} + +uint8_t getpiece(char c) { + switch (c) { + case 'R': return ROOK; + case 'N': return KNIGHT; + case 'B': return BISHOP; + case 'Q': return QUEEN; + case 'K': return KING; + default: return 0; + } +} + +static void apply_move_impl(GameState *gamestate, Move *move, _Bool simulate) { + uint8_t piece = move->piece & PIECE_MASK; + uint8_t color = move->piece & COLOR_MASK; + + /* en passant capture */ + if (move->capture && piece == PAWN && + mdst(gamestate->board, move) == 0) { + gamestate->board[move->fromrow][move->tofile] = 0; + } + + /* remove old en passant threats */ + for (uint8_t file = 0 ; file < 8 ; file++) { + gamestate->board[3][file] &= ~ENPASSANT_THREAT; + gamestate->board[4][file] &= ~ENPASSANT_THREAT; + } + + /* add new en passant threat */ + if (piece == PAWN && ( + (move->fromrow == 1 && move->torow == 3) || + (move->fromrow == 6 && move->torow == 4))) { + move->piece |= ENPASSANT_THREAT; + } + + /* move (and maybe capture or promote) */ + msrc(gamestate->board, move) = 0; + if (move->promotion) { + mdst(gamestate->board, move) = move->promotion; + } else { + mdst(gamestate->board, move) = move->piece; + } + + /* castling */ + if (piece == KING && move->fromfile == fileidx('e')) { + + if (move->tofile == fileidx('g')) { + gamestate->board[move->torow][fileidx('h')] = 0; + gamestate->board[move->torow][fileidx('f')] = color|ROOK; + } else if (move->tofile == fileidx('c')) { + gamestate->board[move->torow][fileidx('a')] = 0; + gamestate->board[move->torow][fileidx('d')] = color|ROOK; + } + } + + if (!simulate) { + if (!move->string[0]) { + format_move(gamestate, move); + } + } + /* add move, even in simulation (checkmate test needs it) */ + addmove(gamestate, move); +} + +void apply_move(GameState *gamestate, Move *move) { + apply_move_impl(gamestate, move, 0); +} + +static int validate_move_rules(GameState *gamestate, Move *move) { + /* validate indices (don't trust opponent) */ + if (!chkidx(move)) { + return INVALID_POSITION; + } + + /* must move */ + if (move->fromfile == move->tofile && move->fromrow == move->torow) { + return INVALID_MOVE_SYNTAX; + } + + /* does piece exist */ + if ((msrc(gamestate->board, move)&(PIECE_MASK|COLOR_MASK)) + != (move->piece&(PIECE_MASK|COLOR_MASK))) { + return INVALID_POSITION; + } + + /* can't capture own pieces */ + if ((mdst(gamestate->board, move) & COLOR_MASK) + == (move->piece & COLOR_MASK)) { + return RULES_VIOLATED; + } + + /* must capture, if and only if destination is occupied */ + if ((mdst(gamestate->board, move) == 0 && move->capture) || + (mdst(gamestate->board, move) != 0 && !move->capture)) { + return INVALID_MOVE_SYNTAX; + } + + /* validate individual rules */ + _Bool chkrules; + switch (move->piece & PIECE_MASK) { + case PAWN: + chkrules = pawn_chkrules(gamestate, move) && + !pawn_isblocked(gamestate, move); + break; + case ROOK: + chkrules = rook_chkrules(move) && + !rook_isblocked(gamestate, move); + break; + case KNIGHT: + chkrules = knight_chkrules(move); /* knight is never blocked */ + break; + case BISHOP: + chkrules = bishop_chkrules(move) && + !bishop_isblocked(gamestate, move); + break; + case QUEEN: + chkrules = queen_chkrules(move) && + !queen_isblocked(gamestate, move); + break; + case KING: + chkrules = king_chkrules(gamestate, move) && + !king_isblocked(gamestate, move); + break; + default: + return INVALID_MOVE_SYNTAX; + } + + return chkrules ? VALID_MOVE_SEMANTICS : RULES_VIOLATED; +} + +int validate_move(GameState *gamestate, Move *move) { + + int result = validate_move_rules(gamestate, move); + + /* cancel processing to save resources */ + if (result != VALID_MOVE_SEMANTICS) { + return result; + } + + /* find kings for check validation */ + uint8_t piececolor = (move->piece & COLOR_MASK); + + uint8_t mykingfile = 0, mykingrow = 0, opkingfile = 0, opkingrow = 0; + for (uint8_t row = 0 ; row < 8 ; row++) { + for (uint8_t file = 0 ; file < 8 ; file++) { + if (gamestate->board[row][file] == + (piececolor == WHITE?WKING:BKING)) { + mykingfile = file; + mykingrow = row; + } else if (gamestate->board[row][file] == + (piececolor == WHITE?BKING:WKING)) { + opkingfile = file; + opkingrow = row; + } + } + } + + /* simulate move for check validation */ + GameState simulation = gamestate_copy_sim(gamestate); + Move simmove = *move; + apply_move_impl(&simulation, &simmove, 1); + + /* don't move into or stay in check position */ + if (is_covered(&simulation, mykingrow, mykingfile, + opponent_color(piececolor))) { + + gamestate_cleanup(&simulation); + if ((move->piece & PIECE_MASK) == KING) { + return KING_MOVES_INTO_CHECK; + } else { + /* last move is always not null in this case */ + return gamestate->lastmove->move.check ? + KING_IN_CHECK : PIECE_PINNED; + } + } + + /* correct check and checkmate flags (move is still valid) */ + Move threats[16]; + uint8_t threatcount; + move->check = get_threats(&simulation, opkingrow, opkingfile, + piececolor, threats, &threatcount); + + if (move->check) { + /* determine possible escape fields */ + _Bool canescape = 0; + for (int dr = -1 ; dr <= 1 && !canescape ; dr++) { + for (int df = -1 ; df <= 1 && !canescape ; df++) { + if (!(dr == 0 && df == 0) && + isidx(opkingrow + dr) && isidx(opkingfile + df)) { + + /* escape field neither blocked nor covered */ + if ((simulation.board[opkingrow + dr][opkingfile + df] + & COLOR_MASK) != opponent_color(piececolor)) { + canescape |= !is_covered(&simulation, + opkingrow + dr, opkingfile + df, piececolor); + } + } + } + } + /* can't escape, can he capture? */ + if (!canescape && threatcount == 1) { + canescape = is_attacked(&simulation, threats[0].fromrow, + threats[0].fromfile, opponent_color(piececolor)); + } + + /* can't capture, can he block? */ + if (!canescape && threatcount == 1) { + Move *threat = &(threats[0]); + uint8_t threatpiece = threat->piece & PIECE_MASK; + + /* knight, pawns and the king cannot be blocked */ + if (threatpiece == BISHOP || threatpiece == ROOK + || threatpiece == QUEEN) { + if (threat->fromrow == threat->torow) { + /* rook aspect (on row) */ + int d = threat->tofile > threat->fromfile ? 1 : -1; + uint8_t file = threat->fromfile; + while (!canescape && file != threat->tofile - d) { + file += d; + canescape |= is_protected(&simulation, + threat->torow, file, opponent_color(piececolor)); + } + } else if (threat->fromfile == threat->tofile) { + /* rook aspect (on file) */ + int d = threat->torow > threat->fromrow ? 1 : -1; + uint8_t row = threat->fromrow; + while (!canescape && row != threat->torow - d) { + row += d; + canescape |= is_protected(&simulation, + row, threat->tofile, opponent_color(piececolor)); + } + } else { + /* bishop aspect */ + int dr = threat->torow > threat->fromrow ? 1 : -1; + int df = threat->tofile > threat->fromfile ? 1 : -1; + + uint8_t row = threat->fromrow; + uint8_t file = threat->fromfile; + while (!canescape && file != threat->tofile - df + && row != threat->torow - dr) { + row += dr; + file += df; + canescape |= is_protected(&simulation, row, file, + opponent_color(piececolor)); + } + } + } + } + + if (!canescape) { + gamestate->checkmate = 1; + } + } + + gamestate_cleanup(&simulation); + + return VALID_MOVE_SEMANTICS; +} + +_Bool get_threats(GameState *gamestate, uint8_t row, uint8_t file, + uint8_t color, Move *threats, uint8_t *threatcount) { + Move candidates[32]; + int candidatecount = 0; + for (uint8_t r = 0 ; r < 8 ; r++) { + for (uint8_t f = 0 ; f < 8 ; f++) { + if ((gamestate->board[r][f] & COLOR_MASK) == color) { + // non-capturing move + memset(&(candidates[candidatecount]), 0, sizeof(Move)); + candidates[candidatecount].piece = gamestate->board[r][f]; + candidates[candidatecount].fromrow = r; + candidates[candidatecount].fromfile = f; + candidates[candidatecount].torow = row; + candidates[candidatecount].tofile = file; + candidatecount++; + + // capturing move + memcpy(&(candidates[candidatecount]), + &(candidates[candidatecount-1]), sizeof(Move)); + candidates[candidatecount].capture = 1; + candidatecount++; + } + } + } + + if (threatcount) { + *threatcount = 0; + } + + + _Bool result = 0; + + for (int i = 0 ; i < candidatecount ; i++) { + if (validate_move_rules(gamestate, &(candidates[i])) + == VALID_MOVE_SEMANTICS) { + result = 1; + if (threats && threatcount) { + threats[(*threatcount)++] = candidates[i]; + } + } + } + + return result; +} + +_Bool is_pinned(GameState *gamestate, Move *move) { + uint8_t color = move->piece & COLOR_MASK; + + uint8_t kingfile = 0, kingrow = 0; + for (uint8_t row = 0 ; row < 8 ; row++) { + for (uint8_t file = 0 ; file < 8 ; file++) { + if (gamestate->board[row][file] == (color|KING)) { + kingfile = file; + kingrow = row; + } + } + } + + GameState simulation = gamestate_copy_sim(gamestate); + Move simmove = *move; + apply_move(&simulation, &simmove); + _Bool covered = is_covered(&simulation, + kingrow, kingfile, opponent_color(color)); + gamestate_cleanup(&simulation); + + return covered; +} + +_Bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file, + uint8_t color, Move *threats, uint8_t *threatcount) { + + if (threatcount) { + *threatcount = 0; + } + + Move candidates[16]; + uint8_t candidatecount; + if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) { + + _Bool result = 0; + uint8_t kingfile = 0, kingrow = 0; + for (uint8_t row = 0 ; row < 8 ; row++) { + for (uint8_t file = 0 ; file < 8 ; file++) { + if (gamestate->board[row][file] == (color|KING)) { + kingfile = file; + kingrow = row; + } + } + } + + for (uint8_t i = 0 ; i < candidatecount ; i++) { + GameState simulation = gamestate_copy_sim(gamestate); + Move simmove = candidates[i]; + apply_move(&simulation, &simmove); + if (!is_covered(&simulation, kingrow, kingfile, + opponent_color(color))) { + result = 1; + if (threats && threatcount) { + threats[(*threatcount)++] = candidates[i]; + } + } + } + + return result; + } else { + return 0; + } +} + +static int getlocation(GameState *gamestate, Move *move) { + + uint8_t color = move->piece & COLOR_MASK; + _Bool incheck = gamestate->lastmove?gamestate->lastmove->move.check:0; + + Move threats[16], *threat = NULL; + uint8_t threatcount; + + if (get_threats(gamestate, move->torow, move->tofile, color, + threats, &threatcount)) { + + int reason = INVALID_POSITION; + + // find threats for the specified position + for (uint8_t i = 0 ; i < threatcount ; i++) { + if ((threats[i].piece & (PIECE_MASK | COLOR_MASK)) + == move->piece && + (move->fromrow == POS_UNSPECIFIED || + move->fromrow == threats[i].fromrow) && + (move->fromfile == POS_UNSPECIFIED || + move->fromfile == threats[i].fromfile)) { + + if (threat) { + return AMBIGUOUS_MOVE; + } else { + // found threat is no real threat + if (is_pinned(gamestate, &(threats[i]))) { + reason = incheck?KING_IN_CHECK:PIECE_PINNED; + } else { + threat = &(threats[i]); + } + } + } + } + + // can't threaten specified position + if (!threat) { + return reason; + } + + memcpy(move, threat, sizeof(Move)); + return VALID_MOVE_SYNTAX; + } else { + return INVALID_POSITION; + } +} + +int eval_move(GameState *gamestate, char *mstr, Move *move, uint8_t color) { + memset(move, 0, sizeof(Move)); + move->fromfile = POS_UNSPECIFIED; + move->fromrow = POS_UNSPECIFIED; + + size_t len = strlen(mstr); + if (len < 1 || len > 6) { + return INVALID_MOVE_SYNTAX; + } + + /* evaluate check/checkmate flags */ + if (mstr[len-1] == '+') { + len--; mstr[len] = '\0'; + move->check = 1; + } else if (mstr[len-1] == '#') { + len--; mstr[len] = '\0'; + /* ignore - validation should set game state */ + } + + /* evaluate promotion */ + if (len > 3 && mstr[len-2] == '=') { + move->promotion = getpiece(mstr[len-1]); + if (!move->promotion) { + return INVALID_MOVE_SYNTAX; + } else { + move->promotion |= color; + len -= 2; + mstr[len] = 0; + } + } + + if (len == 2) { + /* pawn move (e.g. "e4") */ + move->piece = PAWN; + move->tofile = fileidx(mstr[0]); + move->torow = rowidx(mstr[1]); + } else if (len == 3) { + if (strcmp(mstr, "O-O") == 0) { + /* king side castling */ + move->piece = KING; + move->fromfile = fileidx('e'); + move->tofile = fileidx('g'); + move->fromrow = move->torow = color == WHITE ? 0 : 7; + } else { + /* move (e.g. "Nf3") */ + move->piece = getpiece(mstr[0]); + move->tofile = fileidx(mstr[1]); + move->torow = rowidx(mstr[2]); + } + } else if (len == 4) { + move->piece = getpiece(mstr[0]); + if (!move->piece) { + move->piece = PAWN; + move->fromfile = fileidx(mstr[0]); + } + if (mstr[1] == 'x') { + /* capture (e.g. "Nxf3", "dxe5") */ + move->capture = 1; + } else { + /* move (e.g. "Ndf3", "N2c3", "e2e4") */ + if (isfile(mstr[1])) { + move->fromfile = fileidx(mstr[1]); + if (move->piece == PAWN) { + move->piece = 0; + } + } else { + move->fromrow = rowidx(mstr[1]); + } + } + move->tofile = fileidx(mstr[2]); + move->torow = rowidx(mstr[3]); + } else if (len == 5) { + if (strcmp(mstr, "O-O-O") == 0) { + /* queen side castling "O-O-O" */ + move->piece = KING; + move->fromfile = fileidx('e'); + move->tofile = fileidx('c'); + move->fromrow = move->torow = color == WHITE ? 0 : 7; + } else { + move->piece = getpiece(mstr[0]); + if (mstr[2] == 'x') { + move->capture = 1; + if (move->piece) { + /* capture (e.g. "Ndxf3") */ + move->fromfile = fileidx(mstr[1]); + } else { + /* long notation capture (e.g. "e5xf6") */ + move->piece = PAWN; + move->fromfile = fileidx(mstr[0]); + move->fromrow = rowidx(mstr[1]); + } + } else { + /* long notation move (e.g. "Nc5a4") */ + move->fromfile = fileidx(mstr[1]); + move->fromrow = rowidx(mstr[2]); + } + move->tofile = fileidx(mstr[3]); + move->torow = rowidx(mstr[4]); + } + } else if (len == 6) { + /* long notation capture (e.g. "Nc5xf3") */ + if (mstr[3] == 'x') { + move->capture = 1; + move->piece = getpiece(mstr[0]); + move->fromfile = fileidx(mstr[1]); + move->fromrow = rowidx(mstr[2]); + move->tofile = fileidx(mstr[4]); + move->torow = rowidx(mstr[5]); + } + } + + + if (move->piece) { + if (move->piece == PAWN + && move->torow == (color==WHITE?7:0) + && !move->promotion) { + return NEED_PROMOTION; + } + + move->piece |= color; + if (move->fromfile == POS_UNSPECIFIED + || move->fromrow == POS_UNSPECIFIED) { + return getlocation(gamestate, move); + } else { + return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION; + } + } else { + return INVALID_MOVE_SYNTAX; + } +} + +_Bool is_protected(GameState *gamestate, uint8_t row, uint8_t file, + uint8_t color) { + + Move threats[16]; + uint8_t threatcount; + if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) { + for (int i = 0 ; i < threatcount ; i++) { + if (threats[i].piece != (color|KING)) { + return 1; + } + } + return 0; + } else { + return 0; + } +} + +uint16_t remaining_movetime(GameInfo *gameinfo, GameState *gamestate, + uint8_t color) { + if (!gameinfo->timecontrol) { + return 0; + } + + if (gamestate->movelist) { + uint16_t time = gameinfo->time; + suseconds_t micros = 0; + + MoveList *movelist = color == WHITE ? + gamestate->movelist : gamestate->movelist->next; + + while (movelist) { + time += gameinfo->addtime; + + struct movetimeval *movetime = &(movelist->move.movetime); + if (movetime->tv_sec >= time) { + return 0; + } + + time -= movetime->tv_sec; + micros += movetime->tv_usec; + + movelist = movelist->next ? movelist->next->next : NULL; + } + + time_t sec; + movelist = gamestate->lastmove; + if ((movelist->move.piece & COLOR_MASK) != color) { + struct movetimeval *lastmovetstamp = &(movelist->move.timestamp); + struct timeval currenttstamp; + gettimeofday(¤ttstamp, NULL); + micros += currenttstamp.tv_usec - lastmovetstamp->tv_usec; + sec = currenttstamp.tv_sec - lastmovetstamp->tv_sec; + if (sec >= time) { + return 0; + } + + time -= sec; + } + + sec = micros / 1e6L; + + if (sec >= time) { + return 0; + } + + time -= sec; + + return time; + } else { + return gameinfo->time; + } +} diff -r 60decfc9ad13 -r 7dd4fd1e7071 test/bigtestfile.c --- a/test/bigtestfile.c Mon Oct 03 12:27:10 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,808 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2014 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "rules.h" -#include "chess.h" -#include -#include -#include - -static GameState gamestate_copy_sim(GameState *gamestate) { - GameState simulation = *gamestate; - if (simulation.lastmove) { - MoveList *lastmovecopy = malloc(sizeof(MoveList)); - *lastmovecopy = *(simulation.lastmove); - simulation.movelist = simulation.lastmove = lastmovecopy; - } - - return simulation; -} - -void gamestate_init(GameState *gamestate) { - memset(gamestate, 0, sizeof(GameState)); - - Board initboard = { - {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK}, - {WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN, WPAWN}, - {0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0}, - {0, 0, 0, 0, 0, 0, 0, 0}, - {BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN, BPAWN}, - {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK} - }; - memcpy(gamestate->board, initboard, sizeof(Board)); -} - -void gamestate_cleanup(GameState *gamestate) { - MoveList *elem; - elem = gamestate->movelist; - while (elem) { - MoveList *cur = elem; - elem = elem->next; - free(cur); - }; -} - -/* MUST be called IMMEDIATLY after applying a move to work correctly */ -static void format_move(GameState *gamestate, Move *move) { - char *string = move->string; - - /* at least 8 characters should be available, wipe them out */ - memset(string, 0, 8); - - /* special formats for castling */ - if ((move->piece&PIECE_MASK) == KING && - abs(move->tofile-move->fromfile) == 2) { - if (move->tofile==fileidx('c')) { - memcpy(string, "O-O-O", 5); - } else { - memcpy(string, "O-O", 3); - } - } - - /* start by notating the piece character */ - string[0] = getpiecechr(move->piece); - int idx = string[0] ? 1 : 0; - - /* find out how many source information we do need */ - uint8_t piece = move->piece & PIECE_MASK; - if (piece == PAWN) { - if (move->capture) { - string[idx++] = filechr(move->fromfile); - } - } else if (piece != KING) { - Move threats[16]; - uint8_t threatcount; - get_real_threats(gamestate, move->torow, move->tofile, - move->piece&COLOR_MASK, threats, &threatcount); - if (threatcount > 1) { - int ambrows = 0, ambfiles = 0; - for (uint8_t i = 0 ; i < threatcount ; i++) { - if (threats[i].fromrow == move->fromrow) { - ambrows++; - } - if (threats[i].fromfile == move->fromfile) { - ambfiles++; - } - } - /* ambiguous row, name file */ - if (ambrows > 1) { - string[idx++] = filechr(move->fromfile); - } - /* ambiguous file, name row */ - if (ambfiles > 1) { - string[idx++] = filechr(move->fromrow); - } - } - } - - /* capturing? */ - if (move->capture) { - string[idx++] = 'x'; - } - - /* destination */ - string[idx++] = filechr(move->tofile); - string[idx++] = rowchr(move->torow); - - /* promotion? */ - if (move->promotion) { - string[idx++] = '='; - string[idx++] = getpiecechr(move->promotion); - } - - /* check? */ - if (move->check) { - /* works only, if this function is called when applying the move */ - string[idx++] = gamestate->checkmate?'#':'+'; - } -} - -static void addmove(GameState* gamestate, Move *move) { - MoveList *elem = malloc(sizeof(MoveList)); - elem->next = NULL; - elem->move = *move; - - struct timeval curtimestamp; - gettimeofday(&curtimestamp, NULL); - elem->move.timestamp.tv_sec = curtimestamp.tv_sec; - elem->move.timestamp.tv_usec = curtimestamp.tv_usec; - - if (gamestate->lastmove) { - struct movetimeval *lasttstamp = &(gamestate->lastmove->move.timestamp); - uint64_t sec = curtimestamp.tv_sec - lasttstamp->tv_sec; - suseconds_t micros; - if (curtimestamp.tv_usec < lasttstamp->tv_usec) { - micros = 1e6L-(lasttstamp->tv_usec - curtimestamp.tv_usec); - sec--; - } else { - micros = curtimestamp.tv_usec - lasttstamp->tv_usec; - } - - elem->move.movetime.tv_sec = sec; - elem->move.movetime.tv_usec = micros; - - gamestate->lastmove->next = elem; - gamestate->lastmove = elem; - } else { - elem->move.movetime.tv_usec = 0; - elem->move.movetime.tv_sec = 0; - gamestate->movelist = gamestate->lastmove = elem; - } -} - -char getpiecechr(uint8_t piece) { - switch (piece & PIECE_MASK) { - case ROOK: return 'R'; - case KNIGHT: return 'N'; - case BISHOP: return 'B'; - case QUEEN: return 'Q'; - case KING: return 'K'; - default: return '\0'; - } -} - -uint8_t getpiece(char c) { - switch (c) { - case 'R': return ROOK; - case 'N': return KNIGHT; - case 'B': return BISHOP; - case 'Q': return QUEEN; - case 'K': return KING; - default: return 0; - } -} - -static void apply_move_impl(GameState *gamestate, Move *move, _Bool simulate) { - uint8_t piece = move->piece & PIECE_MASK; - uint8_t color = move->piece & COLOR_MASK; - - /* en passant capture */ - if (move->capture && piece == PAWN && - mdst(gamestate->board, move) == 0) { - gamestate->board[move->fromrow][move->tofile] = 0; - } - - /* remove old en passant threats */ - for (uint8_t file = 0 ; file < 8 ; file++) { - gamestate->board[3][file] &= ~ENPASSANT_THREAT; - gamestate->board[4][file] &= ~ENPASSANT_THREAT; - } - - /* add new en passant threat */ - if (piece == PAWN && ( - (move->fromrow == 1 && move->torow == 3) || - (move->fromrow == 6 && move->torow == 4))) { - move->piece |= ENPASSANT_THREAT; - } - - /* move (and maybe capture or promote) */ - msrc(gamestate->board, move) = 0; - if (move->promotion) { - mdst(gamestate->board, move) = move->promotion; - } else { - mdst(gamestate->board, move) = move->piece; - } - - /* castling */ - if (piece == KING && move->fromfile == fileidx('e')) { - - if (move->tofile == fileidx('g')) { - gamestate->board[move->torow][fileidx('h')] = 0; - gamestate->board[move->torow][fileidx('f')] = color|ROOK; - } else if (move->tofile == fileidx('c')) { - gamestate->board[move->torow][fileidx('a')] = 0; - gamestate->board[move->torow][fileidx('d')] = color|ROOK; - } - } - - if (!simulate) { - if (!move->string[0]) { - format_move(gamestate, move); - } - } - /* add move, even in simulation (checkmate test needs it) */ - addmove(gamestate, move); -} - -void apply_move(GameState *gamestate, Move *move) { - apply_move_impl(gamestate, move, 0); -} - -static int validate_move_rules(GameState *gamestate, Move *move) { - /* validate indices (don't trust opponent) */ - if (!chkidx(move)) { - return INVALID_POSITION; - } - - /* must move */ - if (move->fromfile == move->tofile && move->fromrow == move->torow) { - return INVALID_MOVE_SYNTAX; - } - - /* does piece exist */ - if ((msrc(gamestate->board, move)&(PIECE_MASK|COLOR_MASK)) - != (move->piece&(PIECE_MASK|COLOR_MASK))) { - return INVALID_POSITION; - } - - /* can't capture own pieces */ - if ((mdst(gamestate->board, move) & COLOR_MASK) - == (move->piece & COLOR_MASK)) { - return RULES_VIOLATED; - } - - /* must capture, if and only if destination is occupied */ - if ((mdst(gamestate->board, move) == 0 && move->capture) || - (mdst(gamestate->board, move) != 0 && !move->capture)) { - return INVALID_MOVE_SYNTAX; - } - - /* validate individual rules */ - _Bool chkrules; - switch (move->piece & PIECE_MASK) { - case PAWN: - chkrules = pawn_chkrules(gamestate, move) && - !pawn_isblocked(gamestate, move); - break; - case ROOK: - chkrules = rook_chkrules(move) && - !rook_isblocked(gamestate, move); - break; - case KNIGHT: - chkrules = knight_chkrules(move); /* knight is never blocked */ - break; - case BISHOP: - chkrules = bishop_chkrules(move) && - !bishop_isblocked(gamestate, move); - break; - case QUEEN: - chkrules = queen_chkrules(move) && - !queen_isblocked(gamestate, move); - break; - case KING: - chkrules = king_chkrules(gamestate, move) && - !king_isblocked(gamestate, move); - break; - default: - return INVALID_MOVE_SYNTAX; - } - - return chkrules ? VALID_MOVE_SEMANTICS : RULES_VIOLATED; -} - -int validate_move(GameState *gamestate, Move *move) { - - int result = validate_move_rules(gamestate, move); - - /* cancel processing to save resources */ - if (result != VALID_MOVE_SEMANTICS) { - return result; - } - - /* find kings for check validation */ - uint8_t piececolor = (move->piece & COLOR_MASK); - - uint8_t mykingfile = 0, mykingrow = 0, opkingfile = 0, opkingrow = 0; - for (uint8_t row = 0 ; row < 8 ; row++) { - for (uint8_t file = 0 ; file < 8 ; file++) { - if (gamestate->board[row][file] == - (piececolor == WHITE?WKING:BKING)) { - mykingfile = file; - mykingrow = row; - } else if (gamestate->board[row][file] == - (piececolor == WHITE?BKING:WKING)) { - opkingfile = file; - opkingrow = row; - } - } - } - - /* simulate move for check validation */ - GameState simulation = gamestate_copy_sim(gamestate); - Move simmove = *move; - apply_move_impl(&simulation, &simmove, 1); - - /* don't move into or stay in check position */ - if (is_covered(&simulation, mykingrow, mykingfile, - opponent_color(piececolor))) { - - gamestate_cleanup(&simulation); - if ((move->piece & PIECE_MASK) == KING) { - return KING_MOVES_INTO_CHECK; - } else { - /* last move is always not null in this case */ - return gamestate->lastmove->move.check ? - KING_IN_CHECK : PIECE_PINNED; - } - } - - /* correct check and checkmate flags (move is still valid) */ - Move threats[16]; - uint8_t threatcount; - move->check = get_threats(&simulation, opkingrow, opkingfile, - piececolor, threats, &threatcount); - - if (move->check) { - /* determine possible escape fields */ - _Bool canescape = 0; - for (int dr = -1 ; dr <= 1 && !canescape ; dr++) { - for (int df = -1 ; df <= 1 && !canescape ; df++) { - if (!(dr == 0 && df == 0) && - isidx(opkingrow + dr) && isidx(opkingfile + df)) { - - /* escape field neither blocked nor covered */ - if ((simulation.board[opkingrow + dr][opkingfile + df] - & COLOR_MASK) != opponent_color(piececolor)) { - canescape |= !is_covered(&simulation, - opkingrow + dr, opkingfile + df, piececolor); - } - } - } - } - /* can't escape, can he capture? */ - if (!canescape && threatcount == 1) { - canescape = is_attacked(&simulation, threats[0].fromrow, - threats[0].fromfile, opponent_color(piececolor)); - } - - /* can't capture, can he block? */ - if (!canescape && threatcount == 1) { - Move *threat = &(threats[0]); - uint8_t threatpiece = threat->piece & PIECE_MASK; - - /* knight, pawns and the king cannot be blocked */ - if (threatpiece == BISHOP || threatpiece == ROOK - || threatpiece == QUEEN) { - if (threat->fromrow == threat->torow) { - /* rook aspect (on row) */ - int d = threat->tofile > threat->fromfile ? 1 : -1; - uint8_t file = threat->fromfile; - while (!canescape && file != threat->tofile - d) { - file += d; - canescape |= is_protected(&simulation, - threat->torow, file, opponent_color(piececolor)); - } - } else if (threat->fromfile == threat->tofile) { - /* rook aspect (on file) */ - int d = threat->torow > threat->fromrow ? 1 : -1; - uint8_t row = threat->fromrow; - while (!canescape && row != threat->torow - d) { - row += d; - canescape |= is_protected(&simulation, - row, threat->tofile, opponent_color(piececolor)); - } - } else { - /* bishop aspect */ - int dr = threat->torow > threat->fromrow ? 1 : -1; - int df = threat->tofile > threat->fromfile ? 1 : -1; - - uint8_t row = threat->fromrow; - uint8_t file = threat->fromfile; - while (!canescape && file != threat->tofile - df - && row != threat->torow - dr) { - row += dr; - file += df; - canescape |= is_protected(&simulation, row, file, - opponent_color(piececolor)); - } - } - } - } - - if (!canescape) { - gamestate->checkmate = 1; - } - } - - gamestate_cleanup(&simulation); - - return VALID_MOVE_SEMANTICS; -} - -_Bool get_threats(GameState *gamestate, uint8_t row, uint8_t file, - uint8_t color, Move *threats, uint8_t *threatcount) { - Move candidates[32]; - int candidatecount = 0; - for (uint8_t r = 0 ; r < 8 ; r++) { - for (uint8_t f = 0 ; f < 8 ; f++) { - if ((gamestate->board[r][f] & COLOR_MASK) == color) { - // non-capturing move - memset(&(candidates[candidatecount]), 0, sizeof(Move)); - candidates[candidatecount].piece = gamestate->board[r][f]; - candidates[candidatecount].fromrow = r; - candidates[candidatecount].fromfile = f; - candidates[candidatecount].torow = row; - candidates[candidatecount].tofile = file; - candidatecount++; - - // capturing move - memcpy(&(candidates[candidatecount]), - &(candidates[candidatecount-1]), sizeof(Move)); - candidates[candidatecount].capture = 1; - candidatecount++; - } - } - } - - if (threatcount) { - *threatcount = 0; - } - - - _Bool result = 0; - - for (int i = 0 ; i < candidatecount ; i++) { - if (validate_move_rules(gamestate, &(candidates[i])) - == VALID_MOVE_SEMANTICS) { - result = 1; - if (threats && threatcount) { - threats[(*threatcount)++] = candidates[i]; - } - } - } - - return result; -} - -_Bool is_pinned(GameState *gamestate, Move *move) { - uint8_t color = move->piece & COLOR_MASK; - - uint8_t kingfile = 0, kingrow = 0; - for (uint8_t row = 0 ; row < 8 ; row++) { - for (uint8_t file = 0 ; file < 8 ; file++) { - if (gamestate->board[row][file] == (color|KING)) { - kingfile = file; - kingrow = row; - } - } - } - - GameState simulation = gamestate_copy_sim(gamestate); - Move simmove = *move; - apply_move(&simulation, &simmove); - _Bool covered = is_covered(&simulation, - kingrow, kingfile, opponent_color(color)); - gamestate_cleanup(&simulation); - - return covered; -} - -_Bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file, - uint8_t color, Move *threats, uint8_t *threatcount) { - - if (threatcount) { - *threatcount = 0; - } - - Move candidates[16]; - uint8_t candidatecount; - if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) { - - _Bool result = 0; - uint8_t kingfile = 0, kingrow = 0; - for (uint8_t row = 0 ; row < 8 ; row++) { - for (uint8_t file = 0 ; file < 8 ; file++) { - if (gamestate->board[row][file] == (color|KING)) { - kingfile = file; - kingrow = row; - } - } - } - - for (uint8_t i = 0 ; i < candidatecount ; i++) { - GameState simulation = gamestate_copy_sim(gamestate); - Move simmove = candidates[i]; - apply_move(&simulation, &simmove); - if (!is_covered(&simulation, kingrow, kingfile, - opponent_color(color))) { - result = 1; - if (threats && threatcount) { - threats[(*threatcount)++] = candidates[i]; - } - } - } - - return result; - } else { - return 0; - } -} - -static int getlocation(GameState *gamestate, Move *move) { - - uint8_t color = move->piece & COLOR_MASK; - _Bool incheck = gamestate->lastmove?gamestate->lastmove->move.check:0; - - Move threats[16], *threat = NULL; - uint8_t threatcount; - - if (get_threats(gamestate, move->torow, move->tofile, color, - threats, &threatcount)) { - - int reason = INVALID_POSITION; - - // find threats for the specified position - for (uint8_t i = 0 ; i < threatcount ; i++) { - if ((threats[i].piece & (PIECE_MASK | COLOR_MASK)) - == move->piece && - (move->fromrow == POS_UNSPECIFIED || - move->fromrow == threats[i].fromrow) && - (move->fromfile == POS_UNSPECIFIED || - move->fromfile == threats[i].fromfile)) { - - if (threat) { - return AMBIGUOUS_MOVE; - } else { - // found threat is no real threat - if (is_pinned(gamestate, &(threats[i]))) { - reason = incheck?KING_IN_CHECK:PIECE_PINNED; - } else { - threat = &(threats[i]); - } - } - } - } - - // can't threaten specified position - if (!threat) { - return reason; - } - - memcpy(move, threat, sizeof(Move)); - return VALID_MOVE_SYNTAX; - } else { - return INVALID_POSITION; - } -} - -int eval_move(GameState *gamestate, char *mstr, Move *move, uint8_t color) { - memset(move, 0, sizeof(Move)); - move->fromfile = POS_UNSPECIFIED; - move->fromrow = POS_UNSPECIFIED; - - size_t len = strlen(mstr); - if (len < 1 || len > 6) { - return INVALID_MOVE_SYNTAX; - } - - /* evaluate check/checkmate flags */ - if (mstr[len-1] == '+') { - len--; mstr[len] = '\0'; - move->check = 1; - } else if (mstr[len-1] == '#') { - len--; mstr[len] = '\0'; - /* ignore - validation should set game state */ - } - - /* evaluate promotion */ - if (len > 3 && mstr[len-2] == '=') { - move->promotion = getpiece(mstr[len-1]); - if (!move->promotion) { - return INVALID_MOVE_SYNTAX; - } else { - move->promotion |= color; - len -= 2; - mstr[len] = 0; - } - } - - if (len == 2) { - /* pawn move (e.g. "e4") */ - move->piece = PAWN; - move->tofile = fileidx(mstr[0]); - move->torow = rowidx(mstr[1]); - } else if (len == 3) { - if (strcmp(mstr, "O-O") == 0) { - /* king side castling */ - move->piece = KING; - move->fromfile = fileidx('e'); - move->tofile = fileidx('g'); - move->fromrow = move->torow = color == WHITE ? 0 : 7; - } else { - /* move (e.g. "Nf3") */ - move->piece = getpiece(mstr[0]); - move->tofile = fileidx(mstr[1]); - move->torow = rowidx(mstr[2]); - } - } else if (len == 4) { - move->piece = getpiece(mstr[0]); - if (!move->piece) { - move->piece = PAWN; - move->fromfile = fileidx(mstr[0]); - } - if (mstr[1] == 'x') { - /* capture (e.g. "Nxf3", "dxe5") */ - move->capture = 1; - } else { - /* move (e.g. "Ndf3", "N2c3", "e2e4") */ - if (isfile(mstr[1])) { - move->fromfile = fileidx(mstr[1]); - if (move->piece == PAWN) { - move->piece = 0; - } - } else { - move->fromrow = rowidx(mstr[1]); - } - } - move->tofile = fileidx(mstr[2]); - move->torow = rowidx(mstr[3]); - } else if (len == 5) { - if (strcmp(mstr, "O-O-O") == 0) { - /* queen side castling "O-O-O" */ - move->piece = KING; - move->fromfile = fileidx('e'); - move->tofile = fileidx('c'); - move->fromrow = move->torow = color == WHITE ? 0 : 7; - } else { - move->piece = getpiece(mstr[0]); - if (mstr[2] == 'x') { - move->capture = 1; - if (move->piece) { - /* capture (e.g. "Ndxf3") */ - move->fromfile = fileidx(mstr[1]); - } else { - /* long notation capture (e.g. "e5xf6") */ - move->piece = PAWN; - move->fromfile = fileidx(mstr[0]); - move->fromrow = rowidx(mstr[1]); - } - } else { - /* long notation move (e.g. "Nc5a4") */ - move->fromfile = fileidx(mstr[1]); - move->fromrow = rowidx(mstr[2]); - } - move->tofile = fileidx(mstr[3]); - move->torow = rowidx(mstr[4]); - } - } else if (len == 6) { - /* long notation capture (e.g. "Nc5xf3") */ - if (mstr[3] == 'x') { - move->capture = 1; - move->piece = getpiece(mstr[0]); - move->fromfile = fileidx(mstr[1]); - move->fromrow = rowidx(mstr[2]); - move->tofile = fileidx(mstr[4]); - move->torow = rowidx(mstr[5]); - } - } - - - if (move->piece) { - if (move->piece == PAWN - && move->torow == (color==WHITE?7:0) - && !move->promotion) { - return NEED_PROMOTION; - } - - move->piece |= color; - if (move->fromfile == POS_UNSPECIFIED - || move->fromrow == POS_UNSPECIFIED) { - return getlocation(gamestate, move); - } else { - return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION; - } - } else { - return INVALID_MOVE_SYNTAX; - } -} - -_Bool is_protected(GameState *gamestate, uint8_t row, uint8_t file, - uint8_t color) { - - Move threats[16]; - uint8_t threatcount; - if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) { - for (int i = 0 ; i < threatcount ; i++) { - if (threats[i].piece != (color|KING)) { - return 1; - } - } - return 0; - } else { - return 0; - } -} - -uint16_t remaining_movetime(GameInfo *gameinfo, GameState *gamestate, - uint8_t color) { - if (!gameinfo->timecontrol) { - return 0; - } - - if (gamestate->movelist) { - uint16_t time = gameinfo->time; - suseconds_t micros = 0; - - MoveList *movelist = color == WHITE ? - gamestate->movelist : gamestate->movelist->next; - - while (movelist) { - time += gameinfo->addtime; - - struct movetimeval *movetime = &(movelist->move.movetime); - if (movetime->tv_sec >= time) { - return 0; - } - - time -= movetime->tv_sec; - micros += movetime->tv_usec; - - movelist = movelist->next ? movelist->next->next : NULL; - } - - time_t sec; - movelist = gamestate->lastmove; - if ((movelist->move.piece & COLOR_MASK) != color) { - struct movetimeval *lastmovetstamp = &(movelist->move.timestamp); - struct timeval currenttstamp; - gettimeofday(¤ttstamp, NULL); - micros += currenttstamp.tv_usec - lastmovetstamp->tv_usec; - sec = currenttstamp.tv_sec - lastmovetstamp->tv_sec; - if (sec >= time) { - return 0; - } - - time -= sec; - } - - sec = micros / 1e6L; - - if (sec >= time) { - return 0; - } - - time -= sec; - - return time; - } else { - return gameinfo->time; - } -} diff -r 60decfc9ad13 -r 7dd4fd1e7071 test/ctest.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/ctest.c Mon Oct 03 12:56:28 2022 +0200 @@ -0,0 +1,391 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2015 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "crypto.h" +#include "webdav.h" + +#define MACRO1337 1337L + +/* -------------------- This is a testing file. -------------------------- */ +/* +time_t util_parse_creationdate(char *str) { + // example: 2012-11-29T21:35:35Z + if(!str) { + return 0; + } + // TODO + return 0; +} +*/ +time_t util_parse_lastmodified(char *str) { + // example: Thu, 29 Nov 2012 21:35:35 GMT + if(!str) { + return 0; + } else { + return curl_getdate(str, NULL); + } +} + +int util_getboolean(char *v) { + if(v[0] == 'T' || v[0] == 't') { + return 1; + } + return 0; +} + +int util_strtoint(char *str, int64_t *value) { + char *end; + int64_t val = strtoll(str, &end, 0); + if(strlen(end) == 0) { + *value = val; + return 1; + } else { + return 0; + } +} + +char* util_url_path(char *url) { + char *path = NULL; + size_t len = strlen(url); + int slashcount = 0; + int slmax; + if(len > 7 && !strncasecmp(url, "http://", 7)) { + slmax = 3; + } else if(len > 8 && !strncasecmp(url, "https://", 8)) { + slmax = 3; + } else { + slmax = 1; + } + char c; + for(int i=0;ihandle, url, strlen(url), NULL); + char *ret = strdup(unesc); + curl_free(unesc); + return ret; +} + +char* util_resource_name(char *url) { + int si = 0; + int osi = 0; + int i = 0; + int p = 0; + char c; + while((c = url[i]) != 0) { + if(c == '/') { + osi = si; + si = i; + p = 1; + } + i++; + } + + char *name = url + si + p; + if(name[0] == 0) { + name = url + osi + p; + if(name[0] == 0) { + return url; + } + } + + return name; +} + +int util_mkdir(char *path, mode_t mode) { +#ifdef _WIN32 + return mkdir(path); +#else + return mkdir(path, mode); +#endif +} + +char* util_concat_path(char *url_base, char *p) { + sstr_t base = sstr(url_base); + sstr_t path; + if(p) { + path = sstr(p); + } else { + path = sstrn("", 0); + } + + int add_separator = 0; + if(base.ptr[base.length-1] == '/') { + if(path.ptr[0] == '/') { + base.length--; + } + } else { + if(path.length == 0 || path.ptr[0] != '/') { + add_separator = 1; + } + } + + sstr_t url; + if(add_separator) { + url = sstrcat(3, base, sstr("/"), path); + } else { + url = sstrcat(2, base, path); + } + + return url.ptr; +} + +void util_set_url(DavSession *sn, char *href) { + sstr_t base = sstr(sn->base_url); + sstr_t href_str = sstr(href); + + char *base_path = util_url_path(sn->base_url); + base.length -= strlen(base_path); + + sstr_t url = sstrcat(2, base, href_str); + + curl_easy_setopt(sn->handle, CURLOPT_URL, url.ptr); + free(url.ptr); +} + +char* util_path_to_url(DavSession *sn, char *path) { + char *space = malloc(256); + UcxBuffer *url = ucx_buffer_new(space, 256, UCX_BUFFER_AUTOEXTEND); + + // add base url + ucx_buffer_write(sn->base_url, 1, strlen(sn->base_url), url); + // remove trailing slash + ucx_buffer_seek(url, -1, SEEK_CUR); + + sstr_t p = sstr(path); + ssize_t ntk = 0; + sstr_t *tks = sstrsplit(p, S("/"), &ntk); + + for(int i=0;i 0) { + char *esc = curl_easy_escape(sn->handle, node.ptr, node.length); + ucx_buffer_putc(url, '/'); + ucx_buffer_write(esc, 1, strlen(esc), url); + curl_free(esc); + } + free(node.ptr); + } + free(tks); + if(path[p.length-1] == '/') { + ucx_buffer_putc(url, '/'); + } + ucx_buffer_putc(url, 0); + + space = url->space; + ucx_buffer_free(url); + + return space; +} + +char* util_parent_path(char *path) { + char *name = util_resource_name(path); + size_t namelen = strlen(name); + size_t pathlen = strlen(path); + size_t parentlen = pathlen - namelen; + char *parent = malloc(parentlen + 1); + memcpy(parent, path, parentlen); + parent[parentlen] = '\0'; + return parent; +} + + +char* util_xml_get_text(xmlNode *elm) { + xmlNode *node = elm->children; + while(node) { + if(node->type == XML_TEXT_NODE) { + return (char*)node->content; + } + node = node->next; + } + return NULL; +} + + +char* util_base64decode(char *in) { + int len = 0; + return util_base64decode_len(in, &len); +} + +char* util_base64decode_len(char* in, int *outlen) { + size_t len = strlen(in); + char *out = calloc(1, len); + + BIO* b = BIO_new_mem_buf(in, len); + BIO *d = BIO_new(BIO_f_base64()); + BIO_set_flags(d, BIO_FLAGS_BASE64_NO_NL); + b = BIO_push(d, b); + + *outlen = BIO_read(b, out, len); + BIO_free_all(b); + + return out; +} + +char* util_base64encode(char *in, size_t len) { + BIO *b; + BIO *e; + BUF_MEM *mem; + + e = BIO_new(BIO_f_base64()); + b = BIO_new(BIO_s_mem()); + + e = BIO_push(e, b); + BIO_write(e, in, len); + BIO_flush(e); + + BIO_get_mem_ptr(e, &mem); + char *out = malloc(mem->length); + memcpy(out, mem->data, mem->length -1); + out[mem->length - 1] = '\0'; + + BIO_free_all(e); + + return out; +} + +char* util_encrypt_str(DavSession *sn, char *str, char *key) { + DavKey *k = dav_context_get_key(sn->context, key); + if(!k) { + // TODO: session error + return NULL; + } + + char *enc_str = aes_encrypt(str, k); + char *ret_str = dav_session_strdup(sn, enc_str); + free(enc_str); + return ret_str; +} + +/* commented out for testing reasons */ +/* +char* util_decrypt_str(DavSession *sn, char *str, char *key) { + DavKey *k = dav_context_get_key(sn->context, key); + if(!k) { + // TODO: session error + return NULL; + } + + char *dec_str = aes_decrypt(str, k); + char *ret_str = dav_session_strdup(sn, dec_str); + free(dec_str); + return ret_str; +} +*/ +char* util_random_str() { + unsigned char *str = malloc(25); + str[24] = '\0'; + + sstr_t t = S( + "01234567890" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + const unsigned char *table = (const unsigned char*)t.ptr; + + RAND_pseudo_bytes(str, 24); + for(int i=0;i<24;i++) { + int c = str[i] % t.length; + str[i] = table[c]; + } + + return (char*)str; +} + +/* + * gets a substring from 0 to the appearance of the token + * tokens are separated by space + * sets sub to the substring and returns the remaining string + */ +sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub) { + int i; + int token_start = -1; + int token_end = -1; + for(i=0;i<=str.length;i++) { + int c; + if(i == str.length) { + c = ' '; + } else { + c = str.ptr[i]; + } + if(c < 33) { + if(token_start != -1) { + token_end = i; + size_t len = token_end - token_start; + sstr_t tk = sstrsubsl(str, token_start, len); + //printf("token: {%.*s}\n", token.length, token.ptr); + if(!sstrcmp(tk, token)) { + *sub = sstrtrim(sstrsubsl(str, 0, token_start)); + break; + } + token_start = -1; + token_end = -1; + } + } else { + if(token_start == -1) { + token_start = i; + } + } + } + + if(i < str.length) { + return sstrtrim(sstrsubs(str, i)); + } else { + str.ptr = NULL; + str.length = 0; + return str; + } +} diff -r 60decfc9ad13 -r 7dd4fd1e7071 test/ctestfile.c --- a/test/ctestfile.c Mon Oct 03 12:27:10 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,391 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2015 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "utils.h" -#include "crypto.h" -#include "webdav.h" - -#define MACRO1337 1337L - -/* -------------------- This is a testing file. -------------------------- */ -/* -time_t util_parse_creationdate(char *str) { - // example: 2012-11-29T21:35:35Z - if(!str) { - return 0; - } - // TODO - return 0; -} -*/ -time_t util_parse_lastmodified(char *str) { - // example: Thu, 29 Nov 2012 21:35:35 GMT - if(!str) { - return 0; - } else { - return curl_getdate(str, NULL); - } -} - -int util_getboolean(char *v) { - if(v[0] == 'T' || v[0] == 't') { - return 1; - } - return 0; -} - -int util_strtoint(char *str, int64_t *value) { - char *end; - int64_t val = strtoll(str, &end, 0); - if(strlen(end) == 0) { - *value = val; - return 1; - } else { - return 0; - } -} - -char* util_url_path(char *url) { - char *path = NULL; - size_t len = strlen(url); - int slashcount = 0; - int slmax; - if(len > 7 && !strncasecmp(url, "http://", 7)) { - slmax = 3; - } else if(len > 8 && !strncasecmp(url, "https://", 8)) { - slmax = 3; - } else { - slmax = 1; - } - char c; - for(int i=0;ihandle, url, strlen(url), NULL); - char *ret = strdup(unesc); - curl_free(unesc); - return ret; -} - -char* util_resource_name(char *url) { - int si = 0; - int osi = 0; - int i = 0; - int p = 0; - char c; - while((c = url[i]) != 0) { - if(c == '/') { - osi = si; - si = i; - p = 1; - } - i++; - } - - char *name = url + si + p; - if(name[0] == 0) { - name = url + osi + p; - if(name[0] == 0) { - return url; - } - } - - return name; -} - -int util_mkdir(char *path, mode_t mode) { -#ifdef _WIN32 - return mkdir(path); -#else - return mkdir(path, mode); -#endif -} - -char* util_concat_path(char *url_base, char *p) { - sstr_t base = sstr(url_base); - sstr_t path; - if(p) { - path = sstr(p); - } else { - path = sstrn("", 0); - } - - int add_separator = 0; - if(base.ptr[base.length-1] == '/') { - if(path.ptr[0] == '/') { - base.length--; - } - } else { - if(path.length == 0 || path.ptr[0] != '/') { - add_separator = 1; - } - } - - sstr_t url; - if(add_separator) { - url = sstrcat(3, base, sstr("/"), path); - } else { - url = sstrcat(2, base, path); - } - - return url.ptr; -} - -void util_set_url(DavSession *sn, char *href) { - sstr_t base = sstr(sn->base_url); - sstr_t href_str = sstr(href); - - char *base_path = util_url_path(sn->base_url); - base.length -= strlen(base_path); - - sstr_t url = sstrcat(2, base, href_str); - - curl_easy_setopt(sn->handle, CURLOPT_URL, url.ptr); - free(url.ptr); -} - -char* util_path_to_url(DavSession *sn, char *path) { - char *space = malloc(256); - UcxBuffer *url = ucx_buffer_new(space, 256, UCX_BUFFER_AUTOEXTEND); - - // add base url - ucx_buffer_write(sn->base_url, 1, strlen(sn->base_url), url); - // remove trailing slash - ucx_buffer_seek(url, -1, SEEK_CUR); - - sstr_t p = sstr(path); - ssize_t ntk = 0; - sstr_t *tks = sstrsplit(p, S("/"), &ntk); - - for(int i=0;i 0) { - char *esc = curl_easy_escape(sn->handle, node.ptr, node.length); - ucx_buffer_putc(url, '/'); - ucx_buffer_write(esc, 1, strlen(esc), url); - curl_free(esc); - } - free(node.ptr); - } - free(tks); - if(path[p.length-1] == '/') { - ucx_buffer_putc(url, '/'); - } - ucx_buffer_putc(url, 0); - - space = url->space; - ucx_buffer_free(url); - - return space; -} - -char* util_parent_path(char *path) { - char *name = util_resource_name(path); - size_t namelen = strlen(name); - size_t pathlen = strlen(path); - size_t parentlen = pathlen - namelen; - char *parent = malloc(parentlen + 1); - memcpy(parent, path, parentlen); - parent[parentlen] = '\0'; - return parent; -} - - -char* util_xml_get_text(xmlNode *elm) { - xmlNode *node = elm->children; - while(node) { - if(node->type == XML_TEXT_NODE) { - return (char*)node->content; - } - node = node->next; - } - return NULL; -} - - -char* util_base64decode(char *in) { - int len = 0; - return util_base64decode_len(in, &len); -} - -char* util_base64decode_len(char* in, int *outlen) { - size_t len = strlen(in); - char *out = calloc(1, len); - - BIO* b = BIO_new_mem_buf(in, len); - BIO *d = BIO_new(BIO_f_base64()); - BIO_set_flags(d, BIO_FLAGS_BASE64_NO_NL); - b = BIO_push(d, b); - - *outlen = BIO_read(b, out, len); - BIO_free_all(b); - - return out; -} - -char* util_base64encode(char *in, size_t len) { - BIO *b; - BIO *e; - BUF_MEM *mem; - - e = BIO_new(BIO_f_base64()); - b = BIO_new(BIO_s_mem()); - - e = BIO_push(e, b); - BIO_write(e, in, len); - BIO_flush(e); - - BIO_get_mem_ptr(e, &mem); - char *out = malloc(mem->length); - memcpy(out, mem->data, mem->length -1); - out[mem->length - 1] = '\0'; - - BIO_free_all(e); - - return out; -} - -char* util_encrypt_str(DavSession *sn, char *str, char *key) { - DavKey *k = dav_context_get_key(sn->context, key); - if(!k) { - // TODO: session error - return NULL; - } - - char *enc_str = aes_encrypt(str, k); - char *ret_str = dav_session_strdup(sn, enc_str); - free(enc_str); - return ret_str; -} - -/* commented out for testing reasons */ -/* -char* util_decrypt_str(DavSession *sn, char *str, char *key) { - DavKey *k = dav_context_get_key(sn->context, key); - if(!k) { - // TODO: session error - return NULL; - } - - char *dec_str = aes_decrypt(str, k); - char *ret_str = dav_session_strdup(sn, dec_str); - free(dec_str); - return ret_str; -} -*/ -char* util_random_str() { - unsigned char *str = malloc(25); - str[24] = '\0'; - - sstr_t t = S( - "01234567890" - "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); - const unsigned char *table = (const unsigned char*)t.ptr; - - RAND_pseudo_bytes(str, 24); - for(int i=0;i<24;i++) { - int c = str[i] % t.length; - str[i] = table[c]; - } - - return (char*)str; -} - -/* - * gets a substring from 0 to the appearance of the token - * tokens are separated by space - * sets sub to the substring and returns the remaining string - */ -sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub) { - int i; - int token_start = -1; - int token_end = -1; - for(i=0;i<=str.length;i++) { - int c; - if(i == str.length) { - c = ' '; - } else { - c = str.ptr[i]; - } - if(c < 33) { - if(token_start != -1) { - token_end = i; - size_t len = token_end - token_start; - sstr_t tk = sstrsubsl(str, token_start, len); - //printf("token: {%.*s}\n", token.length, token.ptr); - if(!sstrcmp(tk, token)) { - *sub = sstrtrim(sstrsubsl(str, 0, token_start)); - break; - } - token_start = -1; - token_end = -1; - } - } else { - if(token_start == -1) { - token_start = i; - } - } - } - - if(i < str.length) { - return sstrtrim(sstrsubs(str, i)); - } else { - str.ptr = NULL; - str.length = 0; - return str; - } -} diff -r 60decfc9ad13 -r 7dd4fd1e7071 test/empty.c diff -r 60decfc9ad13 -r 7dd4fd1e7071 test/emptyfile.c diff -r 60decfc9ad13 -r 7dd4fd1e7071 test/golden-master/empty.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/golden-master/empty.html Mon Oct 03 12:56:28 2022 +0200 @@ -0,0 +1,53 @@ + + + + c2html + + + + +
+1 
+ + + diff -r 60decfc9ad13 -r 7dd4fd1e7071 test/golden-master/emptyfile.html --- a/test/golden-master/emptyfile.html Mon Oct 03 12:27:10 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,54 +0,0 @@ - - - - c2html - - - - -
-1 
-
- - - diff -r 60decfc9ad13 -r 7dd4fd1e7071 test/javatest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/javatest.java Mon Oct 03 12:56:28 2022 +0200 @@ -0,0 +1,176 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2014 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +package de.uapcore.sigred.doc.base; + +import de.uapcore.sigred.doc.Resources; +import de.uapcore.sigrapi.impl.Digraph; +import de.uapcore.sigrapi.impl.Graph; +import de.uapcore.sigrapi.IGraph; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import org.apache.xerces.impl.Constants; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; +import org.dom4j.Namespace; +import org.dom4j.QName; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.SAXReader; +import org.dom4j.io.XMLWriter; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +public abstract class AbstractGraphDocument + extends FileBackedDocument { + + protected static final Namespace NAMESPACE = Namespace.get("sigred", + "http://develop.uap-core.de/sigred/"); + + private static final + QName TAG_GRAPHDOC = QName.get("graph-document", NAMESPACE); + private static final + QName TAG_GRAPH = QName.get("graph", NAMESPACE); + private static final + QName TAG_DIGRAPH = QName.get("digraph", NAMESPACE); + private static final + QName TAG_METADATA = QName.get("metadata", NAMESPACE); + + protected final T graph; + + private final GraphDocumentMetadata metadata; + + public AbstractGraphDocument(Class graphType) { + T g; + try { + g = graphType.newInstance(); + } catch (ReflectiveOperationException e) { + assert false; + g = null; // for the compiler + } + graph = g; + metadata = new GraphDocumentMetadata(); + } + + public T getGraph() { + return graph; + } + + public GraphDocumentMetadata getMetadata() { + return metadata; + } + + protected abstract void writeGraph(Element rootNode) throws IOException; + protected abstract void readGraph(Element rootNode) throws IOException; + + @Override + public void writeTo(OutputStream out) throws IOException { + Document doc = DocumentHelper.createDocument(); + + Element rootNode = doc.addElement(TAG_GRAPHDOC); + + Element metadataNode = rootNode.addElement(TAG_METADATA); + + metadata.write(metadataNode); + + if (graph instanceof Graph) { + writeGraph(rootNode.addElement(TAG_GRAPH)); + } else if (graph instanceof Digraph) { + writeGraph(rootNode.addElement(TAG_DIGRAPH)); + } else { + throw new IOException("unsupported graph type"); + } + + XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint()); + writer.write(doc); + writer.flush(); + } + + @Override + public void readFrom(InputStream in) throws IOException { + try { + SAXReader reader = new SAXReader(true); + reader.setStripWhitespaceText(true); + + reader.setFeature(Constants.XERCES_FEATURE_PREFIX+ + Constants.SCHEMA_VALIDATION_FEATURE, true); + reader.setProperty(Constants.XERCES_PROPERTY_PREFIX + + Constants.SCHEMA_LOCATION, String.format("%s %s", + NAMESPACE.getURI(), Resources.class.getResource( + "graph-document.xsd").toExternalForm())); + + final AtomicBoolean passed = new AtomicBoolean(true); + final AtomicReference xmlerror = new AtomicReference<>(); + // TODO: we should do more detailed error handling here + reader.setErrorHandler(new ErrorHandler() { + @Override + public void warning(SAXParseException exception) throws SAXException { + } + + @Override + public void error(SAXParseException exception) throws SAXException { + xmlerror.set(exception); + passed.set(false); + } + + @Override + public void fatalError(SAXParseException exception) throws SAXException { + xmlerror.set(exception); + passed.set(false); + } + + }); + Document doc = reader.read(in); + if (!passed.get()) { + // TODO: provide details (maybe via separate error object?) + throw xmlerror.get(); + } + + doc.normalize(); + + Element root = doc.getRootElement(); + metadata.read(root.element(TAG_METADATA)); + + if (graph instanceof Graph) { + readGraph(root.element(TAG_GRAPH)); + } else if (graph instanceof Digraph) { + readGraph(root.element(TAG_DIGRAPH)); + } else { + throw new IOException("unsupported graph type"); + } + } catch (DocumentException | SAXException ex) { + throw new IOException(ex); + } + } +} diff -r 60decfc9ad13 -r 7dd4fd1e7071 test/javatestfile.java --- a/test/javatestfile.java Mon Oct 03 12:27:10 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,176 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2014 Mike Becker. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - */ - -package de.uapcore.sigred.doc.base; - -import de.uapcore.sigred.doc.Resources; -import de.uapcore.sigrapi.impl.Digraph; -import de.uapcore.sigrapi.impl.Graph; -import de.uapcore.sigrapi.IGraph; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import org.apache.xerces.impl.Constants; -import org.dom4j.Document; -import org.dom4j.DocumentException; -import org.dom4j.DocumentHelper; -import org.dom4j.Element; -import org.dom4j.Namespace; -import org.dom4j.QName; -import org.dom4j.io.OutputFormat; -import org.dom4j.io.SAXReader; -import org.dom4j.io.XMLWriter; -import org.xml.sax.ErrorHandler; -import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; - -public abstract class AbstractGraphDocument - extends FileBackedDocument { - - protected static final Namespace NAMESPACE = Namespace.get("sigred", - "http://develop.uap-core.de/sigred/"); - - private static final - QName TAG_GRAPHDOC = QName.get("graph-document", NAMESPACE); - private static final - QName TAG_GRAPH = QName.get("graph", NAMESPACE); - private static final - QName TAG_DIGRAPH = QName.get("digraph", NAMESPACE); - private static final - QName TAG_METADATA = QName.get("metadata", NAMESPACE); - - protected final T graph; - - private final GraphDocumentMetadata metadata; - - public AbstractGraphDocument(Class graphType) { - T g; - try { - g = graphType.newInstance(); - } catch (ReflectiveOperationException e) { - assert false; - g = null; // for the compiler - } - graph = g; - metadata = new GraphDocumentMetadata(); - } - - public T getGraph() { - return graph; - } - - public GraphDocumentMetadata getMetadata() { - return metadata; - } - - protected abstract void writeGraph(Element rootNode) throws IOException; - protected abstract void readGraph(Element rootNode) throws IOException; - - @Override - public void writeTo(OutputStream out) throws IOException { - Document doc = DocumentHelper.createDocument(); - - Element rootNode = doc.addElement(TAG_GRAPHDOC); - - Element metadataNode = rootNode.addElement(TAG_METADATA); - - metadata.write(metadataNode); - - if (graph instanceof Graph) { - writeGraph(rootNode.addElement(TAG_GRAPH)); - } else if (graph instanceof Digraph) { - writeGraph(rootNode.addElement(TAG_DIGRAPH)); - } else { - throw new IOException("unsupported graph type"); - } - - XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint()); - writer.write(doc); - writer.flush(); - } - - @Override - public void readFrom(InputStream in) throws IOException { - try { - SAXReader reader = new SAXReader(true); - reader.setStripWhitespaceText(true); - - reader.setFeature(Constants.XERCES_FEATURE_PREFIX+ - Constants.SCHEMA_VALIDATION_FEATURE, true); - reader.setProperty(Constants.XERCES_PROPERTY_PREFIX + - Constants.SCHEMA_LOCATION, String.format("%s %s", - NAMESPACE.getURI(), Resources.class.getResource( - "graph-document.xsd").toExternalForm())); - - final AtomicBoolean passed = new AtomicBoolean(true); - final AtomicReference xmlerror = new AtomicReference<>(); - // TODO: we should do more detailed error handling here - reader.setErrorHandler(new ErrorHandler() { - @Override - public void warning(SAXParseException exception) throws SAXException { - } - - @Override - public void error(SAXParseException exception) throws SAXException { - xmlerror.set(exception); - passed.set(false); - } - - @Override - public void fatalError(SAXParseException exception) throws SAXException { - xmlerror.set(exception); - passed.set(false); - } - - }); - Document doc = reader.read(in); - if (!passed.get()) { - // TODO: provide details (maybe via separate error object?) - throw xmlerror.get(); - } - - doc.normalize(); - - Element root = doc.getRootElement(); - metadata.read(root.element(TAG_METADATA)); - - if (graph instanceof Graph) { - readGraph(root.element(TAG_GRAPH)); - } else if (graph instanceof Digraph) { - readGraph(root.element(TAG_DIGRAPH)); - } else { - throw new IOException("unsupported graph type"); - } - } catch (DocumentException | SAXException ex) { - throw new IOException(ex); - } - } -} diff -r 60decfc9ad13 -r 7dd4fd1e7071 test/plain.csp --- a/test/plain.csp Mon Oct 03 12:27:10 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ - - - - diff -r 60decfc9ad13 -r 7dd4fd1e7071 test/plain.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/plain.txt Mon Oct 03 12:56:28 2022 +0200 @@ -0,0 +1,6 @@ + + + +