// 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;
}