From 2b138a87644a29d0778b02d1e063ab100a9c9079 Mon Sep 17 00:00:00 2001 From: cozis Date: Tue, 27 Jun 2023 17:13:57 +0200 Subject: [PATCH] first commit --- README.md | 53 ++ cli.c | 230 ++++++ makefile | 8 + page.html | 13 + page2.html | 19 + params.json | 12 + test.html | 1236 ++++++++++++++++++++++++++++++ tinytemplate.c | 1593 ++++++++++++++++++++++++++++++++++++++ tinytemplate.h | 89 +++ tt | Bin 0 -> 105296 bytes tt.exe | Bin 0 -> 160242 bytes xjson.c | 1985 ++++++++++++++++++++++++++++++++++++++++++++++++ xjson.h | 70 ++ 13 files changed, 5308 insertions(+) create mode 100644 README.md create mode 100644 cli.c create mode 100644 makefile create mode 100644 page.html create mode 100644 page2.html create mode 100644 params.json create mode 100644 test.html create mode 100644 tinytemplate.c create mode 100644 tinytemplate.h create mode 100644 tt create mode 100644 tt.exe create mode 100644 xjson.c create mode 100644 xjson.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..b067f39 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# TinyTemplate +TinyTemplate is a minimal templating engine for C. It can be used as a library or through the command-line interface. + +* Very minimal in functionality +* Implemented in one file (plus header) +* No dynamic allocations +* Only depends on libc + +## How it looks +Here's how it looks: +```html + + + {{username}}s blog! + + +

Hei, welcome to my blog! I'm {{name}} {{surname}} and I'm {{age}} years old!

