<commit_before>#include "IncludeDefine.h"
#include "Parameters.h"
#include "Transcript.h"
#include "ReadAlign.h"
#include "BAMfunctions.h"
#include "blocksOverlap.h"
#include "ChimericSegment.h"
#include "ChimericAlign.h"

int chimericAlignScore (ChimericSegment & seg1, ChimericSegment & seg2)
{
    int chimScore=0;
    uint chimOverlap = seg2.roS>seg1.roS ?  (seg2.roS>seg1.roE ? 0 : seg1.roE-seg2.roS+1) : (seg2.roE<seg1.roS ? 0 : seg2.roE-seg1.roS+1);
    bool diffMates=(seg1.roE < seg1.align.readLength[0] && seg2.roS >= seg1.align.readLength[0]) || (seg2.roE < seg1.align.readLength[0] && seg1.roS >= seg1.align.readLength[0]);

    //segment lengths && (different mates || small gap between segments)
    if (seg1.roE > seg1.P.pCh.segmentMin + seg1.roS + chimOverlap && seg2.roE > seg1.P.pCh.segmentMin + seg2.roS + chimOverlap  \
        && ( diffMates || ( (seg1.roE + seg1.P.pCh.segmentReadGapMax + 1) >= seg2.roS && (seg2.roE + seg1.P.pCh.segmentReadGapMax + 1) >= seg1.roS ) ) )
    {
       chimScore = seg1.align.maxScore + seg2.align.maxScore - (int)chimOverlap; //subtract overlap to avoid double counting
    };

    return chimScore;
};

/////////////////////////////////////////////////////////////
bool ReadAlign::chimericDetectionMult() {

    bool chimRecord=false;
    vector <ChimericAlign> chimAligns;
    int chimScoreBest=0;

    for (uint iW1=0; iW1<nW; iW1++) {//cycle windows
        for (uint iA1=0; iA1<nWinTr[iW1]; iA1++) {//cycle aligns in the window

            ChimericSegment seg1(P,*trAll[iW1][iA1]);

            bool seg1yes = true;
            seg1yes = seg1yes && seg1.align.rLength >= P.pCh.segmentMin; //mapped length >= chim segmentMin
            seg1yes = seg1yes && (seg1.align.exons[seg1.align.nExons-1][EX_R] + seg1.align.exons[seg1.align.nExons-1][EX_L] + P.pCh.segmentMin <= Lread \
                      || seg1.align.exons[0][EX_R] >= P.pCh.segmentMin); //uncovered by seg1 read length is <= segmentMin
            seg1yes = seg1yes && seg1.align.intronMotifs[0]==0; //no non-canonical juncions. TODO: allow non-canonical anotated
            seg1yes = seg1yes && (seg1.align.intronMotifs[1]==0 || seg1.align.intronMotifs[2]==0); //consistent intron motifs. TODO: switch to strands

            if (!seg1yes)
                continue; //seg1 is bad

            for (uint iW2=0; iW2<nW; iW2++)
            {//check all windows for chimeras
                for (uint iA2=0; iA2<nWinTr[iW2]; iA2++)
                {//cycle over aligns in the window
                    if (trBest!=trAll[iW2][0] && iA2>0) break; //check only best transcripts in each window 2(i.e. iA2=0), for all windows except that of the trBest
                    if (trBest==trAll[iW2][0] && iA2==0) continue;//for trBest window, check all iA2>0

                    ChimericSegment seg2(P,*trAll[iW2][iA2]);

                    if (seg2.align.intronMotifs[0]>0)
                        continue; //do not stitch to a window with non-canonical junctions
                    if (seg1.str!=0 && seg2.str!=0 && seg2.str!=seg1.str)
                        continue; //chimeric segments have to have consitent strands

                    int chimScore=chimericAlignScore(seg1,seg2);

                    if ( &seg1.align!=trBest && &seg2.align!=trBest )
                        continue; //debug

                    if  (chimScore>0)
                    {//candidate chimera

                        ChimericSegment *s1=&seg1,*s2=&seg2;
                        if (seg1.align.roStart > seg2.align.roStart) swap (s1,s2);
                        uint e1 = s1->align.Str==1 ? 0 : s1->align.nExons-1;
                        uint e2 = s2->align.Str==0 ? 0 : s2->align.nExons-1;
                        if ( s1->align.exons[e1][EX_iFrag] > s2->align.exons[e2][EX_iFrag] )
                            continue; //strange configuration

                        //         if ( trChim[0].exons[e0][EX_iFrag] > trChim[1].exons[e1][EX_iFrag] ) {//strange configuration, rare, similar to the next one
                        //             chimN=0;//reject such chimeras
                        //             //good test example:
                        //             //CTTAGCTAGCAGCGTCTTCCCAGTGCCTGGAGGGCCAGTGAGAATGGCACCCTCTGGGATTTTTGCTCCTAGGTCT
                        //             //TTGAGGTGAAGTTCAAAGATGTGGCTGGCTGTGAGGAGGCCGAGCTAGAGATCATGGAATTTGTGAATTTCTTGAA
                        //         } else

                        if (s1->align.exons[e1][EX_L] < P.pCh.junctionOverhangMin &&  s2->align.exons[e2][EX_L] < P.pCh.junctionOverhangMin)
                            continue; //junction overhangs too short

                        if (chimScore>=chimScoreBest-(int)P.pCh.multimapScoreRange && chimScore >= P.pCh.scoreMin)
                            chimAligns.push_back(ChimericAlign(seg1, seg2, chimScore));

                        if ( chimScore > chimScoreBest && chimScore >= (int)(readLength[0]+readLength[1]) - P.pCh.scoreDropMax ) {
                            chimAligns.back().chimericStitching(mapGen.G, Read1[0]);
                            if (chimAligns.back().chimScore > chimScoreBest)
                                chimScoreBest=chimAligns.back().chimScore;
                        };

                    };
                };//cycle over window2 aligns
            };//cycle over window2
        };//cycle over window1 aligns
    };//cycle over window1

    if (chimScoreBest==0)
        return chimRecord;

    chimN=0;
    for (uint ic=0; ic<chimAligns.size(); ic++) {//scan all chimeras, find the number within score range
        if (chimAligns[ic].chimScore >= chimScoreBest - (int)P.pCh.multimapScoreRange)
            ++chimN;
    };
    if (chimN > 2*P.pCh.multimapNmax) //too many loci (considering 2* more candidates for stitching below)
        return chimRecord;

    chimN=0;
    for (uint ic=0; ic<chimAligns.size(); ic++) {//re-scan all chimeras: stitch and re-check the score
        if (chimAligns[ic].chimScore >= chimScoreBest-(int)P.pCh.multimapScoreRange) {
            chimAligns[ic].chimericStitching(mapGen.G, Read1[0]);
            if (chimAligns[ic].chimScore >= chimScoreBest - (int)P.pCh.multimapScoreRange)
                ++chimN;
        };
    };
    if (chimN > P.pCh.multimapNmax) //too many loci
        return chimRecord;

    for (uint ic=0; ic<chimAligns.size(); ic++) {//output chimeras within score range
        if (chimAligns[ic].chimScore >= chimScoreBest-(int)P.pCh.multimapScoreRange)
            chimAligns[ic].chimericJunctionOutput(chunkOutChimJunction);
    };

    if (chimN>0)
        chimRecord=true;

    return chimRecord;
};//END
<commit_msg>Scanning through all windows/aligns, not just bestTr. This adds a number of multimapping chimeras. With --chimMultimapNmax 50, all the old chimeric reads are present.<commit_after>#include "IncludeDefine.h"
#include "Parameters.h"
#include "Transcript.h"
#include "ReadAlign.h"
#include "BAMfunctions.h"
#include "blocksOverlap.h"
#include "ChimericSegment.h"
#include "ChimericAlign.h"

int chimericAlignScore (ChimericSegment & seg1, ChimericSegment & seg2)
{
    int chimScore=0;
    uint chimOverlap = seg2.roS>seg1.roS ?  (seg2.roS>seg1.roE ? 0 : seg1.roE-seg2.roS+1) : (seg2.roE<seg1.roS ? 0 : seg2.roE-seg1.roS+1);
    bool diffMates=(seg1.roE < seg1.align.readLength[0] && seg2.roS >= seg1.align.readLength[0]) || (seg2.roE < seg1.align.readLength[0] && seg1.roS >= seg1.align.readLength[0]);

    //segment lengths && (different mates || small gap between segments)
    if (seg1.roE > seg1.P.pCh.segmentMin + seg1.roS + chimOverlap && seg2.roE > seg1.P.pCh.segmentMin + seg2.roS + chimOverlap  \
        && ( diffMates || ( (seg1.roE + seg1.P.pCh.segmentReadGapMax + 1) >= seg2.roS && (seg2.roE + seg1.P.pCh.segmentReadGapMax + 1) >= seg1.roS ) ) )
    {
       chimScore = seg1.align.maxScore + seg2.align.maxScore - (int)chimOverlap; //subtract overlap to avoid double counting
    };

    return chimScore;
};

/////////////////////////////////////////////////////////////
bool ReadAlign::chimericDetectionMult() {

    bool chimRecord=false;
    vector <ChimericAlign> chimAligns;
    int chimScoreBest=0;

    for (uint iW1=0; iW1<nW; iW1++) {//cycle windows
        for (uint iA1=0; iA1<nWinTr[iW1]; iA1++) {//cycle aligns in the window

            ChimericSegment seg1(P,*trAll[iW1][iA1]);

            bool seg1yes = true;
            seg1yes = seg1yes && seg1.align.rLength >= P.pCh.segmentMin; //mapped length >= chim segmentMin
            seg1yes = seg1yes && (seg1.align.exons[seg1.align.nExons-1][EX_R] + seg1.align.exons[seg1.align.nExons-1][EX_L] + P.pCh.segmentMin <= Lread \
                      || seg1.align.exons[0][EX_R] >= P.pCh.segmentMin); //uncovered by seg1 read length is <= segmentMin
            seg1yes = seg1yes && seg1.align.intronMotifs[0]==0; //no non-canonical juncions. TODO: allow non-canonical anotated
            seg1yes = seg1yes && (seg1.align.intronMotifs[1]==0 || seg1.align.intronMotifs[2]==0); //consistent intron motifs. TODO: switch to strands

            if (!seg1yes)
                continue; //seg1 is bad

            for (uint iW2=iW1; iW2<nW; iW2++) {//check all windows for chimeras
                for (uint iA2=(iW1!=iW2 ? 0 : iA1+1); iA2<nWinTr[iW2]; iA2++) {//cycle over aligns in the window
                    //for the same window, start iA2 at iA1+1 to avoid duplicating

                    ChimericSegment seg2(P,*trAll[iW2][iA2]);

                    if (seg2.align.intronMotifs[0]>0)
                        continue; //do not stitch to a window with non-canonical junctions
                    if (seg1.str!=0 && seg2.str!=0 && seg2.str!=seg1.str)
                        continue; //chimeric segments have to have consitent strands

                    int chimScore=chimericAlignScore(seg1,seg2);

                    if ( &seg1.align!=trBest && &seg2.align!=trBest )
                        continue; //debug

                    if  (chimScore>0)
                    {//candidate chimera

                        ChimericSegment *s1=&seg1,*s2=&seg2;
                        if (seg1.align.roStart > seg2.align.roStart) swap (s1,s2);
                        uint e1 = s1->align.Str==1 ? 0 : s1->align.nExons-1;
                        uint e2 = s2->align.Str==0 ? 0 : s2->align.nExons-1;
                        if ( s1->align.exons[e1][EX_iFrag] > s2->align.exons[e2][EX_iFrag] )
                            continue; //strange configuration

                        //         if ( trChim[0].exons[e0][EX_iFrag] > trChim[1].exons[e1][EX_iFrag] ) {//strange configuration, rare, similar to the next one
                        //             chimN=0;//reject such chimeras
                        //             //good test example:
                        //             //CTTAGCTAGCAGCGTCTTCCCAGTGCCTGGAGGGCCAGTGAGAATGGCACCCTCTGGGATTTTTGCTCCTAGGTCT
                        //             //TTGAGGTGAAGTTCAAAGATGTGGCTGGCTGTGAGGAGGCCGAGCTAGAGATCATGGAATTTGTGAATTTCTTGAA
                        //         } else

                        if (s1->align.exons[e1][EX_L] < P.pCh.junctionOverhangMin &&  s2->align.exons[e2][EX_L] < P.pCh.junctionOverhangMin)
                            continue; //junction overhangs too short

                        if (chimScore>=chimScoreBest-(int)P.pCh.multimapScoreRange && chimScore >= P.pCh.scoreMin)
                            chimAligns.push_back(ChimericAlign(seg1, seg2, chimScore));

                        if ( chimScore > chimScoreBest && chimScore >= (int)(readLength[0]+readLength[1]) - P.pCh.scoreDropMax ) {
                            chimAligns.back().chimericStitching(mapGen.G, Read1[0]);
                            if (chimAligns.back().chimScore > chimScoreBest)
                                chimScoreBest=chimAligns.back().chimScore;
                        };

                    };
                };//cycle over window2 aligns
            };//cycle over window2
        };//cycle over window1 aligns
    };//cycle over window1

    if (chimScoreBest==0)
        return chimRecord;

    chimN=0;
    for (uint ic=0; ic<chimAligns.size(); ic++) {//scan all chimeras, find the number within score range
        if (chimAligns[ic].chimScore >= chimScoreBest - (int)P.pCh.multimapScoreRange)
            ++chimN;
    };
    if (chimN > 2*P.pCh.multimapNmax) //too many loci (considering 2* more candidates for stitching below)
        return chimRecord;

    chimN=0;
    for (uint ic=0; ic<chimAligns.size(); ic++) {//re-scan all chimeras: stitch and re-check the score
        if (chimAligns[ic].chimScore >= chimScoreBest-(int)P.pCh.multimapScoreRange) {
            chimAligns[ic].chimericStitching(mapGen.G, Read1[0]);
            if (chimAligns[ic].chimScore >= chimScoreBest - (int)P.pCh.multimapScoreRange)
                ++chimN;
        };
    };
    if (chimN > P.pCh.multimapNmax) //too many loci
        return chimRecord;

    for (uint ic=0; ic<chimAligns.size(); ic++) {//output chimeras within score range
        if (chimAligns[ic].chimScore >= chimScoreBest-(int)P.pCh.multimapScoreRange)
            chimAligns[ic].chimericJunctionOutput(chunkOutChimJunction);
    };

    if (chimN>0)
        chimRecord=true;

    return chimRecord;
};//END
<|endoftext|>


