diff --git a/ast.c b/a/ast.c similarity index 100% rename from ast.c rename to a/ast.c diff --git a/base.c b/a/base.c similarity index 100% rename from base.c rename to a/base.c diff --git a/build.sh b/a/build.sh similarity index 100% rename from build.sh rename to a/build.sh diff --git a/emit_asm_x64.c b/a/emit_asm_x64.c similarity index 100% rename from emit_asm_x64.c rename to a/emit_asm_x64.c diff --git a/lex.c b/a/lex.c similarity index 100% rename from lex.c rename to a/lex.c diff --git a/main.c b/a/main.c similarity index 100% rename from main.c rename to a/main.c diff --git a/meta.c b/a/meta.c similarity index 100% rename from meta.c rename to a/meta.c diff --git a/meta_gen.c b/a/meta_gen.c similarity index 100% rename from meta_gen.c rename to a/meta_gen.c diff --git a/parser.c b/a/parser.c similarity index 100% rename from parser.c rename to a/parser.c diff --git a/b/.gitignore b/b/.gitignore new file mode 100644 index 0000000..788f8b9 --- /dev/null +++ b/b/.gitignore @@ -0,0 +1,3 @@ +build/ +examples/ +backup.c diff --git a/b/build.sh b/b/build.sh new file mode 100755 index 0000000..71f2c0c --- /dev/null +++ b/b/build.sh @@ -0,0 +1,8 @@ +set -euo pipefail + +if [ ! -e build ]; then + mkdir build +fi +cd build +clang ../src/main.c -g -Wextra -Werror -Wall -std=c11 -lSDL3 -lSDL3_ttf -lm -fdiagnostics-absolute-paths +./a.out diff --git a/b/src/acwi.json b/b/src/acwi.json new file mode 100644 index 0000000..630389e --- /dev/null +++ b/b/src/acwi.json @@ -0,0 +1 @@ +{"chart":{"result":[{"meta":{"currency":"USD","symbol":"ACWI","exchangeName":"NGM","fullExchangeName":"NasdaqGM","instrumentType":"ETF","firstTradeDate":1206711000,"regularMarketTime":1781812801,"hasPrePostMarketData":true,"gmtoffset":-14400,"timezone":"EDT","exchangeTimezoneName":"America/New_York","regularMarketPrice":157.74,"fiftyTwoWeekHigh":160.03,"fiftyTwoWeekLow":123.2,"regularMarketDayHigh":158.04,"regularMarketDayLow":157.065,"regularMarketVolume":5631819,"longName":"iShares MSCI ACWI ETF","shortName":"iShares MSCI ACWI ETF","chartPreviousClose":155.81,"previousClose":155.81,"scale":3,"priceHint":2,"currentTradingPeriod":{"pre":{"timezone":"EDT","end":1781789400,"start":1781769600,"gmtoffset":-14400},"regular":{"timezone":"EDT","end":1781812800,"start":1781789400,"gmtoffset":-14400},"post":{"timezone":"EDT","end":1781827200,"start":1781812800,"gmtoffset":-14400}},"tradingPeriods":[[{"timezone":"EDT","end":1781812800,"start":1781789400,"gmtoffset":-14400}]],"dataGranularity":"1m","range":"1d","validRanges":["1d","5d","1mo","3mo","6mo","1y","2y","5y","10y","ytd","max"]},"timestamp":[1781789400,1781789460,1781789520,1781789580,1781789640,1781789700,1781789760,1781789820,1781789880,1781789940,1781790000,1781790060,1781790120,1781790180,1781790240,1781790300,1781790360,1781790420,1781790480,1781790540,1781790600,1781790660,1781790720,1781790780,1781790840,1781790900,1781790960,1781791020,1781791080,1781791140,1781791200,1781791260,1781791320,1781791380,1781791440,1781791500,1781791560,1781791620,1781791680,1781791740,1781791800,1781791860,1781791920,1781791980,1781792040,1781792100,1781792160,1781792220,1781792280,1781792340,1781792400,1781792460,1781792520,1781792580,1781792640,1781792700,1781792760,1781792820,1781792880,1781792940,1781793000,1781793060,1781793120,1781793180,1781793240,1781793300,1781793360,1781793420,1781793480,1781793540,1781793600,1781793660,1781793720,1781793780,1781793840,1781793900,1781793960,1781794020,1781794080,1781794140,1781794200,1781794260,1781794320,1781794380,1781794440,1781794500,1781794560,1781794620,1781794680,1781794740,1781794800,1781794860,1781794920,1781794980,1781795040,1781795100,1781795160,1781795220,1781795280,1781795340,1781795400,1781795460,1781795520,1781795580,1781795640,1781795700,1781795760,1781795820,1781795880,1781795940,1781796000,1781796060,1781796120,1781796180,1781796240,1781796300,1781796360,1781796420,1781796480,1781796540,1781796600,1781796660,1781796720,1781796780,1781796840,1781796900,1781796960,1781797020,1781797080,1781797140,1781797200,1781797260,1781797320,1781797380,1781797440,1781797500,1781797560,1781797620,1781797680,1781797740,1781797800,1781797860,1781797920,1781797980,1781798040,1781798100,1781798160,1781798220,1781798280,1781798340,1781798400,1781798460,1781798520,1781798580,1781798640,1781798700,1781798760,1781798820,1781798880,1781798940,1781799000,1781799060,1781799120,1781799180,1781799240,1781799300,1781799360,1781799420,1781799480,1781799540,1781799600,1781799660,1781799720,1781799780,1781799840,1781799900,1781799960,1781800020,1781800080,1781800140,1781800200,1781800260,1781800320,1781800380,1781800440,1781800500,1781800560,1781800620,1781800680,1781800740,1781800800,1781800860,1781800920,1781800980,1781801040,1781801100,1781801160,1781801220,1781801280,1781801340,1781801400,1781801460,1781801520,1781801580,1781801640,1781801700,1781801760,1781801820,1781801880,1781801940,1781802000,1781802060,1781802120,1781802180,1781802240,1781802300,1781802360,1781802420,1781802480,1781802540,1781802600,1781802660,1781802720,1781802780,1781802840,1781802900,1781802960,1781803020,1781803080,1781803140,1781803200,1781803260,1781803320,1781803380,1781803440,1781803500,1781803560,1781803620,1781803680,1781803740,1781803800,1781803860,1781803920,1781803980,1781804040,1781804100,1781804160,1781804220,1781804280,1781804340,1781804400,1781804460,1781804520,1781804580,1781804640,1781804700,1781804760,1781804820,1781804880,1781804940,1781805000,1781805060,1781805120,1781805180,1781805240,1781805300,1781805360,1781805420,1781805480,1781805540,1781805600,1781805660,1781805720,1781805780,1781805840,1781805900,1781805960,1781806020,1781806080,1781806140,1781806200,1781806260,1781806320,1781806380,1781806440,1781806500,1781806560,1781806620,1781806680,1781806740,1781806800,1781806860,1781806920,1781806980,1781807040,1781807100,1781807160,1781807220,1781807280,1781807340,1781807400,1781807460,1781807520,1781807580,1781807640,1781807700,1781807760,1781807820,1781807880,1781807940,1781808000,1781808060,1781808120,1781808180,1781808240,1781808300,1781808360,1781808420,1781808480,1781808540,1781808600,1781808660,1781808720,1781808780,1781808840,1781808900,1781808960,1781809020,1781809080,1781809140,1781809200,1781809260,1781809320,1781809380,1781809440,1781809500,1781809560,1781809620,1781809680,1781809740,1781809800,1781809860,1781809920,1781809980,1781810040,1781810100,1781810160,1781810220,1781810280,1781810340,1781810400,1781810460,1781810520,1781810580,1781810640,1781810700,1781810760,1781810820,1781810880,1781810940,1781811000,1781811060,1781811120,1781811180,1781811240,1781811300,1781811360,1781811420,1781811480,1781811540,1781811600,1781811660,1781811720,1781811780,1781811840,1781811900,1781811960,1781812020,1781812080,1781812140,1781812200,1781812260,1781812320,1781812380,1781812440,1781812500,1781812560,1781812620,1781812680,1781812740,1781812800],"indicators":{"quote":[{"low":[157.66000366210938,157.52000427246094,157.5800018310547,157.47000122070312,157.37339782714844,157.3300018310547,157.2200927734375,157.1699981689453,157.22999572753906,157.22999572753906,157.25,157.22999572753906,157.27999877929688,157.1999969482422,157.1699981689453,157.1999969482422,157.2100067138672,157.14500427246094,157.22000122070312,157.2100067138672,157.27999877929688,157.27999877929688,157.35000610351562,157.3800048828125,157.32000732421875,157.2100067138672,157.17550659179688,157.0800018310547,157.06500244140625,157.07000732421875,157.13999938964844,157.22000122070312,157.17999267578125,157.21499633789062,157.27999877929688,157.25999450683594,157.3000030517578,157.3699951171875,157.36000061035156,157.39500427246094,157.36000061035156,157.27999877929688,157.2899932861328,157.30999755859375,157.22000122070312,157.13999938964844,157.2100067138672,157.3800048828125,157.4499969482422,157.47000122070312,157.47000122070312,157.47000122070312,157.52999877929688,157.5399932861328,157.66000366210938,157.61000061035156,157.6199951171875,157.5,157.4199981689453,157.44000244140625,157.30999755859375,157.32000732421875,157.35000610351562,157.35009765625,157.4600067138672,157.4600067138672,157.4600067138672,157.57000732421875,157.61000061035156,157.58999633789062,157.625,157.5399932861328,157.5,157.5500030517578,157.5500030517578,157.5399932861328,157.60000610351562,157.64999389648438,157.6999969482422,157.75999450683594,157.69859313964844,157.63999938964844,157.67999267578125,157.63999938964844,157.66000366210938,157.7100067138672,157.7100067138672,157.72000122070312,157.77000427246094,157.77000427246094,157.75,157.67999267578125,157.6699981689453,157.6199951171875,157.64999389648438,157.6199951171875,157.61000061035156,157.63999938964844,157.6699981689453,157.6699981689453,157.63999938964844,157.6999969482422,157.72039794921875,157.72999572753906,157.64500427246094,157.56500244140625,157.58999633789062,157.53500366210938,157.5500030517578,157.47799682617188,157.4949951171875,157.55999755859375,157.53500366210938,157.47999572753906,157.49000549316406,157.49000549316406,157.5,157.50999450683594,157.49000549316406,157.41969299316406,157.4199981689453,157.49000549316406,157.44000244140625,157.3800048828125,157.42999267578125,157.360107421875,157.3300018310547,157.33999633789062,157.25,157.25,157.3300018310547,157.39999389648438,157.4199981689453,157.3699951171875,157.3699951171875,157.30499267578125,157.3300018310547,157.3000030517578,157.375,157.40499877929688,157.47000122070312,157.51499938964844,157.53500366210938,157.625,157.59500122070312,157.59500122070312,157.625,157.625,157.65499877929688,157.6199951171875,157.6300048828125,157.6199951171875,157.61500549316406,157.58999633789062,157.60000610351562,157.58999633789062,157.64999389648438,157.5800018310547,157.625,157.61500549316406,157.61500549316406,157.6199951171875,157.63999938964844,157.63999938964844,157.6300048828125,157.61500549316406,157.59500122070312,157.61000061035156,157.5749969482422,157.5399932861328,157.60499572753906,157.63999938964844,157.64500427246094,157.64999389648438,157.64999389648438,157.61000061035156,157.64999389648438,157.64999389648438,157.63999938964844,157.64500427246094,157.63499450683594,157.6199951171875,157.6300048828125,157.625,157.61000061035156,157.6699981689453,157.65499877929688,157.69500732421875,157.75,157.78500366210938,157.78500366210938,157.80499267578125,157.7899932861328,157.8000030517578,157.78500366210938,157.76229858398438,157.76499938964844,157.75999450683594,157.77450561523438,157.75999450683594,157.7050018310547,157.6750030517578,157.66000366210938,157.69000244140625,157.6750030517578,157.64999389648438,157.63499450683594,157.65499877929688,157.67999267578125,157.6699981689453,157.6699981689453,157.6999969482422,157.72000122070312,157.77000427246094,157.77499389648438,157.8249969482422,157.875,157.8800048828125,157.8699951171875,157.88499450683594,157.8699951171875,157.88499450683594,157.91000366210938,157.9199981689453,157.9250030517578,157.9199981689453,157.9499969482422,158.0,158.02000427246094,158.00999450683594,157.9949951171875,158.0,157.97000122070312,157.97999572753906,157.9199981689453,157.9250030517578,157.9250030517578,157.90499877929688,157.9149932861328,157.91000366210938,157.94000244140625,157.9949951171875,157.9600067138672,157.94000244140625,157.9600067138672,157.9600067138672,157.94500732421875,157.92999267578125,157.9199981689453,157.89999389648438,157.88999938964844,157.90499877929688,157.90499877929688,157.89999389648438,157.9149932861328,157.92999267578125,157.8800048828125,157.89999389648438,157.91000366210938,157.90499877929688,157.89999389648438,157.89999389648438,157.90499877929688,157.92999267578125,157.9199981689453,157.8699951171875,157.86500549316406,157.85000610351562,157.83999633789062,157.84500122070312,157.7899932861328,157.7899932861328,157.7899932861328,157.75,157.75999450683594,157.75999450683594,157.77499389648438,157.77999877929688,157.7899932861328,157.8000030517578,157.8000030517578,157.83999633789062,157.85000610351562,157.8300018310547,157.81500244140625,157.77999877929688,157.75999450683594,157.76499938964844,157.78500366210938,157.82000732421875,157.77999877929688,157.7949981689453,157.8249969482422,157.8249969482422,157.82000732421875,157.7899932861328,157.77999877929688,157.80499267578125,157.77000427246094,157.77999877929688,157.75999450683594,157.75,157.74000549316406,157.73500061035156,157.74000549316406,157.7449951171875,157.71499633789062,157.72000122070312,157.7050018310547,157.7100067138672,157.7100067138672,157.72000122070312,157.74000549316406,157.7449951171875,157.74000549316406,157.75,157.75999450683594,157.7550048828125,157.77000427246094,157.77999877929688,157.77499389648438,157.77000427246094,157.7899932861328,157.8249969482422,157.80999755859375,157.8000030517578,157.7899932861328,157.7449951171875,157.7449951171875,157.7550048828125,157.72000122070312,157.6999969482422,157.6300048828125,157.6199951171875,157.6199951171875,157.6199951171875,157.60000610351562,157.61000061035156,157.60499572753906,157.6199951171875,157.5,157.47999572753906,157.47000122070312,157.5050048828125,157.5449981689453,157.52999877929688,157.52000427246094,157.52000427246094,157.49000549316406,157.50999450683594,157.59500122070312,157.6300048828125,157.63999938964844,157.63999938964844,157.6300048828125,157.61000061035156,157.6300048828125,157.61000061035156,157.61000061035156,157.6199951171875,157.55499267578125,157.5850067138672,157.60499572753906,157.58999633789062,157.65499877929688,157.69500732421875,157.73500061035156,157.72500610351562,157.74000549316406,157.75,157.80999755859375,157.8300018310547,157.85000610351562,157.8249969482422,157.86000061035156,157.8699951171875,157.9149932861328,157.90499877929688,157.83999633789062,157.83999633789062,157.81500244140625,157.9600067138672,157.97000122070312,157.94500732421875,157.89999389648438,157.9199981689453,157.9199981689453,157.91000366210938,157.92999267578125,157.69000244140625,157.74000549316406],"high":[157.85000610351562,157.63999938964844,157.64999389648438,157.60000610351562,157.51499938964844,157.3800048828125,157.35000610351562,157.2100067138672,157.25,157.27000427246094,157.27000427246094,157.32000732421875,157.36000061035156,157.35000610351562,157.1999969482422,157.33999633789062,157.25,157.24000549316406,157.22000122070312,157.3699951171875,157.36000061035156,157.35000610351562,157.3800048828125,157.40499877929688,157.36000061035156,157.22000122070312,157.25,157.13999938964844,157.1199951171875,157.14999389648438,157.25999450683594,157.27999877929688,157.22999572753906,157.3000030517578,157.3000030517578,157.25999450683594,157.3699951171875,157.44000244140625,157.42100524902344,157.4199981689453,157.36000061035156,157.32000732421875,157.3000030517578,157.32000732421875,157.24000549316406,157.24000549316406,157.3699951171875,157.47000122070312,157.5,157.48500061035156,157.47000122070312,157.49000549316406,157.52999877929688,157.6300048828125,157.6999969482422,157.64999389648438,157.63999938964844,157.5399932861328,157.4600067138672,157.44000244140625,157.3249969482422,157.42999267578125,157.4600067138672,157.39999389648438,157.47999572753906,157.50999450683594,157.58999633789062,157.60000610351562,157.6199951171875,157.61000061035156,157.66000366210938,157.59500122070312,157.5500030517578,157.5500030517578,157.57000732421875,157.60000610351562,157.6300048828125,157.71499633789062,157.72999572753906,157.76499938964844,157.72999572753906,157.69000244140625,157.69500732421875,157.63999938964844,157.6999969482422,157.72000122070312,157.7100067138672,157.74000549316406,157.82000732421875,157.8300018310547,157.75999450683594,157.72999572753906,157.6999969482422,157.63999938964844,157.7050018310547,157.67999267578125,157.64999389648438,157.63999938964844,157.7100067138672,157.69000244140625,157.69000244140625,157.77000427246094,157.74000549316406,157.72999572753906,157.72999572753906,157.56500244140625,157.6300048828125,157.60000610351562,157.5800018310547,157.50999450683594,157.59500122070312,157.55999755859375,157.5500030517578,157.5399932861328,157.55999755859375,157.5050048828125,157.52000427246094,157.60000610351562,157.57000732421875,157.4499969482422,157.5,157.5399932861328,157.46499633789062,157.42999267578125,157.50999450683594,157.41000366210938,157.3800048828125,157.38999938964844,157.39999389648438,157.375,157.39999389648438,157.4949951171875,157.47500610351562,157.4499969482422,157.4199981689453,157.38999938964844,157.375,157.39999389648438,157.4149932861328,157.48500061035156,157.52999877929688,157.55499267578125,157.64500427246094,157.67999267578125,157.63999938964844,157.63499450683594,157.66000366210938,157.6750030517578,157.68499755859375,157.6699981689453,157.71499633789062,157.6999969482422,157.64500427246094,157.61500549316406,157.64500427246094,157.6699981689453,157.69500732421875,157.64999389648438,157.6750030517578,157.63999938964844,157.63999938964844,157.6750030517578,157.6750030517578,157.66000366210938,157.66000366210938,157.64999389648438,157.61500549316406,157.63999938964844,157.64999389648438,157.5850067138672,157.63999938964844,157.69000244140625,157.7050018310547,157.6750030517578,157.6649932861328,157.6649932861328,157.67999267578125,157.6649932861328,157.65499877929688,157.65499877929688,157.6699981689453,157.64999389648438,157.64999389648438,157.65499877929688,157.65499877929688,157.69000244140625,157.69500732421875,157.77499389648438,157.77499389648438,157.81500244140625,157.81500244140625,157.8300018310547,157.81500244140625,157.8249969482422,157.80999755859375,157.7949981689453,157.78500366210938,157.7899932861328,157.78500366210938,157.7949981689453,157.7550048828125,157.6999969482422,157.71499633789062,157.72000122070312,157.69000244140625,157.69000244140625,157.6750030517578,157.6999969482422,157.7050018310547,157.6999969482422,157.72000122070312,157.7100067138672,157.78500366210938,157.7949981689453,157.81500244140625,157.90499877929688,157.91000366210938,157.88999938964844,157.89500427246094,157.9250030517578,157.90499877929688,157.90499877929688,157.9550018310547,157.93499755859375,157.94500732421875,157.96499633789062,158.00999450683594,158.02999877929688,158.03500366210938,158.02499389648438,158.03500366210938,158.03500366210938,157.9949951171875,158.0050048828125,157.98500061035156,157.94500732421875,157.9550018310547,157.92999267578125,157.94500732421875,157.94000244140625,157.98500061035156,158.01499938964844,157.99000549316406,157.98500061035156,158.0050048828125,157.98500061035156,157.96499633789062,157.94500732421875,157.94500732421875,157.9550018310547,157.92999267578125,157.9250030517578,157.94500732421875,157.9149932861328,157.9499969482422,157.94500732421875,157.94500732421875,157.9250030517578,157.9250030517578,157.9250030517578,157.9149932861328,157.9149932861328,157.9550018310547,157.9550018310547,157.96499633789062,157.90499877929688,157.875,157.88499450683594,157.88499450683594,157.86500549316406,157.84500122070312,157.8350067138672,157.8350067138672,157.80499267578125,157.7949981689453,157.85000610351562,157.80499267578125,157.7949981689453,157.8249969482422,157.8300018310547,157.84500122070312,157.875,157.875,157.875,157.84500122070312,157.81500244140625,157.78500366210938,157.80499267578125,157.8300018310547,157.84500122070312,157.81500244140625,157.8350067138672,157.8699951171875,157.85000610351562,157.85000610351562,157.86500549316406,157.80499267578125,157.85499572753906,157.85000610351562,157.7949981689453,157.77999877929688,157.78500366210938,157.77999877929688,157.75,157.77499389648438,157.76499938964844,157.7550048828125,157.7449951171875,157.74000549316406,157.7449951171875,157.73500061035156,157.75999450683594,157.77499389648438,157.76499938964844,157.76499938964844,157.77499389648438,157.78500366210938,157.7899932861328,157.80499267578125,157.81500244140625,157.80999755859375,157.7949981689453,157.8350067138672,157.8350067138672,157.8350067138672,157.8249969482422,157.8249969482422,157.76499938964844,157.77499389648438,157.78500366210938,157.7550048828125,157.7449951171875,157.69500732421875,157.64500427246094,157.65499877929688,157.6300048828125,157.625,157.6300048828125,157.63499450683594,157.63999938964844,157.60000610351562,157.55499267578125,157.52000427246094,157.5449981689453,157.5800018310547,157.5850067138672,157.5449981689453,157.56500244140625,157.5399932861328,157.61500549316406,157.64500427246094,157.64999389648438,157.68499755859375,157.68499755859375,157.64999389648438,157.6300048828125,157.64500427246094,157.6300048828125,157.64500427246094,157.65499877929688,157.63999938964844,157.6199951171875,157.6199951171875,157.65499877929688,157.68499755859375,157.7449951171875,157.77999877929688,157.7550048828125,157.75999450683594,157.81500244140625,157.85499572753906,157.85499572753906,157.88499450683594,157.85499572753906,157.89999389648438,157.9250030517578,157.92999267578125,157.93960571289062,157.9149932861328,157.86000061035156,157.9499969482422,158.0399932861328,158.02999877929688,157.9949951171875,158.0050048828125,157.98500061035156,157.9499969482422,157.97500610351562,157.97500610351562,157.97000122070312,157.74000549316406],"volume":[38997,15002,4383,6644,7611,22785,13441,11780,6836,4351,11554,4160,2479,3244,3782,6110,2258,4946,1742,54331,2878,3385,1747,12944,5313,4365,2334,2282,3205,4507,2165,1607,5796,1197,2964,663,1953,5592,2046,3790,540,4626,2756,2215,13047,3167,2722,2359,25407,4427,1728,1559,173,1859,4135,2007,2355,1838,1063,2188,1265,1878,1444,2418,2089,15263,4581,1159,2133,1704,1463,3773,1930,1314,484,2183,1180,2582,3086,2815,4390,2148,1866,630,2082,1829,265,1782,1443,2366,1965,2192,4727,1111,2207,1958,4235,2100,1877,4408,1624,3013,3100,3038,3245,586,2076,3269,2208,2184,2052,1424,2824,1621,1103,1565,2453,2008,3589,3513,3889,3951,2589,2699,2467,2360,10395,11336,16199,11402,13485,11392,14566,11244,11743,12442,8836,12918,10097,11672,12852,10530,12834,11437,6874,12942,9931,12755,10379,24777,11460,12405,13924,14864,9744,16295,13191,12412,10151,17342,3983,10403,1088,2286,2595,2118,2558,2891,1716,3623,2055,2645,1881,2911,1400,3735,1704,3995,6092,1804,3168,4998,4552,3256,5371,37738,60893,14832,34040,7496,63282,8522,4097,3964,5340,5611,6573,4185,5098,15059,3666,33717,5880,12955,21344,71659,12189,11159,6196,72391,5020,3521,5883,3077,14098,5800,3799,3558,18441,5520,15311,3217,8871,40719,10038,8476,7327,8452,7738,8908,8234,10107,9760,6935,11132,8720,8296,8013,7737,169436,10342,8482,4868,11070,21378,6109,10143,9473,9927,9577,18046,12516,8897,8244,11292,4814,11402,7358,9673,7957,10319,9455,8306,16270,9831,11208,8807,11136,143017,9733,9628,16316,9516,14105,0,127919,11512,10530,11279,86520,9220,7966,9549,7798,9523,11326,10152,12658,7483,9248,7780,24216,12997,7017,9981,23301,10806,9615,9413,1036250,5326,489954,8925,3394,6196,6095,8605,9723,6828,6237,7010,8820,8132,5506,6831,10389,8191,10333,8008,5791,9162,13306,5389,5215,6093,11716,8712,6775,11166,7784,9030,6546,22122,5959,10346,6520,7250,5832,7241,12220,8591,12042,11111,9880,7393,10783,7591,8848,12253,11118,17193,11647,7082,7809,6696,7794,15387,7876,11235,10153,10669,9719,11227,12269,9975,11685,10495,8779,10151,9869,11163,9839,12286,9671,18285,15651,14132,18054,30274,12631,44619,28100,18556,17875,25201,31657,28742,34015,43108,100741,0],"open":[157.8300018310547,157.5800018310547,157.6300048828125,157.60000610351562,157.47000122070312,157.3699951171875,157.3000030517578,157.17999267578125,157.22999572753906,157.22999572753906,157.27000427246094,157.22999572753906,157.27999877929688,157.33999633789062,157.1999969482422,157.1999969482422,157.25,157.24000549316406,157.22000122070312,157.2100067138672,157.36000061035156,157.3300018310547,157.35000610351562,157.38999938964844,157.36000061035156,157.22000122070312,157.25,157.13999938964844,157.08999633789062,157.0800018310547,157.22999572753906,157.27000427246094,157.19000244140625,157.21499633789062,157.27999877929688,157.25999450683594,157.3000030517578,157.3800048828125,157.42100524902344,157.41000366210938,157.36000061035156,157.31500244140625,157.2899932861328,157.30999755859375,157.24000549316406,157.24000549316406,157.2100067138672,157.3800048828125,157.4600067138672,157.48500061035156,157.47000122070312,157.47999572753906,157.52999877929688,157.5399932861328,157.67999267578125,157.61000061035156,157.63999938964844,157.5399932861328,157.4600067138672,157.44000244140625,157.3249969482422,157.32000732421875,157.4600067138672,157.35009765625,157.47000122070312,157.50999450683594,157.4600067138672,157.57000732421875,157.61000061035156,157.58999633789062,157.66000366210938,157.59500122070312,157.5500030517578,157.5500030517578,157.57000732421875,157.5399932861328,157.60000610351562,157.64999389648438,157.6999969482422,157.76499938964844,157.72999572753906,157.69000244140625,157.67999267578125,157.63999938964844,157.66000366210938,157.7100067138672,157.7100067138672,157.72000122070312,157.77000427246094,157.8300018310547,157.75999450683594,157.72000122070312,157.69000244140625,157.6199951171875,157.64999389648438,157.6699981689453,157.63499450683594,157.63999938964844,157.6699981689453,157.69000244140625,157.67999267578125,157.6999969482422,157.74000549316406,157.72999572753906,157.72000122070312,157.56500244140625,157.6300048828125,157.5800018310547,157.5500030517578,157.50999450683594,157.4949951171875,157.55999755859375,157.5500030517578,157.47999572753906,157.55999755859375,157.5,157.52000427246094,157.50999450683594,157.57000732421875,157.4499969482422,157.4199981689453,157.5,157.4600067138672,157.42999267578125,157.44000244140625,157.41000366210938,157.3800048828125,157.36000061035156,157.39500427246094,157.25,157.38499450683594,157.39999389648438,157.42999267578125,157.44500732421875,157.38999938964844,157.3800048828125,157.36500549316406,157.34500122070312,157.4149932861328,157.40499877929688,157.47000122070312,157.51499938964844,157.53500366210938,157.625,157.6199951171875,157.59500122070312,157.63499450683594,157.625,157.66000366210938,157.6699981689453,157.6300048828125,157.6999969482422,157.6199951171875,157.60499572753906,157.60499572753906,157.58999633789062,157.65499877929688,157.63999938964844,157.6300048828125,157.625,157.63999938964844,157.6199951171875,157.63999938964844,157.66000366210938,157.64999389648438,157.64999389648438,157.60499572753906,157.61500549316406,157.6300048828125,157.5850067138672,157.60499572753906,157.63999938964844,157.7050018310547,157.64999389648438,157.6649932861328,157.625,157.64999389648438,157.66000366210938,157.64999389648438,157.65499877929688,157.63499450683594,157.64999389648438,157.64999389648438,157.6300048828125,157.6199951171875,157.6699981689453,157.6699981689453,157.69500732421875,157.77499389648438,157.78500366210938,157.81500244140625,157.8249969482422,157.81500244140625,157.8000030517578,157.8000030517578,157.78500366210938,157.77999877929688,157.75999450683594,157.77499389648438,157.78500366210938,157.7550048828125,157.6999969482422,157.6750030517578,157.71499633789062,157.69000244140625,157.67999267578125,157.65499877929688,157.65499877929688,157.7050018310547,157.69000244140625,157.72000122070312,157.6999969482422,157.72000122070312,157.7949981689453,157.77499389648438,157.8249969482422,157.89999389648438,157.8800048828125,157.89500427246094,157.88499450683594,157.8800048828125,157.88499450683594,157.9199981689453,157.9199981689453,157.9250030517578,157.92999267578125,157.9550018310547,158.01499938964844,158.02499389648438,158.02499389648438,158.01499938964844,158.01499938964844,157.9949951171875,157.9949951171875,157.98500061035156,157.92999267578125,157.9250030517578,157.9199981689453,157.94500732421875,157.91000366210938,157.94500732421875,157.9949951171875,157.99000549316406,157.96499633789062,157.9600067138672,157.97999572753906,157.96499633789062,157.94500732421875,157.93499755859375,157.9499969482422,157.90499877929688,157.90499877929688,157.91000366210938,157.91000366210938,157.9149932861328,157.92999267578125,157.94500732421875,157.91000366210938,157.9199981689453,157.9250030517578,157.9149932861328,157.90499877929688,157.90499877929688,157.93499755859375,157.94500732421875,157.89999389648438,157.86500549316406,157.875,157.8800048828125,157.84500122070312,157.84500122070312,157.80499267578125,157.7949981689453,157.80499267578125,157.77000427246094,157.77000427246094,157.80499267578125,157.77999877929688,157.8000030517578,157.80499267578125,157.81500244140625,157.83999633789062,157.85499572753906,157.8699951171875,157.83999633789062,157.81500244140625,157.78500366210938,157.76499938964844,157.7949981689453,157.8350067138672,157.81500244140625,157.7949981689453,157.8249969482422,157.85000610351562,157.85000610351562,157.84500122070312,157.80499267578125,157.80499267578125,157.85000610351562,157.77999877929688,157.77999877929688,157.77000427246094,157.77499389648438,157.7449951171875,157.74000549316406,157.75,157.7550048828125,157.72999572753906,157.72000122070312,157.72500610351562,157.72000122070312,157.72000122070312,157.7550048828125,157.76499938964844,157.74000549316406,157.76499938964844,157.75999450683594,157.75999450683594,157.77000427246094,157.77999877929688,157.80999755859375,157.78500366210938,157.7899932861328,157.8350067138672,157.8350067138672,157.8000030517578,157.82000732421875,157.76499938964844,157.7449951171875,157.76499938964844,157.7550048828125,157.7449951171875,157.69000244140625,157.63999938964844,157.6199951171875,157.6199951171875,157.625,157.61000061035156,157.60499572753906,157.63499450683594,157.59500122070312,157.55499267578125,157.47999572753906,157.5050048828125,157.55999755859375,157.5449981689453,157.52999877929688,157.52499389648438,157.5399932861328,157.52499389648438,157.61500549316406,157.64500427246094,157.63999938964844,157.68499755859375,157.64500427246094,157.6199951171875,157.6300048828125,157.6300048828125,157.61000061035156,157.6199951171875,157.6300048828125,157.60000610351562,157.61000061035156,157.61500549316406,157.65499877929688,157.69500732421875,157.75,157.74000549316406,157.7550048828125,157.75,157.82000732421875,157.8350067138672,157.85499572753906,157.85499572753906,157.86000061035156,157.88999938964844,157.9149932861328,157.9199981689453,157.91000366210938,157.86000061035156,157.86000061035156,157.9600067138672,158.0,157.97000122070312,157.97500610351562,157.9499969482422,157.9499969482422,157.91000366210938,157.9499969482422,157.9550018310547,157.74000549316406],"close":[157.6649932861328,157.5850067138672,157.5800018310547,157.47000122070312,157.37339782714844,157.3300018310547,157.2200927734375,157.19000244140625,157.22999572753906,157.27000427246094,157.25,157.32000732421875,157.32000732421875,157.1999969482422,157.1699981689453,157.27999877929688,157.2100067138672,157.14500427246094,157.22000122070312,157.3699951171875,157.3300018310547,157.32000732421875,157.3800048828125,157.38499450683594,157.32000732421875,157.22000122070312,157.17550659179688,157.08999633789062,157.06500244140625,157.14500427246094,157.25999450683594,157.22000122070312,157.22000122070312,157.3000030517578,157.3000030517578,157.25999450683594,157.36500549316406,157.44000244140625,157.38999938964844,157.41000366210938,157.36000061035156,157.27999877929688,157.3000030517578,157.32000732421875,157.22999572753906,157.16000366210938,157.3699951171875,157.47000122070312,157.47000122070312,157.47000122070312,157.47000122070312,157.49000549316406,157.52999877929688,157.6300048828125,157.66000366210938,157.64999389648438,157.6199951171875,157.50999450683594,157.4199981689453,157.44000244140625,157.30999755859375,157.42999267578125,157.35000610351562,157.39999389648438,157.47999572753906,157.47999572753906,157.5500030517578,157.60000610351562,157.6199951171875,157.61000061035156,157.625,157.5399932861328,157.5,157.5500030517578,157.5500030517578,157.60000610351562,157.6300048828125,157.7100067138672,157.72999572753906,157.75999450683594,157.6999969482422,157.6699981689453,157.69500732421875,157.63999938964844,157.6999969482422,157.72000122070312,157.7100067138672,157.72999572753906,157.82000732421875,157.77000427246094,157.75,157.67999267578125,157.6699981689453,157.63999938964844,157.7050018310547,157.6300048828125,157.63999938964844,157.63999938964844,157.7100067138672,157.6699981689453,157.69000244140625,157.77000427246094,157.72079467773438,157.72999572753906,157.64500427246094,157.56500244140625,157.58999633789062,157.53500366210938,157.5500030517578,157.5,157.59500122070312,157.55999755859375,157.53500366210938,157.5399932861328,157.49000549316406,157.5050048828125,157.50999450683594,157.60000610351562,157.49000549316406,157.42999267578125,157.5,157.49000549316406,157.44000244140625,157.3800048828125,157.42999267578125,157.3800048828125,157.3699951171875,157.3800048828125,157.25,157.375,157.39999389648438,157.44000244140625,157.44500732421875,157.38999938964844,157.38499450683594,157.3699951171875,157.3350067138672,157.39999389648438,157.39500427246094,157.46499633789062,157.51499938964844,157.52999877929688,157.63499450683594,157.65499877929688,157.59500122070312,157.6300048828125,157.6300048828125,157.66000366210938,157.6750030517578,157.6199951171875,157.69500732421875,157.6199951171875,157.625,157.60499572753906,157.60000610351562,157.65499877929688,157.6649932861328,157.64500427246094,157.625,157.63499450683594,157.6199951171875,157.6649932861328,157.6750030517578,157.64999389648438,157.64500427246094,157.61500549316406,157.59500122070312,157.63999938964844,157.5749969482422,157.5850067138672,157.63499450683594,157.69000244140625,157.64500427246094,157.66000366210938,157.65499877929688,157.625,157.67999267578125,157.6649932861328,157.65499877929688,157.64999389648438,157.6699981689453,157.64500427246094,157.64500427246094,157.6300048828125,157.65499877929688,157.6699981689453,157.69500732421875,157.77499389648438,157.75999450683594,157.80999755859375,157.7949981689453,157.80999755859375,157.8000030517578,157.8000030517578,157.7899932861328,157.78500366210938,157.77000427246094,157.77499389648438,157.78500366210938,157.75999450683594,157.7050018310547,157.67999267578125,157.7100067138672,157.7050018310547,157.68499755859375,157.65499877929688,157.65499877929688,157.69000244140625,157.69000244140625,157.6999969482422,157.69000244140625,157.7100067138672,157.78500366210938,157.77000427246094,157.81500244140625,157.88999938964844,157.8800048828125,157.88999938964844,157.8800048828125,157.88999938964844,157.89500427246094,157.90499877929688,157.91000366210938,157.9250030517578,157.94500732421875,157.9600067138672,158.00999450683594,158.02499389648438,158.02499389648438,158.02499389648438,158.0,158.0,157.99000549316406,157.97999572753906,157.9199981689453,157.9250030517578,157.94500732421875,157.92999267578125,157.9149932861328,157.94000244140625,157.98500061035156,157.9949951171875,157.9600067138672,157.96499633789062,157.99000549316406,157.9600067138672,157.9550018310547,157.93499755859375,157.94500732421875,157.91000366210938,157.91000366210938,157.9199981689453,157.9199981689453,157.9149932861328,157.92999267578125,157.94500732421875,157.91000366210938,157.91000366210938,157.9199981689453,157.91000366210938,157.90499877929688,157.89999389648438,157.93499755859375,157.94500732421875,157.9250030517578,157.8699951171875,157.86500549316406,157.8699951171875,157.85000610351562,157.85000610351562,157.80499267578125,157.7949981689453,157.80999755859375,157.75999450683594,157.77000427246094,157.82000732421875,157.77499389648438,157.7949981689453,157.80999755859375,157.80499267578125,157.83999633789062,157.85499572753906,157.875,157.83999633789062,157.82000732421875,157.77999877929688,157.77499389648438,157.8000030517578,157.8300018310547,157.82000732421875,157.7949981689453,157.8249969482422,157.86500549316406,157.84500122070312,157.8350067138672,157.80499267578125,157.80499267578125,157.85499572753906,157.7899932861328,157.7899932861328,157.75999450683594,157.77999877929688,157.74000549316406,157.73500061035156,157.77499389648438,157.7550048828125,157.71499633789062,157.72000122070312,157.71499633789062,157.7100067138672,157.72500610351562,157.7550048828125,157.74000549316406,157.7449951171875,157.75999450683594,157.77000427246094,157.75999450683594,157.76499938964844,157.8000030517578,157.81500244140625,157.7899932861328,157.77000427246094,157.8350067138672,157.8350067138672,157.81500244140625,157.8249969482422,157.7899932861328,157.7449951171875,157.75999450683594,157.7550048828125,157.72999572753906,157.6999969482422,157.63999938964844,157.64500427246094,157.6300048828125,157.6199951171875,157.61000061035156,157.61000061035156,157.63499450683594,157.6199951171875,157.55499267578125,157.49000549316406,157.5,157.53500366210938,157.5449981689453,157.52999877929688,157.52499389648438,157.5449981689453,157.5,157.61500549316406,157.64500427246094,157.63999938964844,157.67999267578125,157.63999938964844,157.6300048828125,157.62989807128906,157.63999938964844,157.61000061035156,157.6300048828125,157.63999938964844,157.60000610351562,157.60499572753906,157.6199951171875,157.65499877929688,157.67999267578125,157.7449951171875,157.7449951171875,157.75,157.75,157.81500244140625,157.83999633789062,157.85000610351562,157.85499572753906,157.85499572753906,157.89500427246094,157.9199981689453,157.9149932861328,157.9199981689453,157.86000061035156,157.85499572753906,157.9499969482422,158.00999450683594,157.97000122070312,157.97500610351562,157.94500732421875,157.9600067138672,157.92999267578125,157.9600067138672,157.9600067138672,157.69000244140625,157.74000549316406]}]}}],"error":null}} \ No newline at end of file diff --git a/b/src/json.c b/b/src/json.c new file mode 100755 index 0000000..9c2da61 --- /dev/null +++ b/b/src/json.c @@ -0,0 +1,1125 @@ +//usr/bin/env tcc -run -bt -g -b -DJSON_LIBRARY_MAIN "$0" "$@"; exit +/* + json.c - small single-file JSON parser, serializer, query helper, and CLI. + + This file is meant to work in three modes: + + 1. Command line utility + Run it directly, or compile with JSON_LIBRARY_MAIN: + + ./json.c --test + printf '{"name":["zero","one"]}' | ./json.c 'name[1]' + ./json.c 'chart.result[0].meta.symbol' acwi + + Without --test it reads JSON from stdin, or from an optional file argument, + applies an optional query, and pretty-prints the selected JSON node. + + usage: ./json.c [query] [file] + + Query syntax supports dotted object fields and bracket array indexes: + + name + items[0] + config.timeout_ms + chart.result[0].meta.symbol + + 2. Header-only declarations + Include this file normally to get the public types and function prototypes: + + #include "json.c" + + int main(void) { + json_t *root = json_read("data.json"); + char *name = jqs(root, "user.name"); + double price = jqn(root, "items[0].price"); + json_free(root); + } + + Link with one translation unit compiled with JSON_LIBRARY_IMPL. + + 3. Library implementation + In exactly one .c file, define JSON_LIBRARY_IMPL before including json.c, + or compile json.c directly with -DJSON_LIBRARY_IMPL: + + #define JSON_LIBRARY_IMPL + #include "json.c" + + gcc -DJSON_LIBRARY_IMPL -c json.c -o json.o + + Public API: + + json_t *json_parse(const char *stream) + Parse a NUL-terminated JSON string. Returns NULL on error. + + json_t *json_read(char *filename) + Read a file and parse it as JSON. Returns NULL on read/parse error. + + char *json_serialize_compact(json_t *n) + char *json_serialize_pretty(json_t *n, const char *new_line, const char *indent) + Serialize a JSON tree. Returned strings are heap allocated; free them. + + void json_print(json_t *n) + Pretty-print a JSON value to stdout. + + json_t *json_object_get(json_t *n, const char *key) + json_t *json_array_get(json_t *n, int idx) + Direct object/array access helpers. Return NULL on wrong type/miss. + + json_t *jq(json_t *n, const char *fmt, ...) + double jqn(json_t *n, const char *fmt, ...) + char *jqs(json_t *n, const char *fmt, ...) + int jql(json_t *n, const char *fmt, ...) + Query helpers. jq returns a node, jqn extracts a number, jqs extracts + a string, and jql returns array/object length. Queries use dotted + object fields and bracket array indexes. The query format is + printf-style, so indexes can be formatted: jqs(root, "items[%d]", i). + + int json_equal(json_t *a, json_t *b) + Structural equality test. + + void json_free(json_t *n) + Recursively free a JSON tree returned by this library. + + Data model: + Inspect n->kind, then use n->number, n->string, n->boolean, n->array, or + n->object. Arrays contain json_t* elements. Objects contain key/value pairs. + + Notes: + - Supports null, booleans, numbers, strings, arrays, and objects. + - Stops at the first parse error and prints an error with line/column. + - Object member order is preserved. + + Refactors: + - Parsing error handling should maybe be based on setjmp longjmp (problem is allocations) + - If error user should get a NULL, context should have the line, column and error message + - Make sure there are no fixed size stack buffers to exploit (like keys being too big in json etc.) + - Add ability to redefine the allocator at compile time + - Add more examples, test more in practice and get more insights! + +*/ +#ifndef JSON_LIBRARY_HEADER +#define JSON_LIBRARY_HEADER + +#ifndef JSON_API +#define JSON_API +#endif + +typedef enum { + JSON_NULL, + JSON_BOOL, + JSON_ARRAY, + JSON_OBJECT, + JSON_NUMBER, + JSON_STRING, +} json_kind_t; + +typedef struct json_t json_t; +typedef struct json_object_t json_object_t; +typedef struct json_array_t json_array_t; +typedef struct json_kv_t json_kv_t; + +struct json_kv_t { + char *key; + json_t *value; +}; + +struct json_object_t { + json_kv_t *data; + int len; + int cap; +}; + +struct json_array_t { + json_t **data; + int len; + int cap; +}; + +struct json_t { + json_kind_t kind; + union { + int boolean; + double number; + char *string; + json_object_t object; + json_array_t array; + }; +}; + +JSON_API json_t *json_parse(const char *stream); +JSON_API char *json_serialize_compact(json_t *n); +JSON_API char *json_serialize_pretty(json_t *n, const char *new_line, const char *indent); +JSON_API char *json_summarize(json_t *n); +JSON_API void json_print(json_t *n); +JSON_API json_t *json_object_get(json_t *n, const char *key); +JSON_API json_t *json_array_get(json_t *n, int idx); +JSON_API json_t *jq(json_t *n, const char *fmt, ...); +JSON_API double jqn(json_t *n, const char *fmt, ...); +JSON_API char *jqs(json_t *n, const char *fmt, ...); +JSON_API int jql(json_t *n, const char *fmt, ...); +JSON_API void json_free(json_t *n); +JSON_API int json_equal(json_t *a, json_t *b); +JSON_API json_t *json_read(const char *filename); + +#endif // JSON_LIBRARY_HEADER + +#if defined(JSON_LIBRARY_IMPL) || defined(JSON_LIBRARY_MAIN) +#include +#include +#include +#include +#include + +#define JSON_ERROR_CAP 512 + +typedef struct json_parse_ctx_t json_parse_ctx_t; +struct json_parse_ctx_t { + const char *start; + const char *at; + int line; + int column; + int has_error; + char error[JSON_ERROR_CAP]; + json_t *result; +}; + +typedef struct json_sb_t json_sb_t; +struct json_sb_t { + char *data; + int len; + int cap; +}; + +static void *json_realloc(void *p, size_t n) { + void *r = realloc(p, n); + if (!r && n) { fprintf(stderr, "out of memory\n"); exit(1); } + return r; +} + +static json_t *json_new(json_kind_t kind) { + json_t *n = (json_t *)calloc(1, sizeof(json_t)); + if (!n) { fprintf(stderr, "out of memory\n"); exit(1); } + n->kind = kind; + return n; +} + +static void json_set_error(json_parse_ctx_t *ctx, const char *fmt, ...) { + if (ctx->has_error) return; + ctx->has_error = 1; + + int off = snprintf(ctx->error, sizeof(ctx->error), + "line %d, column %d: ", ctx->line, ctx->column); + if (off < 0) { + ctx->error[0] = 0; + return; + } + if (off >= (int)sizeof(ctx->error)) off = (int)sizeof(ctx->error) - 1; + + va_list ap; + va_start(ap, fmt); + vsnprintf(ctx->error + off, sizeof(ctx->error) - (size_t)off, fmt, ap); + va_end(ap); + ctx->error[sizeof(ctx->error) - 1] = 0; +} + +static void json_advance(json_parse_ctx_t *ctx) { + if (*ctx->at == '\n') { + ctx->line += 1; + ctx->column = 1; + } else { + ctx->column += 1; + } + ctx->at += 1; +} + +static void json_skip_ws(json_parse_ctx_t *ctx) { + while (*ctx->at && isspace((unsigned char)*ctx->at)) json_advance(ctx); +} + +static int json_match(json_parse_ctx_t *ctx, const char *s) { + const char *p = ctx->at; + while (*s) { + if (*p++ != *s++) return 0; + } + while (ctx->at < p) json_advance(ctx); + return 1; +} + +static void sb_init(json_sb_t *sb) { + sb->cap = 128; + sb->len = 0; + sb->data = (char *)json_realloc(NULL, sb->cap); + sb->data[0] = 0; +} + +static void sb_reserve(json_sb_t *sb, int extra) { + int need = sb->len + extra + 1; + if (need <= sb->cap) return; + while (sb->cap < need) sb->cap *= 2; + sb->data = (char *)json_realloc(sb->data, sb->cap); +} + +static void sb_putc(json_sb_t *sb, char c) { + sb_reserve(sb, 1); + sb->data[sb->len++] = c; + sb->data[sb->len] = 0; +} + +static void sb_puts(json_sb_t *sb, const char *s) { + int n = (int)strlen(s); + sb_reserve(sb, n); + memcpy(sb->data + sb->len, s, n + 1); + sb->len += n; +} + +static void sb_printf(json_sb_t *sb, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + va_list ap2; + va_copy(ap2, ap); + int n = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + sb_reserve(sb, n); + vsnprintf(sb->data + sb->len, sb->cap - sb->len, fmt, ap2); + va_end(ap2); + sb->len += n; +} + +static void sb_put_utf8(json_sb_t *sb, unsigned code) { + if (code <= 0x7f) { + sb_putc(sb, (char)code); + } else if (code <= 0x7ff) { + sb_putc(sb, (char)(0xc0 | (code >> 6))); + sb_putc(sb, (char)(0x80 | (code & 0x3f))); + } else { + sb_putc(sb, (char)(0xe0 | (code >> 12))); + sb_putc(sb, (char)(0x80 | ((code >> 6) & 0x3f))); + sb_putc(sb, (char)(0x80 | (code & 0x3f))); + } +} + +static int hexval(char c) { + if ('0' <= c && c <= '9') return c - '0'; + if ('a' <= c && c <= 'f') return c - 'a' + 10; + if ('A' <= c && c <= 'F') return c - 'A' + 10; + return -1; +} + +static json_t *json_parse_value(json_parse_ctx_t *ctx); + +static char *json_parse_string(json_parse_ctx_t *ctx) { + if (*ctx->at != '"') { json_set_error(ctx, "expected string"); return NULL; } + json_advance(ctx); + json_sb_t sb; + sb_init(&sb); + + while (*ctx->at && *ctx->at != '"') { + unsigned char c = (unsigned char)*ctx->at; + if (c < 0x20) { json_set_error(ctx, "control character in string"); free(sb.data); return NULL; } + if (c == '\\') { + json_advance(ctx); + switch (*ctx->at) { + case '"': sb_putc(&sb, '"'); json_advance(ctx); break; + case '\\': sb_putc(&sb, '\\'); json_advance(ctx); break; + case '/': sb_putc(&sb, '/'); json_advance(ctx); break; + case 'b': sb_putc(&sb, '\b'); json_advance(ctx); break; + case 'f': sb_putc(&sb, '\f'); json_advance(ctx); break; + case 'n': sb_putc(&sb, '\n'); json_advance(ctx); break; + case 'r': sb_putc(&sb, '\r'); json_advance(ctx); break; + case 't': sb_putc(&sb, '\t'); json_advance(ctx); break; + case 'u': { + json_advance(ctx); + unsigned code = 0; + for (int i = 0; i < 4; i += 1) { + int h = hexval(*ctx->at); + if (h < 0) { json_set_error(ctx, "bad unicode escape"); free(sb.data); return NULL; } + code = (code << 4) | (unsigned)h; + json_advance(ctx); + } + sb_put_utf8(&sb, code); + } break; + default: json_set_error(ctx, "bad escape sequence"); free(sb.data); return NULL; + } + } else { + sb_putc(&sb, (char)c); + json_advance(ctx); + } + } + if (*ctx->at != '"') { json_set_error(ctx, "unterminated string"); free(sb.data); return NULL; } + json_advance(ctx); + return sb.data; +} + +static json_t *json_parse_number(json_parse_ctx_t *ctx) { + char *end = NULL; + double v = strtod(ctx->at, &end); + if (end == ctx->at) { json_set_error(ctx, "expected number"); return NULL; } + while (ctx->at < end) json_advance(ctx); + json_t *n = json_new(JSON_NUMBER); + n->number = v; + return n; +} + +static void json_array_push(json_array_t *a, json_t *v) { + if (a->len == a->cap) { + a->cap = a->cap ? a->cap * 2 : 8; + a->data = (json_t **)json_realloc(a->data, sizeof(json_t *) * a->cap); + } + a->data[a->len++] = v; +} + +static void json_object_put(json_object_t *o, char *key, json_t *value) { + if (o->len == o->cap) { + o->cap = o->cap ? o->cap * 2 : 8; + o->data = (json_kv_t *)json_realloc(o->data, sizeof(json_kv_t) * o->cap); + } + o->data[o->len].key = key; + o->data[o->len].value = value; + o->len += 1; +} + +static int json_parse_next(json_parse_ctx_t *ctx, char close, const char *what) { + json_skip_ws(ctx); + if (*ctx->at == close) { json_advance(ctx); return 0; } + if (*ctx->at != ',') { json_set_error(ctx, "expected ',' or '%c' in %s", close, what); return -1; } + json_advance(ctx); + json_skip_ws(ctx); + return 1; +} + +static json_t *json_parse_array(json_parse_ctx_t *ctx) { + json_t *n = json_new(JSON_ARRAY); + json_advance(ctx); // [ + json_skip_ws(ctx); + if (*ctx->at == ']') { json_advance(ctx); return n; } + for (;;) { + json_t *v = json_parse_value(ctx); + if (!v) return n; + json_array_push(&n->array, v); + int next = json_parse_next(ctx, ']', "array"); + if (next <= 0) return n; + } +} + +static json_t *json_parse_object(json_parse_ctx_t *ctx) { + json_t *n = json_new(JSON_OBJECT); + json_advance(ctx); // { + json_skip_ws(ctx); + if (*ctx->at == '}') { json_advance(ctx); return n; } + for (;;) { + if (*ctx->at != '"') { json_set_error(ctx, "expected object key string"); return n; } + char *key = json_parse_string(ctx); + if (!key) return n; + json_skip_ws(ctx); + if (*ctx->at != ':') { json_set_error(ctx, "expected ':' after object key"); free(key); return n; } + json_advance(ctx); + json_t *value = json_parse_value(ctx); + if (!value) { free(key); return n; } + json_object_put(&n->object, key, value); + int next = json_parse_next(ctx, '}', "object"); + if (next <= 0) return n; + } +} + +static json_t *json_parse_value(json_parse_ctx_t *ctx) { + json_skip_ws(ctx); + switch (*ctx->at) { + case 'n': + if (json_match(ctx, "null")) return json_new(JSON_NULL); + break; + case 't': + if (json_match(ctx, "true")) { json_t *n = json_new(JSON_BOOL); n->boolean = 1; return n; } + break; + case 'f': + if (json_match(ctx, "false")) { json_t *n = json_new(JSON_BOOL); n->boolean = 0; return n; } + break; + case '"': { + json_t *n = json_new(JSON_STRING); + n->string = json_parse_string(ctx); + if (!n->string) { free(n); return NULL; } + return n; + } + case '[': return json_parse_array(ctx); + case '{': return json_parse_object(ctx); + default: + if (*ctx->at == '-' || isdigit((unsigned char)*ctx->at)) return json_parse_number(ctx); + break; + } + json_set_error(ctx, "expected JSON value"); + return NULL; +} + +static void json_parse_ex(json_parse_ctx_t *ctx) { + ctx->line = ctx->line ? ctx->line : 1; + ctx->column = ctx->column ? ctx->column : 1; + ctx->result = json_parse_value(ctx); + if (ctx->has_error) return; + json_skip_ws(ctx); + if (*ctx->at) json_set_error(ctx, "unexpected trailing data"); +} + +JSON_API json_t *json_parse(const char *stream) { + json_parse_ctx_t ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.start = stream; + ctx.at = stream; + ctx.line = 1; + ctx.column = 1; + json_parse_ex(&ctx); + if (ctx.has_error) { + fprintf(stderr, "json parse error: %s\n", ctx.error); + json_free(ctx.result); + return NULL; + } + return ctx.result; +} + +static void json_escape_string(json_sb_t *sb, const char *s) { + sb_putc(sb, '"'); + for (; *s; s += 1) { + unsigned char c = (unsigned char)*s; + switch (c) { + case '"': sb_puts(sb, "\\\""); break; + case '\\': sb_puts(sb, "\\\\"); break; + case '\b': sb_puts(sb, "\\b"); break; + case '\f': sb_puts(sb, "\\f"); break; + case '\n': sb_puts(sb, "\\n"); break; + case '\r': sb_puts(sb, "\\r"); break; + case '\t': sb_puts(sb, "\\t"); break; + default: + if (c < 0x20) sb_printf(sb, "\\u%04x", c); + else sb_putc(sb, (char)c); + } + } + sb_putc(sb, '"'); +} + +static void json_indent(json_sb_t *sb, const char *nl, const char *indent, int depth) { + if (!nl) return; + sb_puts(sb, nl); + for (int i = 0; i < depth; i += 1) sb_puts(sb, indent); +} + +static void json_serialize_into(json_sb_t *sb, json_t *n, const char *nl, const char *indent, int depth) { + if (!n) { sb_puts(sb, "null"); return; } + switch (n->kind) { + case JSON_NULL: sb_puts(sb, "null"); break; + case JSON_BOOL: sb_puts(sb, n->boolean ? "true" : "false"); break; + case JSON_NUMBER: sb_printf(sb, "%.17g", n->number); break; + case JSON_STRING: json_escape_string(sb, n->string ? n->string : ""); break; + case JSON_ARRAY: + sb_putc(sb, '['); + for (int i = 0; i < n->array.len; i += 1) { + if (i) sb_putc(sb, ','); + json_indent(sb, nl, indent, depth + 1); + json_serialize_into(sb, n->array.data[i], nl, indent, depth + 1); + } + if (n->array.len) json_indent(sb, nl, indent, depth); + sb_putc(sb, ']'); + break; + case JSON_OBJECT: + sb_putc(sb, '{'); + for (int i = 0; i < n->object.len; i += 1) { + if (i) sb_putc(sb, ','); + json_indent(sb, nl, indent, depth + 1); + json_escape_string(sb, n->object.data[i].key); + sb_putc(sb, ':'); + if (nl) sb_putc(sb, ' '); + json_serialize_into(sb, n->object.data[i].value, nl, indent, depth + 1); + } + if (n->object.len) json_indent(sb, nl, indent, depth); + sb_putc(sb, '}'); + break; + } +} + +JSON_API char *json_serialize_compact(json_t *n) { + json_sb_t sb; + sb_init(&sb); + json_serialize_into(&sb, n, NULL, NULL, 0); + return sb.data; +} + +JSON_API char *json_serialize_pretty(json_t *n, const char *new_line, const char *indent) { + json_sb_t sb; + sb_init(&sb); + json_serialize_into(&sb, n, new_line ? new_line : "\n", indent ? indent : " ", 0); + return sb.data; +} + +JSON_API void json_print(json_t *n) { + char *s = json_serialize_pretty(n, "\n", " "); + puts(s); + free(s); +} + +static const char *json_kind_name(json_kind_t kind) { + switch (kind) { + case JSON_NULL: return "null"; + case JSON_BOOL: return "bool"; + case JSON_ARRAY: return "array"; + case JSON_OBJECT: return "object"; + case JSON_NUMBER: return "number"; + case JSON_STRING: return "string"; + } + return "unknown"; +} + +static void json_summary_indent(json_sb_t *sb, int depth) { + for (int i = 0; i < depth; i += 1) sb_puts(sb, " "); +} + +static void json_summary_string(json_sb_t *sb, const char *s) { + int limit = 60; + sb_putc(sb, '"'); + for (int i = 0; s && s[i] && i < limit; i += 1) { + unsigned char c = (unsigned char)s[i]; + switch (c) { + case '"': sb_puts(sb, "\\\""); break; + case '\\': sb_puts(sb, "\\\\"); break; + case '\n': sb_puts(sb, "\\n"); break; + case '\r': sb_puts(sb, "\\r"); break; + case '\t': sb_puts(sb, "\\t"); break; + default: if (c < 0x20) sb_printf(sb, "\\u%04x", c); else sb_putc(sb, (char)c); + } + } + if (s && (int)strlen(s) > limit) sb_puts(sb, "..."); + sb_putc(sb, '"'); +} + +static int json_summary_kind_count(json_t *n, int counts[6]) { + int kinds = 0; + memset(counts, 0, sizeof(int) * 6); + if (!n || n->kind != JSON_ARRAY) return 0; + for (int i = 0; i < n->array.len; i += 1) { + json_kind_t k = n->array.data[i] ? n->array.data[i]->kind : JSON_NULL; + if (counts[k]++ == 0) kinds += 1; + } + return kinds; +} + +static void json_summarize_into(json_sb_t *sb, json_t *n, int depth, int max_depth); + +static void json_summarize_array(json_sb_t *sb, json_t *n, int depth, int max_depth) { + int counts[6]; + int kinds = json_summary_kind_count(n, counts); + sb_printf(sb, "array[%d]", n->array.len); + if (n->array.len == 0) { sb_putc(sb, '\n'); return; } + + if (kinds == 1 && counts[JSON_NUMBER]) { + double min = n->array.data[0]->number, max = min, sum = 0.0; + for (int i = 0; i < n->array.len; i += 1) { + double v = n->array.data[i]->number; + if (v < min) min = v; + if (v > max) max = v; + sum += v; + } + sb_printf(sb, " of number { min: %.17g, max: %.17g, avg: %.17g, first: %.17g, last: %.17g }\n", + min, max, sum / n->array.len, n->array.data[0]->number, n->array.data[n->array.len - 1]->number); + return; + } + + if (kinds <= 2 && counts[JSON_NUMBER] && counts[JSON_NULL]) { + int nums = 0; + double min = 0.0, max = 0.0, sum = 0.0, first = 0.0, last = 0.0; + for (int i = 0; i < n->array.len; i += 1) if (n->array.data[i] && n->array.data[i]->kind == JSON_NUMBER) { + double v = n->array.data[i]->number; + if (nums == 0) min = max = first = v; + if (v < min) min = v; + if (v > max) max = v; + sum += v; + last = v; + nums += 1; + } + sb_printf(sb, " of number|null { numbers: %d, nulls: %d, min: %.17g, max: %.17g, avg: %.17g, first-number: %.17g, last-number: %.17g }\n", + nums, counts[JSON_NULL], min, max, nums ? sum / nums : 0.0, first, last); + return; + } + + if (kinds == 1) sb_printf(sb, " of %s", json_kind_name(n->array.data[0] ? n->array.data[0]->kind : JSON_NULL)); + else { + sb_puts(sb, " mixed {"); + for (int k = 0; k < 6; k += 1) if (counts[k]) sb_printf(sb, " %s:%d", json_kind_name((json_kind_t)k), counts[k]); + sb_puts(sb, " }"); + } + + if (depth >= max_depth) { sb_puts(sb, " { ... }\n"); return; } + sb_puts(sb, " {\n"); + int samples = n->array.len < 3 ? n->array.len : 3; + for (int i = 0; i < samples; i += 1) { + json_summary_indent(sb, depth + 1); + sb_printf(sb, "[%d]: ", i); + json_summarize_into(sb, n->array.data[i], depth + 1, max_depth); + } + if (n->array.len > samples) { + json_summary_indent(sb, depth + 1); + sb_printf(sb, "... %d more\n", n->array.len - samples); + } + json_summary_indent(sb, depth); + sb_puts(sb, "}\n"); +} + +static void json_summarize_into(json_sb_t *sb, json_t *n, int depth, int max_depth) { + if (!n) { sb_puts(sb, "null\n"); return; } + switch (n->kind) { + case JSON_NULL: sb_puts(sb, "null\n"); break; + case JSON_BOOL: sb_printf(sb, "bool %s\n", n->boolean ? "true" : "false"); break; + case JSON_NUMBER: sb_printf(sb, "number %.17g\n", n->number); break; + case JSON_STRING: sb_puts(sb, "string "); json_summary_string(sb, n->string ? n->string : ""); sb_putc(sb, '\n'); break; + case JSON_ARRAY: json_summarize_array(sb, n, depth, max_depth); break; + case JSON_OBJECT: { + sb_printf(sb, "object[%d]", n->object.len); + if (depth >= max_depth) { sb_puts(sb, " { ... }\n"); break; } + sb_puts(sb, " {\n"); + int limit = n->object.len < 32 ? n->object.len : 32; + for (int i = 0; i < limit; i += 1) { + json_summary_indent(sb, depth + 1); + sb_puts(sb, n->object.data[i].key); + sb_puts(sb, ": "); + json_summarize_into(sb, n->object.data[i].value, depth + 1, max_depth); + } + if (n->object.len > limit) { + json_summary_indent(sb, depth + 1); + sb_printf(sb, "... %d more keys\n", n->object.len - limit); + } + json_summary_indent(sb, depth); + sb_puts(sb, "}\n"); + } break; + } +} + +JSON_API char *json_summarize(json_t *n) { + json_sb_t sb; + sb_init(&sb); + json_summarize_into(&sb, n, 0, 8); + return sb.data; +} + +JSON_API json_t *json_object_get(json_t *n, const char *key) { + if (!n || n->kind != JSON_OBJECT) return NULL; + for (int i = 0; i < n->object.len; i += 1) { + if (strcmp(n->object.data[i].key, key) == 0) return n->object.data[i].value; + } + return NULL; +} + +static json_t *json_object_get_n(json_t *n, const char *key, size_t key_len) { + if (!n || n->kind != JSON_OBJECT) return NULL; + for (int i = 0; i < n->object.len; i += 1) { + const char *candidate = n->object.data[i].key; + if (strlen(candidate) == key_len && memcmp(candidate, key, key_len) == 0) { + return n->object.data[i].value; + } + } + return NULL; +} + +JSON_API json_t *json_array_get(json_t *n, int idx) { + if (!n || n->kind != JSON_ARRAY || idx < 0 || idx >= n->array.len) return NULL; + return n->array.data[idx]; +} + +static json_t *jqv(json_t *n, const char *fmt, va_list ap) { + va_list ap2; + va_copy(ap2, ap); + int path_len = vsnprintf(NULL, 0, fmt, ap2); + va_end(ap2); + if (path_len < 0) return NULL; + + char *path = (char *)json_realloc(NULL, (size_t)path_len + 1); + vsnprintf(path, (size_t)path_len + 1, fmt, ap); + + const char *p = path; + json_t *cur = n; + + while (cur && *p) { + if (*p == '.') { p += 1; continue; } + if (*p == '[') { + p += 1; + int idx = (int)strtol(p, (char **)&p, 10); + if (*p++ != ']') { cur = NULL; break; } + cur = json_array_get(cur, idx); + } else { + const char *key = p; + while (*p && *p != '.' && *p != '[') p += 1; + cur = json_object_get_n(cur, key, (size_t)(p - key)); + } + } + + free(path); + return cur; +} + +// Query with dotted object fields and bracket array indexes: root.items[0].name +JSON_API json_t *jq(json_t *n, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + json_t *r = jqv(n, fmt, ap); + va_end(ap); + return r; +} + +JSON_API double jqn(json_t *n, const char *fmt, ...) { // extract number from query + va_list ap; + va_start(ap, fmt); + json_t *r = jqv(n, fmt, ap); + va_end(ap); + return (r && r->kind == JSON_NUMBER) ? r->number : 0.0; +} + +JSON_API char *jqs(json_t *n, const char *fmt, ...) { // extract string from query + va_list ap; + va_start(ap, fmt); + json_t *r = jqv(n, fmt, ap); + va_end(ap); + return (r && r->kind == JSON_STRING) ? r->string : NULL; +} + +JSON_API int jql(json_t *n, const char *fmt, ...) { // extract len of array or object from query + va_list ap; + va_start(ap, fmt); + json_t *r = jqv(n, fmt, ap); + va_end(ap); + if (!r) return 0; + if (r->kind == JSON_ARRAY) return r->array.len; + if (r->kind == JSON_OBJECT) return r->object.len; + return 0; +} + +JSON_API void json_free(json_t *n) { + if (!n) return; + switch (n->kind) { + case JSON_STRING: free(n->string); break; + case JSON_ARRAY: + for (int i = 0; i < n->array.len; i += 1) json_free(n->array.data[i]); + free(n->array.data); + break; + case JSON_OBJECT: + for (int i = 0; i < n->object.len; i += 1) { + free(n->object.data[i].key); + json_free(n->object.data[i].value); + } + free(n->object.data); + break; + default: break; + } + free(n); +} + +JSON_API int json_equal(json_t *a, json_t *b) { + if (a == b) return 1; + if (!a || !b || a->kind != b->kind) return 0; + + switch (a->kind) { + case JSON_NULL: return 1; + case JSON_BOOL: return a->boolean == b->boolean; + case JSON_NUMBER: return a->number == b->number; + case JSON_STRING: return strcmp(a->string ? a->string : "", b->string ? b->string : "") == 0; + case JSON_ARRAY: + if (a->array.len != b->array.len) return 0; + for (int i = 0; i < a->array.len; i += 1) { + if (!json_equal(a->array.data[i], b->array.data[i])) return 0; + } + return 1; + case JSON_OBJECT: + if (a->object.len != b->object.len) return 0; + for (int i = 0; i < a->object.len; i += 1) { + // Serialization preserves object member order, so compare in order. + if (strcmp(a->object.data[i].key, b->object.data[i].key) != 0) return 0; + if (!json_equal(a->object.data[i].value, b->object.data[i].value)) return 0; + } + return 1; + } + return 0; +} + +static char *json_read_entire_file(const char *path) { + FILE *f = fopen(path, "rb"); + if (!f) return NULL; + + if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; } + long size = ftell(f); + if (size < 0) { fclose(f); return NULL; } + rewind(f); + + char *data = (char *)json_realloc(NULL, (size_t)size + 1); + size_t got = fread(data, 1, (size_t)size, f); + fclose(f); + data[got] = 0; + return data; +} + +JSON_API json_t *json_read(const char *filename) { + char *stream = json_read_entire_file(filename); + if (!stream) return NULL; + json_t *result = json_parse(stream); + free(stream); + return result; +} + +#endif +#ifdef JSON_LIBRARY_MAIN + +int test_read_acwi(void) { + printf("\n-- test_read_acwi --\n"); + + char *stream = json_read_entire_file("acwi.json"); + if (!stream) { + fprintf(stderr, "could not read ./acwi.json\n"); + return 0; + } + + json_t *root = json_parse(stream); + free(stream); + if (!root) return 0; + + char *serialized = json_serialize_compact(root); + json_t *roundtrip = json_parse(serialized); + if (!roundtrip) { + fprintf(stderr, "could not parse serialized acwi json\n"); + free(serialized); + json_free(root); + return 0; + } + if (!json_equal(root, roundtrip)) { + fprintf(stderr, "acwi json was not equal after parse -> serialize -> parse\n"); + free(serialized); + json_free(roundtrip); + json_free(root); + return 0; + } + printf("roundtrip parse/serialize/parse equality: ok (%d bytes serialized)\n", (int)strlen(serialized)); + free(serialized); + json_free(roundtrip); + + char *symbol = jqs(root, "chart.result[0].meta.symbol"); + char *name = jqs(root, "chart.result[0].meta.longName"); + double market_price = jqn(root, "chart.result[0].meta.regularMarketPrice"); + int timestamp_count = jql(root, "chart.result[0].timestamp"); + + json_t *closes = jq(root, "chart.result[0].indicators.quote[0].close"); + if (!symbol || strcmp(symbol, "ACWI") != 0 || !closes || closes->kind != JSON_ARRAY || timestamp_count <= 0) { + fprintf(stderr, "acwi json did not have the expected Yahoo chart shape\n"); + json_free(root); + return 0; + } + + double min_close = 0.0, max_close = 0.0, last_close = 0.0, sum_close = 0.0; + int close_count = 0; + for (int i = 0; i < closes->array.len; i += 1) { + json_t *it = closes->array.data[i]; + if (!it || it->kind != JSON_NUMBER) continue; // Yahoo may include nulls. + double v = it->number; + if (close_count == 0 || v < min_close) min_close = v; + if (close_count == 0 || v > max_close) max_close = v; + last_close = v; + sum_close += v; + close_count += 1; + } + + if (close_count == 0) { + fprintf(stderr, "acwi close array had no numeric prices\n"); + json_free(root); + return 0; + } + + printf("symbol: %s\n", symbol); + printf("name: %s\n", name ? name : "(missing)"); + printf("regularMarketPrice: %.2f\n", market_price); + printf("timestamps: %d, closes: %d numeric / %d total\n", timestamp_count, close_count, closes->array.len); + printf("close min/avg/max/last: %.2f / %.2f / %.2f / %.2f\n", + min_close, sum_close / close_count, max_close, last_close); + + json_free(root); + return 1; +} + +int test_simple_json(void) { + printf("\n-- test_simple_json --\n"); + + const char *json = + "{" + "\"name\":\"example\"," + "\"version\":1," + "\"enabled\":true," + "\"items\":[\"alpha\",\"beta\",\"gamma\"]," + "\"config\":{" + "\"timeout_ms\":5000," + "\"retry_count\":3" + "}," + "\"message\":\"Hello, \\\"world\\\"!\"" + "}"; + json_t *n = json_parse(json); + if (!n) return 0; + + for (int i = 0; i < n->object.len; i += 1) { + json_kv_t *it = n->object.data + i; + printf("%s\n", it->key); + } + + char *name = jqs(n, "name"); + double version = jqn(n, "version"); + double timeout = jqn(n, "config.timeout_ms"); + printf("name=%s version=%.0f timeout=%.0f\n", name, version, timeout); + + printf("items = {\n"); + for (int i = 0; i < jql(n, "items"); i += 1) { + char *it = jqs(n, "items[%d]", i); + printf(" %s\n", it); + } + printf("}\n"); + + char *pretty = json_serialize_pretty(n, "\n", " "); + puts(pretty); + free(pretty); + json_free(n); + return 1; +} + +int test_large_parse_error_buffer(void) { + printf("\n-- test_large_parse_error_buffer --\n"); + + size_t len = 2048; + char *json = (char *)json_realloc(NULL, len + 3); + json[0] = '"'; + memset(json + 1, 'A', len); + json[len + 1] = 0; // Deliberately no closing quote. + + json_t *n = json_parse(json); + free(json); + if (n) { + fprintf(stderr, "unterminated string larger than 512 bytes parsed unexpectedly\n"); + json_free(n); + return 0; + } + + printf("large unterminated string parse error: ok\n"); + return 1; +} + +int test_large_query_key(void) { + printf("\n-- test_large_query_key --\n"); + + size_t key_len = 768; + char *json = (char *)json_realloc(NULL, key_len + 32); + char *query = (char *)json_realloc(NULL, key_len + 1); + memset(query, 'k', key_len); + query[key_len] = 0; + + json[0] = '{'; + json[1] = '"'; + memcpy(json + 2, query, key_len); + memcpy(json + 2 + key_len, "\":12345}", 9); + + json_t *root = json_parse(json); + if (!root) { + fprintf(stderr, "could not parse object with key larger than 512 bytes\n"); + free(query); + free(json); + return 0; + } + + double value = jqn(root, "%s", query); + int ok = value == 12345.0; + if (!ok) fprintf(stderr, "query with key larger than 512 bytes failed\n"); + + json_free(root); + free(query); + free(json); + printf("large query key: %s\n", ok ? "ok" : "failed"); + return ok; +} + +char *json_read_stream(FILE *f) { + json_sb_t sb; + sb_init(&sb); + char buf[4096]; + for (;;) { + size_t n = fread(buf, 1, sizeof(buf), f); + if (n > 0) { + sb_reserve(&sb, (int)n); + memcpy(sb.data + sb.len, buf, n); + sb.len += (int)n; + sb.data[sb.len] = 0; + } + if (n < sizeof(buf)) { + if (ferror(f)) { free(sb.data); return NULL; } + break; + } + } + return sb.data; +} + +void usage(const char *argv0) { + fprintf(stderr, + "usage:\n" + " %s --test\n" + " %s [query] [file]\n" + " %s --summary [query] [file]\n" + "\n" + "Reads JSON from file, or stdin when file is omitted.\n" + "Query examples: name, items[0], chart.result[0].meta.symbol\n", + argv0, argv0, argv0); +} + +int run_tests(void) { + int ok = 1; + ok = test_simple_json() && ok; + ok = test_large_parse_error_buffer() && ok; + ok = test_large_query_key() && ok; + ok = test_read_acwi() && ok; + printf("\n%s\n", ok ? "all tests passed" : "some tests failed"); + return ok ? 0 : 1; +} + +int main(int argc, char **argv) { + if (argc >= 2 && strcmp(argv[1], "--test") == 0) return run_tests(); + + int summary = 0; + int argi = 1; + if (argc >= 2 && strcmp(argv[1], "--summary") == 0) { + summary = 1; + argi = 2; + } + + if (argc > argi + 2 || (argc >= 2 && strcmp(argv[1], "--help") == 0)) { + usage(argv[0]); + return argc > argi + 2 ? 1 : 0; + } + + const char *query = argc > argi ? argv[argi] : NULL; + const char *path = argc > argi + 1 ? argv[argi + 1] : NULL; + + char *stream = NULL; + if (path) { + stream = json_read_entire_file(path); + } else if (summary && query) { + stream = json_read_entire_file(query); + if (stream) { path = query; query = NULL; } + } + if (!stream) stream = json_read_stream(stdin); + if (!stream) { + fprintf(stderr, "could not read %s\n", path ? path : "stdin"); + return 1; + } + + json_t *root = json_parse(stream); + free(stream); + if (!root) return 1; + + json_t *selected = query && *query ? jq(root, "%s", query) : root; + if (!selected) { + fprintf(stderr, "query did not match: %s\n", query); + json_free(root); + return 1; + } + + char *pretty = summary ? json_summarize(selected) : json_serialize_pretty(selected, "\n", " "); + fputs(pretty, stdout); + if (!summary) putchar('\n'); + free(pretty); + json_free(root); + return 0; +} + +#endif diff --git a/b/src/main.c b/b/src/main.c new file mode 100644 index 0000000..d5bfaca --- /dev/null +++ b/b/src/main.c @@ -0,0 +1,382 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define SDL_MAIN_USE_CALLBACKS +#include +#include +#include + +#define unused(x) ((void)x) + +#include "plot.h" +#define JSON_LIBRARY_IMPL +#include "json.c" +#include "plot.c" + +typedef struct AppState { + SDL_Window *window; + SDL_Renderer *renderer; + TTF_Font *font; + json_t *acwi; + PlotPoint *raw_prices; + PlotPoint *daily_returns; + PlotPoint *cumulative_returns; + PlotPoint *annualized_volatility; + int raw_price_count; + Plot plots[4]; + int plot_count; + int active_plot; +} AppState; + +static void dashboard_layout(SDL_FRect *rects, int rect_count, int window_width, int window_height) { + int cols = 2; + float gap = 12.0f; + float margin = 12.0f; + float cell_w = ((float)window_width - margin * 2.0f - gap) / 2.0f; + float cell_h = ((float)window_height - margin * 2.0f - gap) / 2.0f; + + for (int i = 0; i < rect_count; i += 1) { + int row = i / cols; + int col = i % cols; + rects[i] = (SDL_FRect) { + .x = margin + (float)col * (cell_w + gap), + .y = margin + (float)row * (cell_h + gap), + .w = cell_w, + .h = cell_h, + }; + } +} + +// This is very inefficient: every call rasterizes text to a CPU surface, +// uploads it to a GPU texture, renders it, then destroys both objects. +// We only draw a small number of labels, so it should be fine for now. +static void draw_text(SDL_Renderer *renderer, TTF_Font *font, const char *text, float x, float y, float anchor_x, float anchor_y, SDL_Color color) { + SDL_Surface *surface = TTF_RenderText_Blended(font, text, strlen(text), color); + if (surface == NULL) { + SDL_Log("Could not render text '%s': %s", text, SDL_GetError()); + return; + } + + SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface); + if (texture == NULL) { + SDL_Log("Could not create text texture '%s': %s", text, SDL_GetError()); + SDL_DestroySurface(surface); + return; + } + + SDL_FRect dst = { + .x = x - (float)surface->w * anchor_x, + .y = y - (float)surface->h * anchor_y, + .w = (float)surface->w, + .h = (float)surface->h, + }; + SDL_RenderTexture(renderer, texture, NULL, &dst); + + SDL_DestroyTexture(texture); + SDL_DestroySurface(surface); +} + +typedef struct PlotBounds { + double min_x; + double max_x; + double min_y; + double max_y; +} PlotBounds; + +static void bounds_include(PlotBounds *bounds, double x, double y) { + if (x < bounds->min_x) bounds->min_x = x; + if (x > bounds->max_x) bounds->max_x = x; + if (y < bounds->min_y) bounds->min_y = y; + if (y > bounds->max_y) bounds->max_y = y; +} + +static void add_plot_point(PlotPoint *points, int *count, PlotBounds *bounds, double x, double y) { + points[*count] = (PlotPoint){.x = x, .y = y}; + *count += 1; + bounds_include(bounds, x, y); +} + +static void free_plot_arrays(PlotPoint *raw_prices, PlotPoint *daily_returns, PlotPoint *cumulative_returns, PlotPoint *annualized_volatility, double *returns) { + free(raw_prices); + free(daily_returns); + free(cumulative_returns); + free(annualized_volatility); + free(returns); +} + +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { + unused(argc); + unused(argv); + + json_t *acwi = json_read("acwi.json"); + if (acwi == NULL) { + SDL_Log("Could not read acwi.json"); + return SDL_APP_FAILURE; + } + + json_t *timestamps = jq(acwi, "chart.result[0].timestamp"); + json_t *closes = jq(acwi, "chart.result[0].indicators.quote[0].close"); + if (timestamps == NULL || closes == NULL || timestamps->kind != JSON_ARRAY || closes->kind != JSON_ARRAY) { + SDL_Log("Could not find timestamp/close arrays in acwi.json"); + json_free(acwi); + return SDL_APP_FAILURE; + } + + int len = timestamps->array.len < closes->array.len ? timestamps->array.len : closes->array.len; + PlotPoint *raw_prices = calloc((size_t)len, sizeof(*raw_prices)); + PlotPoint *daily_returns = calloc((size_t)len, sizeof(*daily_returns)); + PlotPoint *cumulative_returns = calloc((size_t)len, sizeof(*cumulative_returns)); + PlotPoint *annualized_volatility = calloc((size_t)len, sizeof(*annualized_volatility)); + double *returns = calloc((size_t)len, sizeof(*returns)); + if (raw_prices == NULL || daily_returns == NULL || cumulative_returns == NULL || annualized_volatility == NULL || returns == NULL) { + SDL_Log("Could not allocate plot data"); + free_plot_arrays(raw_prices, daily_returns, cumulative_returns, annualized_volatility, returns); + json_free(acwi); + return SDL_APP_FAILURE; + } + + PlotBounds raw_bounds = {DBL_MAX, -DBL_MAX, DBL_MAX, -DBL_MAX}; + PlotBounds daily_bounds = {DBL_MAX, -DBL_MAX, DBL_MAX, -DBL_MAX}; + PlotBounds cumulative_bounds = {DBL_MAX, -DBL_MAX, DBL_MAX, -DBL_MAX}; + PlotBounds volatility_bounds = {DBL_MAX, -DBL_MAX, DBL_MAX, -DBL_MAX}; + int raw_count = 0; + int daily_count = 0; + int cumulative_count = 0; + int volatility_count = 0; + int return_count = 0; + bool have_previous_close = false; + double previous_close = 0.0; + double first_close = 0.0; + const int volatility_window = 30; + + for (int i = 0; i < len; i += 1) { + json_t *x_node = timestamps->array.data[i]; + json_t *close_node = closes->array.data[i]; + if (x_node->kind != JSON_NUMBER || close_node->kind != JSON_NUMBER) { + continue; + } + + double x = x_node->number; + double close = close_node->number; + add_plot_point(raw_prices, &raw_count, &raw_bounds, x, close); + + if (!have_previous_close) { + first_close = close; + add_plot_point(cumulative_returns, &cumulative_count, &cumulative_bounds, x, 0.0); + previous_close = close; + have_previous_close = true; + continue; + } + + double daily_return = close / previous_close - 1.0; + double cumulative_return = close / first_close - 1.0; + returns[return_count] = daily_return; + return_count += 1; + + add_plot_point(daily_returns, &daily_count, &daily_bounds, x, daily_return * 100.0); + add_plot_point(cumulative_returns, &cumulative_count, &cumulative_bounds, x, cumulative_return * 100.0); + + if (return_count >= volatility_window) { + double sum = 0.0; + for (int j = return_count - volatility_window; j < return_count; j += 1) { + sum += returns[j]; + } + double mean = sum / (double)volatility_window; + double variance = 0.0; + for (int j = return_count - volatility_window; j < return_count; j += 1) { + double d = returns[j] - mean; + variance += d * d; + } + variance /= (double)(volatility_window - 1); + double annualized = sqrt(variance) * sqrt(252.0) * 100.0; + add_plot_point(annualized_volatility, &volatility_count, &volatility_bounds, x, annualized); + } + + previous_close = close; + } + + free(returns); + returns = NULL; + + if (raw_count < 2 || daily_count < 2 || cumulative_count < 2 || volatility_count < 2) { + SDL_Log("Not enough close data to build all plots"); + free_plot_arrays(raw_prices, daily_returns, cumulative_returns, annualized_volatility, returns); + json_free(acwi); + return SDL_APP_FAILURE; + } + + SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "wayland,x11,offscreen,dummy"); + + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_Log("Could not initialize SDL: %s", SDL_GetError()); + free_plot_arrays(raw_prices, daily_returns, cumulative_returns, annualized_volatility, returns); + json_free(acwi); + return SDL_APP_FAILURE; + } + + if (!TTF_Init()) { + SDL_Log("Could not initialize SDL_ttf: %s", SDL_GetError()); + free_plot_arrays(raw_prices, daily_returns, cumulative_returns, annualized_volatility, returns); + json_free(acwi); + return SDL_APP_FAILURE; + } + + AppState *state = calloc(1, sizeof(*state)); + if (state == NULL) { + SDL_Log("Could not allocate app state"); + TTF_Quit(); + free_plot_arrays(raw_prices, daily_returns, cumulative_returns, annualized_volatility, returns); + json_free(acwi); + return SDL_APP_FAILURE; + } + + state->acwi = acwi; + state->raw_prices = raw_prices; + state->daily_returns = daily_returns; + state->cumulative_returns = cumulative_returns; + state->annualized_volatility = annualized_volatility; + state->raw_price_count = raw_count; + state->plot_count = 4; + state->active_plot = -1; + + state->font = TTF_OpenFont("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12.0f); + if (state->font == NULL) { + SDL_Log("Could not open font: %s", SDL_GetError()); + free_plot_arrays(state->raw_prices, state->daily_returns, state->cumulative_returns, state->annualized_volatility, returns); + json_free(state->acwi); + free(state); + TTF_Quit(); + return SDL_APP_FAILURE; + } + + state->window = SDL_CreateWindow("ACWI Dashboard", 1200, 800, SDL_WINDOW_RESIZABLE); + if (state->window == NULL) { + SDL_Log("Could not create window: %s", SDL_GetError()); + TTF_CloseFont(state->font); + free_plot_arrays(state->raw_prices, state->daily_returns, state->cumulative_returns, state->annualized_volatility, returns); + json_free(state->acwi); + free(state); + TTF_Quit(); + return SDL_APP_FAILURE; + } + + state->renderer = SDL_CreateRenderer(state->window, NULL); + if (state->renderer == NULL) { + SDL_Log("Could not create renderer: %s", SDL_GetError()); + SDL_DestroyWindow(state->window); + TTF_CloseFont(state->font); + free_plot_arrays(state->raw_prices, state->daily_returns, state->cumulative_returns, state->annualized_volatility, returns); + json_free(state->acwi); + free(state); + TTF_Quit(); + return SDL_APP_FAILURE; + } + + plot_init(&state->plots[0], "ACWI close", "Date", "Close", raw_prices, raw_count, raw_bounds.min_x, raw_bounds.max_x, raw_bounds.min_y, raw_bounds.max_y, true, (SDL_Color){80, 210, 130, 255}); + plot_init(&state->plots[1], "Daily return", "Date", "Return %", daily_returns, daily_count, daily_bounds.min_x, daily_bounds.max_x, daily_bounds.min_y, daily_bounds.max_y, true, (SDL_Color){90, 170, 255, 255}); + plot_init(&state->plots[2], "Cumulative return", "Date", "Return %", cumulative_returns, cumulative_count, cumulative_bounds.min_x, cumulative_bounds.max_x, cumulative_bounds.min_y, cumulative_bounds.max_y, true, (SDL_Color){245, 180, 70, 255}); + plot_init(&state->plots[3], "30-day annualized volatility", "Date", "Volatility %", annualized_volatility, volatility_count, volatility_bounds.min_x, volatility_bounds.max_x, volatility_bounds.min_y, volatility_bounds.max_y, true, (SDL_Color){220, 110, 220, 255}); + + printf("Loaded %d ACWI close-price points\n", state->raw_price_count); + *appstate = state; + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { + AppState *state = appstate; + + if (event->type == SDL_EVENT_QUIT) { + return SDL_APP_SUCCESS; + } + + int window_width = 0; + int window_height = 0; + SDL_GetCurrentRenderOutputSize(state->renderer, &window_width, &window_height); + SDL_FRect rects[4]; + dashboard_layout(rects, state->plot_count, window_width, window_height); + + if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN && event->button.button == SDL_BUTTON_LEFT) { + state->active_plot = -1; + for (int i = state->plot_count - 1; i >= 0; i -= 1) { + if (point_in_rect(event->button.x, event->button.y, rects[i])) { + state->active_plot = i; + state->plots[i].dragging = true; + break; + } + } + return SDL_APP_CONTINUE; + } + + if (event->type == SDL_EVENT_MOUSE_BUTTON_UP && event->button.button == SDL_BUTTON_LEFT) { + if (state->active_plot >= 0) { + state->plots[state->active_plot].dragging = false; + } + state->active_plot = -1; + return SDL_APP_CONTINUE; + } + + if (event->type == SDL_EVENT_MOUSE_MOTION && state->active_plot >= 0) { + Plot *plot = &state->plots[state->active_plot]; + if (plot->dragging) { + plot_handle_mouse_motion(plot, rects[state->active_plot], event->motion.xrel); + } + return SDL_APP_CONTINUE; + } + + if (event->type == SDL_EVENT_MOUSE_WHEEL && event->wheel.y != 0.0f) { + for (int i = state->plot_count - 1; i >= 0; i -= 1) { + SDL_FRect inner = plot_inner_rect(rects[i]); + if (point_in_rect(event->wheel.mouse_x, event->wheel.mouse_y, inner)) { + plot_handle_mouse_wheel(&state->plots[i], rects[i], event->wheel.mouse_x, event->wheel.y); + break; + } + } + return SDL_APP_CONTINUE; + } + + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppIterate(void *appstate) { + AppState *state = appstate; + + int window_width = 0; + int window_height = 0; + if (!SDL_GetCurrentRenderOutputSize(state->renderer, &window_width, &window_height)) { + SDL_Log("Could not get render size: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + SDL_SetRenderDrawColor(state->renderer, 10, 12, 18, 255); + SDL_RenderClear(state->renderer); + + SDL_FRect rects[4]; + dashboard_layout(rects, state->plot_count, window_width, window_height); + for (int i = 0; i < state->plot_count; i += 1) { + plot_render(&state->plots[i], state->renderer, state->font, rects[i]); + } + + SDL_RenderPresent(state->renderer); + return SDL_APP_CONTINUE; +} + +void SDL_AppQuit(void *appstate, SDL_AppResult result) { + unused(result); + + AppState *state = appstate; + if (state != NULL) { + SDL_DestroyRenderer(state->renderer); + SDL_DestroyWindow(state->window); + TTF_CloseFont(state->font); + free_plot_arrays(state->raw_prices, state->daily_returns, state->cumulative_returns, state->annualized_volatility, NULL); + json_free(state->acwi); + free(state); + } + TTF_Quit(); +} diff --git a/b/src/plot.c b/b/src/plot.c new file mode 100644 index 0000000..9de44ba --- /dev/null +++ b/b/src/plot.c @@ -0,0 +1,196 @@ + +static bool point_in_rect(float x, float y, SDL_FRect rect) { + return x >= rect.x && x <= rect.x + rect.w && y >= rect.y && y <= rect.y + rect.h; +} + +static SDL_FRect plot_inner_rect(SDL_FRect outer) { + SDL_FRect result = { + .x = outer.x + 58.0f, + .y = outer.y + 30.0f, + .w = outer.w - 76.0f, + .h = outer.h - 76.0f, + }; + return result; +} + +static void plot_clamp_view_to_data(Plot *plot) { + double full_range = plot->max_x - plot->min_x; + double view_range = plot->view_max_x - plot->view_min_x; + double min_view_range = full_range * 0.001; + + if (min_view_range <= 0.0) { + min_view_range = 1.0; + } + + if (view_range < min_view_range) { + double center = (plot->view_min_x + plot->view_max_x) * 0.5; + view_range = min_view_range; + plot->view_min_x = center - view_range * 0.5; + plot->view_max_x = center + view_range * 0.5; + } + + if (view_range >= full_range) { + plot->view_min_x = plot->min_x; + plot->view_max_x = plot->max_x; + return; + } + + if (plot->view_min_x < plot->min_x) { + plot->view_min_x = plot->min_x; + plot->view_max_x = plot->view_min_x + view_range; + } + if (plot->view_max_x > plot->max_x) { + plot->view_max_x = plot->max_x; + plot->view_min_x = plot->view_max_x - view_range; + } +} + +static void plot_init(Plot *plot, const char *title, const char *x_axis_label, const char *y_axis_label, PlotPoint *points, int point_count, double min_x, double max_x, double min_y, double max_y, bool x_axis_is_time, SDL_Color line_color) { + snprintf(plot->title, sizeof(plot->title), "%s", title); + snprintf(plot->x_axis_label, sizeof(plot->x_axis_label), "%s", x_axis_label); + snprintf(plot->y_axis_label, sizeof(plot->y_axis_label), "%s", y_axis_label); + plot->points = points; + plot->point_count = point_count; + plot->min_x = min_x; + plot->max_x = max_x; + plot->min_y = min_y; + plot->max_y = max_y; + plot->view_min_x = min_x; + plot->view_max_x = max_x; + plot->x_axis_is_time = x_axis_is_time; + plot->dragging = false; + plot->line_color = line_color; +} + +static void plot_handle_mouse_motion(Plot *plot, SDL_FRect outer, float xrel) { + SDL_FRect inner = plot_inner_rect(outer); + double view_range = plot->view_max_x - plot->view_min_x; + double x_delta = -((double)xrel / (double)inner.w) * view_range; + plot->view_min_x += x_delta; + plot->view_max_x += x_delta; + plot_clamp_view_to_data(plot); +} + +static void plot_handle_mouse_wheel(Plot *plot, SDL_FRect outer, float mouse_x, float wheel_y) { + SDL_FRect inner = plot_inner_rect(outer); + double view_range = plot->view_max_x - plot->view_min_x; + double mouse_fraction = ((double)mouse_x - (double)inner.x) / (double)inner.w; + if (mouse_fraction < 0.0) mouse_fraction = 0.0; + if (mouse_fraction > 1.0) mouse_fraction = 1.0; + + double anchor_x = plot->view_min_x + mouse_fraction * view_range; + double zoom_factor = wheel_y > 0.0f ? 0.8 : 1.25; + double new_range = view_range * zoom_factor; + + plot->view_min_x = anchor_x - mouse_fraction * new_range; + plot->view_max_x = plot->view_min_x + new_range; + plot_clamp_view_to_data(plot); +} + +static void format_x_label(Plot *plot, double x, char *buffer, size_t buffer_size) { + if (plot->x_axis_is_time) { + time_t time = (time_t)x; + struct tm *tm = gmtime(&time); + if (tm != NULL) { + strftime(buffer, buffer_size, "%Y-%m", tm); + return; + } + } + + snprintf(buffer, buffer_size, "%.2f", x); +} + +static void plot_render(Plot *plot, SDL_Renderer *renderer, TTF_Font *font, SDL_FRect outer) { + SDL_FRect inner = plot_inner_rect(outer); + if (outer.w < 80.0f || outer.h < 80.0f || inner.w < 10.0f || inner.h < 10.0f) { + return; + } + + double visible_min_y = DBL_MAX; + double visible_max_y = -DBL_MAX; + for (int i = 0; i < plot->point_count; i += 1) { + PlotPoint point = plot->points[i]; + if (point.x < plot->view_min_x || point.x > plot->view_max_x) { + continue; + } + if (point.y < visible_min_y) visible_min_y = point.y; + if (point.y > visible_max_y) visible_max_y = point.y; + } + if (visible_min_y == DBL_MAX || visible_min_y == visible_max_y) { + visible_min_y = plot->min_y; + visible_max_y = plot->max_y; + } + double y_padding = (visible_max_y - visible_min_y) * 0.05; + visible_min_y -= y_padding; + visible_max_y += y_padding; + + SDL_SetRenderDrawColor(renderer, 15, 18, 26, 255); + SDL_RenderFillRect(renderer, &outer); + + SDL_SetRenderDrawColor(renderer, 18, 24, 34, 255); + SDL_RenderFillRect(renderer, &inner); + + SDL_SetRenderDrawColor(renderer, 45, 55, 70, 255); + for (int i = 0; i <= 4; i += 1) { + float y = inner.y + ((float)i / 4.0f) * inner.h; + SDL_RenderLine(renderer, inner.x, y, inner.x + inner.w, y); + } + for (int i = 0; i <= 4; i += 1) { + float x = inner.x + ((float)i / 4.0f) * inner.w; + SDL_RenderLine(renderer, x, inner.y, x, inner.y + inner.h); + } + + SDL_SetRenderDrawColor(renderer, 190, 195, 205, 255); + SDL_RenderRect(renderer, &inner); + + double x_range = plot->view_max_x - plot->view_min_x; + double y_range = visible_max_y - visible_min_y; + + SDL_Rect clip = { + .x = (int)inner.x, + .y = (int)inner.y, + .w = (int)inner.w, + .h = (int)inner.h, + }; + SDL_SetRenderClipRect(renderer, &clip); + SDL_SetRenderDrawColor(renderer, plot->line_color.r, plot->line_color.g, plot->line_color.b, plot->line_color.a); + for (int i = 1; i < plot->point_count; i += 1) { + PlotPoint a = plot->points[i - 1]; + PlotPoint b = plot->points[i]; + + if ((a.x < plot->view_min_x && b.x < plot->view_min_x) || + (a.x > plot->view_max_x && b.x > plot->view_max_x)) { + continue; + } + + float x1 = inner.x + (float)(((a.x - plot->view_min_x) / x_range) * inner.w); + float y1 = inner.y + inner.h - (float)(((a.y - visible_min_y) / y_range) * inner.h); + float x2 = inner.x + (float)(((b.x - plot->view_min_x) / x_range) * inner.w); + float y2 = inner.y + inner.h - (float)(((b.y - visible_min_y) / y_range) * inner.h); + SDL_RenderLine(renderer, x1, y1, x2, y2); + } + SDL_SetRenderClipRect(renderer, NULL); + + SDL_Color title_color = {230, 235, 245, 255}; + SDL_Color label_color = {175, 185, 200, 255}; + SDL_Color axis_color = {130, 145, 165, 255}; + + draw_text(renderer, font, plot->title, outer.x + outer.w * 0.5f, outer.y + 6.0f, 0.5f, 0.0f, title_color); + draw_text(renderer, font, plot->y_axis_label, inner.x, outer.y + 8.0f, 0.0f, 0.0f, axis_color); + draw_text(renderer, font, plot->x_axis_label, inner.x + inner.w * 0.5f, inner.y + inner.h + 28.0f, 0.5f, 0.0f, axis_color); + + char label[64]; + for (int i = 0; i <= 4; i += 1) { + double value = visible_max_y - ((double)i / 4.0) * y_range; + float y = inner.y + ((float)i / 4.0f) * inner.h; + snprintf(label, sizeof(label), "%.2f", value); + draw_text(renderer, font, label, inner.x - 6.0f, y, 1.0f, 0.5f, label_color); + } + + for (int i = 0; i <= 2; i += 1) { + double x_value = plot->view_min_x + ((double)i / 2.0) * x_range; + format_x_label(plot, x_value, label, sizeof(label)); + float x = inner.x + ((float)i / 2.0f) * inner.w; + draw_text(renderer, font, label, x, inner.y + inner.h + 8.0f, 0.5f, 0.0f, label_color); + } +} diff --git a/b/src/plot.h b/b/src/plot.h new file mode 100644 index 0000000..492b00c --- /dev/null +++ b/b/src/plot.h @@ -0,0 +1,35 @@ +#ifndef plot_h_INCLUDED +#define plot_h_INCLUDED + +typedef struct PlotPoint { + double x; + double y; +} PlotPoint; + +typedef struct Plot { + char title[64]; + char x_axis_label[32]; + char y_axis_label[32]; + PlotPoint *points; + int point_count; + double min_x; + double max_x; + double min_y; + double max_y; + double view_min_x; + double view_max_x; + bool x_axis_is_time; + bool dragging; + SDL_Color line_color; +} Plot; + +static bool point_in_rect(float x, float y, SDL_FRect rect); +static SDL_FRect plot_inner_rect(SDL_FRect outer); +static void plot_clamp_view_to_data(Plot *plot); +static void plot_init(Plot *plot, const char *title, const char *x_axis_label, const char *y_axis_label, PlotPoint *points, int point_count, double min_x, double max_x, double min_y, double max_y, bool x_axis_is_time, SDL_Color line_color); +static void plot_handle_mouse_motion(Plot *plot, SDL_FRect outer, float xrel); +static void plot_handle_mouse_wheel(Plot *plot, SDL_FRect outer, float mouse_x, float wheel_y); +static void plot_render(Plot *plot, SDL_Renderer *renderer, TTF_Font *font, SDL_FRect outer); + +static void draw_text(SDL_Renderer *renderer, TTF_Font *font, const char *text, float x, float y, float anchor_x, float anchor_y, SDL_Color color); +#endif // plot_h_INCLUDED