+ + + +``` +if you've already used some templating engines, then +this won't blow your mind. + +## Build +To build it you run +```sh +make +``` +which will generate the `tt` file, the command-line utility. + +## Embed +If you want to embed TinyTemplate in your own C project, +just pop `tinytemplate.c` and `tinytemplate.h` in you own +source tree and compile then as they were your own. + +## Command-line usage +The `tt` utility gets the template string from stdin and writes the evaluated version to stdout. Any errors are +reported to stderr, as you would expect. To provide any +parameters to the template, you can specify a json file +containing them. Here's an example usage which transforms +a template into an HTML page + +```sh +cat page.tt | ./tt params.json > page.html +``` + +## Internals +TinyTemplate works by compiling a template string into a bytecode program, and then evaluating the program to generate the output. \ No newline at end of file diff --git a/cli.c b/cli.c new file mode 100644 index 0000000..589e5f0 --- /dev/null +++ b/cli.c @@ -0,0 +1,230 @@ +#include +#include +#include +#include +#include "xjson.h" +#include "tinytemplate.h" + +typedef struct wrap_t wrap_t; +struct wrap_t { + eval_context_t *context; + union { + xj_value *value; + wrap_t *next; + }; +}; + +typedef struct { + FILE *stream; + xj_value *params; + wrap_t *free; + wrap_t pool[TINYTEMPLATE_MAX_ITER_DEPTH]; +} eval_context_t; + +static bool query_json_array(void *data, + tinytemplate_type_t *type, + tinytemplate_value_t *value); + +static bool query_json_object(void *data, const char *key, size_t len, + tinytemplate_type_t *type, + tinytemplate_value_t *value); + +static void convert_json_object(eval_context_t *context, + xj_value *child, + tinytemplate_type_t *type, + tinytemplate_value_t *value) +{ + switch (child->type) { + + case XJ_NULL: + *type = TINYTEMPLATE_TYPE_INT; + value->as_int = 0; + break; + + case XJ_INT: + *type = TINYTEMPLATE_TYPE_INT; + value->as_int = child->as_int; + break; + + case XJ_FLOAT: + *type = TINYTEMPLATE_TYPE_INT; + value->as_float = child->as_float; + break; + + case XJ_BOOL: + *type = TINYTEMPLATE_TYPE_INT; + value->as_int = child->as_bool; + break; + + case XJ_ARRAY: + { + iter_t *iter = context->free; + *type = TINYTEMPLATE_TYPE_ARRAY; + value->as_array.data = iter; + value->as_array.next = query_json_array; + if (iter) { + context->free = iter->next; + iter->curs = child->as_array; + } + break; + } + + case XJ_OBJECT: + *type = TINYTEMPLATE_TYPE_DICT; + value->as_dict.data = child; + value->as_dict.get = query_json_object; + break; + + case XJ_STRING: + *type = TINYTEMPLATE_TYPE_STRING; + value->as_string.str = child->as_string; + value->as_string.len = child->size; + break; + } +} + +static bool query_json_array(void *data, + tinytemplate_type_t *type, + tinytemplate_value_t *value) +{ + iter_t *iter = data; + + if (iter->curs) { + iter->curs = iter->curs->next; + if (iter->curs) { + convert_json_object(iter->curs, type, value); + return true; + } + } + return false; +} + +static bool query_json_object(void *data, const char *key, size_t len, + tinytemplate_type_t *type, + tinytemplate_value_t *value) +{ + xj_value *json_value = data; + assert(json_value->type == XJ_OBJECT); + + xj_value *child = json_value->as_object; + while (child) { + size_t keylen = strlen(child->key); + if (keylen == len && !strncmp(key, child->key, len)) + break; + child = child->next; + } + if (!child) + return false; + + convert_json_object(child, type, value); + return true; +} + +static void callback(void *userp, const char *str, size_t len) +{ + eval_context_t *context = userp; + fwrite(str, 1, len, context->stream); +} + +static bool query_root_json_object(void *data, const char *key, size_t len, + tinytemplate_type_t *type, + tinytemplate_value_t *value) +{ + eval_context_t *context = data; + if (context->params) + return query_json_object(context->params, key, len, type, value); + else + return false; +} + +static void init_context(eval_context_t *context, + FILE *stream, xj_value *root) +{ + context->stream=stdout; + context->params=root; + context->free = context->pool; + + for (int i = 0; i < TINYTEMPLATE_MAX_ITER_DEPTH-1; i++) + context->pool[i].next = &context->pool[i+1]; + context->pool[TINYTEMPLATE_MAX_ITER_DEPTH-1].next = NULL; +} + +int main(int argc, char **argv) +{ + const char *param_file = NULL; + if (argc > 1) + param_file = argv[1]; + + char buffer[1 << 16]; + char json[1 << 16]; + xj_value *root = NULL; + xj_alloc *alloc = NULL; + + if (param_file) { + + FILE *stream = fopen(param_file, "rb"); + if (stream == NULL) { + fprintf(stderr, "Couldn't open parameter file \"%s\"\n", param_file); + return -1; + } + + size_t num = fread(buffer, 1, sizeof(buffer), stream); + if (ferror(stream)) { + fprintf(stderr, "Couldn't read from \"%s\"\n", param_file); + fclose(stream); + return -1; + } + + alloc = xj_alloc_using(json, sizeof(json), 0, NULL); + + xj_error error; + root = xj_decode(buffer, (int) num, alloc, &error); + if (root == NULL) { + fprintf(stderr, "Couldn't decode JSON (%s)\n", error.message); + fclose(stream); + return -1; + } + + fclose(stream); + } + + size_t num = fread(buffer, 1, sizeof(buffer), stdin); + if (ferror(stdin)) { + fprintf(stderr, "Failed to read from stdin\n"); + return -1; + } + + char message[256]; + + tinytemplate_instr_t *program = NULL; + size_t max_instr = 1 << 10; + + tinytemplate_status_t status; + do { + max_instr *= 2; + program = realloc(program, max_instr * sizeof(tinytemplate_instr_t)); + if (program == NULL) { + fprintf(stderr, "Out of memory"); + return -1; + } + + status = tinytemplate_compile(buffer, num, program, max_instr, NULL, message, sizeof(message)); + if (status != TINYTEMPLATE_STATUS_DONE && + status != TINYTEMPLATE_STATUS_EMEMORY) { + fprintf(stderr, "Failed to compile template (%s)\n", message); + return -1; + } + } while (status == TINYTEMPLATE_STATUS_EMEMORY); + + eval_context_t context; + init_context(&context); + + if (tinytemplate_eval(buffer, program, &context, query_root_json_object, callback, message, sizeof(message)) != TINYTEMPLATE_STATUS_DONE) { + fprintf(stderr, "Failed to evaluate template (%s)\n", message); + free(program); + return -1; + } + + free(program); + return 0; +} diff --git a/makefile b/makefile new file mode 100644 index 0000000..0ad2b06 --- /dev/null +++ b/makefile @@ -0,0 +1,8 @@ + +all: tt + +tt: cli.c tinytemplate.c xjson.c + gcc $^ -o $@ -Wall -Wextra -g + +clean: + rm tt tt.exe diff --git a/page.html b/page.html new file mode 100644 index 0000000..dde8008 --- /dev/null +++ b/page.html @@ -0,0 +1,13 @@ + + + {{username}}s blog! + + +

Hei, welcome to my blog! I'm {{name}} {{surname}} and I'm {{age}} years old!

+
    + {% for post in posts %} +
  • {{post.title}} - {{post.date}}
  • + {% end %} +
+ + \ No newline at end of file diff --git a/page2.html b/page2.html new file mode 100644 index 0000000..741126a --- /dev/null +++ b/page2.html @@ -0,0 +1,19 @@ + + + coziss blog! + + +

Hei, welcome to my blog! I'm Francesco Cozzuto and I'm 24 years old!

+
    + +
  • Scriviamo un server TCP - 10/9/2023
  • + +
  • Gli assert - 4/5/2022
  • + +
  • Big e little endian: Ordinamento dei byte - 14/5/2022
  • + +
  • Complessità computazionale - ???
  • + +
+ + \ No newline at end of file diff --git a/params.json b/params.json new file mode 100644 index 0000000..6be63fd --- /dev/null +++ b/params.json @@ -0,0 +1,12 @@ +{ + "username": "cozis", + "name": "Francesco", + "surname": "Cozzuto", + "age": 24, + "posts": [ + {"title": "Scriviamo un server TCP", "date": "10/9/2023"}, + {"title": "Gli assert", "date": "4/5/2022"}, + {"title": "Big e little endian: Ordinamento dei byte", "date": "14/5/2022"}, + {"title": "Complessità computazionale", "date": "???"} + ] +} diff --git a/test.html b/test.html new file mode 100644 index 0000000..c321c70 --- /dev/null +++ b/test.html @@ -0,0 +1,1236 @@ + + + + + Scrivere web server - v0.2 + + + + +
+ << Home +
+

Scrivere un server TCP

+ +

Indice

+ + +

Introduzione

+

+ In questo post spiegherò come scrivere un server TCP e, per mostrarne il funzionamento, il rispettico client. +

+ +

Cos'è TCP?

+

+ TCP è un protocollo di rete, ossia un protocollo che permette lo scambio di messaggi tra computer. Più nel dettaglio quello che ci permette di fare è collegare virtualmente due computer e trasferire un insieme di byte da uno all'altro. Il collegamento è virtuale perchè in realtà i computer si suppongono già fisicamente collegati mediante un insieme di router, tuttavia è solo quando entrambi manifestano l'intento di voler comunicare che questo può avvenire. Quindi per "virtuale" intendo dire che il collegamento è un'astrazione perchè di fatto i computer sono sempre stati collegati. +

+ +

Come funziona?

+

+ Per poter comunicare usando TCP, si deve prima decidere chi dei due nodi è il server e chi è il client. Il server è quello che si mette in ascolto per la connessione, mentre il client è colui che decide quando comunicazione deve cominciare. È da notare che la differenza tra server e client non è legata a chi invia e chi riceve informazioni, è solo un modo per gestire il primo collegamento. Se non ci fosse questa distinzione di ruoli allora sarebbe quasi impossibile creare connessioni TCP! Supponiamo che i ruoli siano simmetrici e che la connessione si avvii quando entrambi allo stesso tempo decidono di aprirla: quali sono le probabilità che due utenti ed i rispettivi computer si colleghino allo stesso tempo? Per farlo in modo preciso sarebbe richiesto qualche tipo di sincronizzazione, ma questa implicherebbe una comunicazione, tuttavia per ipotesi i computer non stanno ancora comunicando!! Un analogia che potrebbe mostrare questo problema sotto un'altra luce è quella dei telefoni cellulari. In effetti ogni volta che abbiamo addosso il cellulare ci stiamo comportando da server, perchè in ogni istante siamo aperti a ricevere telefonate. Dall'altra parte un nostro amico che ci chiama si comporta da client poichè è lui che decide di avviare la comunicazione. Rimuovere questi due ruoli comporterebbe che per parlare a telefono sia noi che il nostro amico dovremmo premere il tasto verde di chiamata allo stesso tempo. +

+

+ Per il protocollo TCP ogni computer su una rete è caratterizzato da un identificativo univoco chiamato indirizzo IP. In più su ciascun PC ogni programma è caratterizzato da un ulteriore identificativo chiamato porta univoco localmente. Naturalmente due computer per parlarsi hanno bisogno dei rispettivi IP, tuttavia quelli che parlano fra di loro sono i programmi in esecuzione sui due PC, quindi c'è bisogno della porta come ulteriore fattore discriminante. Per questo motivo per avviare la comunicazione il client deve conoscere il "nome" del server, cioè la coppia indirizzo-porta. Quando il client contatterà il server, automaticamente questo verrà a conoscenza degli identificativi del client. Questo comporta che il client debba conoscere a priori l'indirizzo e la porta del server! Questo però nella pratica rappresenta un vincolo molto poco limitante. +

+

+ Gli indirizzi IP sono stringhe di 32 cifre binarie (bit). Le combinazioni di 0 ed 1 (i bit) che si possono fare con una stringa di questa lunghezza sono circa 4 miliardi, per cui questo è il limite di computer che si possono avere su una rete. Per quanto riguarda le porte, queste sono stringhe binarie di 16 cifre. Le permutazioni che invece si possono ottenere in questo caso sono 65536. Quando si programma di solito le porte, dato che assumono valori piccoli (rispetto agli indirizzi IP), vengono riportate come numero (cioè in base 10 invece che base 2). Per quanto riguarda gli indirizzi IP invece, riportarle come numeri risulterebbe scomodo dato che è difficile distinguere numeri molto grandi ad occhio. Per questo motivo si usa la notazione dot-decimal, per la quale la stringa di 32 bit viene divisa in 4 stringhe da 8 e ciascuna sotto-stringa viene rappresentata in base 10 separata dalle altre con un puntino. Indirizzi IP espressi usando questa notazione sono molto comuni e chiunque abbia usato un computer ne avrà visto uno. Un esempio di indirizzo IP in notazione dot-decimal è 192.168.0.1. Dato che una stringa di 8 bit ha 256 permutazioni, i 4 numeri nella notazione dot-decimal possono solo assumere valori da 0 a 255 (i numeri da 0 a 255 sono in totale 256, cioè di conta pure lo 0). +

+

+ Una volta avviata la comunicazione, i due processi mandano messaggi ed attendono risposte, sincronizzandosi, effettuando elaborazioni e producendo risultati grazie a questa cooperazione. Una volta conclusa la comunicazione, entrambi i processi possono chiudere la connessione. +

+

+ Queste sono solo parole, noi dobbiamo farlo in codice tutto questo!! Me non temete! Ogni passo sarà accuratamente ed amorevolmente documentato :^) <3 +

+ +

Cosa realizzeremo

+

+ In verità dire che faremo un server ed un client TCP non è una descrizione esaustiva. TCP descrive solo il metodo che i programmi useranno per comunicare, ma non cosa si diranno! In particolare quel che faremo è un client che invia un messaggio al server, che leggerà il messaggio, ne convertirà maiuscole in minuscole e viceversa, e poi rimanderà la versione elaborata del messaggio. +

+ +

Come lo realizzeremo: I socket

+

+ Per realizzare un'applicazione del genere, dovremo fare quella che viene chiamata socket programming, cioè la programmazione che usa i "socket". +

+

+ Un socket è una struttura dati del kernel della quale possiamo chiamare dei metodi per effettuare delle comunicazioni sulla rete. È il modo in cui il sistema operativo astrae le funzionalità più crude dell'hardware. Il tema dell'astrazione dell'hardare è molto vasto, ma per i nostri scopi ci basta questo accenno. +

+

+ Il client avrà una struttura del tipo +

+
+
+ + + + + + + + + + + + + + + + + + + +
1main()
2{
3 address = 127.0.0.1
4 port = 8080
5 socket = create_tcp_socket()
6
7 connect(socket, address, port)
8
9 messaggio = "Ossimoro"
10
11 send(socket, messaggio)
12
13 messaggio = receive(socket)
14
15 print('Il server ha risposto con: ' + messaggio)
16
17 close(socket)
18}
+
+
+ +

+ mentre il server: +

+
+
+ + + + + + + + + + + + + + + + + + + + + +
1main()
2{
3 port = 8080
4 socket = create_tcp_socket()
5
6 listen(socket, port)
7
8 loop {
9 socket2 = accept(socket)
10
11 messaggio = read(socket2)
12
13 messaggio = convert_case(messaggio)
14
15 send(socket2, messaggio)
16
17 close(socket2)
18 }
19 close(socket)
20}
+
+
+ +

Socket

+

+ I socket possono svolgere tante funzioni, quindi quando ne creiamo uno dobbiamo specificare le modalità di utilizzo. La funzione che permette di creare un socket è +

+
+
+ + + + +
1#include <sys/socket.h>
2
3int socket(int domain, int type, int protocol);
+
+
+ + +

+ Con questa notazione include + prototipo della funzione intendo dire che la libreria inclusa definisce la funzione sotto. +

+ +

+ Il modo in cui si usa questa funzione è un po' caotico. Questa cosa è dovuta al fatto che quando ne fu progettata l'interfaccia, si cercò di prevedere gli sviluppi futuri, che poi non avvennero mai. +

+ +

+ Il primo argomento domain specifica lo spazio degli indirizzi del protocollo che vogliamo usare. Il valore di questo parametro può essere specificato usando dei valori specificati in sys/socket.h come AF_INET, AF_UNIT oppure AF_BLUETOOTH. Il prefisso AF_ sta per Address Family. Noi useremo solo AF_INET, in cui INET sta, banalmente, per internet. +

+ +

+ Il secondo argomento type specifica quello che comunemente indichiamo col nome di protocollo, cioè TCP nel nostro caso. Il valore che dobbiamo fornire per TCP è SOCK_STREAM. Altri esempi di protocolli che possiamo specificare sono UDP (SOCK_DGRAM) o l'assenza di protocollo (SOCK_RAW). +

+ +

+ Il terzo valore protocol è superfluo. Nel caso di TCP può sempre essere zero. +

+ +

+ La funzione socket ritorna quel che è comunemente chiamato descrittore del socket, cioè un numero intero positivo che descrive inequivocabilmente il nostro socket all'interno del kernel. Ogni volta che vorremo operare sul socket, forniremo alle funzione questo descrittore ed il sistema operativo saprà su quale socket operare. Se la creazione della struttura fallisce, allora un valore negativo è ritornato. +

+ +

+ In sintesi, per creare un socket TCP faremo una cosa del tipo: +

+
+
+ + + + + + + + + + + + + + + + + +
1#include <unistd.h> // Questa definisce [close]
2#include <sys/socket.h>
3
4int main(void)
5{
6 int fd = socket(AF_INET, SOCK_STREAM, 0);
7 if(fd < 0) {
8 // Non sono riuscito a creare il socket TCP!
9 return -1;
10 }
11
12 // .. fai cose col socket ..
13
14 close(fd); // Quando abbiamo finito, possiamo chiuderlo.
15 return 0;
16}
+
+
+ +

+ Su linux ed i sistemi unix in generale, le risorse sono spesso astratte sotto il concetto di file. I socket sarebbero un tipo speciale di file. Per questo motivo il descrittore dei socket è anche chiamato file descriptor (e da questo il nome fd della variabile) +

+ +

+ Sia nel caso del client che del server l'apertura e chiusura del socket sono uguali. Quello che cambia è la loro configurazione e poi connessione. Una volta avviata invece il codice torna simmetrico, nel senso che i due programmi possono ricevere ed inviare dati allo stesso modo. +

+ +

+ Configurazione del socket per il server +

+

+ Quel che il server dovrà fare dopo aver creato il socket è assegnargli un indirizzo IP tra quelli che ha a disposizione (potrebbe evere più di una interfaccia di rete!). Questo si fa usando la funzione bind, infatti "bind" si potrebbe tradurre dall'inglese all'italiano con "legare" (il socket è legato all'indirizzo). Una volta fatto questo è possibile attendere la connessione del client usando la funzione listen, che "ascolta" per connessioni ("listen" è la parola inglese che corrisponde ad "ascoltare" in italiano). Per accettare le connessioni dopo aver fatto listen, il server userà la funzione accept, che restituirà un nuovo socket relativo alla particolare connessione appena creata. +

+ +

Bind

+

+ La funzione bind è la parte più difficile della guida, quindi una volta fatta quella sarà tutto in discesa! L'interfaccia della funzione è questa qui: +

+ +
+
+ + + + +
1#include <sys/socket.h>
2
3int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
+
+
+ +

+ Ad un livello più alto, quello che fa bind è prendere un buffer contenente un indirizzo ed associarlo ad un socket. Dato che questa funzione si può usare per più protocolli con spazi di indirizzi diversi, fa il minimo numero di assunzioni su come sia fatto un indirizzo. Infatti, la struttura che rappresente un indirizzo generico per bind è questa qui +

+ +
+
+ + + + + + + + + + +
1#include <sys/socket.h>
2
3typedef unsigned short sa_family_t;
4// [sa_family_t] contiene i valori [AF_*]
5
6struct sockaddr {
7 sa_family_t sa_family;
8 char sa_data[14];
9};
+
+
+ +

+ in cui l'effettivo valore dell'indirizzo è un array di byte. Programmando in C, interpretare una struttura dati come array di caratteri corrisponde a dire "non mi interessa sapere come è strutturato questo dato, voglio solo poterlo spostare in giro". L'indirizzo effettivo poi potrebbe anche essere meno o più grande di struct sockaddr, da cui la necessita dell'argomento addrlen di bind in cui è possibile specificare la dimensione della regione puntata dall'argomento addr. +

+ +

+ Se questa interfaccia ti sembra confusa, non temere! È quel che provano tutti. È il risultato di anni di standard e convenzioni che hanno avuto bisogno di essere sempre retrocompatibili. +

+ +

+ Tuttavia, quando andremo a costruire l'indirizzo da fornire a bind non opereremo su una struttura del tipo struct sockaddr ma di tipo struct sockaddr_in, definita come +

+ +
+
+ + + + + + + + + + + + + +
1#include <netinet/in.h>
2
3struct sockaddr_in {
4 short sin_family;
5 unsigned short sin_port;
6 struct in_addr sin_addr;
7 char sin_zero[8];
8};
9
10struct in_addr {
11 unsigned long s_addr;
12};
+
+
+ +

+ il cui layout è compatibile a struct sockaddr (entrambi hanno fin_family allo stesso posto e le loro dimensioni sono uguali, di 16 byte), ma in più il campi sa_data di struct sockaddr è esploso nei tre campo sin_port, sin_addr e sin_zero. Una volta configurata questa struttura potremo fornirla a bind dopo aver fatto un cast a struct sockaddr. +

+ +

+ Il campo sin_family di struct sockaddr (e quindi di struct sockaddr_in) contiene il tipo di indirizzo, quindi uno di quegli identificativi che cominciano con AF_. Il valore di questo campo deve essere uguale al primo argomento di socket, cioè AF_INET. Del resto ha senso interpretare struct sockaddr come un struct sockaddr_in solo quando usiamo AF_INET. Il campo sin_family di un struct sockaddr_in sarà sempre AF_INET. +

+ +

+ I campi sin_port e sin_addr (più in particolare sin_addr.s_addr) sono rispettivamente porta ed indirizzo, cioè gli stessi porta ed indirizzo di cui parlavamo nell'introduzione al TCP. Questi due campi dovranno avere l'ordinamento dei byte della rete (cioè big endian, nel caso del TCP), quindi se il nostro computer non ha lo stesso suo ordinamento (cioè è little endian) allora dovremo effettuare una conversione. Per effettuare questa conversione comodamente, possiamo usare le funzioni +

+ +
+
+ + + + + + +
1#include <arpa/inet.h>
2uint32_t htonl(uint32_t hostlong);
3uint16_t htons(uint16_t hostshort);
4uint32_t ntohl(uint32_t netlong);
5uint16_t ntohs(uint16_t netshort);
+
+
+ + +

+ Che trasformano interi da ordinamento dell'host (il nostro computer) a quello della rete o viceversa. Infatti "hton" ed "ntoh" stanno per "host to network" e "network to host". La "l" o "s" finali invece fanno riferimento al tipo di dato che convertono, cioè "long" o "short". Nel caso dell'indirizzo dovremo usare htonl, mentre nel caso della porta htons. +

+ +

+ Invece di specificare un singolo indirizzo IP, possiamo usare la costante INADDR_ANY + definita in arpa/inet.h per effettuare il bind su tutti i possibili indirizzi disponibili. +

+ +

+ L'ultimo campo sin_zero ha come unica funzione quella di far combaciare la dimensione delle strutture sockaddr e sockaddr_in. Infatti il nome stesso contiene la parola "zero" per indicare che questo campo dovrebbe solo errere posto uguale a zero. +

+ +

+ Il valore di ritorno di bind è zero quando tutto è andato a buon fine ed un valore negativo quando ha fallito. +

+ +

+ Detto questo, il binding del server sarà fatto in questo modo qui +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1#include <string.h> // memset
2#include <unistd.h> // Questa definisce [close]
3#include <sys/socket.h>
4#include <arpa/inet.h>
5#include <netinet/in.h>
6
7int main(void)
8{
9 int fd = socket(AF_INET, SOCK_STREAM, 0);
10 if(fd < 0) {
11 // Non sono riuscito a creare il socket TCP!
12 return -1;
13 }
14
15 struct sockaddr_in addr;
16 addr.sin_family = AF_INET;
17 addr.sin_port = htons(8080);
18 addr.sin_addr.s_addr = INADDR_ANY;
19 memset(&addr.sin_zero, 0, sizeof(addr.sin_zero));
20
21 if(bind(fd, (struct sockaddr*) &addr,
22 sizeof(struct sockaddr_in))) {
23 // Il binding è fallito!!
24 close(fd);
25 return -1;
26 }
27
28 // .. fai cose col socket ..
29
30 close(fd); // Quando abbiamo finito, possiamo chiuderlo.
31 return 0;
32}
+
+
+ +

+ Decisamente più facile a farsi che a dirsi!! +

+ +

+ Come per AF_INET, le altre famiglie di indirizzi rappresentano a loro modo il contenuto di struct sockaddr. Per questo motivo ciascuna definisce una propria struttura analoga a struct sockaddr_in per costruire i propri indirizzi. Ad esempio per AF_INET6 (anche noto come IPv6) si usa sockaddr_in6, mentre per AF_UNIX (i socket unix) si usa struct sockaddr_un. Tuttavia queste strutture potrebbero non avere la stessa dimensione di struct sockaddr! Questo è dovuto al fatto che quando è stata progettata questa API originariamente si è supposto che 14 byte sarebbero bastati per ogni genere di indirizzo. Quando poi è risultato necessario avere indirizzi più grandi, si è preferito non cambiare la definizione di struct sockaddr per rimanere retrocompatibili. In ogni caso questa differenza di dimensione non crea problemi di correttezza nei programmi. +

+ +

Listen

+

+ Ultimo passo dell'inizializzazione è il listening, cioè effettivamente ascoltare per nuove connessioni. La funzione che dobbiamo usare è +

+ +
+
+ + + + +
1#include <sys/socket.h>
2
3int listen(int sockfd, int backlog);
+
+
+ + +

+ Il primo argomento sockfd naturalmente è il socket che vogliamo mettere in ascolto, mentre il secondo backlog corrisponde alla dimensione della coda di connessioni non ancora accettate. Il valore di ritorno, come per bind, è non-zero quando qualcosa va storto. +

+ +

+ Ogni volta che una richiesta di connessione viene ricevuta mentre il programma è in ascolto, il kernel la mette in una coda. Il nostro programma dal lato suo prenderà elementi da quella coda usando la funzione accept. Il paramentro backlog fa riferimento alla dimensione di queste coda. Se una nuova connessione arriva ma la code è piena, sarà automaticamente rifiutata, per questo è importante scegliere accuratamente questo valore. Se questo parametro è troppo piccolo ed il programma non riesce ad accettare connessioni con una frequenza comparabile a quella con cui arrivano, allora delle connessioni saranno perse. Se invece lo scegliamo troppo grande, delle risorse saranno sprecate. +

+ +

+ Naturalmente per condizioni di prova come la nostra ha poca importanza che valore usiamo. Anche scegliere 1 andrà bene, tuttavia portando questo programma in circostanze più realistiche, questo creerebbe problemi! +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1#include <string.h> // memset
2#include <unistd.h> // Questa definisce [close]
3#include <sys/socket.h>
4#include <arpa/inet.h>
5#include <netinet/in.h>
6
7int main(void)
8{
9 int fd = socket(AF_INET, SOCK_STREAM, 0);
10 if(fd < 0) {
11 // Non sono riuscito a creare il socket TCP!
12 return -1;
13 }
14
15 struct sockaddr_in addr;
16 addr.sin_family = AF_INET;
17 addr.sin_port = htons(8080);
18 addr.sin_addr.s_addr = INADDR_ANY;
19 memset(&addr.sin_zero, 0, sizeof(addr.sin_zero));
20
21 if(bind(fd, (struct sockaddr*) &addr,
22 sizeof(struct sockaddr_in))) {
23 // Il binding è fallito!!
24 close(fd);
25 return -1;
26 }
27
28 if(listen(fd, 32)) {
29 // Non sono riuscito ad ascoltare!
30 close(fd);
31 return -1;
32 }
33
34 // .. fai cose col socket ..
35
36 close(fd); // Quando abbiamo finito, possiamo chiuderlo.
37 return 0;
38}
+
+
+ +

Accept

+

+ Una volta completata la configurazione, il server potrà accettare nuove connessioni, usarle e poi chiuderle iterativamente. Come accennato prima, per accettare nuove richiesta si usa +

+ +
+
+ + + + + +
1#include <sys/socket.h>
2
3int accept(int sockfd, struct sockaddr *restrict addr,
4 socklen_t *restrict addrlen);
+
+
+ +

+ dove sockfd è il descrittore del socket messo in ascolto mentre addr e addrlen sono argomenti di output che restituiscono l'indirizzo del client in una struttura del tipo struct sockaddr_in. Se non siamo interessati all'indirizzo possiamo fornire NULL ai due argomenti finali. Il valore di ritorno invece è il descrittore di un nuovo socket dal quale si potranno leggere e scrivere byte. Una volta conclusa la comunicazione con questo client, questo socket potrà essere chiuso ed una nuova connessione accettata usando dinuovo accept. Naturalmente è anche possibile accettare più connessioni allo stesso tempo chiamando accept più volte in sequenza prima, ma questo ci costringerebbe a dover rendere decisamente più sofisticata la struttura del nostro programma. +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1#include <string.h> // memset
2#include <unistd.h> // Questa definisce [close]
3#include <sys/socket.h>
4#include <arpa/inet.h>
5#include <netinet/in.h>
6
7int main(void)
8{
9 int fd = socket(AF_INET, SOCK_STREAM, 0);
10 if(fd < 0) {
11 // Non sono riuscito a creare il socket TCP!
12 return -1;
13 }
14
15 struct sockaddr_in addr;
16 addr.sin_family = AF_INET;
17 addr.sin_port = htons(8080);
18 addr.sin_addr.s_addr = INADDR_ANY;
19 memset(&addr.sin_zero, 0, sizeof(addr.sin_zero));
20
21 if(bind(fd, (struct sockaddr*) &addr,
22 sizeof(struct sockaddr_in))) {
23 // Il binding è fallito!!
24 close(fd);
25 return -1;
26 }
27
28 if(listen(fd, 32)) {
29 // Non sono riuscito ad ascoltare!
30 close(fd);
31 return -1;
32 }
33
34 while(1) {
35 int clifd = accept(fd, NULL, NULL);
36 if(clifd < 0)
37 continue; // Torna in cima al loop
38
39 // .. fai cose col socket ..
40
41 close(clifd);
42 }
43
44 close(fd); // Quando abbiamo finito, possiamo chiuderlo.
45 return 0;
46}
+
+
+ +

+ Configurazione del socket per il client +

+ +

+ La configurazione del client è più semplice del server. Come il server crea un socket TCP usando socket, poi però si connette al server chiamando la funzione connect. Se connect ha avuto buon fine, la connessione è stata stabilita ed è possibile comunicare. +

+ +

Connect

+

+ La funzione connect è definita in questo modo qui +

+ +
+
+ + + + + +
1#include <sys/socket.h>
2
3int connect(int sockfd, const struct sockaddr *addr,
4 socklen_t addrlen);
+
+
+ +

+ Dove sockfd è il socket che vogliamo usare per effettuare la connessione (quello creato con socket) e la coppia addr/addrlen specifica l'indirizzo al quale vogliamo connetterci. Come per gli altri caso avremo addr pari ad un struct sockaddr_in e addrlen pari a sizeof(struct sockaddr_in). Se la funzione fallisce, allora -1 viene ritornato, altrimenti 0. +

+

+ L'inizializzazione del client quindi avrà una forma del genere +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1#include <string.h> // memset
2#include <unistd.h> // Questa definisce [close]
3#include <sys/socket.h>
4#include <arpa/inet.h>
5#include <netinet/in.h>
6
7int main(void)
8{
9 int fd = socket(AF_INET, SOCK_STREAM, 0);
10 if(fd < 0) {
11 // Non sono riuscito a creare il socket TCP!
12 return -1;
13 }
14
15 struct sockaddr_in addr;
16 addr.sin_family = AF_INET;
17 addr.sin_port = htons(8080); // Questa è la stessa del server!!
18 addr.sin_addr.s_addr = ???; // Ma qui cosa ci và?!
19 memset(&addr.sin_zero, 0, sizeof(addr.sin_zero));
20
21 if(connect(fd, (struct sockaddr*) &addr,
22 sizeof(struct sockaddr_in))) {
23 // La connessione è fallita!!
24 close(fd);
25 return -1;
26 }
27
28 // .. fai cose col socket ..
29
30 close(fd); // Quando abbiamo finito, possiamo chiuderlo.
31 return 0;
32}
+
+
+ +

+ L'indirizzo che forniamo a connect deve avere la stessa porta sulla quale il server ha fatto bind e come indirizzo IP anche quello sul quale ha fatto bind. Se il server ha fatto bind su più di un indirizzo IP usando INADDR_ANY, ci basta specificare uno di quelli a sua disposizione. +

+ +

+ Normalmente gli indirizzi IP sono noti in rappresentazione dotted decimal. In tal caso per convertirli da tale rappresentazione ad un intero con i byte ordinati come piace alla rete, si può usare la funzione inet_pton: +

+ +
+
+ + + + + +
1#include <arpa/inet.h>
2
3int inet_pton(int af, const char *restrict src,
4 void *restrict dst);
+
+
+ + +

+ L'argomento af è la famiglia di indirizzi, ossia AF_INET nel nostro caso. Invece src è la stringa (zero-terminata) contenente l'indirizzo in rappresentazione dotted decimal. L'argomento dst è il riferimento ad una struttura struct in_addr dove vogliamo che la funzione scriva l'indirizzo generato. Se tutto va bene, allora la funzione ritorna 1, altrimenti 0 o -1. +

+ +

+ La funzione inet_pton può operare anche su indirizzi IPv6. In tal caso l'argomento af è pari a AF_INET6 e dst punterà ad una struttura di tipo struct in_addr6. È per questo motivo che dst ha tipo void*, perchè può essere sia di tipo struct in_addr che struct in_addr6. +
+ Esistono molte funzioni che effettuano questo genere di conversione. Un altro esempio è inet_aton, che però converte solo indirizzi IPv4. +

+ +

+ Dato che sia client che server saranno eseguiti sullo stesso computer, possiamo considerare l'indirizzo IP del server pari a 127.0.0.1, quindi la connessione diventerà +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1#include <string.h> // memset
2#include <unistd.h> // Questa definisce [close]
3#include <sys/socket.h>
4#include <arpa/inet.h>
5#include <netinet/in.h>
6
7int main(void)
8{
9 int fd = socket(AF_INET, SOCK_STREAM, 0);
10 if(fd < 0) {
11 // Non sono riuscito a creare il socket TCP!
12 return -1;
13 }
14
15 const char ip[] = "127.0.0.1";
16 unsigned short port = 8080;
17
18 struct sockaddr_in addr;
19 addr.sin_family = AF_INET;
20 addr.sin_port = htons(port);
21 memset(&addr.sin_zero, 0, sizeof(addr.sin_zero));
22
23 if(inet_pton(AF_INET, ip, &addr.sin_addr) != 1) {
24 // Non sono riuscito a convertire l'indirizzo IP!!
25 close(fd);
26 return -1;
27 }
28
29 if(connect(fd, (struct sockaddr*) &addr,
30 sizeof(struct sockaddr_in))) {
31 // La connessione è fallita!!
32 close(fd);
33 return -1;
34 }
35
36 // .. fai cose col socket ..
37
38 close(fd); // Quando abbiamo finito, possiamo chiuderlo.
39 return 0;
40}
+
+
+ +

+ A questo punto anche la configurazione del client è conclusa! +

+ +

La business logic

+

+ Una volta connessi client e server, sarà il client a mandare il primo messaggo, quindi la prima cosa che farà il server dopo la connessione (cioè l'accept) è una chiamata a recv (dall'inglese, "ricevi"), mentre il client dopo la connessione (connect) chiamerà send (dall'inglese, "invia"). +

+ +

Send

+

+ La funzione send è definita così +

+ +
+
+ + + + +
1#include <sys/socket.h>
2
3ssize_t send(int sockfd, const void *buf, size_t len, int flags);
+
+
+ +

+ Dove sockfd è il socket relativo alla connessione (quello ritornato da accept nel server, oppure quello ritornato da socket nel client). Gli argomenti buf e len fanno riferimento all buffer che deve essere inviato e quanto è grande. Infine flags è un argomento che permette di configurare il modo in cui viene effettuato l'invio (per saperne di più sui flags, leggi questo). Il valore di default di flags è 0, ossia quel che useremo noi. Il valore di ritorno invece è -1 se qualcosa è andato storto, oppure il numero di byte inviati. È possibile che il valore di ritorno sia non negativo ma inferiore alla dimensione del buffer, quindi solo una parte del buffer è stata inviata, in tal caso dovremmo manualmente ritentare l'invio della parte mancante! +

+ +

+ La funzione send, quando l'argomento flags è posto a 0, è equivalente alla funzione write. La funzione send opera solo sui socket, mentre write opera sui file in generale (un socket è un tipo di file). +
+
+ Il tipo ssize_t è una versione con segno di size_t (cioè può assumere valori negativi). Questi tipi di dato sono usati di solito per mantenere la dimensione di regioni di memoria. +

+ +

Recv

+

+ La funzione recv è abbastanza simmetrica rispetto alla sorella send. L'interfaccia è questa qui: +

+
+
+ + + + +
1#include <sys/socket.h>
2
3ssize_t recv(int sockfd, void *buf, size_t len, int flags);
+
+
+ + +

+ Anche qui sockfd è il socket della connessione, tuttavia il buffer buff con dimensione len non sarà letto ma scritto. Il valore di len è considerato più come il numero massimo di byte da leggere, infatti il valore di ritorno, che è il numero di byte ricevuti, può solo essere inferiore o uguale a len. Tuttavia, in caso di errore è ritornato -1. Per quanto riguada il parametro flags, il comportamento è analogo a send. +

+ +

+ Una cosa aggiuntiva da notare è che quando recv ritorna 0 significa che il nodo col quale siamo in comunicazione si è disconnesso. +

+ +

+ La relazione fra send e write si ha anche nel caso di recv e read. +

+ +

+ La business logic del client +

+ +

+ Per quanto riguarda il client, un messaggio che verrà mandato al server per essere convertito. Quando la risposta sarà ricevuta, il programma la stampera a video. Avremo una cosa del genere (ho rimosso il codice che abbiamo scritto sino ad ora per poterci concetrare su quello che dobbiamo fare ora) +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
1/* ..gli header qui.. */
2#include <stdio.h>
3
4ssize_t super_send(int fd, const char *buf, size_t len, int flags)
5{
6 ssize_t sent_bytes = 0;
7 do {
8 ssize_t n = send(fd, buf + sent_bytes,
9 len - sent_bytes, flags);
10 if(n < 0) {
11 sent_bytes = -1;
12 break;
13 }
14 sent_bytes += n;
15 } while((size_t) sent_bytes < len);
16 return sent_bytes;
17}
18
19ssize_t super_recv(int fd, char *buf, size_t len, int flags)
20{
21 ssize_t received_bytes = 0;
22 do {
23 ssize_t n = recv(fd, buf + received_bytes,
24 len - received_bytes, flags);
25 if(n <= 0) {
26 received_bytes = n;
27 break;
28 }
29 received_bytes += n;
30 } while((size_t) received_bytes < len);
31 return received_bytes;
32}
33
34int main(void)
35{
36 int fd = /* ..creazione del socket.. */
37
38 /* ..inizializzazione del socket.. */
39
40 const char *message = "Ossimoro";
41
42 char risposta[32]; // Il buffer che conterrà la risposta del server.
43
44 // Assumiamo che la lunghezza massima della risposta
45 // sia 31 byte dato che deve entrare nel buffer
46 // [risposta] (31 perchè dobbiamo aggiungere un byte
47 // nullo alla fine per rendere la risposta una
48 // stringa zero-terminata).
49
50 // Dato che la risposta ha la stessa lunghezza del
51 // nostro messaggio, possiamo assicurarci già ora
52 // che la risposta non sforerà il buffer.
53 size_t message_len = strlen(message);
54 if(message_len > sizeof(risposta)-1) { // NOTA: sizeof(risposta)-1 è 31
55 fprintf(stderr, "Il messaggio non può essere "
56 "più lungo di %ld caratteri\n", sizeof(risposta)-1);
57 close(fd);
58 return -1;
59 }
60
61 // Invia il messaggio!
62 ssize_t n = super_send(fd, message, message_len, 0);
63 if(n < 0) {
64 /* Qualcosa è andato storto! */
65 fprintf(stderr, "Non sono riuscito ad inviare [%s]\n", message);
66 close(fd);
67 return -1;
68 }
69
70 n = super_recv(fd, risposta, message_len, 0);
71 if(n < 0) {
72 /* Qualcosa è andato storto! */
73 fprintf(stderr, "La ricezione della risposta a [%s] è fallita!", message);
74 close(fd);
75 return -1;
76 }
77
78 // A questo punto [recv] avrà scritto esattamente [n] byte
79 // all'interno di [risposta], per cui il primo byte dopo il
80 // messaggio è proprio alla posizione [risposta[n]]. Ora
81 // possiamo aggiungere il byte nullo terminatore:
82 risposta[n] = '\0';
83
84 // E stampare il messaggio
85 printf("[%s] -> [%s]\n", message, risposta);
86
87 /* ..chiudi il socket.. */
88 return 0;
89}
+
+
+ +

+ In realtà in questo codice abbiamo barato un po'. Non è necessariamente vero che recv e send inviino e ricevano l'intero messaggio con una sola chiamata. Per inviare il messaggio potrebbero servire due o più send! La necessità di chiamare iterativamente queste funzioni sorge quando il loro valore di ritorno (cioè n in questo esempio) è positivo ma comunque inferiore alla lunghezza del messaggio che abbiamo inviato. In questo programma non è un problema grave, perchè nel peggiore delle ipotesi risulterà che il messaggio del server è corretto a meno di qualche byte finale mancante. +

+ +

+ Per rendere il nostro programma più robusto, la send potrebbe essere chiamata in questo modo qui +

+
+
+ + + + + + + + + + + + + +
1ssize_t sent_bytes = 0;
2do {
3 // [message[i] + sent_bytes] è l'indirizzo della parte
4 // del messaggio non ancora inviato, mentre la sua
5 // lunghezza è [message_len - sent_bytes].
6 ssize_t n = send(fd, messages[i] + sent_bytes, message_len - sent_bytes, 0);
7 if(n < 0) {
8 sent_bytes = -1;
9 break;
10 }
11 sent_bytes += n;
12} while(sent_bytes < message_len);
+
+
+ +

+ In questo modo send verrà chiamata finchè l'interò messaggio sarà stato inviato, oppure un errore sarà avvenuto. Questo loop nel complesso si comporta come una send dove il valore di ritorno è sent_bytes ma che se invia il messaggio, non è mai parzialmente. +

+

+ Quello che potremmo fare è creare una funzione ausiliaria super_send che effettua questo meccanismo per poi sostituirla alla semplice send interna al nostro programma. +

+

+ Come accennato lo stesso problema si pone per recv. È possibile che ci vogliano più chiamate per ricevere l'intero messaggio. Per risolvere questo problema, come per la send, si potrebbe aggiungere un loop attorno alla sua chiamata +

+
+
+ + + + + + + + + + +
1ssize_t received_bytes = 0;
2do {
3 ssize_t n = recv(fd, risposta + received_bytes, message_len - received_bytes, 0);
4 if(n <= 0) {
5 received_bytes = n;
6 break;
7 }
8 received_bytes += n;
9} while(received_bytes < message_len);
+
+
+ +

+ Le stesse considerazione rispetto alla receive valgono: questo loop si comporta come una versione augmentata di recv che però non riceve mai il messaggio parzialmente, a patto che non avvenga un errore (in tal caso received_bytes == -1) e che il server non si disconnetta (received_bytes == 0). +

+ +

+ Il codice completo del client avendo aggiunto super_send e super_recv è +

+ {% code %} +#include // memset +#include // Questa definisce [close] +#include +#include +#include +#include +ssize_t super_send(int fd, const char *buf, size_t len, int flags) +{ + ssize_t sent_bytes = 0; + do { + ssize_t n = send(fd, buf + sent_bytes, + len - sent_bytes, flags); + if(n < 0) { + sent_bytes = -1; + break; + } + sent_bytes += n; + } while((size_t) sent_bytes < len); + return sent_bytes; +} +ssize_t super_recv(int fd, char *buf, size_t len, int flags) +{ + ssize_t received_bytes = 0; + do { + ssize_t n = recv(fd, buf + received_bytes, + len - received_bytes, flags); + if(n <= 0) { + received_bytes = n; + break; + } + received_bytes += n; + } while((size_t) received_bytes < len); + return received_bytes; +} +int main(void) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + if(fd < 0) { + // Non sono riuscito a creare il socket TCP! + return -1; + } + const char ip[] = "127.0.0.1"; + unsigned short port = 8080; + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + memset(&addr.sin_zero, 0, sizeof(addr.sin_zero)); + + if(inet_pton(AF_INET, ip, &addr.sin_addr) != 1) { + // Non sono riuscito a convertire l'indirizzo IP!! + close(fd); + return -1; + } + if(connect(fd, (struct sockaddr*) &addr, + sizeof(struct sockaddr_in))) { + // La connessione è fallita!! + close(fd); + return -1; + } + + const char *message = "Ossimoro"; + char risposta[32]; // Il buffer che conterrà la risposta del server. + size_t message_len = strlen(message); + if(message_len > sizeof(risposta)-1) { // NOTA: sizeof(risposta)-1 è 31 + fprintf(stderr, "Il messaggio non può essere " + "più lungo di %ld caratteri\n", sizeof(risposta)-1); + close(fd); + return -1; + } + // Invia il messaggio! + ssize_t n = super_send(fd, message, message_len, 0); + if(n < 0) { + /* Qualcosa è andato storto! */ + fprintf(stderr, "Non sono riuscito ad inviare [%s]\n", message); + close(fd); + return -1; + } + n = super_recv(fd, risposta, message_len, 0); + if(n < 0) { + /* Qualcosa è andato storto! */ + fprintf(stderr, "La ricezione della risposta a [%s] è fallita!", message); + close(fd); + return -1; + } + risposta[n] = '\0'; + printf("[%s] -> [%s]\n", message, risposta); + + close(fd); // Quando abbiamo finito, possiamo chiuderlo. + return 0; +} + {% end %} +

+ Decisamente un bel pezzo di programma! +

+ +

+ La business logic del server +

+

+ Il server dalla sua parte, accetterà una connessione, riceverà il messaggio e dopo averlo convertito lo rimanderà al client. Una volta inviato tornera ad attendere per una nuova connessione. +

+ {% code %} +/* ..qui gli header.. */ +#include // isupper, tolower, toupper +ssize_t super_send(int fd, const char *buf, size_t len, int flags) +{ + ssize_t sent_bytes = 0; + do { + ssize_t n = send(fd, buf + sent_bytes, + len - sent_bytes, flags); + if(n < 0) { + sent_bytes = -1; + break; + } + sent_bytes += n; + } while((size_t) sent_bytes < len); + return sent_bytes; +} +int main(void) +{ + int fd = /* .. crea il socket.. */ + /* ..inizializzalo.. */ + + while(1) { + int clifd = accept(fd, NULL, NULL); + if(clifd < 0) + /* Errore durante l'accettazione */ + continue; // Torna ad accettare richieste + + char buffer[32]; + + // In questo caso usiamo [recv] e non [super_recv] + // perchè non conosciamo la dimensione del messaggio + // a priori. + ssize_t n = recv(clifd, buffer, sizeof(buffer)-1, 0); + if(n <= 0) { + // C'è stato un errore oppure + // il client si è disconnesso. + // Chiudi il server e torna ad + // aspettare per nuove richieste. + close(clifd); + continue; + } + buffer[n] = '\0'; + /* Converti la stringa */ + for(int i = 0; buffer[i] != '\0'; i += 1) { + char c = buffer[i]; + if(isupper(c)) + c = tolower(c); + else + c = toupper(c); + buffer[i] = c; + } + + // Ed ora inviala dinuovo + super_send(clifd, buffer, n, 0); + + // Non controlliamo il valore di ritorno di + // [super_send] perchè in ogni caso chiuderemmo + // il socket. + + close(clifd); + } + + /* ..chiudi il socket.. */ + return 0; +} + {% end %} +

+ Aggiungendo la porzione di inizializzazione e le altre omesse, si ottiene +

+ {% code %} +#include // memset +#include // Questa definisce [close] +#include +#include +#include +#include // isupper, tolower, toupper +ssize_t super_send(int fd, const char *buf, size_t len, int flags) +{ + ssize_t sent_bytes = 0; + do { + ssize_t n = send(fd, buf + sent_bytes, + len - sent_bytes, flags); + if(n < 0) { + sent_bytes = -1; + break; + } + sent_bytes += n; + } while((size_t) sent_bytes < len); + return sent_bytes; +} +int main(void) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + if(fd < 0) { + // Non sono riuscito a creare il socket TCP! + return -1; + } + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(8080); + addr.sin_addr.s_addr = INADDR_ANY; + memset(&addr.sin_zero, 0, sizeof(addr.sin_zero)); + + if(bind(fd, (struct sockaddr*) &addr, + sizeof(struct sockaddr_in))) { + // Il binding è fallito!! + close(fd); + return -1; + } + if(listen(fd, 32)) { + // Non sono riuscito ad ascoltare! + close(fd); + return -1; + } + while(1) { + int clifd = accept(fd, NULL, NULL); + if(clifd < 0) + /* Errore durante l'accettazione */ + continue; // Torna ad accettare richieste + + char buffer[32]; + ssize_t n = recv(clifd, buffer, sizeof(buffer)-1, 0); + if(n <= 0) { + close(clifd); + continue; + } + buffer[n] = '\0'; + /* Converti la stringa */ + for(int i = 0; buffer[i] != '\0'; i += 1) { + char c = buffer[i]; + if(isupper(c)) + c = tolower(c); + else + c = toupper(c); + buffer[i] = c; + } + super_send(clifd, buffer, n, 0); + close(clifd); + } + close(fd); // Quando abbiamo finito, possiamo chiuderlo. + return 0; +} + {% end %} +

+ Per come è stato scritto questo programma, il controllo non uscirà mai dal while, per cui l'ultima chiamata a close, in realtà, non sarà mai eseguita! Il motivo per cui l'ho lasciata è per mostrare comunque la struttura generale del programma che usa i socket. +

+ +

+ In realtà i nomi super_send e super_recv sono intesi come nomi umoristici, nomi più appropriati sarebbero send_all e recv_all. +

+ +

Riferimenti

+ +
+
+ + \ No newline at end of file diff --git a/tinytemplate.c b/tinytemplate.c new file mode 100644 index 0000000..313dc1e --- /dev/null +++ b/tinytemplate.c @@ -0,0 +1,1593 @@ +#include +#include +#include +#include +#include "tinytemplate.h" + +/* Configurations */ + +// When enabled, the compiler dumps the location of +// the calls to [next_token] and [peek_token] to +// stderr. This is very useful when debugging. +// +// #define TINYTEMPLATE_TRACE_TOKENS + +/* Some definitions to help with readability */ + +#define DONE TINYTEMPLATE_STATUS_DONE +#define ESYMBOL TINYTEMPLATE_STATUS_ESYMBOL +#define ESCOPE TINYTEMPLATE_STATUS_ESCOPE +#define EDEPTH TINYTEMPLATE_STATUS_EDEPTH +#define ETYPE TINYTEMPLATE_STATUS_ETYPE +#define EITER TINYTEMPLATE_STATUS_EITER +#define EMEMORY TINYTEMPLATE_STATUS_EMEMORY +#define ESYNTAX TINYTEMPLATE_STATUS_ESYNTAX +#define ESEMANT TINYTEMPLATE_STATUS_ESEMANT + +#define instr_t tinytemplate_instr_t +#define status_t tinytemplate_status_t + +/* Utilities */ + +#define NOT_IMPLEMENTED_YET assert(0) + +/* + NOPE - No effect + DONE - Conclude execution + PUSHI - Push integer on the evaluation stack + PUSHF - Push float on the evaluation stack + PUSHS - Push string on the evaluation stack + PUSHV - Push value of a variable on the evaluation stack + JUMP - Jump to a given instruction of the program + JCND - Jump to a given instruction of the program if a condition is verified + WRITE - Write to output a string + WRTOP - Write to output the top of the evaluation stack + POP - Pop a value from the evaluation stack + ADD - Pop the top 2 value of the evaluation stack and push their sum + SUB - Same as ADD, but with subtraction + MUL - Same as ADD, but with multiplication + DIV - Same as ADD, but with division + MOD - Same as ADD, but with the remainder of division +*/ + +typedef enum { + OPCODE_NOPE, + OPCODE_DONE, + OPCODE_PUSHI, + OPCODE_PUSHF, + OPCODE_PUSHS, + OPCODE_PUSHV, + OPCODE_JUMP, + OPCODE_JCND, + OPCODE_WRITE, + OPCODE_WRTOP, + OPCODE_POP, + OPCODE_ADD, + OPCODE_SUB, + OPCODE_MUL, + OPCODE_DIV, + OPCODE_MOD, + OPCODE_NEG, + OPCODE_GETS, + OPCODE_ITER, + OPCODE_NEXT, + OPCODE_CHLD, + OPCODE_IDX, +} opcode_t; + +// Represents a substring of the template +typedef struct { + size_t offset; + size_t length; +} slice_t; + +typedef enum { + SCOPE_IF, + SCOPE_IF_ELSE, + SCOPE_FOR, +} scope_type_t; + +typedef struct { + scope_type_t type; + size_t if_jcnd; + size_t if_jump; + size_t for_next; + slice_t for_child_label; + slice_t for_index_label; +} scope_t; + +typedef struct { + scope_t scope_stack[TINYTEMPLATE_MAX_SCOPE_DEPTH]; + size_t scope_depth; + + instr_t *program; + size_t num_instr; + size_t max_instr; + + bool failed; +} compile_state_t; + +typedef struct { + const char *src; + size_t cur, len; +} scanner_t; + +typedef union { + int64_t as_int; + double as_float; +} token_payload_t; + +typedef enum { + TOKEN_OPER_ADD = '+', // These are defined to avoid + TOKEN_OPER_SUB = '-', // compiler warnings. All ASCII + TOKEN_OPER_MUL = '*', // values are assumed to be + TOKEN_OPER_DIV = '/', // valid "token_t"s. + + TOKEN_END = 128, + TOKEN_IDENT, + TOKEN_NONASCII, + TOKEN_NONPRINT, + TOKEN_VALUE_INT, + TOKEN_VALUE_FLOAT, + TOKEN_KWORD_IF, + TOKEN_KWORD_IN, + TOKEN_KWORD_FOR, + TOKEN_KWORD_ELSE, + TOKEN_KWORD_END, + TOKEN_OPER_MOD, +} token_t; + +typedef struct { + size_t max; + char *dst; +} error_t; + +static void report(error_t *error, const char *fmt, ...) +{ + if (error->dst) { + va_list args; + va_start(args, fmt); + vsnprintf(error->dst, error->max, fmt, args); + va_end(args); + } +} + +static void +append_instr(compile_state_t *state, + opcode_t opcode, ...) +{ + if (state->failed) + return; + + if (state->num_instr == state->max_instr) { + state->failed = true; + return; + } + + va_list operands; + va_start(operands, opcode); + + tinytemplate_instr_t *instr = &state->program[state->num_instr++]; + + instr->opcode = opcode; + switch (opcode) { + + case OPCODE_ITER: + case OPCODE_CHLD: + case OPCODE_IDX: + break; + + case OPCODE_NEXT: + instr->operands[0].as_size = va_arg(operands, size_t); + break; + + case OPCODE_NOPE: + case OPCODE_DONE: + break; + + case OPCODE_PUSHI: instr->operands[0].as_int = va_arg(operands, int64_t); break; + case OPCODE_PUSHF: instr->operands[0].as_float = va_arg(operands, double); break; + + case OPCODE_GETS: + case OPCODE_PUSHV: + case OPCODE_PUSHS: + instr->operands[0].as_size = va_arg(operands, size_t); + instr->operands[1].as_size = va_arg(operands, size_t); + break; + + case OPCODE_JUMP: + case OPCODE_JCND: + instr->operands[0].as_size = va_arg(operands, size_t); + break; + + case OPCODE_WRITE: + instr->operands[0].as_size = va_arg(operands, size_t); + instr->operands[1].as_size = va_arg(operands, size_t); + break; + + case OPCODE_WRTOP: + case OPCODE_POP: + case OPCODE_ADD: + case OPCODE_SUB: + case OPCODE_MUL: + case OPCODE_DIV: + case OPCODE_MOD: + case OPCODE_NEG: + break; + } + + va_end(operands); +} + +static bool is_alpha(char c) +{ + return (c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z'); +} + +static bool is_digit(char c) +{ + return c >= '0' && c <= '9'; +} + +static bool is_space(char c) +{ + return c == ' ' || c == '\t' + || c == '\r' || c == '\n'; +} + +static bool is_ascii(char c) +{ + return !((unsigned char) c & (1 << 7)); +} + +static bool is_printable(char c) +{ + return (unsigned char) c >= 32 + && (unsigned char) c <= 126; +} + +static token_t +next_token_int(scanner_t *scanner, slice_t *slice, + token_payload_t *payload) +{ + assert(scanner->cur < scanner->len && is_digit(scanner->src[scanner->cur])); + + size_t offset = scanner->cur; + + int64_t buf = 0; + do { + int d = scanner->src[scanner->cur++] - '0'; + if (buf > (INT64_MAX - d) / 10) { + // Overflow! + buf = INT64_MAX; + break; + } + buf = buf * 10 + d; + } while (scanner->cur < scanner->len && is_digit(scanner->src[scanner->cur])); + + if (slice) { + slice->offset = offset; + slice->length = scanner->cur - offset; + } + + if (payload) + payload->as_int = buf; + return TOKEN_VALUE_INT; +} + +static token_t +next_token_float(scanner_t *scanner, slice_t *slice, + token_payload_t *payload) +{ + // The caller made sure that follows a float: + // a string of digits followed by a dot and + // another string of digits. + assert(scanner->cur < scanner->len && is_digit(scanner->src[scanner->cur])); + + size_t offset = scanner->cur; + + double buf = 0; + do { + int d = scanner->src[scanner->cur++] - '0'; + buf = buf * 10 + d; + } while (scanner->src[scanner->cur] != '.'); + + scanner->cur++; // Skip the "." + + double q = 1; + do { + q /= 10; + int d = scanner->src[scanner->cur++] - '0'; + buf += d * q; + } while (scanner->cur < scanner->len && is_digit(scanner->src[scanner->cur])); + + if (slice) { + slice->offset = offset; + slice->length = scanner->cur - offset; + } + + if (payload) + payload->as_float = buf; + return TOKEN_VALUE_FLOAT; +} + +static token_t +next_token_numeric(scanner_t *scanner, slice_t *slice, + token_payload_t *payload) +{ + // Scanner points to a digit + assert(scanner->cur < scanner->len && is_digit(scanner->src[scanner->cur])); + + // Is it a float or an int? Check if at the end + // of the first sequence of digit there's a dot + // followed by a digit. + size_t cur = scanner->cur; + while (cur < scanner->len && is_digit(scanner->src[cur])) + cur++; + + if (cur+1 < scanner->len && scanner->src[cur] == '.' && is_digit(scanner->src[cur+1])) + return next_token_float(scanner, slice, payload); + else + return next_token_int(scanner, slice, payload); +} + +static token_t +next_token_kword_or_ident(scanner_t *scanner, slice_t *slice, + token_payload_t *payload) +{ + (void) payload; + + assert(scanner->cur < scanner->len && is_alpha(scanner->src[scanner->cur])); + + size_t offset = scanner->cur; + do + scanner->cur++; + while (scanner->cur < scanner->len && is_alpha(scanner->src[scanner->cur])); + size_t length = scanner->cur - offset; + + if (slice) { + slice->offset = offset; + slice->length = length; + } + + switch (length) { + case 2: // if, in + if (!strncmp(scanner->src + offset, "if", length)) + return TOKEN_KWORD_IF; + if (!strncmp(scanner->src + offset, "in", length)) + return TOKEN_KWORD_IN; + break; + + case 3: // end, mod, for + if (!strncmp(scanner->src + offset, "end", length)) + return TOKEN_KWORD_END; + if (!strncmp(scanner->src + offset, "mod", length)) + return TOKEN_OPER_MOD; + if (!strncmp(scanner->src + offset, "for", length)) + return TOKEN_KWORD_FOR; + break; + + case 4: // else + if (!strncmp(scanner->src + offset, "else", length)) + return TOKEN_KWORD_ELSE; + break; + } + + return TOKEN_IDENT; +} + +static token_t +next_token(scanner_t *scanner, slice_t *slice, + token_payload_t *payload) +{ + while (scanner->cur < scanner->len && is_space(scanner->src[scanner->cur])) + scanner->cur++; + + if (scanner->cur == scanner->len) { + if (slice) { + slice->offset = scanner->cur; + slice->length = 0; + } + return TOKEN_END; + } + + char c = scanner->src[scanner->cur]; + + if (is_digit(c)) + return next_token_numeric(scanner, slice, payload); + + if (is_alpha(c)) + return next_token_kword_or_ident(scanner, slice, payload); + + if (!is_ascii(c)) { + size_t offset = scanner->cur; + do + scanner->cur++; + while (scanner->cur < scanner->len && !is_ascii(scanner->src[scanner->cur])); + if (slice) { + slice->offset = offset; + slice->length = scanner->cur - offset; + } + return TOKEN_NONASCII; + } + + if (!is_printable(c)) { + size_t offset = scanner->cur; + do + scanner->cur++; + while (scanner->cur < scanner->len && !is_printable(scanner->src[scanner->cur])); + if (slice) { + slice->offset = offset; + slice->length = scanner->cur - offset; + } + return TOKEN_NONPRINT; + } + + size_t offset = scanner->cur; + scanner->cur++; + if (slice) { + slice->offset = offset; + slice->length = scanner->cur - offset; + } + return (token_t) c; +} + +static token_t +peek_token(scanner_t *scanner, + slice_t *slice, + token_payload_t *payload) +{ + size_t cur = scanner->cur; + token_t token = next_token(scanner, slice, payload); + scanner->cur = cur; + return token; +} + +#ifdef TINYTEMPLATE_TRACE_TOKENS +static token_t +trace_next_token(const char *c_func, const char *c_file, + int c_line, scanner_t *scanner, slice_t *slice, + token_payload_t *payload) +{ + slice_t maybe; + if (slice == NULL) + slice = &maybe; + token_t token = next_token(scanner, slice, payload); + fprintf(stderr, "NEXT TOKEN [%.*s] @ %s in %s:%d\n", + (int) slice->length, scanner->src + slice->offset, + c_func, c_file, c_line); + return token; +} + +static token_t +trace_peek_token(const char *c_func, const char *c_file, + int c_line, scanner_t *scanner, slice_t *slice, + token_payload_t *payload) +{ + slice_t maybe; + if (slice == NULL) + slice = &maybe; + token_t token = peek_token(scanner, slice, payload); + fprintf(stderr, "PEEK TOKEN [%.*s] @ %s in %s:%d\n", + (int) slice->length, scanner->src + slice->offset, + c_func, c_file, c_line); + return token; +} +#define next_token(scanner, slice, payload) trace_next_token(__func__, __FILE__, __LINE__, scanner, slice, payload) +#define peek_token(scanner, slice, payload) trace_peek_token(__func__, __FILE__, __LINE__, scanner, slice, payload) +#endif + +static status_t +parse_primary(scanner_t *scanner, + compile_state_t *state, + error_t *error) +{ + slice_t slice; + token_payload_t payload; + token_t token = next_token(scanner, &slice, &payload); + + switch (token) { + + case TOKEN_VALUE_INT: append_instr(state, OPCODE_PUSHI, payload.as_int); break; + case TOKEN_VALUE_FLOAT: append_instr(state, OPCODE_PUSHF, payload.as_float); break; + case TOKEN_IDENT: + { + // If the identifier refers to an iteration label, + // than push it. Otherwise assume is a template + // parameter provided by the caller program during + // evaluation. + bool found = false; + for (size_t i = 0, j = 0; i < state->scope_depth; i++) { + scope_t *scope = &state->scope_stack[state->scope_depth-i-1]; + if (scope->type == SCOPE_FOR) { + + // Check if the label matches the iteration label + if (slice.length == scope->for_child_label.length && !memcmp(scanner->src + slice.offset, scanner->src + scope->for_child_label.offset, slice.length)) { + append_instr(state, OPCODE_CHLD, slice.offset, slice.length); + found = true; + break; + } + + // Check if the label matches the index label. + // If no iteration label was specified, then + // its length will be 0 and the expression's + // label won't match. + if (slice.length == scope->for_index_label.length && !memcmp(scanner->src + slice.offset, scanner->src + scope->for_index_label.offset, slice.length)) { + append_instr(state, OPCODE_IDX, slice.offset, slice.length); + found = true; + break; + } + j++; + } + } + + if (!found) + // Label doesn't refer to an iteration + append_instr(state, OPCODE_PUSHV, slice.offset, slice.length); + break; + } + + default: + report(error, "Bad token [%.*s] in primary expression", + (int) slice.length, scanner->src + slice.offset); + return ESYNTAX; + } + return DONE; +} + +static status_t +parse_suffix(scanner_t *scanner, + compile_state_t *state, + error_t *error) +{ + status_t status; + + status = parse_primary(scanner, state, error); + if (status != DONE) + return status; + + token_t suffix = peek_token(scanner, NULL, NULL); + if (suffix == '.') { + + // Offset of the dot token + size_t checkpoint = scanner->cur; + + // Consume the dot + next_token(scanner, NULL, NULL); + + slice_t slice; + token_t key = next_token(scanner, &slice, NULL); + if (key == TOKEN_IDENT) + append_instr(state, OPCODE_GETS, slice.offset, slice.length); + else + scanner->cur = checkpoint; + } + return DONE; +} + +static bool +parse_prefix(scanner_t *scanner, + compile_state_t *state, + error_t *error) +{ + status_t status; + + token_t prefix = peek_token(scanner, NULL, NULL); + if (prefix == '+' || prefix == '-') { + next_token(scanner, NULL, NULL); + status = parse_prefix(scanner, state, error); + if (status != DONE) + return status; + if (prefix == '-') + append_instr(state, OPCODE_NEG); + return DONE; + } + return parse_suffix(scanner, state, error); +} + +static int +precedence_of(token_t token) +{ + switch (token) { + case '+': return 1; + case '-': return 1; + case '*': return 2; + case '/': return 2; + case TOKEN_OPER_MOD: return 2; + default: break; + } + return 0; +} + +static bool +is_binary_operator(token_t token) +{ + return precedence_of(token) > 0; +} + +static bool +is_right_associative(token_t token) +{ + (void) token; + return false; +} + +static bool +can_continue_climbing(scanner_t *scanner, + int min_precedence) +{ + token_t peek = peek_token(scanner, NULL, NULL); + return is_binary_operator(peek) && precedence_of(peek) >= min_precedence; +} + +static bool +should_associate_right(scanner_t *scanner, + token_t oper, token_t *peek) +{ + *peek = peek_token(scanner, NULL, NULL); + return is_binary_operator(*peek) + && (precedence_of(*peek) > precedence_of(oper) || (precedence_of(*peek) == precedence_of(oper) && is_right_associative(*peek))); +} + +static opcode_t +operator_opcode(token_t token) +{ + switch (token) { + case '+': return OPCODE_ADD; + case '-': return OPCODE_SUB; + case '*': return OPCODE_MUL; + case '/': return OPCODE_DIV; + case TOKEN_OPER_MOD: return OPCODE_MOD; + default: break; + } + + return OPCODE_ADD; +} + +static status_t +parse_expr_2(scanner_t *scanner, + compile_state_t *state, + int min_precedence, + error_t *error) +{ + status_t status; + + while (can_continue_climbing(scanner, min_precedence)) { + + token_t oper = next_token(scanner, NULL, NULL); + if ((status = parse_prefix(scanner, state, error)) != DONE) + return status; + + token_t peek; + while (should_associate_right(scanner, oper, &peek)) { + int precedence = precedence_of(oper) + (precedence_of(peek) > precedence_of(oper)); + if ((status = parse_expr_2(scanner, state, precedence, error)) != DONE) + return status; + } + + append_instr(state, operator_opcode(oper)); + } + return DONE; +} + +static bool +parse_expr(scanner_t *scanner, + compile_state_t *state, + error_t *error) +{ + status_t status; + if ((status = parse_prefix(scanner, state, error)) != DONE) + return status; + return parse_expr_2(scanner, state, 0, error); +} + +static status_t +expr_block(scanner_t *scanner, + compile_state_t *state, + error_t *error) +{ + scanner->cur += 2; // Skip the "{{" + + status_t status = parse_expr(scanner, state, error); + if (status != DONE) + return status; + + append_instr(state, OPCODE_WRTOP); + append_instr(state, OPCODE_POP, 1); + + if (scanner->cur+1 < scanner->len + && scanner->src[scanner->cur+0] == '}' + && scanner->src[scanner->cur+1] == '}') + scanner->cur += 2; + else { + report(error, "No closing [}}] after expression block"); + return ESYNTAX; + } + return DONE; +} + +static status_t +close_construct(scanner_t *scanner, + error_t *error, + const char *block_name) +{ + // Consume the following "%}" + + while (scanner->cur < scanner->len && is_space(scanner->src[scanner->cur])) + scanner->cur++; + if (scanner->cur+1 < scanner->len && scanner->src[scanner->cur] == '%' && scanner->src[scanner->cur+1] == '}') + scanner->cur += 2; + else { + report(error, "Missing closing %%} in {%% %s %%} block", block_name); + return ESYNTAX; + } + return DONE; +} + +static status_t +selection_construct_start(scanner_t *scanner, + compile_state_t *state, + error_t *error) +{ + status_t status; + + // This function is called after "{% if" is parsed + assert(scanner->src[scanner->cur-2] == 'i' + && scanner->src[scanner->cur-1] == 'f'); + + if (state->scope_depth == TINYTEMPLATE_MAX_SCOPE_DEPTH) { + report(error, "Scope depth limit reached"); + return ESCOPE; + } + + if ((status = parse_expr(scanner, state, error)) != DONE) + return status; + + if ((status = close_construct(scanner, error, "if")) != DONE) + return status; + + size_t if_jcnd = state->num_instr; + append_instr(state, OPCODE_JCND, 0); + + state->scope_stack[state->scope_depth].type = SCOPE_IF; + state->scope_stack[state->scope_depth].if_jcnd = if_jcnd; + state->scope_depth++; + return DONE; +} + + +static status_t +iteration_construct_start(scanner_t *scanner, + compile_state_t *state, + error_t *error) +{ + // This function is called after "{% for" is parsed + assert(scanner->src[scanner->cur-3] == 'f' + && scanner->src[scanner->cur-2] == 'o' + && scanner->src[scanner->cur-1] == 'r'); + + if (state->scope_depth == TINYTEMPLATE_MAX_SCOPE_DEPTH) { + report(error, "Scope depth limit reached"); + return ESCOPE; + } + + slice_t child_label; + if (next_token(scanner, &child_label, NULL) != TOKEN_IDENT) { + report(error, "Missing iteration label"); + return ESYNTAX; + } + + slice_t index_label; + if (peek_token(scanner, NULL, NULL) == ',') { + next_token(scanner, NULL, NULL); // Consume the comma + if (next_token(scanner, &index_label, NULL) != TOKEN_IDENT) { + report(error, "Missing iteration index label after [,]"); + return ESYNTAX; + } + } else + index_label = (slice_t) {0, 0}; + + if (next_token(scanner, NULL, NULL) != TOKEN_KWORD_IN) { + report(error, "Missing [in] keyword after iteration label"); + return ESYNTAX; + } + + status_t status; + if ((status = parse_expr(scanner, state, error)) != DONE) + return status; + + append_instr(state, OPCODE_ITER); + size_t for_next = state->num_instr; + append_instr(state, OPCODE_NEXT, 0); + + if ((status = close_construct(scanner, error, "for")) != DONE) + return status; + + state->scope_stack[state->scope_depth].type = SCOPE_FOR; + state->scope_stack[state->scope_depth].for_child_label = child_label; + state->scope_stack[state->scope_depth].for_index_label = index_label; + state->scope_stack[state->scope_depth].for_next = for_next; + state->scope_depth++; + return DONE; +} + +static void +resolve_scope(compile_state_t *state) +{ + assert(state->scope_depth > 0); + + scope_t *scope = state->scope_stack + + state->scope_depth - 1; + + switch (scope->type) { + + // Useful for all cases but it changes + // meaning for each one. + tinytemplate_instr_t *instr; + + case SCOPE_FOR: + append_instr(state, OPCODE_JUMP, scope->for_next); + instr = state->program + scope->for_next; + instr->operands[0].as_size = state->num_instr; + break; + + case SCOPE_IF: + instr = state->program + scope->if_jcnd; + instr->operands[0].as_size = state->num_instr; + break; + + case SCOPE_IF_ELSE: + instr = state->program + scope->if_jump; + instr->operands[0].as_size = state->num_instr; + break; + } + + state->scope_depth--; +} + +static status_t +construct_end(scanner_t *scanner, + compile_state_t *state, + error_t *error) +{ + if (state->scope_depth == 0) { + report(error, "Orphan {%% end %%} block"); + return ESEMANT; + } + + status_t status = close_construct(scanner, error, "end"); + if (status != DONE) + return status; + + resolve_scope(state); + return DONE; +} + +static status_t +construct_else(scanner_t *scanner, + compile_state_t *state, + error_t *error) +{ + if (state->scope_depth == 0) { + report(error, "Orphan {%% else %%} block"); + return ESEMANT; + } + + status_t status = close_construct(scanner, error, "else"); + if (status != DONE) + return status; + + scope_t *scope = state->scope_stack + + state->scope_depth - 1; + + switch (scope->type) { + + tinytemplate_instr_t *instr; + + case SCOPE_IF: + scope->if_jump = state->num_instr; + append_instr(state, OPCODE_JUMP, 0); + instr = state->program + scope->if_jcnd; + instr->operands[0].as_size = state->num_instr; + scope->type = SCOPE_IF_ELSE; + break; + + case SCOPE_IF_ELSE: + report(error, "Duplicate {%% else %%} case"); + return ESEMANT; + + case SCOPE_FOR: + report(error, "Bad {%% else %%} coupled with {%% for .. %%}"); + return ESEMANT; + } + + return DONE; +} + +static status_t +control_flow_block(scanner_t *scanner, + compile_state_t *state, + error_t *error) +{ + scanner->cur += 2; // Skip the "{%" + + slice_t slice; + switch (next_token(scanner, &slice, NULL)) { + case TOKEN_KWORD_IF: return selection_construct_start(scanner, state, error); + case TOKEN_KWORD_FOR: return iteration_construct_start(scanner, state, error); + case TOKEN_KWORD_END: return construct_end(scanner, state, error); + case TOKEN_KWORD_ELSE: return construct_else(scanner, state, error); + + default: + report(error, "Bad token [%.*s] after [{%%]", + (int) slice.length, scanner->src + slice.offset); + return ESYNTAX; + } + return DONE; +} + +status_t +tinytemplate_compile(const char *src, size_t len, + instr_t *program, size_t max_instr, + size_t *num_instr, char *errmsg, + size_t errmax) +{ + error_t error = { + .dst=errmsg, + .max=errmax, + }; + + scanner_t scanner = { + .src=src, + .len=len, + .cur=0 + }; + + compile_state_t state = { + .program=program, + .max_instr=max_instr, + .num_instr=0, + .failed=false + }; + + status_t status; + + while (scanner.cur < scanner.len) { + // A program is a sequence of alternating raw text blocks and + // blocks enclosed in "{%" and "%}" or "{{" and "}}". So for + // each iteration we scan through a block of raw text and then + // a "{{ .. }}"/"{% .. %}" block. + + size_t raw_off = scanner.cur; // Start offset of the raw block + while (scanner.cur < scanner.len) { + // Look for a "{" or the end + while (scanner.cur < scanner.len && scanner.src[scanner.cur] != '{') + scanner.cur++; + + // If the end wasn't reached (a "{" was found) + // then exit if the following character is a + // "{" or "%". If it isn't, then skip the first + // "{" and continue scanning for the next "{" + // by starting a new iteration. + if (scanner.cur < scanner.len) { + assert(scanner.src[scanner.cur] == '{'); + if (scanner.cur+1 < scanner.len && (scanner.src[scanner.cur+1] == '{' || scanner.src[scanner.cur+1] == '%')) + break; + scanner.cur++; // Consume the "{" + } + } + size_t raw_len = scanner.cur - raw_off; // Length of the raw block + + if (raw_len > 0) // The raw block isn't empty + append_instr(&state, OPCODE_WRITE, raw_off, raw_len); + + if (scanner.cur < scanner.len) { + assert(scanner.cur+1 < scanner.len && scanner.src[scanner.cur] == '{' && (scanner.src[scanner.cur+1] == '{' || scanner.src[scanner.cur+1] == '%')); + + if (scanner.src[scanner.cur+1] == '{') + status = expr_block(&scanner, &state, &error); + else { + assert(scanner.src[scanner.cur+1] == '%'); + status = control_flow_block(&scanner, &state, &error); + } + if (status != DONE) + return status; + } + } + + // Close all pending scoped that were + // waiting for a {% end %} block. + while (state.scope_depth > 0) + resolve_scope(&state); + + append_instr(&state, OPCODE_DONE); + + if (state.failed) { + report(&error, "Out of template program memory"); + return EMEMORY; + } + if (num_instr) + *num_instr = state.num_instr; + return DONE; +} + +static bool +value_can_be_considered_true(tinytemplate_type_t type, + tinytemplate_value_t value) +{ + switch (type) { + + case TINYTEMPLATE_TYPE_INT: + return value.as_int != 0; + + case TINYTEMPLATE_TYPE_FLOAT: + return value.as_float != 0; + + default: + return false; + } +} + +typedef struct { + tinytemplate_array_t iter; + tinytemplate_value_t child; + tinytemplate_type_t ctype; + size_t next_index; +} iter_state_t; + +status_t +tinytemplate_eval(const char *src, const instr_t *program, void *userp, + tinytemplate_getter_t params, + tinytemplate_callback_t callback, char *errmsg, size_t errmax) +{ + error_t error = {.dst=errmsg, .max=errmax}; + + iter_state_t iter_stack[TINYTEMPLATE_MAX_ITER_DEPTH]; + size_t iter_depth = 0; + + tinytemplate_type_t types[TINYTEMPLATE_MAX_EXPR_DEPTH]; + tinytemplate_value_t stack[TINYTEMPLATE_MAX_EXPR_DEPTH]; + size_t stack_depth = 0; + + bool done = false; + int index = 0; + while (!done) { + const tinytemplate_instr_t *instr = &program[index++]; + switch (instr->opcode) { + case OPCODE_NOPE: /* Do nothing */ break; + case OPCODE_DONE: done = true; break; + + case OPCODE_ITER: + assert(stack_depth > 0); // ITER excepts a value on the stack + // as an iteration target + if (types[stack_depth-1] != TINYTEMPLATE_TYPE_ARRAY) { + report(&error, "Iteration on something other than an array"); + return ETYPE; + } + if (iter_depth == TINYTEMPLATE_MAX_ITER_DEPTH) { + report(&error, "Maximum nested iteration limit reached"); + return EITER; + } + iter_stack[iter_depth].iter = stack[--stack_depth].as_array; + iter_stack[iter_depth].next_index = 0; + iter_depth++; + break; + + case OPCODE_NEXT: + { + assert(iter_depth > 0); // The NEXT instruction can't + // be run outside of an iteration. + + iter_state_t *top_iter = &iter_stack[iter_depth-1]; + + tinytemplate_type_t type; + tinytemplate_value_t value; + if (top_iter->iter.next(top_iter->iter.data, &type, &value)) { + top_iter->child = value; + top_iter->ctype = type; + top_iter->next_index++; + } else { + index = instr->operands[0].as_size; + iter_depth--; + } + break; + } + + case OPCODE_CHLD: + { + if (stack_depth == TINYTEMPLATE_MAX_EXPR_DEPTH) { + report(&error, "Evaluation stack limit reached"); + return EDEPTH; + } + + size_t iter_idx = instr->operands[0].as_size; + + assert(iter_depth > 0); // The CHLD instruction can't + // be run outside of an iteration. + + assert(iter_idx < iter_depth); // The index of the iteration must + // refer to one of the active ones. + + iter_state_t *iter = &iter_stack[iter_depth-iter_idx-1]; + + assert(iter->next_index > 0); // CHLD can only be executed after + // the iteration started. + + stack[stack_depth] = iter->child; + types[stack_depth] = iter->ctype; + stack_depth++; + break; + } + + case OPCODE_IDX: + { + if (stack_depth == TINYTEMPLATE_MAX_EXPR_DEPTH) { + report(&error, "Evaluation stack limit reached"); + return EDEPTH; + } + + size_t iter_idx = instr->operands[0].as_size; + + assert(iter_depth > 0); // The IDX instruction can't + // be run outside of an iteration. + + assert(iter_idx < iter_depth); // The index of the iteration must + // refer to one of the active ones. + + iter_state_t *iter = &iter_stack[iter_depth-iter_idx-1]; + + assert(iter->next_index > 0); // IDX can only be executed after + // the iteration started. + + stack[stack_depth].as_int = iter->next_index-1; + types[stack_depth] = TINYTEMPLATE_TYPE_INT; + stack_depth++; + break; + } + + case OPCODE_GETS: + { + assert(stack_depth > 0); // GETS takes the top of the stack + // and transforms it in one of its + // properties, so the stack can't + // be empty. + + if (types[stack_depth-1] != TINYTEMPLATE_TYPE_DICT) { + report(&error, "Access by string on non-dict value"); + return ETYPE; + } + + size_t offset = instr->operands[0].as_size; + size_t length = instr->operands[1].as_size; + tinytemplate_type_t type; + tinytemplate_value_t value; + if (!stack[stack_depth-1].as_dict.get(stack[stack_depth-1].as_dict.data, src + offset, length, &type, &value)) { + report(&error, "Key %.*s not in dict", (int) length, src + offset); + return ESYMBOL; + } + types[stack_depth-1] = type; + stack[stack_depth-1] = value; + break; + } + + case OPCODE_PUSHI: + if (stack_depth == TINYTEMPLATE_MAX_EXPR_DEPTH) { + report(&error, "Evaluation stack limit reached"); + return EDEPTH; + } + types[stack_depth] = TINYTEMPLATE_TYPE_INT; + stack[stack_depth].as_int = instr->operands[0].as_int; + stack_depth++; + break; + + case OPCODE_PUSHF: + if (stack_depth == TINYTEMPLATE_MAX_EXPR_DEPTH) { + report(&error, "Evaluation stack limit reached"); + return EDEPTH; + } + types[stack_depth] = TINYTEMPLATE_TYPE_FLOAT; + stack[stack_depth].as_float = instr->operands[0].as_float; + stack_depth++; + break; + + case OPCODE_PUSHS: NOT_IMPLEMENTED_YET; break; + + case OPCODE_PUSHV: + { + if (stack_depth == TINYTEMPLATE_MAX_EXPR_DEPTH) { + report(&error, "Evaluation stack limit reached"); + return EDEPTH; + } + size_t varname_offset = instr->operands[0].as_size; + size_t varname_length = instr->operands[1].as_size; + + tinytemplate_type_t type; + tinytemplate_value_t value; + if (params(userp, src + varname_offset, varname_length, &type, &value)) { + types[stack_depth] = type; + stack[stack_depth] = value; + stack_depth++; + } else { + report(&error, "Undefined variable [%.*s]", + (int) varname_length, src + varname_offset); + return ESYMBOL; + } + break; + } + + case OPCODE_JUMP: + index = instr->operands[0].as_size; + break; + + case OPCODE_JCND: + assert(stack_depth > 0); // JCND jumps if the top of the + // stack is true, so the stack + // can't be empty. + if (!value_can_be_considered_true(types[stack_depth-1], stack[stack_depth-1])) + index = instr->operands[0].as_size; + stack_depth--; + break; + + case OPCODE_WRITE: + { + size_t offset = instr->operands[0].as_size; + size_t length = instr->operands[1].as_size; + callback(userp, src + offset, length); + break; + } + + case OPCODE_WRTOP: + { + assert(stack_depth > 0); // WRTOP output the top of the stack, + // so the stack can't be empty. + + switch (types[stack_depth-1]) { + case TINYTEMPLATE_TYPE_INT: + { + char text[128]; + int num = snprintf(text, sizeof(text), "%lld", stack[stack_depth-1].as_int); + assert(num > 0); + callback(userp, text, (size_t) num); + break; + } + + case TINYTEMPLATE_TYPE_FLOAT: + { + char text[128]; + int num = snprintf(text, sizeof(text), "%lf", stack[stack_depth-1].as_float); + assert(num > 0); + callback(userp, text, (size_t) num); + break; + } + + case TINYTEMPLATE_TYPE_STRING: + callback(userp, stack[stack_depth-1].as_string.str, + stack[stack_depth-1].as_string.len); + break; + + default: + report(&error, "Can't write non-primitive value"); + return ETYPE; + } + break; + } + + case OPCODE_POP: + assert(stack_depth > 0); + stack_depth--; + break; + + // This will be useful from here on + #define PAIR(x, y) ((unsigned char) (x) | ((unsigned char) (y) << 8)) + + case OPCODE_NEG: + { + assert(stack_depth > 0); + + switch (types[stack_depth-1]) { + + case TINYTEMPLATE_TYPE_INT: + stack[stack_depth-1].as_int = -stack[stack_depth-1].as_int; + break; + + case TINYTEMPLATE_TYPE_FLOAT: + stack[stack_depth-1].as_float = -stack[stack_depth-1].as_float; + break; + + default: + report(&error, "Negation on non-numeric operands"); + return ETYPE; + } + break; + } + + case OPCODE_ADD: + { + assert(stack_depth >= 2); + + switch PAIR(types[stack_depth-2], types[stack_depth-1]) { + + case PAIR(TINYTEMPLATE_TYPE_INT, + TINYTEMPLATE_TYPE_INT): + { + int64_t op1 = stack[stack_depth-2].as_int; + int64_t op2 = stack[stack_depth-1].as_int; + int64_t res = op1 + op2; + stack_depth -= 2; + types[stack_depth] = TINYTEMPLATE_TYPE_INT; + stack[stack_depth].as_int = res; + stack_depth++; + break; + } + + case PAIR(TINYTEMPLATE_TYPE_INT, + TINYTEMPLATE_TYPE_FLOAT): + { + int64_t op1 = stack[stack_depth-2].as_int; + int64_t op2 = (int64_t) stack[stack_depth-1].as_float; + int64_t res = op1 + op2; + stack_depth -= 2; + types[stack_depth] = TINYTEMPLATE_TYPE_INT; + stack[stack_depth].as_int = res; + stack_depth++; + break; + } + + case PAIR(TINYTEMPLATE_TYPE_FLOAT, + TINYTEMPLATE_TYPE_INT): + { + double op1 = stack[stack_depth-2].as_float; + double op2 = (double) stack[stack_depth-1].as_int; + double res = op1 + op2; + stack_depth -= 2; + types[stack_depth] = TINYTEMPLATE_TYPE_FLOAT; + stack[stack_depth].as_float = res; + stack_depth++; + break; + } + + case PAIR(TINYTEMPLATE_TYPE_FLOAT, + TINYTEMPLATE_TYPE_FLOAT): + { + double op1 = stack[stack_depth-2].as_float; + double op2 = stack[stack_depth-1].as_float; + double res = op1 + op2; + stack_depth -= 2; + types[stack_depth] = TINYTEMPLATE_TYPE_FLOAT; + stack[stack_depth].as_float = res; + stack_depth++; + break; + } + + default: + report(&error, "Addition on non-numeric operands"); + return ETYPE; + } + break; + } + + case OPCODE_SUB: + { + assert(stack_depth >= 2); + + switch PAIR(types[stack_depth-2], types[stack_depth-1]) { + + case PAIR(TINYTEMPLATE_TYPE_INT, + TINYTEMPLATE_TYPE_INT): + { + int64_t op1 = stack[stack_depth-2].as_int; + int64_t op2 = stack[stack_depth-1].as_int; + int64_t res = op1 - op2; + stack_depth -= 2; + types[stack_depth] = TINYTEMPLATE_TYPE_INT; + stack[stack_depth].as_int = res; + stack_depth++; + break; + } + + case PAIR(TINYTEMPLATE_TYPE_INT, + TINYTEMPLATE_TYPE_FLOAT): + { + int64_t op1 = stack[stack_depth-2].as_int; + int64_t op2 = (int64_t) stack[stack_depth-1].as_float; + int64_t res = op1 - op2; + stack_depth -= 2; + types[stack_depth] = TINYTEMPLATE_TYPE_INT; + stack[stack_depth].as_int = res; + stack_depth++; + break; + } + + case PAIR(TINYTEMPLATE_TYPE_FLOAT, + TINYTEMPLATE_TYPE_INT): + { + double op1 = stack[stack_depth-2].as_float; + double op2 = (double) stack[stack_depth-1].as_int; + double res = op1 - op2; + stack_depth -= 2; + types[stack_depth] = TINYTEMPLATE_TYPE_FLOAT; + stack[stack_depth].as_float = res; + stack_depth++; + break; + } + + case PAIR(TINYTEMPLATE_TYPE_FLOAT, + TINYTEMPLATE_TYPE_FLOAT): + { + double op1 = stack[stack_depth-2].as_float; + double op2 = stack[stack_depth-1].as_float; + double res = op1 - op2; + stack_depth -= 2; + types[stack_depth] = TINYTEMPLATE_TYPE_FLOAT; + stack[stack_depth].as_float = res; + stack_depth++; + break; + } + + default: + report(&error, "Subtraction on non-numeric operands"); + return ETYPE; + } + break; + } + + case OPCODE_MUL: + { + assert(stack_depth >= 2); + + switch PAIR(types[stack_depth-2], types[stack_depth-1]) { + + case PAIR(TINYTEMPLATE_TYPE_INT, + TINYTEMPLATE_TYPE_INT): + { + int64_t op1 = stack[stack_depth-2].as_int; + int64_t op2 = stack[stack_depth-1].as_int; + int64_t res = op1 * op2; + stack_depth -= 2; + types[stack_depth] = TINYTEMPLATE_TYPE_INT; + stack[stack_depth].as_int = res; + stack_depth++; + break; + } + + case PAIR(TINYTEMPLATE_TYPE_INT, + TINYTEMPLATE_TYPE_FLOAT): + { + int64_t op1 = stack[stack_depth-2].as_int; + int64_t op2 = (int64_t) stack[stack_depth-1].as_float; + int64_t res = op1 * op2; + stack_depth -= 2; + types[stack_depth] = TINYTEMPLATE_TYPE_INT; + stack[stack_depth].as_int = res; + stack_depth++; + break; + } + + case PAIR(TINYTEMPLATE_TYPE_FLOAT, + TINYTEMPLATE_TYPE_INT): + { + double op1 = stack[stack_depth-2].as_float; + double op2 = (double) stack[stack_depth-1].as_int; + double res = op1 * op2; + stack_depth -= 2; + types[stack_depth] = TINYTEMPLATE_TYPE_FLOAT; + stack[stack_depth].as_float = res; + stack_depth++; + break; + } + + case PAIR(TINYTEMPLATE_TYPE_FLOAT, + TINYTEMPLATE_TYPE_FLOAT): + { + double op1 = stack[stack_depth-2].as_float; + double op2 = stack[stack_depth-1].as_float; + double res = op1 * op2; + stack_depth -= 2; + types[stack_depth] = TINYTEMPLATE_TYPE_FLOAT; + stack[stack_depth].as_float = res; + stack_depth++; + break; + } + + default: + report(&error, "Multiplication on non-numeric operands"); + return ETYPE; + } + break; + } + + case OPCODE_DIV: + { + assert(stack_depth >= 2); + + switch PAIR(types[stack_depth-2], types[stack_depth-1]) { + + case PAIR(TINYTEMPLATE_TYPE_INT, + TINYTEMPLATE_TYPE_INT): + { + int64_t op1 = stack[stack_depth-2].as_int; + int64_t op2 = stack[stack_depth-1].as_int; + int64_t res = op1 / op2; + stack_depth -= 2; + types[stack_depth] = TINYTEMPLATE_TYPE_INT; + stack[stack_depth].as_int = res; + stack_depth++; + break; + } + + case PAIR(TINYTEMPLATE_TYPE_INT, + TINYTEMPLATE_TYPE_FLOAT): + { + int64_t op1 = stack[stack_depth-2].as_int; + int64_t op2 = (int64_t) stack[stack_depth-1].as_float; + int64_t res = op1 / op2; + stack_depth -= 2; + types[stack_depth] = TINYTEMPLATE_TYPE_INT; + stack[stack_depth].as_int = res; + stack_depth++; + break; + } + + case PAIR(TINYTEMPLATE_TYPE_FLOAT, + TINYTEMPLATE_TYPE_INT): + { + double op1 = stack[stack_depth-2].as_float; + double op2 = (double) stack[stack_depth-1].as_int; + double res = op1 / op2; + stack_depth -= 2; + types[stack_depth] = TINYTEMPLATE_TYPE_FLOAT; + stack[stack_depth].as_float = res; + stack_depth++; + break; + } + + case PAIR(TINYTEMPLATE_TYPE_FLOAT, + TINYTEMPLATE_TYPE_FLOAT): + { + double op1 = stack[stack_depth-2].as_float; + double op2 = stack[stack_depth-1].as_float; + double res = op1 / op2; + stack_depth -= 2; + types[stack_depth] = TINYTEMPLATE_TYPE_FLOAT; + stack[stack_depth].as_float = res; + stack_depth++; + break; + } + + default: + report(&error, "Division on non-numeric operands"); + return ETYPE; + } + break; + } + + case OPCODE_MOD: + { + assert(stack_depth >= 2); + + switch PAIR(types[stack_depth-2], types[stack_depth-1]) { + + case PAIR(TINYTEMPLATE_TYPE_INT, + TINYTEMPLATE_TYPE_INT): + { + int64_t op1 = stack[stack_depth-2].as_int; + int64_t op2 = stack[stack_depth-1].as_int; + int64_t res = op1 % op2; + stack_depth -= 2; + types[stack_depth] = TINYTEMPLATE_TYPE_INT; + stack[stack_depth].as_int = res; + stack_depth++; + break; + } + + default: + report(&error, "Modulo operator [mod] only works on integer operands"); + return ETYPE; + } + break; + } + } + } + return DONE; +} \ No newline at end of file diff --git a/tinytemplate.h b/tinytemplate.h new file mode 100644 index 0000000..54ba5a1 --- /dev/null +++ b/tinytemplate.h @@ -0,0 +1,89 @@ +#ifndef TINYTEMPLATE_H +#define TINYTEMPLATE_H + +#include +#include +#include + +#ifndef TINYTEMPLATE_MAX_SCOPE_DEPTH +#define TINYTEMPLATE_MAX_SCOPE_DEPTH 8 +#endif + +#ifndef TINYTEMPLATE_MAX_EXPR_DEPTH +#define TINYTEMPLATE_MAX_EXPR_DEPTH 8 +#endif + +#ifndef TINYTEMPLATE_MAX_ITER_DEPTH +#define TINYTEMPLATE_MAX_ITER_DEPTH 8 +#endif + +typedef enum { + TINYTEMPLATE_TYPE_INT, + TINYTEMPLATE_TYPE_FLOAT, + TINYTEMPLATE_TYPE_DICT, + TINYTEMPLATE_TYPE_ARRAY, + TINYTEMPLATE_TYPE_STRING, +} tinytemplate_type_t; + +typedef union tinytemplate_value_t tinytemplate_value_t; + +typedef bool (*tinytemplate_nextcallback_t)(void*, tinytemplate_type_t*, tinytemplate_value_t*); +typedef bool (*tinytemplate_getter_t)(void*, const char*, size_t, tinytemplate_type_t*, tinytemplate_value_t*); +typedef void (*tinytemplate_callback_t)(void *userp, const char *str, size_t len); + +typedef struct { + void *data; + tinytemplate_getter_t get; +} tinytemplate_dict_t; + +typedef struct { + void *data; + tinytemplate_nextcallback_t next; +} tinytemplate_array_t; + +typedef struct { + const char *str; + size_t len; +} tinytemplate_string_t; + +union tinytemplate_value_t { + int64_t as_int; + double as_float; + tinytemplate_dict_t as_dict; + tinytemplate_array_t as_array; + tinytemplate_string_t as_string; +}; + +typedef struct { + int opcode; + struct { + int64_t as_int; + size_t as_size; + double as_float; + } operands[2]; +} tinytemplate_instr_t; + +typedef enum { + TINYTEMPLATE_STATUS_DONE, + TINYTEMPLATE_STATUS_ESYMBOL, + TINYTEMPLATE_STATUS_ESCOPE, + TINYTEMPLATE_STATUS_EDEPTH, + TINYTEMPLATE_STATUS_ETYPE, + TINYTEMPLATE_STATUS_EITER, + TINYTEMPLATE_STATUS_EMEMORY, + TINYTEMPLATE_STATUS_ESYNTAX, + TINYTEMPLATE_STATUS_ESEMANT, +} tinytemplate_status_t; + +tinytemplate_status_t +tinytemplate_eval(const char *src, const tinytemplate_instr_t *program, + void *userp, tinytemplate_getter_t params, + tinytemplate_callback_t callback, + char *errmsg, size_t errmax); + +tinytemplate_status_t +tinytemplate_compile(const char *src, size_t len, + tinytemplate_instr_t *program, + size_t max_instr, size_t *num_instr, + char *errmsg, size_t errmax); +#endif diff --git a/tt b/tt new file mode 100644 index 0000000000000000000000000000000000000000..e526c01484e9112427a79a6bbae5624289c75966 GIT binary patch literal 105296 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 J3Pfd%{||T_ub%(_ literal 0 HcmV?d00001 diff --git a/tt.exe b/tt.exe new file mode 100644 index 0000000000000000000000000000000000000000..11476337b2598401f2fb9652f74e6dc12c65fc31 GIT binary patch literal 160242 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

%lM2=qvaxB2L7WmYl+&8MJ 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 +#include +#include +#include +#include +#include +#include +#include "xjson.h" + +#define XJ_MAX_DEPTH 128 +#define XJ_MAX_EXPNT 10 + +typedef struct chunk_t chunk_t; + +/* Symbol: + * chunk_t + * + * Description: + * This is the structure that implements a pool of + * an [xj_alloc] allocator. It's used for both the + * main pool and any extension pool. It's basically + * just a chunk of memory with a pointer before it + * to make a linked list of chunks. + * + * Fields: + * prev: Pointer to the previously allocated chunk. + * + * body: The actual chunk of memory. This hold the + * memory allocations. It's important to make + * sure that this field is properly aligned + * so that the first allocation is also aligned. + */ +struct chunk_t { + chunk_t *prev; + _Alignas(void*) char body[]; +}; + +/* Symbol: + * xj_alloc + * + * Description: + * This is the structure that holds the state of a + * bump-pointer allocator. + * + * A bump-pointer allocator is the simplest form of + * allocation scheme. It's basically a big pool of + * memory that's linearly filled up with allocations. + * Since the allocations may be of different sizes, + * there's no way of freeing previous allocations, + * so all allocations must be freed at the same time + * with the whole pool. + * + * A bump-pointer allocator is good for JSON objects + * because they're made up by lots of nodes with the + * same lifetime. + * + * This implementation allows a dynamic growth of the + * memory it holds by appending extension pools. It's + * both possible to specify the size of the main pool + * and the extension pools on instanciation of the + * allocator (all extension pools will have the same + * size which may be different to the main pool's size). + * + * The first pool is allocated along with the allocator + * object. By using [xj_alloc_using], the user provides + * a memory region that the allocator will use to instanciate + * itself. This memory region must both hold the allocator + * and the first chunk. Since this memory was provided + * by the user, he must also be able to specify a way + * to free the provided chunk that holds allocator and + * pool. + * + * Fields: + * free: An user-provided freeing callback that, if not + * NULL, is called on the allocator pointer (xj_alloc*). + * This is useful when it's the user to provide + * the allocator with memory, by instanciating it + * using [xj_alloc_using]. + * + * tail: The currently used pool. At first this will refer + * to the main pool. When extensions are added, this + * refers to the last extension. + * All chunks are linked together using their [prev] + * pointer in allocation order, therefore the [tail] + * pointer is the tail of the linked list of all chunks. + * + * tail_used: The amount of bytes used of the currently + * used pool (the [tail]). Allocation occur + * by incrementing this offset in the pool. + * + * tail_size: The total size of the tail pool. This is + * equal to the main pool's size when there + * are no extension pools and it's equal to + * the extensions size when there are. + * + * ext_size: The size of an extension pool. + */ +struct xj_alloc { + void (*free)(void*); + chunk_t *tail; + int tail_used; + int tail_size; + int ext_size; +}; + +/* Symbol: + * xj_alloc_new + * + * Description: + * Instanciate an allocator. + * + * Arguments: + * size: The size of the main memory pool. + * + * ext: The size of the pools allocated if the + * main pool isn't enough. By specifying 0, + * you're telling the allocator to only use + * the main pool and fail if it's not enough. + * + * Returns: + * The pointer to an allocator instance if all went + * well or NULL. + * + * Notes: + * The returned pointer, if not NULL, must be + * deallocated using [xj_alloc_del]. + */ +xj_alloc *xj_alloc_new(int size, int ext) +{ + assert(size >= 0 && ext >= 0); + + int allocated = sizeof(xj_alloc) + sizeof(chunk_t) + size; + void *temp = malloc(allocated); + + if(temp == NULL) + return NULL; + + return xj_alloc_using(temp, allocated, ext, free); +} + +/* Symbol: + * xj_alloc_using + * + * Description: + * Instanciate an allocator by telling by + * providing it with the main pool's memory. + * + * Arguments: + * mem: The the pointer to the main memory pool. + * It can't be NULL. + * + * size: The size of the region referred by [mem] + * in bytes. It can't be negative. + * + * ext: The size of any extension pool allocated + * if the main pool isn't enough. + * + * free: The freeing routine that needs to be + * called on [mem] when the allocator is + * destroyed using [xj_alloc_del]. This + * is only called on the [mem] pointer and + * not on any additional extension pool. + * + * Returns: + * The pointer to an allocator instance if all went + * well or NULL. + * + * Notes: + * The returned pointer, if not NULL, must be + * deallocated using [xj_alloc_del]. + * + * The [mem] pool is also used to store the allocator's + * header, so if it's not big enough, this function will + * fail. + */ +xj_alloc *xj_alloc_using(void *mem, int size, int ext, void (*free)(void*)) +{ + assert(mem != NULL && size >= 0 && ext >= 0); + + if((unsigned int) size < sizeof(xj_alloc) + sizeof(chunk_t)) + return NULL; + + xj_alloc *alloc = mem; + alloc->free = free; + alloc->tail = (chunk_t*) (alloc + 1); + alloc->tail->prev = NULL; + alloc->tail_used = 0; + alloc->tail_size = size - (sizeof(xj_alloc) + sizeof(chunk_t)); + alloc->ext_size = ext; + return alloc; +} + +/* Symbol: + * xj_alloc_del + * + * Description: + * Free an allocator instance. + */ +void xj_alloc_del(xj_alloc *alloc) +{ + // Free all of the allocator's chunks, + // with exception of the first one, + // which is allocated with the allocator's + // header and must be deallocated with + // the user-provided callback. + chunk_t *curr = alloc->tail; + while(curr->prev != NULL) + { + chunk_t *prev = curr->prev; + free(curr); + curr = prev; + } + + // Free the allocator header and first + // chunk. + if(alloc->free != NULL) + alloc->free(alloc); +} + +/* Symbol: + * next_aligned + * + * Description: + * If the argument is multiple of 8, then + * the argument is returned, else the first + * multiple of 8 higher than the argument is + * returned. + */ +unsigned long long next_aligned(unsigned long long n) +{ + // NOTE: For powers of 2, the modulo operator + // is equivalent to and & operation where + // the right operand if the power of 2 + // minus 1: + // + // x % (2^i) === x & (2^i - 1) + // + // usually & are faster than %'s so if it's + // known that the divisor (the right argument) + // is a power of 2, it's preferred to use the + // &. + // + // (n & 7) is equivalent to (n % 8), to it's the + // remainder of the division by 8, therefore an + // unaligned [n] will have a non-zero (n & 7). + // If the [n] is aligned to 8, then we return 8 + // (the case after the :). If there's a remainder + // then we need to find the first aligned offset + // after [n], which can be calculated by removing + // the remainder (n & ~7) and adding 8. + return (n & 7) ? (n & ~7) + 8 : n; +} + +void *xj_bpalloc(xj_alloc *alloc, int size) +{ + assert(size >= 0); + + // Make sure the returned memory is aligned + // to 8 bytes boundaries, which is assumed + // to be the a valid alignment for anything. + alloc->tail_used = next_aligned(alloc->tail_used); + + // If there's not enough memory in the + // current chunk, allocate an extension. + if(alloc->tail_used + size > alloc->tail_size) + { + // When the user instanciated the allocator, + // he specified an extension size of 0, which + // means that he doesn't want the allocator + // to grow. Therefore, we just wen out of + // memory! + if(alloc->ext_size == 0) + return NULL; + + // Either allocate a chunk of the size specified + // by the user during the instanciation of the + // allocator, or a bigger one if the current + // allocation wouldn't fit in it. + int new_chunk_size = alloc->ext_size; + + if(new_chunk_size < size) + new_chunk_size = size; + + chunk_t *chunk = malloc(sizeof(chunk_t) + new_chunk_size); + + if(chunk == NULL) + return NULL; + + chunk->prev = alloc->tail; + alloc->tail = chunk; + alloc->tail_used = 0; + alloc->tail_size = new_chunk_size; + } + + // Do the bump-pointer's bumping of the pointer. + void *addr = alloc->tail->body + alloc->tail_used; + + alloc->tail_used += size; + + return addr; +} + +void xj_preport(xj_error *error, const char *src, int off, const char *fmt, ...) +{ + if(error != NULL) + { + int row = -1, + col = -1; + if(src != NULL) + { + // Calculate column and row given + // the source string and an index + // in it. + assert(off >= 0); + col = 0; + row = 0; + int i = 0; + while(i < off) + { + if(src[i] == '\n') + { + row += 1; + col = 0; + } + else + col += 1; + i += 1; + } + } + + int k; + va_list va; + va_start(va, fmt); + k = vsnprintf(error->message, sizeof(error->message), fmt, va); + va_end(va); + + assert(k >= 0); + + error->truncated = (k >= (int) sizeof(error->message)-1); + error->occurred = 1; + error->off = off; + error->row = row; + error->col = col; + } +} + +// Create an [xj_value] that represents the [null] JSON value. +xj_value *xj_value_null(xj_alloc *alloc, xj_error *error) +{ + xj_value *x = xj_bpalloc(alloc, sizeof(xj_value)); + if(x == NULL) + xj_report(error, "Out of memory"); + else + { + x->type = XJ_NULL; + x->size = -1; + x->next = NULL; + x->key = NULL; + } + return x; +} + +// Create an [xj_value] that represents a boolean value. +xj_value *xj_value_bool(xj_bool val, xj_alloc *alloc, xj_error *error) +{ + xj_value *x = xj_value_null(alloc, error); + if(x != NULL) + { + x->type = XJ_BOOL; + x->as_bool = val; + } + return x; +} + +xj_value *xj_value_int(xj_i64 val, xj_alloc *alloc, xj_error *error) +{ + xj_value *x = xj_value_null(alloc, error); + if(x != NULL) + { + x->type = XJ_INT; + x->as_int = val; + } + return x; +} + +xj_value *xj_value_float(xj_f64 val, xj_alloc *alloc, xj_error *error) +{ + xj_value *x = xj_value_null(alloc, error); + if(x != NULL) + { + x->type = XJ_FLOAT; + x->as_float = val; + } + return x; +} + +xj_value *xj_value_string(const char *str, int len, xj_alloc *alloc, xj_error *error) +{ + if(str == NULL) str = ""; + if(len < 0) len = strlen(str); + + char *copy = xj_strdup(str, len, alloc, error); + + if(copy == NULL) + return NULL; + + xj_value *x = xj_value_null(alloc, error); + if(x != NULL) + { + x->type = XJ_STRING; + x->size = len; + x->as_string = copy; + } + return x; +} + +xj_value *xj_value_array__nocheck(xj_value *head, int count, xj_alloc *alloc, xj_error *error) +{ + if(count < 0) + { + count = 0; + xj_value *curs = head; + while(curs != NULL) + { + count += 1; + curs = curs->next; + } + } + + xj_value *x = xj_value_null(alloc, error); + if(x != NULL) + { + x->type = XJ_ARRAY; + x->size = count; + x->as_array = head; + } + return x; +} + +xj_value *xj_value_array(xj_value *head, xj_alloc *alloc, xj_error *error) +{ + int count = 0; + xj_value *curs = head; + while(curs != NULL) + { + if(curs->key != NULL) + { + /* Array child has a + key associated to it? */ + return NULL; + } + count += 1; + curs = curs->next; + } + + return xj_value_array__nocheck(head, count, alloc, error); +} + +xj_value *xj_value_object__nocheck(xj_value *head, int count, xj_alloc *alloc, xj_error *error) +{ + if(count < 0) + { + count = 0; + xj_value *curs = head; + while(curs != NULL) + { + count += 1; + curs = curs->next; + } + } + + xj_value *x = xj_value_null(alloc, error); + if(x != NULL) + { + x->type = XJ_OBJECT; + x->size = count; + x->as_object = head; + } + return x; +} + +xj_value *xj_value_object(xj_value *head, xj_alloc *alloc, xj_error *error) +{ + int count = 0; + xj_value *curs = head; + while(curs != NULL) + { + if(curs->key == NULL) + { + /* Object child has no + key associated to it! */ + return NULL; + } + + xj_value *curs2 = head; + while(curs2 != curs) + { + if(!strcmp(curs->key, curs2->key)) + { + /* Duplicate key. */ + return NULL; + } + curs2 = curs2->next; + } + + count += 1; + curs = curs->next; + } + + return xj_value_object__nocheck(head, count, alloc, error); +} + +_Bool xj_array_append(xj_value *array, xj_value *child, + xj_error *error) +{ + assert(array != NULL); + + if(child != NULL) + { + if(child->key != NULL) + { + xj_report(error, "Array child can't have a key"); + return 0; + } + + if(child->next != NULL) + { + xj_report(error, "Array child can't be in a list"); + return 0; + } + + // Find the end of the array + xj_value **tail; + + if(array->as_array == NULL) + // The tail is the base node pointer + tail = &array->as_array; + else + { + // Scan the list 'til the end + xj_value *curs = array->as_array; + while(curs->next != NULL) + curs = curs->next; + tail = &curs; + } + + *tail = child; + } + + return 1; +} + +char *xj_strdup(const char *str, int len, xj_alloc *alloc, xj_error *error) +{ + assert(str != NULL); + + if(len < 0) + len = strlen(str); + + char *copy = xj_bpalloc(alloc, len+1); + + if(copy == NULL) + xj_report(error, "Out of memory"); + else + { + memcpy(copy, str, len); + copy[len] = '\0'; + } + return copy; +} + +typedef struct { + const char *str; + int i, len, depth; + xj_alloc *alloc; + xj_error *error; +} context_t; + +/* Symbol: + * xutf8_sequence_from_utf32_codepoint + * + * Description: + * Transform a UTF-32 encoded codepoint to a UTF-8 encoded byte sequence. + * + * Arguments: + * utf8_data: Refers to the location of the UTF-8 sequence of bytes. + * + * nbytes: The maximum number of bytes that can be written to [utf8_data]. + * It can't be negative. + * + * utf32_code: UTF-32 codepoint that needs to be converted. + * + * Returns: + * If [utf32_code] is valid UTF-32 and the provided buffer is big enough, + * the UTF-8 equivalent sequence is stored in [utf8_data]. No more than + * [nbytes] are ever written. If one of those conitions isn't true, -1 is + * returned. + * + * Notes: + * This was taken by the cozis/xUTF8 library on github.com + */ +static int xutf8_sequence_from_utf32_codepoint(char *utf8_data, int nbytes, uint32_t utf32_code) +{ + if(utf32_code < 128) + { + if(nbytes < 1) + return -1; + + utf8_data[0] = utf32_code; + return 1; + } + + if(utf32_code < 2048) + { + if(nbytes < 2) + return -1; + + utf8_data[0] = 0xc0 | (utf32_code >> 6); + utf8_data[1] = 0x80 | (utf32_code & 0x3f); + return 2; + } + + if(utf32_code < 65536) + { + if(nbytes < 3) + return -1; + + utf8_data[0] = 0xe0 | (utf32_code >> 12); + utf8_data[1] = 0x80 | ((utf32_code >> 6) & 0x3f); + utf8_data[2] = 0x80 | (utf32_code & 0x3f); + return 3; + } + + if(utf32_code <= 0x10ffff) + { + if(nbytes < 4) + return -1; + + utf8_data[0] = 0xf0 | (utf32_code >> 18); + utf8_data[1] = 0x80 | ((utf32_code >> 12) & 0x3f); + utf8_data[2] = 0x80 | ((utf32_code >> 6) & 0x3f); + utf8_data[3] = 0x80 | (utf32_code & 0x3f); + return 4; + } + + // Code is out of range for UTF-8. + return -1; +} + +/* Symbol + * xutf8_sequence_to_utf32_codepoint + * + * Description + * Transform a UTF-8 encoded byte sequence pointed by `utf8_data` + * into a UTF-32 encoded codepoint. + * + * Arguments: + * utf8_data: Refers to the location of the UTF-8 byte sequence. + * + * nbytes: The maximum number of bytes that can be read after + * [utf8_data]. It can't be negative. + * + * utf32_code: Location where the encoded UTF-32 code will be stored. + * It may be NULL, in which case the value is evaluated + * and then thrown away. + * + * Returns: + * The codepoint is returned through the output parameter `utf32_code`. + * The returned value is the number of bytes of the UTF-8 sequence that + * were scanned to encode the UTF-32 code, or -1 if the UTF-8 sequence + * is invalid. + * + * Notes: + * By calling this function with a NULL [utf32_code], you can check the + * validity of a UTF-8 sequence. + * + * The [nbytes] argument has no relation to the UTF-8 byte count sequence. + * You may think about this argument as the "raw" string length (the one + * [strlen] whould return if [utf8_data] were zero-terminated). + * +* This was taken by the cozis/xUTF8 library on github.com + */ +static int xutf8_sequence_to_utf32_codepoint(const char *utf8_data, int nbytes, uint32_t *utf32_code) +{ + assert(utf8_data != NULL); + assert(nbytes >= 0); + + uint32_t dummy; + if(utf32_code == NULL) + utf32_code = &dummy; + + if(nbytes == 0) + return -1; + + if(utf8_data[0] & 0x80) + { + // May be UTF-8. + + if((unsigned char) utf8_data[0] >= 0xF0) + { + // 4 bytes. + // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + + if(nbytes < 4) + return -1; + + uint32_t temp + = (((uint32_t) utf8_data[0] & 0x07) << 18) + | (((uint32_t) utf8_data[1] & 0x3f) << 12) + | (((uint32_t) utf8_data[2] & 0x3f) << 6) + | (((uint32_t) utf8_data[3] & 0x3f)); + + if(temp > 0x10ffff) + return -1; + + *utf32_code = temp; + return 4; + } + + if((unsigned char) utf8_data[0] >= 0xE0) + { + // 3 bytes. + // 1110xxxx 10xxxxxx 10xxxxxx + + if(nbytes < 3) + return -1; + + uint32_t temp + = (((uint32_t) utf8_data[0] & 0x0f) << 12) + | (((uint32_t) utf8_data[1] & 0x3f) << 6) + | (((uint32_t) utf8_data[2] & 0x3f)); + + if(temp > 0x10ffff) + return -1; + + *utf32_code = temp; + return 3; + } + + if((unsigned char) utf8_data[0] >= 0xC0) + { + // 2 bytes. + // 110xxxxx 10xxxxxx + + if(nbytes < 2) + return -1; + + *utf32_code + = (((uint32_t) utf8_data[0] & 0x1f) << 6) + | (((uint32_t) utf8_data[1] & 0x3f)); + + assert(*utf32_code <= 0x10ffff); + return 2; + } + + // 1 byte + // 10xxxxxx + *utf32_code = (uint32_t) utf8_data[0] & 0x3f; + return 1; + } + + // It's ASCII + // 0xxxxxxx + + *utf32_code = (uint32_t) utf8_data[0]; + return 1; +} + +static _Bool parse_XXXX_after_u(context_t *ctx, uint16_t *res) +{ + const char *bytes = ctx->str + ctx->i; + + if(ctx->i+3 >= ctx->len + || !isxdigit(bytes[0]) || !isxdigit(bytes[1]) + || !isxdigit(bytes[2]) || !isxdigit(bytes[3])) + { + xj_preport(ctx->error, ctx->str, ctx->i, + "The \\u specifier expects 4 hex digits after it"); + return 0; + } + + ctx->i += 4; + + uint16_t rune = 0; + + for(int i = 0; i < 4; i += 1) + { + char c = tolower(bytes[i]); + + if(isdigit(c)) + c = c - '0'; + else + c = c - 'a' + 10; + + rune |= c << ((3 - i) * 4); + } + + if(res) + *res = rune; + + return 1; +} + +typedef struct { + char *buffer; + int size, capacity; + char maybe[256]; +} string_parsing_context_t; + +static _Bool spc_append(string_parsing_context_t *spc, const char *str, int len) +{ + if(spc->size + len > spc->capacity) + { + // Grow the buffer. + + int new_capacity = spc->capacity * 2; + + if(new_capacity < (spc->size + len)) + new_capacity = (spc->size + len); + + char *temp; + + if(spc->maybe == spc->buffer) + { + temp = malloc(new_capacity); + + if(temp == NULL) + return 0; + + memcpy(temp, spc->buffer, spc->size); + } + else + { + temp = realloc(spc->buffer, new_capacity); + + if(temp == NULL) + return 0; + } + + spc->buffer = temp; + spc->capacity = new_capacity; + } + + memcpy(spc->buffer + spc->size, str, len); + spc->size += len; + return 1; +} + +static void spc_free(string_parsing_context_t *spc) +{ + if(spc->maybe != spc->buffer) + free(spc->buffer); +} + +static void *parse_string(context_t *ctx, _Bool raw) +{ + // This is probably the hottest function of the + // parser. JSON documents contain a lot of strings. + // The string is scanned and copied into a temporary + // buffer, then the buffer is transformed into + // the final form that will be returned. + + assert(ctx->i < ctx->len && ctx->str[ctx->i] == '"'); + + string_parsing_context_t spc; + { + spc.buffer = spc.maybe; + spc.size = 0; + spc.capacity = sizeof(spc.maybe); + } + + ctx->i += 1; // Skip '"'. + + while(1) + { + int start = ctx->i; + + while(ctx->i < ctx->len + && ctx->str[ctx->i] != '\\' + && ctx->str[ctx->i] != '"' + && (unsigned char) ctx->str[ctx->i] >= 32 + && (unsigned char) ctx->str[ctx->i] <= 127) + ctx->i += 1; + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside a string value"); + spc_free(&spc); + return NULL; + } + + if((unsigned char) ctx->str[ctx->i] < 32) + { + xj_preport(ctx->error, ctx->str, ctx->i, "String contains control characters"); + spc_free(&spc); + return NULL; + } + + int end = ctx->i; + + if(!spc_append(&spc, ctx->str + start, end - start)) + { + xj_report(ctx->error, "Out of memory"); + spc_free(&spc); + return NULL; + } + + if(ctx->str[ctx->i] == '"') + break; + + if(ctx->str[ctx->i] == '\\') + { + ctx->i += 1; // Skip '\'. + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside a string"); + spc_free(&spc); + return NULL; + } + + uint32_t rune; + int rune_byte_count = xutf8_sequence_to_utf32_codepoint(ctx->str + ctx->i, ctx->len - ctx->i, &rune); + + if(rune == 'u') + { + int start = ctx->i-1; // Points to the '\'. + assert(start >= 0); + + assert(rune_byte_count == 1); + ctx->i += 1; // Skip the 'u'. + + uint16_t first_half; + if(!parse_XXXX_after_u(ctx, &first_half)) + { + spc_free(&spc); + return NULL; + } + + int end = ctx->i; + + _Bool have_2_parts = 0; + uint16_t second_half; + if(ctx->i+1 < ctx->len && ctx->str[ctx->i] == '\\' + && ctx->str[ctx->i+1] == 'u') + { + have_2_parts = 1; + + ctx->i += 2; // Skip the "\u". + + if(!parse_XXXX_after_u(ctx, &second_half)) + { + spc_free(&spc); + return NULL; + } + + end = ctx->i; + } + + uint32_t rune = first_half; + if(have_2_parts) + rune = (rune << 16) | second_half; + + char as_utf8[16]; + int byte_count_as_utf8 = xutf8_sequence_from_utf32_codepoint(as_utf8, sizeof(as_utf8), rune); + if(byte_count_as_utf8 < 0) + { + // Failed to convert to UTF-8. + // Either the rune isn't valid unicode or + // the buffer is too small to hold the + // UTF-8 text. We'll assume the buffer is + // big enough to hold any UTF-8 symbol and + // the error is due to malformed unicode. + + // If the invalid UTF-32 token was invalid + // but composed of two \uXXXX tokens, maybe + // they're valid individually. + + if(have_2_parts == 0) + { + xj_preport(ctx->error, ctx->str, start, "Invalid unicode symbol %.*s", end - start, ctx->str + start); + spc_free(&spc); + return NULL; + } + + rune = first_half; + byte_count_as_utf8 = xutf8_sequence_from_utf32_codepoint(as_utf8, sizeof(as_utf8), rune); + + if(byte_count_as_utf8 < 0) + { + xj_preport(ctx->error, ctx->str, start, "Invalid unicode symbol %.*s", end - start, ctx->str + start); + spc_free(&spc); + return NULL; + } + + if(!spc_append(&spc, as_utf8, byte_count_as_utf8)) + { + xj_report(ctx->error, "Out of memory"); + spc_free(&spc); + return NULL; + } + + rune = second_half; + byte_count_as_utf8 = xutf8_sequence_from_utf32_codepoint(as_utf8, sizeof(as_utf8), rune); + + if(byte_count_as_utf8 < 0) + { + xj_preport(ctx->error, ctx->str, start, "Invalid unicode symbol %.*s", end - start, ctx->str + start); + spc_free(&spc); + return NULL; + } + + if(!spc_append(&spc, as_utf8, byte_count_as_utf8)) + { + xj_report(ctx->error, "Out of memory"); + spc_free(&spc); + return NULL; + } + } + else + { + if(!spc_append(&spc, as_utf8, byte_count_as_utf8)) + { + xj_report(ctx->error, "Out of memory"); + spc_free(&spc); + return NULL; + } + } + } + else + { + const char *s; int l; + switch(rune) + { + case 'n': s = "\n"; l = 1; break; + case 't': s = "\t"; l = 1; break; + case 'b': s = "\b"; l = 1; break; + case 'f': s = "\f"; l = 1; break; + case 'r': s = "\r"; l = 1; break; + default: + s = ctx->str + ctx->i; + l = rune_byte_count; + break; + } + + ctx->i += rune_byte_count; + + if(!spc_append(&spc, s, l)) + { + xj_report(ctx->error, "Out of memory"); + spc_free(&spc); + return NULL; + } + } + } + else + { + assert(!isascii(ctx->str[ctx->i])); + + int n = xutf8_sequence_to_utf32_codepoint(ctx->str + ctx->i, ctx->len - ctx->i, NULL); + if(n < 0) + { + xj_preport(ctx->error, ctx->str, ctx->i, "Invalid UTF-8"); + spc_free(&spc); + return NULL; + } + + assert(n > 0); + + if(!spc_append(&spc, ctx->str + ctx->i, n)) + { + xj_report(ctx->error, "Out of memory"); + spc_free(&spc); + return NULL; + } + + ctx->i += n; + } + } + + ctx->i += 1; // Skip '"'. + + void *p = raw ? (void*) xj_strdup(spc.buffer, spc.size, ctx->alloc, ctx->error) + : (void*) xj_value_string(spc.buffer, spc.size, ctx->alloc, ctx->error); + if(p == NULL) + xj_report(ctx->error, "No memory"); + spc_free(&spc); + return p; +} + +static xj_value *parse_number(context_t *ctx) +{ + assert(ctx->i < ctx->len && (isdigit(ctx->str[ctx->i]) || ctx->str[ctx->i] == '-')); + + _Bool negative = 0; + if(ctx->str[ctx->i] == '-') + { + negative = 1; + + ctx->i += 1; // Skip '-'. + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside after minus sign"); + return NULL; + } + + if(!isdigit(ctx->str[ctx->i])) + { + xj_preport(ctx->error, ctx->str, ctx->i, "Expected a digit after minus sign"); + return NULL; + } + } + + // NOTE: We allow non-0 numbers starting with 0. + + xj_i64 parsed = 0; + + while(ctx->i < ctx->len && isdigit(ctx->str[ctx->i])) + { + if(parsed > (INT64_MAX - ctx->str[ctx->i] + '0') / 10) + { + /* Overflow */ + xj_preport(ctx->error, ctx->str, ctx->i, "Integer would overflow"); + return NULL; + } + + parsed = parsed * 10 + ctx->str[ctx->i] - '0'; + + ctx->i += 1; + } + + xj_bool followed_by_dot = ctx->i+1 < ctx->len && ctx->str[ctx->i] == '.' && isdigit(ctx->str[ctx->i+1]); + + xj_f64 decimal; + + if(followed_by_dot) + { + ctx->i += 1; // Skip '.'. + + xj_f64 f = 1.0; + + decimal = 0; + + while(ctx->i < ctx->len && isdigit(ctx->str[ctx->i])) + { + f /= 10; + decimal += f * (ctx->str[ctx->i] - '0'); + ctx->i += 1; + } + } + + _Bool have_exponent = 0; + xj_f64 coeff; + + if(ctx->i < ctx->len && (ctx->str[ctx->i] == 'e' || ctx->str[ctx->i] == 'E')) + { + ctx->i += 1; // Skip 'e'. + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended where an exponent was expected"); + return NULL; + } + + int exponent_start = ctx->i; + + _Bool negative_exponent = 0; + if(ctx->str[ctx->i] == '+' || ctx->str[ctx->i] == '-') + { + if(ctx->str[ctx->i] == '-') + negative_exponent = 1; + + ctx->i += 1; + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended where an exponent was expected"); + return NULL; + } + } + + if(!isdigit(ctx->str[ctx->i])) + { + xj_preport(ctx->error, ctx->str, ctx->i, "Expected digit as exponent"); + return NULL; + } + + have_exponent = 1; + int exponent = 0; + while(ctx->i < ctx->len && isdigit(ctx->str[ctx->i])) + { + exponent = exponent * 10 + ctx->str[ctx->i] - '0'; + ctx->i += 1; + } + + if(exponent > XJ_MAX_EXPNT) + { + xj_preport(ctx->error, ctx->str, exponent_start, "Exponent is too big"); + return NULL; + } + + coeff = 1; + for(int j = 0; j < exponent; j += 1) + coeff *= 10; + + if(negative_exponent) + coeff = -coeff; + } + + xj_value *v; + if(followed_by_dot) + { + xj_f64 r = (xj_f64) parsed + decimal; + + if(negative) + r = -r; + + if(have_exponent) + r = r * coeff; + + v = xj_value_float(r, ctx->alloc, ctx->error); + } + else + { + xj_i64 r = parsed; + + if(negative) + r = -r; + + if(have_exponent) + r = r * coeff; + + v = xj_value_int(r, ctx->alloc, ctx->error); + } + return v; +} + +static xj_value *parse_value(context_t *ctx); + +static xj_value *parse_array(context_t *ctx) +{ + assert(ctx->i < ctx->len && ctx->str[ctx->i] == '['); + + ctx->i += 1; // Skip '['. + + // Skip whitespace. + while(ctx->i < ctx->len && isspace(ctx->str[ctx->i])) + ctx->i += 1; + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside an array, right after the first '['"); + return NULL; + } + + if(ctx->str[ctx->i] == ']') /* Empty array */ + { + ctx->i += 1; // Skip ']'. + return xj_value_array__nocheck(NULL, 0, ctx->alloc, ctx->error); + } + + xj_value *head = NULL; + xj_value **tail = &head; + int count = 0; + + while(1) + { + xj_value *child = parse_value(ctx); + + if(child == NULL) + return NULL; + + // Skip whitespace. + while(ctx->i < ctx->len && isspace(ctx->str[ctx->i])) + ctx->i += 1; + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside an array, right after the %dth child", count+1); + return NULL; + } + + *tail = child; + tail = &child->next; + count += 1; + + if(ctx->str[ctx->i] == ']') + break; + + if(ctx->str[ctx->i] != ',') + { + xj_preport(ctx->error, ctx->str, ctx->i, "Bad character '%c' inside of an array", ctx->str[ctx->i]); + return NULL; + } + + ctx->i += 1; // Skip ','. + + // Skip whitespace. + while(ctx->i < ctx->len && isspace(ctx->str[ctx->i])) + ctx->i += 1; + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside an array, right after the ',' after the %dth child", count+1); + return NULL; + } + } + + ctx->i += 1; // Skip ']'. + + return xj_value_array__nocheck(head, count, ctx->alloc, ctx->error); +} + +static xj_value *parse_object(context_t *ctx) +{ + assert(ctx->i < ctx->len && ctx->str[ctx->i] == '{'); + + ctx->i += 1; // Skip '{'. + + // Skip whitespace. + while(ctx->i < ctx->len && isspace(ctx->str[ctx->i])) + ctx->i += 1; + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside an object, right after the first '{'"); + return NULL; + } + + if(ctx->str[ctx->i] == '}') /* Empty object */ + { + ctx->i += 1; // Skip '}'. + return xj_value_object__nocheck(NULL, 0, ctx->alloc, ctx->error); + } + + xj_value *head = NULL; + xj_value **tail = &head; + int count = 0; + + while(1) + { + if(ctx->str[ctx->i] != '"') + { + xj_preport(ctx->error, ctx->str, ctx->i, "Bad character '%c' where a string was expected"); + return NULL; + } + + char *key = parse_string(ctx, 1); + + if(key == NULL) + return NULL; + + // Skip whitespace before ':'. + while(ctx->i < ctx->len && isspace(ctx->str[ctx->i])) + ctx->i += 1; + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside an object, right after the %dth child's key", count+1); + return NULL; + } + + if(ctx->str[ctx->i] != ':') + { + xj_preport(ctx->error, ctx->str, ctx->i, "Bad character '%c' where ':' was expected"); + return NULL; + } + + ctx->i += 1; // Skip the ':'. + + // Skip whitespace after ':'. + while(ctx->i < ctx->len && isspace(ctx->str[ctx->i])) + ctx->i += 1; + + xj_value *child = parse_value(ctx); + + if(child == NULL) + return NULL; + + // Skip whitespace. + while(ctx->i < ctx->len && isspace(ctx->str[ctx->i])) + ctx->i += 1; + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside an object, right after the %dth child", count+1); + return NULL; + } + + child->key = key; + + *tail = child; + tail = &child->next; + count += 1; + + if(ctx->str[ctx->i] == '}') + break; + + if(ctx->str[ctx->i] != ',') + { + xj_preport(ctx->error, ctx->str, ctx->i, "Bad character '%c' inside of an object", ctx->str[ctx->i]); + return NULL; + } + + ctx->i += 1; // Skip ','. + + // Skip whitespace. + while(ctx->i < ctx->len && isspace(ctx->str[ctx->i])) + ctx->i += 1; + + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended inside an object, right after the ',' after the %dth child", count+1); + return NULL; + } + } + + ctx->i += 1; // Skip '}'. + + return xj_value_object__nocheck(head, count, ctx->alloc, ctx->error); +} + +static xj_value *parse_bool_or_null(context_t *ctx) +{ + static const char kword_null [] = "null"; + static const char kword_true [] = "true"; + static const char kword_false[] = "false"; + const char *kword; + int kwlen; + + char c = ctx->str[ctx->i]; + + if(c == 'n') + { + kword = kword_null; + kwlen = sizeof(kword_null)-1; + } + else if(c == 't') + { + kword = kword_true; + kwlen = sizeof(kword_true)-1; + } + else if(c == 'f') + { + kword = kword_false; + kwlen = sizeof(kword_false)-1; + } + else + { + xj_preport(ctx->error, ctx->str, ctx->i, "Bad character '%c'", c); + return NULL; + } + + if(ctx->i + kwlen <= ctx->len && !strncmp(ctx->str + ctx->i, kword, kwlen)) + { + ctx->i += kwlen; + switch(c) + { + case 'n': return xj_value_null(ctx->alloc, ctx->error); + case 't': return xj_value_bool(1, ctx->alloc, ctx->error); + case 'f': return xj_value_bool(0, ctx->alloc, ctx->error); + } + /* UNREACHABLE */ + } + + if(ctx->i + kwlen > ctx->len) + { + xj_report(ctx->error, "String ended unexpectedly"); + return NULL; + } + + // Get to the character that made the comparison fail + int p = 0; + while(kword[p] == ctx->str[ctx->i+p]) + p += 1; + ctx->i += p; + + xj_preport(ctx->error, ctx->str, ctx->i, + "Bad character '%c'", ctx->str[ctx->i]); + return NULL; +} + +static xj_value *parse_value(context_t *ctx) +{ + if(ctx->i == ctx->len) + { + xj_report(ctx->error, "String ended where a value was expected"); + return NULL; + } + + if(ctx->depth+1 == XJ_MAX_DEPTH) + { + xj_preport(ctx->error, ctx->str, ctx->i, "Maximum depth reached"); + return NULL; + } + ctx->depth += 1; + + assert(!isspace(ctx->str[ctx->i])); + + xj_value *res; + + char c = ctx->str[ctx->i]; + + if(c == '"') + res = parse_string(ctx, 0); + else if(isdigit(c) || c == '-') + res = parse_number(ctx); + else if(c == '[') + res = parse_array(ctx); + else if(c == '{') + res = parse_object(ctx); + else + res = parse_bool_or_null(ctx); + + ctx->depth -= 1; + return res; +} + +/* Symbol: + * xj_decode + * + * Description: + * Transform a JSON UTF-8 string to a tree of [xj_value] nodes. + * + * Arguments: + * str: The string to be parsed. It's doesn't need to be + * zero-terminated. If NULL, an empty string is assumed. + * + * len: The length of [str] (in bytes). If negative, [str] is + * assumed to be zero-terminated and [len] is computed + * using [strlen]. + * + * alloc: The allocator that will be used to store the parsing + * result. It's not optional (can't be NULL). + * + * error: The reference to a caller-allocated [xj_error]. If + * an error occurres (NULL is returned) then this is + * used to provide the caller with useful information + * regarting the failure. It's not required and can be + * NULL. + * + * Returns: + * The pointer to a tree of [xj_value] nodes, or NULL on failure. + * If NULL is returned and an [xj_error] is provided, than it's + * fields are set to provide the caller with extra information + * related to the failure. + * + * Notes: + * The returned objects are deallocated with the whole allocator + * when calling [xj_alloc_del]. + */ +xj_value *xj_decode(const char *str, int len, + xj_alloc *alloc, xj_error *error) +{ + if(str == NULL) + str = ""; + + if(len < 0) + len = strlen(str); + + if(error != NULL) + memset(error, 0, sizeof(xj_error)); + + int i = 0; + + // Skip whitespace + while(i < len && isspace(str[i])) + i += 1; + + if(i == len) + { + xj_report(error, "The string only contains whitespace"); + return NULL; + } + + context_t ctx = { + .str = str, .i = i, .len = len, .depth = 0, + .alloc = alloc, .error = error }; + return parse_value(&ctx); +} + +typedef struct bucket_t bucket_t; + +/* Symbol: + * bucket_t + * + * Description: + * A memory region that linked with other [bucket_t] + * can represent long strings of text. It's a sub-type + * of [bucket_t]. + * + * Notes: + * This is a big structure. + * + * The [body]'s was chosen to be such that the whole + * [bucket_t] is 4kb big, but it's not really necessary. + */ +struct bucket_t { + bucket_t *next; + char body[4096-sizeof(void*)]; +}; + +/* Symbol: + * buffer_t + * + * Description: + * A buffer that can be used to build large strings + * without the degradation of performance that one + * would get by using a plain dinamically growing + * array. + * It's implemented as a linked list of chunks, so + * it grows by adding new chunks, without the need + * to move the old chunks. + * + * Fields: + * size: The absolute string size (in bytes) that is + * contained in the buffer. When the buffer is + * serialized, the resulting string will have + * this size. + * + * used: The amount of bytes held by the last chunk. + * + * tail: The pointer to the last chunk. + * + * head: The first chunk of the buffer. It's not a + * pointer because it's pre-allocated with + * the [buffer_t]. + * + * Notes: + * The fact that the first chunk comes preallocated with + * the buffer makes it a large structure. A [bucket_t] is + * around 4kb, so a buffer will be bigger than that. + * + * The [head] is the last field so that the other fields + * are contiguous in memory. If [head] were between other + * fields, then there would be a 4kb distance between them. + */ +typedef struct { + int size, used; + bucket_t *tail, head; +} buffer_t; + +/* Symbol: + * buffer_append + * + * Description: + * Appends a string to a [buffer_t]. + * + * Returns: + * 1 if all went well or 0 if an error occurred. + */ +static xj_bool buffer_append(buffer_t *buff, const char *str, int len) +{ + assert(str != NULL && len >= 0); + + // If there's not enough memory in the tail chunk + // then create a new tail chunk! + + if(buff->used + len > (int) sizeof(buff->tail->body)) + { + // It's not possible to add a string that + // is bigger than a chunk. + if(len > (int) sizeof(buff->tail->body)) + return 0; + + bucket_t *buck = malloc(sizeof(bucket_t)); + + if(buck == NULL) + return 0; + + buck->next = NULL; + buff->tail->next = buck; + buff->tail = buck; + buff->used = 0; + } + + memcpy(buff->tail->body + buff->used, str, len); + buff->used += len; + buff->size += len; + return 1; +} + +/* Symbol: + * encode_string + * + * Description: + * Serializes a string to a [buffer_t] in JSON form. + * + * Returns: + * 1 if all went well or 0 if an error occurred. + */ +static _Bool encode_string(const char *str, int len, buffer_t *buff) +{ + assert(str != NULL && len >= 0); + + if(!buffer_append(buff, "\"", 1)) + return 0; + + int i = 0; + while(1) + { + int start = i; + + while(i < len && str[i] != '"' && str[i] != '\\' + && (unsigned char) str[i] >= 32 + && (unsigned char) str[i] <= 127) + i += 1; + + int end = i; + + if(!buffer_append(buff, str + start, end - start)) + return 0; + + if(i == len) + break; + + if(str[i] == '"') + { + if(!buffer_append(buff, "\\\"", 2)) + return 0; + i += 1; + } + else if(str[i] == '\\') + { + if(!buffer_append(buff, "\\\\", 2)) + return 0; + i += 1; + } + else if((unsigned char) str[i] < 32) + { + char *m = NULL; + switch(str[i]) + { + case '\t': m = "\\t"; break; + case '\n': m = "\\n"; break; + case '\b': m = "\\b"; break; + case '\f': m = "\\f"; break; + case '\r': m = "\\r"; break; + default: + assert(0); + // Unexpected control character. + break; + } + + assert(m != NULL); + + if(!buffer_append(buff, m, 2)) + return 0; + + i += 1; + } + else + { + uint32_t rune; + int scanned = xutf8_sequence_to_utf32_codepoint(str + i, len - i, &rune); + + if(scanned < 0) + { + assert(0); + // Invalid UTF-8 + } + + static const char map[] = "0123456789ABCDEF"; + + char buffer[13]; + int used; + + if((rune >> 16) == 0) + { + used = 6; + buffer[0] = '\\'; + buffer[1] = 'u'; + buffer[2] = map[(rune >> 12) & 0xF]; + buffer[3] = map[(rune >> 8) & 0xF]; + buffer[4] = map[(rune >> 4) & 0xF]; + buffer[5] = map[(rune >> 0) & 0xF]; + buffer[6] = '\0'; + } + else + { + used = 12; + buffer[0] = '\\'; + buffer[1] = 'u'; + buffer[2] = map[(rune >> 28) & 0xF]; + buffer[3] = map[(rune >> 24) & 0xF]; + buffer[4] = map[(rune >> 20) & 0xF]; + buffer[5] = map[(rune >> 16) & 0xF]; + buffer[6] = '\\'; + buffer[7] = 'u'; + buffer[8] = map[(rune >> 12) & 0xF]; + buffer[9] = map[(rune >> 8) & 0xF]; + buffer[10] = map[(rune >> 4) & 0xF]; + buffer[11] = map[(rune >> 0) & 0xF]; + buffer[12] = '\0'; + } + + if(!buffer_append(buff, buffer, used)) + return 0; + + i += scanned; + } + } + + if(!buffer_append(buff, "\"", 1)) + return 0; + + return 1; +} + +/* Symbol: + * encode_value + * + * Description: + * Serializes an [xj_value] to a [buffer_t] + * + * Returns: + * 1 if all went well or 0 if an error occurred. + */ +static _Bool encode_value(xj_value *val, buffer_t *buff) +{ + switch(val == NULL ? XJ_NULL : val->type) + { + case XJ_NULL: + return buffer_append(buff, "null", 4); + + case XJ_BOOL: + return val->as_bool + ? buffer_append(buff, "true", 4) + : buffer_append(buff, "false", 5); + + case XJ_INT: + { + char temp[32]; + int k = snprintf(temp, sizeof(temp), + "%lld", val->as_int); + assert(k >= 0 && k < (int) sizeof(temp)); + if(!buffer_append(buff, temp, k)) + return 0; + return 1; + } + + case XJ_FLOAT: + { + char temp[32]; + int k = snprintf(temp, sizeof(temp), + "%g", val->as_float); + assert(k >= 0 && k < (int) sizeof(temp)); + if(!buffer_append(buff, temp, k)) + return 0; + return 1; + } + + case XJ_ARRAY: + { + if(!buffer_append(buff, "[", 1)) + return 0; + + xj_value *child = val->as_object; + while(child != NULL) + { + if(!encode_value(child, buff)) + return 0; + + child = child->next; + + if(child != NULL) + if(!buffer_append(buff, ", ", 2)) + return 0; + } + + if(!buffer_append(buff, "]", 1)) + return 0; + return 1; + } + + case XJ_OBJECT: + { + if(!buffer_append(buff, "{", 1)) + return 0; + + xj_value *child = val->as_object; + while(child != NULL) + { + if(!encode_string(child->key, strlen(child->key), buff)) + return 0; + + if(!buffer_append(buff, ": ", 2)) + return 0; + + if(!encode_value(child, buff)) + return 0; + + child = child->next; + + if(child != NULL) + if(!buffer_append(buff, ", ", 2)) + return 0; + } + + if(!buffer_append(buff, "}", 1)) + return 0; + return 1; + } + + case XJ_STRING: + return encode_string(val->as_string, val->size, buff); + } + return 0; +} + +/* Symbol: + * xj_encode + * + * Description: + * Transforms an [xj_value] to a string. + * + * Arguments: + * value: The object to be converted to a string. + * + * len: An output argument that returns the length + * of the generated string. It's optional, so + * it can be NULL. + * + * Returns: + * The pointer to a zero-terminated string if all went + * well or NULL. + * + * Notes: + * The returned pointer, if not NULL, must be + * deallocated using [free]. + */ +char *xj_encode(xj_value *value, int *len) +{ + buffer_t buff; + buff.size = 0; + buff.used = 0; + buff.tail = &buff.head; + buff.head.next = NULL; + + _Bool ok = encode_value(value, &buff); + + char *serialized = NULL; + + if(ok) + { + /* Serialize */ + + serialized = malloc(buff.size+1); + + if(serialized != NULL) + { + int copied = 0; + + bucket_t *curs = &buff.head; + while(curs->next != NULL) + { + memcpy(serialized + copied, + curs->body, sizeof(curs->body)); + + copied += sizeof(curs->body); + curs = curs->next; + } + + memcpy(serialized + copied, + curs->body, buff.used); + + serialized[buff.size] = '\0'; + + if(len) + *len = buff.size; + } + } + + /* Free the buffer */ + bucket_t *curs = buff.head.next; + while(curs != NULL) + { + bucket_t *next = curs->next; + free(curs); + curs = next; + } + + return serialized; +} \ No newline at end of file diff --git a/xjson.h b/xjson.h new file mode 100644 index 0000000..85ae1e6 --- /dev/null +++ b/xjson.h @@ -0,0 +1,70 @@ +#ifndef XJSON_H +#define XJSON_H + +typedef double xj_f64; +typedef long long xj_i64; +typedef _Bool xj_bool; + +_Static_assert(sizeof(xj_f64) == 8, "double isn't 8 bytes long"); +_Static_assert(sizeof(xj_i64) == 8, "long long isn't 8 bytes long"); + +enum { + XJ_NULL, + XJ_BOOL, + XJ_INT, + XJ_FLOAT, + XJ_ARRAY, + XJ_OBJECT, + XJ_STRING, +}; + +typedef struct xj_value xj_value; +struct xj_value { + int type; + int size; + xj_value *next; + char *key; + union { + xj_i64 as_int; + xj_bool as_bool; + xj_f64 as_float; + xj_value *as_array; + xj_value *as_object; + char *as_string; + }; +}; + +typedef struct { + xj_bool occurred; + xj_bool truncated; + int off, row, col; + char message[128]; +} xj_error; + +typedef struct xj_alloc xj_alloc; +xj_alloc *xj_alloc_using(void *mem, int size, int ext, void (*free)(void*)); +xj_alloc *xj_alloc_new(int size, int ext); +void xj_alloc_del(xj_alloc *alloc); + +void *xj_bpalloc(xj_alloc *alloc, int size); +void xj_preport(xj_error *error, const char *src, int off, const char *fmt, ...); +#define xj_report(error, fmt, ...) xj_preport(error, NULL, -1, fmt, ## __VA_ARGS__) + +xj_value *xj_value_null(xj_alloc *alloc, xj_error *error); +xj_value *xj_value_bool(xj_bool val, xj_alloc *alloc, xj_error *error); +xj_value *xj_value_int(xj_i64 val, xj_alloc *alloc, xj_error *error); +xj_value *xj_value_float(xj_f64 val, xj_alloc *alloc, xj_error *error); +xj_value *xj_value_array(xj_value *head, xj_alloc *alloc, xj_error *error); +xj_value *xj_value_object(xj_value *head, xj_alloc *alloc, xj_error *error); +xj_value *xj_value_string(const char *str, int len, xj_alloc *alloc, xj_error *error); +xj_value *xj_value_array__nocheck(xj_value *head, int count, xj_alloc *alloc, xj_error *error); +xj_value *xj_value_object__nocheck(xj_value *head, int count, xj_alloc *alloc, xj_error *error); + +_Bool xj_array_append(xj_value *array, xj_value *child, xj_error *error); + +char *xj_strdup(const char *str, int len, xj_alloc *alloc, xj_error *error); + +xj_value *xj_decode(const char *str, int len, xj_alloc *alloc, xj_error *error); +char *xj_encode(xj_value *value, int *len); + +#endif /* XJSON_H */ \ No newline at end of file