<commit_before>#include "stdafx.h"
#include "TestLib.h"
#include "LandauVishkin.h"

// Test fixture for all the Landau-Viskhin Tests
struct LandauVishkinTest {
    LandauVishkin lv;
    LandauVishkinWithCigar lvc;
};

TEST_F(LandauVishkinTest, "equal strings") {
    ASSERT_EQ(0, lv.computeEditDistance("abcde", 5, "abcde", 5, 2));
}

TEST_F(LandauVishkinTest, "prefixes") {
    ASSERT_EQ(0, lv.computeEditDistance("abcde", 5, "abcd", 4, 2));
    ASSERT_EQ(0, lv.computeEditDistance("abcde", 5, "abc", 3, 2));
    ASSERT_EQ(0, lv.computeEditDistance("abcde", 5, "ab", 2, 2));
}

TEST_F(LandauVishkinTest, "non-equal strings") {
    ASSERT_EQ(1, lv.computeEditDistance("abcde", 5, "abcdX", 5, 2));
    ASSERT_EQ(1, lv.computeEditDistance("abcde", 5, "abde", 4, 2));
    ASSERT_EQ(1, lv.computeEditDistance("abcde", 5, "bcde", 4, 2));
    ASSERT_EQ(1, lv.computeEditDistance("abcde", 5, "abcXde", 6, 2));
    ASSERT_EQ(2, lv.computeEditDistance("abcde", 5, "abXXe", 5, 2));
    ASSERT_EQ(2, lv.computeEditDistance("abcde", 5, "abcXXde", 7, 2));
}

TEST_F(LandauVishkinTest, "overly distant strings") {
    ASSERT_EQ(-1, lv.computeEditDistance("abcde", 5, "XXXXX", 5, 2));
}

TEST_F(LandauVishkinTest, "CIGAR strings") {
    char cigarBuf[1024];
    int bufLen = sizeof(cigarBuf);

    lvc.computeEditDistance("abcde", 5, "abcde", 5, 2, cigarBuf, bufLen);
    ASSERT_STREQ("5=", cigarBuf);

    lvc.computeEditDistance("abcdef", 6, "abcde", 5, 2, cigarBuf, bufLen);
    ASSERT_STREQ("5=", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "abcdX", 5, 2, cigarBuf, bufLen);
    ASSERT_STREQ("4=1X", cigarBuf);   // This used to give 4=1I before

    lvc.computeEditDistance("abcde", 5, "Xbcde", 5, 2, cigarBuf, bufLen);
    ASSERT_STREQ("1X4=", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "abde", 4, 2, cigarBuf, bufLen);
    ASSERT_STREQ("2=1D2=", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "bcde", 4, 2, cigarBuf, bufLen);
    ASSERT_STREQ("1D4=", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "abcXde", 6, 2, cigarBuf, bufLen);
    ASSERT_STREQ("3=1I2=", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "abXXe", 5, 2, cigarBuf, bufLen);
    ASSERT_STREQ("2=2X1=", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "abcXXde", 7, 3, cigarBuf, bufLen);
    ASSERT_STREQ("3=2I2=", cigarBuf);

    lvc.computeEditDistance("ttttc", 5, "tttc", 4, 3, cigarBuf, bufLen);
    ASSERT_STREQ("3=1X", cigarBuf);

    lvc.computeEditDistance("tttcc", 5, "ttttc", 5, 3, cigarBuf, bufLen);
    ASSERT_STREQ("3=1X1=", cigarBuf);

    lvc.computeEditDistance("tttcc", 5, "tttaa", 5, 3, cigarBuf, bufLen);
    ASSERT_STREQ("3=2X", cigarBuf);

    // A real example where we used to give 1D and later 1I instead of 2X
    lvc.computeEditDistance("atctcag", 7, "acttcag", 7, 3, cigarBuf, bufLen);
    ASSERT_STREQ("1=2X4=", cigarBuf);

    // Edge cases when pattern is longer than available text
    lvc.computeEditDistance("abc", 3, "abcde", 5, 3, cigarBuf, bufLen);
    ASSERT_STREQ("3=2X", cigarBuf);

    lvc.computeEditDistance("abc", 3, "abXde", 5, 3, cigarBuf, bufLen);
    ASSERT_STREQ("2=3X", cigarBuf);
}
<commit_msg>Add useM tests to LandauVishkinTest.cpp<commit_after>#include "stdafx.h"
#include "TestLib.h"
#include "LandauVishkin.h"

// Test fixture for all the Landau-Viskhin Tests
struct LandauVishkinTest {
    LandauVishkin lv;
    LandauVishkinWithCigar lvc;
};

TEST_F(LandauVishkinTest, "equal strings") {
    ASSERT_EQ(0, lv.computeEditDistance("abcde", 5, "abcde", 5, 2));
}

TEST_F(LandauVishkinTest, "prefixes") {
    ASSERT_EQ(0, lv.computeEditDistance("abcde", 5, "abcd", 4, 2));
    ASSERT_EQ(0, lv.computeEditDistance("abcde", 5, "abc", 3, 2));
    ASSERT_EQ(0, lv.computeEditDistance("abcde", 5, "ab", 2, 2));
}

TEST_F(LandauVishkinTest, "non-equal strings") {
    ASSERT_EQ(1, lv.computeEditDistance("abcde", 5, "abcdX", 5, 2));
    ASSERT_EQ(1, lv.computeEditDistance("abcde", 5, "abde", 4, 2));
    ASSERT_EQ(1, lv.computeEditDistance("abcde", 5, "bcde", 4, 2));
    ASSERT_EQ(1, lv.computeEditDistance("abcde", 5, "abcXde", 6, 2));
    ASSERT_EQ(2, lv.computeEditDistance("abcde", 5, "abXXe", 5, 2));
    ASSERT_EQ(2, lv.computeEditDistance("abcde", 5, "abcXXde", 7, 2));
}

TEST_F(LandauVishkinTest, "overly distant strings") {
    ASSERT_EQ(-1, lv.computeEditDistance("abcde", 5, "XXXXX", 5, 2));
}

TEST_F(LandauVishkinTest, "CIGAR strings") {
    char cigarBuf[1024];
    int bufLen = sizeof(cigarBuf);

    lvc.computeEditDistance("abcde", 5, "abcde", 5, 2, cigarBuf, bufLen, false);
    ASSERT_STREQ("5=", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "abcde", 5, 2, cigarBuf, bufLen, true);
    ASSERT_STREQ("5M", cigarBuf);

    lvc.computeEditDistance("abcdef", 6, "abcde", 5, 2, cigarBuf, bufLen, false);
    ASSERT_STREQ("5=", cigarBuf);

    lvc.computeEditDistance("abcdef", 6, "abcde", 5, 2, cigarBuf, bufLen, true);
    ASSERT_STREQ("5M", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "abcdX", 5, 2, cigarBuf, bufLen, false);
    ASSERT_STREQ("4=1X", cigarBuf);   // This used to give 4=1I before

    lvc.computeEditDistance("abcde", 5, "abcdX", 5, 2, cigarBuf, bufLen, true);
    ASSERT_STREQ("5M", cigarBuf);   // This used to give 4=1I before

    lvc.computeEditDistance("abcde", 5, "Xbcde", 5, 2, cigarBuf, bufLen, false);
    ASSERT_STREQ("1X4=", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "Xbcde", 5, 2, cigarBuf, bufLen, true);
    ASSERT_STREQ("5M", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "abde", 4, 2, cigarBuf, bufLen, false);
    ASSERT_STREQ("2=1D2=", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "abde", 4, 2, cigarBuf, bufLen, true);
    ASSERT_STREQ("2M1D2M", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "bcde", 4, 2, cigarBuf, bufLen, false);
    ASSERT_STREQ("1D4=", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "bcde", 4, 2, cigarBuf, bufLen, true);
    ASSERT_STREQ("1D4M", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "abcXde", 6, 2, cigarBuf, bufLen, false);
    ASSERT_STREQ("3=1I2=", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "abcXde", 6, 2, cigarBuf, bufLen, true);
    ASSERT_STREQ("3M1I2M", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "abXXe", 5, 2, cigarBuf, bufLen, false);
    ASSERT_STREQ("2=2X1=", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "abXXe", 5, 2, cigarBuf, bufLen, true);
    ASSERT_STREQ("2M2X1M", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "abcXXde", 7, 3, cigarBuf, bufLen, false);
    ASSERT_STREQ("3=2I2=", cigarBuf);

    lvc.computeEditDistance("abcde", 5, "abcXXde", 7, 3, cigarBuf, bufLen, true);
    ASSERT_STREQ("3M2I2M", cigarBuf);

    lvc.computeEditDistance("ttttc", 5, "tttc", 4, 3, cigarBuf, bufLen, false);
    ASSERT_STREQ("3=1X", cigarBuf);

    lvc.computeEditDistance("ttttc", 5, "tttc", 4, 3, cigarBuf, bufLen, true);
    ASSERT_STREQ("4M", cigarBuf);

    lvc.computeEditDistance("tttcc", 5, "ttttc", 5, 3, cigarBuf, bufLen, false);
    ASSERT_STREQ("3=1X1=", cigarBuf);

    lvc.computeEditDistance("tttcc", 5, "ttttc", 5, 3, cigarBuf, bufLen, true);
    ASSERT_STREQ("5M", cigarBuf);

    lvc.computeEditDistance("tttcc", 5, "tttaa", 5, 3, cigarBuf, bufLen, false);
    ASSERT_STREQ("3=2X", cigarBuf);

    lvc.computeEditDistance("tttcc", 5, "tttaa", 5, 3, cigarBuf, bufLen, true);
    ASSERT_STREQ("5M", cigarBuf);

    // A real example where we used to give 1D and later 1I instead of 2X
    lvc.computeEditDistance("atctcag", 7, "acttcag", 7, 3, cigarBuf, bufLen, false);
    ASSERT_STREQ("1=2X4=", cigarBuf);

    lvc.computeEditDistance("atctcag", 7, "acttcag", 7, 3, cigarBuf, bufLen, true);
    ASSERT_STREQ("7M", cigarBuf);

    // Edge cases when pattern is longer than available text
    lvc.computeEditDistance("abc", 3, "abcde", 5, 3, cigarBuf, bufLen, false);
    ASSERT_STREQ("3=2X", cigarBuf);

    lvc.computeEditDistance("abc", 3, "abcde", 5, 3, cigarBuf, bufLen, true);
    ASSERT_STREQ("5M", cigarBuf);

    lvc.computeEditDistance("abc", 3, "abXde", 5, 3, cigarBuf, bufLen, false);
    ASSERT_STREQ("2=3X", cigarBuf);

    lvc.computeEditDistance("abc", 3, "abXde", 5, 3, cigarBuf, bufLen, true);
    ASSERT_STREQ("5M", cigarBuf);
}
<|endoftext|>


