// wget http://projects.scratch.mit.edu/internalapi/project/21234602/get/ /* to do: top-level of codegen, extract all the separate scripts, remember the comments re x/y location of blocks. Also, how do I attach actual comments to the code? They use some sort of index into some block table, which I might not be able to locate. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> #ifndef JSMN_PARENT_LINKS #define JSMN_PARENT_LINKS #endif #define JSMN_STRICT #include "jsmn.c" #define JSMN_CHILD 255 // I originally added JSMN_CHILD to the enum, but I reverted jsmn to its original code // and added that element here as a #define. Being careful that it is higher than // the elements in the enum in case the author adds any new elements of his own. #define tokstrcmp(js, t, s) ((strncmp((js)+(t).start, (s), (t).end - (t).start) == 0) && (strlen(s) == (t).end - (t).start)) #ifndef TRUE #define TRUE (0==0) #define FALSE (0!=0) #endif void showtok (char *js, jsmntok_t * token, int tok) { int i; if ((token[tok].type != JSMN_OBJECT) && (token[tok].type != JSMN_ARRAY)) { // to do - handle escapes as necessary if (token[tok].type == JSMN_STRING) putchar ('"'); for (i = token[tok].start; i < token[tok].end; i++) putchar (js[i]); if (token[tok].type == JSMN_STRING) putchar ('"'); } } void print_var_name (char *js, jsmntok_t * token, int tok) { int i; if ((token[tok].type != JSMN_OBJECT) && (token[tok].type != JSMN_ARRAY)) { // to do - handle escapes as necessary for (i = token[tok].start; i < token[tok].end; i++) { if (js[i] != '\\') { if (js[i] == ':') { if (i+1 == token[tok].end) { } else { putchar ('_'); } } else { putchar (js[i]); } } } } } void indent (int n) { while (n-- > 0) fprintf (stdout, " "); // better than \t } void reorder (char *js, jsmntok_t * token, jsmntok_t * tree, int tree_max, int root, int depth, int display) { // This makes up for a deficit in the lightweight json parser. The jsmn // code as supplied requires you to walk *all* leaves of the tree in left // to right order whenever you access the json structure. This is not // useful when you want to rearrange the order of blocks etc // so this code takes the serialised tree and inserts explicit branch // nodes, so that you may follow any path down the tree using your // preferred traverse order and omitting any parts that you are not // interested in. Unfortunately I don't see how to add this to the original // code as it builds the tree, so I have retrofitted it by having it copy // from the original tree to a new copy. Somewhat wasteful of space, but // not really an issue for Scratch json. static int tokenptr = 0, treeptr = 0; int base, children, i; tree[treeptr].type = token[root].type; tree[treeptr].start = token[root].start; tree[treeptr].end = token[root].end; tree[treeptr].size = token[root].size; #ifdef JSMN_PARENT_LINKS tree[treeptr].parent = token[root].parent; #endif if (treeptr + 1 >= tree_max) { // we could actually calculate the output size exactly with a dummy // pass through the data, allocate enough space off the heap, and then // copy the result back on top of the original array, followed by // freeing the heap space. Checking of course that the original array // is large enough before mallocing the temp workspace... next version // perhaps... fprintf (stderr, "reorder: the output array is not large enough - %d limit reached\n", tree_max); exit (1); } tokenptr += 1; treeptr += 1; base = treeptr; if (token[root].type == JSMN_OBJECT || token[root].type == JSMN_ARRAY) { children = token[root].size; if (treeptr + 1 + children >= tree_max) { fprintf (stderr, "reorder: the output array is not large enough - %d limit exceeded\n", tree_max); exit (1); } treeptr += children; // reserve extra space for the children for (i = 0; i < children; i++) { tree[base + i].type = JSMN_CHILD; tree[base + i].size = treeptr; reorder (js, token, tree, tree_max, tokenptr, depth + 3, display); } } } void tree_walk (char *js, jsmntok_t * token, int root, int depth, int display) { int keyword; // token index int children, i; // I expect to write several procedures for walking parts of the json tree // - this one just displays the whole tree in the original format (with // some contents elided for brevity since this is for doing a visual check) // (The next one most likely print the script components in C-like // notation.) // So this version is not especially pretty but it'll do as a debug // tool... switch (token[root].type) { case JSMN_OBJECT: children = token[root].size; printf("\n"); if (display) putchar ('{'); for (i = 0; i < children; i++) { if ((i & 1) == 0) { if (display) putchar ('\n'); if (display) indent (depth + 1); keyword = token[root + 1 + i].size; } else { if (display) putchar (':'); if (display) putchar (' '); } // fprintf(stdout, "%d@%d: ", i+1, root+1+i); if (i && ((i & 1) == 1) && (tokstrcmp (js, token[keyword], "variables") || tokstrcmp (js, token[keyword], "contents") || tokstrcmp (js, token[keyword], "lists") || tokstrcmp (js, token[keyword], "sounds") || tokstrcmp (js, token[keyword], "scripts") || tokstrcmp (js, token[keyword], "costumes") )) { // now that we've added explicit child links using the 'reorder' // procedure, we can skip the subtree entirely, without requiring // to traverse it all in order to step over data and reach the // following sibling... if (tokstrcmp (js, token[keyword], "scripts")) { tree_walk (js, token, token[root + 1 + i].size, 0, TRUE); } else { if (display) fprintf (stdout, " << skipped >> "); } } else { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); // these are all JSMN_CHILD } if (i & 1) { if ((i + 1) != children) if (display) printf (","); } } if (display) putchar ('\n'); if (display) indent (depth); if (display) putchar ('}'); break; case JSMN_ARRAY: // if (display) printf ("[DEPTH%0d ", depth); children = token[root].size; if ((depth == 0) || (depth == 2)) { // list of sprite files for (i = 0; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); } } else if (depth == 1) { // list of blocks x y subblock for (i = 2; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); } } else if (depth >= 3) { // single statement if (token[token[root + 1].size].type == JSMN_STRING) { if (tokstrcmp(js, token[token[root + 1].size], "doIf")) { printf("if ("); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf(") {\n"); tree_walk (js, token, token[root + 1 + 2].size, 2, display); printf("}\n"); } else if (tokstrcmp(js, token[token[root + 1].size], "doUntil")) { printf("while (!("); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf(")) {\n"); tree_walk (js, token, token[root + 1 + 2].size, 2, display); printf("}\n"); } else if (tokstrcmp(js, token[token[root + 1].size], "doWaitUntil")) { printf("do { wait_elapsed_from (0.001); } while ("); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf(");\n"); } else if (tokstrcmp(js, token[token[root + 1].size], "doForever")) { printf("for (;;) {"); tree_walk (js, token, token[root + 1 + 1].size, 2, display); printf("}\n"); } else if (tokstrcmp(js, token[token[root + 1].size], "doRepeat")) { printf("{\nint _i;\nfor (_i = 0; _i < "); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf("; _i++) {\n"); tree_walk (js, token, token[root + 1 + 2].size, 2, display); printf("}\n}\n"); } else if (tokstrcmp(js, token[token[root + 1].size], "doIfElse")) { printf("if ("); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf(") {\n"); tree_walk (js, token, token[root + 1 + 2].size, 2, display); printf("} else {\n"); tree_walk (js, token, token[root + 1 + 3].size, 2, display); printf("}\n"); } else if (tokstrcmp(js, token[token[root + 1].size], "<")) { printf("StrLT("); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf(","); tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display); printf(")"); } else if (tokstrcmp(js, token[token[root + 1].size], "=")) { printf("StrEQ("); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf(","); tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display); printf(")"); } else if (tokstrcmp(js, token[token[root + 1].size], ">")) { printf("StrGT("); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf(","); tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display); printf(")"); } else if (tokstrcmp(js, token[token[root + 1].size], "|")) { printf("StrOR("); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf(","); tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display); printf(")"); } else if (tokstrcmp(js, token[token[root + 1].size], "&")) { printf("StrAND("); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf(","); tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display); printf(")"); } else if (tokstrcmp(js, token[token[root + 1].size], "+")) { printf("StrADD("); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf(","); tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display); printf(")"); } else if (tokstrcmp(js, token[token[root + 1].size], "-")) { printf("StrSUB("); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf(","); tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display); printf(")"); } else if (tokstrcmp(js, token[token[root + 1].size], "*")) { printf("StrMUL("); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf(","); tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display); printf(")"); } else if (tokstrcmp(js, token[token[root + 1].size], "\\/")) { printf("StrDIV("); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf(","); tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display); printf(")"); } else { print_var_name (js, token, token[root + 1].size); putchar('('); for (i = 1; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (","); } putchar(')'); } } else { for (i = 0; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (";\n"); } } if (depth == 3) printf (";\n"); } // if (display) putchar (']'); break; default: if (display) { showtok (js, token, root); } } } // if you declare tokens[] on the stack inside main, (as was originally done following the // example in the jsmn package) you'll get a run time error if BIGPROG is much over 400000 // - it's better to be static or allocated off the heap. #define BIGPROG 1000000 static jsmntok_t tokens[BIGPROG]; static jsmntok_t tree[BIGPROG]; int main (int argc, char **argv) { char *js; int r; jsmn_parser p; int json_fd; off_t flen; if (argc != 2) { fprintf (stderr, "syntax: %s file.json\n", argv[1]); exit (1); } json_fd = open (argv[1], O_RDONLY); if (json_fd == -1) { fprintf (stderr, "%s - %s\n", argv[0], strerror (errno)); exit (2); } flen = lseek (json_fd, (off_t) 0L, SEEK_END); js = mmap (NULL, flen, PROT_READ, MAP_SHARED, json_fd, (off_t) 0L); jsmn_init (&p); r = jsmn_parse (&p, js, strlen (js), tokens, BIGPROG); if (r <= 0) { fprintf (stderr, "syntax: %s - parse failed\n", argv[1]); } reorder (js, tokens, tree, BIGPROG, 0, 0, FALSE); printf("#include <Scratc.h>\nint main(int arc, char **argv) {\n"); tree_walk (js, tree, 0, 0, FALSE); printf("}\n"); exit (0); return 0; }