// 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 <ctype.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/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. int my_strncmp(char *left, char *right, int chars) { // fflush(stderr); fflush(stdout); // fprintf(stdout, "/*strncmp(\"%.*s\", \"%.*s\", %d)*/", strlen(left) < chars ? strlen(left) : chars, left, strlen(right) < chars ? strlen(right) : chars, right, chars); // fflush(stderr); fflush(stdout); return strncmp(left, right, chars); } #define tokstrcmp(js, t, s) ((my_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 debug_showtok (char *js, jsmntok_t * token, int tok) { int i,j=0; static char result[1024]; 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 (isalnum(js[i])) { result[j++] = js[i]; } else { //printf ("/*"); if (js[i] == '%' && js[i+1] == 'n') { i += 1; } else if (js[i] != ' ' && js[i] != '#' && js[i] != '?') result[j++] = js[i]; //printf ("*/"); } } } result[j] = '\0'; if (result[0] == '*' && isalpha(result[1]) && isupper(result[1])) { fprintf(stdout, "%s", result+1); } else if (strcmp(result, "char") == 0) { fprintf(stdout, "tchar"); } else fprintf(stdout, "%s", result); } 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 debug_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++) { 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("("); 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("("); 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("("); 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("("); 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("("); 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("("); 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("("); 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("("); 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("("); 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("("); 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(")"); /* ADD HERE */ } else if (tokstrcmp(js, token[token[root + 1].size], "getAttribute:of:")) { tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); } else if (tokstrcmp(js, token[token[root + 1].size], "wait:elapsed:from:")) { //debug_var_name (js, token, token[root + 1].size); printf("fsleep("); for (i = 1; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (","); } printf(")"); } else if (tokstrcmp(js, token[token[root + 1].size], "penSize:")) { //debug_var_name (js, token, token[root + 1].size); printf("linewidth("); for (i = 1; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (","); } printf(")"); } else if (tokstrcmp(js, token[token[root + 1].size], "stringLength:")) { //debug_var_name (js, token, token[root + 1].size); printf("numdigits("); for (i = 1; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (","); } printf(")"); } else if (tokstrcmp(js, token[token[root + 1].size], "lineCountOfList:")) { //debug_var_name (js, token, token[root + 1].size); printf("(sizeof("); for (i = 1; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (","); } printf(")/sizeof("); for (i = 1; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (","); } printf("[0]))"); } else if (tokstrcmp(js, token[token[root + 1].size], "keyPressed:")) { //debug_var_name (js, token, token[root + 1].size); printf("keypressed("); for (i = 1; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (","); } printf(")"); } else if (tokstrcmp(js, token[token[root + 1].size], "penColor:")) { //debug_var_name (js, token, token[root + 1].size); printf("colour("); for (i = 1; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (","); } printf(")"); } else if (tokstrcmp(js, token[token[root + 1].size], "randomFrom:to:")) { //debug_var_name (js, token, token[root + 1].size); printf("pickrandom("); for (i = 1; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (","); } printf(")"); } else if (tokstrcmp(js, token[token[root + 1].size], "gotoX:y:")) { //debug_var_name (js, token, token[root + 1].size); printf("gotoxy("); for (i = 1; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (","); } printf(")"); } else if (tokstrcmp(js, token[token[root + 1].size], "broadcast:")) { //debug_var_name (js, token, token[root + 1].size); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf("("); for (i = 2; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (","); } printf(")"); } else if (tokstrcmp(js, token[token[root + 1].size], "computeFunction:of:")) { //debug_var_name (js, token, token[root + 1].size); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf("("); for (i = 2; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (","); } printf(")"); } else if (tokstrcmp(js, token[token[root + 1].size], "setLine:ofList:to:")) { tree_walk (js, token, token[root + 1 + 2].size, depth + 1, display); printf("["); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf("] = "); for (i = 3; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (","); } printf(";\n"); } else if (tokstrcmp(js, token[token[root + 1].size], "setVar:to:")) { //debug_var_name (js, token, token[root + 1].size); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf(" = "); for (i = 2; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (","); } printf(";\n"); } else if (tokstrcmp(js, token[token[root + 1].size], "changeVar:by:")) { //debug_var_name (js, token, token[root + 1].size); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf(" += "); for (i = 2; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (","); } printf(";\n"); } else if (tokstrcmp(js, token[token[root + 1].size], "getLine:ofList:")) { for (i = 2; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (","); } printf("["); tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); printf("]"); } else if (tokstrcmp(js, token[token[root + 1].size], "call")) { tree_walk (js, token, token[root + 2].size, depth + 1, display); putchar('('); for (i = 2; i < children; i++) { tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); if ((i + 1) != children) printf (","); } printf(");\n"); } else if (tokstrcmp(js, token[token[root + 1].size], "getParam")) { tree_walk (js, token, token[root + 2].size, depth + 1, display); //putchar('('); //for (i = 2; i < children; i++) { // tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); // if ((i + 1) != children) printf (","); //} //printf(");\n"); } else if (tokstrcmp(js, token[token[root + 1].size], "readVariable")) { tree_walk (js, token, token[root + 2].size, depth + 1, display); //putchar('('); //for (i = 2; i < children; i++) { // tree_walk (js, token, token[root + 1 + i].size, depth + 1, display); // if ((i + 1) != children) printf (","); //} //printf(");\n"); } else if (tokstrcmp(js, token[token[root + 1].size], "whenIReceive")) { printf("\n}\nvoid "); tree_walk (js, token, token[root + 2].size, depth + 1, display); printf(" (void) {\n"); //} else if (tokstrcmp(js, token[token[root + 1].size], "whenGreenFlag")) { // printf("\n}\nvoid "); // tree_walk (js, token, token[root + 2].size, depth + 1, display); // printf(" (void) {\n"); } else { debug_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) { if (tokstrcmp(js, token[root], "readVariable")) { //tree_walk (js, token, token[root + 1 + 1].size, depth + 1, display); //printf(" := "); //tree_walk (js, token, token[root + 1 + 2].size, 2, display); //printf(";\n"); } else { debug_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; }