<commit_before>#include "tankitem.h"

#include <QPainter>
#include <QGraphicsScene>

#include <math.h>

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

class Action
{
public:
    Action(TankItem *item) : m_item(item)
    {
    }

    TankItem *item() const { return m_item; }
    void setItem(TankItem *item) { m_item = item; }

    virtual bool apply(qreal timeDelta) = 0;

private:
    TankItem *m_item;
};

class MoveAction: public Action
{
public:
    MoveAction(TankItem *item, qreal distance)
        : Action(item), m_distance(distance)
    {
        m_reverse = m_distance < 0.0;
    }

    bool apply(qreal timeDelta)
    {
        qreal dist = timeDelta * item()->speed() * (m_reverse ? -1.0 : 1.0);

        m_distance -= dist;
        if (m_reverse && m_distance > 0.0)
            return false;
        else if (m_distance < 0.0)
            return false;

        qreal a = item()->direction() * M_PI / 180.0;

        qreal yd = dist * sin(a);
        qreal xd = dist * sin(M_PI / 2.0 - a);

        item()->setPos(item()->pos() + QPointF(xd, yd));
        return true;
    }

private:
    qreal m_distance;
    bool m_reverse;
};

class TurnAction: public Action
{
public:
    TurnAction(TankItem *item, qreal distance)
        : Action(item), m_distance(distance)
    {
        m_reverse = m_distance < 0.0;
    }

    bool apply(qreal timeDelta)
    {
        qreal dist = timeDelta * item()->angularSpeed() * (m_reverse ? -1.0 : 1.0);
        m_distance -= dist;
        if (m_reverse && m_distance > 0.0)
            return false;
        else if (m_distance < 0.0)
            return false;

        item()->setDirection(item()->direction() + dist);
        return true;
    }

private:
    qreal m_distance;
    bool m_reverse;
};

TankItem::TankItem(QObject *parent)
    : GameItem(parent), m_currentAction(0), m_currentDirection(0.0), m_enabled(true)
{
    connect(this, SIGNAL(cannonFired()), this, SIGNAL(actionCompleted()));
}

void TankItem::idle(qreal elapsed)
{
    if (m_enabled) {
        if (m_currentAction != 0) {
            if (!m_currentAction->apply(elapsed)) {
                setAction(0);
                emit actionCompleted();
            }

            QGraphicsItem *item = 0;
            qreal distance = distanceToObstacle(&item);
            if (TankItem *tankItem = qgraphicsitem_cast<TankItem *>(item))
                emit tankSpotted(tankItem->direction(), distance);
        }
    }
}

void TankItem::hitByRocket()
{
    deleteLater();
}

void TankItem::setAction(Action *newAction)
{
    if (m_currentAction != 0)
        delete m_currentAction;

    m_currentAction = newAction;
}

void TankItem::fireCannon()
{
    emit cannonFired();
}

void TankItem::moveForwards(qreal length)
{
    setAction(new MoveAction(this, length));
}

void TankItem::moveBackwards(qreal length)
{
    setAction(new MoveAction(this, -length));
}

void TankItem::turn(qreal degrees)
{
    setAction(new TurnAction(this, degrees));
}

void TankItem::turnTo(qreal degrees)
{
    setAction(new TurnAction(this, degrees - direction()));
}

void TankItem::stop()
{
    setAction(0);
}

QVariant TankItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
    if (change == ItemPositionChange && scene()) {
        QPointF requestedPosition = value.toPointF();
        QLineF collidedLine;
        QPointF nextPoint = tryMove(requestedPosition, &collidedLine);
        if (nextPoint != requestedPosition)
            emit collision(collidedLine);
        return nextPoint;
    } else {
        return QGraphicsItem::itemChange(change, value);
    }
}

void TankItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
    QRectF brect = boundingRect();

    painter->setBrush(m_color);
    painter->setPen(Qt::black);

    // body
    painter->drawRect(brect.adjusted(0.0, 4.0, -2.0, -4.0));

    // cannon
    QRectF cannonBase = brect.adjusted(10.0, 6.0, -12.0, -6.0);
    painter->drawEllipse(cannonBase);

    painter->drawRect(QRectF(QPointF(cannonBase.center().x(), cannonBase.center().y() - 2.0),
                             QPointF(brect.right(), cannonBase.center().y() + 2.0)));

    // left track
    painter->setBrush(QBrush(Qt::black, Qt::VerPattern));
    QRectF leftTrackRect = QRectF(brect.topLeft(), QPointF(brect.right() - 2.0, brect.top() + 4.0));
    painter->fillRect(leftTrackRect, Qt::darkYellow);
    painter->drawRect(leftTrackRect);

    // right track
    QRectF rightTrackRect = QRectF(QPointF(brect.left(), brect.bottom() - 4.0),
                                   QPointF(brect.right() - 2.0, brect.bottom()));
    painter->fillRect(rightTrackRect, Qt::darkYellow);
    painter->drawRect(rightTrackRect);

    if (!m_enabled) {
        painter->setPen(QPen(Qt::red, 5));

        painter->drawEllipse(brect);

        QPainterPath path;
        path.addEllipse(brect);
        painter->setClipPath(path);
        painter->drawLine(brect.topRight(), brect.bottomLeft());
    }
}

QRectF TankItem::boundingRect() const
{
    return QRectF(-20.0, -10.0, 40.0, 20.0);
}

qreal TankItem::direction() const
{
    return m_currentDirection;
}

void TankItem::setDirection(qreal newDirection)
{
    qreal diff = newDirection - m_currentDirection;
    m_currentDirection = newDirection;
    rotate(diff);
}

qreal TankItem::distanceToObstacle(QGraphicsItem **obstacle) const
{
    qreal dist = sqrt(pow(scene()->sceneRect().width(), 2) + pow(scene()->sceneRect().height(), 2));

    qreal a = m_currentDirection * M_PI / 180.0;

    qreal yd = dist * sin(a);
    qreal xd = dist * sin(M_PI / 2.0 - a);

    QPointF requestedPosition = pos() + QPointF(xd, yd);
    QGraphicsItem *collidedItem = 0;
    QPointF nextPosition = tryMove(requestedPosition, 0, &collidedItem);
    if (collidedItem != 0) {
        if (obstacle != 0)
            *obstacle = collidedItem;

        QPointF d = nextPosition - pos();
        return sqrt(pow(d.x(), 2) + pow(d.y(), 2));
    } else {
        return 0.0;
    }
}

qreal TankItem::distanceToObstacle() const
{
    return distanceToObstacle(0);
}

<commit_msg>Fix support reverse actions. Logic was broken and would always return false immediately for reverse actions, thus immediately stopping them.<commit_after>#include "tankitem.h"

#include <QPainter>
#include <QGraphicsScene>

#include <math.h>

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

class Action
{
public:
    Action(TankItem *item) : m_item(item)
    {
    }

    TankItem *item() const { return m_item; }
    void setItem(TankItem *item) { m_item = item; }

    virtual bool apply(qreal timeDelta) = 0;

private:
    TankItem *m_item;
};

class MoveAction: public Action
{
public:
    MoveAction(TankItem *item, qreal distance)
        : Action(item), m_distance(distance)
    {
        m_reverse = m_distance < 0.0;
    }

    bool apply(qreal timeDelta)
    {
        qreal dist = timeDelta * item()->speed() * (m_reverse ? -1.0 : 1.0);

        m_distance -= dist;
        if (m_reverse && m_distance > 0.0)
            return false;
        else if (!m_reverse && m_distance < 0.0)
            return false;

        qreal a = item()->direction() * M_PI / 180.0;

        qreal yd = dist * sin(a);
        qreal xd = dist * sin(M_PI / 2.0 - a);

        item()->setPos(item()->pos() + QPointF(xd, yd));
        return true;
    }

private:
    qreal m_distance;
    bool m_reverse;
};

class TurnAction: public Action
{
public:
    TurnAction(TankItem *item, qreal distance)
        : Action(item), m_distance(distance)
    {
        m_reverse = m_distance < 0.0;
    }

    bool apply(qreal timeDelta)
    {
        qreal dist = timeDelta * item()->angularSpeed() * (m_reverse ? -1.0 : 1.0);
        m_distance -= dist;
        if (m_reverse && m_distance > 0.0)
            return false;
        else if (!m_reverse && m_distance < 0.0)
            return false;

        item()->setDirection(item()->direction() + dist);
        return true;
    }

private:
    qreal m_distance;
    bool m_reverse;
};

TankItem::TankItem(QObject *parent)
    : GameItem(parent), m_currentAction(0), m_currentDirection(0.0), m_enabled(true)
{
    connect(this, SIGNAL(cannonFired()), this, SIGNAL(actionCompleted()));
}

void TankItem::idle(qreal elapsed)
{
    if (m_enabled) {
        if (m_currentAction != 0) {
            if (!m_currentAction->apply(elapsed)) {
                setAction(0);
                emit actionCompleted();
            }

            QGraphicsItem *item = 0;
            qreal distance = distanceToObstacle(&item);
            if (TankItem *tankItem = qgraphicsitem_cast<TankItem *>(item))
                emit tankSpotted(tankItem->direction(), distance);
        }
    }
}

void TankItem::hitByRocket()
{
    deleteLater();
}

void TankItem::setAction(Action *newAction)
{
    if (m_currentAction != 0)
        delete m_currentAction;

    m_currentAction = newAction;
}

void TankItem::fireCannon()
{
    emit cannonFired();
}

void TankItem::moveForwards(qreal length)
{
    setAction(new MoveAction(this, length));
}

void TankItem::moveBackwards(qreal length)
{
    setAction(new MoveAction(this, -length));
}

void TankItem::turn(qreal degrees)
{
    setAction(new TurnAction(this, degrees));
}

void TankItem::turnTo(qreal degrees)
{
    setAction(new TurnAction(this, degrees - direction()));
}

void TankItem::stop()
{
    setAction(0);
}

QVariant TankItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
{
    if (change == ItemPositionChange && scene()) {
        QPointF requestedPosition = value.toPointF();
        QLineF collidedLine;
        QPointF nextPoint = tryMove(requestedPosition, &collidedLine);
        if (nextPoint != requestedPosition)
            emit collision(collidedLine);
        return nextPoint;
    } else {
        return QGraphicsItem::itemChange(change, value);
    }
}

void TankItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
    QRectF brect = boundingRect();

    painter->setBrush(m_color);
    painter->setPen(Qt::black);

    // body
    painter->drawRect(brect.adjusted(0.0, 4.0, -2.0, -4.0));

    // cannon
    QRectF cannonBase = brect.adjusted(10.0, 6.0, -12.0, -6.0);
    painter->drawEllipse(cannonBase);

    painter->drawRect(QRectF(QPointF(cannonBase.center().x(), cannonBase.center().y() - 2.0),
                             QPointF(brect.right(), cannonBase.center().y() + 2.0)));

    // left track
    painter->setBrush(QBrush(Qt::black, Qt::VerPattern));
    QRectF leftTrackRect = QRectF(brect.topLeft(), QPointF(brect.right() - 2.0, brect.top() + 4.0));
    painter->fillRect(leftTrackRect, Qt::darkYellow);
    painter->drawRect(leftTrackRect);

    // right track
    QRectF rightTrackRect = QRectF(QPointF(brect.left(), brect.bottom() - 4.0),
                                   QPointF(brect.right() - 2.0, brect.bottom()));
    painter->fillRect(rightTrackRect, Qt::darkYellow);
    painter->drawRect(rightTrackRect);

    if (!m_enabled) {
        painter->setPen(QPen(Qt::red, 5));

        painter->drawEllipse(brect);

        QPainterPath path;
        path.addEllipse(brect);
        painter->setClipPath(path);
        painter->drawLine(brect.topRight(), brect.bottomLeft());
    }
}

QRectF TankItem::boundingRect() const
{
    return QRectF(-20.0, -10.0, 40.0, 20.0);
}

qreal TankItem::direction() const
{
    return m_currentDirection;
}

void TankItem::setDirection(qreal newDirection)
{
    qreal diff = newDirection - m_currentDirection;
    m_currentDirection = newDirection;
    rotate(diff);
}

qreal TankItem::distanceToObstacle(QGraphicsItem **obstacle) const
{
    qreal dist = sqrt(pow(scene()->sceneRect().width(), 2) + pow(scene()->sceneRect().height(), 2));

    qreal a = m_currentDirection * M_PI / 180.0;

    qreal yd = dist * sin(a);
    qreal xd = dist * sin(M_PI / 2.0 - a);

    QPointF requestedPosition = pos() + QPointF(xd, yd);
    QGraphicsItem *collidedItem = 0;
    QPointF nextPosition = tryMove(requestedPosition, 0, &collidedItem);
    if (collidedItem != 0) {
        if (obstacle != 0)
            *obstacle = collidedItem;

        QPointF d = nextPosition - pos();
        return sqrt(pow(d.x(), 2) + pow(d.y(), 2));
    } else {
        return 0.0;
    }
}

qreal TankItem::distanceToObstacle() const
{
    return distanceToObstacle(0);
}

<|endoftext|>


<commit_before>//
//  WOZ.cpp
//  Clock Signal
//
//  Created by Thomas Harte on 23/04/2018.
//  Copyright 2018 Thomas Harte. All rights reserved.
//

#include "WOZ.hpp"

#include "../../Track/PCMTrack.hpp"
#include "../../Track/TrackSerialiser.hpp"

#include <cstring>

using namespace Storage::Disk;

WOZ::WOZ(const std::string &file_name) :
	file_(file_name) {

	constexpr const char signature1[8] = {
		'W', 'O', 'Z', '1',
		char(0xff), 0x0a, 0x0d, 0x0a
	};
	constexpr const char signature2[8] = {
		'W', 'O', 'Z', '2',
		char(0xff), 0x0a, 0x0d, 0x0a
	};

	const bool isWoz1 = file_.check_signature(signature1, 8);
	file_.seek(0, SEEK_SET);
	const bool isWoz2 = file_.check_signature(signature2, 8);

	if(!isWoz1 && !isWoz2) throw Error::InvalidFormat;
	type_ = isWoz2 ? Type::WOZ2 : Type::WOZ1;

	// Get the file's CRC32.
	const uint32_t crc = file_.get32le();

	// Get the collection of all data that contributes to the CRC.
	post_crc_contents_ = file_.read(size_t(file_.stats().st_size - 12));

	// Test the CRC.
	const uint32_t computed_crc = crc_generator.compute_crc(post_crc_contents_);
	if(crc != computed_crc) {
		 throw Error::InvalidFormat;
	}

	// Retreat to the first byte after the CRC.
	file_.seek(12, SEEK_SET);

	// Parse all chunks up front.
	bool has_tmap = false;
	while(true) {
		const uint32_t chunk_id = file_.get32le();
		const uint32_t chunk_size = file_.get32le();
		if(file_.eof()) break;

		long end_of_chunk = file_.tell() + long(chunk_size);

		#define CK(str) (str[0] | (str[1] << 8) | (str[2] << 16) | (str[3] << 24))
		switch(chunk_id) {
			case CK("INFO"): {
				const uint8_t version = file_.get8();
				if(version > 2) break;
				is_3_5_disk_ = file_.get8() == 2;
				is_read_only_ = file_.get8() == 1;
				/*
					Ignored:
						1 byte: Synchronized; 1 = Cross track sync was used during imaging.
						1 byte: Cleaned; 1 = MC3470 fake bits have been removed.
						32 bytes: Creator; a UTF-8 string.

					And, if version 2, following the creator:
						1 byte number of disk sides
						1 byte boot sector format
						1 byte optimal bit timing
						2 bytes compatible hardware
						2 bytes minimum required RAM
						2 bytes largest track
				*/
			} break;

			case CK("TMAP"): {
				file_.read(track_map_, 160);
				has_tmap = true;
			} break;

			case CK("TRKS"): {
				tracks_offset_ = file_.tell();
			} break;

			// TODO: parse META chunks.

			default:
			break;
		}
		#undef CK

		file_.seek(end_of_chunk, SEEK_SET);
	}

	if(tracks_offset_ == -1 || !has_tmap) throw Error::InvalidFormat;
}

HeadPosition WOZ::get_maximum_head_position() {
	return is_3_5_disk_ ? HeadPosition(80) : HeadPosition(160, 4);
}

int WOZ::get_head_count() {
	return is_3_5_disk_ ? 2 : 1;
}

long WOZ::file_offset(Track::Address address) {
	// Calculate table position.
	int table_position;
	if(!is_3_5_disk_) {
		table_position = address.head * 160 + address.position.as_quarter();
	} else {
		if(type_ == Type::WOZ1) {
			table_position = address.head * 80 + address.position.as_int();
		} else {
			table_position = address.head + (address.position.as_int() * 2);
		}
	}

	// Check that this track actually exists.
	if(track_map_[table_position] == 0xff) {
		return NoSuchTrack;
	}

	// Seek to the real track.
	switch(type_) {
		case Type::WOZ1:	return tracks_offset_ + track_map_[table_position] * 6656;
		default:
		case Type::WOZ2:	return tracks_offset_ + track_map_[table_position] * 8;
	}
}

bool WOZ::tracks_differ(Track::Address lhs, Track::Address rhs) {
	const long offset1 = file_offset(lhs);
	const long offset2 = file_offset(rhs);
	return offset1 != offset2;
}

std::shared_ptr<Track> WOZ::get_track_at_position(Track::Address address) {
	const long offset = file_offset(address);
	if(offset == NoSuchTrack) {
		return nullptr;
	}

	// Seek to the real track.
	std::vector<uint8_t> track_contents;
	size_t number_of_bits;
	{
		std::lock_guard lock_guard(file_.get_file_access_mutex());
		file_.seek(offset, SEEK_SET);

		switch(type_) {
			case Type::WOZ1:
				// In WOZ 1, a track is up to 6646 bytes of data, followed by a two-byte record of the
				// number of bytes that actually had data in them, then a two-byte count of the number
				// of bits that were used. Other information follows but is not intended for emulation.
				track_contents = file_.read(6646);
				file_.seek(2, SEEK_CUR);
				number_of_bits = std::min(file_.get16le(), uint16_t(6646*8));
			break;

			case Type::WOZ2: {
				// In WOZ 2 an extra level of indirection allows for variable track sizes.
				const uint16_t starting_block = file_.get16le();
				file_.seek(2, SEEK_CUR);	// Skip the block count; the amount of data to read is implied by the number of bits.
				number_of_bits = file_.get32le();

				file_.seek(starting_block * 512, SEEK_SET);
				track_contents = file_.read((number_of_bits + 7) >> 3);
			} break;
		}
	}

	return std::make_shared<PCMTrack>(PCMSegment(number_of_bits, track_contents));
}

void WOZ::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) {
	if(type_ == Type::WOZ2) return;

	for(const auto &pair: tracks) {
		// Decode the track and store, patching into the post_crc_contents_.
		auto segment = Storage::Disk::track_serialisation(*pair.second, Storage::Time(1, 50000));

		auto offset = size_t(file_offset(pair.first) - 12);
		std::vector<uint8_t> segment_bytes = segment.byte_data();
		memcpy(&post_crc_contents_[offset - 12], segment_bytes.data(), segment_bytes.size());

		// Write number of bytes and number of bits.
		post_crc_contents_[offset + 6646] = uint8_t(segment.data.size() >> 3);
		post_crc_contents_[offset + 6647] = uint8_t(segment.data.size() >> 11);
		post_crc_contents_[offset + 6648] = uint8_t(segment.data.size());
		post_crc_contents_[offset + 6649] = uint8_t(segment.data.size() >> 8);

		// Set no splice information now provided, since it's been lost if ever it was known.
		post_crc_contents_[offset + 6650] = 0xff;
		post_crc_contents_[offset + 6651] = 0xff;
	}

	// Calculate the new CRC.
	const uint32_t crc = crc_generator.compute_crc(post_crc_contents_);

	// Grab the file lock, then write the CRC, then just dump the entire file buffer.
	std::lock_guard lock_guard(file_.get_file_access_mutex());
	file_.seek(8, SEEK_SET);
	file_.put_le(crc);
	file_.write(post_crc_contents_);
}

bool WOZ::get_is_read_only() {
	/*
		There is an unintended issue with the disk code that sites above here: it doesn't understand the idea
		of multiple addresses mapping to the same track, yet it maintains a cache of track contents. Therefore
		if a WOZ is written to, what's written will magically be exactly 1/4 track wide, not affecting its
		neighbours. I've made WOZs readonly until I can correct that issue.
	*/
	return true;
//	return file_.get_is_known_read_only() || is_read_only_ || type_ == Type::WOZ2;	// WOZ 2 disks are currently read only.
}
<commit_msg>Ensures WOZ2 behaviour even if `type_` has an invalid value.<commit_after>//
//  WOZ.cpp
//  Clock Signal
//
//  Created by Thomas Harte on 23/04/2018.
//  Copyright 2018 Thomas Harte. All rights reserved.
//

#include "WOZ.hpp"

#include "../../Track/PCMTrack.hpp"
#include "../../Track/TrackSerialiser.hpp"

#include <cstring>

using namespace Storage::Disk;

WOZ::WOZ(const std::string &file_name) :
	file_(file_name) {

	constexpr const char signature1[8] = {
		'W', 'O', 'Z', '1',
		char(0xff), 0x0a, 0x0d, 0x0a
	};
	constexpr const char signature2[8] = {
		'W', 'O', 'Z', '2',
		char(0xff), 0x0a, 0x0d, 0x0a
	};

	const bool isWoz1 = file_.check_signature(signature1, 8);
	file_.seek(0, SEEK_SET);
	const bool isWoz2 = file_.check_signature(signature2, 8);

	if(!isWoz1 && !isWoz2) throw Error::InvalidFormat;
	type_ = isWoz2 ? Type::WOZ2 : Type::WOZ1;

	// Get the file's CRC32.
	const uint32_t crc = file_.get32le();

	// Get the collection of all data that contributes to the CRC.
	post_crc_contents_ = file_.read(size_t(file_.stats().st_size - 12));

	// Test the CRC.
	const uint32_t computed_crc = crc_generator.compute_crc(post_crc_contents_);
	if(crc != computed_crc) {
		 throw Error::InvalidFormat;
	}

	// Retreat to the first byte after the CRC.
	file_.seek(12, SEEK_SET);

	// Parse all chunks up front.
	bool has_tmap = false;
	while(true) {
		const uint32_t chunk_id = file_.get32le();
		const uint32_t chunk_size = file_.get32le();
		if(file_.eof()) break;

		long end_of_chunk = file_.tell() + long(chunk_size);

		#define CK(str) (str[0] | (str[1] << 8) | (str[2] << 16) | (str[3] << 24))
		switch(chunk_id) {
			case CK("INFO"): {
				const uint8_t version = file_.get8();
				if(version > 2) break;
				is_3_5_disk_ = file_.get8() == 2;
				is_read_only_ = file_.get8() == 1;
				/*
					Ignored:
						1 byte: Synchronized; 1 = Cross track sync was used during imaging.
						1 byte: Cleaned; 1 = MC3470 fake bits have been removed.
						32 bytes: Creator; a UTF-8 string.

					And, if version 2, following the creator:
						1 byte number of disk sides
						1 byte boot sector format
						1 byte optimal bit timing
						2 bytes compatible hardware
						2 bytes minimum required RAM
						2 bytes largest track
				*/
			} break;

			case CK("TMAP"): {
				file_.read(track_map_, 160);
				has_tmap = true;
			} break;

			case CK("TRKS"): {
				tracks_offset_ = file_.tell();
			} break;

			// TODO: parse META chunks.

			default:
			break;
		}
		#undef CK

		file_.seek(end_of_chunk, SEEK_SET);
	}

	if(tracks_offset_ == -1 || !has_tmap) throw Error::InvalidFormat;
}

