// wget http://projects.scratch.mit.edu/internalapi/project/21234602/get/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#ifndef JSMN_PARENT_LINKS
#define JSMN_PARENT_LINKS
#endif
#define JSMN_STRICT
#include "jsmn/jsmn.c"
#define JSMN_CHILD 4
// 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.
// (255 is probably better than 4)
char *typename[] = {
"primitive", "object", "array", "string", "child"
};
typedef struct blockfm
{
char *name;
char *scratchblock;
} blockfm;
blockfm block[] = {
{"append:toList:", "add %s to %m.list\n"},
{"backdrop1", "switch backdrop to %m.backdrop and wait\n"},
{"bounceOffEdge", "if on edge, bounce\n"},
{"broadcast:", "broadcast %m.broadcast\n"},
{"changeGraphicEffect:by:", "change %m.effect effect by %n\n"},
{"changePenHueBy:", "change pen color by %n\n"},
{"changePenShadeBy:", "change pen shade by %n\n"},
{"changePenSizeBy:", "change pen size by %n\n"},
{"changeSizeBy:", "change size by %n\n"},
{"changeTempoBy:", "change tempo by %n\n"},
{"changeVar:by:", "change %m.var by %n\n"},
{"changeVolumeBy:", "change volume by %n\n"},
{"changeXposBy:", "change x by %n\n"},
{"changeYposBy:", "change y by %n\n"},
{"clearPenTrails", "clear\n"},
{"comeToFront", "go to front\n"},
{"createCloneOf", "create clone of %m.spriteOnly\n"},
{"deleteLine:ofList:", "delete %d.listDeleteItem of %m.list\n"},
{"doAsk", "ask %s and wait\n"},
{"doBroadcastAndWait", "broadcast %m.broadcast and wait\n"},
{"doPlaySoundAndWait", "play sound %m.sound until done\n"},
{"doWaitUntil", "wait until %b\n"},
{"drum:duration:elapsed:from:", "play drum %n for %n beats\n"},
{"filterReset", "clear graphic effects\n"},
{"forward:", "move %n steps\n"},
{"glideSecs:toX:y:elapsed:from:", "glide %n secs to x:%n y:%n\n"},
{"goBackByLayers:", "go back %n layers\n"},
{"gotoSpriteOrMouse:", "go to %m.spriteOrMouse\n"},
{"gotoX:y:", "go to x:%n y:%n\n"},
{"heading:", "point in direction %d.direction\n"},
{"hide", "hide\n"},
{"hideAll", "hide all sprites\n"},
{"hideList:", "hide list %m.list\n"},
{"hideVariable:", "hide variable %m.var\n"},
{"insert:at:ofList:", "insert %s at %d.listItem of %m.list\n"},
{"instrument:", "set instrument to %d.instrument\n"},
{"lookLike:", "switch costume to %m.costume\n"},
{"midiInstrument:", "set instrument to %n\n"},
{"nextBackground", "next background\n"},
{"nextCostume", "next costume\n"},
{"nextScene", "next backdrop\n"},
{"noteOn:duration:elapsed:from:", "play note %d.note for %n beats\n"},
{"penColor:", "set pen color to %c\n"},
{"penSize:", "set pen size to %n\n"},
{"playDrum", "play drum %d.drum for %n beats\n"},
{"playSound:", "play sound %m.sound\n"},
{"pointTowards:", "point towards %m.spriteOrMouse\n"},
{"putPenDown", "pen down\n"},
{"putPenUp", "pen up\n"},
{"rest:elapsed:from:", "rest for %n beats\n"},
{"say:", "say %s\n"},
{"say:duration:elapsed:from:", "say %s for %n secs\n"},
{"scrollAlign", "align scene %m.scrollAlign\n"},
{"scrollRight", "scroll right %n\n"},
{"scrollUp", "scroll up %n\n"},
{"setGraphicEffect:to:", "set %m.effect effect to %n\n"},
{"setLine:ofList:to:", "replace item %d.listItem of %m.list with %s\n"},
{"setPenHueTo:", "set pen color to %n\n"},
{"setPenShadeTo:", "set pen shade to %n\n"},
{"setRotationStyle", "set rotation style %m.rotationStyle\n"},
{"setSizeTo:", "set size to %n\\%\n"},
{"setTempoTo:", "set tempo to %n bpm\n"},
{"setVar:to:", "set %m.var to %s\n"},
{"setVideoState", "turn video %m.videoState\n"},
{"setVideoTransparency", "set video transparency to %n\\%\n"},
{"setVolumeTo:", "set volume to %n\\%\n"},
{"show", "show\n"},
{"showBackground:", "switch to background %m.costume\n"},
{"showList:", "show list %m.list\n"},
{"showVariable:", "show variable %m.var\n"},
{"stampCostume", "stamp\n"},
{"startScene", "switch backdrop to %m.backdrop\n"},
{"stopAllSounds", "stop all sounds\n"},
{"think:", "think %s\n"},
{"think:duration:elapsed:from:", "think %s for %n secs\n"},
{"timerReset", "reset timer\n"},
{"turnLeft:", "turn Left %n degrees\n"},
{"turnRight:", "turn Right %n degrees\n"},
{"wait:elapsed:from:", "wait %n secs\n"},
{"xpos:", "set x to %n\n"},
{"ypos:", "set y to %n\n"},
{"doForever", "forever\n%xend\n"},
{"doForeverIf", "forever if %b\n%xend\n"},
{"&", "%b and %b"},
{"|", "%b or %b"},
{"<", "%s < %s"},
{"=", "%s = %s"},
{">", "%s > %s"},
{"not", "not %b"},
{"isLoud", "loud?"},
{"mousePressed", "mouse down?"},
{"color:sees:", "color %c is touching %c?"},
{"keyPressed:", "key %m.key pressed?"},
{"list:contains:", "%m.list contains %s"},
{"touching:", "touching %m.touching?"},
{"touchingColor:", "touching color %c?"},
{"doForLoop", "for each %m.varName in %s\n%xend\n"},
{"doIf", "if %b then\n%xend\n"},
{"doRepeat", "repeat %n\n%xend\n"},
{"doUntil", "repeat until %b\n%xend\n"},
{"doWhile", "while %b\n%xend\n"},
{"doIfElse", "if %b then\n%xelse\n%xend\n"},
{"deleteClone", "delete this clone\n"},
{"doReturn", "stop script\n"},
{"stopAll", "stop all\n"},
{"stopScripts", "stop %m.stop\n"},
{"whenClicked", "\nwhen this sprite clicked\n"},
{"whenCloned", "\nwhen I start as a clone\n"},
{"whenGreenFlag", "\nwhen gf clicked\n"},
{"whenIReceive", "\nwhen I receive %m.broadcast\n"},
{"whenKeyPressed", "\nwhen %m.key key pressed\n"},
{"whenSceneStarts", "\nwhen backdrop switches to %m.backdrop\n"},
{"whenSensorGreaterThan", "\nwhen %m.triggerSensor > %n\n"},
{"%", "%n mod %n"},
{"*", "%n * %n"},
{"+", "%n + %n"},
{"-", "%n - %n"},
{"\\/", "%n / %n"},
{"abs", "abs %n"},
{"answer", "answer"},
{"backgroundIndex", "backdrop #"},
{"computeFunction:of:", "%m.mathOp of %n"},
{"concatenate:with:", "join %s %s"},
{"costumeIndex", "costume #"},
{"distanceTo:", "distance to %m.spriteOrMouse"},
{"getAttribute:of:", "%m.attribute of %m.spriteOrStage"},
{"getLine:ofList:", "item %d.listItem of %m.list"},
{"getUserId", "user id"},
{"getUserName", "username"},
{"heading", "direction"},
{"letter:of:", "letter %n of %s"},
{"lineCountOfList:", "length of %m.list"},
{"mouseX", "mouse x"},
{"mouseY", "mouse y"},
{"randomFrom:to:", "pick random %n to %n"},
{"rounded", "round %n"},
{"scale", "size"},
{"sceneName", "backdrop name"},
{"senseVideoMotion", "video %m.videoMotionType on %m.stageOrThis"},
{"soundLevel", "loudness"},
{"sqrt", "sqrt %n"},
{"stringLength:", "length of %s"},
{"tempo", "tempo"},
{"timeAndDate", "current %m.timeAndDate"},
{"timer", "timer"},
{"timestamp", "days since 2000"},
{"volume", "volume"},
{"xScroll", "x scroll"},
{"xpos", "x position"},
{"yScroll", "y scroll"},
{"ypos", "y position"},
{NULL, NULL}
};
#ifndef TRUE
#define TRUE (0==0)
#define FALSE (0!=0)
#endif
// When I first coded this, I passed all data around as parameters, but since
// this file totally encapsulates the translator anyway, there's no real benefit
// from keeping the code 'pure' - and it's a lot easier to read without a bunch
// of extra parameters to every procedure. Undoubtedly faster too.
static char *js;
#define BIGPROG 1000000
static jsmntok_t token[BIGPROG];
static jsmntok_t tree[BIGPROG];
static int codeindent = 0, lastch = '\n';
#define ARG(n) token[root + 1 + (n)].size
void indent(int n) {while (n-- > 0) fprintf(stdout, " ");}
void codechar(int c) {if (lastch == '\n') indent(codeindent); putchar(lastch = c);}
void codestr(char *s) {while (*s != '\0') codechar(*s++);}
// if you declare token[] 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.
int tokstrcmp(jsmntok_t t, char *s) {return ((strncmp(js + t.start, s, t.end - t.start) == 0) && (strlen(s) == (t.end - t.start)));}
void showtok(int tok) {
int i;
if ((token[tok].type != JSMN_OBJECT) && (token[tok].type != JSMN_ARRAY)) {
for (i = token[tok].start; i < token[tok].end; i++) codechar(js[i]);
}
}
void HEX(int tok)
{
#define hexdigit(i) codechar("0123456789ABCDEF"[i&15])
char *s, dec[256]; // need range check
int i, hexnum;
s = dec;
if ((token[tok].type != JSMN_OBJECT) && (token[tok].type != JSMN_ARRAY)) {
for (i = token[tok].start; i < token[tok].end; i++) *s++ = js[i]; *s = '\0';
hexnum = atoi(dec);
codechar('#');
hexdigit(hexnum >> 20); hexdigit(hexnum >> 16); hexdigit(hexnum >> 12);
hexdigit(hexnum >> 8); hexdigit(hexnum >> 4); hexdigit(hexnum);
}
}
void reorder(jsmntok_t * tree, int tree_max,
int root, int depth, int display)
{
static int tokenptr = 0, treeptr = 0;
int base, children, i;
// 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.
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(4);
}
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(5);
}
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(tree, tree_max, tokenptr, depth + 3, display);
}
}
}
void codegen(int root);
void translate(int root);
void statement(int root)
{
int children, i;
children = token[root].size;
switch ((int)token[root].type) {
case JSMN_CHILD:
statement(ARG(0));
return;
case JSMN_PRIMITIVE:
if (js[token[root].start] == 'f') {
// false - used for empty boolean parameters
} else if (js[token[root].start] == 't') {
// true - not sure if it is used in code
} else if (js[token[root].start] == 'n') {
// null
} else {
showtok(root); // otherwise a number. (starts with 0..9)
}
return;
case JSMN_STRING:
if (tokstrcmp(token[root], "_mouse_")) {
codestr("mouse-pointer"); // hack. Would use an array as for the block names, if there were more of these...
} else {
showtok(root);
}
break;
case JSMN_ARRAY:
if (token[ARG(0)].type == JSMN_ARRAY) { // need to check that this one is correct...
for (i = 0; i < children; i++) {
statement(ARG(i));// possibly more children???
}
break;
}
// TO DO: not handling comments yet.
if (tokstrcmp(token[ARG(0)], "readVariable")) { // Handle this with a block string?
statement(ARG(1));
} else if (tokstrcmp(token[ARG(0)], "procDef")) { // TO DO.
codestr("\ndefine ");
showtok(ARG(1)); // followed by number, boolean or string arguments in subarray ARG(2), encoded as %n %b and %s in ARG(1)
codestr("\n");
} else if (tokstrcmp(token[ARG(0)], "getParam")) { // TO DO.
codestr("(param ");
statement(ARG(1));
codestr(")");
} else if (tokstrcmp(token[ARG(0)], "call")) { // TO DO.
//codestr("CALL ");
statement(ARG(1)); // followed by number, boolean or string arguments in subarray ARG(2), encoded as %n %b and %s in ARG(1)
//translate(ARG(2)); // tweak translate to take two parameters - format string, and start of argument list
// // watch out for the '%x' code which will need fixing...
codestr("\n");
} else {
translate(root);
}
break;
default:
codestr("[statement: did not expect an unknown jsmn type ");
printf("%s", typename[token[root].type]);
codestr(" here.]");
fprintf(stderr, "Error.\n");
exit(__LINE__);
}
}
void translate(int root)
{
int argtype;
int each = 0, nextarg = 1, i, j;
char c, *format;
char menutype[256]; // to do - add range check
int found = FALSE;
int argstr = ARG(0); // to do - define args
do {
if (tokstrcmp(token[argstr], block[each].name)) {
found = TRUE;
format = block[each].scratchblock;
i = j = 0;
for (;;) {
c = format[i];
if (c == '\\') {
codechar(format[++i]);
} else if (c == '%') {
argtype = format[++i];
if (format[i + 1] == '.') {
i += 1;
while (isalpha(format[++i])) {
menutype[j++] = format[i];
}
i -= 1;
menutype[j] = '\0';
(void)menutype;
}
switch (argtype) {
case 'p':
// invented type for procedure parameters
nextarg = ARG(2);
break;
case 'x':
// invented type for nested block of text
codeindent += 1;
statement(ARG(nextarg++));
codeindent -= 1;
break;
// d, m, & c have a drop-down selector
case 'd':
if (token[ARG(nextarg)].type == JSMN_PRIMITIVE) { // [n] or [...string...] or [true] etc
codestr("("); statement(ARG(nextarg++)); codestr(" v)"); // was [] BEING TESTED
} else if (token[ARG(nextarg)].type == JSMN_ARRAY) { // [n] or [...string...] or [true] etc
codestr("("); statement(ARG(nextarg++)); codestr(")");
} else if (token[ARG(nextarg)].type == JSMN_STRING) { // [n] or [...string...] or [true] etc
codestr("("); statement(ARG(nextarg++)); codestr(" v)");
} else {
codestr("{n1:"); codestr(typename[token[ARG(nextarg)].type]); codestr(": "); statement(ARG(nextarg)); codestr("}");
statement(ARG(nextarg++));
}
break;
case 'm':
if (token[ARG(nextarg)].type == JSMN_PRIMITIVE) { // [n] or [...string...] or [true] etc
codestr("["); statement(ARG(nextarg++)); codestr(" v]");
} else if (token[ARG(nextarg)].type == JSMN_STRING) { // [n] or [...string...] or [true] etc
codestr("["); statement(ARG(nextarg++)); codestr(" v]");
} else {
//codestr("{s:"); codestr(typename[token[ARG(nextarg)].type]); codestr(": "); statement(ARG(nextarg)); codestr("}");
codestr("("); statement(ARG(nextarg++)); codestr(")");
//statement(ARG(nextarg++));
}
break;
case 'c': // colour picker or name???
// if the param is a variable, show it (thus) otherwise:
if (token[ARG(nextarg)].type == JSMN_PRIMITIVE) { // [n] or [...string...] or [true] etc
codestr("["); HEX(ARG(nextarg++)); codestr("]");
//} else if (token[ARG(nextarg)].type == JSMN_STRING) { // [n] or [...string...] or [true] etc
//codestr("["); HEX(ARG(nextarg++)); codestr("]");
} else {
codestr("("); statement(ARG(nextarg++)); codestr(")");
}
break;
// string, number, boolean
case 's': // [string] var expr
if (token[ARG(nextarg)].type == JSMN_PRIMITIVE) { // [n] or [...string...] or [true] etc
codestr("["); statement(ARG(nextarg++)); codestr("]");
} else if (token[ARG(nextarg)].type == JSMN_STRING) { // [n] or [...string...] or [true] etc
codestr("["); statement(ARG(nextarg++)); codestr("]");
} else {
//codestr("{s:"); codestr(typename[token[ARG(nextarg)].type]); codestr(": "); statement(ARG(nextarg)); codestr("}");
codestr("("); statement(ARG(nextarg++)); codestr(")");
//statement(ARG(nextarg++));
}
break;
case 'n': // [number] var expr
if (token[ARG(nextarg)].type == JSMN_PRIMITIVE) { // [n] or [...string...] or [true] etc
codestr("("); statement(ARG(nextarg++)); codestr(")"); // was [] BEING TESTED
} else if (token[ARG(nextarg)].type == JSMN_ARRAY) { // [n] or [...string...] or [true] etc
codestr("("); statement(ARG(nextarg++)); codestr(")");
} else if (token[ARG(nextarg)].type == JSMN_STRING) { // [n] or [...string...] or [true] etc
codestr("("); statement(ARG(nextarg++)); codestr(")");
} else {
codestr("{n2:"); codestr(typename[token[ARG(nextarg)].type]); codestr(": "); statement(ARG(nextarg)); codestr("}");
statement(ARG(nextarg++));
}
break;
case 'b': // <boolean>
codestr("<"); statement(ARG(nextarg++)); codestr(">");
break;
default:
fprintf(stderr, "warning: unknown format type %%%c\n", c);
statement(ARG(nextarg++));
}
} else if (c == '\0') {
break;
} else {
codechar(c);
}
i += 1;
}
}
each += 1;
} while (block[each].name != NULL);
if (!found) {
codestr("[% unknown block '");
showtok(ARG(0));
codestr("' %]");
}
}
void codeblock(int root) {int i; for (i = 0; i < token[root].size; i++) statement(ARG(i));}
void section(int root) {codeblock(ARG(2));}
void codegen(int root) {int i; for (i = 0; i < token[root].size; i++) section(ARG(i));}
void tree_walk(int root, int depth, int display)
{
int keyword; // token index
int children, i;
children = token[root].size;
switch (token[root].type) {
case JSMN_OBJECT:
if (display) putchar('{');
for (i = 0; i < children; i++) {
if ((i & 1) == 0) {
if (display) {putchar('\n'); indent(depth + 1);}
keyword = ARG(i);
} else {
if (display) printf(": ");
}
if (i &&
((i & 1) == 1) && (tokstrcmp(token[keyword], "variables") ||
tokstrcmp(token[keyword], "contents") ||
tokstrcmp(token[keyword], "lists") ||
tokstrcmp(token[keyword], "children") ||
tokstrcmp(token[keyword], "sounds") ||
tokstrcmp(token[keyword], "scripts") ||
tokstrcmp(token[keyword], "costumes") ||
tokstrcmp(token[keyword], "scriptComments"))) {
// now that we've added explicit child links using the
// 'reorder' procedure, we can skip the subtree entirely,
// without reqiring to traverse it all in order to step
// over data and reach the following sibling...
if (tokstrcmp(token[keyword], "scripts")) {
fflush(stdout);
codestr("\n[scratchblocks]\n");
codegen(ARG(i));
codestr("[/scratchblocks]\n");
fflush(stdout);
} else if (tokstrcmp(token[keyword], "children")) {
tree_walk(ARG(i), depth, display); // sprites that hang off STAGE
} else {
if (display) fprintf(stdout, " << skipped >> ");
}
} else {
tree_walk(ARG(i), depth + 1, display);
}
if ((i & 1) && ((i + 1) != children)) {
if (display) printf(",");
}
}
if (display) {putchar('\n'); indent(depth); putchar('}');}
break;
case JSMN_ARRAY:
if ((depth == 0) || (depth == 2)) { // list of sprite files
for (i = 0; i < children; i++) tree_walk(ARG(i), depth + 1, display);
} else if (depth == 1) { // list of blocks x y subblock
for (i = 2; i < children; i++) tree_walk(ARG(i), depth + 1, display);
} else if (depth >= 3) { // single statement
fprintf(stderr, "unexpected ARRAY at top level\n");
exit(__LINE__);
}
break;
default:
if (display) showtok(root);
}
}
// On windows, you can remove the memory mapping code and just read the
// file int a malloc()ed array with stdio.
#include <fcntl.h> // open
#include <sys/mman.h> // mmap
#include <unistd.h> // lseek
int main(int argc, char **argv)
{
int i, 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), token, BIGPROG);
if (r <= 0) {
fprintf(stderr, "syntax: %s - parse failed\n", argv[1]);
exit(3);
}
reorder(tree, BIGPROG, 0, 0, FALSE); for (i = 0; i < BIGPROG; i++) token[i] = tree[i];
tree_walk(0, 0, FALSE);
exit(0);
return 0;
}