rewrite Makefile to work with different getopt implementations

Mon, 03 Oct 2022 12:56:28 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 03 Oct 2022 12:56:28 +0200
changeset 65
7dd4fd1e7071
parent 64
60decfc9ad13
child 67
5da2cb5aea6b

rewrite Makefile to work with different getopt implementations

Makefile file | annotate | diff | comparison | revisions
test/bigtest.c file | annotate | diff | comparison | revisions
test/bigtestfile.c file | annotate | diff | comparison | revisions
test/ctest.c file | annotate | diff | comparison | revisions
test/ctestfile.c file | annotate | diff | comparison | revisions
test/empty.c file | annotate | diff | comparison | revisions
test/emptyfile.c file | annotate | diff | comparison | revisions
test/golden-master/empty.html file | annotate | diff | comparison | revisions
test/golden-master/emptyfile.html file | annotate | diff | comparison | revisions
test/javatest.java file | annotate | diff | comparison | revisions
test/javatestfile.java file | annotate | diff | comparison | revisions
test/plain.csp file | annotate | diff | comparison | revisions
test/plain.txt file | annotate | diff | comparison | revisions
     1.1 --- a/Makefile	Mon Oct 03 12:27:10 2022 +0200
     1.2 +++ b/Makefile	Mon Oct 03 12:56:28 2022 +0200
     1.3 @@ -47,23 +47,37 @@
     1.4  	
     1.5  build:
     1.6  	$(MKDIR) $@
     1.7 -	
     1.8 -test: all
     1.9 -	./build/$(BIN) test/ctestfile.c -o build/ctest.html \
    1.10 -	-H test/header.html -F test/footer.html
    1.11 -	./build/$(BIN) -j test/javatestfile.java -o build/javatest.html \
    1.12 -	-H test/jheader.html -F test/footer.html
    1.13 -	./build/$(BIN) test/bigtestfile.c -o build/bigtest.html \
    1.14 -	-H test/header.html -F test/footer.html
    1.15 -	./build/$(BIN) -p test/plain.csp -o build/plain.html \
    1.16 -	-H test/header.html -F test/footer.html
    1.17 -	./build/$(BIN) -p test/emptyfile.c -o build/emptyfile.html \
    1.18 -    	-H test/header.html -F test/footer.html
    1.19 -	diff build/ctest.html test/golden-master/ctest.html && \
    1.20 -	diff build/javatest.html test/golden-master/javatest.html && \
    1.21 -	diff build/bigtest.html test/golden-master/bigtest.html && \
    1.22 -	diff build/plain.html test/golden-master/plain.html && \
    1.23 -	diff build/emptyfile.html test/golden-master/emptyfile.html
    1.24 +
    1.25 +test-c: all
    1.26 +	for f in ctest bigtest empty ; do \
    1.27 +  		echo "test/$$f.c" ; \
    1.28 +  		./build/$(BIN) -o "build/$$f.html" \
    1.29 +        	-H test/header.html \
    1.30 +        	-F test/footer.html \
    1.31 +        	"test/$$f.c" && \
    1.32 +        diff "build/$$f.html" "test/golden-master/$$f.html" ; \
    1.33 +    done
    1.34 +
    1.35 +test-java: all
    1.36 +	for f in javatest ; do \
    1.37 +  		./build/$(BIN) -j -o "build/$$f.html" \
    1.38 +        	-H test/jheader.html \
    1.39 +        	-F test/footer.html \
    1.40 +        	"test/$$f.java" && \
    1.41 +        diff "build/$$f.html" "test/golden-master/$$f.html" ; \
    1.42 +    done
    1.43 +
    1.44 +test-plain: all
    1.45 +	for f in plain ; do \
    1.46 +  		./build/$(BIN) -p -o "build/$$f.html" \
    1.47 +        	-H test/header.html \
    1.48 +        	-F test/footer.html \
    1.49 +        	"test/$$f.txt" && \
    1.50 +        diff "build/$$f.html" "test/golden-master/$$f.html" ; \
    1.51 +    done
    1.52 +
    1.53 +test: test-c test-java test-plain
    1.54 +	@echo "Tests successful."
    1.55  	
    1.56  clean:
    1.57  	$(RM) $(RMFLAGS) build
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/test/bigtest.c	Mon Oct 03 12:56:28 2022 +0200
     2.3 @@ -0,0 +1,808 @@
     2.4 +/*
     2.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     2.6 + *
     2.7 + * Copyright 2014 Mike Becker. All rights reserved.
     2.8 + *
     2.9 + * Redistribution and use in source and binary forms, with or without
    2.10 + * modification, are permitted provided that the following conditions are met:
    2.11 + *
    2.12 + *   1. Redistributions of source code must retain the above copyright
    2.13 + *      notice, this list of conditions and the following disclaimer.
    2.14 + *
    2.15 + *   2. Redistributions in binary form must reproduce the above copyright
    2.16 + *      notice, this list of conditions and the following disclaimer in the
    2.17 + *      documentation and/or other materials provided with the distribution.
    2.18 + *
    2.19 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    2.20 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    2.21 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    2.22 + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    2.23 + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    2.24 + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    2.25 + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    2.26 + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    2.27 + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    2.28 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    2.29 + * POSSIBILITY OF SUCH DAMAGE.
    2.30 + *
    2.31 + */
    2.32 +
    2.33 +#include "rules.h"
    2.34 +#include "chess.h"
    2.35 +#include <string.h>
    2.36 +#include <stdlib.h>
    2.37 +#include <sys/time.h>
    2.38 +
    2.39 +static GameState gamestate_copy_sim(GameState *gamestate) {
    2.40 +    GameState simulation = *gamestate;
    2.41 +    if (simulation.lastmove) {
    2.42 +        MoveList *lastmovecopy = malloc(sizeof(MoveList));
    2.43 +        *lastmovecopy = *(simulation.lastmove);
    2.44 +        simulation.movelist = simulation.lastmove = lastmovecopy;
    2.45 +    }
    2.46 +
    2.47 +    return simulation;
    2.48 +}
    2.49 +
    2.50 +void gamestate_init(GameState *gamestate) {
    2.51 +    memset(gamestate, 0, sizeof(GameState));
    2.52 +    
    2.53 +    Board initboard = {
    2.54 +        {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
    2.55 +        {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
    2.56 +        {0,     0,       0,       0,      0,     0,       0,       0},
    2.57 +        {0,     0,       0,       0,      0,     0,       0,       0},
    2.58 +        {0,     0,       0,       0,      0,     0,       0,       0},
    2.59 +        {0,     0,       0,       0,      0,     0,       0,       0},
    2.60 +        {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
    2.61 +        {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
    2.62 +    };
    2.63 +    memcpy(gamestate->board, initboard, sizeof(Board));
    2.64 +}
    2.65 +
    2.66 +void gamestate_cleanup(GameState *gamestate) {
    2.67 +    MoveList *elem;
    2.68 +    elem = gamestate->movelist;
    2.69 +    while (elem) {
    2.70 +        MoveList *cur = elem;
    2.71 +        elem = elem->next;
    2.72 +        free(cur);
    2.73 +    };
    2.74 +}
    2.75 +
    2.76 +/* MUST be called IMMEDIATLY after applying a move to work correctly */
    2.77 +static void format_move(GameState *gamestate, Move *move) {
    2.78 +    char *string = move->string;
    2.79 +    
    2.80 +    /* at least 8 characters should be available, wipe them out */
    2.81 +    memset(string, 0, 8);
    2.82 +    
    2.83 +    /* special formats for castling */
    2.84 +    if ((move->piece&PIECE_MASK) == KING &&
    2.85 +            abs(move->tofile-move->fromfile) == 2) {
    2.86 +        if (move->tofile==fileidx('c')) {
    2.87 +            memcpy(string, "O-O-O", 5);
    2.88 +        } else {
    2.89 +            memcpy(string, "O-O", 3);
    2.90 +        }
    2.91 +    }
    2.92 +
    2.93 +    /* start by notating the piece character */
    2.94 +    string[0] = getpiecechr(move->piece);
    2.95 +    int idx = string[0] ? 1 : 0;
    2.96 +    
    2.97 +    /* find out how many source information we do need */
    2.98 +    uint8_t piece = move->piece & PIECE_MASK;
    2.99 +    if (piece == PAWN) {
   2.100 +        if (move->capture) {
   2.101 +            string[idx++] = filechr(move->fromfile);
   2.102 +        }
   2.103 +    } else if (piece != KING) {
   2.104 +        Move threats[16];
   2.105 +        uint8_t threatcount;
   2.106 +        get_real_threats(gamestate, move->torow, move->tofile,
   2.107 +            move->piece&COLOR_MASK, threats, &threatcount);
   2.108 +        if (threatcount > 1) {
   2.109 +            int ambrows = 0, ambfiles = 0;
   2.110 +            for (uint8_t i = 0 ; i < threatcount ; i++) {
   2.111 +                if (threats[i].fromrow == move->fromrow) {
   2.112 +                    ambrows++;
   2.113 +                }
   2.114 +                if (threats[i].fromfile == move->fromfile) {
   2.115 +                    ambfiles++;
   2.116 +                }
   2.117 +            }
   2.118 +            /* ambiguous row, name file */
   2.119 +            if (ambrows > 1) {
   2.120 +                string[idx++] = filechr(move->fromfile);
   2.121 +            }
   2.122 +            /* ambiguous file, name row */
   2.123 +            if (ambfiles > 1) {
   2.124 +                string[idx++] = filechr(move->fromrow);
   2.125 +            }
   2.126 +        }
   2.127 +    }
   2.128 +    
   2.129 +    /* capturing? */
   2.130 +    if (move->capture) {
   2.131 +        string[idx++] = 'x';
   2.132 +    }
   2.133 +    
   2.134 +    /* destination */
   2.135 +    string[idx++] = filechr(move->tofile);
   2.136 +    string[idx++] = rowchr(move->torow);
   2.137 +    
   2.138 +    /* promotion? */
   2.139 +    if (move->promotion) {
   2.140 +        string[idx++] = '=';
   2.141 +        string[idx++] = getpiecechr(move->promotion);
   2.142 +    }
   2.143 +    
   2.144 +    /* check? */
   2.145 +    if (move->check) {
   2.146 +        /* works only, if this function is called when applying the move */
   2.147 +        string[idx++] = gamestate->checkmate?'#':'+';
   2.148 +    }
   2.149 +}
   2.150 +
   2.151 +static void addmove(GameState* gamestate, Move *move) {
   2.152 +    MoveList *elem = malloc(sizeof(MoveList));
   2.153 +    elem->next = NULL;
   2.154 +    elem->move = *move;
   2.155 +    
   2.156 +    struct timeval curtimestamp;
   2.157 +    gettimeofday(&curtimestamp, NULL);
   2.158 +    elem->move.timestamp.tv_sec = curtimestamp.tv_sec;
   2.159 +    elem->move.timestamp.tv_usec = curtimestamp.tv_usec;
   2.160 +    
   2.161 +    if (gamestate->lastmove) {
   2.162 +        struct movetimeval *lasttstamp = &(gamestate->lastmove->move.timestamp);
   2.163 +        uint64_t sec = curtimestamp.tv_sec - lasttstamp->tv_sec;
   2.164 +        suseconds_t micros;
   2.165 +        if (curtimestamp.tv_usec < lasttstamp->tv_usec) {
   2.166 +            micros = 1e6L-(lasttstamp->tv_usec - curtimestamp.tv_usec);
   2.167 +            sec--;
   2.168 +        } else {
   2.169 +            micros = curtimestamp.tv_usec - lasttstamp->tv_usec;
   2.170 +        }
   2.171 +        
   2.172 +        elem->move.movetime.tv_sec = sec;
   2.173 +        elem->move.movetime.tv_usec = micros;
   2.174 +        
   2.175 +        gamestate->lastmove->next = elem;
   2.176 +        gamestate->lastmove = elem;
   2.177 +    } else {
   2.178 +        elem->move.movetime.tv_usec = 0;
   2.179 +        elem->move.movetime.tv_sec = 0;
   2.180 +        gamestate->movelist = gamestate->lastmove = elem;
   2.181 +    }
   2.182 +}
   2.183 +
   2.184 +char getpiecechr(uint8_t piece) {
   2.185 +    switch (piece & PIECE_MASK) {
   2.186 +    case ROOK: return 'R';
   2.187 +    case KNIGHT: return 'N';
   2.188 +    case BISHOP: return 'B';
   2.189 +    case QUEEN: return 'Q';
   2.190 +    case KING: return 'K';
   2.191 +    default: return '\0';
   2.192 +    }
   2.193 +}
   2.194 +
   2.195 +uint8_t getpiece(char c) {
   2.196 +    switch (c) {
   2.197 +        case 'R': return ROOK;
   2.198 +        case 'N': return KNIGHT;
   2.199 +        case 'B': return BISHOP;
   2.200 +        case 'Q': return QUEEN;
   2.201 +        case 'K': return KING;
   2.202 +        default: return 0;
   2.203 +    }
   2.204 +}
   2.205 +
   2.206 +static void apply_move_impl(GameState *gamestate, Move *move, _Bool simulate) {
   2.207 +    uint8_t piece = move->piece & PIECE_MASK;
   2.208 +    uint8_t color = move->piece & COLOR_MASK;
   2.209 +    
   2.210 +    /* en passant capture */
   2.211 +    if (move->capture && piece == PAWN &&
   2.212 +        mdst(gamestate->board, move) == 0) {
   2.213 +        gamestate->board[move->fromrow][move->tofile] = 0;
   2.214 +    }
   2.215 +    
   2.216 +    /* remove old en passant threats */
   2.217 +    for (uint8_t file = 0 ; file < 8 ; file++) {
   2.218 +        gamestate->board[3][file] &= ~ENPASSANT_THREAT;
   2.219 +        gamestate->board[4][file] &= ~ENPASSANT_THREAT;
   2.220 +    }
   2.221 +    
   2.222 +    /* add new en passant threat */
   2.223 +    if (piece == PAWN && (
   2.224 +        (move->fromrow == 1 && move->torow == 3) ||
   2.225 +        (move->fromrow == 6 && move->torow == 4))) {
   2.226 +        move->piece |= ENPASSANT_THREAT;
   2.227 +    }
   2.228 +    
   2.229 +    /* move (and maybe capture or promote) */
   2.230 +    msrc(gamestate->board, move) = 0;
   2.231 +    if (move->promotion) {
   2.232 +        mdst(gamestate->board, move) = move->promotion;
   2.233 +    } else {
   2.234 +        mdst(gamestate->board, move) = move->piece;
   2.235 +    }
   2.236 +    
   2.237 +    /* castling */
   2.238 +    if (piece == KING && move->fromfile == fileidx('e')) {
   2.239 +        
   2.240 +        if (move->tofile == fileidx('g')) {
   2.241 +            gamestate->board[move->torow][fileidx('h')] = 0;
   2.242 +            gamestate->board[move->torow][fileidx('f')] = color|ROOK;
   2.243 +        } else if (move->tofile == fileidx('c')) {
   2.244 +            gamestate->board[move->torow][fileidx('a')] = 0;
   2.245 +            gamestate->board[move->torow][fileidx('d')] = color|ROOK;
   2.246 +        }
   2.247 +    }
   2.248 +
   2.249 +    if (!simulate) {
   2.250 +        if (!move->string[0]) {
   2.251 +            format_move(gamestate, move);
   2.252 +        }
   2.253 +    }
   2.254 +    /* add move, even in simulation (checkmate test needs it) */
   2.255 +    addmove(gamestate, move);
   2.256 +}
   2.257 +
   2.258 +void apply_move(GameState *gamestate, Move *move) {
   2.259 +    apply_move_impl(gamestate, move, 0);
   2.260 +}
   2.261 +
   2.262 +static int validate_move_rules(GameState *gamestate, Move *move) {
   2.263 +    /* validate indices (don't trust opponent) */
   2.264 +    if (!chkidx(move)) {
   2.265 +        return INVALID_POSITION;
   2.266 +    }
   2.267 +    
   2.268 +    /* must move */
   2.269 +    if (move->fromfile == move->tofile && move->fromrow == move->torow) {
   2.270 +        return INVALID_MOVE_SYNTAX;
   2.271 +    }
   2.272 +    
   2.273 +    /* does piece exist */
   2.274 +    if ((msrc(gamestate->board, move)&(PIECE_MASK|COLOR_MASK))
   2.275 +           != (move->piece&(PIECE_MASK|COLOR_MASK))) {
   2.276 +        return INVALID_POSITION;
   2.277 +    }
   2.278 +    
   2.279 +    /* can't capture own pieces */
   2.280 +    if ((mdst(gamestate->board, move) & COLOR_MASK)
   2.281 +            == (move->piece & COLOR_MASK)) {
   2.282 +        return RULES_VIOLATED;
   2.283 +    }
   2.284 +    
   2.285 +    /* must capture, if and only if destination is occupied */
   2.286 +    if ((mdst(gamestate->board, move) == 0 && move->capture) ||
   2.287 +            (mdst(gamestate->board, move) != 0 && !move->capture)) {
   2.288 +        return INVALID_MOVE_SYNTAX;
   2.289 +    }
   2.290 +    
   2.291 +    /* validate individual rules */
   2.292 +    _Bool chkrules;
   2.293 +    switch (move->piece & PIECE_MASK) {
   2.294 +    case PAWN:
   2.295 +        chkrules = pawn_chkrules(gamestate, move) &&
   2.296 +            !pawn_isblocked(gamestate, move);
   2.297 +        break;
   2.298 +    case ROOK:
   2.299 +        chkrules = rook_chkrules(move) &&
   2.300 +            !rook_isblocked(gamestate, move);
   2.301 +        break;
   2.302 +    case KNIGHT:
   2.303 +        chkrules = knight_chkrules(move); /* knight is never blocked */
   2.304 +        break;
   2.305 +    case BISHOP:
   2.306 +        chkrules = bishop_chkrules(move) &&
   2.307 +            !bishop_isblocked(gamestate, move);
   2.308 +        break;
   2.309 +    case QUEEN:
   2.310 +        chkrules = queen_chkrules(move) &&
   2.311 +            !queen_isblocked(gamestate, move);
   2.312 +        break;
   2.313 +    case KING:
   2.314 +        chkrules = king_chkrules(gamestate, move) &&
   2.315 +            !king_isblocked(gamestate, move);
   2.316 +        break;
   2.317 +    default:
   2.318 +        return INVALID_MOVE_SYNTAX;
   2.319 +    }
   2.320 +    
   2.321 +    return chkrules ? VALID_MOVE_SEMANTICS : RULES_VIOLATED;
   2.322 +}
   2.323 +
   2.324 +int validate_move(GameState *gamestate, Move *move) {
   2.325 +    
   2.326 +    int result = validate_move_rules(gamestate, move);
   2.327 +    
   2.328 +    /* cancel processing to save resources */
   2.329 +    if (result != VALID_MOVE_SEMANTICS) {
   2.330 +        return result;
   2.331 +    }
   2.332 +    
   2.333 +    /* find kings for check validation */
   2.334 +    uint8_t piececolor = (move->piece & COLOR_MASK);
   2.335 +    
   2.336 +    uint8_t mykingfile = 0, mykingrow = 0, opkingfile = 0, opkingrow = 0;
   2.337 +    for (uint8_t row = 0 ; row < 8 ; row++) {
   2.338 +        for (uint8_t file = 0 ; file < 8 ; file++) {
   2.339 +            if (gamestate->board[row][file] ==
   2.340 +                    (piececolor == WHITE?WKING:BKING)) {
   2.341 +                mykingfile = file;
   2.342 +                mykingrow = row;
   2.343 +            } else if (gamestate->board[row][file] ==
   2.344 +                    (piececolor == WHITE?BKING:WKING)) {
   2.345 +                opkingfile = file;
   2.346 +                opkingrow = row;
   2.347 +            }
   2.348 +        }
   2.349 +    }
   2.350 +    
   2.351 +    /* simulate move for check validation */
   2.352 +    GameState simulation = gamestate_copy_sim(gamestate);
   2.353 +    Move simmove = *move;
   2.354 +    apply_move_impl(&simulation, &simmove, 1);
   2.355 +    
   2.356 +    /* don't move into or stay in check position */
   2.357 +    if (is_covered(&simulation, mykingrow, mykingfile,
   2.358 +        opponent_color(piececolor))) {
   2.359 +        
   2.360 +        gamestate_cleanup(&simulation);
   2.361 +        if ((move->piece & PIECE_MASK) == KING) {
   2.362 +            return KING_MOVES_INTO_CHECK;
   2.363 +        } else {
   2.364 +            /* last move is always not null in this case */
   2.365 +            return gamestate->lastmove->move.check ?
   2.366 +                KING_IN_CHECK : PIECE_PINNED;
   2.367 +        }
   2.368 +    }
   2.369 +    
   2.370 +    /* correct check and checkmate flags (move is still valid) */
   2.371 +    Move threats[16];
   2.372 +    uint8_t threatcount;
   2.373 +    move->check = get_threats(&simulation, opkingrow, opkingfile,
   2.374 +        piececolor, threats, &threatcount);
   2.375 +    
   2.376 +    if (move->check) {
   2.377 +        /* determine possible escape fields */
   2.378 +        _Bool canescape = 0;
   2.379 +        for (int dr = -1 ; dr <= 1 && !canescape ; dr++) {
   2.380 +            for (int df = -1 ; df <= 1 && !canescape ; df++) {
   2.381 +                if (!(dr == 0 && df == 0)  &&
   2.382 +                        isidx(opkingrow + dr) && isidx(opkingfile + df)) {
   2.383 +                    
   2.384 +                    /* escape field neither blocked nor covered */
   2.385 +                    if ((simulation.board[opkingrow + dr][opkingfile + df]
   2.386 +                            & COLOR_MASK) != opponent_color(piececolor)) {
   2.387 +                        canescape |= !is_covered(&simulation,
   2.388 +                            opkingrow + dr, opkingfile + df, piececolor);
   2.389 +                    }
   2.390 +                }
   2.391 +            }
   2.392 +        }
   2.393 +        /* can't escape, can he capture? */
   2.394 +        if (!canescape && threatcount == 1) {
   2.395 +            canescape = is_attacked(&simulation, threats[0].fromrow,
   2.396 +                threats[0].fromfile, opponent_color(piececolor));
   2.397 +        }
   2.398 +        
   2.399 +        /* can't capture, can he block? */
   2.400 +        if (!canescape && threatcount == 1) {
   2.401 +            Move *threat = &(threats[0]);
   2.402 +            uint8_t threatpiece = threat->piece & PIECE_MASK;
   2.403 +            
   2.404 +            /* knight, pawns and the king cannot be blocked */
   2.405 +            if (threatpiece == BISHOP || threatpiece == ROOK
   2.406 +                || threatpiece == QUEEN) {
   2.407 +                if (threat->fromrow == threat->torow) {
   2.408 +                    /* rook aspect (on row) */
   2.409 +                    int d = threat->tofile > threat->fromfile ? 1 : -1;
   2.410 +                    uint8_t file = threat->fromfile;
   2.411 +                    while (!canescape && file != threat->tofile - d) {
   2.412 +                        file += d;
   2.413 +                        canescape |= is_protected(&simulation,
   2.414 +                            threat->torow, file, opponent_color(piececolor));
   2.415 +                    }
   2.416 +                } else if (threat->fromfile == threat->tofile) {
   2.417 +                    /* rook aspect (on file) */
   2.418 +                    int d = threat->torow > threat->fromrow ? 1 : -1;
   2.419 +                    uint8_t row = threat->fromrow;
   2.420 +                    while (!canescape && row != threat->torow - d) {
   2.421 +                        row += d;
   2.422 +                        canescape |= is_protected(&simulation,
   2.423 +                            row, threat->tofile, opponent_color(piececolor));
   2.424 +                    }
   2.425 +                } else {
   2.426 +                    /* bishop aspect */
   2.427 +                    int dr = threat->torow > threat->fromrow ? 1 : -1;
   2.428 +                    int df = threat->tofile > threat->fromfile ? 1 : -1;
   2.429 +
   2.430 +                    uint8_t row = threat->fromrow;
   2.431 +                    uint8_t file = threat->fromfile;
   2.432 +                    while (!canescape && file != threat->tofile - df
   2.433 +                        && row != threat->torow - dr) {
   2.434 +                        row += dr;
   2.435 +                        file += df;
   2.436 +                        canescape |= is_protected(&simulation, row, file,
   2.437 +                            opponent_color(piececolor));
   2.438 +                    }
   2.439 +                }
   2.440 +            }
   2.441 +        }
   2.442 +            
   2.443 +        if (!canescape) {
   2.444 +            gamestate->checkmate = 1;
   2.445 +        }
   2.446 +    }
   2.447 +    
   2.448 +    gamestate_cleanup(&simulation);
   2.449 +    
   2.450 +    return VALID_MOVE_SEMANTICS;
   2.451 +}
   2.452 +
   2.453 +_Bool get_threats(GameState *gamestate, uint8_t row, uint8_t file,
   2.454 +        uint8_t color, Move *threats, uint8_t *threatcount) {
   2.455 +    Move candidates[32];
   2.456 +    int candidatecount = 0;
   2.457 +    for (uint8_t r = 0 ; r < 8 ; r++) {
   2.458 +        for (uint8_t f = 0 ; f < 8 ; f++) {
   2.459 +            if ((gamestate->board[r][f] & COLOR_MASK) == color) {
   2.460 +                // non-capturing move
   2.461 +                memset(&(candidates[candidatecount]), 0, sizeof(Move));
   2.462 +                candidates[candidatecount].piece = gamestate->board[r][f];
   2.463 +                candidates[candidatecount].fromrow = r;
   2.464 +                candidates[candidatecount].fromfile = f;
   2.465 +                candidates[candidatecount].torow = row;
   2.466 +                candidates[candidatecount].tofile = file;
   2.467 +                candidatecount++;
   2.468 +
   2.469 +                // capturing move
   2.470 +                memcpy(&(candidates[candidatecount]),
   2.471 +                    &(candidates[candidatecount-1]), sizeof(Move));
   2.472 +                candidates[candidatecount].capture = 1;
   2.473 +                candidatecount++;
   2.474 +            }
   2.475 +        }
   2.476 +    }
   2.477 +
   2.478 +    if (threatcount) {
   2.479 +        *threatcount = 0;
   2.480 +    }
   2.481 +    
   2.482 +    
   2.483 +    _Bool result = 0;
   2.484 +    
   2.485 +    for (int i = 0 ; i < candidatecount ; i++) {
   2.486 +        if (validate_move_rules(gamestate, &(candidates[i]))
   2.487 +                == VALID_MOVE_SEMANTICS) {
   2.488 +            result = 1;
   2.489 +            if (threats && threatcount) {
   2.490 +                threats[(*threatcount)++] = candidates[i];
   2.491 +            }
   2.492 +        }
   2.493 +    }
   2.494 +    
   2.495 +    return result;
   2.496 +}
   2.497 +
   2.498 +_Bool is_pinned(GameState *gamestate, Move *move) {
   2.499 +    uint8_t color = move->piece & COLOR_MASK;
   2.500 +
   2.501 +    uint8_t kingfile = 0, kingrow = 0;
   2.502 +    for (uint8_t row = 0 ; row < 8 ; row++) {
   2.503 +        for (uint8_t file = 0 ; file < 8 ; file++) {
   2.504 +            if (gamestate->board[row][file] == (color|KING)) {
   2.505 +                kingfile = file;
   2.506 +                kingrow = row;
   2.507 +            }
   2.508 +        }
   2.509 +    }
   2.510 +
   2.511 +    GameState simulation = gamestate_copy_sim(gamestate);
   2.512 +    Move simmove = *move;
   2.513 +    apply_move(&simulation, &simmove);
   2.514 +    _Bool covered = is_covered(&simulation,
   2.515 +        kingrow, kingfile, opponent_color(color));
   2.516 +    gamestate_cleanup(&simulation);
   2.517 +    
   2.518 +    return covered;
   2.519 +}
   2.520 +
   2.521 +_Bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file,
   2.522 +        uint8_t color, Move *threats, uint8_t *threatcount) {
   2.523 +    
   2.524 +    if (threatcount) {
   2.525 +        *threatcount = 0;
   2.526 +    }
   2.527 +
   2.528 +    Move candidates[16];
   2.529 +    uint8_t candidatecount;
   2.530 +    if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) {
   2.531 +        
   2.532 +        _Bool result = 0;
   2.533 +        uint8_t kingfile = 0, kingrow = 0;
   2.534 +        for (uint8_t row = 0 ; row < 8 ; row++) {
   2.535 +            for (uint8_t file = 0 ; file < 8 ; file++) {
   2.536 +                if (gamestate->board[row][file] == (color|KING)) {
   2.537 +                    kingfile = file;
   2.538 +                    kingrow = row;
   2.539 +                }
   2.540 +            }
   2.541 +        }
   2.542 +
   2.543 +        for (uint8_t i = 0 ; i < candidatecount ; i++) {
   2.544 +            GameState simulation = gamestate_copy_sim(gamestate);
   2.545 +            Move simmove = candidates[i];
   2.546 +            apply_move(&simulation, &simmove);
   2.547 +            if (!is_covered(&simulation, kingrow, kingfile,
   2.548 +                    opponent_color(color))) {
   2.549 +                result = 1;
   2.550 +                if (threats && threatcount) {
   2.551 +                    threats[(*threatcount)++] = candidates[i];
   2.552 +                }
   2.553 +            }
   2.554 +        }
   2.555 +        
   2.556 +        return result;
   2.557 +    } else {
   2.558 +        return 0;
   2.559 +    }
   2.560 +}
   2.561 +
   2.562 +static int getlocation(GameState *gamestate, Move *move) {   
   2.563 +
   2.564 +    uint8_t color = move->piece & COLOR_MASK;
   2.565 +    _Bool incheck = gamestate->lastmove?gamestate->lastmove->move.check:0;
   2.566 +    
   2.567 +    Move threats[16], *threat = NULL;
   2.568 +    uint8_t threatcount;
   2.569 +    
   2.570 +    if (get_threats(gamestate, move->torow, move->tofile, color,
   2.571 +            threats, &threatcount)) {
   2.572 +        
   2.573 +        int reason = INVALID_POSITION;
   2.574 +        
   2.575 +        // find threats for the specified position
   2.576 +        for (uint8_t i = 0 ; i < threatcount ; i++) {
   2.577 +            if ((threats[i].piece & (PIECE_MASK | COLOR_MASK))
   2.578 +                    == move->piece &&
   2.579 +                    (move->fromrow == POS_UNSPECIFIED ||
   2.580 +                    move->fromrow == threats[i].fromrow) &&
   2.581 +                    (move->fromfile == POS_UNSPECIFIED ||
   2.582 +                    move->fromfile == threats[i].fromfile)) {
   2.583 +
   2.584 +                if (threat) {
   2.585 +                    return AMBIGUOUS_MOVE;
   2.586 +                } else {
   2.587 +                    // found threat is no real threat
   2.588 +                    if (is_pinned(gamestate, &(threats[i]))) {
   2.589 +                        reason = incheck?KING_IN_CHECK:PIECE_PINNED;
   2.590 +                    } else {
   2.591 +                        threat = &(threats[i]);
   2.592 +                    }
   2.593 +                }
   2.594 +            }
   2.595 +        }
   2.596 +        
   2.597 +        // can't threaten specified position
   2.598 +        if (!threat) {
   2.599 +            return reason;
   2.600 +        }
   2.601 +
   2.602 +        memcpy(move, threat, sizeof(Move));
   2.603 +        return VALID_MOVE_SYNTAX;
   2.604 +    } else {
   2.605 +        return INVALID_POSITION;
   2.606 +    }
   2.607 +}
   2.608 +
   2.609 +int eval_move(GameState *gamestate, char *mstr, Move *move, uint8_t color) {
   2.610 +    memset(move, 0, sizeof(Move));
   2.611 +    move->fromfile = POS_UNSPECIFIED;
   2.612 +    move->fromrow = POS_UNSPECIFIED;
   2.613 +
   2.614 +    size_t len = strlen(mstr);
   2.615 +    if (len < 1 || len > 6) {
   2.616 +        return INVALID_MOVE_SYNTAX;
   2.617 +    }
   2.618 +    
   2.619 +    /* evaluate check/checkmate flags */
   2.620 +    if (mstr[len-1] == '+') {
   2.621 +        len--; mstr[len] = '\0';
   2.622 +        move->check = 1;
   2.623 +    } else if (mstr[len-1] == '#') {
   2.624 +        len--; mstr[len] = '\0';
   2.625 +        /* ignore - validation should set game state */
   2.626 +    }
   2.627 +    
   2.628 +    /* evaluate promotion */
   2.629 +    if (len > 3 && mstr[len-2] == '=') {
   2.630 +        move->promotion = getpiece(mstr[len-1]);
   2.631 +        if (!move->promotion) {
   2.632 +            return INVALID_MOVE_SYNTAX;
   2.633 +        } else {
   2.634 +            move->promotion |= color;
   2.635 +            len -= 2;
   2.636 +            mstr[len] = 0;
   2.637 +        }
   2.638 +    }
   2.639 +    
   2.640 +    if (len == 2) {
   2.641 +        /* pawn move (e.g. "e4") */
   2.642 +        move->piece = PAWN;
   2.643 +        move->tofile = fileidx(mstr[0]);
   2.644 +        move->torow = rowidx(mstr[1]);
   2.645 +    } else if (len == 3) {
   2.646 +        if (strcmp(mstr, "O-O") == 0) {
   2.647 +            /* king side castling */
   2.648 +            move->piece = KING;
   2.649 +            move->fromfile = fileidx('e');
   2.650 +            move->tofile = fileidx('g');
   2.651 +            move->fromrow = move->torow = color == WHITE ? 0 : 7;
   2.652 +        } else {
   2.653 +            /* move (e.g. "Nf3") */
   2.654 +            move->piece = getpiece(mstr[0]);
   2.655 +            move->tofile = fileidx(mstr[1]);
   2.656 +            move->torow = rowidx(mstr[2]);
   2.657 +        }
   2.658 +    } else if (len == 4) {
   2.659 +        move->piece = getpiece(mstr[0]);
   2.660 +        if (!move->piece) {
   2.661 +            move->piece = PAWN;
   2.662 +            move->fromfile = fileidx(mstr[0]);
   2.663 +        }
   2.664 +        if (mstr[1] == 'x') {
   2.665 +            /* capture (e.g. "Nxf3", "dxe5") */
   2.666 +            move->capture = 1;
   2.667 +        } else {
   2.668 +            /* move (e.g. "Ndf3", "N2c3", "e2e4") */
   2.669 +            if (isfile(mstr[1])) {
   2.670 +                move->fromfile = fileidx(mstr[1]);
   2.671 +                if (move->piece == PAWN) {
   2.672 +                    move->piece = 0;
   2.673 +                }
   2.674 +            } else {
   2.675 +                move->fromrow = rowidx(mstr[1]);
   2.676 +            }
   2.677 +        }
   2.678 +        move->tofile = fileidx(mstr[2]);
   2.679 +        move->torow = rowidx(mstr[3]);
   2.680 +    } else if (len == 5) {
   2.681 +        if (strcmp(mstr, "O-O-O") == 0) {
   2.682 +            /* queen side castling "O-O-O" */
   2.683 +            move->piece = KING;
   2.684 +            move->fromfile = fileidx('e');
   2.685 +            move->tofile = fileidx('c');
   2.686 +            move->fromrow = move->torow = color == WHITE ? 0 : 7;
   2.687 +        } else {
   2.688 +            move->piece = getpiece(mstr[0]);
   2.689 +            if (mstr[2] == 'x') {
   2.690 +                move->capture = 1;
   2.691 +                if (move->piece) {
   2.692 +                    /* capture (e.g. "Ndxf3") */
   2.693 +                    move->fromfile = fileidx(mstr[1]);
   2.694 +                } else {
   2.695 +                    /* long notation capture (e.g. "e5xf6") */
   2.696 +                    move->piece = PAWN;
   2.697 +                    move->fromfile = fileidx(mstr[0]);
   2.698 +                    move->fromrow = rowidx(mstr[1]);
   2.699 +                }
   2.700 +            } else {
   2.701 +                /* long notation move (e.g. "Nc5a4") */
   2.702 +                move->fromfile = fileidx(mstr[1]);
   2.703 +                move->fromrow = rowidx(mstr[2]);
   2.704 +            }
   2.705 +            move->tofile = fileidx(mstr[3]);
   2.706 +            move->torow = rowidx(mstr[4]);
   2.707 +        }
   2.708 +    } else if (len == 6) {
   2.709 +        /* long notation capture (e.g. "Nc5xf3") */
   2.710 +        if (mstr[3] == 'x') {
   2.711 +            move->capture = 1;
   2.712 +            move->piece = getpiece(mstr[0]);
   2.713 +            move->fromfile = fileidx(mstr[1]);
   2.714 +            move->fromrow = rowidx(mstr[2]);
   2.715 +            move->tofile = fileidx(mstr[4]);
   2.716 +            move->torow = rowidx(mstr[5]);
   2.717 +        }
   2.718 +    }
   2.719 +
   2.720 +    
   2.721 +    if (move->piece) {
   2.722 +        if (move->piece == PAWN
   2.723 +            && move->torow == (color==WHITE?7:0)
   2.724 +            && !move->promotion) {
   2.725 +            return NEED_PROMOTION;
   2.726 +        }
   2.727 +        
   2.728 +        move->piece |= color;
   2.729 +        if (move->fromfile == POS_UNSPECIFIED
   2.730 +            || move->fromrow == POS_UNSPECIFIED) {
   2.731 +            return getlocation(gamestate, move);
   2.732 +        } else {
   2.733 +            return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION;
   2.734 +        }
   2.735 +    } else {
   2.736 +        return INVALID_MOVE_SYNTAX;
   2.737 +    }
   2.738 +}
   2.739 +
   2.740 +_Bool is_protected(GameState *gamestate, uint8_t row, uint8_t file,
   2.741 +        uint8_t color) {
   2.742 +    
   2.743 +    Move threats[16];
   2.744 +    uint8_t threatcount;
   2.745 +    if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) {
   2.746 +        for (int i = 0 ; i < threatcount ; i++) {
   2.747 +            if (threats[i].piece != (color|KING)) {
   2.748 +                return 1;
   2.749 +            }
   2.750 +        }
   2.751 +        return 0;
   2.752 +    } else {
   2.753 +        return 0;
   2.754 +    }
   2.755 +}
   2.756 +
   2.757 +uint16_t remaining_movetime(GameInfo *gameinfo, GameState *gamestate,
   2.758 +        uint8_t color) {
   2.759 +    if (!gameinfo->timecontrol) {
   2.760 +        return 0;
   2.761 +    }
   2.762 +    
   2.763 +    if (gamestate->movelist) {
   2.764 +        uint16_t time = gameinfo->time;
   2.765 +        suseconds_t micros = 0;
   2.766 +        
   2.767 +        MoveList *movelist = color == WHITE ?
   2.768 +            gamestate->movelist : gamestate->movelist->next;
   2.769 +        
   2.770 +        while (movelist) {
   2.771 +            time += gameinfo->addtime;
   2.772 +            
   2.773 +            struct movetimeval *movetime = &(movelist->move.movetime);
   2.774 +            if (movetime->tv_sec >= time) {
   2.775 +                return 0;
   2.776 +            }
   2.777 +            
   2.778 +            time -= movetime->tv_sec;
   2.779 +            micros += movetime->tv_usec;
   2.780 +            
   2.781 +            movelist = movelist->next ? movelist->next->next : NULL;
   2.782 +        }
   2.783 +        
   2.784 +        time_t sec;
   2.785 +        movelist = gamestate->lastmove;
   2.786 +        if ((movelist->move.piece & COLOR_MASK) != color) {
   2.787 +            struct movetimeval *lastmovetstamp = &(movelist->move.timestamp);
   2.788 +            struct timeval currenttstamp;
   2.789 +            gettimeofday(&currenttstamp, NULL);
   2.790 +            micros += currenttstamp.tv_usec - lastmovetstamp->tv_usec;
   2.791 +            sec = currenttstamp.tv_sec - lastmovetstamp->tv_sec;
   2.792 +            if (sec >= time) {
   2.793 +                return 0;
   2.794 +            }
   2.795 +            
   2.796 +            time -= sec;
   2.797 +        }
   2.798 +        
   2.799 +        sec = micros / 1e6L;
   2.800 +        
   2.801 +        if (sec >= time) {
   2.802 +            return 0;
   2.803 +        }
   2.804 +
   2.805 +        time -= sec;
   2.806 +        
   2.807 +        return time;
   2.808 +    } else {
   2.809 +        return gameinfo->time;
   2.810 +    }
   2.811 +}
     3.1 --- a/test/bigtestfile.c	Mon Oct 03 12:27:10 2022 +0200
     3.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.3 @@ -1,808 +0,0 @@
     3.4 -/*
     3.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3.6 - *
     3.7 - * Copyright 2014 Mike Becker. All rights reserved.
     3.8 - *
     3.9 - * Redistribution and use in source and binary forms, with or without
    3.10 - * modification, are permitted provided that the following conditions are met:
    3.11 - *
    3.12 - *   1. Redistributions of source code must retain the above copyright
    3.13 - *      notice, this list of conditions and the following disclaimer.
    3.14 - *
    3.15 - *   2. Redistributions in binary form must reproduce the above copyright
    3.16 - *      notice, this list of conditions and the following disclaimer in the
    3.17 - *      documentation and/or other materials provided with the distribution.
    3.18 - *
    3.19 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    3.20 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    3.21 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    3.22 - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    3.23 - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    3.24 - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    3.25 - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    3.26 - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    3.27 - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    3.28 - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    3.29 - * POSSIBILITY OF SUCH DAMAGE.
    3.30 - *
    3.31 - */
    3.32 -
    3.33 -#include "rules.h"
    3.34 -#include "chess.h"
    3.35 -#include <string.h>
    3.36 -#include <stdlib.h>
    3.37 -#include <sys/time.h>
    3.38 -
    3.39 -static GameState gamestate_copy_sim(GameState *gamestate) {
    3.40 -    GameState simulation = *gamestate;
    3.41 -    if (simulation.lastmove) {
    3.42 -        MoveList *lastmovecopy = malloc(sizeof(MoveList));
    3.43 -        *lastmovecopy = *(simulation.lastmove);
    3.44 -        simulation.movelist = simulation.lastmove = lastmovecopy;
    3.45 -    }
    3.46 -
    3.47 -    return simulation;
    3.48 -}
    3.49 -
    3.50 -void gamestate_init(GameState *gamestate) {
    3.51 -    memset(gamestate, 0, sizeof(GameState));
    3.52 -    
    3.53 -    Board initboard = {
    3.54 -        {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
    3.55 -        {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
    3.56 -        {0,     0,       0,       0,      0,     0,       0,       0},
    3.57 -        {0,     0,       0,       0,      0,     0,       0,       0},
    3.58 -        {0,     0,       0,       0,      0,     0,       0,       0},
    3.59 -        {0,     0,       0,       0,      0,     0,       0,       0},
    3.60 -        {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
    3.61 -        {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
    3.62 -    };
    3.63 -    memcpy(gamestate->board, initboard, sizeof(Board));
    3.64 -}
    3.65 -
    3.66 -void gamestate_cleanup(GameState *gamestate) {
    3.67 -    MoveList *elem;
    3.68 -    elem = gamestate->movelist;
    3.69 -    while (elem) {
    3.70 -        MoveList *cur = elem;
    3.71 -        elem = elem->next;
    3.72 -        free(cur);
    3.73 -    };
    3.74 -}
    3.75 -
    3.76 -/* MUST be called IMMEDIATLY after applying a move to work correctly */
    3.77 -static void format_move(GameState *gamestate, Move *move) {
    3.78 -    char *string = move->string;
    3.79 -    
    3.80 -    /* at least 8 characters should be available, wipe them out */
    3.81 -    memset(string, 0, 8);
    3.82 -    
    3.83 -    /* special formats for castling */
    3.84 -    if ((move->piece&PIECE_MASK) == KING &&
    3.85 -            abs(move->tofile-move->fromfile) == 2) {
    3.86 -        if (move->tofile==fileidx('c')) {
    3.87 -            memcpy(string, "O-O-O", 5);
    3.88 -        } else {
    3.89 -            memcpy(string, "O-O", 3);
    3.90 -        }
    3.91 -    }
    3.92 -
    3.93 -    /* start by notating the piece character */
    3.94 -    string[0] = getpiecechr(move->piece);
    3.95 -    int idx = string[0] ? 1 : 0;
    3.96 -    
    3.97 -    /* find out how many source information we do need */
    3.98 -    uint8_t piece = move->piece & PIECE_MASK;
    3.99 -    if (piece == PAWN) {
   3.100 -        if (move->capture) {
   3.101 -            string[idx++] = filechr(move->fromfile);
   3.102 -        }
   3.103 -    } else if (piece != KING) {
   3.104 -        Move threats[16];
   3.105 -        uint8_t threatcount;
   3.106 -        get_real_threats(gamestate, move->torow, move->tofile,
   3.107 -            move->piece&COLOR_MASK, threats, &threatcount);
   3.108 -        if (threatcount > 1) {
   3.109 -            int ambrows = 0, ambfiles = 0;
   3.110 -            for (uint8_t i = 0 ; i < threatcount ; i++) {
   3.111 -                if (threats[i].fromrow == move->fromrow) {
   3.112 -                    ambrows++;
   3.113 -                }
   3.114 -                if (threats[i].fromfile == move->fromfile) {
   3.115 -                    ambfiles++;
   3.116 -                }
   3.117 -            }
   3.118 -            /* ambiguous row, name file */
   3.119 -            if (ambrows > 1) {
   3.120 -                string[idx++] = filechr(move->fromfile);
   3.121 -            }
   3.122 -            /* ambiguous file, name row */
   3.123 -            if (ambfiles > 1) {
   3.124 -                string[idx++] = filechr(move->fromrow);
   3.125 -            }
   3.126 -        }
   3.127 -    }
   3.128 -    
   3.129 -    /* capturing? */
   3.130 -    if (move->capture) {
   3.131 -        string[idx++] = 'x';
   3.132 -    }
   3.133 -    
   3.134 -    /* destination */
   3.135 -    string[idx++] = filechr(move->tofile);
   3.136 -    string[idx++] = rowchr(move->torow);
   3.137 -    
   3.138 -    /* promotion? */
   3.139 -    if (move->promotion) {
   3.140 -        string[idx++] = '=';
   3.141 -        string[idx++] = getpiecechr(move->promotion);
   3.142 -    }
   3.143 -    
   3.144 -    /* check? */
   3.145 -    if (move->check) {
   3.146 -        /* works only, if this function is called when applying the move */
   3.147 -        string[idx++] = gamestate->checkmate?'#':'+';
   3.148 -    }
   3.149 -}
   3.150 -
   3.151 -static void addmove(GameState* gamestate, Move *move) {
   3.152 -    MoveList *elem = malloc(sizeof(MoveList));
   3.153 -    elem->next = NULL;
   3.154 -    elem->move = *move;
   3.155 -    
   3.156 -    struct timeval curtimestamp;
   3.157 -    gettimeofday(&curtimestamp, NULL);
   3.158 -    elem->move.timestamp.tv_sec = curtimestamp.tv_sec;
   3.159 -    elem->move.timestamp.tv_usec = curtimestamp.tv_usec;
   3.160 -    
   3.161 -    if (gamestate->lastmove) {
   3.162 -        struct movetimeval *lasttstamp = &(gamestate->lastmove->move.timestamp);
   3.163 -        uint64_t sec = curtimestamp.tv_sec - lasttstamp->tv_sec;
   3.164 -        suseconds_t micros;
   3.165 -        if (curtimestamp.tv_usec < lasttstamp->tv_usec) {
   3.166 -            micros = 1e6L-(lasttstamp->tv_usec - curtimestamp.tv_usec);
   3.167 -            sec--;
   3.168 -        } else {
   3.169 -            micros = curtimestamp.tv_usec - lasttstamp->tv_usec;
   3.170 -        }
   3.171 -        
   3.172 -        elem->move.movetime.tv_sec = sec;
   3.173 -        elem->move.movetime.tv_usec = micros;
   3.174 -        
   3.175 -        gamestate->lastmove->next = elem;
   3.176 -        gamestate->lastmove = elem;
   3.177 -    } else {
   3.178 -        elem->move.movetime.tv_usec = 0;
   3.179 -        elem->move.movetime.tv_sec = 0;
   3.180 -        gamestate->movelist = gamestate->lastmove = elem;
   3.181 -    }
   3.182 -}
   3.183 -
   3.184 -char getpiecechr(uint8_t piece) {
   3.185 -    switch (piece & PIECE_MASK) {
   3.186 -    case ROOK: return 'R';
   3.187 -    case KNIGHT: return 'N';
   3.188 -    case BISHOP: return 'B';
   3.189 -    case QUEEN: return 'Q';
   3.190 -    case KING: return 'K';
   3.191 -    default: return '\0';
   3.192 -    }
   3.193 -}
   3.194 -
   3.195 -uint8_t getpiece(char c) {
   3.196 -    switch (c) {
   3.197 -        case 'R': return ROOK;
   3.198 -        case 'N': return KNIGHT;
   3.199 -        case 'B': return BISHOP;
   3.200 -        case 'Q': return QUEEN;
   3.201 -        case 'K': return KING;
   3.202 -        default: return 0;
   3.203 -    }
   3.204 -}
   3.205 -
   3.206 -static void apply_move_impl(GameState *gamestate, Move *move, _Bool simulate) {
   3.207 -    uint8_t piece = move->piece & PIECE_MASK;
   3.208 -    uint8_t color = move->piece & COLOR_MASK;
   3.209 -    
   3.210 -    /* en passant capture */
   3.211 -    if (move->capture && piece == PAWN &&
   3.212 -        mdst(gamestate->board, move) == 0) {
   3.213 -        gamestate->board[move->fromrow][move->tofile] = 0;
   3.214 -    }
   3.215 -    
   3.216 -    /* remove old en passant threats */
   3.217 -    for (uint8_t file = 0 ; file < 8 ; file++) {
   3.218 -        gamestate->board[3][file] &= ~ENPASSANT_THREAT;
   3.219 -        gamestate->board[4][file] &= ~ENPASSANT_THREAT;
   3.220 -    }
   3.221 -    
   3.222 -    /* add new en passant threat */
   3.223 -    if (piece == PAWN && (
   3.224 -        (move->fromrow == 1 && move->torow == 3) ||
   3.225 -        (move->fromrow == 6 && move->torow == 4))) {
   3.226 -        move->piece |= ENPASSANT_THREAT;
   3.227 -    }
   3.228 -    
   3.229 -    /* move (and maybe capture or promote) */
   3.230 -    msrc(gamestate->board, move) = 0;
   3.231 -    if (move->promotion) {
   3.232 -        mdst(gamestate->board, move) = move->promotion;
   3.233 -    } else {
   3.234 -        mdst(gamestate->board, move) = move->piece;
   3.235 -    }
   3.236 -    
   3.237 -    /* castling */
   3.238 -    if (piece == KING && move->fromfile == fileidx('e')) {
   3.239 -        
   3.240 -        if (move->tofile == fileidx('g')) {
   3.241 -            gamestate->board[move->torow][fileidx('h')] = 0;
   3.242 -            gamestate->board[move->torow][fileidx('f')] = color|ROOK;
   3.243 -        } else if (move->tofile == fileidx('c')) {
   3.244 -            gamestate->board[move->torow][fileidx('a')] = 0;
   3.245 -            gamestate->board[move->torow][fileidx('d')] = color|ROOK;
   3.246 -        }
   3.247 -    }
   3.248 -
   3.249 -    if (!simulate) {
   3.250 -        if (!move->string[0]) {
   3.251 -            format_move(gamestate, move);
   3.252 -        }
   3.253 -    }
   3.254 -    /* add move, even in simulation (checkmate test needs it) */
   3.255 -    addmove(gamestate, move);
   3.256 -}
   3.257 -
   3.258 -void apply_move(GameState *gamestate, Move *move) {
   3.259 -    apply_move_impl(gamestate, move, 0);
   3.260 -}
   3.261 -
   3.262 -static int validate_move_rules(GameState *gamestate, Move *move) {
   3.263 -    /* validate indices (don't trust opponent) */
   3.264 -    if (!chkidx(move)) {
   3.265 -        return INVALID_POSITION;
   3.266 -    }
   3.267 -    
   3.268 -    /* must move */
   3.269 -    if (move->fromfile == move->tofile && move->fromrow == move->torow) {
   3.270 -        return INVALID_MOVE_SYNTAX;
   3.271 -    }
   3.272 -    
   3.273 -    /* does piece exist */
   3.274 -    if ((msrc(gamestate->board, move)&(PIECE_MASK|COLOR_MASK))
   3.275 -           != (move->piece&(PIECE_MASK|COLOR_MASK))) {
   3.276 -        return INVALID_POSITION;
   3.277 -    }
   3.278 -    
   3.279 -    /* can't capture own pieces */
   3.280 -    if ((mdst(gamestate->board, move) & COLOR_MASK)
   3.281 -            == (move->piece & COLOR_MASK)) {
   3.282 -        return RULES_VIOLATED;
   3.283 -    }
   3.284 -    
   3.285 -    /* must capture, if and only if destination is occupied */
   3.286 -    if ((mdst(gamestate->board, move) == 0 && move->capture) ||
   3.287 -            (mdst(gamestate->board, move) != 0 && !move->capture)) {
   3.288 -        return INVALID_MOVE_SYNTAX;
   3.289 -    }
   3.290 -    
   3.291 -    /* validate individual rules */
   3.292 -    _Bool chkrules;
   3.293 -    switch (move->piece & PIECE_MASK) {
   3.294 -    case PAWN:
   3.295 -        chkrules = pawn_chkrules(gamestate, move) &&
   3.296 -            !pawn_isblocked(gamestate, move);
   3.297 -        break;
   3.298 -    case ROOK:
   3.299 -        chkrules = rook_chkrules(move) &&
   3.300 -            !rook_isblocked(gamestate, move);
   3.301 -        break;
   3.302 -    case KNIGHT:
   3.303 -        chkrules = knight_chkrules(move); /* knight is never blocked */
   3.304 -        break;
   3.305 -    case BISHOP:
   3.306 -        chkrules = bishop_chkrules(move) &&
   3.307 -            !bishop_isblocked(gamestate, move);
   3.308 -        break;
   3.309 -    case QUEEN:
   3.310 -        chkrules = queen_chkrules(move) &&
   3.311 -            !queen_isblocked(gamestate, move);
   3.312 -        break;
   3.313 -    case KING:
   3.314 -        chkrules = king_chkrules(gamestate, move) &&
   3.315 -            !king_isblocked(gamestate, move);
   3.316 -        break;
   3.317 -    default:
   3.318 -        return INVALID_MOVE_SYNTAX;
   3.319 -    }
   3.320 -    
   3.321 -    return chkrules ? VALID_MOVE_SEMANTICS : RULES_VIOLATED;
   3.322 -}
   3.323 -
   3.324 -int validate_move(GameState *gamestate, Move *move) {
   3.325 -    
   3.326 -    int result = validate_move_rules(gamestate, move);
   3.327 -    
   3.328 -    /* cancel processing to save resources */
   3.329 -    if (result != VALID_MOVE_SEMANTICS) {
   3.330 -        return result;
   3.331 -    }
   3.332 -    
   3.333 -    /* find kings for check validation */
   3.334 -    uint8_t piececolor = (move->piece & COLOR_MASK);
   3.335 -    
   3.336 -    uint8_t mykingfile = 0, mykingrow = 0, opkingfile = 0, opkingrow = 0;
   3.337 -    for (uint8_t row = 0 ; row < 8 ; row++) {
   3.338 -        for (uint8_t file = 0 ; file < 8 ; file++) {
   3.339 -            if (gamestate->board[row][file] ==
   3.340 -                    (piececolor == WHITE?WKING:BKING)) {
   3.341 -                mykingfile = file;
   3.342 -                mykingrow = row;
   3.343 -            } else if (gamestate->board[row][file] ==
   3.344 -                    (piececolor == WHITE?BKING:WKING)) {
   3.345 -                opkingfile = file;
   3.346 -                opkingrow = row;
   3.347 -            }
   3.348 -        }
   3.349 -    }
   3.350 -    
   3.351 -    /* simulate move for check validation */
   3.352 -    GameState simulation = gamestate_copy_sim(gamestate);
   3.353 -    Move simmove = *move;
   3.354 -    apply_move_impl(&simulation, &simmove, 1);
   3.355 -    
   3.356 -    /* don't move into or stay in check position */
   3.357 -    if (is_covered(&simulation, mykingrow, mykingfile,
   3.358 -        opponent_color(piececolor))) {
   3.359 -        
   3.360 -        gamestate_cleanup(&simulation);
   3.361 -        if ((move->piece & PIECE_MASK) == KING) {
   3.362 -            return KING_MOVES_INTO_CHECK;
   3.363 -        } else {
   3.364 -            /* last move is always not null in this case */
   3.365 -            return gamestate->lastmove->move.check ?
   3.366 -                KING_IN_CHECK : PIECE_PINNED;
   3.367 -        }
   3.368 -    }
   3.369 -    
   3.370 -    /* correct check and checkmate flags (move is still valid) */
   3.371 -    Move threats[16];
   3.372 -    uint8_t threatcount;
   3.373 -    move->check = get_threats(&simulation, opkingrow, opkingfile,
   3.374 -        piececolor, threats, &threatcount);
   3.375 -    
   3.376 -    if (move->check) {
   3.377 -        /* determine possible escape fields */
   3.378 -        _Bool canescape = 0;
   3.379 -        for (int dr = -1 ; dr <= 1 && !canescape ; dr++) {
   3.380 -            for (int df = -1 ; df <= 1 && !canescape ; df++) {
   3.381 -                if (!(dr == 0 && df == 0)  &&
   3.382 -                        isidx(opkingrow + dr) && isidx(opkingfile + df)) {
   3.383 -                    
   3.384 -                    /* escape field neither blocked nor covered */
   3.385 -                    if ((simulation.board[opkingrow + dr][opkingfile + df]
   3.386 -                            & COLOR_MASK) != opponent_color(piececolor)) {
   3.387 -                        canescape |= !is_covered(&simulation,
   3.388 -                            opkingrow + dr, opkingfile + df, piececolor);
   3.389 -                    }
   3.390 -                }
   3.391 -            }
   3.392 -        }
   3.393 -        /* can't escape, can he capture? */
   3.394 -        if (!canescape && threatcount == 1) {
   3.395 -            canescape = is_attacked(&simulation, threats[0].fromrow,
   3.396 -                threats[0].fromfile, opponent_color(piececolor));
   3.397 -        }
   3.398 -        
   3.399 -        /* can't capture, can he block? */
   3.400 -        if (!canescape && threatcount == 1) {
   3.401 -            Move *threat = &(threats[0]);
   3.402 -            uint8_t threatpiece = threat->piece & PIECE_MASK;
   3.403 -            
   3.404 -            /* knight, pawns and the king cannot be blocked */
   3.405 -            if (threatpiece == BISHOP || threatpiece == ROOK
   3.406 -                || threatpiece == QUEEN) {
   3.407 -                if (threat->fromrow == threat->torow) {
   3.408 -                    /* rook aspect (on row) */
   3.409 -                    int d = threat->tofile > threat->fromfile ? 1 : -1;
   3.410 -                    uint8_t file = threat->fromfile;
   3.411 -                    while (!canescape && file != threat->tofile - d) {
   3.412 -                        file += d;
   3.413 -                        canescape |= is_protected(&simulation,
   3.414 -                            threat->torow, file, opponent_color(piececolor));
   3.415 -                    }
   3.416 -                } else if (threat->fromfile == threat->tofile) {
   3.417 -                    /* rook aspect (on file) */
   3.418 -                    int d = threat->torow > threat->fromrow ? 1 : -1;
   3.419 -                    uint8_t row = threat->fromrow;
   3.420 -                    while (!canescape && row != threat->torow - d) {
   3.421 -                        row += d;
   3.422 -                        canescape |= is_protected(&simulation,
   3.423 -                            row, threat->tofile, opponent_color(piececolor));
   3.424 -                    }
   3.425 -                } else {
   3.426 -                    /* bishop aspect */
   3.427 -                    int dr = threat->torow > threat->fromrow ? 1 : -1;
   3.428 -                    int df = threat->tofile > threat->fromfile ? 1 : -1;
   3.429 -
   3.430 -                    uint8_t row = threat->fromrow;
   3.431 -                    uint8_t file = threat->fromfile;
   3.432 -                    while (!canescape && file != threat->tofile - df
   3.433 -                        && row != threat->torow - dr) {
   3.434 -                        row += dr;
   3.435 -                        file += df;
   3.436 -                        canescape |= is_protected(&simulation, row, file,
   3.437 -                            opponent_color(piececolor));
   3.438 -                    }
   3.439 -                }
   3.440 -            }
   3.441 -        }
   3.442 -            
   3.443 -        if (!canescape) {
   3.444 -            gamestate->checkmate = 1;
   3.445 -        }
   3.446 -    }
   3.447 -    
   3.448 -    gamestate_cleanup(&simulation);
   3.449 -    
   3.450 -    return VALID_MOVE_SEMANTICS;
   3.451 -}
   3.452 -
   3.453 -_Bool get_threats(GameState *gamestate, uint8_t row, uint8_t file,
   3.454 -        uint8_t color, Move *threats, uint8_t *threatcount) {
   3.455 -    Move candidates[32];
   3.456 -    int candidatecount = 0;
   3.457 -    for (uint8_t r = 0 ; r < 8 ; r++) {
   3.458 -        for (uint8_t f = 0 ; f < 8 ; f++) {
   3.459 -            if ((gamestate->board[r][f] & COLOR_MASK) == color) {
   3.460 -                // non-capturing move
   3.461 -                memset(&(candidates[candidatecount]), 0, sizeof(Move));
   3.462 -                candidates[candidatecount].piece = gamestate->board[r][f];
   3.463 -                candidates[candidatecount].fromrow = r;
   3.464 -                candidates[candidatecount].fromfile = f;
   3.465 -                candidates[candidatecount].torow = row;
   3.466 -                candidates[candidatecount].tofile = file;
   3.467 -                candidatecount++;
   3.468 -
   3.469 -                // capturing move
   3.470 -                memcpy(&(candidates[candidatecount]),
   3.471 -                    &(candidates[candidatecount-1]), sizeof(Move));
   3.472 -                candidates[candidatecount].capture = 1;
   3.473 -                candidatecount++;
   3.474 -            }
   3.475 -        }
   3.476 -    }
   3.477 -
   3.478 -    if (threatcount) {
   3.479 -        *threatcount = 0;
   3.480 -    }
   3.481 -    
   3.482 -    
   3.483 -    _Bool result = 0;
   3.484 -    
   3.485 -    for (int i = 0 ; i < candidatecount ; i++) {
   3.486 -        if (validate_move_rules(gamestate, &(candidates[i]))
   3.487 -                == VALID_MOVE_SEMANTICS) {
   3.488 -            result = 1;
   3.489 -            if (threats && threatcount) {
   3.490 -                threats[(*threatcount)++] = candidates[i];
   3.491 -            }
   3.492 -        }
   3.493 -    }
   3.494 -    
   3.495 -    return result;
   3.496 -}
   3.497 -
   3.498 -_Bool is_pinned(GameState *gamestate, Move *move) {
   3.499 -    uint8_t color = move->piece & COLOR_MASK;
   3.500 -
   3.501 -    uint8_t kingfile = 0, kingrow = 0;
   3.502 -    for (uint8_t row = 0 ; row < 8 ; row++) {
   3.503 -        for (uint8_t file = 0 ; file < 8 ; file++) {
   3.504 -            if (gamestate->board[row][file] == (color|KING)) {
   3.505 -                kingfile = file;
   3.506 -                kingrow = row;
   3.507 -            }
   3.508 -        }
   3.509 -    }
   3.510 -
   3.511 -    GameState simulation = gamestate_copy_sim(gamestate);
   3.512 -    Move simmove = *move;
   3.513 -    apply_move(&simulation, &simmove);
   3.514 -    _Bool covered = is_covered(&simulation,
   3.515 -        kingrow, kingfile, opponent_color(color));
   3.516 -    gamestate_cleanup(&simulation);
   3.517 -    
   3.518 -    return covered;
   3.519 -}
   3.520 -
   3.521 -_Bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file,
   3.522 -        uint8_t color, Move *threats, uint8_t *threatcount) {
   3.523 -    
   3.524 -    if (threatcount) {
   3.525 -        *threatcount = 0;
   3.526 -    }
   3.527 -
   3.528 -    Move candidates[16];
   3.529 -    uint8_t candidatecount;
   3.530 -    if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) {
   3.531 -        
   3.532 -        _Bool result = 0;
   3.533 -        uint8_t kingfile = 0, kingrow = 0;
   3.534 -        for (uint8_t row = 0 ; row < 8 ; row++) {
   3.535 -            for (uint8_t file = 0 ; file < 8 ; file++) {
   3.536 -                if (gamestate->board[row][file] == (color|KING)) {
   3.537 -                    kingfile = file;
   3.538 -                    kingrow = row;
   3.539 -                }
   3.540 -            }
   3.541 -        }
   3.542 -
   3.543 -        for (uint8_t i = 0 ; i < candidatecount ; i++) {
   3.544 -            GameState simulation = gamestate_copy_sim(gamestate);
   3.545 -            Move simmove = candidates[i];
   3.546 -            apply_move(&simulation, &simmove);
   3.547 -            if (!is_covered(&simulation, kingrow, kingfile,
   3.548 -                    opponent_color(color))) {
   3.549 -                result = 1;
   3.550 -                if (threats && threatcount) {
   3.551 -                    threats[(*threatcount)++] = candidates[i];
   3.552 -                }
   3.553 -            }
   3.554 -        }
   3.555 -        
   3.556 -        return result;
   3.557 -    } else {
   3.558 -        return 0;
   3.559 -    }
   3.560 -}
   3.561 -
   3.562 -static int getlocation(GameState *gamestate, Move *move) {   
   3.563 -
   3.564 -    uint8_t color = move->piece & COLOR_MASK;
   3.565 -    _Bool incheck = gamestate->lastmove?gamestate->lastmove->move.check:0;
   3.566 -    
   3.567 -    Move threats[16], *threat = NULL;
   3.568 -    uint8_t threatcount;
   3.569 -    
   3.570 -    if (get_threats(gamestate, move->torow, move->tofile, color,
   3.571 -            threats, &threatcount)) {
   3.572 -        
   3.573 -        int reason = INVALID_POSITION;
   3.574 -        
   3.575 -        // find threats for the specified position
   3.576 -        for (uint8_t i = 0 ; i < threatcount ; i++) {
   3.577 -            if ((threats[i].piece & (PIECE_MASK | COLOR_MASK))
   3.578 -                    == move->piece &&
   3.579 -                    (move->fromrow == POS_UNSPECIFIED ||
   3.580 -                    move->fromrow == threats[i].fromrow) &&
   3.581 -                    (move->fromfile == POS_UNSPECIFIED ||
   3.582 -                    move->fromfile == threats[i].fromfile)) {
   3.583 -
   3.584 -                if (threat) {
   3.585 -                    return AMBIGUOUS_MOVE;
   3.586 -                } else {
   3.587 -                    // found threat is no real threat
   3.588 -                    if (is_pinned(gamestate, &(threats[i]))) {
   3.589 -                        reason = incheck?KING_IN_CHECK:PIECE_PINNED;
   3.590 -                    } else {
   3.591 -                        threat = &(threats[i]);
   3.592 -                    }
   3.593 -                }
   3.594 -            }
   3.595 -        }
   3.596 -        
   3.597 -        // can't threaten specified position
   3.598 -        if (!threat) {
   3.599 -            return reason;
   3.600 -        }
   3.601 -
   3.602 -        memcpy(move, threat, sizeof(Move));
   3.603 -        return VALID_MOVE_SYNTAX;
   3.604 -    } else {
   3.605 -        return INVALID_POSITION;
   3.606 -    }
   3.607 -}
   3.608 -
   3.609 -int eval_move(GameState *gamestate, char *mstr, Move *move, uint8_t color) {
   3.610 -    memset(move, 0, sizeof(Move));
   3.611 -    move->fromfile = POS_UNSPECIFIED;
   3.612 -    move->fromrow = POS_UNSPECIFIED;
   3.613 -
   3.614 -    size_t len = strlen(mstr);
   3.615 -    if (len < 1 || len > 6) {
   3.616 -        return INVALID_MOVE_SYNTAX;
   3.617 -    }
   3.618 -    
   3.619 -    /* evaluate check/checkmate flags */
   3.620 -    if (mstr[len-1] == '+') {
   3.621 -        len--; mstr[len] = '\0';
   3.622 -        move->check = 1;
   3.623 -    } else if (mstr[len-1] == '#') {
   3.624 -        len--; mstr[len] = '\0';
   3.625 -        /* ignore - validation should set game state */
   3.626 -    }
   3.627 -    
   3.628 -    /* evaluate promotion */
   3.629 -    if (len > 3 && mstr[len-2] == '=') {
   3.630 -        move->promotion = getpiece(mstr[len-1]);
   3.631 -        if (!move->promotion) {
   3.632 -            return INVALID_MOVE_SYNTAX;
   3.633 -        } else {
   3.634 -            move->promotion |= color;
   3.635 -            len -= 2;
   3.636 -            mstr[len] = 0;
   3.637 -        }
   3.638 -    }
   3.639 -    
   3.640 -    if (len == 2) {
   3.641 -        /* pawn move (e.g. "e4") */
   3.642 -        move->piece = PAWN;
   3.643 -        move->tofile = fileidx(mstr[0]);
   3.644 -        move->torow = rowidx(mstr[1]);
   3.645 -    } else if (len == 3) {
   3.646 -        if (strcmp(mstr, "O-O") == 0) {
   3.647 -            /* king side castling */
   3.648 -            move->piece = KING;
   3.649 -            move->fromfile = fileidx('e');
   3.650 -            move->tofile = fileidx('g');
   3.651 -            move->fromrow = move->torow = color == WHITE ? 0 : 7;
   3.652 -        } else {
   3.653 -            /* move (e.g. "Nf3") */
   3.654 -            move->piece = getpiece(mstr[0]);
   3.655 -            move->tofile = fileidx(mstr[1]);
   3.656 -            move->torow = rowidx(mstr[2]);
   3.657 -        }
   3.658 -    } else if (len == 4) {
   3.659 -        move->piece = getpiece(mstr[0]);
   3.660 -        if (!move->piece) {
   3.661 -            move->piece = PAWN;
   3.662 -            move->fromfile = fileidx(mstr[0]);
   3.663 -        }
   3.664 -        if (mstr[1] == 'x') {
   3.665 -            /* capture (e.g. "Nxf3", "dxe5") */
   3.666 -            move->capture = 1;
   3.667 -        } else {
   3.668 -            /* move (e.g. "Ndf3", "N2c3", "e2e4") */
   3.669 -            if (isfile(mstr[1])) {
   3.670 -                move->fromfile = fileidx(mstr[1]);
   3.671 -                if (move->piece == PAWN) {
   3.672 -                    move->piece = 0;
   3.673 -                }
   3.674 -            } else {
   3.675 -                move->fromrow = rowidx(mstr[1]);
   3.676 -            }
   3.677 -        }
   3.678 -        move->tofile = fileidx(mstr[2]);
   3.679 -        move->torow = rowidx(mstr[3]);
   3.680 -    } else if (len == 5) {
   3.681 -        if (strcmp(mstr, "O-O-O") == 0) {
   3.682 -            /* queen side castling "O-O-O" */
   3.683 -            move->piece = KING;
   3.684 -            move->fromfile = fileidx('e');
   3.685 -            move->tofile = fileidx('c');
   3.686 -            move->fromrow = move->torow = color == WHITE ? 0 : 7;
   3.687 -        } else {
   3.688 -            move->piece = getpiece(mstr[0]);
   3.689 -            if (mstr[2] == 'x') {
   3.690 -                move->capture = 1;
   3.691 -                if (move->piece) {
   3.692 -                    /* capture (e.g. "Ndxf3") */
   3.693 -                    move->fromfile = fileidx(mstr[1]);
   3.694 -                } else {
   3.695 -                    /* long notation capture (e.g. "e5xf6") */
   3.696 -                    move->piece = PAWN;
   3.697 -                    move->fromfile = fileidx(mstr[0]);
   3.698 -                    move->fromrow = rowidx(mstr[1]);
   3.699 -                }
   3.700 -            } else {
   3.701 -                /* long notation move (e.g. "Nc5a4") */
   3.702 -                move->fromfile = fileidx(mstr[1]);
   3.703 -                move->fromrow = rowidx(mstr[2]);
   3.704 -            }
   3.705 -            move->tofile = fileidx(mstr[3]);
   3.706 -            move->torow = rowidx(mstr[4]);
   3.707 -        }
   3.708 -    } else if (len == 6) {
   3.709 -        /* long notation capture (e.g. "Nc5xf3") */
   3.710 -        if (mstr[3] == 'x') {
   3.711 -            move->capture = 1;
   3.712 -            move->piece = getpiece(mstr[0]);
   3.713 -            move->fromfile = fileidx(mstr[1]);
   3.714 -            move->fromrow = rowidx(mstr[2]);
   3.715 -            move->tofile = fileidx(mstr[4]);
   3.716 -            move->torow = rowidx(mstr[5]);
   3.717 -        }
   3.718 -    }
   3.719 -
   3.720 -    
   3.721 -    if (move->piece) {
   3.722 -        if (move->piece == PAWN
   3.723 -            && move->torow == (color==WHITE?7:0)
   3.724 -            && !move->promotion) {
   3.725 -            return NEED_PROMOTION;
   3.726 -        }
   3.727 -        
   3.728 -        move->piece |= color;
   3.729 -        if (move->fromfile == POS_UNSPECIFIED
   3.730 -            || move->fromrow == POS_UNSPECIFIED) {
   3.731 -            return getlocation(gamestate, move);
   3.732 -        } else {
   3.733 -            return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION;
   3.734 -        }
   3.735 -    } else {
   3.736 -        return INVALID_MOVE_SYNTAX;
   3.737 -    }
   3.738 -}
   3.739 -
   3.740 -_Bool is_protected(GameState *gamestate, uint8_t row, uint8_t file,
   3.741 -        uint8_t color) {
   3.742 -    
   3.743 -    Move threats[16];
   3.744 -    uint8_t threatcount;
   3.745 -    if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) {
   3.746 -        for (int i = 0 ; i < threatcount ; i++) {
   3.747 -            if (threats[i].piece != (color|KING)) {
   3.748 -                return 1;
   3.749 -            }
   3.750 -        }
   3.751 -        return 0;
   3.752 -    } else {
   3.753 -        return 0;
   3.754 -    }
   3.755 -}
   3.756 -
   3.757 -uint16_t remaining_movetime(GameInfo *gameinfo, GameState *gamestate,
   3.758 -        uint8_t color) {
   3.759 -    if (!gameinfo->timecontrol) {
   3.760 -        return 0;
   3.761 -    }
   3.762 -    
   3.763 -    if (gamestate->movelist) {
   3.764 -        uint16_t time = gameinfo->time;
   3.765 -        suseconds_t micros = 0;
   3.766 -        
   3.767 -        MoveList *movelist = color == WHITE ?
   3.768 -            gamestate->movelist : gamestate->movelist->next;
   3.769 -        
   3.770 -        while (movelist) {
   3.771 -            time += gameinfo->addtime;
   3.772 -            
   3.773 -            struct movetimeval *movetime = &(movelist->move.movetime);
   3.774 -            if (movetime->tv_sec >= time) {
   3.775 -                return 0;
   3.776 -            }
   3.777 -            
   3.778 -            time -= movetime->tv_sec;
   3.779 -            micros += movetime->tv_usec;
   3.780 -            
   3.781 -            movelist = movelist->next ? movelist->next->next : NULL;
   3.782 -        }
   3.783 -        
   3.784 -        time_t sec;
   3.785 -        movelist = gamestate->lastmove;
   3.786 -        if ((movelist->move.piece & COLOR_MASK) != color) {
   3.787 -            struct movetimeval *lastmovetstamp = &(movelist->move.timestamp);
   3.788 -            struct timeval currenttstamp;
   3.789 -            gettimeofday(&currenttstamp, NULL);
   3.790 -            micros += currenttstamp.tv_usec - lastmovetstamp->tv_usec;
   3.791 -            sec = currenttstamp.tv_sec - lastmovetstamp->tv_sec;
   3.792 -            if (sec >= time) {
   3.793 -                return 0;
   3.794 -            }
   3.795 -            
   3.796 -            time -= sec;
   3.797 -        }
   3.798 -        
   3.799 -        sec = micros / 1e6L;
   3.800 -        
   3.801 -        if (sec >= time) {
   3.802 -            return 0;
   3.803 -        }
   3.804 -
   3.805 -        time -= sec;
   3.806 -        
   3.807 -        return time;
   3.808 -    } else {
   3.809 -        return gameinfo->time;
   3.810 -    }
   3.811 -}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/test/ctest.c	Mon Oct 03 12:56:28 2022 +0200
     4.3 @@ -0,0 +1,391 @@
     4.4 +/*
     4.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     4.6 + *
     4.7 + * Copyright 2015 Olaf Wintermann. All rights reserved.
     4.8 + *
     4.9 + * Redistribution and use in source and binary forms, with or without
    4.10 + * modification, are permitted provided that the following conditions are met:
    4.11 + *
    4.12 + *   1. Redistributions of source code must retain the above copyright
    4.13 + *      notice, this list of conditions and the following disclaimer.
    4.14 + *
    4.15 + *   2. Redistributions in binary form must reproduce the above copyright
    4.16 + *      notice, this list of conditions and the following disclaimer in the
    4.17 + *      documentation and/or other materials provided with the distribution.
    4.18 + *
    4.19 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    4.20 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    4.21 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    4.22 + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    4.23 + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    4.24 + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    4.25 + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    4.26 + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    4.27 + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    4.28 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    4.29 + * POSSIBILITY OF SUCH DAMAGE.
    4.30 + */
    4.31 +
    4.32 +#include <time.h>
    4.33 +#include <stdio.h>
    4.34 +#include <stdlib.h>
    4.35 +#include <string.h>
    4.36 +#include <ucx/string.h>
    4.37 +#include <ucx/buffer.h>
    4.38 +#include <ucx/utils.h>
    4.39 +#include <libxml/tree.h>
    4.40 +#include <curl/curl.h>
    4.41 +
    4.42 +#include <openssl/sha.h>
    4.43 +#include <openssl/hmac.h>
    4.44 +#include <openssl/evp.h>
    4.45 +#include <openssl/bio.h>
    4.46 +#include <openssl/buffer.h>
    4.47 +#include <openssl/rand.h>
    4.48 +
    4.49 +#include "utils.h"
    4.50 +#include "crypto.h"
    4.51 +#include "webdav.h"
    4.52 +
    4.53 +#define MACRO1337 1337L
    4.54 +
    4.55 +/* -------------------- This is a testing file. -------------------------- */
    4.56 +/*
    4.57 +time_t util_parse_creationdate(char *str) {
    4.58 +    // example: 2012-11-29T21:35:35Z
    4.59 +    if(!str) {
    4.60 +        return 0;
    4.61 +    }
    4.62 +    // TODO
    4.63 +    return 0;
    4.64 +}
    4.65 +*/
    4.66 +time_t util_parse_lastmodified(char *str) {
    4.67 +    // example: Thu, 29 Nov 2012 21:35:35 GMT
    4.68 +    if(!str) {
    4.69 +        return 0;
    4.70 +    } else {
    4.71 +        return curl_getdate(str, NULL);
    4.72 +    }
    4.73 +}
    4.74 +
    4.75 +int util_getboolean(char *v) {
    4.76 +    if(v[0] == 'T' || v[0] == 't') {
    4.77 +        return 1;
    4.78 +    }
    4.79 +    return 0;
    4.80 +}
    4.81 +
    4.82 +int util_strtoint(char *str, int64_t *value) {
    4.83 +    char *end;
    4.84 +    int64_t val = strtoll(str, &end, 0);
    4.85 +    if(strlen(end) == 0) {
    4.86 +        *value = val;
    4.87 +        return 1;
    4.88 +    } else {
    4.89 +        return 0;
    4.90 +    }
    4.91 +}
    4.92 +
    4.93 +char* util_url_path(char *url) { 
    4.94 +    char *path = NULL;
    4.95 +    size_t len = strlen(url);
    4.96 +    int slashcount = 0;
    4.97 +    int slmax;
    4.98 +    if(len > 7 && !strncasecmp(url, "http://", 7)) {
    4.99 +        slmax = 3;
   4.100 +    } else if(len > 8 && !strncasecmp(url, "https://", 8)) {
   4.101 +        slmax = 3;
   4.102 +    } else {
   4.103 +        slmax = 1;
   4.104 +    }
   4.105 +    char c;
   4.106 +    for(int i=0;i<len;i++) {
   4.107 +        c = url[i];
   4.108 +        if(c == '/') {
   4.109 +            slashcount++;
   4.110 +            if(slashcount == slmax) {
   4.111 +                path = url + i;
   4.112 +                break;
   4.113 +            }
   4.114 +        }
   4.115 +    } 
   4.116 +    return path;
   4.117 +}
   4.118 +
   4.119 +char* util_url_decode(DavSession *sn, char *url) {
   4.120 +    char *unesc = curl_easy_unescape(sn->handle, url, strlen(url), NULL);
   4.121 +    char *ret = strdup(unesc);
   4.122 +    curl_free(unesc);
   4.123 +    return ret;
   4.124 +}
   4.125 +
   4.126 +char* util_resource_name(char *url) {
   4.127 +    int si = 0;
   4.128 +    int osi = 0;
   4.129 +    int i = 0;
   4.130 +    int p = 0;
   4.131 +    char c;
   4.132 +    while((c = url[i]) != 0) {
   4.133 +        if(c == '/') {
   4.134 +            osi = si;
   4.135 +            si = i;
   4.136 +            p = 1;
   4.137 +        }
   4.138 +        i++;
   4.139 +    }
   4.140 +    
   4.141 +    char *name = url + si + p;
   4.142 +    if(name[0] == 0) {
   4.143 +        name = url + osi + p;
   4.144 +        if(name[0] == 0) {
   4.145 +            return url;
   4.146 +        }
   4.147 +    }
   4.148 +    
   4.149 +    return name;
   4.150 +}
   4.151 +
   4.152 +int util_mkdir(char *path, mode_t mode) {
   4.153 +#ifdef _WIN32
   4.154 +    return mkdir(path);
   4.155 +#else
   4.156 +    return mkdir(path, mode);
   4.157 +#endif
   4.158 +}
   4.159 +
   4.160 +char* util_concat_path(char *url_base, char *p) {
   4.161 +    sstr_t base = sstr(url_base);
   4.162 +    sstr_t path;
   4.163 +    if(p) {
   4.164 +        path = sstr(p);
   4.165 +    } else {
   4.166 +        path = sstrn("", 0);
   4.167 +    }
   4.168 +    
   4.169 +    int add_separator = 0;
   4.170 +    if(base.ptr[base.length-1] == '/') {
   4.171 +        if(path.ptr[0] == '/') {
   4.172 +            base.length--;
   4.173 +        }
   4.174 +    } else {
   4.175 +        if(path.length == 0 || path.ptr[0] != '/') {
   4.176 +            add_separator = 1;
   4.177 +        }
   4.178 +    }
   4.179 +    
   4.180 +    sstr_t url;
   4.181 +    if(add_separator) {
   4.182 +        url = sstrcat(3, base, sstr("/"), path);
   4.183 +    } else {
   4.184 +        url = sstrcat(2, base, path);
   4.185 +    }
   4.186 +    
   4.187 +    return url.ptr;
   4.188 +}
   4.189 +
   4.190 +void util_set_url(DavSession *sn, char *href) {
   4.191 +    sstr_t base = sstr(sn->base_url);
   4.192 +    sstr_t href_str = sstr(href);
   4.193 +    
   4.194 +    char *base_path = util_url_path(sn->base_url);
   4.195 +    base.length -= strlen(base_path);
   4.196 +    
   4.197 +    sstr_t url = sstrcat(2, base, href_str);
   4.198 +    
   4.199 +    curl_easy_setopt(sn->handle, CURLOPT_URL, url.ptr);
   4.200 +    free(url.ptr);
   4.201 +}
   4.202 +
   4.203 +char* util_path_to_url(DavSession *sn, char *path) {
   4.204 +    char *space = malloc(256);
   4.205 +    UcxBuffer *url = ucx_buffer_new(space, 256, UCX_BUFFER_AUTOEXTEND);
   4.206 +    
   4.207 +    // add base url
   4.208 +    ucx_buffer_write(sn->base_url, 1, strlen(sn->base_url), url);
   4.209 +    // remove trailing slash
   4.210 +    ucx_buffer_seek(url, -1, SEEK_CUR);
   4.211 +    
   4.212 +    sstr_t p = sstr(path);
   4.213 +    ssize_t ntk = 0;
   4.214 +    sstr_t *tks = sstrsplit(p, S("/"), &ntk);
   4.215 +    
   4.216 +    for(int i=0;i<ntk;i++) {
   4.217 +        sstr_t node = tks[i];
   4.218 +        if(node.length > 0) {
   4.219 +            char *esc = curl_easy_escape(sn->handle, node.ptr, node.length);
   4.220 +            ucx_buffer_putc(url, '/');
   4.221 +            ucx_buffer_write(esc, 1, strlen(esc), url);
   4.222 +            curl_free(esc);
   4.223 +        }
   4.224 +        free(node.ptr);
   4.225 +    }
   4.226 +    free(tks);
   4.227 +    if(path[p.length-1] == '/') {
   4.228 +        ucx_buffer_putc(url, '/');
   4.229 +    }
   4.230 +    ucx_buffer_putc(url, 0);
   4.231 +    
   4.232 +    space = url->space;
   4.233 +    ucx_buffer_free(url);
   4.234 +    
   4.235 +    return space;
   4.236 +}
   4.237 +
   4.238 +char* util_parent_path(char *path) {
   4.239 +    char *name = util_resource_name(path);
   4.240 +    size_t namelen = strlen(name);
   4.241 +    size_t pathlen = strlen(path);
   4.242 +    size_t parentlen = pathlen - namelen;
   4.243 +    char *parent = malloc(parentlen + 1);
   4.244 +    memcpy(parent, path, parentlen);
   4.245 +    parent[parentlen] = '\0';
   4.246 +    return parent;
   4.247 +}
   4.248 +
   4.249 +
   4.250 +char* util_xml_get_text(xmlNode *elm) {
   4.251 +    xmlNode *node = elm->children;
   4.252 +    while(node) {
   4.253 +        if(node->type == XML_TEXT_NODE) {
   4.254 +            return (char*)node->content;
   4.255 +        }
   4.256 +        node = node->next;
   4.257 +    }
   4.258 +    return NULL;
   4.259 +}
   4.260 +
   4.261 +
   4.262 +char* util_base64decode(char *in) {
   4.263 +    int len = 0;
   4.264 +    return util_base64decode_len(in, &len);
   4.265 +}
   4.266 +
   4.267 +char* util_base64decode_len(char* in, int *outlen) {
   4.268 +    size_t len = strlen(in);
   4.269 +    char *out = calloc(1, len);
   4.270 +    
   4.271 +    BIO* b = BIO_new_mem_buf(in, len);
   4.272 +    BIO *d = BIO_new(BIO_f_base64());
   4.273 +    BIO_set_flags(d, BIO_FLAGS_BASE64_NO_NL);
   4.274 +    b = BIO_push(d, b);
   4.275 +
   4.276 +    *outlen = BIO_read(b, out, len);
   4.277 +    BIO_free_all(b);
   4.278 +    
   4.279 +    return out;
   4.280 +}
   4.281 +
   4.282 +char* util_base64encode(char *in, size_t len) { 
   4.283 +    BIO *b;
   4.284 +    BIO *e;
   4.285 +    BUF_MEM *mem;
   4.286 +
   4.287 +    e = BIO_new(BIO_f_base64());
   4.288 +    b = BIO_new(BIO_s_mem());
   4.289 +    
   4.290 +    e = BIO_push(e, b);
   4.291 +    BIO_write(e, in, len);
   4.292 +    BIO_flush(e);
   4.293 +    
   4.294 +    BIO_get_mem_ptr(e, &mem);
   4.295 +    char *out = malloc(mem->length);
   4.296 +    memcpy(out, mem->data, mem->length -1);
   4.297 +    out[mem->length - 1] = '\0';
   4.298 +
   4.299 +    BIO_free_all(e);
   4.300 +
   4.301 +    return out;
   4.302 +}
   4.303 +
   4.304 +char* util_encrypt_str(DavSession *sn, char *str, char *key) {
   4.305 +    DavKey *k = dav_context_get_key(sn->context, key);
   4.306 +    if(!k) {
   4.307 +        // TODO: session error
   4.308 +        return NULL;
   4.309 +    }
   4.310 +    
   4.311 +    char *enc_str = aes_encrypt(str, k);
   4.312 +    char *ret_str = dav_session_strdup(sn, enc_str);
   4.313 +    free(enc_str);
   4.314 +    return ret_str;
   4.315 +}
   4.316 +
   4.317 +/* commented out for testing reasons */
   4.318 +/*
   4.319 +char* util_decrypt_str(DavSession *sn, char *str, char *key) {
   4.320 +    DavKey *k = dav_context_get_key(sn->context, key);
   4.321 +    if(!k) {
   4.322 +        // TODO: session error
   4.323 +        return NULL;
   4.324 +    }
   4.325 +    
   4.326 +    char *dec_str = aes_decrypt(str, k);
   4.327 +    char *ret_str = dav_session_strdup(sn, dec_str);
   4.328 +    free(dec_str);
   4.329 +    return ret_str;
   4.330 +}
   4.331 +*/
   4.332 +char* util_random_str() {
   4.333 +    unsigned char *str = malloc(25);
   4.334 +    str[24] = '\0';
   4.335 +    
   4.336 +    sstr_t t = S(
   4.337 +            "01234567890"
   4.338 +            "abcdefghijklmnopqrstuvwxyz"
   4.339 +            "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
   4.340 +    const unsigned char *table = (const unsigned char*)t.ptr;
   4.341 +    
   4.342 +    RAND_pseudo_bytes(str, 24);
   4.343 +    for(int i=0;i<24;i++) {
   4.344 +        int c = str[i] % t.length;
   4.345 +        str[i] = table[c];
   4.346 +    }
   4.347 +    
   4.348 +    return (char*)str;
   4.349 +}
   4.350 +
   4.351 +/*
   4.352 + * gets a substring from 0 to the appearance of the token
   4.353 + * tokens are separated by space
   4.354 + * sets sub to the substring and returns the remaining string
   4.355 + */
   4.356 +sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub) {  
   4.357 +    int i;
   4.358 +    int token_start = -1;
   4.359 +    int token_end = -1;
   4.360 +    for(i=0;i<=str.length;i++) {
   4.361 +        int c;
   4.362 +        if(i == str.length) {
   4.363 +            c = ' ';
   4.364 +        } else {
   4.365 +            c = str.ptr[i];
   4.366 +        }
   4.367 +        if(c < 33) {
   4.368 +            if(token_start != -1) {
   4.369 +                token_end = i;
   4.370 +                size_t len = token_end - token_start;
   4.371 +                sstr_t tk = sstrsubsl(str, token_start, len);
   4.372 +                //printf("token: {%.*s}\n", token.length, token.ptr);
   4.373 +                if(!sstrcmp(tk, token)) {
   4.374 +                    *sub = sstrtrim(sstrsubsl(str, 0, token_start));
   4.375 +                    break;
   4.376 +                }
   4.377 +                token_start = -1;
   4.378 +                token_end = -1;
   4.379 +            }
   4.380 +        } else {
   4.381 +            if(token_start == -1) {
   4.382 +                token_start = i;
   4.383 +            }
   4.384 +        }
   4.385 +    }
   4.386 +    
   4.387 +    if(i < str.length) {
   4.388 +        return sstrtrim(sstrsubs(str, i));
   4.389 +    } else {
   4.390 +        str.ptr = NULL;
   4.391 +        str.length = 0;
   4.392 +        return str;
   4.393 +    }
   4.394 +}
     5.1 --- a/test/ctestfile.c	Mon Oct 03 12:27:10 2022 +0200
     5.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.3 @@ -1,391 +0,0 @@
     5.4 -/*
     5.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     5.6 - *
     5.7 - * Copyright 2015 Olaf Wintermann. All rights reserved.
     5.8 - *
     5.9 - * Redistribution and use in source and binary forms, with or without
    5.10 - * modification, are permitted provided that the following conditions are met:
    5.11 - *
    5.12 - *   1. Redistributions of source code must retain the above copyright
    5.13 - *      notice, this list of conditions and the following disclaimer.
    5.14 - *
    5.15 - *   2. Redistributions in binary form must reproduce the above copyright
    5.16 - *      notice, this list of conditions and the following disclaimer in the
    5.17 - *      documentation and/or other materials provided with the distribution.
    5.18 - *
    5.19 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    5.20 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    5.21 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    5.22 - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    5.23 - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    5.24 - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    5.25 - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    5.26 - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    5.27 - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    5.28 - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    5.29 - * POSSIBILITY OF SUCH DAMAGE.
    5.30 - */
    5.31 -
    5.32 -#include <time.h>
    5.33 -#include <stdio.h>
    5.34 -#include <stdlib.h>
    5.35 -#include <string.h>
    5.36 -#include <ucx/string.h>
    5.37 -#include <ucx/buffer.h>
    5.38 -#include <ucx/utils.h>
    5.39 -#include <libxml/tree.h>
    5.40 -#include <curl/curl.h>
    5.41 -
    5.42 -#include <openssl/sha.h>
    5.43 -#include <openssl/hmac.h>
    5.44 -#include <openssl/evp.h>
    5.45 -#include <openssl/bio.h>
    5.46 -#include <openssl/buffer.h>
    5.47 -#include <openssl/rand.h>
    5.48 -
    5.49 -#include "utils.h"
    5.50 -#include "crypto.h"
    5.51 -#include "webdav.h"
    5.52 -
    5.53 -#define MACRO1337 1337L
    5.54 -
    5.55 -/* -------------------- This is a testing file. -------------------------- */
    5.56 -/*
    5.57 -time_t util_parse_creationdate(char *str) {
    5.58 -    // example: 2012-11-29T21:35:35Z
    5.59 -    if(!str) {
    5.60 -        return 0;
    5.61 -    }
    5.62 -    // TODO
    5.63 -    return 0;
    5.64 -}
    5.65 -*/
    5.66 -time_t util_parse_lastmodified(char *str) {
    5.67 -    // example: Thu, 29 Nov 2012 21:35:35 GMT
    5.68 -    if(!str) {
    5.69 -        return 0;
    5.70 -    } else {
    5.71 -        return curl_getdate(str, NULL);
    5.72 -    }
    5.73 -}
    5.74 -
    5.75 -int util_getboolean(char *v) {
    5.76 -    if(v[0] == 'T' || v[0] == 't') {
    5.77 -        return 1;
    5.78 -    }
    5.79 -    return 0;
    5.80 -}
    5.81 -
    5.82 -int util_strtoint(char *str, int64_t *value) {
    5.83 -    char *end;
    5.84 -    int64_t val = strtoll(str, &end, 0);
    5.85 -    if(strlen(end) == 0) {
    5.86 -        *value = val;
    5.87 -        return 1;
    5.88 -    } else {
    5.89 -        return 0;
    5.90 -    }
    5.91 -}
    5.92 -
    5.93 -char* util_url_path(char *url) { 
    5.94 -    char *path = NULL;
    5.95 -    size_t len = strlen(url);
    5.96 -    int slashcount = 0;
    5.97 -    int slmax;
    5.98 -    if(len > 7 && !strncasecmp(url, "http://", 7)) {
    5.99 -        slmax = 3;
   5.100 -    } else if(len > 8 && !strncasecmp(url, "https://", 8)) {
   5.101 -        slmax = 3;
   5.102 -    } else {
   5.103 -        slmax = 1;
   5.104 -    }
   5.105 -    char c;
   5.106 -    for(int i=0;i<len;i++) {
   5.107 -        c = url[i];
   5.108 -        if(c == '/') {
   5.109 -            slashcount++;
   5.110 -            if(slashcount == slmax) {
   5.111 -                path = url + i;
   5.112 -                break;
   5.113 -            }
   5.114 -        }
   5.115 -    } 
   5.116 -    return path;
   5.117 -}
   5.118 -
   5.119 -char* util_url_decode(DavSession *sn, char *url) {
   5.120 -    char *unesc = curl_easy_unescape(sn->handle, url, strlen(url), NULL);
   5.121 -    char *ret = strdup(unesc);
   5.122 -    curl_free(unesc);
   5.123 -    return ret;
   5.124 -}
   5.125 -
   5.126 -char* util_resource_name(char *url) {
   5.127 -    int si = 0;
   5.128 -    int osi = 0;
   5.129 -    int i = 0;
   5.130 -    int p = 0;
   5.131 -    char c;
   5.132 -    while((c = url[i]) != 0) {
   5.133 -        if(c == '/') {
   5.134 -            osi = si;
   5.135 -            si = i;
   5.136 -            p = 1;
   5.137 -        }
   5.138 -        i++;
   5.139 -    }
   5.140 -    
   5.141 -    char *name = url + si + p;
   5.142 -    if(name[0] == 0) {
   5.143 -        name = url + osi + p;
   5.144 -        if(name[0] == 0) {
   5.145 -            return url;
   5.146 -        }
   5.147 -    }
   5.148 -    
   5.149 -    return name;
   5.150 -}
   5.151 -
   5.152 -int util_mkdir(char *path, mode_t mode) {
   5.153 -#ifdef _WIN32
   5.154 -    return mkdir(path);
   5.155 -#else
   5.156 -    return mkdir(path, mode);
   5.157 -#endif
   5.158 -}
   5.159 -
   5.160 -char* util_concat_path(char *url_base, char *p) {
   5.161 -    sstr_t base = sstr(url_base);
   5.162 -    sstr_t path;
   5.163 -    if(p) {
   5.164 -        path = sstr(p);
   5.165 -    } else {
   5.166 -        path = sstrn("", 0);
   5.167 -    }
   5.168 -    
   5.169 -    int add_separator = 0;
   5.170 -    if(base.ptr[base.length-1] == '/') {
   5.171 -        if(path.ptr[0] == '/') {
   5.172 -            base.length--;
   5.173 -        }
   5.174 -    } else {
   5.175 -        if(path.length == 0 || path.ptr[0] != '/') {
   5.176 -            add_separator = 1;
   5.177 -        }
   5.178 -    }
   5.179 -    
   5.180 -    sstr_t url;
   5.181 -    if(add_separator) {
   5.182 -        url = sstrcat(3, base, sstr("/"), path);
   5.183 -    } else {
   5.184 -        url = sstrcat(2, base, path);
   5.185 -    }
   5.186 -    
   5.187 -    return url.ptr;
   5.188 -}
   5.189 -
   5.190 -void util_set_url(DavSession *sn, char *href) {
   5.191 -    sstr_t base = sstr(sn->base_url);
   5.192 -    sstr_t href_str = sstr(href);
   5.193 -    
   5.194 -    char *base_path = util_url_path(sn->base_url);
   5.195 -    base.length -= strlen(base_path);
   5.196 -    
   5.197 -    sstr_t url = sstrcat(2, base, href_str);
   5.198 -    
   5.199 -    curl_easy_setopt(sn->handle, CURLOPT_URL, url.ptr);
   5.200 -    free(url.ptr);
   5.201 -}
   5.202 -
   5.203 -char* util_path_to_url(DavSession *sn, char *path) {
   5.204 -    char *space = malloc(256);
   5.205 -    UcxBuffer *url = ucx_buffer_new(space, 256, UCX_BUFFER_AUTOEXTEND);
   5.206 -    
   5.207 -    // add base url
   5.208 -    ucx_buffer_write(sn->base_url, 1, strlen(sn->base_url), url);
   5.209 -    // remove trailing slash
   5.210 -    ucx_buffer_seek(url, -1, SEEK_CUR);
   5.211 -    
   5.212 -    sstr_t p = sstr(path);
   5.213 -    ssize_t ntk = 0;
   5.214 -    sstr_t *tks = sstrsplit(p, S("/"), &ntk);
   5.215 -    
   5.216 -    for(int i=0;i<ntk;i++) {
   5.217 -        sstr_t node = tks[i];
   5.218 -        if(node.length > 0) {
   5.219 -            char *esc = curl_easy_escape(sn->handle, node.ptr, node.length);
   5.220 -            ucx_buffer_putc(url, '/');
   5.221 -            ucx_buffer_write(esc, 1, strlen(esc), url);
   5.222 -            curl_free(esc);
   5.223 -        }
   5.224 -        free(node.ptr);
   5.225 -    }
   5.226 -    free(tks);
   5.227 -    if(path[p.length-1] == '/') {
   5.228 -        ucx_buffer_putc(url, '/');
   5.229 -    }
   5.230 -    ucx_buffer_putc(url, 0);
   5.231 -    
   5.232 -    space = url->space;
   5.233 -    ucx_buffer_free(url);
   5.234 -    
   5.235 -    return space;
   5.236 -}
   5.237 -
   5.238 -char* util_parent_path(char *path) {
   5.239 -    char *name = util_resource_name(path);
   5.240 -    size_t namelen = strlen(name);
   5.241 -    size_t pathlen = strlen(path);
   5.242 -    size_t parentlen = pathlen - namelen;
   5.243 -    char *parent = malloc(parentlen + 1);
   5.244 -    memcpy(parent, path, parentlen);
   5.245 -    parent[parentlen] = '\0';
   5.246 -    return parent;
   5.247 -}
   5.248 -
   5.249 -
   5.250 -char* util_xml_get_text(xmlNode *elm) {
   5.251 -    xmlNode *node = elm->children;
   5.252 -    while(node) {
   5.253 -        if(node->type == XML_TEXT_NODE) {
   5.254 -            return (char*)node->content;
   5.255 -        }
   5.256 -        node = node->next;
   5.257 -    }
   5.258 -    return NULL;
   5.259 -}
   5.260 -
   5.261 -
   5.262 -char* util_base64decode(char *in) {
   5.263 -    int len = 0;
   5.264 -    return util_base64decode_len(in, &len);
   5.265 -}
   5.266 -
   5.267 -char* util_base64decode_len(char* in, int *outlen) {
   5.268 -    size_t len = strlen(in);
   5.269 -    char *out = calloc(1, len);
   5.270 -    
   5.271 -    BIO* b = BIO_new_mem_buf(in, len);
   5.272 -    BIO *d = BIO_new(BIO_f_base64());
   5.273 -    BIO_set_flags(d, BIO_FLAGS_BASE64_NO_NL);
   5.274 -    b = BIO_push(d, b);
   5.275 -
   5.276 -    *outlen = BIO_read(b, out, len);
   5.277 -    BIO_free_all(b);
   5.278 -    
   5.279 -    return out;
   5.280 -}
   5.281 -
   5.282 -char* util_base64encode(char *in, size_t len) { 
   5.283 -    BIO *b;
   5.284 -    BIO *e;
   5.285 -    BUF_MEM *mem;
   5.286 -
   5.287 -    e = BIO_new(BIO_f_base64());
   5.288 -    b = BIO_new(BIO_s_mem());
   5.289 -    
   5.290 -    e = BIO_push(e, b);
   5.291 -    BIO_write(e, in, len);
   5.292 -    BIO_flush(e);
   5.293 -    
   5.294 -    BIO_get_mem_ptr(e, &mem);
   5.295 -    char *out = malloc(mem->length);
   5.296 -    memcpy(out, mem->data, mem->length -1);
   5.297 -    out[mem->length - 1] = '\0';
   5.298 -
   5.299 -    BIO_free_all(e);
   5.300 -
   5.301 -    return out;
   5.302 -}
   5.303 -
   5.304 -char* util_encrypt_str(DavSession *sn, char *str, char *key) {
   5.305 -    DavKey *k = dav_context_get_key(sn->context, key);
   5.306 -    if(!k) {
   5.307 -        // TODO: session error
   5.308 -        return NULL;
   5.309 -    }
   5.310 -    
   5.311 -    char *enc_str = aes_encrypt(str, k);
   5.312 -    char *ret_str = dav_session_strdup(sn, enc_str);
   5.313 -    free(enc_str);
   5.314 -    return ret_str;
   5.315 -}
   5.316 -
   5.317 -/* commented out for testing reasons */
   5.318 -/*
   5.319 -char* util_decrypt_str(DavSession *sn, char *str, char *key) {
   5.320 -    DavKey *k = dav_context_get_key(sn->context, key);
   5.321 -    if(!k) {
   5.322 -        // TODO: session error
   5.323 -        return NULL;
   5.324 -    }
   5.325 -    
   5.326 -    char *dec_str = aes_decrypt(str, k);
   5.327 -    char *ret_str = dav_session_strdup(sn, dec_str);
   5.328 -    free(dec_str);
   5.329 -    return ret_str;
   5.330 -}
   5.331 -*/
   5.332 -char* util_random_str() {
   5.333 -    unsigned char *str = malloc(25);
   5.334 -    str[24] = '\0';
   5.335 -    
   5.336 -    sstr_t t = S(
   5.337 -            "01234567890"
   5.338 -            "abcdefghijklmnopqrstuvwxyz"
   5.339 -            "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
   5.340 -    const unsigned char *table = (const unsigned char*)t.ptr;
   5.341 -    
   5.342 -    RAND_pseudo_bytes(str, 24);
   5.343 -    for(int i=0;i<24;i++) {
   5.344 -        int c = str[i] % t.length;
   5.345 -        str[i] = table[c];
   5.346 -    }
   5.347 -    
   5.348 -    return (char*)str;
   5.349 -}
   5.350 -
   5.351 -/*
   5.352 - * gets a substring from 0 to the appearance of the token
   5.353 - * tokens are separated by space
   5.354 - * sets sub to the substring and returns the remaining string
   5.355 - */
   5.356 -sstr_t util_getsubstr_until_token(sstr_t str, sstr_t token, sstr_t *sub) {  
   5.357 -    int i;
   5.358 -    int token_start = -1;
   5.359 -    int token_end = -1;
   5.360 -    for(i=0;i<=str.length;i++) {
   5.361 -        int c;
   5.362 -        if(i == str.length) {
   5.363 -            c = ' ';
   5.364 -        } else {
   5.365 -            c = str.ptr[i];
   5.366 -        }
   5.367 -        if(c < 33) {
   5.368 -            if(token_start != -1) {
   5.369 -                token_end = i;
   5.370 -                size_t len = token_end - token_start;
   5.371 -                sstr_t tk = sstrsubsl(str, token_start, len);
   5.372 -                //printf("token: {%.*s}\n", token.length, token.ptr);
   5.373 -                if(!sstrcmp(tk, token)) {
   5.374 -                    *sub = sstrtrim(sstrsubsl(str, 0, token_start));
   5.375 -                    break;
   5.376 -                }
   5.377 -                token_start = -1;
   5.378 -                token_end = -1;
   5.379 -            }
   5.380 -        } else {
   5.381 -            if(token_start == -1) {
   5.382 -                token_start = i;
   5.383 -            }
   5.384 -        }
   5.385 -    }
   5.386 -    
   5.387 -    if(i < str.length) {
   5.388 -        return sstrtrim(sstrsubs(str, i));
   5.389 -    } else {
   5.390 -        str.ptr = NULL;
   5.391 -        str.length = 0;
   5.392 -        return str;
   5.393 -    }
   5.394 -}
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/test/golden-master/empty.html	Mon Oct 03 12:56:28 2022 +0200
     6.3 @@ -0,0 +1,53 @@
     6.4 +<!DOCTYPE html>
     6.5 +<html>
     6.6 +  <head>
     6.7 +    <title>c2html</title>
     6.8 +    <style type="text/css">
     6.9 +      a.c2html-lineno {
    6.10 +        /* as long as user-select isn't widely spread, we throw the bomb */
    6.11 +        -webkit-user-select: none;
    6.12 +        -moz-user-select: none;
    6.13 +        -ms-user-select: none;
    6.14 +        user-select: none;
    6.15 +        display: inline-block;
    6.16 +        font-style: italic;
    6.17 +        text-decoration: none;
    6.18 +        color: grey;
    6.19 +      }
    6.20 +      span.c2html-keyword {
    6.21 +        color: blue;
    6.22 +      }
    6.23 +      span.c2html-macroconst {
    6.24 +        color: cornflowerblue;
    6.25 +      }
    6.26 +      span.c2html-type {
    6.27 +        color: cornflowerblue;
    6.28 +      }
    6.29 +      span.c2html-directive {
    6.30 +        color: green;
    6.31 +      }
    6.32 +      span.c2html-string {
    6.33 +        color: darkorange;
    6.34 +      }
    6.35 +      span.c2html-comment {
    6.36 +        color: grey;
    6.37 +      }
    6.38 +      span.c2html-stdinclude {
    6.39 +        color: darkorange;
    6.40 +      }
    6.41 +      span.c2html-userinclude {
    6.42 +        color: darkorange;
    6.43 +      }
    6.44 +      a.c2html-userinclude {
    6.45 +        color: darkorange;
    6.46 +        text-decoration: underline;
    6.47 +      }
    6.48 +    </style>
    6.49 +  </head>
    6.50 +  <body>
    6.51 +
    6.52 +<pre>
    6.53 +<a class="c2html-lineno" name="l1" href="#l1">1 </a></pre>
    6.54 +  </body>
    6.55 +</html>
    6.56 +
     7.1 --- a/test/golden-master/emptyfile.html	Mon Oct 03 12:27:10 2022 +0200
     7.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.3 @@ -1,54 +0,0 @@
     7.4 -<!DOCTYPE html>
     7.5 -<html>
     7.6 -  <head>
     7.7 -    <title>c2html</title>
     7.8 -    <style type="text/css">
     7.9 -      a.c2html-lineno {
    7.10 -        /* as long as user-select isn't widely spread, we throw the bomb */
    7.11 -        -webkit-user-select: none;
    7.12 -        -moz-user-select: none;
    7.13 -        -ms-user-select: none;
    7.14 -        user-select: none;
    7.15 -        display: inline-block;
    7.16 -        font-style: italic;
    7.17 -        text-decoration: none;
    7.18 -        color: grey;
    7.19 -      }
    7.20 -      span.c2html-keyword {
    7.21 -        color: blue;
    7.22 -      }
    7.23 -      span.c2html-macroconst {
    7.24 -        color: cornflowerblue;
    7.25 -      }
    7.26 -      span.c2html-type {
    7.27 -        color: cornflowerblue;
    7.28 -      }
    7.29 -      span.c2html-directive {
    7.30 -        color: green;
    7.31 -      }
    7.32 -      span.c2html-string {
    7.33 -        color: darkorange;
    7.34 -      }
    7.35 -      span.c2html-comment {
    7.36 -        color: grey;
    7.37 -      }
    7.38 -      span.c2html-stdinclude {
    7.39 -        color: darkorange;
    7.40 -      }
    7.41 -      span.c2html-userinclude {
    7.42 -        color: darkorange;
    7.43 -      }
    7.44 -      a.c2html-userinclude {
    7.45 -        color: darkorange;
    7.46 -        text-decoration: underline;
    7.47 -      }
    7.48 -    </style>
    7.49 -  </head>
    7.50 -  <body>
    7.51 -
    7.52 -<pre>
    7.53 -<a class="c2html-lineno" name="l1" href="#l1">1 </a>
    7.54 -</pre>
    7.55 -  </body>
    7.56 -</html>
    7.57 -
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/test/javatest.java	Mon Oct 03 12:56:28 2022 +0200
     8.3 @@ -0,0 +1,176 @@
     8.4 +/*
     8.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     8.6 + *
     8.7 + * Copyright 2014 Mike Becker. All rights reserved.
     8.8 + *
     8.9 + * Redistribution and use in source and binary forms, with or without
    8.10 + * modification, are permitted provided that the following conditions are met:
    8.11 + *
    8.12 + *   1. Redistributions of source code must retain the above copyright
    8.13 + *      notice, this list of conditions and the following disclaimer.
    8.14 + *
    8.15 + *   2. Redistributions in binary form must reproduce the above copyright
    8.16 + *      notice, this list of conditions and the following disclaimer in the
    8.17 + *      documentation and/or other materials provided with the distribution.
    8.18 + *
    8.19 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    8.20 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    8.21 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    8.22 + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    8.23 + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    8.24 + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    8.25 + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    8.26 + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    8.27 + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    8.28 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    8.29 + * POSSIBILITY OF SUCH DAMAGE.
    8.30 + *
    8.31 + */
    8.32 +
    8.33 +package de.uapcore.sigred.doc.base;
    8.34 +
    8.35 +import de.uapcore.sigred.doc.Resources;
    8.36 +import de.uapcore.sigrapi.impl.Digraph;
    8.37 +import de.uapcore.sigrapi.impl.Graph;
    8.38 +import de.uapcore.sigrapi.IGraph;
    8.39 +import java.io.IOException;
    8.40 +import java.io.InputStream;
    8.41 +import java.io.OutputStream;
    8.42 +import java.util.concurrent.atomic.AtomicBoolean;
    8.43 +import java.util.concurrent.atomic.AtomicReference;
    8.44 +import org.apache.xerces.impl.Constants;
    8.45 +import org.dom4j.Document;
    8.46 +import org.dom4j.DocumentException;
    8.47 +import org.dom4j.DocumentHelper;
    8.48 +import org.dom4j.Element;
    8.49 +import org.dom4j.Namespace;
    8.50 +import org.dom4j.QName;
    8.51 +import org.dom4j.io.OutputFormat;
    8.52 +import org.dom4j.io.SAXReader;
    8.53 +import org.dom4j.io.XMLWriter;
    8.54 +import org.xml.sax.ErrorHandler;
    8.55 +import org.xml.sax.SAXException;
    8.56 +import org.xml.sax.SAXParseException;
    8.57 +
    8.58 +public abstract class AbstractGraphDocument<T extends IGraph>
    8.59 +        extends FileBackedDocument {
    8.60 +    
    8.61 +    protected static final Namespace NAMESPACE = Namespace.get("sigred",
    8.62 +        "http://develop.uap-core.de/sigred/");
    8.63 +    
    8.64 +    private static final
    8.65 +        QName TAG_GRAPHDOC = QName.get("graph-document", NAMESPACE);
    8.66 +    private static final
    8.67 +        QName TAG_GRAPH = QName.get("graph", NAMESPACE);
    8.68 +    private static final
    8.69 +        QName TAG_DIGRAPH = QName.get("digraph", NAMESPACE);
    8.70 +    private static final
    8.71 +        QName TAG_METADATA = QName.get("metadata", NAMESPACE);
    8.72 +    
    8.73 +    protected final T graph;
    8.74 +    
    8.75 +    private final GraphDocumentMetadata metadata;
    8.76 +    
    8.77 +    public AbstractGraphDocument(Class<T> graphType) {
    8.78 +        T g;
    8.79 +        try {
    8.80 +            g = graphType.newInstance();
    8.81 +        } catch (ReflectiveOperationException e) {
    8.82 +            assert false;
    8.83 +            g = null; // for the compiler
    8.84 +        }
    8.85 +        graph = g;
    8.86 +        metadata = new GraphDocumentMetadata();
    8.87 +    }
    8.88 +
    8.89 +    public T getGraph() {
    8.90 +        return graph;
    8.91 +    }
    8.92 +    
    8.93 +    public GraphDocumentMetadata getMetadata() {
    8.94 +        return metadata;
    8.95 +    }
    8.96 +
    8.97 +    protected abstract void writeGraph(Element rootNode) throws IOException;
    8.98 +    protected abstract void readGraph(Element rootNode) throws IOException;
    8.99 +
   8.100 +    @Override
   8.101 +    public void writeTo(OutputStream out) throws IOException {
   8.102 +        Document doc = DocumentHelper.createDocument();
   8.103 +
   8.104 +        Element rootNode = doc.addElement(TAG_GRAPHDOC);
   8.105 +
   8.106 +        Element metadataNode = rootNode.addElement(TAG_METADATA);
   8.107 +
   8.108 +        metadata.write(metadataNode);
   8.109 +
   8.110 +        if (graph instanceof Graph) {
   8.111 +            writeGraph(rootNode.addElement(TAG_GRAPH));
   8.112 +        } else if (graph instanceof Digraph) {
   8.113 +            writeGraph(rootNode.addElement(TAG_DIGRAPH));
   8.114 +        } else {
   8.115 +            throw new IOException("unsupported graph type");
   8.116 +        }
   8.117 +
   8.118 +        XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
   8.119 +        writer.write(doc);
   8.120 +        writer.flush();
   8.121 +    }
   8.122 +
   8.123 +    @Override
   8.124 +    public void readFrom(InputStream in) throws IOException {
   8.125 +        try {
   8.126 +            SAXReader reader = new SAXReader(true);
   8.127 +            reader.setStripWhitespaceText(true);
   8.128 +            
   8.129 +            reader.setFeature(Constants.XERCES_FEATURE_PREFIX+
   8.130 +                Constants.SCHEMA_VALIDATION_FEATURE, true);
   8.131 +            reader.setProperty(Constants.XERCES_PROPERTY_PREFIX +
   8.132 +                Constants.SCHEMA_LOCATION, String.format("%s %s",
   8.133 +                    NAMESPACE.getURI(), Resources.class.getResource(
   8.134 +                        "graph-document.xsd").toExternalForm()));
   8.135 +            
   8.136 +            final AtomicBoolean passed = new AtomicBoolean(true);
   8.137 +            final AtomicReference<SAXParseException> xmlerror = new AtomicReference<>();
   8.138 +            // TODO: we should do more detailed error handling here
   8.139 +            reader.setErrorHandler(new ErrorHandler() {
   8.140 +                @Override
   8.141 +                public void warning(SAXParseException exception) throws SAXException {
   8.142 +                }
   8.143 +
   8.144 +                @Override
   8.145 +                public void error(SAXParseException exception) throws SAXException {
   8.146 +                    xmlerror.set(exception);
   8.147 +                    passed.set(false);
   8.148 +                }
   8.149 +
   8.150 +                @Override
   8.151 +                public void fatalError(SAXParseException exception) throws SAXException {
   8.152 +                    xmlerror.set(exception);
   8.153 +                    passed.set(false);
   8.154 +                }
   8.155 +                
   8.156 +            });
   8.157 +            Document doc = reader.read(in);
   8.158 +            if (!passed.get()) {
   8.159 +                // TODO: provide details (maybe via separate error object?)
   8.160 +                throw xmlerror.get();
   8.161 +            }
   8.162 +            
   8.163 +            doc.normalize();
   8.164 +            
   8.165 +            Element root = doc.getRootElement();
   8.166 +            metadata.read(root.element(TAG_METADATA));
   8.167 +            
   8.168 +            if (graph instanceof Graph) {
   8.169 +                readGraph(root.element(TAG_GRAPH));
   8.170 +            } else if (graph instanceof Digraph) {
   8.171 +                readGraph(root.element(TAG_DIGRAPH));
   8.172 +            } else {
   8.173 +                throw new IOException("unsupported graph type");
   8.174 +            }
   8.175 +        } catch (DocumentException | SAXException ex) {
   8.176 +            throw new IOException(ex);
   8.177 +        }
   8.178 +    }
   8.179 +}
     9.1 --- a/test/javatestfile.java	Mon Oct 03 12:27:10 2022 +0200
     9.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.3 @@ -1,176 +0,0 @@
     9.4 -/*
     9.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     9.6 - *
     9.7 - * Copyright 2014 Mike Becker. All rights reserved.
     9.8 - *
     9.9 - * Redistribution and use in source and binary forms, with or without
    9.10 - * modification, are permitted provided that the following conditions are met:
    9.11 - *
    9.12 - *   1. Redistributions of source code must retain the above copyright
    9.13 - *      notice, this list of conditions and the following disclaimer.
    9.14 - *
    9.15 - *   2. Redistributions in binary form must reproduce the above copyright
    9.16 - *      notice, this list of conditions and the following disclaimer in the
    9.17 - *      documentation and/or other materials provided with the distribution.
    9.18 - *
    9.19 - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    9.20 - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    9.21 - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    9.22 - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    9.23 - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    9.24 - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    9.25 - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    9.26 - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    9.27 - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    9.28 - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    9.29 - * POSSIBILITY OF SUCH DAMAGE.
    9.30 - *
    9.31 - */
    9.32 -
    9.33 -package de.uapcore.sigred.doc.base;
    9.34 -
    9.35 -import de.uapcore.sigred.doc.Resources;
    9.36 -import de.uapcore.sigrapi.impl.Digraph;
    9.37 -import de.uapcore.sigrapi.impl.Graph;
    9.38 -import de.uapcore.sigrapi.IGraph;
    9.39 -import java.io.IOException;
    9.40 -import java.io.InputStream;
    9.41 -import java.io.OutputStream;
    9.42 -import java.util.concurrent.atomic.AtomicBoolean;
    9.43 -import java.util.concurrent.atomic.AtomicReference;
    9.44 -import org.apache.xerces.impl.Constants;
    9.45 -import org.dom4j.Document;
    9.46 -import org.dom4j.DocumentException;
    9.47 -import org.dom4j.DocumentHelper;
    9.48 -import org.dom4j.Element;
    9.49 -import org.dom4j.Namespace;
    9.50 -import org.dom4j.QName;
    9.51 -import org.dom4j.io.OutputFormat;
    9.52 -import org.dom4j.io.SAXReader;
    9.53 -import org.dom4j.io.XMLWriter;
    9.54 -import org.xml.sax.ErrorHandler;
    9.55 -import org.xml.sax.SAXException;
    9.56 -import org.xml.sax.SAXParseException;
    9.57 -
    9.58 -public abstract class AbstractGraphDocument<T extends IGraph>
    9.59 -        extends FileBackedDocument {
    9.60 -    
    9.61 -    protected static final Namespace NAMESPACE = Namespace.get("sigred",
    9.62 -        "http://develop.uap-core.de/sigred/");
    9.63 -    
    9.64 -    private static final
    9.65 -        QName TAG_GRAPHDOC = QName.get("graph-document", NAMESPACE);
    9.66 -    private static final
    9.67 -        QName TAG_GRAPH = QName.get("graph", NAMESPACE);
    9.68 -    private static final
    9.69 -        QName TAG_DIGRAPH = QName.get("digraph", NAMESPACE);
    9.70 -    private static final
    9.71 -        QName TAG_METADATA = QName.get("metadata", NAMESPACE);
    9.72 -    
    9.73 -    protected final T graph;
    9.74 -    
    9.75 -    private final GraphDocumentMetadata metadata;
    9.76 -    
    9.77 -    public AbstractGraphDocument(Class<T> graphType) {
    9.78 -        T g;
    9.79 -        try {
    9.80 -            g = graphType.newInstance();
    9.81 -        } catch (ReflectiveOperationException e) {
    9.82 -            assert false;
    9.83 -            g = null; // for the compiler
    9.84 -        }
    9.85 -        graph = g;
    9.86 -        metadata = new GraphDocumentMetadata();
    9.87 -    }
    9.88 -
    9.89 -    public T getGraph() {
    9.90 -        return graph;
    9.91 -    }
    9.92 -    
    9.93 -    public GraphDocumentMetadata getMetadata() {
    9.94 -        return metadata;
    9.95 -    }
    9.96 -
    9.97 -    protected abstract void writeGraph(Element rootNode) throws IOException;
    9.98 -    protected abstract void readGraph(Element rootNode) throws IOException;
    9.99 -
   9.100 -    @Override
   9.101 -    public void writeTo(OutputStream out) throws IOException {
   9.102 -        Document doc = DocumentHelper.createDocument();
   9.103 -
   9.104 -        Element rootNode = doc.addElement(TAG_GRAPHDOC);
   9.105 -
   9.106 -        Element metadataNode = rootNode.addElement(TAG_METADATA);
   9.107 -
   9.108 -        metadata.write(metadataNode);
   9.109 -
   9.110 -        if (graph instanceof Graph) {
   9.111 -            writeGraph(rootNode.addElement(TAG_GRAPH));
   9.112 -        } else if (graph instanceof Digraph) {
   9.113 -            writeGraph(rootNode.addElement(TAG_DIGRAPH));
   9.114 -        } else {
   9.115 -            throw new IOException("unsupported graph type");
   9.116 -        }
   9.117 -
   9.118 -        XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
   9.119 -        writer.write(doc);
   9.120 -        writer.flush();
   9.121 -    }
   9.122 -
   9.123 -    @Override
   9.124 -    public void readFrom(InputStream in) throws IOException {
   9.125 -        try {
   9.126 -            SAXReader reader = new SAXReader(true);
   9.127 -            reader.setStripWhitespaceText(true);
   9.128 -            
   9.129 -            reader.setFeature(Constants.XERCES_FEATURE_PREFIX+
   9.130 -                Constants.SCHEMA_VALIDATION_FEATURE, true);
   9.131 -            reader.setProperty(Constants.XERCES_PROPERTY_PREFIX +
   9.132 -                Constants.SCHEMA_LOCATION, String.format("%s %s",
   9.133 -                    NAMESPACE.getURI(), Resources.class.getResource(
   9.134 -                        "graph-document.xsd").toExternalForm()));
   9.135 -            
   9.136 -            final AtomicBoolean passed = new AtomicBoolean(true);
   9.137 -            final AtomicReference<SAXParseException> xmlerror = new AtomicReference<>();
   9.138 -            // TODO: we should do more detailed error handling here
   9.139 -            reader.setErrorHandler(new ErrorHandler() {
   9.140 -                @Override
   9.141 -                public void warning(SAXParseException exception) throws SAXException {
   9.142 -                }
   9.143 -
   9.144 -                @Override
   9.145 -                public void error(SAXParseException exception) throws SAXException {
   9.146 -                    xmlerror.set(exception);
   9.147 -                    passed.set(false);
   9.148 -                }
   9.149 -
   9.150 -                @Override
   9.151 -                public void fatalError(SAXParseException exception) throws SAXException {
   9.152 -                    xmlerror.set(exception);
   9.153 -                    passed.set(false);
   9.154 -                }
   9.155 -                
   9.156 -            });
   9.157 -            Document doc = reader.read(in);
   9.158 -            if (!passed.get()) {
   9.159 -                // TODO: provide details (maybe via separate error object?)
   9.160 -                throw xmlerror.get();
   9.161 -            }
   9.162 -            
   9.163 -            doc.normalize();
   9.164 -            
   9.165 -            Element root = doc.getRootElement();
   9.166 -            metadata.read(root.element(TAG_METADATA));
   9.167 -            
   9.168 -            if (graph instanceof Graph) {
   9.169 -                readGraph(root.element(TAG_GRAPH));
   9.170 -            } else if (graph instanceof Digraph) {
   9.171 -                readGraph(root.element(TAG_DIGRAPH));
   9.172 -            } else {
   9.173 -                throw new IOException("unsupported graph type");
   9.174 -            }
   9.175 -        } catch (DocumentException | SAXException ex) {
   9.176 -            throw new IOException(ex);
   9.177 -        }
   9.178 -    }
   9.179 -}
    10.1 --- a/test/plain.csp	Mon Oct 03 12:27:10 2022 +0200
    10.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.3 @@ -1,6 +0,0 @@
    10.4 -</body>
    10.5 -</html>
    10.6 -<!c
    10.7 -pblock_free(q);
    10.8 -!>
    10.9 -
    11.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.2 +++ b/test/plain.txt	Mon Oct 03 12:56:28 2022 +0200
    11.3 @@ -0,0 +1,6 @@
    11.4 +</body>
    11.5 +</html>
    11.6 +<!c
    11.7 +pblock_free(q);
    11.8 +!>
    11.9 +

mercurial