HeadPosition WOZ::get_maximum_head_position() {
	return is_3_5_disk_ ? HeadPosition(80) : HeadPosition(160, 4);
}

int WOZ::get_head_count() {
	return is_3_5_disk_ ? 2 : 1;
}

long WOZ::file_offset(Track::Address address) {
	// Calculate table position.
	int table_position;
	if(!is_3_5_disk_) {
		table_position = address.head * 160 + address.position.as_quarter();
	} else {
		if(type_ == Type::WOZ1) {
			table_position = address.head * 80 + address.position.as_int();
		} else {
			table_position = address.head + (address.position.as_int() * 2);
		}
	}

	// Check that this track actually exists.
	if(track_map_[table_position] == 0xff) {
		return NoSuchTrack;
	}

	// Seek to the real track.
	switch(type_) {
		case Type::WOZ1:	return tracks_offset_ + track_map_[table_position] * 6656;
		default:
		case Type::WOZ2:	return tracks_offset_ + track_map_[table_position] * 8;
	}
}

bool WOZ::tracks_differ(Track::Address lhs, Track::Address rhs) {
	const long offset1 = file_offset(lhs);
	const long offset2 = file_offset(rhs);
	return offset1 != offset2;
}

std::shared_ptr<Track> WOZ::get_track_at_position(Track::Address address) {
	const long offset = file_offset(address);
	if(offset == NoSuchTrack) {
		return nullptr;
	}

	// Seek to the real track.
	std::vector<uint8_t> track_contents;
	size_t number_of_bits;
	{
		std::lock_guard lock_guard(file_.get_file_access_mutex());
		file_.seek(offset, SEEK_SET);

		switch(type_) {
			case Type::WOZ1:
				// In WOZ 1, a track is up to 6646 bytes of data, followed by a two-byte record of the
				// number of bytes that actually had data in them, then a two-byte count of the number
				// of bits that were used. Other information follows but is not intended for emulation.
				track_contents = file_.read(6646);
				file_.seek(2, SEEK_CUR);
				number_of_bits = std::min(file_.get16le(), uint16_t(6646*8));
			break;

			default:
			case Type::WOZ2: {
				// In WOZ 2 an extra level of indirection allows for variable track sizes.
				const uint16_t starting_block = file_.get16le();
				file_.seek(2, SEEK_CUR);	// Skip the block count; the amount of data to read is implied by the number of bits.
				number_of_bits = file_.get32le();

				file_.seek(starting_block * 512, SEEK_SET);
				track_contents = file_.read((number_of_bits + 7) >> 3);
			} break;
		}
	}

	return std::make_shared<PCMTrack>(PCMSegment(number_of_bits, track_contents));
}

void WOZ::set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) {
	if(type_ == Type::WOZ2) return;

	for(const auto &pair: tracks) {
		// Decode the track and store, patching into the post_crc_contents_.
		auto segment = Storage::Disk::track_serialisation(*pair.second, Storage::Time(1, 50000));

		auto offset = size_t(file_offset(pair.first) - 12);
		std::vector<uint8_t> segment_bytes = segment.byte_data();
		memcpy(&post_crc_contents_[offset - 12], segment_bytes.data(), segment_bytes.size());

		// Write number of bytes and number of bits.
		post_crc_contents_[offset + 6646] = uint8_t(segment.data.size() >> 3);
		post_crc_contents_[offset + 6647] = uint8_t(segment.data.size() >> 11);
		post_crc_contents_[offset + 6648] = uint8_t(segment.data.size());
		post_crc_contents_[offset + 6649] = uint8_t(segment.data.size() >> 8);

		// Set no splice information now provided, since it's been lost if ever it was known.
		post_crc_contents_[offset + 6650] = 0xff;
		post_crc_contents_[offset + 6651] = 0xff;
	}

	// Calculate the new CRC.
	const uint32_t crc = crc_generator.compute_crc(post_crc_contents_);

	// Grab the file lock, then write the CRC, then just dump the entire file buffer.
	std::lock_guard lock_guard(file_.get_file_access_mutex());
	file_.seek(8, SEEK_SET);
	file_.put_le(crc);
	file_.write(post_crc_contents_);
}

bool WOZ::get_is_read_only() {
	/*
		There is an unintended issue with the disk code that sites above here: it doesn't understand the idea
		of multiple addresses mapping to the same track, yet it maintains a cache of track contents. Therefore
		if a WOZ is written to, what's written will magically be exactly 1/4 track wide, not affecting its
		neighbours. I've made WOZs readonly until I can correct that issue.
	*/
	return true;
//	return file_.get_is_known_read_only() || is_read_only_ || type_ == Type::WOZ2;	// WOZ 2 disks are currently read only.
}
<|endoftext|>


<commit_before>
#include <iostream>
#include <chrono>
#include <algorithm>

#include <cpplocate/cpplocate.h>

#include <glm/vec2.hpp>
#include <glm/vec4.hpp>
#include <glm/gtx/transform.hpp>

#include <glbinding/gl/gl.h>
#include <glbinding/Version.h>
#include <glbinding-aux/ContextInfo.h>
#include <glbinding-aux/types_to_string.h>

#include <GLFW/glfw3.h>

#include <globjects/globjects.h>
#include <globjects/base/File.h>
#include <globjects/logging.h>

#include <globjects/Uniform.h>
#include <globjects/Program.h>
#include <globjects/Shader.h>
#include <globjects/Buffer.h>
#include <globjects/VertexArray.h>
#include <globjects/VertexAttributeBinding.h>
#include <globjects/TransformFeedback.h>

#include "datapath.inl"

using namespace gl;

namespace
{
    std::unique_ptr<globjects::VertexArray> g_vao = nullptr;
    std::unique_ptr<globjects::Program> g_transformFeedbackProgram = nullptr;
    std::unique_ptr<globjects::TransformFeedback> g_transformFeedback = nullptr;
    std::unique_ptr<globjects::Program> g_shaderProgram = nullptr;

    std::unique_ptr<globjects::File> g_feedbackShaderSource = nullptr;
    std::unique_ptr<globjects::AbstractStringSource> g_feedbackShaderTemplate = nullptr;
    std::unique_ptr<globjects::Shader> g_feedbackShader = nullptr;

    std::unique_ptr<globjects::File> g_vertexShaderSource = nullptr;
    std::unique_ptr<globjects::AbstractStringSource> g_vertexShaderTemplate = nullptr;
    std::unique_ptr<globjects::Shader> g_vertexShader = nullptr;
    std::unique_ptr<globjects::File> g_fragmentShaderSource = nullptr;
    std::unique_ptr<globjects::AbstractStringSource> g_fragmentShaderTemplate = nullptr;
    std::unique_ptr<globjects::Shader> g_fragmentShader = nullptr;

    std::unique_ptr<globjects::Buffer> g_vertexBuffer1 = nullptr;
    std::unique_ptr<globjects::Buffer> g_vertexBuffer2 = nullptr;
    std::unique_ptr<globjects::Buffer> g_colorBuffer = nullptr;

    std::chrono::high_resolution_clock::time_point g_startTime;

    auto g_size = glm::ivec2{};
}

void initialize()
{
    const auto dataPath = common::determineDataPath();

    g_shaderProgram = globjects::Program::create();

    g_vertexShaderSource = globjects::Shader::sourceFromFile(dataPath + "/transformfeedback/simple.vert");
    g_vertexShaderTemplate = globjects::Shader::applyGlobalReplacements(g_vertexShaderSource.get());
    g_vertexShader = globjects::Shader::create(GL_VERTEX_SHADER, g_vertexShaderTemplate.get());

    g_fragmentShaderSource = globjects::Shader::sourceFromFile(dataPath + "/transformfeedback/simple.frag");
    g_fragmentShaderTemplate = globjects::Shader::applyGlobalReplacements(g_fragmentShaderSource.get());
    g_fragmentShader = globjects::Shader::create(GL_FRAGMENT_SHADER, g_fragmentShaderTemplate.get());

    g_shaderProgram->attach(g_vertexShader.get(), g_fragmentShader.get());

    g_transformFeedbackProgram = globjects::Program::create();

    g_feedbackShaderSource = globjects::Shader::sourceFromFile(dataPath + "/transformfeedback/transformfeedback.vert");
    g_feedbackShaderTemplate = globjects::Shader::applyGlobalReplacements(g_feedbackShaderSource.get());
    g_feedbackShader = globjects::Shader::create(GL_VERTEX_SHADER, g_feedbackShaderTemplate.get());

    g_transformFeedbackProgram->attach(g_feedbackShader.get());

    g_transformFeedbackProgram->setUniform("deltaT", 0.0f);

    g_shaderProgram->setUniform("modelView", glm::mat4());
    g_shaderProgram->setUniform("projection", glm::ortho(-0.4f, 1.4f, -0.4f, 1.4f, 0.f, 1.f));

    // Create and setup geometry
    auto vertexArray = std::vector<glm::vec4>({
        { 0, 0, 0, 1 }
      , { 1, 0, 0, 1 }
      , { 0, 1, 0, 1 }
      , { 1, 0, 0, 1 }
      , { 0, 1, 0, 1 }
      , { 1, 1, 0, 1 } });

    auto colorArray = std::vector<glm::vec4>({
        { 1, 0, 0, 1 }
      , { 1, 1, 0, 1 }
      , { 0, 0, 1, 1 }
      , { 1, 1, 0, 1 }
      , { 0, 0, 1, 1 }
      , { 0, 1, 0, 1 } });

    g_vertexBuffer1 = globjects::Buffer::create();
    g_vertexBuffer1->setData(vertexArray, GL_STATIC_DRAW);
    g_vertexBuffer2 = globjects::Buffer::create();
    g_vertexBuffer2->setData(vertexArray, GL_STATIC_DRAW);
    g_colorBuffer = globjects::Buffer::create();
    g_colorBuffer->setData(colorArray, GL_STATIC_DRAW);

    g_vao = globjects::VertexArray::create();

    g_vao->binding(0)->setAttribute(0);
    g_vao->binding(0)->setFormat(4, GL_FLOAT);

    g_vao->binding(1)->setAttribute(1);
    g_vao->binding(1)->setBuffer(g_colorBuffer.get(), 0, sizeof(glm::vec4));
    g_vao->binding(1)->setFormat(4, GL_FLOAT);

    g_vao->enable(0);
    g_vao->enable(1);

    // Create and setup TransformFeedback
    g_transformFeedback = globjects::TransformFeedback::create();
    g_transformFeedback->setVaryings(g_transformFeedbackProgram.get(), { { "next_position" } }, GL_INTERLEAVED_ATTRIBS);

    g_startTime = std::chrono::high_resolution_clock::now();
}

void deinitialize()
{
    g_vao.reset(nullptr);

    g_transformFeedback.reset(nullptr);
    g_transformFeedbackProgram.reset(nullptr);
    g_shaderProgram.reset(nullptr);

    g_feedbackShader.reset(nullptr);
    g_feedbackShaderTemplate.reset(nullptr);
    g_feedbackShaderSource.reset(nullptr);

    g_vertexShader.reset(nullptr);
    g_vertexShaderTemplate.reset(nullptr);
    g_vertexShaderSource.reset(nullptr);
    g_fragmentShader.reset(nullptr);
    g_fragmentShaderTemplate.reset(nullptr);
    g_fragmentShaderSource.reset(nullptr);

    g_vertexBuffer1.reset(nullptr);
    g_vertexBuffer2.reset(nullptr);
    g_colorBuffer.reset(nullptr);
}

void draw()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    const auto side = std::min<int>(g_size.x, g_size.y);
    glViewport((g_size.x - side) / 2, (g_size.y - side) / 2, side, side);

    const auto t_elapsed = std::chrono::high_resolution_clock::now() - g_startTime;
    g_startTime = std::chrono::high_resolution_clock::now();

    auto drawBuffer  = g_vertexBuffer1.get();
    auto writeBuffer = g_vertexBuffer2.get();

    g_vao->bind();

    g_transformFeedbackProgram->setUniform("deltaT", static_cast<float>(t_elapsed.count()) * float(std::nano::num) / float(std::nano::den));

    g_vao->binding(0)->setBuffer(drawBuffer, 0, sizeof(glm::vec4));

    g_transformFeedback->bind();
    writeBuffer->bindBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0);

    glEnable(GL_RASTERIZER_DISCARD);

    g_transformFeedbackProgram->use();
    g_transformFeedback->begin(GL_TRIANGLES);
    g_vao->drawArrays(GL_TRIANGLES, 0, 6);
    g_transformFeedback->end();
    glDisable(GL_RASTERIZER_DISCARD);

    g_transformFeedback->unbind();

    g_vao->binding(0)->setBuffer(writeBuffer, 0, sizeof(glm::vec4));

    g_shaderProgram->use();
    g_transformFeedback->draw(GL_TRIANGLE_STRIP);
    g_shaderProgram->release();

    g_vao->unbind();

    std::swap(g_vertexBuffer1, g_vertexBuffer2);
}

void error(int errnum, const char * errmsg)
{
    globjects::critical() << errnum << ": " << errmsg << std::endl;
}

void framebuffer_size_callback(GLFWwindow * /*window*/, int width, int height)
{
    g_size = glm::ivec2{ width, height };
}

void key_callback(GLFWwindow * window, int key, int /*scancode*/, int action, int /*modes*/)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE)
        glfwSetWindowShouldClose(window, true);

    if (key == GLFW_KEY_F5 && action == GLFW_RELEASE)
    {
        g_feedbackShaderSource->reload();
        g_vertexShaderSource->reload();
        g_fragmentShaderSource->reload();
    }
}

int main(int /*argc*/, char * /*argv*/[])
{
    // Initialize GLFW
    if (!glfwInit())
        return 1;

    glfwSetErrorCallback(error);

    glfwDefaultWindowHints();
    glfwWindowHint(GLFW_VISIBLE, true);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, true);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
#ifdef SYSTEM_DARWIN
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#else
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
#endif

    // Create a context and, if valid, make it current
    GLFWwindow * window = glfwCreateWindow(640, 480, "globjects Transform Feedback", nullptr, nullptr);
    if (window == nullptr)
    {
        globjects::critical() << "Context creation failed. Terminate execution.";

        glfwTerminate();
        return -1;
    }
    glfwSetKeyCallback(window, key_callback);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    glfwMakeContextCurrent(window);

    // Initialize globjects (internally initializes glbinding, and registers the current context)
    globjects::init([](const char * name) {
        return glfwGetProcAddress(name);
    });

    std::cout << std::endl
        << "OpenGL Version:  " << glbinding::aux::ContextInfo::version() << std::endl
        << "OpenGL Vendor:   " << glbinding::aux::ContextInfo::vendor() << std::endl
        << "OpenGL Renderer: " << glbinding::aux::ContextInfo::renderer() << std::endl << std::endl;

    globjects::info() << "Press F5 to reload shaders." << std::endl << std::endl;

    glfwGetFramebufferSize(window, &g_size[0], &g_size[1]);
    initialize();

    // Main loop
    while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();
        draw();
        glfwSwapBuffers(window);
    }
    deinitialize();

    // Properly shutdown GLFW
    glfwTerminate();

    return 0;
}
<commit_msg>Fix transform feedback example<commit_after>
#include <iostream>
#include <chrono>
#include <algorithm>

#include <cpplocate/cpplocate.h>

#include <glm/vec2.hpp>
#include <glm/vec4.hpp>
#include <glm/gtx/transform.hpp>

#include <glbinding/gl/gl.h>
#include <glbinding/Version.h>
#include <glbinding-aux/ContextInfo.h>
#include <glbinding-aux/types_to_string.h>

#include <GLFW/glfw3.h>

#include <globjects/globjects.h>
#include <globjects/base/File.h>
#include <globjects/logging.h>

#include <globjects/Uniform.h>
#include <globjects/Program.h>
#include <globjects/Shader.h>
#include <globjects/Buffer.h>
#include <globjects/VertexArray.h>
#include <globjects/VertexAttributeBinding.h>
#include <globjects/TransformFeedback.h>

#include "datapath.inl"

using namespace gl;

namespace
{
    std::unique_ptr<globjects::VertexArray> g_vao = nullptr;
    std::unique_ptr<globjects::Program> g_transformFeedbackProgram = nullptr;
    std::unique_ptr<globjects::TransformFeedback> g_transformFeedback = nullptr;
    std::unique_ptr<globjects::Program> g_shaderProgram = nullptr;

    std::unique_ptr<globjects::File> g_feedbackShaderSource = nullptr;
    std::unique_ptr<globjects::AbstractStringSource> g_feedbackShaderTemplate = nullptr;
    std::unique_ptr<globjects::Shader> g_feedbackShader = nullptr;

    std::unique_ptr<globjects::File> g_vertexShaderSource = nullptr;
    std::unique_ptr<globjects::AbstractStringSource> g_vertexShaderTemplate = nullptr;
    std::unique_ptr<globjects::Shader> g_vertexShader = nullptr;
    std::unique_ptr<globjects::File> g_fragmentShaderSource = nullptr;
    std::unique_ptr<globjects::AbstractStringSource> g_fragmentShaderTemplate = nullptr;
    std::unique_ptr<globjects::Shader> g_fragmentShader = nullptr;

    std::unique_ptr<globjects::Buffer> g_vertexBuffer1 = nullptr;
    std::unique_ptr<globjects::Buffer> g_vertexBuffer2 = nullptr;
    std::unique_ptr<globjects::Buffer> g_colorBuffer = nullptr;

    std::chrono::high_resolution_clock::time_point g_startTime;

    auto g_size = glm::ivec2{};
}

void initialize()
{
    const auto dataPath = common::determineDataPath();

    g_shaderProgram = globjects::Program::create();

    g_vertexShaderSource = globjects::Shader::sourceFromFile(dataPath + "/transformfeedback/simple.vert");
    g_vertexShaderTemplate = globjects::Shader::applyGlobalReplacements(g_vertexShaderSource.get());
    g_vertexShader = globjects::Shader::create(GL_VERTEX_SHADER, g_vertexShaderTemplate.get());

    g_fragmentShaderSource = globjects::Shader::sourceFromFile(dataPath + "/transformfeedback/simple.frag");
    g_fragmentShaderTemplate = globjects::Shader::applyGlobalReplacements(g_fragmentShaderSource.get());
    g_fragmentShader = globjects::Shader::create(GL_FRAGMENT_SHADER, g_fragmentShaderTemplate.get());

    g_shaderProgram->attach(g_vertexShader.get(), g_fragmentShader.get());

    g_transformFeedbackProgram = globjects::Program::create();

    g_feedbackShaderSource = globjects::Shader::sourceFromFile(dataPath + "/transformfeedback/transformfeedback.vert");
    g_feedbackShaderTemplate = globjects::Shader::applyGlobalReplacements(g_feedbackShaderSource.get());
    g_feedbackShader = globjects::Shader::create(GL_VERTEX_SHADER, g_feedbackShaderTemplate.get());

    g_transformFeedbackProgram->attach(g_feedbackShader.get());

    g_transformFeedbackProgram->setUniform("deltaT", 0.0f);

    g_shaderProgram->setUniform("modelView", glm::mat4(1.0f));
    g_shaderProgram->setUniform("projection", glm::ortho(-0.4f, 1.4f, -0.4f, 1.4f, 0.f, 1.f));

    // Create and setup geometry
    auto vertexArray = std::vector<glm::vec4>({
        { 0, 0, 0, 1 }
      , { 1, 0, 0, 1 }
      , { 0, 1, 0, 1 }
      , { 1, 0, 0, 1 }
      , { 0, 1, 0, 1 }
      , { 1, 1, 0, 1 } });

    auto colorArray = std::vector<glm::vec4>({
        { 1, 0, 0, 1 }
      , { 1, 1, 0, 1 }
      , { 0, 0, 1, 1 }
      , { 1, 1, 0, 1 }
      , { 0, 0, 1, 1 }
      , { 0, 1, 0, 1 } });

    g_vertexBuffer1 = globjects::Buffer::create();
    g_vertexBuffer1->setData(vertexArray, GL_STATIC_DRAW);
    g_vertexBuffer2 = globjects::Buffer::create();
    g_vertexBuffer2->setData(vertexArray, GL_STATIC_DRAW);
    g_colorBuffer = globjects::Buffer::create();
    g_colorBuffer->setData(colorArray, GL_STATIC_DRAW);

    g_vao = globjects::VertexArray::create();

    g_vao->binding(0)->setAttribute(0);
    g_vao->binding(0)->setFormat(4, GL_FLOAT);
    g_vao->binding(0)->setBuffer(g_vertexBuffer1.get(), 0, sizeof(glm::vec4));
    g_vao->enable(0);

    g_vao->binding(1)->setAttribute(1);
    g_vao->binding(1)->setBuffer(g_colorBuffer.get(), 0, sizeof(glm::vec4));
    g_vao->binding(1)->setFormat(4, GL_FLOAT);
    g_vao->enable(1);

    // Create and setup TransformFeedback
    g_transformFeedback = globjects::TransformFeedback::create();
    g_transformFeedback->setVaryings(g_transformFeedbackProgram.get(), { { "next_position" } }, GL_INTERLEAVED_ATTRIBS);
    g_transformFeedback->unbind();

    g_startTime = std::chrono::high_resolution_clock::now();
}

void deinitialize()
{
    g_vao.reset(nullptr);

    g_transformFeedback.reset(nullptr);
    g_transformFeedbackProgram.reset(nullptr);
    g_shaderProgram.reset(nullptr);

    g_feedbackShader.reset(nullptr);
    g_feedbackShaderTemplate.reset(nullptr);
    g_feedbackShaderSource.reset(nullptr);

    g_vertexShader.reset(nullptr);
    g_vertexShaderTemplate.reset(nullptr);
    g_vertexShaderSource.reset(nullptr);
    g_fragmentShader.reset(nullptr);
    g_fragmentShaderTemplate.reset(nullptr);
    g_fragmentShaderSource.reset(nullptr);

    g_vertexBuffer1.reset(nullptr);
    g_vertexBuffer2.reset(nullptr);
    g_colorBuffer.reset(nullptr);
}

void draw()
{
    const auto t_elapsed = std::chrono::high_resolution_clock::now() - g_startTime;
    g_startTime = std::chrono::high_resolution_clock::now();

    const auto drawBuffer  = g_vertexBuffer1.get();
    const auto writeBuffer = g_vertexBuffer2.get();

    // RECORD / PROCESS

    g_vao->bind();

    g_transformFeedbackProgram->setUniform("deltaT", static_cast<float>(t_elapsed.count()) * float(std::nano::num) / float(std::nano::den));

    g_vao->binding(0)->setBuffer(drawBuffer, 0, sizeof(glm::vec4));

    g_transformFeedback->bind();
    writeBuffer->bindBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0);

    glEnable(GL_RASTERIZER_DISCARD);

    g_transformFeedbackProgram->use();
    g_transformFeedback->begin(GL_TRIANGLES);
    g_vao->drawArrays(GL_TRIANGLES, 0, 6);
    g_transformFeedback->end();
    glDisable(GL_RASTERIZER_DISCARD);

    // writeBuffer->unbindBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0); // not implemented?
    g_transformFeedback->unbind();

    // DRAW

    // g_vao->bind();
    g_vao->binding(0)->setBuffer(writeBuffer, 0, sizeof(glm::vec4));

    const auto side = std::min<int>(g_size.x, g_size.y);
    glViewport((g_size.x - side) / 2, (g_size.y - side) / 2, side, side);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    g_shaderProgram->use();
    g_transformFeedback->draw(GL_TRIANGLES);
    g_shaderProgram->release();

    g_vao->unbind();

    std::swap(g_vertexBuffer1, g_vertexBuffer2);
}

void error(int errnum, const char * errmsg)
{
    globjects::critical() << errnum << ": " << errmsg << std::endl;
}

void framebuffer_size_callback(GLFWwindow * /*window*/, int width, int height)
{
    g_size = glm::ivec2{ width, height };
}

void key_callback(GLFWwindow * window, int key, int /*scancode*/, int action, int /*modes*/)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE)
        glfwSetWindowShouldClose(window, true);

    if (key == GLFW_KEY_F5 && action == GLFW_RELEASE)
    {
        g_feedbackShaderSource->reload();
        g_vertexShaderSource->reload();
        g_fragmentShaderSource->reload();
    }
}

int main(int /*argc*/, char * /*argv*/[])
{
    // Initialize GLFW
    if (!glfwInit())
        return 1;

    glfwSetErrorCallback(error);

    glfwDefaultWindowHints();
    glfwWindowHint(GLFW_VISIBLE, true);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, true);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
#ifdef SYSTEM_DARWIN
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#else
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
#endif

    // Create a context and, if valid, make it current
    GLFWwindow * window = glfwCreateWindow(640, 480, "globjects Transform Feedback", nullptr, nullptr);
    if (window == nullptr)
    {
        globjects::critical() << "Context creation failed. Terminate execution.";

        glfwTerminate();
        return -1;
    }
    glfwSetKeyCallback(window, key_callback);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    glfwMakeContextCurrent(window);

    // Initialize globjects (internally initializes glbinding, and registers the current context)
    globjects::init([](const char * name) {
        return glfwGetProcAddress(name);
    });

    std::cout << std::endl
        << "OpenGL Version:  " << glbinding::aux::ContextInfo::version() << std::endl
        << "OpenGL Vendor:   " << glbinding::aux::ContextInfo::vendor() << std::endl
        << "OpenGL Renderer: " << glbinding::aux::ContextInfo::renderer() << std::endl << std::endl;

    globjects::info() << "Press F5 to reload shaders." << std::endl << std::endl;

    glfwGetFramebufferSize(window, &g_size[0], &g_size[1]);
    initialize();

    // Main loop
    while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();
        draw();
        glfwSwapBuffers(window);
    }
    deinitialize();

    // Properly shutdown GLFW
    glfwTerminate();

    return 0;
}
<|endoftext|>


<commit_before>#include "gtest/gtest.h"
#include <stan/proto/stanc.pb.h>
#include <protostan/lang/compiler.hpp>

TEST(stanc, minimumModelCompile) {
  stan::proto::StanCompileRequest request;
  stan::proto::StanCompileResponse response;

  request.set_model_name("test");
  request.set_model_code("model {}");
  request.set_model_file_name("test.stan");
  response = stan::proto::compile(request);
  // FIXME: These tests are lame, but that's all that
  // stan-dev/stan does... (?)
  EXPECT_EQ(stan::proto::StanCompileResponse::SUCCESS, response.state());
  EXPECT_EQ("", response.messages());
  EXPECT_EQ(std::string::npos, response.cpp_code().find("int main("));
}

TEST(stanc, invalidModelCompile) {
  stan::proto::StanCompileRequest request;
  stan::proto::StanCompileResponse response;

  request.set_model_name("test");
  request.set_model_code("invalid model code");
  request.set_model_file_name("test.stan");
  response = stan::proto::compile(request);
  EXPECT_EQ(stan::proto::StanCompileResponse::ERROR, response.state());
  EXPECT_EQ("", response.messages());
  EXPECT_EQ(std::string::npos, response.cpp_code().find("int main("));
}

int main(int argc, char** argv) {
  ::testing::InitGoogleTest(&argc, argv);
  int returnValue;
  returnValue = RUN_ALL_TESTS();
  return returnValue;
}

<commit_msg>TST: additional tests for stan::proto::compile<commit_after>#include "gtest/gtest.h"
#include <stan/proto/stanc.pb.h>
#include <protostan/lang/compiler.hpp>

TEST(stanc, minimumModelCompile) {
  stan::proto::StanCompileRequest request;
  stan::proto::StanCompileResponse response;

  request.set_model_name("test");
  request.set_model_code("model {}");
  request.set_model_file_name("test.stan");
  response = stan::proto::compile(request);
  EXPECT_EQ(stan::proto::StanCompileResponse::SUCCESS, response.state());
  EXPECT_STREQ("", response.messages().c_str());
  // "int main(" should *not* be in the program C++ code
  EXPECT_EQ(std::string::npos, response.cpp_code().find("int main("));
  // "class test" should be, since the model name is "test"
  EXPECT_NE(std::string::npos, response.cpp_code().find("class test"));
}

TEST(stanc, invalidModelCompile) {
  stan::proto::StanCompileRequest request;
  stan::proto::StanCompileResponse response;

  request.set_model_name("test");
  request.set_model_code("invalid model code");
  request.set_model_file_name("test.stan");
  response = stan::proto::compile(request);
  EXPECT_EQ(stan::proto::StanCompileResponse::ERROR, response.state());
  EXPECT_STREQ("", response.cpp_code().c_str());
  EXPECT_STRNE("", response.messages().c_str());
  EXPECT_NE(std::string::npos, response.messages().find("PARSER EXPECTED: <model declaration"));
}

int main(int argc, char** argv) {
  ::testing::InitGoogleTest(&argc, argv);
  int returnValue;
  returnValue = RUN_ALL_TESTS();
  return returnValue;
}

<|endoftext|>


<commit_before>/*
 * Copyright (c) 2001,2002 Steven M. Cherry. All rights reserved.
 *
 * This file is a part of slib - a c++ utility library
 *
 * The slib project, including all files needed to compile
 * it, is free software; you can redistribute it and/or use it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  See file COPYING for details.
 */

/* ******************************************************* */
/* Simple program to test the functions in twine.cpp       */
/* ******************************************************* */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "twine.h"
using namespace SLib;

#include "catch.hpp"

const size_t MAX_INPUT_SIZE = 1024000000;

TEST_CASE( "Twine - short string", "[twine]" )
{
    twine t( "I am a short string" );

    REQUIRE_FALSE( t.empty() );
    REQUIRE( t.size() == 19 );
    REQUIRE( t.capacity() >= 19 );
}

// according to twine.h, a "Short string" is <=32 characters, including the final '\0'
TEST_CASE( "Twine - assignment from const char", "[twine]" )
{
    SECTION( "Short string") {
        SECTION( "Direct assignment" ){
            twine t = "I am a short string";
            REQUIRE_FALSE( t.empty() );
            REQUIRE( t.size() == 19 );
            REQUIRE( t.capacity() >= 19 );
        }

        SECTION( "Assignment after construction" ){
            twine t; t = "I am a short string";
            REQUIRE_FALSE( t.empty() );
            REQUIRE( t.size() == 19 );
            REQUIRE( t.capacity() >= 19 );
        }
    }

    SECTION( "Long string") {
        SECTION( "Direct assignment of a long string" ){
            twine t = "I am a longer string that will not fit in optimized storage.";
            REQUIRE_FALSE( t.empty() );
            REQUIRE( t.size() == 60 );
            REQUIRE( t.capacity() >= 60 );
        }

        SECTION( "Assignment after construction of a long string" ){
            twine t; t = "I am a longer string that will not fit in optimized storage.";
            REQUIRE_FALSE( t.empty() );
            REQUIRE( t.size() == 60 );
            REQUIRE( t.capacity() >= 60 );
        }
    }

    SECTION("Extremely long string") {
        SECTION("MAX_INPUT_SIZE") {

            // This is a valid size, because the exception is
            // thrown when number of non-zero chars in string
            // is greater than MAX_INPUT_SIZE
            char* monster = (char*)malloc((MAX_INPUT_SIZE + 1) * sizeof(*monster));
            for(int i = 0; i < MAX_INPUT_SIZE; ++i)
            {
                monster[i] = (i % 94) + 32; // fill it with printable characters! Why? Who cares!
            }
            monster[MAX_INPUT_SIZE] = 0;

            SECTION( "Direct assignment of a long string" ){
                twine* t = NULL;

                REQUIRE_NOTHROW([&](){t = new twine(monster);}());
                REQUIRE_FALSE(t->empty());
                REQUIRE(t->size() == MAX_INPUT_SIZE);
                REQUIRE(t->capacity() >= MAX_INPUT_SIZE);
                INFO("t.capacity() is " << t->capacity());
                INFO("MAX_INPUT_SIZE is " << MAX_INPUT_SIZE);

                if(t != NULL)
                    delete t;
            }

            SECTION( "Assignment after construction of a long string" ){
                twine t;
                REQUIRE_NOTHROW(t = monster);

                REQUIRE_FALSE(t.empty());
                REQUIRE(t.size() == MAX_INPUT_SIZE);
                REQUIRE(t.capacity() >= MAX_INPUT_SIZE);
                INFO("t.capacity() is " << t.capacity());
                INFO("MAX_INPUT_SIZE is " << MAX_INPUT_SIZE);
            }

            free(monster);
        }

        SECTION("MAX_INPUT_SIZE + 1") {
            // This is an invalid size, because the nonzero portion of the string is of length greater
            // than MAX_INPUT_SIZE
            char* monster = (char*)malloc((MAX_INPUT_SIZE + 2) * sizeof(*monster));
            for(int i = 0; i <= MAX_INPUT_SIZE; ++i)
            {
                monster[i] = (i % 94) + 32; // fill it with printable characters! Why? Who cares!
            }
            monster[MAX_INPUT_SIZE + 1] = 0;

            SECTION( "Direct assignment of a long string" ){
                REQUIRE_THROWS([&](){twine t = monster;}());
            }

            SECTION( "Assignment after construction of a long string" ){
                twine t;
                REQUIRE_THROWS(t = monster);
            }

            free(monster);
        }
    }

}

TEST_CASE( "Twine - Copy Constructor", "[twine]" )
{
    SECTION( "Copy Empty" ) {
        SECTION( "Calling constructor by name" ){
            twine orig;
            twine t = twine(orig);
            REQUIRE(t.empty());
            REQUIRE(t.size() == 0);
            REQUIRE(t.capacity() == TWINE_SMALL_STRING - 1);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }

        SECTION("Implicitly calling constructor"){
            twine orig;
            twine t = orig;
            REQUIRE(t.empty());
            REQUIRE(t.size() == 0);
            REQUIRE(t.capacity() == TWINE_SMALL_STRING - 1);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }

        SECTION("Calling constructor after initial creation"){
            twine orig;
            twine t; t = orig;
            REQUIRE(t.empty());
            REQUIRE(t.size() == 0);
            REQUIRE(t.capacity() == TWINE_SMALL_STRING - 1);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }
    }

    SECTION( "Copy Short Twine" ) {
        twine orig = "I am a short string";

        SECTION( "Calling constructor by name" ){
            twine t = twine(orig);
            REQUIRE_FALSE(t.empty());
            REQUIRE(t.size() == 19);
            REQUIRE(t.capacity() == TWINE_SMALL_STRING - 1);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }

        SECTION("Implicitly calling constructor"){
            twine t = orig;
            REQUIRE_FALSE(t.empty());
            REQUIRE(t.size() == 19);
            REQUIRE(t.capacity() == TWINE_SMALL_STRING - 1);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }

        SECTION("Calling constructor after initial creation"){
            twine t; t = orig;
            REQUIRE_FALSE(t.empty());
            REQUIRE(t.size() == 19);
            REQUIRE(t.capacity() == TWINE_SMALL_STRING - 1);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }
    }

    SECTION("Copy Long Twine") {
        twine orig = "I am a longer string that will not fit in optimized storage.";

        SECTION( "Calling constructor by name" ){
            twine t = twine(orig);
            REQUIRE_FALSE(t.empty());
            REQUIRE(t.size() == 60);
            REQUIRE(t.capacity() >= 60);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }

        SECTION("Implicitly calling constructor"){
            twine t = orig;
            REQUIRE_FALSE(t.empty());
            REQUIRE(t.size() == 60);
            REQUIRE(t.capacity() >= 60);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }

        SECTION("Calling constructor after initial creation"){
            twine t; t = orig;
            REQUIRE_FALSE(t.empty());
            REQUIRE(t.size() == 60);
            REQUIRE(t.capacity() >= 60);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }
    }
}

TEST_CASE("Twine - Random Testing", "[twine][random]")
{
    SECTION("Randomized String")
    {
        srand(time(0));

        int length = rand();
        char* randStr = (char*)malloc((length + 1) * sizeof(*randStr));
        for(int i = 0; i < length; ++i)
        {
            randStr[i] = (rand() % (CHAR_MAX - CHAR_MIN + 1)) + CHAR_MIN;
        }
        randStr[length] = 0; // make sure we have at least one null char in the string

        SECTION("Create from random String"){
            if(strlen(randStr) > MAX_INPUT_SIZE)
                REQUIRE_THROWS([&](){twine t = randStr;});
            else
            {
                twine t = randStr;
                if(randStr[0] == 0)
                    REQUIRE(t.empty());
                else
                    REQUIRE_FALSE(t.empty());

                REQUIRE(t.size() == strlen(randStr));
                REQUIRE(t.capacity() >= strlen(randStr));
                if(strlen(randStr) < TWINE_SMALL_STRING)
                    REQUIRE(t.capacity() == TWINE_SMALL_STRING - 1);
            }
        }

        SECTION("Copy from random String"){
            twine orig;
            if(strlen(randStr) > MAX_INPUT_SIZE)
                REQUIRE_THROWS([&](){orig = randStr;});
            else
            {
                orig = randStr;

                twine t = orig;
                if(randStr[0] == 0)
                    REQUIRE(t.empty());
                else
                    REQUIRE_FALSE(t.empty());

                REQUIRE(t.size() == strlen(randStr));
                REQUIRE(t.capacity() >= strlen(randStr));
                if(strlen(randStr) < TWINE_SMALL_STRING)
                    REQUIRE(t.capacity() == TWINE_SMALL_STRING - 1);

                REQUIRE(t.compare(orig) == 0);
                REQUIRE(&t != &orig);
            }
        }

        free(randStr);
    }

}
<commit_msg>Optimizations for extremely long and random strings<commit_after>/*
 * Copyright (c) 2001,2002 Steven M. Cherry. All rights reserved.
 *
 * This file is a part of slib - a c++ utility library
 *
 * The slib project, including all files needed to compile
 * it, is free software; you can redistribute it and/or use it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  See file COPYING for details.
 */

/* ******************************************************* */
/* Simple program to test the functions in twine.cpp       */
/* ******************************************************* */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "twine.h"
using namespace SLib;

#include "catch.hpp"

const size_t MAX_INPUT_SIZE = 1024000000;

TEST_CASE( "Twine - short string", "[twine]" )
{
    twine t( "I am a short string" );

    REQUIRE_FALSE( t.empty() );
    REQUIRE( t.size() == 19 );
    REQUIRE( t.capacity() >= 19 );
}

// according to twine.h, a "Short string" is <=32 characters, including the final '\0'
TEST_CASE( "Twine - assignment from const char", "[twine]" )
{
    SECTION( "Short string") {
        SECTION( "Direct assignment" ){
            twine t = "I am a short string";
            REQUIRE_FALSE( t.empty() );
            REQUIRE( t.size() == 19 );
            REQUIRE( t.capacity() >= 19 );
        }

        SECTION( "Assignment after construction" ){
            twine t; t = "I am a short string";
            REQUIRE_FALSE( t.empty() );
            REQUIRE( t.size() == 19 );
            REQUIRE( t.capacity() >= 19 );
        }
    }

    SECTION( "Long string") {
        SECTION( "Direct assignment of a long string" ){
            twine t = "I am a longer string that will not fit in optimized storage.";
            REQUIRE_FALSE( t.empty() );
            REQUIRE( t.size() == 60 );
            REQUIRE( t.capacity() >= 60 );
        }

        SECTION( "Assignment after construction of a long string" ){
            twine t; t = "I am a longer string that will not fit in optimized storage.";
            REQUIRE_FALSE( t.empty() );
            REQUIRE( t.size() == 60 );
            REQUIRE( t.capacity() >= 60 );
        }
    }

    SECTION("Extremely long string") {
        SECTION("MAX_INPUT_SIZE") {

            // This is a valid size, because the exception is
            // thrown when number of non-zero chars in string
            // is greater than MAX_INPUT_SIZE
            char* monster = (char*)malloc((MAX_INPUT_SIZE + 1) * sizeof(*monster));
         // for(int i = 0; i < MAX_INPUT_SIZE; ++i)
         // {
         //     monster[i] = (i % 94) + 32; // fill it with printable characters! Why? Who cares!
         // }
            memset(monster, 'A', MAX_INPUT_SIZE);
            monster[MAX_INPUT_SIZE] = 0;

            SECTION( "Direct assignment of a long string" ){
                twine* t = NULL;

                REQUIRE_NOTHROW([&](){t = new twine(monster);}());
                REQUIRE_FALSE(t->empty());
                REQUIRE(t->size() == MAX_INPUT_SIZE);
                REQUIRE(t->capacity() >= MAX_INPUT_SIZE);
                INFO("t.capacity() is " << t->capacity());
                INFO("MAX_INPUT_SIZE is " << MAX_INPUT_SIZE);

                if(t != NULL)
                    delete t;
            }

            SECTION( "Assignment after construction of a long string" ){
                twine t;
                REQUIRE_NOTHROW(t = monster);

                REQUIRE_FALSE(t.empty());
                REQUIRE(t.size() == MAX_INPUT_SIZE);
                REQUIRE(t.capacity() >= MAX_INPUT_SIZE);
                INFO("t.capacity() is " << t.capacity());
                INFO("MAX_INPUT_SIZE is " << MAX_INPUT_SIZE);
            }

            free(monster);
        }

        SECTION("MAX_INPUT_SIZE + 1") {
            // This is an invalid size, because the nonzero portion of the string is of length greater
            // than MAX_INPUT_SIZE
            char* monster = (char*)malloc((MAX_INPUT_SIZE + 2) * sizeof(*monster));
         // for(int i = 0; i <= MAX_INPUT_SIZE; ++i)
         // {
         //     monster[i] = (i % 94) + 32; // fill it with printable characters! Why? Who cares!
         // }
            memset(monster, 'B', MAX_INPUT_SIZE + 1);
            monster[MAX_INPUT_SIZE + 1] = 0;

            SECTION( "Direct assignment of a long string" ){
                REQUIRE_THROWS([&](){twine t = monster;}());
            }

            SECTION( "Assignment after construction of a long string" ){
                twine t;
                REQUIRE_THROWS(t = monster);
            }

            free(monster);
        }
    }

}

TEST_CASE( "Twine - Copy Constructor", "[twine]" )
{
    SECTION( "Copy Empty" ) {
        SECTION( "Calling constructor by name" ){
            twine orig;
            twine t = twine(orig);
            REQUIRE(t.empty());
            REQUIRE(t.size() == 0);
            REQUIRE(t.capacity() == TWINE_SMALL_STRING - 1);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }

        SECTION("Implicitly calling constructor"){
            twine orig;
            twine t = orig;
            REQUIRE(t.empty());
            REQUIRE(t.size() == 0);
            REQUIRE(t.capacity() == TWINE_SMALL_STRING - 1);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }

        SECTION("Calling constructor after initial creation"){
            twine orig;
            twine t; t = orig;
            REQUIRE(t.empty());
            REQUIRE(t.size() == 0);
            REQUIRE(t.capacity() == TWINE_SMALL_STRING - 1);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }
    }

    SECTION( "Copy Short Twine" ) {
        twine orig = "I am a short string";

        SECTION( "Calling constructor by name" ){
            twine t = twine(orig);
            REQUIRE_FALSE(t.empty());
            REQUIRE(t.size() == 19);
            REQUIRE(t.capacity() == TWINE_SMALL_STRING - 1);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }

        SECTION("Implicitly calling constructor"){
            twine t = orig;
            REQUIRE_FALSE(t.empty());
            REQUIRE(t.size() == 19);
            REQUIRE(t.capacity() == TWINE_SMALL_STRING - 1);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }

        SECTION("Calling constructor after initial creation"){
            twine t; t = orig;
            REQUIRE_FALSE(t.empty());
            REQUIRE(t.size() == 19);
            REQUIRE(t.capacity() == TWINE_SMALL_STRING - 1);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }
    }

    SECTION("Copy Long Twine") {
        twine orig = "I am a longer string that will not fit in optimized storage.";

        SECTION( "Calling constructor by name" ){
            twine t = twine(orig);
            REQUIRE_FALSE(t.empty());
            REQUIRE(t.size() == 60);
            REQUIRE(t.capacity() >= 60);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }

        SECTION("Implicitly calling constructor"){
            twine t = orig;
            REQUIRE_FALSE(t.empty());
            REQUIRE(t.size() == 60);
            REQUIRE(t.capacity() >= 60);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }

        SECTION("Calling constructor after initial creation"){
            twine t; t = orig;
            REQUIRE_FALSE(t.empty());
            REQUIRE(t.size() == 60);
            REQUIRE(t.capacity() >= 60);

            REQUIRE(t.compare(orig) == 0);
            REQUIRE(&t != &orig);
        }
    }
}

TEST_CASE("Twine - Random Testing", "[twine][random]")
{
    SECTION("Randomized String")
    {
        srand(time(0));

        int length = rand();
        char* randStr = (char*)malloc((length + 1) * sizeof(*randStr));
        for(int i = 0; i < length; ++i)
        {
            randStr[i] = (rand() % (CHAR_MAX - CHAR_MIN + 1)) + CHAR_MIN;
            if(randStr[i] == 0) // this is the null character for end of string
                break; // the rest of the buffer is irrelevant
        }
        randStr[length] = 0; // make sure we have at least one null char in the string

        SECTION("Create from random String"){
            if(strlen(randStr) > MAX_INPUT_SIZE)
                REQUIRE_THROWS([&](){twine t = randStr;});
            else
            {
                twine t = randStr;
                if(randStr[0] == 0)
                    REQUIRE(t.empty());
                else
                    REQUIRE_FALSE(t.empty());

                REQUIRE(t.size() == strlen(randStr));
                REQUIRE(t.capacity() >= strlen(randStr));
                if(strlen(randStr) < TWINE_SMALL_STRING)
                    REQUIRE(t.capacity() == TWINE_SMALL_STRING - 1);
            }
        }

        SECTION("Copy from random String"){
            twine orig;
            if(strlen(randStr) > MAX_INPUT_SIZE)
                REQUIRE_THROWS([&](){orig = randStr;});
            else
            {
                orig = randStr;

                twine t = orig;
                if(randStr[0] == 0)
                    REQUIRE(t.empty());
                else
                    REQUIRE_FALSE(t.empty());

                REQUIRE(t.size() == strlen(randStr));
                REQUIRE(t.capacity() >= strlen(randStr));
                if(strlen(randStr) < TWINE_SMALL_STRING)
                    REQUIRE(t.capacity() == TWINE_SMALL_STRING - 1);

                REQUIRE(t.compare(orig) == 0);
                REQUIRE(&t != &orig);
            }
        }

        free(randStr);
    }

}
<|endoftext|>